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(); } }
public void TestAuthSuccess() { using (NATSServer.CreateWithConfig(Context.Server1.Port, "auth.conf")) { ConnectShouldSucceed($"nats://*****:*****@localhost:{Context.Server1.Port}"); } }
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); } }
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(); } } } }
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); } } }
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)); } } }
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(); } }
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)); } }
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}"); } }
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(); } }
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", "")); } }
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)); } }
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)); } } }
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)); } }
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)); } }
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(); } } } }
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(); } } } }
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)); } }
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)); }
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")); } }
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); } }
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()); } } }
// 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."); } } }