Ejemplo n.º 1
0
        public void Test20SecurityHandlers()
        {
            string userJWT = "eyJ0eXAiOiJqd3QiLCJhbGciOiJlZDI1NTE5In0.e" +
                             "yJqdGkiOiJFU1VQS1NSNFhGR0pLN0FHUk5ZRjc0STVQNTZHMkFGWER" +
                             "YQ01CUUdHSklKUEVNUVhMSDJBIiwiaWF0IjoxNTQ0MjE3NzU3LCJpc" +
                             "3MiOiJBQ1pTV0JKNFNZSUxLN1FWREVMTzY0VlgzRUZXQjZDWENQTUV" +
                             "CVUtBMzZNSkpRUlBYR0VFUTJXSiIsInN1YiI6IlVBSDQyVUc2UFY1N" +
                             "TJQNVNXTFdUQlAzSDNTNUJIQVZDTzJJRUtFWFVBTkpYUjc1SjYzUlE" +
                             "1V002IiwidHlwZSI6InVzZXIiLCJuYXRzIjp7InB1YiI6e30sInN1Y" +
                             "iI6e319fQ.kCR9Erm9zzux4G6M-V2bp7wKMKgnSNqMBACX05nwePRW" +
                             "Qa37aO_yObbhcJWFGYjo1Ix-oepOkoyVLxOJeuD8Bw";

            string userSeed = "SUAIBDPBAUTWCWBKIO6XHQNINK5FWJW4OHLXC3HQ" +
                              "2KFE4PEJUA44CNHTC4";

            using (NATSServer.CreateWithConfig(Context.Server1.Port, "operator.conf"))
            {
                EventHandler <UserJWTEventArgs> jwtEh = (sender, args) =>
                {
                    //just return a jwt
                    args.JWT = userJWT;
                };

                EventHandler <UserSignatureEventArgs> sigEh = (sender, args) =>
                {
                    // generate a nats key pair from a private key.
                    // NEVER EVER handle a real private key/seed like this.
                    var kp = Nkeys.FromSeed(userSeed);
                    args.SignedNonce = kp.Sign(args.ServerNonce);
                };
                var opts = Context.GetTestOptionsWithDefaultTimeout(Context.Server1.Port);
                opts.SetUserCredentialHandlers(jwtEh, sigEh);
                Context.ConnectionFactory.CreateConnection(opts).Close();
            }
        }
Ejemplo n.º 2
0
 public void TestAuthSuccess()
 {
     using (NATSServer.CreateWithConfig(Context.Server1.Port, "auth.conf"))
     {
         ConnectShouldSucceed($"nats://*****:*****@localhost:{Context.Server1.Port}");
     }
 }
Ejemplo n.º 3
0
        public void Test20SecurityHandlerExceptions()
        {
            bool userThrown = false;
            bool sigThrown  = false;

            using (NATSServer.CreateWithConfig(Context.Server1.Port, "operator.conf"))
            {
                EventHandler <UserJWTEventArgs> jwtEh = (sender, args) =>
                {
                    if (!userThrown)
                    {
                        userThrown = true;
                        throw new Exception("Exception from the user JWT handler.");
                    }
                    args.JWT = "somejwt";
                };

                EventHandler <UserSignatureEventArgs> sigEh = (sender, args) =>
                {
                    sigThrown = true;
                    throw new Exception("Exception from the sig handler.");
                };
                var opts = Context.GetTestOptionsWithDefaultTimeout(Context.Server1.Port);
                opts.SetUserCredentialHandlers(jwtEh, sigEh);

                Assert.Throws <NATSConnectionException>(() => Context.ConnectionFactory.CreateConnection(opts));
                Assert.Throws <NATSConnectionException>(() => Context.ConnectionFactory.CreateConnection(opts));
                Assert.True(userThrown);
                Assert.True(sigThrown);
            }
        }
Ejemplo n.º 4
0
        public void TestTlsSuccessWithCert()
        {
            using (NATSServer srv = NATSServer.CreateWithConfig(Context.Server1.Port, "tls_verify.conf"))
            {
                Options opts = Context.GetTestOptions(Context.Server1.Port);
                opts.Secure = true;
                opts.TLSRemoteCertificationValidationCallback = verifyServerCert;

                // .NET requires the private key and cert in the
                //  same file. 'client.pfx' is generated from:
                //
                // openssl pkcs12 -export -out client.pfx
                //    -inkey client-key.pem -in client-cert.pem
                X509Certificate2 cert = new X509Certificate2(
                    UnitTestUtilities.GetFullCertificatePath("client.pfx"), "password");

                opts.AddCertificate(cert);

                using (IConnection c = Context.ConnectionFactory.CreateConnection(opts))
                {
                    using (ISyncSubscription s = c.SubscribeSync("foo"))
                    {
                        c.Publish("foo", null);
                        c.Flush();
                        Msg m = s.NextMessage();
                    }
                }
            }
        }
Ejemplo n.º 5
0
        public void TestAuthServers()
        {
            Options opts = Context.GetTestOptions();

            opts.NoRandomize = true;
            opts.Servers     = new [] { Context.Server1.Url, Context.Server3.Url };
            opts.Timeout     = 5000;

            using (NATSServer as1 = NATSServer.CreateWithConfig(Context.Server1.Port, "auth.conf"),
                   as2 = NATSServer.CreateWithConfig(Context.Server3.Port, "auth.conf"))
            {
                Assert.ThrowsAny <NATSException>(() => Context.ConnectionFactory.CreateConnection(opts));

                // Test that we can connect to a subsequent correct server.
                var authServers = new[] {
                    Context.Server1.Url,
                    $"nats://*****:*****@localhost:{Context.Server3.Port}"
                };

                opts.Servers = authServers;

                using (IConnection c = Context.ConnectionFactory.CreateConnection(opts))
                {
                    Assert.Equal(authServers[1], c.ConnectedUrl);
                }
            }
        }
Ejemplo n.º 6
0
        public void TestTlsReconnectAuthTimeout()
        {
            AutoResetEvent ev = new AutoResetEvent(false);

            using (NATSServer s1 = NATSServer.CreateWithConfig(Context.Server1.Port, "auth_tls.conf"),
                   s2 = NATSServer.CreateWithConfig(Context.Server2.Port, "auth_tls_timeout.conf"),
                   s3 = NATSServer.CreateWithConfig(Context.Server3.Port, "auth_tls.conf"))
            {
                Options opts = Context.GetTestOptions();
                opts.Secure      = true;
                opts.NoRandomize = true;
                opts.TLSRemoteCertificationValidationCallback = verifyServerCert;

                opts.Servers = new[] {
                    $"nats://*****:*****@localhost:{Context.Server1.Port}",
                    $"nats://*****:*****@localhost:{Context.Server2.Port}",
                    $"nats://*****:*****@localhost:{Context.Server3.Port}"
                };

                opts.ReconnectedEventHandler += (sender, args) =>
                {
                    ev.Set();
                };

                using (Context.ConnectionFactory.CreateConnection(opts))
                {
                    s1.Shutdown();

                    // This should fail over to S2 where an authorization timeout occurs
                    // then successfully reconnect to S3.

                    Assert.True(ev.WaitOne(20000));
                }
            }
        }
Ejemplo n.º 7
0
 public void TestAuthSuccess()
 {
     using (NATSServer s = NATSServer.CreateWithConfig(Context.Server1.Port, "auth.conf"))
     {
         IConnection c = Context.ConnectionFactory.CreateConnection($"nats://*****:*****@localhost:{Context.Server1.Port}");
         c.Close();
     }
 }
Ejemplo n.º 8
0
 public void Test20SecurityHandlerNoSigSet()
 {
     using (NATSServer.CreateWithConfig(Context.Server1.Port, "operator.conf"))
     {
         var opts = Context.GetTestOptionsWithDefaultTimeout(Context.Server1.Port);
         opts.SetUserCredentialHandlers((sender, args) => { args.JWT = "somejwt"; }, (sender, args) => { });
         Assert.Throws <NATSConnectionException>(() => Context.ConnectionFactory.CreateConnection(opts));
     }
 }
Ejemplo n.º 9
0
 public void TestAuthFailure()
 {
     using (NATSServer s = NATSServer.CreateWithConfig(Context.Server1.Port, "auth.conf"))
     {
         connectAndFail($"nats://username@localhost:{Context.Server1.Port}");
         connectAndFail($"nats://*****:*****@localhost:{Context.Server1.Port}");
         connectAndFail(Context.Server1.Url);
         connectAndFail($"nats://*****:*****@localhost:{Context.Server1.Port}");
     }
 }
Ejemplo n.º 10
0
        public void TestNKey()
        {
            using (NATSServer.CreateWithConfig(Context.Server1.Port, "nkey.conf"))
            {
                var opts = Context.GetTestOptionsWithDefaultTimeout(Context.Server1.Port);

                // See nkey.conf
                opts.SetNkey("UCKKTOZV72L3NITTGNOCRDZUI5H632XCT4ZWPJBC2X3VEY72KJUWEZ2Z", "./config/certs/user.nk");
                Context.ConnectionFactory.CreateConnection(opts).Close();
            }
        }
Ejemplo n.º 11
0
        public void TestInvalidNKey()
        {
            using (NATSServer.CreateWithConfig(Context.Server1.Port, "nkey.conf"))
            {
                var opts = Context.GetTestOptionsWithDefaultTimeout(Context.Server1.Port);

                opts.SetNkey("XXKKTOZV72L3NITTGNOCRDZUI5H632XCT4ZWPJBC2X3VEY72KJUWEZ2Z", "./config/certs/user.nk");
                Assert.Throws <NATSConnectionException>(() => Context.ConnectionFactory.CreateConnection(opts).Close());


                Assert.Throws <ArgumentException>(() => opts.SetNkey("", "./config/certs/user.nk"));
                Assert.Throws <ArgumentException>(() => opts.SetNkey("UCKKTOZV72L3NITTGNOCRDZUI5H632XCT4ZWPJBC2X3VEY72KJUWEZ2Z", ""));
            }
        }
Ejemplo n.º 12
0
        public void Test20SecurityFactoryApi()
        {
            using (NATSServer.CreateWithConfig(Context.Server1.Port, "operator.conf"))
            {
                var serverUrl = Context.Server1.Url;
                Context.ConnectionFactory.CreateConnection(serverUrl, "./config/certs/test.creds").Close();
                Context.ConnectionFactory.CreateConnection(serverUrl, "./config/certs/test.creds", "./config/certs/test.creds").Close();

                Assert.Throws <ArgumentException>(() => Context.ConnectionFactory.CreateConnection(serverUrl, ""));
                Assert.Throws <ArgumentException>(() => Context.ConnectionFactory.CreateConnection(serverUrl, null));
                Assert.Throws <ArgumentException>(() => Context.ConnectionFactory.CreateConnection(serverUrl, "my.creds", ""));
                Assert.Throws <ArgumentException>(() => Context.ConnectionFactory.CreateConnection(serverUrl, "my.creds", null));
            }
        }
Ejemplo n.º 13
0
        public void TestTlsReconnectAuthTimeoutLateClose()
        {
            AutoResetEvent ev = new AutoResetEvent(false);

            using (NATSServer s1 = NATSServer.CreateWithConfig(Context.Server1.Port, "auth_tls.conf"),
                   s2 = NATSServer.CreateWithConfig(Context.Server2.Port, "auth_tls.conf"))
            {
                Options opts = Context.GetTestOptions();
                opts.Secure      = true;
                opts.NoRandomize = true;
                opts.TLSRemoteCertificationValidationCallback = verifyServerCert;

                opts.Servers = new string[] {
                    $"nats://*****:*****@localhost:{Context.Server1.Port}",
                    $"nats://*****:*****@localhost:{Context.Server2.Port}"
                };

                opts.ReconnectedEventHandler += (sender, args) =>
                {
                    ev.Set();
                };

                using (var c = Context.ConnectionFactory.CreateConnection(opts))
                {
                    // inject an authorization timeout, as if it were processed by an incoming server message.
                    // this is done at the parser level so that parsing is also tested,
                    // therefore it needs reflection since Parser is an internal type.
                    Type parserType = typeof(Connection).Assembly.GetType("NATS.Client.Parser");
                    Assert.NotNull(parserType);

                    BindingFlags flags  = BindingFlags.NonPublic | BindingFlags.Instance;
                    object       parser = Activator.CreateInstance(parserType, flags, null, new object[] { c }, null);
                    Assert.NotNull(parser);

                    MethodInfo parseMethod = parserType.GetMethod("parse", flags);
                    Assert.NotNull(parseMethod);

                    byte[] bytes = "-ERR 'Authorization Timeout'\r\n".ToCharArray().Select(ch => (byte)ch).ToArray();
                    parseMethod.Invoke(parser, new object[] { bytes, bytes.Length });

                    // sleep to allow the client to process the error, then shutdown the server.
                    Thread.Sleep(250);
                    s1.Shutdown();

                    // Wait for a reconnect.
                    Assert.True(ev.WaitOne(20000));
                }
            }
        }
Ejemplo n.º 14
0
        public void TestTlsFailWithInvalidServerCert()
        {
            using (NATSServer srv = NATSServer.CreateWithConfig(Context.Server1.Port, "tls_verify.conf"))
            {
                Options opts = Context.GetTestOptions(Context.Server1.Port);
                opts.Secure = true;
                opts.TLSRemoteCertificationValidationCallback = verifyCertAlwaysFail;

                // this will fail, because it's not complete - missing the private
                // key.
                opts.AddCertificate(UnitTestUtilities.GetFullCertificatePath("client-cert.pem"));

                Assert.ThrowsAny <NATSException>(() => Context.ConnectionFactory.CreateConnection(opts));
            }
        }
Ejemplo n.º 15
0
        public void TestTlsFailWithBadAuth()
        {
            using (NATSServer srv = NATSServer.CreateWithConfig(Context.Server1.Port, "tls_user.conf"))
            {
                Options opts = Context.GetTestOptions(Context.Server1.Port);
                opts.Secure = true;
                opts.Url    = $"nats://*****:*****@localhost:{Context.Server1.Port}";
                opts.TLSRemoteCertificationValidationCallback = verifyServerCert;

                // this will fail, because it's not complete - missing the private
                // key.
                opts.AddCertificate(UnitTestUtilities.GetFullCertificatePath("client-cert.pem"));

                Assert.ThrowsAny <NATSException>(() => Context.ConnectionFactory.CreateConnection(opts));
            }
        }
Ejemplo n.º 16
0
        public void TestTlsReconnect()
        {
            AutoResetEvent ev = new AutoResetEvent(false);

            using (NATSServer
                   srv = NATSServer.CreateWithConfig(Context.Server1.Port, "tls.conf"),
                   srv2 = NATSServer.CreateWithConfig(Context.Server2.Port, "tls.conf"))
            {
                Thread.Sleep(1000);

                Options opts = Context.GetTestOptions();
                opts.Secure      = true;
                opts.NoRandomize = true;
                opts.TLSRemoteCertificationValidationCallback = verifyServerCert;
                opts.Servers = new[] { Context.Server1.Url, Context.Server2.Url };
                opts.ReconnectedEventHandler += (sender, obj) =>
                {
                    ev.Set();
                };

                using (IConnection c = Context.ConnectionFactory.CreateConnection(opts))
                {
                    // send a message to be sure.
                    using (ISyncSubscription s = c.SubscribeSync("foo"))
                    {
                        c.Publish("foo", null);
                        c.Flush();
                        s.NextMessage();

                        // shutdown the server
                        srv.Shutdown();

                        // wait for reconnect
                        Assert.True(ev.WaitOne(30000));

                        c.Publish("foo", null);
                        c.Flush();
                        s.NextMessage();
                    }
                }
            }
        }
Ejemplo n.º 17
0
        public void TestTlsScheme()
        {
            using (NATSServer srv = NATSServer.CreateWithConfig(Context.Server1.Port, "tls.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);
                opts.Url = $"tls://127.0.0.1:{Context.Server1.Port}";
                opts.TLSRemoteCertificationValidationCallback = verifyServerCert;

                using (IConnection c = Context.ConnectionFactory.CreateConnection(opts))
                {
                    using (ISyncSubscription s = c.SubscribeSync("foo"))
                    {
                        c.Publish("foo", null);
                        c.Flush();
                        Msg m = s.NextMessage();
                    }
                }
            }
        }
Ejemplo n.º 18
0
        public void Test20Security()
        {
            IConnection    c  = null;
            AutoResetEvent ev = new AutoResetEvent(false);

            using (NATSServer.CreateWithConfig(Context.Server1.Port, "operator.conf"))
            {
                var opts = Context.GetTestOptionsWithDefaultTimeout(Context.Server1.Port);
                opts.ReconnectedEventHandler += (obj, args) => {
                    ev.Set();
                };
                opts.SetUserCredentials("./config/certs/test.creds");
                c = Context.ConnectionFactory.CreateConnection(opts);
            }

            // effectively bounce the server
            using (NATSServer.CreateWithConfig(Context.Server1.Port, "operator.conf"))
            {
                // wait for reconnect.
                Assert.True(ev.WaitOne(60000));
            }
        }
Ejemplo n.º 19
0
        public void TestCallbackIsPerformedOnAuthFailure()
        {
            var cbEvent = new AutoResetEvent(false);
            var opts    = Context.GetTestOptionsWithDefaultTimeout(Context.Server1.Port);

            opts.Url = $"nats://*****:*****@localhost:{Context.Server1.Port}";

            opts.AsyncErrorEventHandler += (sender, args) =>
            {
                cbEvent.Set();
            };

            using (NATSServer.CreateWithConfig(Context.Server1.Port, "auth.conf"))
            {
                var ex = Assert.Throws <NATSConnectionException>(() =>
                {
                    using (var cn = Context.ConnectionFactory.CreateConnection(opts)) { }
                });
                Assert.Equal("'Authorization Violation'", ex.Message, StringComparer.OrdinalIgnoreCase);
            }

            Assert.True(cbEvent.WaitOne(1000));
        }
Ejemplo n.º 20
0
        public void TestEncodedPassword()
        {
            using (NATSServer.CreateWithConfig(Context.Server1.Port, "encoded_pass.conf"))
            {
                void connectEncoded(string encoded)
                {
                    ConnectShouldSucceed($"nats://u{encoded}:p{encoded}@localhost:{Context.Server1.Port}");
                }

                connectEncoded("space%20space");
                connectEncoded("colon%3Acolon");
                connectEncoded("colon%3acolon"); // just making sure lower case hex
                connectEncoded("quote%27quote");
                connectEncoded("slash%2Fslash");
                connectEncoded("question%3Fquestion");
                connectEncoded("pound%23pound");
                connectEncoded("sqleft%5Bsqleft");
                connectEncoded("sqright%5Dsqright");
                connectEncoded("at%40at");
                connectEncoded("bang%21bang");
                connectEncoded("dollar%24dollar");
                connectEncoded("amp%26amp");
                connectEncoded("comma%2Ccomma");
                connectEncoded("parenleft%28parenleft");
                connectEncoded("parentright%29parentright");
                connectEncoded("asterix%2Aasterix");
                connectEncoded("plus%2Bplus");
                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 <NATSConnectionException>(() => connectEncoded("space+space"));
            }
        }
Ejemplo n.º 21
0
        public void TestExpiredJwt()
        {
            var expiredUserJwt
                = "eyJ0eXAiOiJqd3QiLCJhbGciOiJlZDI1NTE5In0.eyJleHAiOjE1NDg5NzkyMDAs" +
                  "Imp0aSI6IlhURFdZUVc3QldDNzJSR0RaVzNWMlNGQUxFRklCWlRKRkZLWDRTVEpa" +
                  "TVZYWFFBSk01WVEiLCJpYXQiOjE1NzM1NDMyNjYsImlzcyI6IkFBNTVENUw1S0sz" +
                  "WElJNklLSDc0Vk5CUDNTVjNKWUxVQlRKTkxTVEM2NjJKTDZWN0FPWk9GT0NIIiwi" +
                  "bmFtZSI6IlRlc3RVc2VyIiwibmJmIjoxNTQ2MzAwODAwLCJzdWIiOiJVRDZPVUNS" +
                  "T1VEQTZBTTdZMjMySTRLTFVGWU40TTNPWUxJWFhVU0FNTzVQT1RVMkpaVjNVNzY3" +
                  "SiIsInR5cGUiOiJ1c2VyIiwibmF0cyI6eyJwdWIiOnt9LCJzdWIiOnt9fX0.n81V" +
                  "bNLwtYMRYfUDbLgnn0MzFL3imxlEk0PQSzOxQpB_nBkVKvRUtbnd22iS8S9i_HRO" +
                  "FJXfk26xEoOhYtCACg";

            var userSeed = "SUAIBDPBAUTWCWBKIO6XHQNINK5FWJW4OHLXC3HQ2KFE4PEJUA44CNHTC4A";

            using (NATSServer.CreateWithConfig(Context.Server1.Port, "operator.conf"))
            {
                EventHandler <UserJWTEventArgs>       jwtEh = (sender, args) => args.JWT = expiredUserJwt;
                EventHandler <UserSignatureEventArgs> sigEh = (sender, args) =>
                {
                    // generate a nats key pair from a private key.
                    // NEVER EVER handle a real private key/seed like this.
                    var kp = Nkeys.FromSeed(userSeed);
                    args.SignedNonce = kp.Sign(args.ServerNonce);
                };
                var opts = Context.GetTestOptionsWithDefaultTimeout(Context.Server1.Port);
                opts.SetUserCredentialHandlers(jwtEh, sigEh);

                var ex = Assert.Throws <NATSConnectionException>(() =>
                {
                    using (Context.ConnectionFactory.CreateConnection(opts)){ }
                });

                Assert.Equal("'Authorization Violation'", ex.Message, StringComparer.OrdinalIgnoreCase);
            }
        }
Ejemplo n.º 22
0
        public void TestProperFalloutAfterMaxAttemptsWithAuthMismatch()
        {
            Options opts = Context.GetTestOptions();

            Object dmu = new Object();
            Object cmu = new Object();

            opts.Servers = new [] {
                Context.Server8.Url,
                Context.Server1.Url
            };

            opts.NoRandomize   = true;
            opts.MaxReconnect  = 2;
            opts.ReconnectWait = 25; // millis
            opts.Timeout       = 1000;

            bool disconnectHandlerCalled = false;

            opts.DisconnectedEventHandler = (sender, args) =>
            {
                lock (dmu)
                {
                    disconnectHandlerCalled = true;
                    Monitor.Pulse(dmu);
                }
            };

            bool closedHandlerCalled = false;

            opts.ClosedEventHandler = (sender, args) =>
            {
                lock (cmu)
                {
                    closedHandlerCalled = true;
                    Monitor.Pulse(cmu);
                }
            };

            using (NATSServer
                   s1 = NATSServer.Create(Context.Server8.Port),
                   s2 = NATSServer.CreateWithConfig(Context.Server1.Port, "tls_verify.conf"))
            {
                using (IConnection c = Context.ConnectionFactory.CreateConnection(opts))
                {
                    s1.Shutdown();

                    lock (dmu)
                    {
                        if (!disconnectHandlerCalled)
                        {
                            Assert.True(Monitor.Wait(dmu, 20000));
                        }
                    }

                    lock (cmu)
                    {
                        if (!closedHandlerCalled)
                        {
                            Assert.True(Monitor.Wait(cmu, 600000));
                        }
                    }

                    Assert.True(c.Stats.Reconnects != opts.MaxReconnect);

                    Assert.True(disconnectHandlerCalled);
                    Assert.True(closedHandlerCalled);
                    Assert.True(c.IsClosed());
                }
            }
        }
Ejemplo n.º 23
0
        // This test works locally, but fails in AppVeyor some of the time
        // TODO:  Work to identify why this happens...
        public void TestCallbacksOrder()
        {
            bool firstDisconnect = true;

            long orig = DateTime.Now.Ticks;

            long dtime1 = orig;
            long dtime2 = orig;
            long rtime  = orig;
            long atime1 = orig;
            long atime2 = orig;
            long ctime  = orig;

            AutoResetEvent reconnected = new AutoResetEvent(false);
            AutoResetEvent closed      = new AutoResetEvent(false);
            AutoResetEvent asyncErr1   = new AutoResetEvent(false);
            AutoResetEvent asyncErr2   = new AutoResetEvent(false);
            AutoResetEvent recvCh      = new AutoResetEvent(false);
            AutoResetEvent recvCh1     = new AutoResetEvent(false);
            AutoResetEvent recvCh2     = new AutoResetEvent(false);

            using (NATSServer
                   serverAuth = NATSServer.CreateWithConfig(Context.Server1.Port, "auth.conf"),
                   serverNoAuth = NATSServer.CreateFastAndVerify(Context.Server2.Port))
            {
                Options o = Context.GetTestOptions(Context.Server2.Port);

                o.DisconnectedEventHandler += (sender, args) =>
                {
                    Thread.Sleep(100);
                    if (firstDisconnect)
                    {
                        firstDisconnect = false;
                        dtime1          = DateTime.Now.Ticks;
                    }
                    else
                    {
                        dtime2 = DateTime.Now.Ticks;
                    }
                };

                o.ReconnectedEventHandler += (sender, args) =>
                {
                    Thread.Sleep(100);
                    rtime = DateTime.Now.Ticks;
                    reconnected.Set();
                };

                o.AsyncErrorEventHandler += (sender, args) =>
                {
                    Thread.Sleep(100);
                    if (args.Subscription.Subject.Equals("foo"))
                    {
                        atime1 = DateTime.Now.Ticks;
                        asyncErr1.Set();
                    }
                    else
                    {
                        atime2 = DateTime.Now.Ticks;
                        asyncErr2.Set();
                    }
                };

                o.ClosedEventHandler += (sender, args) =>
                {
                    ctime = DateTime.Now.Ticks;
                    closed.Set();
                };

                o.ReconnectWait    = 500;
                o.NoRandomize      = true;
                o.Servers          = new [] { Context.Server2.Url, Context.Server1.Url };
                o.SubChannelLength = 1;

                using (IConnection
                       nc = Context.ConnectionFactory.CreateConnection(o),
                       ncp = Context.OpenConnection(Context.Server1.Port))
                {
                    // On hosted environments, some threads/tasks can start before others
                    // due to resource constraints.  Allow time to start.
                    Thread.Sleep(1000);

                    serverNoAuth.Bounce(1000);

                    Thread.Sleep(1000);

                    Assert.True(reconnected.WaitOne(3000));

                    object asyncLock = new object();
                    EventHandler <MsgHandlerEventArgs> eh = (sender, args) =>
                    {
                        lock (asyncLock)
                        {
                            recvCh.Set();
                            if (args.Message.Subject.Equals("foo"))
                            {
                                recvCh1.Set();
                            }
                            else
                            {
                                recvCh2.Set();
                            }
                        }
                    };

                    IAsyncSubscription sub1 = nc.SubscribeAsync("foo", eh);
                    IAsyncSubscription sub2 = nc.SubscribeAsync("bar", eh);
                    nc.Flush();

                    ncp.Publish("foo", System.Text.Encoding.UTF8.GetBytes("hello"));
                    ncp.Publish("bar", System.Text.Encoding.UTF8.GetBytes("hello"));
                    ncp.Flush();

                    recvCh.WaitOne(3000);

                    for (int i = 0; i < 3; i++)
                    {
                        ncp.Publish("foo", System.Text.Encoding.UTF8.GetBytes("hello"));
                        ncp.Publish("bar", System.Text.Encoding.UTF8.GetBytes("hello"));
                    }

                    ncp.Flush();

                    Assert.True(asyncErr1.WaitOne(3000));
                    Assert.True(asyncErr2.WaitOne(3000));

                    serverNoAuth.Shutdown();

                    Thread.Sleep(1000);
                    closed.Reset();
                    nc.Close();

                    Assert.True(closed.WaitOne(3000));
                }


                if (dtime1 == orig || dtime2 == orig || rtime == orig ||
                    atime1 == orig || atime2 == orig || ctime == orig)
                {
                    Console.WriteLine("Error = callback didn't fire: {0}\n{1}\n{2}\n{3}\n{4}\n{5}\n",
                                      dtime1, dtime2, rtime, atime1, atime2, ctime);
                    throw new Exception("Callback didn't fire.");
                }

                if (rtime < dtime1 || dtime2 < rtime || ctime < atime2)
                {
                    Console.WriteLine("Wrong callback order:\n" +
                                      "dtime1: {0}\n" +
                                      "rtime:  {1}\n" +
                                      "atime1: {2}\n" +
                                      "atime2: {3}\n" +
                                      "dtime2: {4}\n" +
                                      "ctime:  {5}\n",
                                      dtime1, rtime, atime1, atime2, dtime2, ctime);
                    throw new Exception("Invalid callback order.");
                }
            }
        }