// Copyright 2019-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.

package server

import (
	"bufio"
	"bytes"
	"context"
	"crypto/tls"
	"fmt"
	"math/rand"
	"net"
	"net/url"
	"reflect"
	"strings"
	"sync"
	"sync/atomic"
	"testing"
	"time"

	"github.com/nats-io/nkeys"

	"github.com/klauspost/compress/s2"
	jwt "github.com/nats-io/jwt/v2"
	"github.com/nats-io/nats.go"

	"github.com/nats-io/nats-server/v2/internal/testhelper"
)

type captureLeafNodeRandomIPLogger struct {
	DummyLogger
	ch  chan struct{}
	ips [3]int
}

func (c *captureLeafNodeRandomIPLogger) Debugf(format string, v ...interface{}) {
	msg := fmt.Sprintf(format, v...)
	if strings.Contains(msg, "hostname_to_resolve") {
		ippos := strings.Index(msg, "127.0.0.")
		if ippos != -1 {
			n := int(msg[ippos+8] - '1')
			c.ips[n]++
			for _, v := range c.ips {
				if v < 2 {
					return
				}
			}
			// All IPs got at least some hit, we are done.
			c.ch <- struct{}{}
		}
	}
}

func TestLeafNodeRandomIP(t *testing.T) {
	u, err := url.Parse("nats://hostname_to_resolve:1234")
	if err != nil {
		t.Fatalf("Error parsing: %v", err)
	}

	resolver := &myDummyDNSResolver{ips: []string{"127.0.0.1", "127.0.0.2", "127.0.0.3"}}

	o := DefaultOptions()
	o.Host = "127.0.0.1"
	o.Port = -1
	o.LeafNode.Port = 0
	o.LeafNode.Remotes = []*RemoteLeafOpts{{URLs: []*url.URL{u}}}
	o.LeafNode.ReconnectInterval = 50 * time.Millisecond
	o.LeafNode.resolver = resolver
	o.LeafNode.dialTimeout = 15 * time.Millisecond
	s := RunServer(o)
	defer s.Shutdown()

	l := &captureLeafNodeRandomIPLogger{ch: make(chan struct{})}
	s.SetLogger(l, true, true)

	select {
	case <-l.ch:
	case <-time.After(3 * time.Second):
		t.Fatalf("Does not seem to have used random IPs")
	}
}

func TestLeafNodeRandomRemotes(t *testing.T) {
	// 16! possible permutations.
	orderedURLs := make([]*url.URL, 0, 16)
	for i := 0; i < cap(orderedURLs); i++ {
		orderedURLs = append(orderedURLs, &url.URL{
			Scheme: "nats-leaf",
			Host:   fmt.Sprintf("host%d:7422", i),
		})
	}

	o := DefaultOptions()
	o.LeafNode.Remotes = []*RemoteLeafOpts{
		{NoRandomize: true},
		{NoRandomize: false},
	}
	o.LeafNode.Remotes[0].URLs = make([]*url.URL, cap(orderedURLs))
	copy(o.LeafNode.Remotes[0].URLs, orderedURLs)
	o.LeafNode.Remotes[1].URLs = make([]*url.URL, cap(orderedURLs))
	copy(o.LeafNode.Remotes[1].URLs, orderedURLs)

	s := RunServer(o)
	defer s.Shutdown()

	s.mu.Lock()
	r1 := s.leafRemoteCfgs[0]
	r2 := s.leafRemoteCfgs[1]
	s.mu.Unlock()

	r1.RLock()
	gotOrdered := r1.urls
	r1.RUnlock()
	if got, want := len(gotOrdered), len(orderedURLs); got != want {
		t.Fatalf("Unexpected rem0 len URLs, got %d, want %d", got, want)
	}

	// These should be IN order.
	for i := range orderedURLs {
		if got, want := gotOrdered[i].String(), orderedURLs[i].String(); got != want {
			t.Fatalf("Unexpected ordered url, got %s, want %s", got, want)
		}
	}

	r2.RLock()
	gotRandom := r2.urls
	r2.RUnlock()
	if got, want := len(gotRandom), len(orderedURLs); got != want {
		t.Fatalf("Unexpected rem1 len URLs, got %d, want %d", got, want)
	}

	// These should be OUT of order.
	var random bool
	for i := range orderedURLs {
		if gotRandom[i].String() != orderedURLs[i].String() {
			random = true
			break
		}
	}
	if !random {
		t.Fatal("Expected urls to be random")
	}
}

type testLoopbackResolver struct{}

func (r *testLoopbackResolver) LookupHost(ctx context.Context, host string) ([]string, error) {
	return []string{"127.0.0.1"}, nil
}

func TestLeafNodeTLSWithCerts(t *testing.T) {
	conf1 := createConfFile(t, []byte(`
		port: -1
		leaf {
			listen: "127.0.0.1:-1"
			tls {
				ca_file: "../test/configs/certs/tlsauth/ca.pem"
				cert_file: "../test/configs/certs/tlsauth/server.pem"
				key_file:  "../test/configs/certs/tlsauth/server-key.pem"
				timeout: 2
			}
		}
	`))
	s1, o1 := RunServerWithConfig(conf1)
	defer s1.Shutdown()

	u, err := url.Parse(fmt.Sprintf("nats://localhost:%d", o1.LeafNode.Port))
	if err != nil {
		t.Fatalf("Error parsing url: %v", err)
	}
	conf2 := createConfFile(t, []byte(fmt.Sprintf(`
		port: -1
		leaf {
			remotes [
				{
					url: "%s"
					tls {
						ca_file: "../test/configs/certs/tlsauth/ca.pem"
						cert_file: "../test/configs/certs/tlsauth/client.pem"
						key_file:  "../test/configs/certs/tlsauth/client-key.pem"
						timeout: 2
					}
				}
			]
		}
	`, u.String())))
	o2, err := ProcessConfigFile(conf2)
	if err != nil {
		t.Fatalf("Error processing config file: %v", err)
	}
	o2.NoLog, o2.NoSigs = true, true
	o2.LeafNode.resolver = &testLoopbackResolver{}
	s2 := RunServer(o2)
	defer s2.Shutdown()

	checkFor(t, 3*time.Second, 10*time.Millisecond, func() error {
		if nln := s1.NumLeafNodes(); nln != 1 {
			return fmt.Errorf("Number of leaf nodes is %d", nln)
		}
		return nil
	})
}

func TestLeafNodeTLSRemoteWithNoCerts(t *testing.T) {
	conf1 := createConfFile(t, []byte(`
		port: -1
		leaf {
			listen: "127.0.0.1:-1"
			tls {
				ca_file: "../test/configs/certs/tlsauth/ca.pem"
				cert_file: "../test/configs/certs/tlsauth/server.pem"
				key_file:  "../test/configs/certs/tlsauth/server-key.pem"
				timeout: 2
			}
		}
	`))
	s1, o1 := RunServerWithConfig(conf1)
	defer s1.Shutdown()

	u, err := url.Parse(fmt.Sprintf("nats://localhost:%d", o1.LeafNode.Port))
	if err != nil {
		t.Fatalf("Error parsing url: %v", err)
	}
	conf2 := createConfFile(t, []byte(fmt.Sprintf(`
		port: -1
		leaf {
			remotes [
				{
					url: "%s"
					tls {
						ca_file: "../test/configs/certs/tlsauth/ca.pem"
						timeout: 5
					}
				}
			]
		}
	`, u.String())))
	o2, err := ProcessConfigFile(conf2)
	if err != nil {
		t.Fatalf("Error processing config file: %v", err)
	}

	if len(o2.LeafNode.Remotes) == 0 {
		t.Fatal("Expected at least a single leaf remote")
	}

	var (
		got      float64 = o2.LeafNode.Remotes[0].TLSTimeout
		expected float64 = 5
	)
	if got != expected {
		t.Fatalf("Expected %v, got: %v", expected, got)
	}
	o2.NoLog, o2.NoSigs = true, true
	o2.LeafNode.resolver = &testLoopbackResolver{}
	s2 := RunServer(o2)
	defer s2.Shutdown()

	checkFor(t, 3*time.Second, 10*time.Millisecond, func() error {
		if nln := s1.NumLeafNodes(); nln != 1 {
			return fmt.Errorf("Number of leaf nodes is %d", nln)
		}
		return nil
	})

	// Here we only process options without starting the server
	// and without a root CA for the remote.
	conf3 := createConfFile(t, []byte(fmt.Sprintf(`
		port: -1
		leaf {
			remotes [
				{
					url: "%s"
					tls {
						timeout: 10
					}
				}
			]
		}
	`, u.String())))
	o3, err := ProcessConfigFile(conf3)
	if err != nil {
		t.Fatalf("Error processing config file: %v", err)
	}

	if len(o3.LeafNode.Remotes) == 0 {
		t.Fatal("Expected at least a single leaf remote")
	}
	got = o3.LeafNode.Remotes[0].TLSTimeout
	expected = 10
	if got != expected {
		t.Fatalf("Expected %v, got: %v", expected, got)
	}

	// Here we only process options without starting the server
	// and check the default for leafnode remotes.
	conf4 := createConfFile(t, []byte(fmt.Sprintf(`
		port: -1
		leaf {
			remotes [
				{
					url: "%s"
					tls {
						ca_file: "../test/configs/certs/tlsauth/ca.pem"
					}
				}
			]
		}
	`, u.String())))
	o4, err := ProcessConfigFile(conf4)
	if err != nil {
		t.Fatalf("Error processing config file: %v", err)
	}

	if len(o4.LeafNode.Remotes) == 0 {
		t.Fatal("Expected at least a single leaf remote")
	}
	got = o4.LeafNode.Remotes[0].TLSTimeout
	expected = float64(DEFAULT_LEAF_TLS_TIMEOUT) / float64(time.Second)
	if int(got) != int(expected) {
		t.Fatalf("Expected %v, got: %v", expected, got)
	}
}

type captureErrorLogger struct {
	DummyLogger
	errCh chan string
}

func (l *captureErrorLogger) Errorf(format string, v ...interface{}) {
	select {
	case l.errCh <- fmt.Sprintf(format, v...):
	default:
	}
}

func TestLeafNodeAccountNotFound(t *testing.T) {
	ob := DefaultOptions()
	ob.LeafNode.Host = "127.0.0.1"
	ob.LeafNode.Port = -1
	sb := RunServer(ob)
	defer sb.Shutdown()

	u, _ := url.Parse(fmt.Sprintf("nats://127.0.0.1:%d", ob.LeafNode.Port))

	oa := DefaultOptions()
	oa.Cluster.Name = "xyz"
	oa.LeafNode.ReconnectInterval = 10 * time.Millisecond
	oa.LeafNode.Remotes = []*RemoteLeafOpts{
		{
			LocalAccount: "foo",
			URLs:         []*url.URL{u},
		},
	}
	// Expected to fail
	if _, err := NewServer(oa); err == nil || !strings.Contains(err.Error(), "local account") {
		t.Fatalf("Expected server to fail with error about no local account, got %v", err)
	}
	oa.Accounts = []*Account{NewAccount("foo")}
	sa := RunServer(oa)
	defer sa.Shutdown()

	l := &captureErrorLogger{errCh: make(chan string, 1)}
	sa.SetLogger(l, false, false)

	checkLeafNodeConnected(t, sa)

	// Now simulate account is removed with config reload, or it expires.
	sa.accounts.Delete("foo")

	// Restart B (with same Port)
	sb.Shutdown()
	sb = RunServer(ob)
	defer sb.Shutdown()

	// Wait for report of error
	select {
	case e := <-l.errCh:
		if !strings.Contains(e, "Unable to lookup account") {
			t.Fatalf("Expected error about no local account, got %s", e)
		}
	case <-time.After(2 * time.Second):
		t.Fatalf("Did not get the error")
	}

	// TODO below test is bogus. Instead add the account, do a reload, and make sure the connection works.

	// For now, sa would try to recreate the connection for ever.
	// Check that lid is increasing...
	checkFor(t, 2*time.Second, 100*time.Millisecond, func() error {
		if lid := atomic.LoadUint64(&sa.gcid); lid < 3 {
			return fmt.Errorf("Seems like connection was not retried, lid currently only at %d", lid)
		}
		return nil
	})
}

// This test ensures that we can connect using proper user/password
// to a LN URL that was discovered through the INFO protocol.
// We also check that the password doesn't leak to debug/trace logs.
func TestLeafNodeBasicAuthFailover(t *testing.T) {
	// Something a little longer than "pwd" to prevent false positives amongst many log lines;
	// don't make it complex enough to be subject to %-escaping, we want a simple needle search.
	fatalPassword := "pwdfatal"

	content := `
	listen: "127.0.0.1:-1"
	cluster {
		name: "abc"
		listen: "127.0.0.1:-1"
		%s
	}
	leafnodes {
		listen: "127.0.0.1:-1"
		authorization {
			user: foo
			password: %s
			timeout: 1
		}
	}
	`
	conf := createConfFile(t, []byte(fmt.Sprintf(content, "", fatalPassword)))

	sb1, ob1 := RunServerWithConfig(conf)
	defer sb1.Shutdown()

	conf = createConfFile(t, []byte(fmt.Sprintf(content, fmt.Sprintf("routes: [nats://127.0.0.1:%d]", ob1.Cluster.Port), fatalPassword)))

	sb2, _ := RunServerWithConfig(conf)
	defer sb2.Shutdown()

	checkClusterFormed(t, sb1, sb2)

	content = `
	port: -1
	accounts {
		foo {}
	}
	leafnodes {
		listen: "127.0.0.1:-1"
		remotes [
			{
				account: "foo"
				url: "nats://foo:%s@127.0.0.1:%d"
			}
		]
	}
	`
	conf = createConfFile(t, []byte(fmt.Sprintf(content, fatalPassword, ob1.LeafNode.Port)))

	sa, _ := RunServerWithConfig(conf)
	defer sa.Shutdown()

	l := testhelper.NewDummyLogger(100)
	sa.SetLogger(l, true, true) // we want debug & trace logs, to check for passwords in them

	checkLeafNodeConnected(t, sa)

	// Shutdown sb1, sa should reconnect to sb2
	sb1.Shutdown()

	// Wait a bit to make sure there was a disconnect and attempt to reconnect.
	time.Sleep(250 * time.Millisecond)

	// Should be able to reconnect
	checkLeafNodeConnected(t, sa)

	// Look at all our logs for the password; at time of writing it doesn't appear
	// but we want to safe-guard against it.
	l.CheckForProhibited(t, "fatal password", fatalPassword)
}

func TestLeafNodeRTT(t *testing.T) {
	ob := DefaultOptions()
	ob.PingInterval = 15 * time.Millisecond
	ob.LeafNode.Host = "127.0.0.1"
	ob.LeafNode.Port = -1
	sb := RunServer(ob)
	defer sb.Shutdown()

	lnBURL, _ := url.Parse(fmt.Sprintf("nats://127.0.0.1:%d", ob.LeafNode.Port))
	oa := DefaultOptions()
	oa.Cluster.Name = "xyz"
	oa.PingInterval = 15 * time.Millisecond
	oa.LeafNode.Remotes = []*RemoteLeafOpts{{URLs: []*url.URL{lnBURL}}}
	sa := RunServer(oa)
	defer sa.Shutdown()

	checkLeafNodeConnected(t, sa)

	checkRTT := func(t *testing.T, s *Server) time.Duration {
		t.Helper()
		var ln *client
		s.mu.Lock()
		for _, l := range s.leafs {
			ln = l
			break
		}
		s.mu.Unlock()
		var rtt time.Duration
		checkFor(t, 2*firstPingInterval, 15*time.Millisecond, func() error {
			ln.mu.Lock()
			rtt = ln.rtt
			ln.mu.Unlock()
			if rtt == 0 {
				return fmt.Errorf("RTT not tracked")
			}
			return nil
		})
		return rtt
	}

	prevA := checkRTT(t, sa)
	prevB := checkRTT(t, sb)

	// Wait to see if RTT is updated
	checkUpdated := func(t *testing.T, s *Server, prev time.Duration) {
		attempts := 0
		timeout := time.Now().Add(2 * firstPingInterval)
		for time.Now().Before(timeout) {
			if rtt := checkRTT(t, s); rtt != prev {
				return
			}
			attempts++
			if attempts == 5 {
				s.mu.Lock()
				for _, ln := range s.leafs {
					ln.mu.Lock()
					ln.rtt = 0
					ln.mu.Unlock()
					break
				}
				s.mu.Unlock()
			}
			time.Sleep(15 * time.Millisecond)
		}
		t.Fatalf("RTT probably not updated")
	}
	checkUpdated(t, sa, prevA)
	checkUpdated(t, sb, prevB)

	sa.Shutdown()
	sb.Shutdown()

	// Now check that initial RTT is computed prior to first PingInterval
	// Get new options to avoid possible race changing the ping interval.
	ob = DefaultOptions()
	ob.Cluster.Name = "xyz"
	ob.PingInterval = time.Minute
	ob.LeafNode.Host = "127.0.0.1"
	ob.LeafNode.Port = -1
	sb = RunServer(ob)
	defer sb.Shutdown()

	lnBURL, _ = url.Parse(fmt.Sprintf("nats://127.0.0.1:%d", ob.LeafNode.Port))
	oa = DefaultOptions()
	oa.PingInterval = time.Minute
	oa.LeafNode.Remotes = []*RemoteLeafOpts{{URLs: []*url.URL{lnBURL}}}
	sa = RunServer(oa)
	defer sa.Shutdown()

	checkLeafNodeConnected(t, sa)

	checkRTT(t, sa)
	checkRTT(t, sb)
}

func TestLeafNodeValidateAuthOptions(t *testing.T) {
	opts := DefaultOptions()
	opts.LeafNode.Username = "user1"
	opts.LeafNode.Password = "pwd"
	opts.LeafNode.Users = []*User{{Username: "user", Password: "pwd"}}
	if _, err := NewServer(opts); err == nil || !strings.Contains(err.Error(),
		"can not have a single user/pass and a users array") {
		t.Fatalf("Expected error about mixing single/multi users, got %v", err)
	}

	// Check duplicate user names
	opts.LeafNode.Username = _EMPTY_
	opts.LeafNode.Password = _EMPTY_
	opts.LeafNode.Users = append(opts.LeafNode.Users, &User{Username: "user", Password: "pwd"})
	if _, err := NewServer(opts); err == nil || !strings.Contains(err.Error(), "duplicate user") {
		t.Fatalf("Expected error about duplicate user, got %v", err)
	}
}

func TestLeafNodeBasicAuthSingleton(t *testing.T) {
	opts := DefaultOptions()
	opts.LeafNode.Port = -1
	opts.LeafNode.Account = "unknown"
	if s, err := NewServer(opts); err == nil || !strings.Contains(err.Error(), "cannot find") {
		if s != nil {
			s.Shutdown()
		}
		t.Fatalf("Expected error about account not found, got %v", err)
	}

	template := `
		port: -1
		accounts: {
			ACC1: { users = [{user: "user1", password: "user1"}] }
			ACC2: { users = [{user: "user2", password: "user2"}] }
		}
		leafnodes: {
			port: -1
			authorization {
			  %s
              account: "ACC1"
            }
		}
	`
	for iter, test := range []struct {
		name       string
		userSpec   string
		lnURLCreds string
		shouldFail bool
	}{
		{"no user creds required and no user so binds to ACC1", "", "", false},
		{"no user creds required and pick user2 associated to ACC2", "", "user2:user2@", false},
		{"no user creds required and unknown user should fail", "", "unknown:user@", true},
		{"user creds required so binds to ACC1", "user: \"ln\"\npass: \"pwd\"", "ln:pwd@", false},
	} {
		t.Run(test.name, func(t *testing.T) {

			conf := createConfFile(t, []byte(fmt.Sprintf(template, test.userSpec)))
			s1, o1 := RunServerWithConfig(conf)
			defer s1.Shutdown()

			// Create a sub on "foo" for account ACC1 (user user1), which is the one
			// bound to the accepted LN connection.
			ncACC1 := natsConnect(t, fmt.Sprintf("nats://user1:user1@%s:%d", o1.Host, o1.Port))
			defer ncACC1.Close()
			sub1 := natsSubSync(t, ncACC1, "foo")
			natsFlush(t, ncACC1)

			// Create a sub on "foo" for account ACC2 (user user2). This one should
			// not receive any message.
			ncACC2 := natsConnect(t, fmt.Sprintf("nats://user2:user2@%s:%d", o1.Host, o1.Port))
			defer ncACC2.Close()
			sub2 := natsSubSync(t, ncACC2, "foo")
			natsFlush(t, ncACC2)

			conf = createConfFile(t, []byte(fmt.Sprintf(`
				port: -1
				leafnodes: {
					remotes = [ { url: "nats-leaf://%s%s:%d" } ]
				}
			`, test.lnURLCreds, o1.LeafNode.Host, o1.LeafNode.Port)))
			s2, _ := RunServerWithConfig(conf)
			defer s2.Shutdown()

			if test.shouldFail {
				// Wait a bit and ensure that there is no leaf node connection
				time.Sleep(100 * time.Millisecond)
				checkFor(t, time.Second, 15*time.Millisecond, func() error {
					if n := s1.NumLeafNodes(); n != 0 {
						return fmt.Errorf("Expected no leafnode connection, got %v", n)
					}
					return nil
				})
				return
			}

			checkLeafNodeConnected(t, s2)

			nc := natsConnect(t, s2.ClientURL())
			defer nc.Close()
			natsPub(t, nc, "foo", []byte("hello"))
			// If url contains known user, even when there is no credentials
			// required, the connection will be bound to the user's account.
			if iter == 1 {
				// Should not receive on "ACC1", but should on "ACC2"
				if _, err := sub1.NextMsg(100 * time.Millisecond); err != nats.ErrTimeout {
					t.Fatalf("Expected timeout error, got %v", err)
				}
				natsNexMsg(t, sub2, time.Second)
			} else {
				// Should receive on "ACC1"...
				natsNexMsg(t, sub1, time.Second)
				// but not received on "ACC2" since leafnode bound to account "ACC1".
				if _, err := sub2.NextMsg(100 * time.Millisecond); err != nats.ErrTimeout {
					t.Fatalf("Expected timeout error, got %v", err)
				}
			}
		})
	}
}

func TestLeafNodeBasicAuthMultiple(t *testing.T) {
	conf := createConfFile(t, []byte(`
		port: -1
		accounts: {
			S1ACC1: { users = [{user: "user1", password: "user1"}] }
			S1ACC2: { users = [{user: "user2", password: "user2"}] }
		}
		leafnodes: {
			port: -1
			authorization {
			  users = [
				  {user: "ln1", password: "ln1", account: "S1ACC1"}
				  {user: "ln2", password: "ln2", account: "S1ACC2"}
				  {user: "ln3", password: "ln3"}
			  ]
            }
		}
	`))
	s1, o1 := RunServerWithConfig(conf)
	defer s1.Shutdown()

	// Make sure that we reject a LN connection if user does not match
	conf = createConfFile(t, []byte(fmt.Sprintf(`
		port: -1
		leafnodes: {
			remotes = [{url: "nats-leaf://wron:user@%s:%d"}]
		}
	`, o1.LeafNode.Host, o1.LeafNode.Port)))
	s2, _ := RunServerWithConfig(conf)
	defer s2.Shutdown()
	// Give a chance for s2 to attempt to connect and make sure that s1
	// did not register a LN connection.
	time.Sleep(100 * time.Millisecond)
	if n := s1.NumLeafNodes(); n != 0 {
		t.Fatalf("Expected no leafnode connection, got %v", n)
	}
	s2.Shutdown()

	ncACC1 := natsConnect(t, fmt.Sprintf("nats://user1:user1@%s:%d", o1.Host, o1.Port))
	defer ncACC1.Close()
	sub1 := natsSubSync(t, ncACC1, "foo")
	natsFlush(t, ncACC1)

	ncACC2 := natsConnect(t, fmt.Sprintf("nats://user2:user2@%s:%d", o1.Host, o1.Port))
	defer ncACC2.Close()
	sub2 := natsSubSync(t, ncACC2, "foo")
	natsFlush(t, ncACC2)

	// We will start s2 with 2 LN connections that should bind local account S2ACC1
	// to account S1ACC1 and S2ACC2 to account S1ACC2 on s1.
	conf = createConfFile(t, []byte(fmt.Sprintf(`
		port: -1
		accounts {
			S2ACC1 { users = [{user: "user1", password: "user1"}] }
			S2ACC2 { users = [{user: "user2", password: "user2"}] }
		}
		leafnodes: {
			remotes = [
				{
					url: "nats-leaf://ln1:ln1@%s:%d"
					account: "S2ACC1"
				}
				{
					url: "nats-leaf://ln2:ln2@%s:%d"
					account: "S2ACC2"
				}
			]
		}
	`, o1.LeafNode.Host, o1.LeafNode.Port, o1.LeafNode.Host, o1.LeafNode.Port)))
	s2, o2 := RunServerWithConfig(conf)
	defer s2.Shutdown()

	checkFor(t, 5*time.Second, 100*time.Millisecond, func() error {
		if nln := s2.NumLeafNodes(); nln != 2 {
			return fmt.Errorf("Expected 2 connected leafnodes for server %q, got %d", s2.ID(), nln)
		}
		return nil
	})

	// Create a user connection on s2 that binds to S2ACC1 (use user1).
	nc1 := natsConnect(t, fmt.Sprintf("nats://user1:user1@%s:%d", o2.Host, o2.Port))
	defer nc1.Close()

	// Create an user connection on s2 that binds to S2ACC2 (use user2).
	nc2 := natsConnect(t, fmt.Sprintf("nats://user2:user2@%s:%d", o2.Host, o2.Port))
	defer nc2.Close()

	// Now if a message is published from nc1, sub1 should receive it since
	// their account are bound together.
	natsPub(t, nc1, "foo", []byte("hello"))
	natsNexMsg(t, sub1, time.Second)
	// But sub2 should not receive it since different account.
	if _, err := sub2.NextMsg(100 * time.Millisecond); err != nats.ErrTimeout {
		t.Fatalf("Expected timeout error, got %v", err)
	}

	// Now use nc2 (S2ACC2) to publish
	natsPub(t, nc2, "foo", []byte("hello"))
	// Expect sub2 to receive and sub1 not to.
	natsNexMsg(t, sub2, time.Second)
	if _, err := sub1.NextMsg(100 * time.Millisecond); err != nats.ErrTimeout {
		t.Fatalf("Expected timeout error, got %v", err)
	}

	// Now check that we don't panic if no account is specified for
	// a given user.
	conf = createConfFile(t, []byte(fmt.Sprintf(`
		port: -1
		leafnodes: {
			remotes = [
				{ url: "nats-leaf://ln3:ln3@%s:%d" }
			]
		}
	`, o1.LeafNode.Host, o1.LeafNode.Port)))
	s3, _ := RunServerWithConfig(conf)
	defer s3.Shutdown()
}

type loopDetectedLogger struct {
	DummyLogger
	ch chan string
}

func (l *loopDetectedLogger) Errorf(format string, v ...interface{}) {
	msg := fmt.Sprintf(format, v...)
	if strings.Contains(msg, "Loop") {
		select {
		case l.ch <- msg:
		default:
		}
	}
}

func TestLeafNodeLoop(t *testing.T) {
	test := func(t *testing.T, cluster bool) {
		// This test requires that we set the port to known value because
		// we want A point to B and B to A.
		oa := DefaultOptions()
		if !cluster {
			oa.Cluster.Port = 0
			oa.Cluster.Name = _EMPTY_
		}
		oa.LeafNode.ReconnectInterval = 10 * time.Millisecond
		oa.LeafNode.Port = 1234
		ub, _ := url.Parse("nats://127.0.0.1:5678")
		oa.LeafNode.Remotes = []*RemoteLeafOpts{{URLs: []*url.URL{ub}}}
		oa.LeafNode.connDelay = 50 * time.Millisecond
		sa := RunServer(oa)
		defer sa.Shutdown()

		l := &loopDetectedLogger{ch: make(chan string, 1)}
		sa.SetLogger(l, false, false)

		ob := DefaultOptions()
		if !cluster {
			ob.Cluster.Port = 0
			ob.Cluster.Name = _EMPTY_
		} else {
			ob.Cluster.Name = "xyz"
		}
		ob.LeafNode.ReconnectInterval = 10 * time.Millisecond
		ob.LeafNode.Port = 5678
		ua, _ := url.Parse("nats://127.0.0.1:1234")
		ob.LeafNode.Remotes = []*RemoteLeafOpts{{URLs: []*url.URL{ua}}}
		ob.LeafNode.connDelay = 50 * time.Millisecond
		sb := RunServer(ob)
		defer sb.Shutdown()

		select {
		case <-l.ch:
			// OK!
		case <-time.After(2 * time.Second):
			t.Fatalf("Did not get any error regarding loop")
		}

		sb.Shutdown()
		ob.Port = -1
		ob.Cluster.Port = -1
		ob.LeafNode.Remotes = nil
		sb = RunServer(ob)
		defer sb.Shutdown()

		checkLeafNodeConnected(t, sa)
	}
	t.Run("standalone", func(t *testing.T) { test(t, false) })
	t.Run("cluster", func(t *testing.T) { test(t, true) })
}

func TestLeafNodeLoopFromDAG(t *testing.T) {
	// We want B & C to point to A, A itself does not point to any other server.
	// We need to cancel clustering since now this will suppress on its own.
	oa := DefaultOptions()
	oa.ServerName = "A"
	oa.LeafNode.ReconnectInterval = 10 * time.Millisecond
	oa.LeafNode.Port = -1
	oa.Cluster = ClusterOpts{}
	sa := RunServer(oa)
	defer sa.Shutdown()

	ua, _ := url.Parse(fmt.Sprintf("nats://127.0.0.1:%d", oa.LeafNode.Port))

	// B will point to A
	ob := DefaultOptions()
	ob.ServerName = "B"
	ob.LeafNode.ReconnectInterval = 10 * time.Millisecond
	ob.LeafNode.Port = -1
	ob.LeafNode.Remotes = []*RemoteLeafOpts{{URLs: []*url.URL{ua}}}
	ob.Cluster = ClusterOpts{}
	sb := RunServer(ob)
	defer sb.Shutdown()

	ub, _ := url.Parse(fmt.Sprintf("nats://127.0.0.1:%d", ob.LeafNode.Port))

	checkLeafNodeConnected(t, sa)
	checkLeafNodeConnected(t, sb)

	// C will point to A and B
	oc := DefaultOptions()
	oc.ServerName = "C"
	oc.LeafNode.ReconnectInterval = 10 * time.Millisecond
	oc.LeafNode.Remotes = []*RemoteLeafOpts{{URLs: []*url.URL{ua}}, {URLs: []*url.URL{ub}}}
	oc.LeafNode.connDelay = 100 * time.Millisecond // Allow logger to be attached before connecting.
	oc.Cluster = ClusterOpts{}
	sc := RunServer(oc)

	lc := &loopDetectedLogger{ch: make(chan string, 1)}
	sc.SetLogger(lc, false, false)

	// We should get an error.
	select {
	case <-lc.ch:
		// OK
	case <-time.After(2 * time.Second):
		t.Fatalf("Did not get any error regarding loop")
	}

	// C should not be connected to anything.
	checkLeafNodeConnectedCount(t, sc, 0)
	// A and B are connected to each other.
	checkLeafNodeConnectedCount(t, sa, 1)
	checkLeafNodeConnectedCount(t, sb, 1)

	// Shutdown C and restart without the loop.
	sc.Shutdown()
	oc.LeafNode.Remotes = []*RemoteLeafOpts{{URLs: []*url.URL{ub}}}
	sc = RunServer(oc)
	defer sc.Shutdown()

	checkLeafNodeConnectedCount(t, sa, 1)
	checkLeafNodeConnectedCount(t, sb, 2)
	checkLeafNodeConnectedCount(t, sc, 1)
}

func TestLeafNodeCloseTLSConnection(t *testing.T) {
	opts := DefaultOptions()
	opts.DisableShortFirstPing = true
	opts.LeafNode.Host = "127.0.0.1"
	opts.LeafNode.Port = -1
	opts.LeafNode.TLSTimeout = 100
	tc := &TLSConfigOpts{
		CertFile: "./configs/certs/server.pem",
		KeyFile:  "./configs/certs/key.pem",
		Insecure: true,
	}
	tlsConf, err := GenTLSConfig(tc)
	if err != nil {
		t.Fatalf("Error generating tls config: %v", err)
	}
	opts.LeafNode.TLSConfig = tlsConf
	opts.NoLog = true
	opts.NoSigs = true
	s := RunServer(opts)
	defer s.Shutdown()

	endpoint := fmt.Sprintf("%s:%d", opts.LeafNode.Host, opts.LeafNode.Port)
	conn, err := net.DialTimeout("tcp", endpoint, 2*time.Second)
	if err != nil {
		t.Fatalf("Unexpected error on dial: %v", err)
	}
	defer conn.Close()

	br := bufio.NewReaderSize(conn, 100)
	if _, err := br.ReadString('\n'); err != nil {
		t.Fatalf("Unexpected error reading INFO: %v", err)
	}

	tlsConn := tls.Client(conn, &tls.Config{InsecureSkipVerify: true})
	defer tlsConn.Close()
	if err := tlsConn.Handshake(); err != nil {
		t.Fatalf("Unexpected error during handshake: %v", err)
	}
	connectOp := []byte("CONNECT {\"name\":\"leaf\",\"verbose\":false,\"pedantic\":false}\r\n")
	if _, err := tlsConn.Write(connectOp); err != nil {
		t.Fatalf("Unexpected error writing CONNECT: %v", err)
	}
	if _, err := tlsConn.Write([]byte("PING\r\n")); err != nil {
		t.Fatalf("Unexpected error writing PING: %v", err)
	}

	checkLeafNodeConnected(t, s)

	// Get leaf connection
	var leaf *client
	s.mu.Lock()
	for _, l := range s.leafs {
		leaf = l
		break
	}
	s.mu.Unlock()
	// Fill the buffer. We want to timeout on write so that nc.Close()
	// would block due to a write that cannot complete.
	buf := make([]byte, 64*1024)
	done := false
	for !done {
		leaf.nc.SetWriteDeadline(time.Now().Add(time.Second))
		if _, err := leaf.nc.Write(buf); err != nil {
			done = true
		}
		leaf.nc.SetWriteDeadline(time.Time{})
	}
	ch := make(chan bool)
	go func() {
		select {
		case <-ch:
			return
		case <-time.After(3 * time.Second):
			fmt.Println("!!!! closeConnection is blocked, test will hang !!!")
			return
		}
	}()
	// Close the route
	leaf.closeConnection(SlowConsumerWriteDeadline)
	ch <- true
}

func TestLeafNodeTLSSaveName(t *testing.T) {
	opts := DefaultOptions()
	opts.LeafNode.Host = "127.0.0.1"
	opts.LeafNode.Port = -1
	tc := &TLSConfigOpts{
		CertFile: "../test/configs/certs/server-noip.pem",
		KeyFile:  "../test/configs/certs/server-key-noip.pem",
		Insecure: true,
	}
	tlsConf, err := GenTLSConfig(tc)
	if err != nil {
		t.Fatalf("Error generating tls config: %v", err)
	}
	opts.LeafNode.TLSConfig = tlsConf
	s := RunServer(opts)
	defer s.Shutdown()

	lo := DefaultOptions()
	u, _ := url.Parse(fmt.Sprintf("nats://localhost:%d", opts.LeafNode.Port))
	lo.LeafNode.Remotes = []*RemoteLeafOpts{{URLs: []*url.URL{u}}}
	lo.LeafNode.ReconnectInterval = 15 * time.Millisecond
	ln := RunServer(lo)
	defer ln.Shutdown()

	// We know connection will fail, but it should not fail because of error such as:
	// "cannot validate certificate for 127.0.0.1 because it doesn't contain any IP SANs"
	// This would mean that we are not saving the hostname to use during the TLS handshake.

	le := &captureErrorLogger{errCh: make(chan string, 100)}
	ln.SetLogger(le, false, false)

	tm := time.NewTimer(time.Second)
	var done bool
	for !done {
		select {
		case err := <-le.errCh:
			if strings.Contains(err, "doesn't contain any IP SANs") {
				t.Fatalf("Got this error: %q", err)
			}
		case <-tm.C:
			done = true
		}
	}
}

func TestLeafNodeRemoteWrongPort(t *testing.T) {
	for _, test1 := range []struct {
		name              string
		clusterAdvertise  bool
		leafnodeAdvertise bool
	}{
		{"advertise_on", false, false},
		{"cluster_no_advertise", true, false},
		{"leafnode_no_advertise", false, true},
	} {
		t.Run(test1.name, func(t *testing.T) {
			oa := DefaultOptions()
			// Make sure we have all ports (client, route, gateway) and we will try
			// to create a leafnode to connection to each and make sure we get the error.
			oa.Cluster.NoAdvertise = test1.clusterAdvertise
			oa.Cluster.Name = "A"
			oa.Cluster.Host = "127.0.0.1"
			oa.Cluster.Port = -1
			oa.Gateway.Host = "127.0.0.1"
			oa.Gateway.Port = -1
			oa.Gateway.Name = "A"
			oa.LeafNode.Host = "127.0.0.1"
			oa.LeafNode.Port = -1
			oa.LeafNode.NoAdvertise = test1.leafnodeAdvertise
			oa.Accounts = []*Account{NewAccount("sys")}
			oa.SystemAccount = "sys"
			sa := RunServer(oa)
			defer sa.Shutdown()

			ob := DefaultOptions()
			ob.Cluster.NoAdvertise = test1.clusterAdvertise
			ob.Cluster.Name = "A"
			ob.Cluster.Host = "127.0.0.1"
			ob.Cluster.Port = -1
			ob.Routes = RoutesFromStr(fmt.Sprintf("nats://%s:%d", oa.Cluster.Host, oa.Cluster.Port))
			ob.Gateway.Host = "127.0.0.1"
			ob.Gateway.Port = -1
			ob.Gateway.Name = "A"
			ob.LeafNode.Host = "127.0.0.1"
			ob.LeafNode.Port = -1
			ob.LeafNode.NoAdvertise = test1.leafnodeAdvertise
			ob.Accounts = []*Account{NewAccount("sys")}
			ob.SystemAccount = "sys"
			sb := RunServer(ob)
			defer sb.Shutdown()

			checkClusterFormed(t, sa, sb)

			for _, test := range []struct {
				name string
				port int
			}{
				{"client", oa.Port},
				{"cluster", oa.Cluster.Port},
				{"gateway", oa.Gateway.Port},
			} {
				t.Run(test.name, func(t *testing.T) {
					oc := DefaultOptions()
					// Server with the wrong config against non leafnode port.
					leafURL, _ := url.Parse(fmt.Sprintf("nats://127.0.0.1:%d", test.port))
					oc.LeafNode.Remotes = []*RemoteLeafOpts{{URLs: []*url.URL{leafURL}}}
					oc.LeafNode.ReconnectInterval = 5 * time.Millisecond
					sc := RunServer(oc)
					defer sc.Shutdown()
					l := &captureErrorLogger{errCh: make(chan string, 10)}
					sc.SetLogger(l, true, true)

					select {
					case e := <-l.errCh:
						if strings.Contains(e, ErrConnectedToWrongPort.Error()) {
							return
						}
					case <-time.After(2 * time.Second):
						t.Fatalf("Did not get any error about connecting to wrong port for %q - %q",
							test1.name, test.name)
					}
				})
			}
		})
	}
}

func TestLeafNodeRemoteIsHub(t *testing.T) {
	oa := testDefaultOptionsForGateway("A")
	oa.Accounts = []*Account{NewAccount("sys")}
	oa.SystemAccount = "sys"
	sa := RunServer(oa)
	defer sa.Shutdown()

	lno := DefaultOptions()
	lno.LeafNode.Host = "127.0.0.1"
	lno.LeafNode.Port = -1
	ln := RunServer(lno)
	defer ln.Shutdown()

	ob1 := testGatewayOptionsFromToWithServers(t, "B", "A", sa)
	ob1.Accounts = []*Account{NewAccount("sys")}
	ob1.SystemAccount = "sys"
	ob1.Cluster.Host = "127.0.0.1"
	ob1.Cluster.Port = -1
	ob1.LeafNode.Host = "127.0.0.1"
	ob1.LeafNode.Port = -1
	u, _ := url.Parse(fmt.Sprintf("nats://127.0.0.1:%d", lno.LeafNode.Port))
	ob1.LeafNode.Remotes = []*RemoteLeafOpts{
		{
			URLs: []*url.URL{u},
			Hub:  true,
		},
	}
	sb1 := RunServer(ob1)
	defer sb1.Shutdown()

	waitForOutboundGateways(t, sb1, 1, 2*time.Second)
	waitForInboundGateways(t, sb1, 1, 2*time.Second)
	waitForOutboundGateways(t, sa, 1, 2*time.Second)
	waitForInboundGateways(t, sa, 1, 2*time.Second)

	checkLeafNodeConnected(t, sb1)

	// For now, due to issue 977, let's restart the leafnode so that the
	// leafnode connect is propagated in the super-cluster.
	ln.Shutdown()
	ln = RunServer(lno)
	defer ln.Shutdown()
	checkLeafNodeConnected(t, sb1)

	// Connect another server in cluster B
	ob2 := testGatewayOptionsFromToWithServers(t, "B", "A", sa)
	ob2.Accounts = []*Account{NewAccount("sys")}
	ob2.SystemAccount = "sys"
	ob2.Cluster.Host = "127.0.0.1"
	ob2.Cluster.Port = -1
	ob2.Routes = RoutesFromStr(fmt.Sprintf("nats://127.0.0.1:%d", ob1.Cluster.Port))
	sb2 := RunServer(ob2)
	defer sb2.Shutdown()

	checkClusterFormed(t, sb1, sb2)
	waitForOutboundGateways(t, sb2, 1, 2*time.Second)

	expectedSubs := ln.NumSubscriptions() + 2

	// Create sub on "foo" connected to sa
	ncA := natsConnect(t, sa.ClientURL())
	defer ncA.Close()
	subFoo := natsSubSync(t, ncA, "foo")

	// Create sub on "bar" connected to sb2
	ncB2 := natsConnect(t, sb2.ClientURL())
	defer ncB2.Close()
	subBar := natsSubSync(t, ncB2, "bar")

	// Make sure subscriptions have propagated to the leafnode.
	checkFor(t, time.Second, 10*time.Millisecond, func() error {
		if subs := ln.NumSubscriptions(); subs < expectedSubs {
			return fmt.Errorf("Number of subs is %d", subs)
		}
		return nil
	})

	// Create pub connection on leafnode
	ncLN := natsConnect(t, ln.ClientURL())
	defer ncLN.Close()

	// Publish on foo and make sure it is received.
	natsPub(t, ncLN, "foo", []byte("msg"))
	natsNexMsg(t, subFoo, time.Second)

	// Publish on foo and make sure it is received.
	natsPub(t, ncLN, "bar", []byte("msg"))
	natsNexMsg(t, subBar, time.Second)
}

func TestLeafNodePermissions(t *testing.T) {
	lo1 := DefaultOptions()
	lo1.LeafNode.Host = "127.0.0.1"
	lo1.LeafNode.Port = -1
	ln1 := RunServer(lo1)
	defer ln1.Shutdown()

	errLog := &captureErrorLogger{errCh: make(chan string, 1)}
	ln1.SetLogger(errLog, false, false)

	u, _ := url.Parse(fmt.Sprintf("nats://%s:%d", lo1.LeafNode.Host, lo1.LeafNode.Port))
	lo2 := DefaultOptions()
	lo2.Cluster.Name = "xyz"
	lo2.LeafNode.ReconnectInterval = 5 * time.Millisecond
	lo2.LeafNode.connDelay = 100 * time.Millisecond
	lo2.LeafNode.Remotes = []*RemoteLeafOpts{
		{
			URLs:        []*url.URL{u},
			DenyExports: []string{"export.*", "export"},
			DenyImports: []string{"import.*", "import"},
		},
	}
	ln2 := RunServer(lo2)
	defer ln2.Shutdown()

	checkLeafNodeConnected(t, ln1)

	// Create clients on ln1 and ln2
	nc1, err := nats.Connect(ln1.ClientURL())
	if err != nil {
		t.Fatalf("Error creating client: %v", err)
	}
	defer nc1.Close()
	nc2, err := nats.Connect(ln2.ClientURL())
	if err != nil {
		t.Fatalf("Error creating client: %v", err)
	}
	defer nc2.Close()

	checkSubs := func(acc *Account, expected int) {
		t.Helper()
		checkFor(t, time.Second, 15*time.Millisecond, func() error {
			if n := acc.TotalSubs(); n != expected {
				return fmt.Errorf("Expected %d subs, got %v", expected, n)
			}
			return nil
		})
	}

	// Create a sub on ">" on LN1
	subAll := natsSubSync(t, nc1, ">")
	// this should be registered in LN2 (there is 1 sub for LN1 $LDS subject) + SYS IMPORTS
	checkSubs(ln2.globalAccount(), 12)

	// Check deny export clause from messages published from LN2
	for _, test := range []struct {
		name     string
		subject  string
		received bool
	}{
		{"do not send on export.bat", "export.bat", false},
		{"do not send on export", "export", false},
		{"send on foo", "foo", true},
		{"send on export.this.one", "export.this.one", true},
	} {
		t.Run(test.name, func(t *testing.T) {
			nc2.Publish(test.subject, []byte("msg"))
			if test.received {
				natsNexMsg(t, subAll, time.Second)
			} else {
				if _, err := subAll.NextMsg(50 * time.Millisecond); err == nil {
					t.Fatalf("Should not have received message on %q", test.subject)
				}
			}
		})
	}

	subAll.Unsubscribe()
	// Goes down by 1.
	checkSubs(ln2.globalAccount(), 11)

	// We used to make sure we would not do subscriptions however that
	// was incorrect. We need to check publishes, not the subscriptions.
	// For instance if we can publish across a leafnode to foo, and the
	// other side has a subsxcription for '*' the message should cross
	// the leafnode. The old way would not allow this.

	// Now check deny import clause.
	// As of now, we don't suppress forwarding of subscriptions on LN2 that
	// match the deny import clause to be forwarded to LN1. However, messages
	// should still not be able to come back to LN2.
	for _, test := range []struct {
		name       string
		subSubject string
		pubSubject string
		ok         bool
	}{
		{"reject import on import.*", "import.*", "import.bad", false},
		{"reject import on import", "import", "import", false},
		{"accepts import on foo", "foo", "foo", true},
		{"accepts import on import.this.one.ok", "import.*.>", "import.this.one.ok", true},
	} {
		t.Run(test.name, func(t *testing.T) {
			sub := natsSubSync(t, nc2, test.subSubject)
			checkSubs(ln2.globalAccount(), 12)

			if !test.ok {
				nc1.Publish(test.pubSubject, []byte("msg"))
				if _, err := sub.NextMsg(50 * time.Millisecond); err == nil {
					t.Fatalf("Did not expect to get the message")
				}
			} else {
				checkSubs(ln1.globalAccount(), 11)
				nc1.Publish(test.pubSubject, []byte("msg"))
				natsNexMsg(t, sub, time.Second)
			}
			sub.Unsubscribe()
			checkSubs(ln1.globalAccount(), 10)
		})
	}
}

func TestLeafNodePermissionsConcurrentAccess(t *testing.T) {
	lo1 := DefaultOptions()
	lo1.LeafNode.Host = "127.0.0.1"
	lo1.LeafNode.Port = -1
	ln1 := RunServer(lo1)
	defer ln1.Shutdown()

	nc1 := natsConnect(t, ln1.ClientURL())
	defer nc1.Close()

	natsSub(t, nc1, "_INBOX.>", func(_ *nats.Msg) {})
	natsFlush(t, nc1)

	ch := make(chan struct{}, 1)
	wg := sync.WaitGroup{}
	wg.Add(2)

	publish := func(nc *nats.Conn) {
		defer wg.Done()

		for {
			select {
			case <-ch:
				return
			default:
				nc.Publish(nats.NewInbox(), []byte("hello"))
			}
		}
	}

	go publish(nc1)

	u, _ := url.Parse(fmt.Sprintf("nats://%s:%d", lo1.LeafNode.Host, lo1.LeafNode.Port))
	lo2 := DefaultOptions()
	lo2.Cluster.Name = "xyz"
	lo2.LeafNode.ReconnectInterval = 5 * time.Millisecond
	lo2.LeafNode.connDelay = 500 * time.Millisecond
	lo2.LeafNode.Remotes = []*RemoteLeafOpts{
		{
			URLs:        []*url.URL{u},
			DenyExports: []string{"foo"},
			DenyImports: []string{"bar"},
		},
	}
	ln2 := RunServer(lo2)
	defer ln2.Shutdown()

	nc2 := natsConnect(t, ln2.ClientURL())
	defer nc2.Close()

	natsSub(t, nc2, "_INBOX.>", func(_ *nats.Msg) {})
	natsFlush(t, nc2)

	go publish(nc2)

	checkLeafNodeConnected(t, ln1)
	checkLeafNodeConnected(t, ln2)

	time.Sleep(50 * time.Millisecond)
	close(ch)
	wg.Wait()
}

func TestLeafNodePubAllowedPruning(t *testing.T) {
	c := &client{}
	c.setPermissions(&Permissions{Publish: &SubjectPermission{Allow: []string{"foo"}}})

	gr := 100
	wg := sync.WaitGroup{}
	wg.Add(gr)
	for i := 0; i < gr; i++ {
		go func() {
			defer wg.Done()
			for i := 0; i < 100; i++ {
				c.pubAllowed(nats.NewInbox())
			}
		}()
	}

	wg.Wait()
	if n := int(atomic.LoadInt32(&c.perms.pcsz)); n > maxPermCacheSize {
		t.Fatalf("Expected size to be less than %v, got %v", maxPermCacheSize, n)
	}
	if n := atomic.LoadInt32(&c.perms.prun); n != 0 {
		t.Fatalf("c.perms.prun should be 0, was %v", n)
	}
}

func TestLeafNodeExportPermissionsNotForSpecialSubs(t *testing.T) {
	lo1 := DefaultOptions()
	lo1.Accounts = []*Account{NewAccount("SYS")}
	lo1.SystemAccount = "SYS"
	lo1.Cluster.Name = "A"
	lo1.Gateway.Name = "A"
	lo1.Gateway.Port = -1
	lo1.LeafNode.Host = "127.0.0.1"
	lo1.LeafNode.Port = -1
	ln1 := RunServer(lo1)
	defer ln1.Shutdown()

	u, _ := url.Parse(fmt.Sprintf("nats://%s:%d", lo1.LeafNode.Host, lo1.LeafNode.Port))
	lo2 := DefaultOptions()
	lo2.LeafNode.Remotes = []*RemoteLeafOpts{
		{
			URLs:        []*url.URL{u},
			DenyExports: []string{">"},
		},
	}
	ln2 := RunServer(lo2)
	defer ln2.Shutdown()

	checkLeafNodeConnected(t, ln1)

	// The deny is totally restrictive, but make sure that we still accept the $LDS, $GR and _GR_ go from LN1.
	checkFor(t, time.Second, 15*time.Millisecond, func() error {
		// We should have registered the 3 subs from the accepting leafnode.
		if n := ln2.globalAccount().TotalSubs(); n != 9 {
			return fmt.Errorf("Expected %d subs, got %v", 9, n)
		}
		return nil
	})
}

// Make sure that if the node that detects the loop (and sends the error and
// close the connection) is the accept side, the remote node (the one that solicits)
// properly use the reconnect delay.
func TestLeafNodeLoopDetectedOnAcceptSide(t *testing.T) {
	bo := DefaultOptions()
	bo.LeafNode.Host = "127.0.0.1"
	bo.LeafNode.Port = -1
	b := RunServer(bo)
	defer b.Shutdown()

	l := &loopDetectedLogger{ch: make(chan string, 1)}
	b.SetLogger(l, false, false)

	u, _ := url.Parse(fmt.Sprintf("nats://127.0.0.1:%d", bo.LeafNode.Port))

	ao := testDefaultOptionsForGateway("A")
	ao.Accounts = []*Account{NewAccount("SYS")}
	ao.SystemAccount = "SYS"
	ao.LeafNode.ReconnectInterval = 5 * time.Millisecond
	ao.LeafNode.Remotes = []*RemoteLeafOpts{
		{
			URLs: []*url.URL{u},
			Hub:  true,
		},
	}
	a := RunServer(ao)
	defer a.Shutdown()

	co := testGatewayOptionsFromToWithServers(t, "C", "A", a)
	co.Accounts = []*Account{NewAccount("SYS")}
	co.SystemAccount = "SYS"
	co.LeafNode.ReconnectInterval = 5 * time.Millisecond
	co.LeafNode.Remotes = []*RemoteLeafOpts{
		{
			URLs: []*url.URL{u},
			Hub:  true,
		},
	}
	c := RunServer(co)
	defer c.Shutdown()

	for i := 0; i < 2; i++ {
		select {
		case <-l.ch:
			// OK
		case <-time.After(200 * time.Millisecond):
			// We are likely to detect from each A and C servers,
			// but consider a failure if we did not receive any.
			if i == 0 {
				t.Fatalf("Should have detected loop")
			}
		}
	}

	// The reconnect attempt is set to 5ms, but the default loop delay
	// is 30 seconds, so we should not get any new error for that long.
	// Check if we are getting more errors..
	select {
	case e := <-l.ch:
		t.Fatalf("Should not have gotten another error, got %q", e)
	case <-time.After(50 * time.Millisecond):
		// OK!
	}
}

func TestLeafNodeHubWithGateways(t *testing.T) {
	ao := DefaultOptions()
	ao.ServerName = "A"
	ao.LeafNode.Host = "127.0.0.1"
	ao.LeafNode.Port = -1
	a := RunServer(ao)
	defer a.Shutdown()

	ua, _ := url.Parse(fmt.Sprintf("nats://127.0.0.1:%d", ao.LeafNode.Port))

	bo := testDefaultOptionsForGateway("B")
	bo.ServerName = "B"
	bo.Accounts = []*Account{NewAccount("SYS")}
	bo.SystemAccount = "SYS"
	bo.LeafNode.ReconnectInterval = 5 * time.Millisecond
	bo.LeafNode.Remotes = []*RemoteLeafOpts{
		{
			URLs: []*url.URL{ua},
			Hub:  true,
		},
	}
	b := RunServer(bo)
	defer b.Shutdown()

	do := DefaultOptions()
	do.ServerName = "D"
	do.LeafNode.Host = "127.0.0.1"
	do.LeafNode.Port = -1
	d := RunServer(do)
	defer d.Shutdown()

	ud, _ := url.Parse(fmt.Sprintf("nats://127.0.0.1:%d", do.LeafNode.Port))

	co := testGatewayOptionsFromToWithServers(t, "C", "B", b)
	co.ServerName = "C"
	co.Accounts = []*Account{NewAccount("SYS")}
	co.SystemAccount = "SYS"
	co.LeafNode.ReconnectInterval = 5 * time.Millisecond
	co.LeafNode.Remotes = []*RemoteLeafOpts{
		{
			URLs: []*url.URL{ud},
			Hub:  true,
		},
	}
	c := RunServer(co)
	defer c.Shutdown()

	waitForInboundGateways(t, b, 1, 2*time.Second)
	waitForInboundGateways(t, c, 1, 2*time.Second)
	checkLeafNodeConnected(t, a)
	checkLeafNodeConnected(t, d)

	// Create a responder on D
	ncD := natsConnect(t, d.ClientURL())
	defer ncD.Close()

	ncD.Subscribe("service", func(m *nats.Msg) {
		m.Respond([]byte("reply"))
	})
	ncD.Flush()

	checkFor(t, time.Second, 15*time.Millisecond, func() error {
		acc := a.globalAccount()
		if r := acc.sl.Match("service"); r != nil && len(r.psubs) == 1 {
			return nil
		}
		return fmt.Errorf("subscription still not registered")
	})

	// Create requestor on A and send the request, expect a reply.
	ncA := natsConnect(t, a.ClientURL())
	defer ncA.Close()
	if msg, err := ncA.Request("service", []byte("request"), time.Second); err != nil {
		t.Fatalf("Failed to get reply: %v", err)
	} else if string(msg.Data) != "reply" {
		t.Fatalf("Unexpected reply: %q", msg.Data)
	}
}

func TestLeafNodeTmpClients(t *testing.T) {
	ao := DefaultOptions()
	ao.LeafNode.Host = "127.0.0.1"
	ao.LeafNode.Port = -1
	a := RunServer(ao)
	defer a.Shutdown()

	c, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", ao.LeafNode.Port))
	if err != nil {
		t.Fatalf("Error connecting: %v", err)
	}
	defer c.Close()
	// Read info
	br := bufio.NewReader(c)
	br.ReadLine()

	checkTmp := func(expected int) {
		t.Helper()
		checkFor(t, time.Second, 15*time.Millisecond, func() error {
			a.grMu.Lock()
			l := len(a.grTmpClients)
			a.grMu.Unlock()
			if l != expected {
				return fmt.Errorf("Expected tmp map to have %v entries, got %v", expected, l)
			}
			return nil
		})
	}
	checkTmp(1)

	// Close client and wait check that it is removed.
	c.Close()
	checkTmp(0)

	// Check with normal leafnode connection that once connected,
	// the tmp map is also emptied.
	bo := DefaultOptions()
	bo.Cluster.Name = "xyz"
	bo.LeafNode.ReconnectInterval = 5 * time.Millisecond
	u, err := url.Parse(fmt.Sprintf("nats://127.0.0.1:%d", ao.LeafNode.Port))
	if err != nil {
		t.Fatalf("Error creating url: %v", err)
	}
	bo.LeafNode.Remotes = []*RemoteLeafOpts{{URLs: []*url.URL{u}}}
	b := RunServer(bo)
	defer b.Shutdown()

	checkLeafNodeConnected(t, b)
	checkTmp(0)
}

func TestLeafNodeTLSVerifyAndMap(t *testing.T) {
	accName := "MyAccount"
	acc := NewAccount(accName)
	certUserName := "CN=example.com,OU=NATS.io"
	users := []*User{{Username: certUserName, Account: acc}}

	for _, test := range []struct {
		name        string
		leafUsers   bool
		provideCert bool
	}{
		{"no users override, provides cert", false, true},
		{"no users override, does not provide cert", false, false},
		{"users override, provides cert", true, true},
		{"users override, does not provide cert", true, false},
	} {
		t.Run(test.name, func(t *testing.T) {
			o := DefaultOptions()
			o.Accounts = []*Account{acc}
			o.LeafNode.Host = "127.0.0.1"
			o.LeafNode.Port = -1
			if test.leafUsers {
				o.LeafNode.Users = users
			} else {
				o.Users = users
			}
			tc := &TLSConfigOpts{
				CertFile: "../test/configs/certs/tlsauth/server.pem",
				KeyFile:  "../test/configs/certs/tlsauth/server-key.pem",
				CaFile:   "../test/configs/certs/tlsauth/ca.pem",
				Verify:   true,
			}
			tlsc, err := GenTLSConfig(tc)
			if err != nil {
				t.Fatalf("Error creating tls config: %v", err)
			}
			o.LeafNode.TLSConfig = tlsc
			o.LeafNode.TLSMap = true
			s := RunServer(o)
			defer s.Shutdown()

			slo := DefaultOptions()
			slo.Cluster.Name = "xyz"

			sltlsc := &tls.Config{}
			if test.provideCert {
				tc := &TLSConfigOpts{
					CertFile: "../test/configs/certs/tlsauth/client.pem",
					KeyFile:  "../test/configs/certs/tlsauth/client-key.pem",
				}
				var err error
				sltlsc, err = GenTLSConfig(tc)
				if err != nil {
					t.Fatalf("Error generating tls config: %v", err)
				}
			}
			sltlsc.InsecureSkipVerify = true
			u, _ := url.Parse(fmt.Sprintf("nats://%s:%d", o.LeafNode.Host, o.LeafNode.Port))
			slo.LeafNode.Remotes = []*RemoteLeafOpts{
				{
					TLSConfig: sltlsc,
					URLs:      []*url.URL{u},
				},
			}
			sl := RunServer(slo)
			defer sl.Shutdown()

			if !test.provideCert {
				// Wait a bit and make sure we are not connecting
				time.Sleep(100 * time.Millisecond)
				checkLeafNodeConnectedCount(t, s, 0)
				return
			}
			checkLeafNodeConnected(t, s)

			var uname string
			var accname string
			s.mu.Lock()
			for _, c := range s.leafs {
				c.mu.Lock()
				uname = c.opts.Username
				if c.acc != nil {
					accname = c.acc.GetName()
				}
				c.mu.Unlock()
			}
			s.mu.Unlock()
			if uname != certUserName {
				t.Fatalf("Expected username %q, got %q", certUserName, uname)
			}
			if accname != accName {
				t.Fatalf("Expected account %q, got %v", accName, accname)
			}
		})
	}
}

type chanLogger struct {
	DummyLogger
	triggerChan chan string
}

func (l *chanLogger) Warnf(format string, v ...interface{}) {
	l.triggerChan <- fmt.Sprintf(format, v...)
}

func (l *chanLogger) Errorf(format string, v ...interface{}) {
	l.triggerChan <- fmt.Sprintf(format, v...)
}

const (
	testLeafNodeTLSVerifyAndMapSrvA = `
listen: 127.0.0.1:-1
leaf {
	listen: "127.0.0.1:-1"
	tls {
		cert_file: "../test/configs/certs/server-cert.pem"
		key_file:  "../test/configs/certs/server-key.pem"
		ca_file:   "../test/configs/certs/ca.pem"
		timeout: 2
		verify_and_map: true
	}
	authorization {
		users [{
			user: "%s"
		}]
	}
}
`
	testLeafNodeTLSVerifyAndMapSrvB = `
listen: -1
leaf {
	remotes [
		{
			url: "tls://user-provided-in-url@localhost:%d"
			tls {
				cert_file: "../test/configs/certs/server-cert.pem"
				key_file:  "../test/configs/certs/server-key.pem"
				ca_file:   "../test/configs/certs/ca.pem"
			}
		}
	]
}`
)

func TestLeafNodeTLSVerifyAndMapCfgPass(t *testing.T) {
	l := &chanLogger{triggerChan: make(chan string, 100)}
	defer close(l.triggerChan)

	confA := createConfFile(t, []byte(fmt.Sprintf(testLeafNodeTLSVerifyAndMapSrvA, "localhost")))
	srvA, optsA := RunServerWithConfig(confA)
	defer srvA.Shutdown()
	srvA.SetLogger(l, true, true)

	confB := createConfFile(t, []byte(fmt.Sprintf(testLeafNodeTLSVerifyAndMapSrvB, optsA.LeafNode.Port)))
	ob := LoadConfig(confB)
	ob.LeafNode.ReconnectInterval = 50 * time.Millisecond
	srvB := RunServer(ob)
	defer srvB.Shutdown()

	// Now make sure that the leaf node connection is up and the correct account was picked
	checkFor(t, 10*time.Second, 10*time.Millisecond, func() error {
		for _, srv := range []*Server{srvA, srvB} {
			if nln := srv.NumLeafNodes(); nln != 1 {
				return fmt.Errorf("Number of leaf nodes is %d", nln)
			}
			if leafz, err := srv.Leafz(nil); err != nil {
				if len(leafz.Leafs) != 1 {
					return fmt.Errorf("Number of leaf nodes returned by LEAFZ is not one: %d", len(leafz.Leafs))
				} else if leafz.Leafs[0].Account != DEFAULT_GLOBAL_ACCOUNT {
					return fmt.Errorf("Account used is not $G: %s", leafz.Leafs[0].Account)
				}
			}
		}
		return nil
	})
	// Make sure that the user name in the url was ignored and a warning printed
	for {
		select {
		case w := <-l.triggerChan:
			if w == `User "user-provided-in-url" found in connect proto, but user required from cert` {
				return
			}
		case <-time.After(2 * time.Second):
			t.Fatal("Did not get expected warning")
		}
	}
}

func TestLeafNodeTLSVerifyAndMapCfgFail(t *testing.T) {
	l := &chanLogger{triggerChan: make(chan string, 100)}
	defer close(l.triggerChan)

	// use certificate with SAN localhost, but configure the server to not accept it
	// instead provide a name matching the user (to be matched by failed
	confA := createConfFile(t, []byte(fmt.Sprintf(testLeafNodeTLSVerifyAndMapSrvA, "user-provided-in-url")))
	srvA, optsA := RunServerWithConfig(confA)
	defer srvA.Shutdown()
	srvA.SetLogger(l, true, true)

	confB := createConfFile(t, []byte(fmt.Sprintf(testLeafNodeTLSVerifyAndMapSrvB, optsA.LeafNode.Port)))
	ob := LoadConfig(confB)
	ob.LeafNode.ReconnectInterval = 50 * time.Millisecond
	srvB := RunServer(ob)
	defer srvB.Shutdown()

	// Now make sure that the leaf node connection is down
	checkFor(t, 10*time.Second, 10*time.Millisecond, func() error {
		for _, srv := range []*Server{srvA, srvB} {
			if nln := srv.NumLeafNodes(); nln != 0 {
				return fmt.Errorf("Number of leaf nodes is %d", nln)
			}
		}
		return nil
	})
	// Make sure that the connection was closed for the right reason
	for {
		select {
		case w := <-l.triggerChan:
			if strings.Contains(w, ErrAuthentication.Error()) {
				return
			}
		case <-time.After(2 * time.Second):
			t.Fatal("Did not get expected warning")
		}
	}
}

func TestLeafNodeOriginClusterInfo(t *testing.T) {
	hopts := DefaultOptions()
	hopts.ServerName = "hub"
	hopts.LeafNode.Port = -1

	hub := RunServer(hopts)
	defer hub.Shutdown()

	conf := createConfFile(t, []byte(fmt.Sprintf(`
		port: -1
		leaf {
			remotes [ { url: "nats://127.0.0.1:%d" } ]
		}
	`, hopts.LeafNode.Port)))

	opts, err := ProcessConfigFile(conf)
	if err != nil {
		t.Fatalf("Error processing config file: %v", err)
	}
	opts.NoLog, opts.NoSigs = true, true

	s := RunServer(opts)
	defer s.Shutdown()

	checkLeafNodeConnected(t, s)

	// Check the info on the leadnode client in the hub.
	grabLeaf := func() *client {
		var l *client
		hub.mu.Lock()
		for _, l = range hub.leafs {
			break
		}
		hub.mu.Unlock()
		return l
	}

	l := grabLeaf()
	if rc := l.remoteCluster(); rc != "" {
		t.Fatalf("Expected an empty remote cluster, got %q", rc)
	}

	s.Shutdown()

	// Now make our leafnode part of a cluster.
	conf = createConfFile(t, []byte(fmt.Sprintf(`
		port: -1
		leaf {
			remotes [ { url: "nats://127.0.0.1:%d" } ]
		}
		cluster {
			name: "xyz"
			listen: "127.0.0.1:-1"
		}
	`, hopts.LeafNode.Port)))

	opts, err = ProcessConfigFile(conf)
	if err != nil {
		t.Fatalf("Error processing config file: %v", err)
	}
	opts.NoLog, opts.NoSigs = true, true

	s = RunServer(opts)
	defer s.Shutdown()

	checkLeafNodeConnected(t, s)

	l = grabLeaf()
	if rc := l.remoteCluster(); rc != "xyz" {
		t.Fatalf("Expected a remote cluster name of \"xyz\", got %q", rc)
	}
	pcid := l.cid

	// Now make sure that if we update our cluster name, simulating the settling
	// of dynamic cluster names between competing servers.
	s.setClusterName("xyz")
	// Make sure we disconnect and reconnect.
	checkLeafNodeConnectedCount(t, s, 0)
	checkLeafNodeConnected(t, s)
	checkLeafNodeConnected(t, hub)

	l = grabLeaf()
	if rc := l.remoteCluster(); rc != "xyz" {
		t.Fatalf("Expected a remote cluster name of \"xyz\", got %q", rc)
	}
	// Make sure we reconnected and have a new CID.
	if l.cid == pcid {
		t.Fatalf("Expected a different id, got the same")
	}
}

type proxyAcceptDetectFailureLate struct {
	sync.Mutex
	wg         sync.WaitGroup
	acceptPort int
	l          net.Listener
	srvs       []net.Conn
	leaf       net.Conn
	startChan  chan struct{}
}

func (p *proxyAcceptDetectFailureLate) run(t *testing.T) int {
	return p.runEx(t, false)
}

func (p *proxyAcceptDetectFailureLate) runEx(t *testing.T, needStart bool) int {
	l, err := natsListen("tcp", "127.0.0.1:0")
	if err != nil {
		t.Fatalf("Error on listen: %v", err)
	}
	p.Lock()
	var startChan chan struct{}
	if needStart {
		startChan = make(chan struct{})
		p.startChan = startChan
	}
	p.l = l
	p.Unlock()
	port := l.Addr().(*net.TCPAddr).Port
	p.wg.Add(1)
	go func() {
		defer p.wg.Done()
		defer l.Close()
		defer func() {
			p.Lock()
			for _, c := range p.srvs {
				c.Close()
			}
			p.Unlock()
		}()
		if startChan != nil {
			<-startChan
		}
		for {
			c, err := l.Accept()
			if err != nil {
				return
			}
			srv, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", p.acceptPort))
			if err != nil {
				return
			}
			p.Lock()
			p.leaf = c
			p.srvs = append(p.srvs, srv)
			p.Unlock()

			transfer := func(c1, c2 net.Conn) {
				var buf [1024]byte
				for {
					n, err := c1.Read(buf[:])
					if err != nil {
						return
					}
					if _, err := c2.Write(buf[:n]); err != nil {
						return
					}
				}
			}

			go transfer(srv, c)
			go transfer(c, srv)
		}
	}()
	return port
}

func (p *proxyAcceptDetectFailureLate) start() {
	p.Lock()
	if p.startChan != nil {
		close(p.startChan)
		p.startChan = nil
	}
	p.Unlock()
}

func (p *proxyAcceptDetectFailureLate) close() {
	p.Lock()
	if p.startChan != nil {
		close(p.startChan)
		p.startChan = nil
	}
	p.l.Close()
	p.Unlock()

	p.wg.Wait()
}

type oldConnReplacedLogger struct {
	DummyLogger
	errCh  chan string
	warnCh chan string
}

func (l *oldConnReplacedLogger) Errorf(format string, v ...interface{}) {
	select {
	case l.errCh <- fmt.Sprintf(format, v...):
	default:
	}
}

func (l *oldConnReplacedLogger) Warnf(format string, v ...interface{}) {
	select {
	case l.warnCh <- fmt.Sprintf(format, v...):
	default:
	}
}

// This test will simulate that the accept side does not detect the connection
// has been closed early enough. The soliciting side will attempt to reconnect
// and we should not be getting the "loop detected" error.
func TestLeafNodeLoopDetectedDueToReconnect(t *testing.T) {
	o := DefaultOptions()
	o.LeafNode.Host = "127.0.0.1"
	o.LeafNode.Port = -1
	s := RunServer(o)
	defer s.Shutdown()

	l := &oldConnReplacedLogger{errCh: make(chan string, 10), warnCh: make(chan string, 10)}
	s.SetLogger(l, false, false)

	p := &proxyAcceptDetectFailureLate{acceptPort: o.LeafNode.Port}
	defer p.close()
	port := p.run(t)

	aurl, err := url.Parse(fmt.Sprintf("nats://127.0.0.1:%d", port))
	if err != nil {
		t.Fatalf("Error parsing url: %v", err)
	}
	ol := DefaultOptions()
	ol.Cluster.Name = "cde"
	ol.LeafNode.ReconnectInterval = 50 * time.Millisecond
	ol.LeafNode.Remotes = []*RemoteLeafOpts{{URLs: []*url.URL{aurl}}}
	sl := RunServer(ol)
	defer sl.Shutdown()

	checkLeafNodeConnected(t, s)
	checkLeafNodeConnected(t, sl)

	// Cause disconnect client side...
	p.Lock()
	p.leaf.Close()
	p.Unlock()

	// Make sure we did not get the loop detected error
	select {
	case e := <-l.errCh:
		if strings.Contains(e, "Loop detected") {
			t.Fatalf("Loop detected: %v", e)
		}
	case <-time.After(250 * time.Millisecond):
		// We are ok
	}

	// Now make sure we got the warning
	select {
	case w := <-l.warnCh:
		if !strings.Contains(w, "Replacing connection from same server") {
			t.Fatalf("Unexpected warning: %v", w)
		}
	case <-time.After(time.Second):
		t.Fatal("Did not get expected warning")
	}

	checkLeafNodeConnected(t, s)
	checkLeafNodeConnected(t, sl)
}

func TestLeafNodeTwoRemotesBindToSameHubAccount(t *testing.T) {
	opts := DefaultOptions()
	opts.LeafNode.Host = "127.0.0.1"
	opts.LeafNode.Port = -1
	s := RunServer(opts)
	defer s.Shutdown()

	for _, test := range []struct {
		name    string
		account string
		fail    bool
	}{
		{"different local accounts", "b", false},
		{"same local accounts", "a", true},
	} {
		t.Run(test.name, func(t *testing.T) {
			conf := `
			listen: 127.0.0.1:-1
			cluster { name: ln22, listen: 127.0.0.1:-1 }
			accounts {
				a { users [ {user: a, password: a} ]}
				b { users [ {user: b, password: b} ]}
			}
			leafnodes {
				remotes = [
					{
						url: nats-leaf://127.0.0.1:%[1]d
						account: a
					}
					{
						url: nats-leaf://127.0.0.1:%[1]d
						account: %s
					}
				]
			}
			`
			lconf := createConfFile(t, []byte(fmt.Sprintf(conf, opts.LeafNode.Port, test.account)))

			lopts, err := ProcessConfigFile(lconf)
			if err != nil {
				t.Fatalf("Error loading config file: %v", err)
			}
			lopts.NoLog = false
			ln, err := NewServer(lopts)
			if err != nil {
				t.Fatalf("Error creating server: %v", err)
			}
			defer ln.Shutdown()
			l := &captureErrorLogger{errCh: make(chan string, 10)}
			ln.SetLogger(l, false, false)

			wg := sync.WaitGroup{}
			wg.Add(1)
			go func() {
				defer wg.Done()
				ln.Start()
			}()

			select {
			case err := <-l.errCh:
				if test.fail && !strings.Contains(err, DuplicateRemoteLeafnodeConnection.String()) {
					t.Fatalf("Did not get expected duplicate connection error: %v", err)
				} else if !test.fail && strings.Contains(err, DuplicateRemoteLeafnodeConnection.String()) {
					t.Fatalf("Incorrectly detected a duplicate connection: %v", err)
				}
			case <-time.After(250 * time.Millisecond):
				if test.fail {
					t.Fatal("Did not get expected error")
				}
			}
			ln.Shutdown()
			wg.Wait()
		})
	}
}

func TestLeafNodeNoDuplicateWithinCluster(t *testing.T) {
	// This set the cluster name to "abc"
	oSrv1 := DefaultOptions()
	oSrv1.ServerName = "srv1"
	oSrv1.LeafNode.Host = "127.0.0.1"
	oSrv1.LeafNode.Port = -1
	srv1 := RunServer(oSrv1)
	defer srv1.Shutdown()

	u, err := url.Parse(fmt.Sprintf("nats://127.0.0.1:%d", oSrv1.LeafNode.Port))
	if err != nil {
		t.Fatalf("Error parsing url: %v", err)
	}

	oLeaf1 := DefaultOptions()
	oLeaf1.ServerName = "leaf1"
	oLeaf1.Cluster.Name = "xyz"
	oLeaf1.LeafNode.Remotes = []*RemoteLeafOpts{{URLs: []*url.URL{u}}}
	leaf1 := RunServer(oLeaf1)
	defer leaf1.Shutdown()

	leaf1ClusterURL := fmt.Sprintf("nats://127.0.0.1:%d", oLeaf1.Cluster.Port)

	oLeaf2 := DefaultOptions()
	oLeaf2.ServerName = "leaf2"
	oLeaf2.Cluster.Name = "xyz"
	oLeaf2.LeafNode.Remotes = []*RemoteLeafOpts{{URLs: []*url.URL{u}}}
	oLeaf2.Routes = RoutesFromStr(leaf1ClusterURL)
	leaf2 := RunServer(oLeaf2)
	defer leaf2.Shutdown()

	checkClusterFormed(t, leaf1, leaf2)

	checkLeafNodeConnectedCount(t, srv1, 2)
	checkLeafNodeConnected(t, leaf1)
	checkLeafNodeConnected(t, leaf2)

	ncSrv1 := natsConnect(t, srv1.ClientURL())
	defer ncSrv1.Close()
	natsQueueSub(t, ncSrv1, "foo", "queue", func(m *nats.Msg) {
		m.Respond([]byte("from srv1"))
	})

	ncLeaf1 := natsConnect(t, leaf1.ClientURL())
	defer ncLeaf1.Close()
	natsQueueSub(t, ncLeaf1, "foo", "queue", func(m *nats.Msg) {
		m.Respond([]byte("from leaf1"))
	})

	ncLeaf2 := natsConnect(t, leaf2.ClientURL())
	defer ncLeaf2.Close()

	// Check that "foo" interest is available everywhere.
	// For this test, we want to make sure that the 2 queue subs are
	// registered on all servers, so we don't use checkSubInterest
	// which would simply return "true" if there is any interest on "foo".
	servers := []*Server{srv1, leaf1, leaf2}
	checkFor(t, time.Second, 15*time.Millisecond, func() error {
		for _, s := range servers {
			acc, err := s.LookupAccount(globalAccountName)
			if err != nil {
				return err
			}
			acc.mu.RLock()
			r := acc.sl.Match("foo")
			ok := len(r.qsubs) == 1 && len(r.qsubs[0]) == 2
			acc.mu.RUnlock()
			if !ok {
				return fmt.Errorf("interest not propagated on %q", s.Name())
			}
		}
		return nil
	})

	// Send requests (from leaf2). For this test to make sure that
	// there is no duplicate, we want to make sure that we check for
	// multiple replies and that the reply subject subscription has
	// been propagated everywhere.
	sub := natsSubSync(t, ncLeaf2, "reply_subj")
	natsFlush(t, ncLeaf2)

	// Here we have a single sub on "reply_subj" so using checkSubInterest is ok.
	checkSubInterest(t, srv1, globalAccountName, "reply_subj", time.Second)
	checkSubInterest(t, leaf1, globalAccountName, "reply_subj", time.Second)
	checkSubInterest(t, leaf2, globalAccountName, "reply_subj", time.Second)

	for i := 0; i < 5; i++ {
		// Now send the request
		natsPubReq(t, ncLeaf2, "foo", sub.Subject, []byte("req"))
		// Check that we get the reply
		replyMsg := natsNexMsg(t, sub, time.Second)
		// But make sure we received only 1!
		if otherReply, _ := sub.NextMsg(100 * time.Millisecond); otherReply != nil {
			t.Fatalf("Received duplicate reply, first was %q, followed by %q",
				replyMsg.Data, otherReply.Data)
		}
		// We also should have preferred the queue sub that is in the leaf cluster.
		if string(replyMsg.Data) != "from leaf1" {
			t.Fatalf("Expected reply from leaf1, got %q", replyMsg.Data)
		}
	}
}

func TestLeafNodeLMsgSplit(t *testing.T) {
	// This set the cluster name to "abc"
	oSrv1 := DefaultOptions()
	oSrv1.LeafNode.Host = "127.0.0.1"
	oSrv1.LeafNode.Port = -1
	srv1 := RunServer(oSrv1)
	defer srv1.Shutdown()

	oSrv2 := DefaultOptions()
	oSrv2.LeafNode.Host = "127.0.0.1"
	oSrv2.LeafNode.Port = -1
	oSrv2.Routes = RoutesFromStr(fmt.Sprintf("nats://127.0.0.1:%d", oSrv1.Cluster.Port))
	srv2 := RunServer(oSrv2)
	defer srv2.Shutdown()

	checkClusterFormed(t, srv1, srv2)

	u1, err := url.Parse(fmt.Sprintf("nats://127.0.0.1:%d", oSrv1.LeafNode.Port))
	if err != nil {
		t.Fatalf("Error parsing url: %v", err)
	}
	u2, err := url.Parse(fmt.Sprintf("nats://127.0.0.1:%d", oSrv2.LeafNode.Port))
	if err != nil {
		t.Fatalf("Error parsing url: %v", err)
	}

	oLeaf1 := DefaultOptions()
	oLeaf1.Cluster.Name = "xyz"
	oLeaf1.LeafNode.Remotes = []*RemoteLeafOpts{{URLs: []*url.URL{u1, u2}}}
	leaf1 := RunServer(oLeaf1)
	defer leaf1.Shutdown()

	oLeaf2 := DefaultOptions()
	oLeaf2.Cluster.Name = "xyz"
	oLeaf2.LeafNode.Remotes = []*RemoteLeafOpts{{URLs: []*url.URL{u1, u2}}}
	oLeaf2.Routes = RoutesFromStr(fmt.Sprintf("nats://127.0.0.1:%d", oLeaf1.Cluster.Port))
	leaf2 := RunServer(oLeaf2)
	defer leaf2.Shutdown()

	checkClusterFormed(t, leaf1, leaf2)

	checkLeafNodeConnected(t, leaf1)
	checkLeafNodeConnected(t, leaf2)

	ncSrv2 := natsConnect(t, srv2.ClientURL())
	defer ncSrv2.Close()
	natsQueueSub(t, ncSrv2, "foo", "queue", func(m *nats.Msg) {
		m.Respond([]byte("from srv2"))
	})

	// Check that "foo" interest is available everywhere.
	checkSubInterest(t, srv1, globalAccountName, "foo", time.Second)
	checkSubInterest(t, srv2, globalAccountName, "foo", time.Second)
	checkSubInterest(t, leaf1, globalAccountName, "foo", time.Second)
	checkSubInterest(t, leaf2, globalAccountName, "foo", time.Second)

	// Not required, but have a request payload that is more than 100 bytes
	reqPayload := make([]byte, 150)
	for i := 0; i < len(reqPayload); i++ {
		reqPayload[i] = byte((i % 26)) + 'A'
	}

	// Send repeated requests (from scratch) from leaf-2:
	sendReq := func() {
		t.Helper()

		ncLeaf2 := natsConnect(t, leaf2.ClientURL())
		defer ncLeaf2.Close()

		if _, err := ncLeaf2.Request("foo", reqPayload, time.Second); err != nil {
			t.Fatalf("Did not receive reply: %v", err)
		}
	}
	for i := 0; i < 100; i++ {
		sendReq()
	}
}

type parseRouteLSUnsubLogger struct {
	DummyLogger
	gotTrace chan struct{}
	gotErr   chan error
}

func (l *parseRouteLSUnsubLogger) Errorf(format string, v ...interface{}) {
	err := fmt.Errorf(format, v...)
	select {
	case l.gotErr <- err:
	default:
	}
}

func (l *parseRouteLSUnsubLogger) Tracef(format string, v ...interface{}) {
	trace := fmt.Sprintf(format, v...)
	if strings.Contains(trace, "LS- $G foo bar") {
		l.gotTrace <- struct{}{}
	}
}

func TestLeafNodeRouteParseLSUnsub(t *testing.T) {
	// This set the cluster name to "abc"
	oSrv1 := DefaultOptions()
	oSrv1.LeafNode.Host = "127.0.0.1"
	oSrv1.LeafNode.Port = -1
	srv1 := RunServer(oSrv1)
	defer srv1.Shutdown()

	l := &parseRouteLSUnsubLogger{gotTrace: make(chan struct{}, 1), gotErr: make(chan error, 1)}
	srv1.SetLogger(l, true, true)

	oSrv2 := DefaultOptions()
	oSrv2.LeafNode.Host = "127.0.0.1"
	oSrv2.LeafNode.Port = -1
	oSrv2.Routes = RoutesFromStr(fmt.Sprintf("nats://127.0.0.1:%d", oSrv1.Cluster.Port))
	srv2 := RunServer(oSrv2)
	defer srv2.Shutdown()

	checkClusterFormed(t, srv1, srv2)

	u2, err := url.Parse(fmt.Sprintf("nats://127.0.0.1:%d", oSrv2.LeafNode.Port))
	if err != nil {
		t.Fatalf("Error parsing url: %v", err)
	}

	oLeaf2 := DefaultOptions()
	oLeaf2.Cluster.Name = "xyz"
	oLeaf2.LeafNode.Remotes = []*RemoteLeafOpts{{URLs: []*url.URL{u2}}}
	leaf2 := RunServer(oLeaf2)
	defer leaf2.Shutdown()

	checkLeafNodeConnected(t, srv2)
	checkLeafNodeConnected(t, leaf2)

	ncLeaf2 := natsConnect(t, leaf2.ClientURL())
	defer ncLeaf2.Close()

	sub := natsQueueSubSync(t, ncLeaf2, "foo", "bar")
	// The issue was with the unsubscribe of this queue subscription
	natsUnsub(t, sub)

	// We should get the trace
	select {
	case <-l.gotTrace:
		// OK!
	case <-time.After(100 * time.Millisecond):
		t.Fatalf("Did not get LS- trace")
	}
	// And no error...
	select {
	case e := <-l.gotErr:
		t.Fatalf("There was an error on server 1: %q", e.Error())
	case <-time.After(100 * time.Millisecond):
		// OK!
	}
}

func TestLeafNodeOperatorBadCfg(t *testing.T) {
	sysAcc, err := nkeys.CreateAccount()
	require_NoError(t, err)
	sysAccPk, err := sysAcc.PublicKey()
	require_NoError(t, err)
	tmpDir := t.TempDir()

	configTmpl := `
		port: -1
		operator: %s
		system_account: %s
		resolver: {
			type: cache
			dir: '%s'
		}
		leafnodes: {
			%s
		}
	`

	cases := []struct {
		name      string
		errorText string
		cfg       string
	}{
		{
			name:      "Operator with Leafnode",
			errorText: "operator mode does not allow specifying user in leafnode config",
			cfg: `
			port: -1
			authorization {
				users = [{user: "u", password: "p"}]
			}`,
		},
		{
			name:      "Operator with NKey",
			errorText: "operator mode and non account nkeys are incompatible",
			cfg: `
			port: -1
			authorization {
				account: notankey
			}`,
		},
		{
			name: "Operator remote account NKeys",
			errorText: "operator mode requires account nkeys in remotes. " +
				"Please add an `account` key to each remote in your `leafnodes` section, to assign it to an account. " +
				"Each account value should be a 56 character public key, starting with the letter 'A'",
			cfg: `remotes: [{url: u}]`,
		},
	}
	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {

			conf := createConfFile(t, []byte(fmt.Sprintf(configTmpl, ojwt, sysAccPk, tmpDir, c.cfg)))
			opts := LoadConfig(conf)
			s, err := NewServer(opts)
			if err == nil {
				s.Shutdown()
				t.Fatal("Expected an error")
			}
			// Since the server cannot be stopped, since it did not start,
			// let's manually close the account resolver to avoid leaking go routines.
			opts.AccountResolver.Close()
			if err.Error() != c.errorText {
				t.Fatalf("Expected error %s but got %s", c.errorText, err)
			}
		})
	}
}

func TestLeafNodeTLSConfigReload(t *testing.T) {
	template := `
		listen: 127.0.0.1:-1
		leaf {
			listen: "127.0.0.1:-1"
			tls {
				cert_file: "../test/configs/certs/server-cert.pem"
				key_file:  "../test/configs/certs/server-key.pem"
				%s
				timeout: 2
				verify: true
			}
		}
	`
	confA := createConfFile(t, []byte(fmt.Sprintf(template, "")))

	srvA, optsA := RunServerWithConfig(confA)
	defer srvA.Shutdown()

	lg := &captureErrorLogger{errCh: make(chan string, 10)}
	srvA.SetLogger(lg, false, false)

	confB := createConfFile(t, []byte(fmt.Sprintf(`
		listen: -1
		leaf {
			remotes [
				{
					url: "tls://127.0.0.1:%d"
					tls {
						cert_file: "../test/configs/certs/server-cert.pem"
						key_file:  "../test/configs/certs/server-key.pem"
						ca_file:   "../test/configs/certs/ca.pem"
					}
				}
			]
		}
	`, optsA.LeafNode.Port)))

	optsB, err := ProcessConfigFile(confB)
	if err != nil {
		t.Fatalf("Error processing config file: %v", err)
	}
	optsB.LeafNode.ReconnectInterval = 50 * time.Millisecond
	optsB.NoLog, optsB.NoSigs = true, true

	srvB := RunServer(optsB)
	defer srvB.Shutdown()

	// Wait for the error
	select {
	case err := <-lg.errCh:
		// Since Go 1.18, we had to regenerate certs to not have to use GODEBUG="x509sha1=1"
		// But on macOS, with our test CA certs, no SCTs included, it will fail
		// for the reason "x509: “localhost” certificate is not standards compliant"
		// instead of "unknown authority".
		if !strings.Contains(err, "unknown") && !strings.Contains(err, "compliant") {
			t.Fatalf("Unexpected error: %v", err)
		}
	case <-time.After(2 * time.Second):
		t.Fatalf("Did not get TLS error")
	}

	// Add the CA to srvA
	reloadUpdateConfig(t, srvA, confA, fmt.Sprintf(template, `ca_file: "../test/configs/certs/ca.pem"`))

	// Now make sure that srvB can create a LN connection.
	checkFor(t, 3*time.Second, 10*time.Millisecond, func() error {
		if nln := srvB.NumLeafNodes(); nln != 1 {
			return fmt.Errorf("Number of leaf nodes is %d", nln)
		}
		return nil
	})
}

func TestLeafNodeTLSConfigReloadForRemote(t *testing.T) {
	confA := createConfFile(t, []byte(`
		listen: 127.0.0.1:-1
		leaf {
			listen: "127.0.0.1:-1"
			tls {
				cert_file: "../test/configs/certs/server-cert.pem"
				key_file:  "../test/configs/certs/server-key.pem"
				ca_file: "../test/configs/certs/ca.pem"
				timeout: 2
				verify: true
			}
		}
	`))

	srvA, optsA := RunServerWithConfig(confA)
	defer srvA.Shutdown()

	lg := &captureErrorLogger{errCh: make(chan string, 10)}
	srvA.SetLogger(lg, false, false)

	template := `
		listen: -1
		leaf {
			remotes [
				{
					url: "tls://127.0.0.1:%d"
					tls {
						cert_file: "../test/configs/certs/server-cert.pem"
						key_file:  "../test/configs/certs/server-key.pem"
						%s
					}
				}
			]
		}
	`
	confB := createConfFile(t, []byte(fmt.Sprintf(template, optsA.LeafNode.Port, "")))

	srvB, _ := RunServerWithConfig(confB)
	defer srvB.Shutdown()

	// Wait for the error
	select {
	case err := <-lg.errCh:
		if !strings.Contains(err, "bad certificate") {
			t.Fatalf("Unexpected error: %v", err)
		}
	case <-time.After(2 * time.Second):
		t.Fatalf("Did not get TLS error")
	}

	// Add the CA to srvB
	reloadUpdateConfig(t, srvB, confB, fmt.Sprintf(template, optsA.LeafNode.Port, `ca_file: "../test/configs/certs/ca.pem"`))

	// Now make sure that srvB can create a LN connection.
	checkFor(t, 2*time.Second, 10*time.Millisecond, func() error {
		if nln := srvB.NumLeafNodes(); nln != 1 {
			return fmt.Errorf("Number of leaf nodes is %d", nln)
		}
		return nil
	})
}

func testDefaultLeafNodeWSOptions() *Options {
	o := DefaultOptions()
	o.Websocket.Host = "127.0.0.1"
	o.Websocket.Port = -1
	o.Websocket.NoTLS = true
	o.LeafNode.Host = "127.0.0.1"
	o.LeafNode.Port = -1
	return o
}

func testDefaultRemoteLeafNodeWSOptions(t *testing.T, o *Options, tls bool) *Options {
	// Use some path in the URL.. we don't use that, but internally
	// the server will prefix the path with /leafnode so that the
	// WS webserver knows that it needs to create a LEAF connection.
	u, _ := url.Parse(fmt.Sprintf("ws://127.0.0.1:%d/some/path", o.Websocket.Port))
	lo := DefaultOptions()
	lo.Cluster.Name = "LN"
	remote := &RemoteLeafOpts{URLs: []*url.URL{u}}
	if tls {
		tc := &TLSConfigOpts{
			CertFile: "../test/configs/certs/server-cert.pem",
			KeyFile:  "../test/configs/certs/server-key.pem",
			CaFile:   "../test/configs/certs/ca.pem",
		}
		tlsConf, err := GenTLSConfig(tc)
		if err != nil {
			t.Fatalf("Error generating TLS config: %v", err)
		}
		// GenTLSConfig sets the CA in ClientCAs, but since here we act
		// as a client, set RootCAs...
		tlsConf.RootCAs = tlsConf.ClientCAs
		remote.TLSConfig = tlsConf
	}
	lo.LeafNode.Remotes = []*RemoteLeafOpts{remote}
	return lo
}

func TestLeafNodeWSMixURLs(t *testing.T) {
	for _, test := range []struct {
		name string
		urls []string
	}{
		{"mix 1", []string{"nats://127.0.0.1:1234", "ws://127.0.0.1:5678", "wss://127.0.0.1:9012"}},
		{"mix 2", []string{"ws://127.0.0.1:1234", "nats://127.0.0.1:5678", "wss://127.0.0.1:9012"}},
		{"mix 3", []string{"wss://127.0.0.1:1234", "ws://127.0.0.1:5678", "nats://127.0.0.1:9012"}},
		{"mix 4", []string{"ws://127.0.0.1:1234", "nats://127.0.0.1:9012"}},
		{"mix 5", []string{"nats://127.0.0.1:1234", "ws://127.0.0.1:9012"}},
		{"mix 6", []string{"wss://127.0.0.1:1234", "nats://127.0.0.1:9012"}},
		{"mix 7", []string{"nats://127.0.0.1:1234", "wss://127.0.0.1:9012"}},
	} {
		t.Run(test.name, func(t *testing.T) {
			o := DefaultOptions()
			remote := &RemoteLeafOpts{}
			urls := make([]*url.URL, 0, 3)
			for _, ustr := range test.urls {
				u, err := url.Parse(ustr)
				if err != nil {
					t.Fatalf("Error parsing url: %v", err)
				}
				urls = append(urls, u)
			}
			remote.URLs = urls
			o.LeafNode.Remotes = []*RemoteLeafOpts{remote}
			s, err := NewServer(o)
			if err == nil || !strings.Contains(err.Error(), "mix") {
				if s != nil {
					s.Shutdown()
				}
				t.Fatalf("Unexpected error: %v", err)
			}
		})
	}
}

type testConnTrackSize struct {
	sync.Mutex
	net.Conn
	sz int
}

func (c *testConnTrackSize) Write(p []byte) (int, error) {
	c.Lock()
	defer c.Unlock()
	n, err := c.Conn.Write(p)
	c.sz += n
	return n, err
}

func TestLeafNodeWSBasic(t *testing.T) {
	for _, test := range []struct {
		name              string
		masking           bool
		tls               bool
		acceptCompression bool
		remoteCompression bool
	}{
		{"masking plain no compression", true, false, false, false},
		{"masking plain compression", true, false, true, true},
		{"masking plain compression disagree", true, false, false, true},
		{"masking plain compression disagree 2", true, false, true, false},
		{"masking tls no compression", true, true, false, false},
		{"masking tls compression", true, true, true, true},
		{"masking tls compression disagree", true, true, false, true},
		{"masking tls compression disagree 2", true, true, true, false},
		{"no masking plain no compression", false, false, false, false},
		{"no masking plain compression", false, false, true, true},
		{"no masking plain compression disagree", false, false, false, true},
		{"no masking plain compression disagree 2", false, false, true, false},
		{"no masking tls no compression", false, true, false, false},
		{"no masking tls compression", false, true, true, true},
		{"no masking tls compression disagree", false, true, false, true},
		{"no masking tls compression disagree 2", false, true, true, false},
	} {
		t.Run(test.name, func(t *testing.T) {
			o := testDefaultLeafNodeWSOptions()
			o.Websocket.NoTLS = !test.tls
			if test.tls {
				tc := &TLSConfigOpts{
					CertFile: "../test/configs/certs/server-cert.pem",
					KeyFile:  "../test/configs/certs/server-key.pem",
					CaFile:   "../test/configs/certs/ca.pem",
				}
				tlsConf, err := GenTLSConfig(tc)
				if err != nil {
					t.Fatalf("Error generating TLS config: %v", err)
				}
				o.Websocket.TLSConfig = tlsConf
			}
			o.Websocket.Compression = test.acceptCompression
			s := RunServer(o)
			defer s.Shutdown()

			lo := testDefaultRemoteLeafNodeWSOptions(t, o, test.tls)
			lo.LeafNode.Remotes[0].Websocket.Compression = test.remoteCompression
			lo.LeafNode.Remotes[0].Websocket.NoMasking = !test.masking
			ln := RunServer(lo)
			defer ln.Shutdown()

			checkLeafNodeConnected(t, s)
			checkLeafNodeConnected(t, ln)

			var trackSizeConn *testConnTrackSize
			if !test.tls {
				var cln *client
				ln.mu.Lock()
				for _, l := range ln.leafs {
					cln = l
					break
				}
				ln.mu.Unlock()
				cln.mu.Lock()
				trackSizeConn = &testConnTrackSize{Conn: cln.nc}
				cln.nc = trackSizeConn
				cln.mu.Unlock()
			}

			nc1 := natsConnect(t, s.ClientURL())
			defer nc1.Close()
			sub1 := natsSubSync(t, nc1, "foo")
			natsFlush(t, nc1)

			checkSubInterest(t, ln, globalAccountName, "foo", time.Second)

			nc2 := natsConnect(t, ln.ClientURL())
			defer nc2.Close()
			msg1Payload := make([]byte, 2048)
			for i := 0; i < len(msg1Payload); i++ {
				msg1Payload[i] = 'A'
			}
			natsPub(t, nc2, "foo", msg1Payload)

			msg := natsNexMsg(t, sub1, time.Second)
			if !bytes.Equal(msg.Data, msg1Payload) {
				t.Fatalf("Invalid message: %q", msg.Data)
			}

			sub2 := natsSubSync(t, nc2, "bar")
			natsFlush(t, nc2)

			checkSubInterest(t, s, globalAccountName, "bar", time.Second)

			msg2Payload := make([]byte, 2048)
			for i := 0; i < len(msg2Payload); i++ {
				msg2Payload[i] = 'B'
			}
			natsPub(t, nc1, "bar", msg2Payload)

			msg = natsNexMsg(t, sub2, time.Second)
			if !bytes.Equal(msg.Data, msg2Payload) {
				t.Fatalf("Invalid message: %q", msg.Data)
			}

			if !test.tls {
				trackSizeConn.Lock()
				size := trackSizeConn.sz
				trackSizeConn.Unlock()

				if test.acceptCompression && test.remoteCompression {
					if size >= 1024 {
						t.Fatalf("Seems that there was no compression: size=%v", size)
					}
				} else if size < 2048 {
					t.Fatalf("Seems compression was on while it should not: size=%v", size)
				}
			}
		})
	}
}

func TestLeafNodeWSRemoteCompressAndMaskingOptions(t *testing.T) {
	for _, test := range []struct {
		name      string
		compress  bool
		compStr   string
		noMasking bool
		noMaskStr string
	}{
		{"compression masking", true, "true", false, "false"},
		{"compression no masking", true, "true", true, "true"},
		{"no compression masking", false, "false", false, "false"},
		{"no compression no masking", false, "false", true, "true"},
	} {
		t.Run(test.name, func(t *testing.T) {
			conf := createConfFile(t, []byte(fmt.Sprintf(`
				port: -1
				leafnodes {
					remotes [
						{url: "ws://127.0.0.1:1234", ws_compression: %s, ws_no_masking: %s}
					]
				}
			`, test.compStr, test.noMaskStr)))
			o, err := ProcessConfigFile(conf)
			if err != nil {
				t.Fatalf("Error loading conf: %v", err)
			}
			if nr := len(o.LeafNode.Remotes); nr != 1 {
				t.Fatalf("Expected 1 remote, got %v", nr)
			}
			r := o.LeafNode.Remotes[0]
			if cur := r.Websocket.Compression; cur != test.compress {
				t.Fatalf("Expected compress to be %v, got %v", test.compress, cur)
			}
			if cur := r.Websocket.NoMasking; cur != test.noMasking {
				t.Fatalf("Expected ws_masking to be %v, got %v", test.noMasking, cur)
			}
		})
	}
}

func TestLeafNodeWSNoMaskingRejected(t *testing.T) {
	wsTestRejectNoMasking = true
	defer func() { wsTestRejectNoMasking = false }()

	o := testDefaultLeafNodeWSOptions()
	s := RunServer(o)
	defer s.Shutdown()

	lo := testDefaultRemoteLeafNodeWSOptions(t, o, false)
	lo.LeafNode.Remotes[0].Websocket.NoMasking = true
	ln := RunServer(lo)
	defer ln.Shutdown()

	checkLeafNodeConnected(t, s)
	checkLeafNodeConnected(t, ln)

	var cln *client
	ln.mu.Lock()
	for _, l := range ln.leafs {
		cln = l
		break
	}
	ln.mu.Unlock()

	cln.mu.Lock()
	maskWrite := cln.ws.maskwrite
	cln.mu.Unlock()

	if !maskWrite {
		t.Fatal("Leafnode remote connection should mask writes, it does not")
	}
}

func TestLeafNodeWSFailedConnection(t *testing.T) {
	o := testDefaultLeafNodeWSOptions()
	s := RunServer(o)
	defer s.Shutdown()

	lo := testDefaultRemoteLeafNodeWSOptions(t, o, true)
	lo.LeafNode.ReconnectInterval = 100 * time.Millisecond
	ln := RunServer(lo)
	defer ln.Shutdown()

	el := &captureErrorLogger{errCh: make(chan string, 100)}
	ln.SetLogger(el, false, false)

	select {
	case err := <-el.errCh:
		if !strings.Contains(err, "handshake error") {
			t.Fatalf("Unexpected error: %v", err)
		}
	case <-time.After(time.Second):
		t.Fatal("No error reported!")
	}
	ln.Shutdown()
	s.Shutdown()

	lst, err := natsListen("tcp", "127.0.0.1:0")
	if err != nil {
		t.Fatalf("Error starting listener: %v", err)
	}
	defer lst.Close()

	wg := sync.WaitGroup{}
	wg.Add(2)

	go func() {
		defer wg.Done()

		for i := 0; i < 10; i++ {
			c, err := lst.Accept()
			if err != nil {
				return
			}
			time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
			if rand.Intn(2) == 1 {
				c.Write([]byte("something\r\n"))
			}
			c.Close()
		}
	}()

	time.Sleep(100 * time.Millisecond)

	port := lst.Addr().(*net.TCPAddr).Port
	u, _ := url.Parse(fmt.Sprintf("ws://127.0.0.1:%d", port))
	lo = DefaultOptions()
	lo.LeafNode.Remotes = []*RemoteLeafOpts{{URLs: []*url.URL{u}}}
	lo.LeafNode.ReconnectInterval = 10 * time.Millisecond
	ln, _ = NewServer(lo)
	el = &captureErrorLogger{errCh: make(chan string, 100)}
	ln.SetLogger(el, false, false)

	go func() {
		ln.Start()
		wg.Done()
	}()

	timeout := time.NewTimer(time.Second)
	for i := 0; i < 10; i++ {
		select {
		case err := <-el.errCh:
			if !strings.Contains(err, "Error soliciting") {
				t.Fatalf("Unexpected error: %v", err)
			}
		case <-timeout.C:
			t.Fatal("No error reported!")
		}
	}
	ln.Shutdown()
	lst.Close()
	wg.Wait()
}

func TestLeafNodeWSAuth(t *testing.T) {
	template := `
		port: -1
		authorization {
			users [
				{user: "user", pass: "puser", connection_types: ["%s"]}
				{user: "leaf", pass: "pleaf", connection_types: ["%s"%s]}
			]
		}
		websocket {
			port: -1
			no_tls: true
		}
		leafnodes {
			port: -1
		}
	`
	s, o, conf := runReloadServerWithContent(t,
		[]byte(fmt.Sprintf(template, jwt.ConnectionTypeStandard, jwt.ConnectionTypeLeafnode, "")))
	defer s.Shutdown()

	l := &captureErrorLogger{errCh: make(chan string, 10)}
	s.SetLogger(l, false, false)

	lo := testDefaultRemoteLeafNodeWSOptions(t, o, false)
	u, _ := url.Parse(fmt.Sprintf("ws://leaf:pleaf@127.0.0.1:%d", o.Websocket.Port))
	remote := &RemoteLeafOpts{URLs: []*url.URL{u}}
	lo.LeafNode.Remotes = []*RemoteLeafOpts{remote}
	lo.LeafNode.ReconnectInterval = 50 * time.Millisecond
	ln := RunServer(lo)
	defer ln.Shutdown()

	var lasterr string
	tm := time.NewTimer(2 * time.Second)
	for done := false; !done; {
		select {
		case lasterr = <-l.errCh:
			if strings.Contains(lasterr, "authentication") {
				done = true
			}
		case <-tm.C:
			t.Fatalf("Expected auth error, got %v", lasterr)
		}
	}

	ws := fmt.Sprintf(`, "%s"`, jwt.ConnectionTypeLeafnodeWS)
	reloadUpdateConfig(t, s, conf, fmt.Sprintf(template,
		jwt.ConnectionTypeStandard, jwt.ConnectionTypeLeafnode, ws))

	checkLeafNodeConnected(t, s)
	checkLeafNodeConnected(t, ln)

	nc1 := natsConnect(t, fmt.Sprintf("nats://user:puser@127.0.0.1:%d", o.Port))
	defer nc1.Close()

	sub := natsSubSync(t, nc1, "foo")
	natsFlush(t, nc1)

	checkSubInterest(t, ln, globalAccountName, "foo", time.Second)

	nc2 := natsConnect(t, ln.ClientURL())
	defer nc2.Close()

	natsPub(t, nc2, "foo", []byte("msg1"))
	msg := natsNexMsg(t, sub, time.Second)

	if md := string(msg.Data); md != "msg1" {
		t.Fatalf("Invalid message: %q", md)
	}
}

func TestLeafNodeWSGossip(t *testing.T) {
	o1 := testDefaultLeafNodeWSOptions()
	s1 := RunServer(o1)
	defer s1.Shutdown()

	// Now connect from a server that knows only about s1
	lo := testDefaultRemoteLeafNodeWSOptions(t, o1, false)
	lo.LeafNode.ReconnectInterval = 15 * time.Millisecond
	ln := RunServer(lo)
	defer ln.Shutdown()

	checkLeafNodeConnected(t, s1)
	checkLeafNodeConnected(t, ln)

	// Now add a routed server to s1
	o2 := testDefaultLeafNodeWSOptions()
	o2.Routes = RoutesFromStr(fmt.Sprintf("nats://127.0.0.1:%d", o1.Cluster.Port))
	s2 := RunServer(o2)
	defer s2.Shutdown()

	// Wait for cluster to form
	checkClusterFormed(t, s1, s2)

	// Now shutdown s1 and check that ln is able to reconnect to s2.
	s1.Shutdown()

	checkLeafNodeConnected(t, s2)
	checkLeafNodeConnected(t, ln)

	// Make sure that the reconnection was as a WS connection, not simply to
	// the regular LN port.
	var s2lc *client
	s2.mu.Lock()
	for _, l := range s2.leafs {
		s2lc = l
		break
	}
	s2.mu.Unlock()

	s2lc.mu.Lock()
	isWS := s2lc.isWebsocket()
	s2lc.mu.Unlock()

	if !isWS {
		t.Fatal("Leafnode connection is not websocket!")
	}
}

// This test was showing an issue if one set maxBufSize to very small value,
// such as maxBufSize = 10. With such small value, we would get a corruption
// in that LMSG would arrive with missing bytes. We are now always making
// a copy when dealing with messages that are bigger than maxBufSize.
func TestLeafNodeWSNoBufferCorruption(t *testing.T) {
	o := testDefaultLeafNodeWSOptions()
	s := RunServer(o)
	defer s.Shutdown()

	lo1 := testDefaultRemoteLeafNodeWSOptions(t, o, false)
	lo1.LeafNode.ReconnectInterval = 15 * time.Millisecond
	ln1 := RunServer(lo1)
	defer ln1.Shutdown()

	lo2 := DefaultOptions()
	lo2.Cluster.Name = "LN"
	lo2.Routes = RoutesFromStr(fmt.Sprintf("nats://127.0.0.1:%d", lo1.Cluster.Port))
	ln2 := RunServer(lo2)
	defer ln2.Shutdown()

	checkClusterFormed(t, ln1, ln2)

	checkLeafNodeConnected(t, s)
	checkLeafNodeConnected(t, ln1)

	nc := natsConnect(t, s.ClientURL())
	defer nc.Close()
	sub := natsSubSync(t, nc, "foo")

	nc1 := natsConnect(t, ln1.ClientURL())
	defer nc1.Close()

	nc2 := natsConnect(t, ln2.ClientURL())
	defer nc2.Close()
	sub2 := natsSubSync(t, nc2, "foo")

	checkSubInterest(t, s, globalAccountName, "foo", time.Second)
	checkSubInterest(t, ln2, globalAccountName, "foo", time.Second)
	checkSubInterest(t, ln1, globalAccountName, "foo", time.Second)

	payload := make([]byte, 100*1024)
	for i := 0; i < len(payload); i++ {
		payload[i] = 'A'
	}
	natsPub(t, nc1, "foo", payload)

	checkMsgRcv := func(sub *nats.Subscription) {
		msg := natsNexMsg(t, sub, time.Second)
		if !bytes.Equal(msg.Data, payload) {
			t.Fatalf("Invalid message content: %q", msg.Data)
		}
	}
	checkMsgRcv(sub2)
	checkMsgRcv(sub)
}

func TestLeafNodeWSRemoteNoTLSBlockWithWSSProto(t *testing.T) {
	o := testDefaultLeafNodeWSOptions()
	o.Websocket.NoTLS = false
	tc := &TLSConfigOpts{
		CertFile: "../test/configs/certs/server-cert.pem",
		KeyFile:  "../test/configs/certs/server-key.pem",
		CaFile:   "../test/configs/certs/ca.pem",
	}
	tlsConf, err := GenTLSConfig(tc)
	if err != nil {
		t.Fatalf("Error generating TLS config: %v", err)
	}
	o.Websocket.TLSConfig = tlsConf
	s := RunServer(o)
	defer s.Shutdown()

	// The test will make sure that if the protocol is "wss://", a TLS handshake must
	// be initiated, regardless of the presence of a TLS config block in config file
	// or here directly.
	// A bug was causing the absence of TLS config block to initiate a non TLS connection
	// even if "wss://" proto was specified, which would lead to "invalid websocket connection"
	// errors in the log.
	// With the fix, the connection will fail because the remote will fail to verify
	// the root CA, but at least, we will make sure that this is not an "invalid websocket connection"

	u, _ := url.Parse(fmt.Sprintf("wss://127.0.0.1:%d/some/path", o.Websocket.Port))
	lo := DefaultOptions()
	lo.Cluster.Name = "LN"
	remote := &RemoteLeafOpts{URLs: []*url.URL{u}}
	lo.LeafNode.Remotes = []*RemoteLeafOpts{remote}
	lo.LeafNode.ReconnectInterval = 100 * time.Millisecond
	ln := RunServer(lo)
	defer ln.Shutdown()

	l := &captureErrorLogger{errCh: make(chan string, 10)}
	ln.SetLogger(l, false, false)

	select {
	case e := <-l.errCh:
		if strings.Contains(e, "invalid websocket connection") {
			t.Fatalf("The remote did not try to create a TLS connection: %v", e)
		}
		// OK!
		return
	case <-time.After(2 * time.Second):
		t.Fatal("Connection should fail")
	}
}

func TestLeafNodeWSNoAuthUser(t *testing.T) {
	conf := createConfFile(t, []byte(`
	port: -1
	accounts {
		A { users [ {user: a, password: a} ]}
		B { users [ {user: b, password: b} ]}
	}
	websocket {
		port: -1
		no_tls: true
		no_auth_user: a
	}
	leafnodes {
		port: -1
	}
	`))
	s, o := RunServerWithConfig(conf)
	defer s.Shutdown()

	nc1 := natsConnect(t, fmt.Sprintf("nats://a:a@127.0.0.1:%d", o.Port))
	defer nc1.Close()

	lconf := createConfFile(t, []byte(fmt.Sprintf(`
	port: -1
	accounts {
		A { users [ {user: a, password: a} ]}
		B { users [ {user: b, password: b} ]}
	}
	leafnodes {
		remotes [
			{
				url: "ws://127.0.0.1:%d"
				account: A
			}
		]
	}
	`, o.Websocket.Port)))

	ln, lo := RunServerWithConfig(lconf)
	defer ln.Shutdown()

	checkLeafNodeConnected(t, s)
	checkLeafNodeConnected(t, ln)

	nc2 := natsConnect(t, fmt.Sprintf("nats://a:a@127.0.0.1:%d", lo.Port))
	defer nc2.Close()

	sub := natsSubSync(t, nc2, "foo")
	natsFlush(t, nc2)

	checkSubInterest(t, s, "A", "foo", time.Second)

	natsPub(t, nc1, "foo", []byte("msg1"))
	msg := natsNexMsg(t, sub, time.Second)

	if md := string(msg.Data); md != "msg1" {
		t.Fatalf("Invalid message: %q", md)
	}
}

func TestLeafNodeStreamImport(t *testing.T) {
	o1 := DefaultOptions()
	o1.LeafNode.Port = -1
	accA := NewAccount("A")
	o1.Accounts = []*Account{accA}
	o1.Users = []*User{{Username: "a", Password: "a", Account: accA}}
	o1.LeafNode.Account = "A"
	o1.NoAuthUser = "a"
	s1 := RunServer(o1)
	defer s1.Shutdown()

	o2 := DefaultOptions()
	o2.LeafNode.Port = -1
	o2.Cluster.Name = "xyz"

	accB := NewAccount("B")
	if err := accB.AddStreamExport(">", nil); err != nil {
		t.Fatalf("Error adding stream export: %v", err)
	}

	accC := NewAccount("C")
	if err := accC.AddStreamImport(accB, ">", ""); err != nil {
		t.Fatalf("Error adding stream import: %v", err)
	}

	o2.Accounts = []*Account{accB, accC}
	o2.Users = []*User{{Username: "b", Password: "b", Account: accB}, {Username: "c", Password: "c", Account: accC}}
	o2.NoAuthUser = "b"
	u, err := url.Parse(fmt.Sprintf("nats://127.0.0.1:%d", o1.LeafNode.Port))
	if err != nil {
		t.Fatalf("Error parsing url: %v", err)
	}
	o2.LeafNode.Remotes = []*RemoteLeafOpts{{URLs: []*url.URL{u}, LocalAccount: "C"}}
	s2 := RunServer(o2)
	defer s2.Shutdown()

	nc1 := natsConnect(t, s1.ClientURL())
	defer nc1.Close()

	sub := natsSubSync(t, nc1, "a")

	checkSubInterest(t, s2, "C", "a", time.Second)

	nc2 := natsConnect(t, s2.ClientURL())
	defer nc2.Close()

	natsPub(t, nc2, "a", []byte("hello?"))

	natsNexMsg(t, sub, time.Second)
}

func TestLeafNodeRouteSubWithOrigin(t *testing.T) {
	lo1 := DefaultOptions()
	lo1.LeafNode.Host = "127.0.0.1"
	lo1.LeafNode.Port = -1
	lo1.Cluster.Name = "local"
	lo1.Cluster.Host = "127.0.0.1"
	lo1.Cluster.Port = -1
	l1 := RunServer(lo1)
	defer l1.Shutdown()

	lo2 := DefaultOptions()
	lo2.LeafNode.Host = "127.0.0.1"
	lo2.LeafNode.Port = -1
	lo2.Cluster.Name = "local"
	lo2.Cluster.Host = "127.0.0.1"
	lo2.Cluster.Port = -1
	lo2.Routes = RoutesFromStr(fmt.Sprintf("nats://127.0.0.1:%d", lo1.Cluster.Port))
	l2 := RunServer(lo2)
	defer l2.Shutdown()

	checkClusterFormed(t, l1, l2)

	u1, _ := url.Parse(fmt.Sprintf("nats://127.0.0.1:%d", lo1.LeafNode.Port))
	urls := []*url.URL{u1}

	ro1 := DefaultOptions()
	ro1.Cluster.Name = "remote"
	ro1.Cluster.Host = "127.0.0.1"
	ro1.Cluster.Port = -1
	ro1.LeafNode.ReconnectInterval = 50 * time.Millisecond
	ro1.LeafNode.Remotes = []*RemoteLeafOpts{{URLs: urls}}
	r1 := RunServer(ro1)
	defer r1.Shutdown()

	checkLeafNodeConnected(t, r1)

	nc := natsConnect(t, r1.ClientURL(), nats.NoReconnect())
	defer nc.Close()
	natsSubSync(t, nc, "foo")
	natsQueueSubSync(t, nc, "bar", "baz")
	checkSubInterest(t, l2, globalAccountName, "foo", time.Second)
	checkSubInterest(t, l2, globalAccountName, "bar", time.Second)

	// Now shutdown the leafnode and check that any subscription for $G on l2 are gone.
	r1.Shutdown()
	checkFor(t, time.Second, 15*time.Millisecond, func() error {
		acc := l2.GlobalAccount()
		if n := acc.TotalSubs(); n != 5 {
			return fmt.Errorf("Account %q should have 5 subs, got %v", acc.GetName(), n)
		}
		return nil
	})
}

func TestLeafNodeLoopDetectionWithMultipleClusters(t *testing.T) {
	lo1 := DefaultOptions()
	lo1.LeafNode.Host = "127.0.0.1"
	lo1.LeafNode.Port = -1
	lo1.Cluster.Name = "local"
	lo1.Cluster.Host = "127.0.0.1"
	lo1.Cluster.Port = -1
	l1 := RunServer(lo1)
	defer l1.Shutdown()

	lo2 := DefaultOptions()
	lo2.LeafNode.Host = "127.0.0.1"
	lo2.LeafNode.Port = -1
	lo2.Cluster.Name = "local"
	lo2.Cluster.Host = "127.0.0.1"
	lo2.Cluster.Port = -1
	lo2.Routes = RoutesFromStr(fmt.Sprintf("nats://127.0.0.1:%d", lo1.Cluster.Port))
	l2 := RunServer(lo2)
	defer l2.Shutdown()

	checkClusterFormed(t, l1, l2)

	ro1 := DefaultOptions()
	ro1.Cluster.Name = "remote"
	ro1.Cluster.Host = "127.0.0.1"
	ro1.Cluster.Port = -1
	ro1.LeafNode.ReconnectInterval = 50 * time.Millisecond
	ro1.LeafNode.Remotes = []*RemoteLeafOpts{{URLs: []*url.URL{
		{Scheme: "nats", Host: fmt.Sprintf("127.0.0.1:%d", lo1.LeafNode.Port)},
		{Scheme: "nats", Host: fmt.Sprintf("127.0.0.1:%d", lo2.LeafNode.Port)},
	}}}
	r1 := RunServer(ro1)
	defer r1.Shutdown()

	l := &captureErrorLogger{errCh: make(chan string, 100)}
	r1.SetLogger(l, false, false)

	ro2 := DefaultOptions()
	ro2.Cluster.Name = "remote"
	ro2.Cluster.Host = "127.0.0.1"
	ro2.Cluster.Port = -1
	ro2.Routes = RoutesFromStr(fmt.Sprintf("nats://127.0.0.1:%d", ro1.Cluster.Port))
	ro2.LeafNode.ReconnectInterval = 50 * time.Millisecond
	ro2.LeafNode.Remotes = []*RemoteLeafOpts{{URLs: []*url.URL{
		{Scheme: "nats", Host: fmt.Sprintf("127.0.0.1:%d", lo1.LeafNode.Port)},
		{Scheme: "nats", Host: fmt.Sprintf("127.0.0.1:%d", lo2.LeafNode.Port)},
	}}}
	r2 := RunServer(ro2)
	defer r2.Shutdown()

	checkClusterFormed(t, r1, r2)
	checkLeafNodeConnected(t, r1)
	checkLeafNodeConnected(t, r2)

	l1.Shutdown()

	// Now wait for r1 and r2 to reconnect, they should not have a problem of loop detection.
	checkLeafNodeConnected(t, r1)
	checkLeafNodeConnected(t, r2)

	// Wait and make sure we don't have a loop error
	timeout := time.NewTimer(500 * time.Millisecond)
	for {
		select {
		case err := <-l.errCh:
			if strings.Contains(err, "Loop detected") {
				t.Fatal(err)
			}
		case <-timeout.C:
			// OK, we are done.
			return
		}
	}
}

func TestLeafNodeUnsubOnRouteDisconnect(t *testing.T) {
	lo1 := DefaultOptions()
	lo1.LeafNode.Host = "127.0.0.1"
	lo1.LeafNode.Port = -1
	lo1.Cluster.Name = "local"
	lo1.Cluster.Host = "127.0.0.1"
	lo1.Cluster.Port = -1
	l1 := RunServer(lo1)
	defer l1.Shutdown()

	lo2 := DefaultOptions()
	lo2.LeafNode.Host = "127.0.0.1"
	lo2.LeafNode.Port = -1
	lo2.Cluster.Name = "local"
	lo2.Cluster.Host = "127.0.0.1"
	lo2.Cluster.Port = -1
	lo2.Routes = RoutesFromStr(fmt.Sprintf("nats://127.0.0.1:%d", lo1.Cluster.Port))
	l2 := RunServer(lo2)
	defer l2.Shutdown()

	checkClusterFormed(t, l1, l2)

	u1, _ := url.Parse(fmt.Sprintf("nats://127.0.0.1:%d", lo1.LeafNode.Port))
	u2, _ := url.Parse(fmt.Sprintf("nats://127.0.0.1:%d", lo2.LeafNode.Port))
	urls := []*url.URL{u1, u2}

	ro1 := DefaultOptions()
	// DefaultOptions sets a cluster name, so make sure they are different.
	// Also, we don't have r1 and r2 clustered in this test, so set port to 0.
	ro1.Cluster.Name = _EMPTY_
	ro1.Cluster.Port = 0
	ro1.LeafNode.ReconnectInterval = 50 * time.Millisecond
	ro1.LeafNode.Remotes = []*RemoteLeafOpts{{URLs: urls}}
	r1 := RunServer(ro1)
	defer r1.Shutdown()

	ro2 := DefaultOptions()
	ro1.Cluster.Name = _EMPTY_
	ro2.Cluster.Port = 0
	ro2.LeafNode.ReconnectInterval = 50 * time.Millisecond
	// Have this one point only to l2
	ro2.LeafNode.Remotes = []*RemoteLeafOpts{{URLs: []*url.URL{u2}}}
	r2 := RunServer(ro2)
	defer r2.Shutdown()

	checkLeafNodeConnected(t, r1)
	checkLeafNodeConnected(t, r2)

	// Create a subscription on r1.
	nc := natsConnect(t, r1.ClientURL())
	defer nc.Close()
	sub := natsSubSync(t, nc, "foo")
	natsFlush(t, nc)

	checkSubInterest(t, l2, globalAccountName, "foo", time.Second)
	checkSubInterest(t, r2, globalAccountName, "foo", time.Second)

	nc2 := natsConnect(t, r2.ClientURL())
	defer nc2.Close()
	natsPub(t, nc, "foo", []byte("msg1"))

	// Check message received
	natsNexMsg(t, sub, time.Second)

	// Now shutdown l1, l2 should update subscription interest to r2.
	// When r1 reconnects to l2, subscription should be updated too.
	l1.Shutdown()

	// Wait a bit (so that the check of interest is not OK just because
	// the route would not have been yet detected as broken), and check
	// interest still present on r2, l2.
	time.Sleep(100 * time.Millisecond)
	checkSubInterest(t, l2, globalAccountName, "foo", time.Second)
	checkSubInterest(t, r2, globalAccountName, "foo", time.Second)

	// Check again that message received ok
	natsPub(t, nc, "foo", []byte("msg2"))
	natsNexMsg(t, sub, time.Second)

	// Now close client. Interest should disappear on r2. Due to a bug,
	// it was not.
	nc.Close()

	checkFor(t, time.Second, 15*time.Millisecond, func() error {
		acc := r2.GlobalAccount()
		if n := acc.Interest("foo"); n != 0 {
			return fmt.Errorf("Still interest on subject: %v", n)
		}
		return nil
	})
}

func TestLeafNodeNoPingBeforeConnect(t *testing.T) {
	o := DefaultOptions()
	o.LeafNode.Port = -1
	o.LeafNode.AuthTimeout = 0.5
	// For this test we need to disable compression, because we do use
	// the ping timer instead of the auth timer before the negotiation
	// is complete.
	o.LeafNode.Compression.Mode = CompressionOff
	s := RunServer(o)
	defer s.Shutdown()

	addr := fmt.Sprintf("127.0.0.1:%d", o.LeafNode.Port)
	c, err := net.Dial("tcp", addr)
	if err != nil {
		t.Fatalf("Error on dial: %v", err)
	}
	defer c.Close()

	// Read the info
	br := bufio.NewReader(c)
	c.SetReadDeadline(time.Now().Add(time.Second))
	l, _, err := br.ReadLine()
	if err != nil {
		t.Fatalf("Error on read: %v", err)
	}
	if !strings.HasPrefix(string(l), "INFO") {
		t.Fatalf("Wrong proto: %q", l)
	}

	var leaf *client
	checkFor(t, time.Second, 15*time.Millisecond, func() error {
		s.grMu.Lock()
		for _, l := range s.grTmpClients {
			leaf = l
			break
		}
		s.grMu.Unlock()
		if leaf == nil {
			return fmt.Errorf("No leaf connection found")
		}
		return nil
	})

	// Make sure that ping timer is not set
	leaf.mu.Lock()
	ptmrSet := leaf.ping.tmr != nil
	leaf.mu.Unlock()

	if ptmrSet {
		t.Fatal("Ping timer was set before CONNECT was processed")
	}

	// Send CONNECT
	if _, err := c.Write([]byte("CONNECT {}\r\n")); err != nil {
		t.Fatalf("Error writing connect: %v", err)
	}

	// Check that we correctly set the timer now
	checkFor(t, time.Second, 15*time.Millisecond, func() error {
		leaf.mu.Lock()
		ptmrSet := leaf.ping.tmr != nil
		leaf.mu.Unlock()
		if !ptmrSet {
			return fmt.Errorf("Timer still not set")
		}
		return nil
	})

	// Reduce the first ping..
	leaf.mu.Lock()
	leaf.ping.tmr.Reset(15 * time.Millisecond)
	leaf.mu.Unlock()

	// Now consume that PING (we may get LS+, etc..)
	for {
		c.SetReadDeadline(time.Now().Add(time.Second))
		l, _, err = br.ReadLine()
		if err != nil {
			t.Fatalf("Error on read: %v", err)
		}
		if strings.HasPrefix(string(l), "PING") {
			checkLeafNodeConnected(t, s)
			return
		}
	}
}

func TestLeafNodeNoMsgLoop(t *testing.T) {
	hubConf := `
		listen: "127.0.0.1:-1"
		accounts {
			FOO {
				users [
					{username: leaf, password: pass}
					{username: user, password: pass}
				]
			}
		}
		cluster {
			name: "hub"
			listen: "127.0.0.1:-1"
			%s
		}
		leafnodes {
			listen: "127.0.0.1:-1"
			authorization {
				account: FOO
			}
		}
	`
	configS1 := createConfFile(t, []byte(fmt.Sprintf(hubConf, "")))
	s1, o1 := RunServerWithConfig(configS1)
	defer s1.Shutdown()

	configS2S3 := createConfFile(t, []byte(fmt.Sprintf(hubConf, fmt.Sprintf(`routes: ["nats://127.0.0.1:%d"]`, o1.Cluster.Port))))
	s2, o2 := RunServerWithConfig(configS2S3)
	defer s2.Shutdown()

	s3, _ := RunServerWithConfig(configS2S3)
	defer s3.Shutdown()

	checkClusterFormed(t, s1, s2, s3)

	contentLN := `
		listen: "127.0.0.1:%d"
		accounts {
			FOO {
				users [
					{username: leaf, password: pass}
					{username: user, password: pass}
				]
			}
		}
		leafnodes {
			remotes = [
				{
					url: "nats://leaf:pass@127.0.0.1:%d"
					account: FOO
				}
			]
		}
	`
	lnconf := createConfFile(t, []byte(fmt.Sprintf(contentLN, -1, o1.LeafNode.Port)))
	sl1, slo1 := RunServerWithConfig(lnconf)
	defer sl1.Shutdown()

	sl2, slo2 := RunServerWithConfig(lnconf)
	defer sl2.Shutdown()

	checkLeafNodeConnected(t, sl1)
	checkLeafNodeConnected(t, sl2)

	// Create users on each leafnode
	nc1, err := nats.Connect(fmt.Sprintf("nats://user:pass@127.0.0.1:%d", slo1.Port))
	if err != nil {
		t.Fatalf("Error on connect: %v", err)
	}
	defer nc1.Close()

	rch := make(chan struct{}, 1)
	nc2, err := nats.Connect(
		fmt.Sprintf("nats://user:pass@127.0.0.1:%d", slo2.Port),
		nats.ReconnectWait(50*time.Millisecond),
		nats.ReconnectHandler(func(_ *nats.Conn) {
			rch <- struct{}{}
		}),
	)
	if err != nil {
		t.Fatalf("Error on connect: %v", err)
	}
	defer nc2.Close()

	// Create queue subs on sl2
	nc2.QueueSubscribe("foo", "bar", func(_ *nats.Msg) {})
	nc2.QueueSubscribe("foo", "bar", func(_ *nats.Msg) {})
	nc2.Flush()

	// Wait for interest to propagate to sl1
	checkSubInterest(t, sl1, "FOO", "foo", 250*time.Millisecond)

	// Create sub on sl1
	ch := make(chan *nats.Msg, 10)
	nc1.Subscribe("foo", func(m *nats.Msg) {
		select {
		case ch <- m:
		default:
		}
	})
	nc1.Flush()

	checkSubInterest(t, sl2, "FOO", "foo", 250*time.Millisecond)

	// Produce from sl1
	nc1.Publish("foo", []byte("msg1"))

	// Check message is received by plain sub
	select {
	case <-ch:
	case <-time.After(time.Second):
		t.Fatalf("Did not receive message")
	}

	// Restart leaf node, this time make sure we connect to 2nd server.
	sl2.Shutdown()

	// Use config file but this time reuse the client port and set the 2nd server for
	// the remote leaf node port.
	lnconf = createConfFile(t, []byte(fmt.Sprintf(contentLN, slo2.Port, o2.LeafNode.Port)))
	sl2, _ = RunServerWithConfig(lnconf)
	defer sl2.Shutdown()

	checkLeafNodeConnected(t, sl2)

	// Wait for client to reconnect
	select {
	case <-rch:
	case <-time.After(time.Second):
		t.Fatalf("Did not reconnect")
	}

	// Produce a new messages
	for i := 0; i < 10; i++ {
		nc1.Publish("foo", []byte(fmt.Sprintf("msg%d", 2+i)))

		// Check sub receives 1 message
		select {
		case <-ch:
		case <-time.After(time.Second):
			t.Fatalf("Did not receive message")
		}
		// Check that there is no more...
		select {
		case m := <-ch:
			t.Fatalf("Loop: received second message %s", m.Data)
		case <-time.After(50 * time.Millisecond):
			// OK
		}
	}
}

func TestLeafNodeInterestPropagationDaisychain(t *testing.T) {
	aTmpl := `
		port: %d
		leafnodes {
		  port: %d
		}
		`

	confA := createConfFile(t, []byte(fmt.Sprintf(aTmpl, -1, -1)))
	sA, _ := RunServerWithConfig(confA)
	defer sA.Shutdown()

	aPort := sA.opts.Port
	aLeafPort := sA.opts.LeafNode.Port

	confB := createConfFile(t, []byte(fmt.Sprintf(`
		port: -1
		leafnodes {
			port: -1
			remotes = [{
				url:"nats://127.0.0.1:%d"
			}]
		}`, aLeafPort)))
	sB, _ := RunServerWithConfig(confB)
	defer sB.Shutdown()

	confC := createConfFile(t, []byte(fmt.Sprintf(`
		port: -1
		leafnodes {
			port: -1
			remotes = [{url:"nats://127.0.0.1:%d"}]
		}`, sB.opts.LeafNode.Port)))
	sC, _ := RunServerWithConfig(confC)
	defer sC.Shutdown()

	checkLeafNodeConnectedCount(t, sC, 1)
	checkLeafNodeConnectedCount(t, sB, 2)
	checkLeafNodeConnectedCount(t, sA, 1)

	ncC := natsConnect(t, sC.ClientURL())
	defer ncC.Close()
	_, err := ncC.SubscribeSync("foo")
	require_NoError(t, err)
	require_NoError(t, ncC.Flush())

	checkSubInterest(t, sC, "$G", "foo", time.Second)
	checkSubInterest(t, sB, "$G", "foo", time.Second)
	checkSubInterest(t, sA, "$G", "foo", time.Second)

	ncA := natsConnect(t, sA.ClientURL())
	defer ncA.Close()

	sA.Shutdown()
	sA.WaitForShutdown()

	confAA := createConfFile(t, []byte(fmt.Sprintf(aTmpl, aPort, aLeafPort)))
	sAA, _ := RunServerWithConfig(confAA)
	defer sAA.Shutdown()

	checkLeafNodeConnectedCount(t, sAA, 1)
	checkLeafNodeConnectedCount(t, sB, 2)
	checkLeafNodeConnectedCount(t, sC, 1)

	checkSubInterest(t, sC, "$G", "foo", time.Second)
	checkSubInterest(t, sB, "$G", "foo", time.Second)
	checkSubInterest(t, sAA, "$G", "foo", time.Second) // failure issue 2448
}

func TestLeafNodeQueueGroupWithLateLNJoin(t *testing.T) {
	/*

		Topology: A cluster of leafnodes LN2 and LN3, connect
		to a cluster C1, C2.

		sub(foo)     sub(foo)
		    \         /
		    C1  <->  C2
		    ^        ^
		    |        |
		    LN2 <-> LN3
		    /         \
		sub(foo)     sub(foo)

		Once the above is set, start LN1 that connects to C1.

		    sub(foo)     sub(foo)
		        \         /
		LN1 ->  C1  <->  C2
		        ^        ^
		        |        |
		        LN2 <-> LN3
		        /         \
		    sub(foo)     sub(foo)

		Remove subs to LN3, C2 and C1.

		LN1 ->  C1  <->  C2
		        ^        ^
		        |        |
		        LN2 <-> LN3
		        /
		    sub(foo)

		Publish from LN1 and verify message is received by sub on LN2.

		pub(foo)
		\
		LN1 -> C1  <->  C2
		        ^        ^
		        |        |
		        LN2 <-> LN3
		        /
		    sub(foo)
	*/
	co1 := DefaultOptions()
	co1.LeafNode.Host = "127.0.0.1"
	co1.LeafNode.Port = -1
	co1.Cluster.Name = "ngs"
	co1.Cluster.Host = "127.0.0.1"
	co1.Cluster.Port = -1
	c1 := RunServer(co1)
	defer c1.Shutdown()

	co2 := DefaultOptions()
	co2.LeafNode.Host = "127.0.0.1"
	co2.LeafNode.Port = -1
	co2.Cluster.Name = "ngs"
	co2.Cluster.Host = "127.0.0.1"
	co2.Cluster.Port = -1
	co2.Routes = RoutesFromStr(fmt.Sprintf("nats://127.0.0.1:%d", co1.Cluster.Port))
	c2 := RunServer(co2)
	defer c2.Shutdown()

	checkClusterFormed(t, c1, c2)

	lo2 := DefaultOptions()
	lo2.Cluster.Name = "local"
	lo2.Cluster.Host = "127.0.0.1"
	lo2.Cluster.Port = -1
	lo2.LeafNode.ReconnectInterval = 50 * time.Millisecond
	lo2.LeafNode.Remotes = []*RemoteLeafOpts{{URLs: []*url.URL{{Scheme: "nats", Host: fmt.Sprintf("127.0.0.1:%d", co1.LeafNode.Port)}}}}
	ln2 := RunServer(lo2)
	defer ln2.Shutdown()

	lo3 := DefaultOptions()
	lo3.Cluster.Name = "local"
	lo3.Cluster.Host = "127.0.0.1"
	lo3.Cluster.Port = -1
	lo3.Routes = RoutesFromStr(fmt.Sprintf("nats://127.0.0.1:%d", lo2.Cluster.Port))
	lo3.LeafNode.ReconnectInterval = 50 * time.Millisecond
	lo3.LeafNode.Remotes = []*RemoteLeafOpts{{URLs: []*url.URL{{Scheme: "nats", Host: fmt.Sprintf("127.0.0.1:%d", co2.LeafNode.Port)}}}}
	ln3 := RunServer(lo3)
	defer ln3.Shutdown()

	checkClusterFormed(t, ln2, ln3)
	checkLeafNodeConnected(t, ln2)
	checkLeafNodeConnected(t, ln3)

	cln2 := natsConnect(t, ln2.ClientURL())
	defer cln2.Close()
	sln2 := natsQueueSubSync(t, cln2, "foo", "qgroup")
	natsFlush(t, cln2)

	cln3 := natsConnect(t, ln3.ClientURL())
	defer cln3.Close()
	sln3 := natsQueueSubSync(t, cln3, "foo", "qgroup")
	natsFlush(t, cln3)

	cc1 := natsConnect(t, c1.ClientURL())
	defer cc1.Close()
	sc1 := natsQueueSubSync(t, cc1, "foo", "qgroup")
	natsFlush(t, cc1)

	cc2 := natsConnect(t, c2.ClientURL())
	defer cc2.Close()
	sc2 := natsQueueSubSync(t, cc2, "foo", "qgroup")
	natsFlush(t, cc2)

	checkSubInterest(t, c1, globalAccountName, "foo", time.Second)
	checkSubInterest(t, c2, globalAccountName, "foo", time.Second)
	checkSubInterest(t, ln2, globalAccountName, "foo", time.Second)
	checkSubInterest(t, ln3, globalAccountName, "foo", time.Second)

	lo1 := DefaultOptions()
	lo1.LeafNode.ReconnectInterval = 50 * time.Millisecond
	lo1.LeafNode.Remotes = []*RemoteLeafOpts{{URLs: []*url.URL{{Scheme: "nats", Host: fmt.Sprintf("127.0.0.1:%d", co1.LeafNode.Port)}}}}
	ln1 := RunServer(lo1)
	defer ln1.Shutdown()

	checkLeafNodeConnected(t, ln1)
	checkSubInterest(t, ln1, globalAccountName, "foo", time.Second)

	sln3.Unsubscribe()
	natsFlush(t, cln3)
	sc2.Unsubscribe()
	natsFlush(t, cc2)
	sc1.Unsubscribe()
	natsFlush(t, cc1)

	cln1 := natsConnect(t, ln1.ClientURL())
	defer cln1.Close()

	natsPub(t, cln1, "foo", []byte("hello"))
	natsNexMsg(t, sln2, time.Second)
}

func TestLeafNodeJetStreamDomainMapCrossTalk(t *testing.T) {
	accs := `
accounts :{
    A:{   jetstream: enable, users:[ {user:a1,password:a1}]},
    SYS:{ users:[ {user:s1,password:s1}]},
}
system_account: SYS
`

	sd1 := t.TempDir()
	confA := createConfFile(t, []byte(fmt.Sprintf(`
listen: 127.0.0.1:-1
%s
jetstream: { domain: da, store_dir: '%s', max_mem: 50Mb, max_file: 50Mb }
leafnodes: {
	listen: 127.0.0.1:-1
	no_advertise: true
	authorization: {
		timeout: 0.5
	}
}
`, accs, sd1)))
	sA, _ := RunServerWithConfig(confA)
	defer sA.Shutdown()

	sd2 := t.TempDir()
	confL := createConfFile(t, []byte(fmt.Sprintf(`
listen: 127.0.0.1:-1
%s
jetstream: { domain: dl, store_dir: '%s', max_mem: 50Mb, max_file: 50Mb }
leafnodes:{
	no_advertise: true
    remotes:[{url:nats://a1:a1@127.0.0.1:%d, account: A},
		     {url:nats://s1:s1@127.0.0.1:%d, account: SYS}]
}
`, accs, sd2, sA.opts.LeafNode.Port, sA.opts.LeafNode.Port)))
	sL, _ := RunServerWithConfig(confL)
	defer sL.Shutdown()

	ncA := natsConnect(t, sA.ClientURL(), nats.UserInfo("a1", "a1"))
	defer ncA.Close()
	ncL := natsConnect(t, sL.ClientURL(), nats.UserInfo("a1", "a1"))
	defer ncL.Close()

	test := func(jsA, jsL nats.JetStreamContext) {
		kvA, err := jsA.CreateKeyValue(&nats.KeyValueConfig{Bucket: "bucket"})
		require_NoError(t, err)
		kvL, err := jsL.CreateKeyValue(&nats.KeyValueConfig{Bucket: "bucket"})
		require_NoError(t, err)

		_, err = kvA.Put("A", nil)
		require_NoError(t, err)
		_, err = kvL.Put("L", nil)
		require_NoError(t, err)

		// check for unwanted cross talk
		_, err = kvA.Get("A")
		require_NoError(t, err)
		_, err = kvA.Get("l")
		require_Error(t, err)
		require_True(t, err == nats.ErrKeyNotFound)

		_, err = kvL.Get("A")
		require_Error(t, err)
		require_True(t, err == nats.ErrKeyNotFound)
		_, err = kvL.Get("L")
		require_NoError(t, err)

		err = jsA.DeleteKeyValue("bucket")
		require_NoError(t, err)
		err = jsL.DeleteKeyValue("bucket")
		require_NoError(t, err)
	}

	jsA, err := ncA.JetStream()
	require_NoError(t, err)
	jsL, err := ncL.JetStream()
	require_NoError(t, err)
	test(jsA, jsL)

	jsAL, err := ncA.JetStream(nats.Domain("dl"))
	require_NoError(t, err)
	jsLA, err := ncL.JetStream(nats.Domain("da"))
	require_NoError(t, err)
	test(jsAL, jsLA)

	jsAA, err := ncA.JetStream(nats.Domain("da"))
	require_NoError(t, err)
	jsLL, err := ncL.JetStream(nats.Domain("dl"))
	require_NoError(t, err)
	test(jsAA, jsLL)
}

type checkLeafMinVersionLogger struct {
	DummyLogger
	errCh  chan string
	connCh chan string
}

func (l *checkLeafMinVersionLogger) Errorf(format string, args ...interface{}) {
	msg := fmt.Sprintf(format, args...)
	if strings.Contains(msg, "minimum version") {
		select {
		case l.errCh <- msg:
		default:
		}
	}
}

func (l *checkLeafMinVersionLogger) Noticef(format string, args ...interface{}) {
	msg := fmt.Sprintf(format, args...)
	if strings.Contains(msg, "Leafnode connection created") {
		select {
		case l.connCh <- msg:
		default:
		}
	}
}

func TestLeafNodeMinVersion(t *testing.T) {
	conf := createConfFile(t, []byte(`
		port: -1
		leafnodes {
			port: -1
			min_version: 2.8.0
		}
	`))
	s, o := RunServerWithConfig(conf)
	defer s.Shutdown()

	rconf := createConfFile(t, []byte(fmt.Sprintf(`
		port: -1
		leafnodes {
			remotes [
				{url: "nats://127.0.0.1:%d" }
			]
		}
	`, o.LeafNode.Port)))
	ln, _ := RunServerWithConfig(rconf)
	defer ln.Shutdown()

	checkLeafNodeConnected(t, s)
	checkLeafNodeConnected(t, ln)

	ln.Shutdown()
	s.Shutdown()

	// Now makes sure we validate options, not just config file.
	for _, test := range []struct {
		name    string
		version string
		err     string
	}{
		{"invalid version", "abc", "semver"},
		{"version too low", "2.7.9", "the minimum version should be at least 2.8.0"},
	} {
		t.Run(test.name, func(t *testing.T) {
			o.Port = -1
			o.LeafNode.Port = -1
			o.LeafNode.MinVersion = test.version
			if s, err := NewServer(o); err == nil || !strings.Contains(err.Error(), test.err) {
				if s != nil {
					s.Shutdown()
				}
				t.Fatalf("Expected error to contain %q, got %v", test.err, err)
			}
		})
	}

	// Ok, so now to verify that a server rejects a leafnode connection
	// we will set the min_version above our current VERSION. So first
	// decompose the version:
	major, minor, _, err := versionComponents(VERSION)
	if err != nil {
		t.Fatalf("The current server version %q is not valid: %v", VERSION, err)
	}
	// Let's make our minimum server an minor version above
	mv := fmt.Sprintf("%d.%d.0", major, minor+1)
	conf = createConfFile(t, []byte(fmt.Sprintf(`
		port: -1
		leafnodes {
			port: -1
			min_version: "%s"
		}
	`, mv)))
	s, o = RunServerWithConfig(conf)
	defer s.Shutdown()

	l := &checkLeafMinVersionLogger{errCh: make(chan string, 1), connCh: make(chan string, 1)}
	s.SetLogger(l, false, false)

	rconf = createConfFile(t, []byte(fmt.Sprintf(`
		port: -1
		leafnodes {
			remotes [
				{url: "nats://127.0.0.1:%d" }
			]
		}
	`, o.LeafNode.Port)))
	lo := LoadConfig(rconf)
	lo.LeafNode.ReconnectInterval = 50 * time.Millisecond
	ln = RunServer(lo)
	defer ln.Shutdown()

	select {
	case <-l.connCh:
	case <-time.After(time.Second):
		t.Fatal("Remote did not try to connect")
	}

	select {
	case <-l.errCh:
	case <-time.After(time.Second):
		t.Fatal("Did not get the minimum version required error")
	}

	// Since we have a very small reconnect interval, if the connection was
	// closed "right away", then we should have had a reconnect attempt with
	// another failure. This should not be the case because the server will
	// wait 5s before closing the connection.
	select {
	case <-l.connCh:
		t.Fatal("Should not have tried to reconnect")
	case <-time.After(250 * time.Millisecond):
		// OK
	}
}

func TestLeafNodeStreamAndShadowSubs(t *testing.T) {
	hubConf := createConfFile(t, []byte(`
		port: -1
		leafnodes {
			port: -1
			authorization: {
			  user: leaf
			  password: leaf
			  account: B
			}
		}
		accounts: {
			A: {
			  users = [{user: usrA, password: usrA}]
			  exports: [{stream: foo.*.>}]
			}
			B: {
			  imports: [{stream: {account: A, subject: foo.*.>}}]
			}
		}
	`))
	hub, hubo := RunServerWithConfig(hubConf)
	defer hub.Shutdown()

	leafConfContet := fmt.Sprintf(`
		port: -1
		leafnodes {
			remotes = [
				{
					url: "nats-leaf://leaf:leaf@127.0.0.1:%d"
					account: B
				}
			]
		}
		accounts: {
			B: {
				exports: [{stream: foo.*.>}]
			}
			C: {
				users: [{user: usrC, password: usrC}]
				imports: [{stream: {account: B, subject: foo.bar.>}}]
			}
		}
	`, hubo.LeafNode.Port)
	leafConf := createConfFile(t, []byte(leafConfContet))
	leafo := LoadConfig(leafConf)
	leafo.LeafNode.ReconnectInterval = 50 * time.Millisecond
	leaf := RunServer(leafo)
	defer leaf.Shutdown()

	checkLeafNodeConnected(t, hub)
	checkLeafNodeConnected(t, leaf)

	subPubAndCheck := func() {
		t.Helper()

		ncl, err := nats.Connect(leaf.ClientURL(), nats.UserInfo("usrC", "usrC"))
		if err != nil {
			t.Fatalf("Error connecting: %v", err)
		}
		defer ncl.Close()

		// This will send an LS+ to the "hub" server.
		sub, err := ncl.SubscribeSync("foo.*.baz")
		if err != nil {
			t.Fatalf("Error subscribing: %v", err)
		}
		ncl.Flush()

		ncm, err := nats.Connect(hub.ClientURL(), nats.UserInfo("usrA", "usrA"))
		if err != nil {
			t.Fatalf("Error connecting: %v", err)
		}
		defer ncm.Close()

		// Try a few times in case subject interest has not propagated yet
		for i := 0; i < 5; i++ {
			ncm.Publish("foo.bar.baz", []byte("msg"))
			if _, err := sub.NextMsg(time.Second); err == nil {
				// OK, done!
				return
			}
		}
		t.Fatal("Message was not received")
	}
	subPubAndCheck()

	// Now cause a restart of the accepting side so that the leaf connection
	// is recreated.
	hub.Shutdown()
	hub = RunServer(hubo)
	defer hub.Shutdown()

	checkLeafNodeConnected(t, hub)
	checkLeafNodeConnected(t, leaf)

	subPubAndCheck()

	// Issue a config reload even though we make no modification. There was
	// a defect that caused the interest propagation to break.
	// Set the ReconnectInterval to the default value so that reload does not complain.
	leaf.getOpts().LeafNode.ReconnectInterval = DEFAULT_LEAF_NODE_RECONNECT
	reloadUpdateConfig(t, leaf, leafConf, leafConfContet)

	// Check again
	subPubAndCheck()
}

func TestLeafNodeAuthConfigReload(t *testing.T) {
	template := `
		listen: 127.0.0.1:-1
		accounts { test: {} }
		leaf {
			listen: "127.0.0.1:7422"
			tls {
				cert_file: "../test/configs/certs/server-cert.pem"
				key_file:  "../test/configs/certs/server-key.pem"
				ca_file:   "../test/configs/certs/ca.pem"
			}
			authorization {
				# These are only fields allowed atm.
				users = [ { user: test, password: "s3cret1", account: "test"  } ]
			}
		}
	`
	conf := createConfFile(t, []byte(template))

	s, _ := RunServerWithConfig(conf)
	defer s.Shutdown()

	lg := &captureErrorLogger{errCh: make(chan string, 10)}
	s.SetLogger(lg, false, false)

	// Reload here should work ok.
	reloadUpdateConfig(t, s, conf, template)
}

func TestLeafNodeSignatureCB(t *testing.T) {
	content := `
		port: -1
		server_name: OP
		operator = "../test/configs/nkeys/op.jwt"
		resolver = MEMORY
		listen: "127.0.0.1:-1"
		leafnodes {
			listen: "127.0.0.1:-1"
		}
	`
	conf := createConfFile(t, []byte(content))
	s, opts := RunServerWithConfig(conf)
	defer s.Shutdown()

	_, akp := createAccount(s)
	kp, _ := nkeys.CreateUser()
	pub, _ := kp.PublicKey()
	nuc := jwt.NewUserClaims(pub)
	ujwt, err := nuc.Encode(akp)
	if err != nil {
		t.Fatalf("Error generating user JWT: %v", err)
	}

	lopts := &DefaultTestOptions
	u, err := url.Parse(fmt.Sprintf("nats://%s:%d", opts.LeafNode.Host, opts.LeafNode.Port))
	if err != nil {
		t.Fatalf("Error parsing url: %v", err)
	}
	remote := &RemoteLeafOpts{URLs: []*url.URL{u}}
	remote.SignatureCB = func(nonce []byte) (string, []byte, error) {
		return "", nil, fmt.Errorf("on purpose")
	}
	lopts.LeafNode.Remotes = []*RemoteLeafOpts{remote}
	lopts.LeafNode.ReconnectInterval = 100 * time.Millisecond
	sl := RunServer(lopts)
	defer sl.Shutdown()

	slog := &captureErrorLogger{errCh: make(chan string, 10)}
	sl.SetLogger(slog, false, false)

	// Now check that the leafnode got the error that the callback returned.
	select {
	case err := <-slog.errCh:
		if !strings.Contains(err, "on purpose") {
			t.Fatalf("Expected error from cb, got %v", err)
		}
	case <-time.After(time.Second):
		t.Fatal("Did not get expected error")
	}

	sl.Shutdown()
	// Now check what happens if the connection is closed while in the callback.
	blockCh := make(chan struct{})
	remote.SignatureCB = func(nonce []byte) (string, []byte, error) {
		<-blockCh
		sig, err := kp.Sign(nonce)
		return ujwt, sig, err
	}
	sl = RunServer(lopts)
	defer sl.Shutdown()

	// Recreate the logger so that we are sure not to have possible previous errors
	slog = &captureErrorLogger{errCh: make(chan string, 10)}
	sl.SetLogger(slog, false, false)

	// Get the leaf connection from the temp clients map and close it.
	checkFor(t, time.Second, 15*time.Millisecond, func() error {
		var c *client
		sl.grMu.Lock()
		for _, cli := range sl.grTmpClients {
			c = cli
		}
		sl.grMu.Unlock()
		if c == nil {
			return fmt.Errorf("Client still not found in temp map")
		}
		c.closeConnection(ClientClosed)
		return nil
	})

	// Release the callback, and check we get the appropriate error.
	close(blockCh)
	select {
	case err := <-slog.errCh:
		if !strings.Contains(err, ErrConnectionClosed.Error()) {
			t.Fatalf("Expected error that connection was closed, got %v", err)
		}
	case <-time.After(time.Second):
		t.Fatal("Did not get expected error")
	}

	sl.Shutdown()
	// Change to a good CB and now it should work
	remote.SignatureCB = func(nonce []byte) (string, []byte, error) {
		sig, err := kp.Sign(nonce)
		return ujwt, sig, err
	}
	sl = RunServer(lopts)
	defer sl.Shutdown()
	checkLeafNodeConnected(t, sl)
}

type testLeafTraceLogger struct {
	DummyLogger
	ch chan string
}

func (l *testLeafTraceLogger) Tracef(format string, v ...interface{}) {
	msg := fmt.Sprintf(format, v...)
	// We will sub to 'baz' and to 'bar', so filter on 'ba' prefix.
	if strings.Contains(msg, "[LS+ ba") {
		select {
		case l.ch <- msg:
		default:
		}
	}
}

// Make sure permissioned denied subs do not make it to the leafnode even if existing.
func TestLeafNodePermsSuppressSubs(t *testing.T) {
	conf := createConfFile(t, []byte(`
		listen: 127.0.0.1:-1
		authorization {
		  PERMS = {
		    publish = "foo"
		    subscribe = ["_INBOX.>"]
		  }
		  users = [
		    {user: "user", password: "pass"}
		    {user: "ln",  password: "pass" , permissions: $PERMS }
		  ]
		}
		no_auth_user: user

		leafnodes {
		  listen: 127.0.0.1:7422
		}
	`))

	lconf := createConfFile(t, []byte(`
		listen: 127.0.0.1:-1
		leafnodes {
		  remotes = [ { url: "nats://ln:pass@127.0.0.1" } ]
		}
		trace = true
	`))

	s, _ := RunServerWithConfig(conf)
	defer s.Shutdown()

	// Connect client to the hub.
	nc, err := nats.Connect(s.ClientURL())
	require_NoError(t, err)

	// This should not be seen on leafnode side since we only allow pub to "foo"
	_, err = nc.SubscribeSync("baz")
	require_NoError(t, err)

	ln, _ := RunServerWithConfig(lconf)
	defer ln.Shutdown()

	// Setup logger to capture trace events.
	l := &testLeafTraceLogger{ch: make(chan string, 10)}
	ln.SetLogger(l, true, true)

	checkLeafNodeConnected(t, ln)

	// Need to have ot reconnect to trigger since logger attaches too late.
	ln.mu.Lock()
	for _, c := range ln.leafs {
		c.mu.Lock()
		c.nc.Close()
		c.mu.Unlock()
	}
	ln.mu.Unlock()
	checkLeafNodeConnectedCount(t, ln, 0)
	checkLeafNodeConnectedCount(t, ln, 1)

	select {
	case msg := <-l.ch:
		t.Fatalf("Unexpected LS+ seen on leafnode: %s", msg)
	case <-time.After(50 * time.Millisecond):
		// OK
	}

	// Now double check that new subs also do not propagate.
	// This behavior was working already.
	_, err = nc.SubscribeSync("bar")
	require_NoError(t, err)

	select {
	case msg := <-l.ch:
		t.Fatalf("Unexpected LS+ seen on leafnode: %s", msg)
	case <-time.After(50 * time.Millisecond):
		// OK
	}
}

func TestLeafNodeDuplicateMsg(t *testing.T) {
	// This involves 2 clusters with leafnodes to each other with a different
	// account, and those accounts import/export a subject that caused
	// duplicate messages. This test requires static ports since we need to
	// have A->B and B->A.
	a1Conf := createConfFile(t, []byte(`
	cluster : {
		name : A
		port : -1
	}
	leafnodes : {
		port : 14333
		remotes : [{
			account : A
			urls : [nats://leafa:pwd@127.0.0.1:24333]
		}]
	}
	port : -1
	server_name : A_1

	accounts:{
		A:{
			users:[
				{user: leafa, password: pwd},
				{user: usera, password: usera, permissions: {
					publish:{ allow:["iot.b.topic"] }
					subscribe:{ allow:["iot.a.topic"] }
				}}
			]
			imports:[
				{stream:{account:"B", subject:"iot.a.topic"}}
			]
		},
		B:{
			users:[
				{user: leafb, password: pwd},
			]
			exports:[
				{stream: "iot.a.topic", accounts: ["A"]}
			]
		}
	}
	`))
	a1, oa1 := RunServerWithConfig(a1Conf)
	defer a1.Shutdown()

	a2Conf := createConfFile(t, []byte(fmt.Sprintf(`
	cluster : {
		name : A
		port : -1
		routes : [nats://127.0.0.1:%d]
	}
	leafnodes : {
		port : 14334
		remotes : [{
			account : A
			urls : [nats://leafa:pwd@127.0.0.1:24334]
		}]
	}
	port : -1
	server_name : A_2

	accounts:{
		A:{
			users:[
				{user: leafa, password: pwd},
				{user: usera, password: usera, permissions: {
					publish:{ allow:["iot.b.topic"] }
					subscribe:{ allow:["iot.a.topic"] }
				}}
			]
			imports:[
				{stream:{account:"B", subject:"iot.a.topic"}}
			]
		},
		B:{
			users:[
				{user: leafb, password: pwd},
			]
			exports:[
				{stream: "iot.a.topic", accounts: ["A"]}
			]
		}
	}`, oa1.Cluster.Port)))
	a2, _ := RunServerWithConfig(a2Conf)
	defer a2.Shutdown()

	checkClusterFormed(t, a1, a2)

	b1Conf := createConfFile(t, []byte(`
	cluster : {
		name : B
		port : -1
	}
	leafnodes : {
		port : 24333
		remotes : [{
			account : B
			urls : [nats://leafb:pwd@127.0.0.1:14333]
		}]
	}
	port : -1
	server_name : B_1

	accounts:{
		A:{
			users:[
				{user: leafa, password: pwd},
			]
			exports:[
				{stream: "iot.b.topic", accounts: ["B"]}
			]
		},
		B:{
			users:[
				{user: leafb, password: pwd},
				{user: userb, password: userb, permissions: {
					publish:{ allow:["iot.a.topic"] },
					subscribe:{ allow:["iot.b.topic"] }
				}}
			]
			imports:[
				{stream:{account:"A", subject:"iot.b.topic"}}
			]
		}
	}`))
	b1, ob1 := RunServerWithConfig(b1Conf)
	defer b1.Shutdown()

	b2Conf := createConfFile(t, []byte(fmt.Sprintf(`
	cluster : {
		name : B
		port : -1
		routes : [nats://127.0.0.1:%d]
	}
	leafnodes : {
		port : 24334
		remotes : [{
			account : B
			urls : [nats://leafb:pwd@127.0.0.1:14334]
		}]
	}
	port : -1
	server_name : B_2

	accounts:{
		A:{
			users:[
				{user: leafa, password: pwd},
			]
			exports:[
				{stream: "iot.b.topic", accounts: ["B"]}
			]
		},
		B:{
			users:[
				{user: leafb, password: pwd},
				{user: userb, password: userb, permissions: {
					publish:{ allow:["iot.a.topic"] },
					subscribe:{ allow:["iot.b.topic"] }
				}}
			]
			imports:[
				{stream:{account:"A", subject:"iot.b.topic"}}
			]
		}
	}`, ob1.Cluster.Port)))
	b2, _ := RunServerWithConfig(b2Conf)
	defer b2.Shutdown()

	checkClusterFormed(t, b1, b2)

	checkLeafNodeConnectedCount(t, a1, 2)
	checkLeafNodeConnectedCount(t, a2, 2)
	checkLeafNodeConnectedCount(t, b1, 2)
	checkLeafNodeConnectedCount(t, b2, 2)

	check := func(t *testing.T, subSrv *Server, pubSrv *Server) {

		sc := natsConnect(t, subSrv.ClientURL(), nats.UserInfo("userb", "userb"))
		defer sc.Close()

		subject := "iot.b.topic"
		sub := natsSubSync(t, sc, subject)

		// Wait for this to be available in A cluster
		checkSubInterest(t, a1, "A", subject, time.Second)
		checkSubInterest(t, a2, "A", subject, time.Second)

		pb := natsConnect(t, pubSrv.ClientURL(), nats.UserInfo("usera", "usera"))
		defer pb.Close()

		natsPub(t, pb, subject, []byte("msg"))
		natsNexMsg(t, sub, time.Second)
		// Should be only 1
		if msg, err := sub.NextMsg(100 * time.Millisecond); err == nil {
			t.Fatalf("Received duplicate on %q: %s", msg.Subject, msg.Data)
		}
	}
	t.Run("sub_b1_pub_a1", func(t *testing.T) { check(t, b1, a1) })
	t.Run("sub_b1_pub_a2", func(t *testing.T) { check(t, b1, a2) })
	t.Run("sub_b2_pub_a1", func(t *testing.T) { check(t, b2, a1) })
	t.Run("sub_b2_pub_a2", func(t *testing.T) { check(t, b2, a2) })
}

func TestLeafNodeTLSHandshakeFirstVerifyNoInfoSent(t *testing.T) {
	confHub := createConfFile(t, []byte(`
		port : -1
		leafnodes : {
			port : -1
			tls {
				cert_file: "../test/configs/certs/server-cert.pem"
				key_file:  "../test/configs/certs/server-key.pem"
				ca_file: "../test/configs/certs/ca.pem"
				timeout: 2
				handshake_first: true
			}
		}
	`))
	s1, o1 := RunServerWithConfig(confHub)
	defer s1.Shutdown()

	c, err := net.DialTimeout("tcp", fmt.Sprintf("127.0.0.1:%d", o1.LeafNode.Port), 2*time.Second)
	require_NoError(t, err)
	defer c.Close()

	buf := make([]byte, 1024)
	// We will wait for up to 500ms to see if the server is sending (incorrectly)
	// the INFO.
	c.SetReadDeadline(time.Now().Add(500 * time.Millisecond))
	n, err := c.Read(buf)
	c.SetReadDeadline(time.Time{})
	// If we did not get an error, this is an issue...
	if err == nil {
		t.Fatalf("Should not have received anything, got n=%v buf=%s", n, buf[:n])
	}
	// We expect a timeout error
	if ne, ok := err.(net.Error); !ok || !ne.Timeout() {
		t.Fatalf("Expected a timeout error, got %v", err)
	}
}

func TestLeafNodeTLSHandshakeFirst(t *testing.T) {
	tmpl1 := `
		port : -1
		leafnodes : {
			port : -1
			tls {
				cert_file: "../test/configs/certs/server-cert.pem"
				key_file:  "../test/configs/certs/server-key.pem"
				ca_file: "../test/configs/certs/ca.pem"
				timeout: 2
				handshake_first: %s
			}
		}
	`
	confHub := createConfFile(t, []byte(fmt.Sprintf(tmpl1, "true")))
	s1, o1 := RunServerWithConfig(confHub)
	defer s1.Shutdown()

	tmpl2 := `
		port: -1
		leafnodes : {
			port : -1
			remotes : [
				{
					urls : [tls://127.0.0.1:%d]
					tls {
						cert_file: "../test/configs/certs/client-cert.pem"
						key_file:  "../test/configs/certs/client-key.pem"
						ca_file: "../test/configs/certs/ca.pem"
						timeout: 2
						first: %s
					}
				}
			]
		}
	`
	confSpoke := createConfFile(t, []byte(fmt.Sprintf(tmpl2, o1.LeafNode.Port, "true")))
	s2, _ := RunServerWithConfig(confSpoke)
	defer s2.Shutdown()

	checkLeafNodeConnected(t, s2)

	s2.Shutdown()

	// Now check that there will be a failure if the remote does not ask for
	// handshake first since the hub is configured that way.
	// Set a logger on s1 to capture errors
	l := &captureErrorLogger{errCh: make(chan string, 10)}
	s1.SetLogger(l, false, false)

	confSpoke = createConfFile(t, []byte(fmt.Sprintf(tmpl2, o1.LeafNode.Port, "false")))
	s2, _ = RunServerWithConfig(confSpoke)
	defer s2.Shutdown()

	select {
	case err := <-l.errCh:
		if !strings.Contains(err, "handshake error") {
			t.Fatalf("Unexpected error: %v", err)
		}
	case <-time.After(2 * time.Second):
		t.Fatal("Did not get TLS handshake failure")
	}

	// Check configuration reload for this remote
	reloadUpdateConfig(t, s2, confSpoke, fmt.Sprintf(tmpl2, o1.LeafNode.Port, "true"))
	checkLeafNodeConnected(t, s2)
	s2.Shutdown()

	// Drain the logger error channel
	for done := false; !done; {
		select {
		case <-l.errCh:
		default:
			done = true
		}
	}

	// Now change the config on the hub
	reloadUpdateConfig(t, s1, confHub, fmt.Sprintf(tmpl1, "false"))
	// Restart s2
	s2, _ = RunServerWithConfig(confSpoke)
	defer s2.Shutdown()

	select {
	case err := <-l.errCh:
		if !strings.Contains(err, "handshake error") {
			t.Fatalf("Unexpected error: %v", err)
		}
	case <-time.After(2 * time.Second):
		t.Fatal("Did not get TLS handshake failure")
	}

	// Reload again with "true"
	reloadUpdateConfig(t, s1, confHub, fmt.Sprintf(tmpl1, "true"))
	checkLeafNodeConnected(t, s2)
}

func TestLeafNodeTLSHandshakeEvenForRemoteWithNoTLSBlock(t *testing.T) {
	confHub := createConfFile(t, []byte(`
		port : -1
		leafnodes : {
			port : -1
			tls {
				cert_file: "../test/configs/certs/server-cert.pem"
				key_file:  "../test/configs/certs/server-key.pem"
				ca_file: "../test/configs/certs/ca.pem"
				timeout: 2
			}
		}
	`))
	s1, o1 := RunServerWithConfig(confHub)
	defer s1.Shutdown()

	tmpl2 := `
		port: -1
		leafnodes : {
			port : -1
			remotes : [
				{
					urls : [tls://127.0.0.1:%d]
				}
			]
		}
	`
	confSpoke := createConfFile(t, []byte(fmt.Sprintf(tmpl2, o1.LeafNode.Port)))
	s2, _ := RunServerWithConfig(confSpoke)
	defer s2.Shutdown()

	l := &captureDebugLogger{dbgCh: make(chan string, 100)}
	s2.SetLogger(l, true, false)

	tm := time.NewTimer(2 * time.Second)
	defer tm.Stop()
	for {
		select {
		case l := <-l.dbgCh:
			if strings.Contains(l, "Starting TLS") {
				// OK!
				return
			}
		case <-tm.C:
			t.Fatalf("Did not perform a TLS handshake")
		}
	}
}

func TestLeafNodeCompressionOptions(t *testing.T) {
	org := testDefaultLeafNodeCompression
	testDefaultLeafNodeCompression = _EMPTY_
	defer func() { testDefaultLeafNodeCompression = org }()

	tmpl := `
		port: -1
		leafnodes {
			port: -1
			compression: %s
		}
	`
	for _, test := range []struct {
		name     string
		mode     string
		rttVals  []int
		expected string
		rtts     []time.Duration
	}{
		{"boolean enabled", "true", nil, CompressionS2Auto, defaultCompressionS2AutoRTTThresholds},
		{"string enabled", "enabled", nil, CompressionS2Auto, defaultCompressionS2AutoRTTThresholds},
		{"string EnaBled", "EnaBled", nil, CompressionS2Auto, defaultCompressionS2AutoRTTThresholds},
		{"string on", "on", nil, CompressionS2Auto, defaultCompressionS2AutoRTTThresholds},
		{"string ON", "ON", nil, CompressionS2Auto, defaultCompressionS2AutoRTTThresholds},
		{"string fast", "fast", nil, CompressionS2Fast, nil},
		{"string Fast", "Fast", nil, CompressionS2Fast, nil},
		{"string s2_fast", "s2_fast", nil, CompressionS2Fast, nil},
		{"string s2_Fast", "s2_Fast", nil, CompressionS2Fast, nil},
		{"boolean disabled", "false", nil, CompressionOff, nil},
		{"string disabled", "disabled", nil, CompressionOff, nil},
		{"string DisableD", "DisableD", nil, CompressionOff, nil},
		{"string off", "off", nil, CompressionOff, nil},
		{"string OFF", "OFF", nil, CompressionOff, nil},
		{"better", "better", nil, CompressionS2Better, nil},
		{"Better", "Better", nil, CompressionS2Better, nil},
		{"s2_better", "s2_better", nil, CompressionS2Better, nil},
		{"S2_BETTER", "S2_BETTER", nil, CompressionS2Better, nil},
		{"best", "best", nil, CompressionS2Best, nil},
		{"BEST", "BEST", nil, CompressionS2Best, nil},
		{"s2_best", "s2_best", nil, CompressionS2Best, nil},
		{"S2_BEST", "S2_BEST", nil, CompressionS2Best, nil},
		{"auto no rtts", "auto", nil, CompressionS2Auto, defaultCompressionS2AutoRTTThresholds},
		{"s2_auto no rtts", "s2_auto", nil, CompressionS2Auto, defaultCompressionS2AutoRTTThresholds},
		{"auto", "{mode: auto, rtt_thresholds: [%s]}", []int{1}, CompressionS2Auto, []time.Duration{time.Millisecond}},
		{"Auto", "{Mode: Auto, thresholds: [%s]}", []int{1, 2}, CompressionS2Auto, []time.Duration{time.Millisecond, 2 * time.Millisecond}},
		{"s2_auto", "{mode: s2_auto, thresholds: [%s]}", []int{1, 2, 3}, CompressionS2Auto, []time.Duration{time.Millisecond, 2 * time.Millisecond, 3 * time.Millisecond}},
		{"s2_AUTO", "{mode: s2_AUTO, thresholds: [%s]}", []int{1, 2, 3, 4}, CompressionS2Auto, []time.Duration{time.Millisecond, 2 * time.Millisecond, 3 * time.Millisecond, 4 * time.Millisecond}},
		{"s2_auto:-10,5,10", "{mode: s2_auto, thresholds: [%s]}", []int{-10, 5, 10}, CompressionS2Auto, []time.Duration{0, 5 * time.Millisecond, 10 * time.Millisecond}},
		{"s2_auto:5,10,15", "{mode: s2_auto, thresholds: [%s]}", []int{5, 10, 15}, CompressionS2Auto, []time.Duration{5 * time.Millisecond, 10 * time.Millisecond, 15 * time.Millisecond}},
		{"s2_auto:0,5,10", "{mode: s2_auto, thresholds: [%s]}", []int{0, 5, 10}, CompressionS2Auto, []time.Duration{0, 5 * time.Millisecond, 10 * time.Millisecond}},
		{"s2_auto:5,10,0,20", "{mode: s2_auto, thresholds: [%s]}", []int{5, 10, 0, 20}, CompressionS2Auto, []time.Duration{5 * time.Millisecond, 10 * time.Millisecond, 0, 20 * time.Millisecond}},
		{"s2_auto:0,10,0,20", "{mode: s2_auto, thresholds: [%s]}", []int{0, 10, 0, 20}, CompressionS2Auto, []time.Duration{0, 10 * time.Millisecond, 0, 20 * time.Millisecond}},
		{"s2_auto:0,0,0,20", "{mode: s2_auto, thresholds: [%s]}", []int{0, 0, 0, 20}, CompressionS2Auto, []time.Duration{0, 0, 0, 20 * time.Millisecond}},
		{"s2_auto:0,10,0,0", "{mode: s2_auto, rtt_thresholds: [%s]}", []int{0, 10, 0, 0}, CompressionS2Auto, []time.Duration{0, 10 * time.Millisecond}},
	} {
		t.Run(test.name, func(t *testing.T) {
			var val string
			if len(test.rttVals) > 0 {
				var rtts string
				for i, v := range test.rttVals {
					if i > 0 {
						rtts += ", "
					}
					rtts += fmt.Sprintf("%dms", v)
				}
				val = fmt.Sprintf(test.mode, rtts)
			} else {
				val = test.mode
			}
			conf := createConfFile(t, []byte(fmt.Sprintf(tmpl, val)))
			s, o := RunServerWithConfig(conf)
			defer s.Shutdown()

			if cm := o.LeafNode.Compression.Mode; cm != test.expected {
				t.Fatalf("Expected compression value to be %q, got %q", test.expected, cm)
			}
			if !reflect.DeepEqual(test.rtts, o.LeafNode.Compression.RTTThresholds) {
				t.Fatalf("Expected RTT tresholds to be %+v, got %+v", test.rtts, o.LeafNode.Compression.RTTThresholds)
			}
			s.Shutdown()

			o.LeafNode.Port = -1
			o.LeafNode.Compression.Mode = test.mode
			if len(test.rttVals) > 0 {
				o.LeafNode.Compression.Mode = CompressionS2Auto
				o.LeafNode.Compression.RTTThresholds = o.LeafNode.Compression.RTTThresholds[:0]
				for _, v := range test.rttVals {
					o.LeafNode.Compression.RTTThresholds = append(o.LeafNode.Compression.RTTThresholds, time.Duration(v)*time.Millisecond)
				}
			}
			s = RunServer(o)
			defer s.Shutdown()
			if cm := o.LeafNode.Compression.Mode; cm != test.expected {
				t.Fatalf("Expected compression value to be %q, got %q", test.expected, cm)
			}
			if !reflect.DeepEqual(test.rtts, o.LeafNode.Compression.RTTThresholds) {
				t.Fatalf("Expected RTT tresholds to be %+v, got %+v", test.rtts, o.LeafNode.Compression.RTTThresholds)
			}
		})
	}

	// Same, but with remotes
	tmpl = `
		port: -1
		leafnodes {
			port: -1
			remotes [
				{
					url: "nats://127.0.0.1:1234"
					compression: %s
				}
			]
		}
	`
	for _, test := range []struct {
		name     string
		mode     string
		rttVals  []int
		expected string
		rtts     []time.Duration
	}{
		{"boolean enabled", "true", nil, CompressionS2Auto, defaultCompressionS2AutoRTTThresholds},
		{"string enabled", "enabled", nil, CompressionS2Auto, defaultCompressionS2AutoRTTThresholds},
		{"string EnaBled", "EnaBled", nil, CompressionS2Auto, defaultCompressionS2AutoRTTThresholds},
		{"string on", "on", nil, CompressionS2Auto, defaultCompressionS2AutoRTTThresholds},
		{"string ON", "ON", nil, CompressionS2Auto, defaultCompressionS2AutoRTTThresholds},
		{"string fast", "fast", nil, CompressionS2Fast, nil},
		{"string Fast", "Fast", nil, CompressionS2Fast, nil},
		{"string s2_fast", "s2_fast", nil, CompressionS2Fast, nil},
		{"string s2_Fast", "s2_Fast", nil, CompressionS2Fast, nil},
		{"boolean disabled", "false", nil, CompressionOff, nil},
		{"string disabled", "disabled", nil, CompressionOff, nil},
		{"string DisableD", "DisableD", nil, CompressionOff, nil},
		{"string off", "off", nil, CompressionOff, nil},
		{"string OFF", "OFF", nil, CompressionOff, nil},
		{"better", "better", nil, CompressionS2Better, nil},
		{"Better", "Better", nil, CompressionS2Better, nil},
		{"s2_better", "s2_better", nil, CompressionS2Better, nil},
		{"S2_BETTER", "S2_BETTER", nil, CompressionS2Better, nil},
		{"best", "best", nil, CompressionS2Best, nil},
		{"BEST", "BEST", nil, CompressionS2Best, nil},
		{"s2_best", "s2_best", nil, CompressionS2Best, nil},
		{"S2_BEST", "S2_BEST", nil, CompressionS2Best, nil},
		{"auto no rtts", "auto", nil, CompressionS2Auto, defaultCompressionS2AutoRTTThresholds},
		{"s2_auto no rtts", "s2_auto", nil, CompressionS2Auto, defaultCompressionS2AutoRTTThresholds},
		{"auto", "{mode: auto, rtt_thresholds: [%s]}", []int{1}, CompressionS2Auto, []time.Duration{time.Millisecond}},
		{"Auto", "{Mode: Auto, thresholds: [%s]}", []int{1, 2}, CompressionS2Auto, []time.Duration{time.Millisecond, 2 * time.Millisecond}},
		{"s2_auto", "{mode: s2_auto, thresholds: [%s]}", []int{1, 2, 3}, CompressionS2Auto, []time.Duration{time.Millisecond, 2 * time.Millisecond, 3 * time.Millisecond}},
		{"s2_AUTO", "{mode: s2_AUTO, thresholds: [%s]}", []int{1, 2, 3, 4}, CompressionS2Auto, []time.Duration{time.Millisecond, 2 * time.Millisecond, 3 * time.Millisecond, 4 * time.Millisecond}},
		{"s2_auto:-10,5,10", "{mode: s2_auto, thresholds: [%s]}", []int{-10, 5, 10}, CompressionS2Auto, []time.Duration{0, 5 * time.Millisecond, 10 * time.Millisecond}},
		{"s2_auto:5,10,15", "{mode: s2_auto, thresholds: [%s]}", []int{5, 10, 15}, CompressionS2Auto, []time.Duration{5 * time.Millisecond, 10 * time.Millisecond, 15 * time.Millisecond}},
		{"s2_auto:0,5,10", "{mode: s2_auto, thresholds: [%s]}", []int{0, 5, 10}, CompressionS2Auto, []time.Duration{0, 5 * time.Millisecond, 10 * time.Millisecond}},
		{"s2_auto:5,10,0,20", "{mode: s2_auto, thresholds: [%s]}", []int{5, 10, 0, 20}, CompressionS2Auto, []time.Duration{5 * time.Millisecond, 10 * time.Millisecond, 0, 20 * time.Millisecond}},
		{"s2_auto:0,10,0,20", "{mode: s2_auto, thresholds: [%s]}", []int{0, 10, 0, 20}, CompressionS2Auto, []time.Duration{0, 10 * time.Millisecond, 0, 20 * time.Millisecond}},
		{"s2_auto:0,0,0,20", "{mode: s2_auto, thresholds: [%s]}", []int{0, 0, 0, 20}, CompressionS2Auto, []time.Duration{0, 0, 0, 20 * time.Millisecond}},
		{"s2_auto:0,10,0,0", "{mode: s2_auto, rtt_thresholds: [%s]}", []int{0, 10, 0, 0}, CompressionS2Auto, []time.Duration{0, 10 * time.Millisecond}},
	} {
		t.Run("remote leaf "+test.name, func(t *testing.T) {
			var val string
			if len(test.rttVals) > 0 {
				var rtts string
				for i, v := range test.rttVals {
					if i > 0 {
						rtts += ", "
					}
					rtts += fmt.Sprintf("%dms", v)
				}
				val = fmt.Sprintf(test.mode, rtts)
			} else {
				val = test.mode
			}
			conf := createConfFile(t, []byte(fmt.Sprintf(tmpl, val)))
			s, o := RunServerWithConfig(conf)
			defer s.Shutdown()

			r := o.LeafNode.Remotes[0]

			if cm := r.Compression.Mode; cm != test.expected {
				t.Fatalf("Expected compression value to be %q, got %q", test.expected, cm)
			}
			if !reflect.DeepEqual(test.rtts, r.Compression.RTTThresholds) {
				t.Fatalf("Expected RTT tresholds to be %+v, got %+v", test.rtts, r.Compression.RTTThresholds)
			}
			s.Shutdown()

			o.LeafNode.Port = -1
			o.LeafNode.Remotes[0].Compression.Mode = test.mode
			if len(test.rttVals) > 0 {
				o.LeafNode.Remotes[0].Compression.Mode = CompressionS2Auto
				o.LeafNode.Remotes[0].Compression.RTTThresholds = o.LeafNode.Remotes[0].Compression.RTTThresholds[:0]
				for _, v := range test.rttVals {
					o.LeafNode.Remotes[0].Compression.RTTThresholds = append(o.LeafNode.Remotes[0].Compression.RTTThresholds, time.Duration(v)*time.Millisecond)
				}
			}
			s = RunServer(o)
			defer s.Shutdown()
			if cm := o.LeafNode.Remotes[0].Compression.Mode; cm != test.expected {
				t.Fatalf("Expected compression value to be %q, got %q", test.expected, cm)
			}
			if !reflect.DeepEqual(test.rtts, o.LeafNode.Remotes[0].Compression.RTTThresholds) {
				t.Fatalf("Expected RTT tresholds to be %+v, got %+v", test.rtts, o.LeafNode.Remotes[0].Compression.RTTThresholds)
			}
		})
	}

	// Test that with no compression specified, we default to "s2_auto"
	conf := createConfFile(t, []byte(`
		port: -1
		leafnodes {
			port: -1
		}
	`))
	s, o := RunServerWithConfig(conf)
	defer s.Shutdown()
	if o.LeafNode.Compression.Mode != CompressionS2Auto {
		t.Fatalf("Expected compression value to be %q, got %q", CompressionAccept, o.LeafNode.Compression.Mode)
	}
	if !reflect.DeepEqual(defaultCompressionS2AutoRTTThresholds, o.LeafNode.Compression.RTTThresholds) {
		t.Fatalf("Expected RTT tresholds to be %+v, got %+v", defaultCompressionS2AutoRTTThresholds, o.LeafNode.Compression.RTTThresholds)
	}
	// Same for remotes
	conf = createConfFile(t, []byte(`
		port: -1
		leafnodes {
			port: -1
			remotes [ { url: "nats://127.0.0.1:1234" } ]
		}
	`))
	s, o = RunServerWithConfig(conf)
	defer s.Shutdown()
	if cm := o.LeafNode.Remotes[0].Compression.Mode; cm != CompressionS2Auto {
		t.Fatalf("Expected compression value to be %q, got %q", CompressionAccept, cm)
	}
	if !reflect.DeepEqual(defaultCompressionS2AutoRTTThresholds, o.LeafNode.Remotes[0].Compression.RTTThresholds) {
		t.Fatalf("Expected RTT tresholds to be %+v, got %+v", defaultCompressionS2AutoRTTThresholds, o.LeafNode.Remotes[0].Compression.RTTThresholds)
	}
	for _, test := range []struct {
		name string
		mode string
		rtts []time.Duration
		err  string
	}{
		{"unsupported mode", "gzip", nil, "unsupported"},
		{"not ascending order", "s2_auto", []time.Duration{
			5 * time.Millisecond,
			10 * time.Millisecond,
			2 * time.Millisecond,
		}, "ascending"},
		{"too many thresholds", "s2_auto", []time.Duration{
			5 * time.Millisecond,
			10 * time.Millisecond,
			20 * time.Millisecond,
			40 * time.Millisecond,
			60 * time.Millisecond,
		}, "more than 4"},
		{"all 0", "s2_auto", []time.Duration{0, 0, 0, 0}, "at least one"},
		{"single 0", "s2_auto", []time.Duration{0}, "at least one"},
	} {
		t.Run(test.name, func(t *testing.T) {
			o := DefaultOptions()
			o.LeafNode.Port = -1
			o.LeafNode.Compression = CompressionOpts{test.mode, test.rtts}
			if _, err := NewServer(o); err == nil || !strings.Contains(err.Error(), test.err) {
				t.Fatalf("Unexpected error: %v", err)
			}
			// Same with remotes
			o.LeafNode.Compression = CompressionOpts{}
			o.LeafNode.Remotes = []*RemoteLeafOpts{{Compression: CompressionOpts{test.mode, test.rtts}}}
			if _, err := NewServer(o); err == nil || !strings.Contains(err.Error(), test.err) {
				t.Fatalf("Unexpected error: %v", err)
			}
		})
	}
}

func TestLeafNodeCompression(t *testing.T) {
	conf1 := createConfFile(t, []byte(`
		port: -1
		server_name: "Hub"
		accounts {
			A { users: [{user: a, password: pwd}] }
			B { users: [{user: b, password: pwd}] }
			C { users: [{user: c, password: pwd}] }
		}
		leafnodes {
			port: -1
			compression: s2_fast
		}
	`))
	s1, o1 := RunServerWithConfig(conf1)
	defer s1.Shutdown()

	port := o1.LeafNode.Port
	conf2 := createConfFile(t, []byte(fmt.Sprintf(`
		port: -1
		server_name: "Spoke"
		accounts {
			A { users: [{user: a, password: pwd}] }
			B { users: [{user: b, password: pwd}] }
			C { users: [{user: c, password: pwd}] }
		}
		leafnodes {
			remotes [
				{ url: "nats://a:pwd@127.0.0.1:%d", account: "A", compression: s2_better }
				{ url: "nats://b:pwd@127.0.0.1:%d", account: "B", compression: s2_best }
				{ url: "nats://c:pwd@127.0.0.1:%d", account: "C", compression: off }
			]
		}
	`, port, port, port)))
	s2, _ := RunServerWithConfig(conf2)
	defer s2.Shutdown()

	checkLeafNodeConnectedCount(t, s1, 3)
	checkLeafNodeConnectedCount(t, s2, 3)

	s1.mu.RLock()
	for _, l := range s1.leafs {
		l.mu.Lock()
		l.nc = &testConnSentBytes{Conn: l.nc}
		l.mu.Unlock()
	}
	s1.mu.RUnlock()

	var payloads [][]byte
	totalPayloadSize := 0
	count := 26
	for i := 0; i < count; i++ {
		n := rand.Intn(2048) + 1
		p := make([]byte, n)
		for j := 0; j < n; j++ {
			p[j] = byte(i) + 'A'
		}
		totalPayloadSize += len(p)
		payloads = append(payloads, p)
	}

	check := func(acc, user, subj string) {
		t.Helper()
		nc2 := natsConnect(t, s2.ClientURL(), nats.UserInfo(user, "pwd"))
		defer nc2.Close()
		sub := natsSubSync(t, nc2, subj)
		natsFlush(t, nc2)
		checkSubInterest(t, s1, acc, subj, time.Second)

		nc1 := natsConnect(t, s1.ClientURL(), nats.UserInfo(user, "pwd"))
		defer nc1.Close()

		for i := 0; i < count; i++ {
			natsPub(t, nc1, subj, payloads[i])
		}
		for i := 0; i < count; i++ {
			m := natsNexMsg(t, sub, time.Second)
			if !bytes.Equal(m.Data, payloads[i]) {
				t.Fatalf("Expected payload %q - got %q", payloads[i], m.Data)
			}
		}

		// Also check that the leafnode stats shows that compression likely occurred
		var out int
		s1.mu.RLock()
		for _, l := range s1.leafs {
			l.mu.Lock()
			if l.acc.Name == acc && l.nc != nil {
				nc := l.nc.(*testConnSentBytes)
				nc.Lock()
				out = nc.sent
				nc.sent = 0
				nc.Unlock()
			}
			l.mu.Unlock()
		}
		s1.mu.RUnlock()
		// Except for account "C", where compression should be off,
		// "out" should at least be smaller than totalPayloadSize, use 20%.
		if acc == "C" {
			if int(out) < totalPayloadSize {
				t.Fatalf("Expected s1's sent bytes to be at least payload size (%v), got %v", totalPayloadSize, out)
			}
		} else {
			limit := totalPayloadSize * 80 / 100
			if int(out) > limit {
				t.Fatalf("Expected s1's sent bytes to be less than %v, got %v (total payload was %v)", limit, out, totalPayloadSize)
			}
		}
	}
	check("A", "a", "foo")
	check("B", "b", "bar")
	check("C", "c", "baz")

	// Check compression settings. S1 should always be s2_fast, except for account "C"
	// since "C" wanted compression "off"
	l, err := s1.Leafz(nil)
	require_NoError(t, err)
	for _, r := range l.Leafs {
		switch r.Account {
		case "C":
			if r.Compression != CompressionOff {
				t.Fatalf("Expected compression of remote for C account to be %q, got %q", CompressionOff, r.Compression)
			}
		default:
			if r.Compression != CompressionS2Fast {
				t.Fatalf("Expected compression of remote for %s account to be %q, got %q", r.Account, CompressionS2Fast, r.Compression)
			}
		}
	}

	l, err = s2.Leafz(nil)
	require_NoError(t, err)
	for _, r := range l.Leafs {
		switch r.Account {
		case "A":
			if r.Compression != CompressionS2Better {
				t.Fatalf("Expected compression for A account to be %q, got %q", CompressionS2Better, r.Compression)
			}
		case "B":
			if r.Compression != CompressionS2Best {
				t.Fatalf("Expected compression for B account to be %q, got %q", CompressionS2Best, r.Compression)
			}
		case "C":
			if r.Compression != CompressionOff {
				t.Fatalf("Expected compression for C account to be %q, got %q", CompressionOff, r.Compression)
			}
		}
	}
}

func BenchmarkLeafNodeCompression(b *testing.B) {
	conf1 := createConfFile(b, []byte(`
		port: -1
		server_name: "Hub"
		accounts {
			A { users: [{user: a, password: pwd}] }
			B { users: [{user: b, password: pwd}] }
			C { users: [{user: c, password: pwd}] }
			D { users: [{user: d, password: pwd}] }
		}
		leafnodes {
			port: -1
		}
	`))
	s1, o1 := RunServerWithConfig(conf1)
	defer s1.Shutdown()

	port := o1.LeafNode.Port
	conf2 := createConfFile(b, []byte(fmt.Sprintf(`
		port: -1
		server_name: "Spoke"
		accounts {
			A { users: [{user: a, password: pwd}] }
			B { users: [{user: b, password: pwd}] }
			C { users: [{user: c, password: pwd}] }
			D { users: [{user: d, password: pwd}] }
		}
		leafnodes {
			remotes [
				{ url: "nats://a:pwd@127.0.0.1:%d", account: "A", compression: s2_better }
				{ url: "nats://b:pwd@127.0.0.1:%d", account: "B", compression: s2_best }
				{ url: "nats://c:pwd@127.0.0.1:%d", account: "C", compression: s2_fast }
				{ url: "nats://d:pwd@127.0.0.1:%d", account: "D", compression: off }
			]
		}
	`, port, port, port, port)))
	s2, _ := RunServerWithConfig(conf2)
	defer s2.Shutdown()

	checkLeafNodeConnectedCount(b, s1, 4)
	checkLeafNodeConnectedCount(b, s2, 4)

	l, err := s2.Leafz(nil)
	require_NoError(b, err)
	for _, r := range l.Leafs {
		switch {
		case r.Account == "A" && r.Compression == CompressionS2Better:
		case r.Account == "B" && r.Compression == CompressionS2Best:
		case r.Account == "C" && r.Compression == CompressionS2Fast:
		case r.Account == "D" && r.Compression == CompressionOff:
		default:
			b.Fatalf("Account %q had incorrect compression mode %q on leaf connection", r.Account, r.Compression)
		}
	}

	msg := make([]byte, 1024)
	for _, p := range []struct {
		algo string
		user string
	}{
		{"Better", "a"},
		{"Best", "b"},
		{"Fast", "c"},
		{"Off", "d"},
	} {
		nc1 := natsConnect(b, s1.ClientURL(), nats.UserInfo(p.user, "pwd"))
		nc2 := natsConnect(b, s2.ClientURL(), nats.UserInfo(p.user, "pwd"))

		sub, err := nc1.SubscribeSync("foo")
		require_NoError(b, err)

		time.Sleep(time.Second)

		b.Run(p.algo, func(b *testing.B) {
			start := time.Now()

			for i := 0; i < b.N; i++ {
				err = nc2.Publish("foo", msg)
				require_NoError(b, err)

				_, err = sub.NextMsg(time.Second)
				require_NoError(b, err)
			}

			b.ReportMetric(float64(len(msg)*b.N)/1024/1024, "MB")
			b.ReportMetric(float64(len(msg)*b.N)/1024/1024/float64(time.Since(start).Seconds()), "MB/sec")
		})

		nc1.Close()
		nc2.Close()
	}
}

func TestLeafNodeCompressionMatrixModes(t *testing.T) {
	for _, test := range []struct {
		name       string
		s1         string
		s2         string
		s1Expected string
		s2Expected string
	}{
		{"off off", "off", "off", CompressionOff, CompressionOff},
		{"off accept", "off", "accept", CompressionOff, CompressionOff},
		{"off on", "off", "on", CompressionOff, CompressionOff},
		{"off better", "off", "better", CompressionOff, CompressionOff},
		{"off best", "off", "best", CompressionOff, CompressionOff},

		{"accept off", "accept", "off", CompressionOff, CompressionOff},
		{"accept accept", "accept", "accept", CompressionOff, CompressionOff},
		// Note: "on", means s2_auto, which will mean uncompressed since RTT is low.
		{"accept on", "accept", "on", CompressionS2Fast, CompressionS2Uncompressed},
		{"accept better", "accept", "better", CompressionS2Better, CompressionS2Better},
		{"accept best", "accept", "best", CompressionS2Best, CompressionS2Best},

		{"on off", "on", "off", CompressionOff, CompressionOff},
		{"on accept", "on", "accept", CompressionS2Uncompressed, CompressionS2Fast},
		{"on on", "on", "on", CompressionS2Uncompressed, CompressionS2Uncompressed},
		{"on better", "on", "better", CompressionS2Uncompressed, CompressionS2Better},
		{"on best", "on", "best", CompressionS2Uncompressed, CompressionS2Best},

		{"better off", "better", "off", CompressionOff, CompressionOff},
		{"better accept", "better", "accept", CompressionS2Better, CompressionS2Better},
		{"better on", "better", "on", CompressionS2Better, CompressionS2Uncompressed},
		{"better better", "better", "better", CompressionS2Better, CompressionS2Better},
		{"better best", "better", "best", CompressionS2Better, CompressionS2Best},

		{"best off", "best", "off", CompressionOff, CompressionOff},
		{"best accept", "best", "accept", CompressionS2Best, CompressionS2Best},
		{"best on", "best", "on", CompressionS2Best, CompressionS2Uncompressed},
		{"best better", "best", "better", CompressionS2Best, CompressionS2Better},
		{"best best", "best", "best", CompressionS2Best, CompressionS2Best},
	} {
		t.Run(test.name, func(t *testing.T) {
			conf1 := createConfFile(t, []byte(fmt.Sprintf(`
				port: -1
				server_name: "A"
				leafnodes {
					port: -1
					compression: %s
				}
			`, test.s1)))
			s1, o1 := RunServerWithConfig(conf1)
			defer s1.Shutdown()

			conf2 := createConfFile(t, []byte(fmt.Sprintf(`
				port: -1
				server_name: "B"
				leafnodes {
					remotes: [
						{url: "nats://127.0.0.1:%d", compression: %s}
					]
				}
			`, o1.LeafNode.Port, test.s2)))
			s2, _ := RunServerWithConfig(conf2)
			defer s2.Shutdown()

			checkLeafNodeConnected(t, s2)

			nc1 := natsConnect(t, s1.ClientURL())
			defer nc1.Close()

			nc2 := natsConnect(t, s2.ClientURL())
			defer nc2.Close()

			payload := make([]byte, 128)
			check := func(ncp, ncs *nats.Conn, subj string, s *Server) {
				t.Helper()
				sub := natsSubSync(t, ncs, subj)
				checkSubInterest(t, s, globalAccountName, subj, time.Second)
				natsPub(t, ncp, subj, payload)
				natsNexMsg(t, sub, time.Second)

				for _, srv := range []*Server{s1, s2} {
					lz, err := srv.Leafz(nil)
					require_NoError(t, err)
					var expected string
					if srv == s1 {
						expected = test.s1Expected
					} else {
						expected = test.s2Expected
					}
					if cm := lz.Leafs[0].Compression; cm != expected {
						t.Fatalf("Server %s - expected compression %q, got %q", srv, expected, cm)
					}
				}
			}
			check(nc1, nc2, "foo", s1)
			check(nc2, nc1, "bar", s2)
		})
	}
}

func TestLeafNodeCompressionWithOlderServer(t *testing.T) {
	tmpl1 := `
		port: -1
		server_name: "A"
		leafnodes {
			port: -1
			compression: "%s"
		}
	`
	conf1 := createConfFile(t, []byte(fmt.Sprintf(tmpl1, CompressionS2Fast)))
	s1, o1 := RunServerWithConfig(conf1)
	defer s1.Shutdown()

	tmpl2 := `
		port: -1
		server_name: "B"
		leafnodes {
			remotes [
				{url: "nats://127.0.0.1:%d", compression: "%s"}
			]
		}
	`
	conf2 := createConfFile(t, []byte(fmt.Sprintf(tmpl2, o1.LeafNode.Port, CompressionNotSupported)))
	s2, _ := RunServerWithConfig(conf2)
	defer s2.Shutdown()

	checkLeafNodeConnected(t, s2)

	getLeafCompMode := func(s *Server) string {
		var cm string
		s.mu.RLock()
		defer s.mu.RUnlock()
		for _, l := range s1.leafs {
			l.mu.Lock()
			cm = l.leaf.compression
			l.mu.Unlock()
			return cm
		}
		return _EMPTY_
	}
	for _, s := range []*Server{s1, s2} {
		if cm := getLeafCompMode(s); cm != CompressionNotSupported {
			t.Fatalf("Expected compression not supported, got %q", cm)
		}
	}

	s2.Shutdown()
	s1.Shutdown()

	conf1 = createConfFile(t, []byte(fmt.Sprintf(tmpl1, CompressionNotSupported)))
	s1, o1 = RunServerWithConfig(conf1)
	defer s1.Shutdown()

	conf2 = createConfFile(t, []byte(fmt.Sprintf(tmpl2, o1.LeafNode.Port, CompressionS2Fast)))
	s2, _ = RunServerWithConfig(conf2)
	defer s2.Shutdown()

	checkLeafNodeConnected(t, s2)
	for _, s := range []*Server{s1, s2} {
		if cm := getLeafCompMode(s); cm != CompressionNotSupported {
			t.Fatalf("Expected compression not supported, got %q", cm)
		}
	}
}

func TestLeafNodeCompressionAuto(t *testing.T) {
	for _, test := range []struct {
		name          string
		s1Ping        string
		s1Compression string
		s2Ping        string
		s2Compression string
		checkS1       bool
	}{
		{"remote side", "10s", CompressionS2Fast, "100ms", "{mode: s2_auto, rtt_thresholds: [10ms, 20ms, 30ms]}", false},
		{"accept side", "100ms", "{mode: s2_auto, rtt_thresholds: [10ms, 20ms, 30ms]}", "10s", CompressionS2Fast, true},
	} {
		t.Run(test.name, func(t *testing.T) {
			conf1 := createConfFile(t, []byte(fmt.Sprintf(`
				port: -1
				server_name: "A"
				ping_interval: "%s"
				leafnodes {
					port: -1
					compression: %s
				}
			`, test.s1Ping, test.s1Compression)))
			s1, o1 := RunServerWithConfig(conf1)
			defer s1.Shutdown()

			// Start with 0ms RTT
			np := createNetProxy(0, 1024*1024*1024, 1024*1024*1024, fmt.Sprintf("nats://127.0.0.1:%d", o1.LeafNode.Port), true)

			conf2 := createConfFile(t, []byte(fmt.Sprintf(`
				port: -1
				server_name: "B"
				ping_interval: "%s"
				leafnodes {
					remotes [
						{url: %s, compression %s}
					]
				}
			`, test.s2Ping, np.routeURL(), test.s2Compression)))
			s2, _ := RunServerWithConfig(conf2)
			defer s2.Shutdown()
			defer np.stop()

			checkLeafNodeConnected(t, s2)

			checkComp := func(expected string) {
				t.Helper()
				var s *Server
				if test.checkS1 {
					s = s1
				} else {
					s = s2
				}
				checkFor(t, 2*time.Second, 15*time.Millisecond, func() error {
					s.mu.RLock()
					defer s.mu.RUnlock()
					for _, l := range s.leafs {
						l.mu.Lock()
						cm := l.leaf.compression
						l.mu.Unlock()
						if cm != expected {
							return fmt.Errorf("Leaf %v compression mode expected to be %q, got %q", l, expected, cm)
						}
					}
					return nil
				})
			}
			checkComp(CompressionS2Uncompressed)

			// Change the proxy RTT and we should get compression "fast"
			np.updateRTT(15 * time.Millisecond)
			checkComp(CompressionS2Fast)

			// Now 25ms, and get "better"
			np.updateRTT(25 * time.Millisecond)
			checkComp(CompressionS2Better)

			// Above 35 and we should get "best"
			np.updateRTT(35 * time.Millisecond)
			checkComp(CompressionS2Best)

			// Down to 1ms and again should get "uncompressed"
			np.updateRTT(1 * time.Millisecond)
			checkComp(CompressionS2Uncompressed)
		})
	}

	// Make sure that if compression is off on one side, the update of RTT does
	// not trigger a compression change.
	conf1 := createConfFile(t, []byte(`
		port: -1
		server_name: "A"
		leafnodes {
			port: -1
			compression: off
		}
	`))
	s1, o1 := RunServerWithConfig(conf1)
	defer s1.Shutdown()

	// Start with 0ms RTT
	np := createNetProxy(0, 1024*1024*1024, 1024*1024*1024, fmt.Sprintf("nats://127.0.0.1:%d", o1.LeafNode.Port), true)

	conf2 := createConfFile(t, []byte(fmt.Sprintf(`
		port: -1
		server_name: "B"
		ping_interval: "50ms"
		leafnodes {
			remotes [
				{url: %s, compression s2_auto}
			]
		}
	`, np.routeURL())))
	s2, _ := RunServerWithConfig(conf2)
	defer s2.Shutdown()
	defer np.stop()

	checkLeafNodeConnected(t, s2)

	// Even with a bug of updating compression level while it should have been
	// off, the check done below would almost always pass because after
	// reconnecting, there could be a chance to get at first compression set
	// to "off". So we will double check that the leaf node CID did not change
	// at the end of the test.
	getCID := func() uint64 {
		s2.mu.RLock()
		defer s2.mu.RUnlock()
		for _, l := range s2.leafs {
			l.mu.Lock()
			cid := l.cid
			l.mu.Unlock()
			return cid
		}
		return 0
	}
	oldCID := getCID()

	checkCompOff := func() {
		t.Helper()
		checkFor(t, 2*time.Second, 15*time.Millisecond, func() error {
			s2.mu.RLock()
			defer s2.mu.RUnlock()
			if len(s2.leafs) != 1 {
				return fmt.Errorf("Leaf not currently connected")
			}
			for _, l := range s2.leafs {
				l.mu.Lock()
				cm := l.leaf.compression
				l.mu.Unlock()
				if cm != CompressionOff {
					return fmt.Errorf("Leaf %v compression mode expected to be %q, got %q", l, CompressionOff, cm)
				}
			}
			return nil
		})
	}
	checkCompOff()

	// Now change RTT and again, make sure that it is still off
	np.updateRTT(20 * time.Millisecond)
	time.Sleep(100 * time.Millisecond)
	checkCompOff()
	if cid := getCID(); cid != oldCID {
		t.Fatalf("Leafnode has reconnected, cid was %v, now %v", oldCID, cid)
	}
}

func TestLeafNodeCompressionWithWSCompression(t *testing.T) {
	conf1 := createConfFile(t, []byte(`
		port: -1
		server_name: "A"
		websocket {
			port: -1
			no_tls: true
			compression: true
		}
		leafnodes {
			port: -1
			compression: s2_fast
		}
	`))
	s1, o1 := RunServerWithConfig(conf1)
	defer s1.Shutdown()

	conf2 := createConfFile(t, []byte(fmt.Sprintf(`
		port: -1
		server_name: "B"
		leafnodes {
			remotes [
				{
					url: "ws://127.0.0.1:%d"
					ws_compression: true
					compression: s2_fast
				}
			]
		}
	`, o1.Websocket.Port)))
	s2, _ := RunServerWithConfig(conf2)
	defer s2.Shutdown()

	checkLeafNodeConnected(t, s2)

	nc1 := natsConnect(t, s1.ClientURL())
	defer nc1.Close()

	sub := natsSubSync(t, nc1, "foo")
	checkSubInterest(t, s2, globalAccountName, "foo", time.Second)

	nc2 := natsConnect(t, s2.ClientURL())
	defer nc2.Close()

	payload := make([]byte, 1024)
	for i := 0; i < len(payload); i++ {
		payload[i] = 'A'
	}
	natsPub(t, nc2, "foo", payload)
	msg := natsNexMsg(t, sub, time.Second)
	require_True(t, len(msg.Data) == 1024)
	for i := 0; i < len(msg.Data); i++ {
		if msg.Data[i] != 'A' {
			t.Fatalf("Invalid msg: %s", msg.Data)
		}
	}
}

func TestLeafNodeCompressionWithWSGetNeedsData(t *testing.T) {
	conf1 := createConfFile(t, []byte(`
		port: -1
		server_name: "A"
		websocket {
			port: -1
			no_tls: true
		}
		leafnodes {
			port: -1
			compression: s2_fast
		}
	`))
	srv1, o1 := RunServerWithConfig(conf1)
	defer srv1.Shutdown()

	conf2 := createConfFile(t, []byte(fmt.Sprintf(`
		port: -1
		server_name: "B"
		leafnodes {
			remotes [
				{
					url: "ws://127.0.0.1:%d"
					ws_no_masking: true
					compression: s2_fast
				}
			]
		}
	`, o1.Websocket.Port)))
	srv2, _ := RunServerWithConfig(conf2)
	defer srv2.Shutdown()

	checkLeafNodeConnected(t, srv2)

	nc1 := natsConnect(t, srv1.ClientURL())
	defer nc1.Close()

	sub := natsSubSync(t, nc1, "foo")
	checkSubInterest(t, srv2, globalAccountName, "foo", time.Second)

	// We want to have the payload more than 126 bytes so that the websocket
	// code need to read 2 bytes for the length. See below.
	payload := "ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ"
	sentBytes := []byte("LMSG foo 156\r\n" + payload + "\r\n")
	h, _ := wsCreateFrameHeader(false, false, wsBinaryMessage, len(sentBytes))
	combined := &bytes.Buffer{}
	combined.Write(h)
	combined.Write(sentBytes)
	toSend := combined.Bytes()

	// We will make a compressed block that cuts the websocket header that
	// makes the reader want to read bytes directly from the connection.
	// We want to make sure that we are not going to get compressed data
	// without going through the (de)compress library. So for that, compress
	// the first 3 bytes.
	b := &bytes.Buffer{}
	w := s2.NewWriter(b)
	w.Write(toSend[:3])
	w.Close()

	var nc net.Conn
	srv2.mu.RLock()
	for _, l := range srv2.leafs {
		l.mu.Lock()
		nc = l.nc
		l.mu.Unlock()
	}
	srv2.mu.RUnlock()

	nc.Write(b.Bytes())

	// Pause to make sure other side just gets a partial of the whole WS frame.
	time.Sleep(100 * time.Millisecond)

	b.Reset()
	w.Reset(b)
	w.Write(toSend[3:])
	w.Close()

	nc.Write(b.Bytes())

	msg := natsNexMsg(t, sub, time.Second)
	require_True(t, len(msg.Data) == 156)
	require_Equal(t, string(msg.Data), payload)
}

func TestLeafNodeCompressionAuthTimeout(t *testing.T) {
	hconf := createConfFile(t, []byte(`
		port: -1
		server_name: "hub"
		leafnodes {
			port: -1
			authorization {
				timeout: 0.75
			}
		}
	`))
	sh, oh := RunServerWithConfig(hconf)
	defer sh.Shutdown()

	sconfTmpl := `
		port: -1
		server_name: "%s"
		cluster {
			port: -1
			name: "spoke"
			%s
		}
		leafnodes {
			port: -1
			remotes [
				{ url: "nats://127.0.0.1:%d" }
			]
		}
	`
	s1conf := createConfFile(t, []byte(fmt.Sprintf(sconfTmpl, "SP1", _EMPTY_, oh.LeafNode.Port)))
	s1, o1 := RunServerWithConfig(s1conf)
	defer s1.Shutdown()

	s2conf := createConfFile(t, []byte(fmt.Sprintf(sconfTmpl, "SP2", fmt.Sprintf("routes: [\"nats://127.0.0.1:%d\"]", o1.Cluster.Port), oh.LeafNode.Port)))
	s2, _ := RunServerWithConfig(s2conf)
	defer s2.Shutdown()

	checkClusterFormed(t, s1, s2)

	checkLeafNodeConnected(t, s1)
	checkLeafNodeConnected(t, s2)

	getCID := func(s *Server) uint64 {
		s.mu.RLock()
		defer s.mu.RUnlock()
		var cid uint64
		for _, l := range s.leafs {
			l.mu.Lock()
			cid = l.cid
			l.mu.Unlock()
		}
		return cid
	}
	leaf1 := getCID(s1)
	leaf2 := getCID(s2)

	// Wait for more than auth timeout
	time.Sleep(time.Second)

	checkLeafNodeConnected(t, s1)
	checkLeafNodeConnected(t, s2)
	if l1 := getCID(s1); l1 != leaf1 {
		t.Fatalf("Leaf connection first connection had CID %v, now %v", leaf1, l1)
	}
	if l2 := getCID(s2); l2 != leaf2 {
		t.Fatalf("Leaf connection first connection had CID %v, now %v", leaf2, l2)
	}
}

func TestLeafNodeWithWeightedDQRequestsToSuperClusterWithSeparateAccounts(t *testing.T) {
	sc := createJetStreamSuperClusterWithTemplate(t, jsClusterAccountsTempl, 3, 2)
	defer sc.shutdown()

	// Now create a leafnode cluster that has 2 LNs, one to each cluster but on separate accounts, ONE and TWO.
	var lnTmpl = `
		listen: 127.0.0.1:-1
		server_name: %s
		jetstream: {max_mem_store: 256MB, max_file_store: 2GB, store_dir: '%s'}

		{{leaf}}

		cluster {
			name: %s
			listen: 127.0.0.1:%d
			routes = [%s]
		}

		accounts { $SYS { users = [ { user: "admin", pass: "s3cr3t!" } ] }}
		`

	var leafFrag = `
		leaf {
			listen: 127.0.0.1:-1
			remotes [
				{ urls: [ %s ] }
				{ urls: [ %s ] }
			]
		}`

	// We want to have two leaf node connections that join to the same local account on the leafnode servers,
	// but connect to different accounts in different clusters.
	c1 := sc.clusters[0] // Will connect to account ONE
	c2 := sc.clusters[1] // Will connect to account TWO

	genLeafTmpl := func(tmpl string) string {
		t.Helper()

		var ln1, ln2 []string
		for _, s := range c1.servers {
			if s.ClusterName() != c1.name {
				continue
			}
			ln := s.getOpts().LeafNode
			ln1 = append(ln1, fmt.Sprintf("nats://one:p@%s:%d", ln.Host, ln.Port))
		}

		for _, s := range c2.servers {
			if s.ClusterName() != c2.name {
				continue
			}
			ln := s.getOpts().LeafNode
			ln2 = append(ln2, fmt.Sprintf("nats://two:p@%s:%d", ln.Host, ln.Port))
		}
		return strings.Replace(tmpl, "{{leaf}}", fmt.Sprintf(leafFrag, strings.Join(ln1, ", "), strings.Join(ln2, ", ")), 1)
	}

	tmpl := strings.Replace(lnTmpl, "store_dir:", fmt.Sprintf(`domain: "%s", store_dir:`, "SA"), 1)
	tmpl = genLeafTmpl(tmpl)

	ln := createJetStreamCluster(t, tmpl, "SA", "SA-", 3, 22280, false)
	ln.waitOnClusterReady()
	defer ln.shutdown()

	for _, s := range ln.servers {
		checkLeafNodeConnectedCount(t, s, 2)
	}

	// Now connect DQ subscribers to each cluster and they separate accounts, and make sure we get the right behavior, balanced between
	// them when requests originate from the leaf cluster.

	// Create 5 clients for each cluster / account
	var c1c, c2c []*nats.Conn
	for i := 0; i < 5; i++ {
		nc1, _ := jsClientConnect(t, c1.randomServer(), nats.UserInfo("one", "p"))
		defer nc1.Close()
		c1c = append(c1c, nc1)
		nc2, _ := jsClientConnect(t, c2.randomServer(), nats.UserInfo("two", "p"))
		defer nc2.Close()
		c2c = append(c2c, nc2)
	}

	createSubs := func(num int, conns []*nats.Conn) (subs []*nats.Subscription) {
		for i := 0; i < num; i++ {
			nc := conns[rand.Intn(len(conns))]
			sub, err := nc.QueueSubscribeSync("REQUEST", "MC")
			require_NoError(t, err)
			subs = append(subs, sub)
			nc.Flush()
		}
		// Let subs propagate.
		time.Sleep(100 * time.Millisecond)
		return subs
	}
	closeSubs := func(subs []*nats.Subscription) {
		for _, sub := range subs {
			sub.Unsubscribe()
		}
	}

	// Simple test first.
	subs1 := createSubs(1, c1c)
	defer closeSubs(subs1)
	subs2 := createSubs(1, c2c)
	defer closeSubs(subs2)

	sendRequests := func(num int) {
		t.Helper()
		// Now connect to the leaf cluster and send some requests.
		nc, _ := jsClientConnect(t, ln.randomServer())
		defer nc.Close()

		for i := 0; i < num; i++ {
			require_NoError(t, nc.Publish("REQUEST", []byte("HELP")))
		}
		nc.Flush()
	}

	pending := func(subs []*nats.Subscription) (total int) {
		t.Helper()
		for _, sub := range subs {
			n, _, err := sub.Pending()
			require_NoError(t, err)
			total += n
		}
		return total
	}

	num := 1000
	checkAllReceived := func() error {
		total := pending(subs1) + pending(subs2)
		if total == num {
			return nil
		}
		return fmt.Errorf("Not all received: %d vs %d", total, num)
	}

	checkBalanced := func(total, pc1, pc2 int) {
		t.Helper()
		tf := float64(total)
		e1 := tf * (float64(pc1) / 100.00)
		e2 := tf * (float64(pc2) / 100.00)
		delta := tf / 10
		p1 := float64(pending(subs1))
		if p1 < e1-delta || p1 > e1+delta {
			t.Fatalf("Value out of range for subs1, expected %v got %v", e1, p1)
		}
		p2 := float64(pending(subs2))
		if p2 < e2-delta || p2 > e2+delta {
			t.Fatalf("Value out of range for subs2, expected %v got %v", e2, p2)
		}
	}

	// Now connect to the leaf cluster and send some requests.

	// Simple 50/50
	sendRequests(num)
	checkFor(t, time.Second, 200*time.Millisecond, checkAllReceived)
	checkBalanced(num, 50, 50)

	closeSubs(subs1)
	closeSubs(subs2)

	// Now test unbalanced. 10/90
	subs1 = createSubs(1, c1c)
	defer closeSubs(subs1)
	subs2 = createSubs(9, c2c)
	defer closeSubs(subs2)

	sendRequests(num)
	checkFor(t, time.Second, 200*time.Millisecond, checkAllReceived)
	checkBalanced(num, 10, 90)

	// Now test draining the subs as we are sending from an initial balanced situation simulating a draining of a cluster.

	closeSubs(subs1)
	closeSubs(subs2)
	subs1, subs2 = nil, nil

	// These subs slightly different.
	var r1, r2 atomic.Uint64
	for i := 0; i < 20; i++ {
		nc := c1c[rand.Intn(len(c1c))]
		sub, err := nc.QueueSubscribe("REQUEST", "MC", func(m *nats.Msg) { r1.Add(1) })
		require_NoError(t, err)
		subs1 = append(subs1, sub)
		nc.Flush()

		nc = c2c[rand.Intn(len(c2c))]
		sub, err = nc.QueueSubscribe("REQUEST", "MC", func(m *nats.Msg) { r2.Add(1) })
		require_NoError(t, err)
		subs2 = append(subs2, sub)
		nc.Flush()
	}
	defer closeSubs(subs1)
	defer closeSubs(subs2)

	nc, _ := jsClientConnect(t, ln.randomServer())
	defer nc.Close()

	for i, dindex := 0, 1; i < num; i++ {
		require_NoError(t, nc.Publish("REQUEST", []byte("HELP")))
		// Check if we have more to simulate draining.
		// Will drain within first ~100 requests using 20% rand test below.
		// Will leave 1 behind.
		if dindex < len(subs1)-1 && rand.Intn(6) > 4 {
			sub := subs1[dindex]
			dindex++
			sub.Drain()
		}
	}
	nc.Flush()

	checkFor(t, time.Second, 200*time.Millisecond, func() error {
		total := int(r1.Load() + r2.Load())
		if total == num {
			return nil
		}
		return fmt.Errorf("Not all received: %d vs %d", total, num)
	})
	require_True(t, r2.Load() > r1.Load())
}

func TestLeafNodeWithWeightedDQRequestsToSuperClusterWithStreamImportAccounts(t *testing.T) {
	var tmpl = `
	listen: 127.0.0.1:-1

	server_name: %s
	jetstream: {max_mem_store: 256MB, max_file_store: 2GB, store_dir: '%s'}

	leaf { listen: 127.0.0.1:-1 }

	cluster {
		name: %s
		listen: 127.0.0.1:%d
		routes = [%s]
	}

	accounts {
		EFG {
			users = [ { user: "efg", pass: "p" } ]
			jetstream: enabled
			imports [
				{ stream: { account: STL, subject: "REQUEST"} }
				{ stream: { account: KSC, subject: "REQUEST"} }
			]
			exports [ { stream: "RESPONSE" } ]
		}
		STL {
			users = [ { user: "stl", pass: "p" } ]
			exports [ { stream: "REQUEST" } ]
			imports [ { stream: { account: EFG, subject: "RESPONSE"} } ]
		}
		KSC {
			users = [ { user: "ksc", pass: "p" } ]
			exports [ { stream: "REQUEST" } ]
			imports [ { stream: { account: EFG, subject: "RESPONSE"} } ]
		}
		$SYS { users = [ { user: "admin", pass: "s3cr3t!" } ] }
	}`

	sc := createJetStreamSuperClusterWithTemplate(t, tmpl, 5, 2)
	defer sc.shutdown()

	// Now create a leafnode cluster that has 2 LNs, one to each cluster but on separate accounts, STL and KSC.
	var lnTmpl = `
		listen: 127.0.0.1:-1
		server_name: %s
		jetstream: {max_mem_store: 256MB, max_file_store: 2GB, store_dir: '%s'}

		{{leaf}}

		cluster {
			name: %s
			listen: 127.0.0.1:%d
			routes = [%s]
		}

		accounts { $SYS { users = [ { user: "admin", pass: "s3cr3t!" } ] }}
		`

	var leafFrag = `
		leaf {
			listen: 127.0.0.1:-1
			remotes [
				{ urls: [ %s ] }
				{ urls: [ %s ] }
				{ urls: [ %s ] ; deny_export: [REQUEST, RESPONSE], deny_import: RESPONSE }
			]
		}`

	// We want to have two leaf node connections that join to the same local account on the leafnode servers,
	// but connect to different accounts in different clusters.
	c1 := sc.clusters[0] // Will connect to account KSC
	c2 := sc.clusters[1] // Will connect to account STL

	genLeafTmpl := func(tmpl string) string {
		t.Helper()

		var ln1, ln2, ln3 []string
		for _, s := range c1.servers {
			if s.ClusterName() != c1.name {
				continue
			}
			ln := s.getOpts().LeafNode
			ln1 = append(ln1, fmt.Sprintf("nats://ksc:p@%s:%d", ln.Host, ln.Port))
		}

		for _, s := range c2.servers {
			if s.ClusterName() != c2.name {
				continue
			}
			ln := s.getOpts().LeafNode
			ln2 = append(ln2, fmt.Sprintf("nats://stl:p@%s:%d", ln.Host, ln.Port))
			ln3 = append(ln3, fmt.Sprintf("nats://efg:p@%s:%d", ln.Host, ln.Port))
		}
		return strings.Replace(tmpl, "{{leaf}}", fmt.Sprintf(leafFrag, strings.Join(ln1, ", "), strings.Join(ln2, ", "), strings.Join(ln3, ", ")), 1)
	}

	tmpl = strings.Replace(lnTmpl, "store_dir:", fmt.Sprintf(`domain: "%s", store_dir:`, "SA"), 1)
	tmpl = genLeafTmpl(tmpl)

	ln := createJetStreamCluster(t, tmpl, "SA", "SA-", 3, 22280, false)
	ln.waitOnClusterReady()
	defer ln.shutdown()

	for _, s := range ln.servers {
		checkLeafNodeConnectedCount(t, s, 3)
	}

	// Now connect DQ subscribers to each cluster but to the global account.

	// Create 5 clients for each cluster / account
	var c1c, c2c []*nats.Conn
	for i := 0; i < 5; i++ {
		nc1, _ := jsClientConnect(t, c1.randomServer(), nats.UserInfo("efg", "p"))
		defer nc1.Close()
		c1c = append(c1c, nc1)
		nc2, _ := jsClientConnect(t, c2.randomServer(), nats.UserInfo("efg", "p"))
		defer nc2.Close()
		c2c = append(c2c, nc2)
	}

	createSubs := func(num int, conns []*nats.Conn) (subs []*nats.Subscription) {
		for i := 0; i < num; i++ {
			nc := conns[rand.Intn(len(conns))]
			sub, err := nc.QueueSubscribeSync("REQUEST", "MC")
			require_NoError(t, err)
			subs = append(subs, sub)
			nc.Flush()
		}
		// Let subs propagate.
		time.Sleep(100 * time.Millisecond)
		return subs
	}
	closeSubs := func(subs []*nats.Subscription) {
		for _, sub := range subs {
			sub.Unsubscribe()
		}
	}

	// Simple test first.
	subs1 := createSubs(1, c1c)
	defer closeSubs(subs1)
	subs2 := createSubs(1, c2c)
	defer closeSubs(subs2)

	sendRequests := func(num int) {
		t.Helper()
		// Now connect to the leaf cluster and send some requests.
		nc, _ := jsClientConnect(t, ln.randomServer())
		defer nc.Close()

		for i := 0; i < num; i++ {
			require_NoError(t, nc.Publish("REQUEST", []byte("HELP")))
		}
		nc.Flush()
	}

	pending := func(subs []*nats.Subscription) (total int) {
		t.Helper()
		for _, sub := range subs {
			n, _, err := sub.Pending()
			require_NoError(t, err)
			total += n
		}
		return total
	}

	num := 1000
	checkAllReceived := func() error {
		total := pending(subs1) + pending(subs2)
		if total == num {
			return nil
		}
		return fmt.Errorf("Not all received: %d vs %d", total, num)
	}

	checkBalanced := func(total, pc1, pc2 int) {
		t.Helper()
		tf := float64(total)
		e1 := tf * (float64(pc1) / 100.00)
		e2 := tf * (float64(pc2) / 100.00)
		delta := tf / 10
		p1 := float64(pending(subs1))
		if p1 < e1-delta || p1 > e1+delta {
			t.Fatalf("Value out of range for subs1, expected %v got %v", e1, p1)
		}
		p2 := float64(pending(subs2))
		if p2 < e2-delta || p2 > e2+delta {
			t.Fatalf("Value out of range for subs2, expected %v got %v", e2, p2)
		}
	}

	// Now connect to the leaf cluster and send some requests.

	// Simple 50/50
	sendRequests(num)
	checkFor(t, time.Second, 200*time.Millisecond, checkAllReceived)
	checkBalanced(num, 50, 50)

	closeSubs(subs1)
	closeSubs(subs2)

	// Now test unbalanced. 10/90
	subs1 = createSubs(1, c1c)
	defer closeSubs(subs1)
	subs2 = createSubs(9, c2c)
	defer closeSubs(subs2)

	sendRequests(num)
	checkFor(t, time.Second, 200*time.Millisecond, checkAllReceived)
	checkBalanced(num, 10, 90)

	closeSubs(subs1)
	closeSubs(subs2)

	// Now test unbalanced. 80/20
	subs1 = createSubs(80, c1c)
	defer closeSubs(subs1)
	subs2 = createSubs(20, c2c)
	defer closeSubs(subs2)

	sendRequests(num)
	checkFor(t, time.Second, 200*time.Millisecond, checkAllReceived)
	checkBalanced(num, 80, 20)

	// Now test draining the subs as we are sending from an initial balanced situation simulating a draining of a cluster.

	closeSubs(subs1)
	closeSubs(subs2)
	subs1, subs2 = nil, nil

	// These subs slightly different.
	var r1, r2 atomic.Uint64
	for i := 0; i < 20; i++ {
		nc := c1c[rand.Intn(len(c1c))]
		sub, err := nc.QueueSubscribe("REQUEST", "MC", func(m *nats.Msg) { r1.Add(1) })
		require_NoError(t, err)
		subs1 = append(subs1, sub)
		nc.Flush()

		nc = c2c[rand.Intn(len(c2c))]
		sub, err = nc.QueueSubscribe("REQUEST", "MC", func(m *nats.Msg) { r2.Add(1) })
		require_NoError(t, err)
		subs2 = append(subs2, sub)
		nc.Flush()
	}
	defer closeSubs(subs1)
	defer closeSubs(subs2)

	nc, _ := jsClientConnect(t, ln.randomServer())
	defer nc.Close()

	for i, dindex := 0, 1; i < num; i++ {
		require_NoError(t, nc.Publish("REQUEST", []byte("HELP")))
		// Check if we have more to simulate draining.
		// Will drain within first ~100 requests using 20% rand test below.
		// Will leave 1 behind.
		if dindex < len(subs1)-1 && rand.Intn(6) > 4 {
			sub := subs1[dindex]
			dindex++
			sub.Drain()
		}
	}
	nc.Flush()

	checkFor(t, time.Second, 200*time.Millisecond, func() error {
		total := int(r1.Load() + r2.Load())
		if total == num {
			return nil
		}
		return fmt.Errorf("Not all received: %d vs %d", total, num)
	})
	require_True(t, r2.Load() > r1.Load())

	// Now check opposite flow for responses.

	// Create 10 subscribers.
	var rsubs []*nats.Subscription

	for i := 0; i < 10; i++ {
		nc, _ := jsClientConnect(t, ln.randomServer())
		defer nc.Close()
		sub, err := nc.QueueSubscribeSync("RESPONSE", "SA")
		require_NoError(t, err)
		nc.Flush()
		rsubs = append(rsubs, sub)
	}

	nc, _ = jsClientConnect(t, ln.randomServer())
	defer nc.Close()
	_, err := nc.SubscribeSync("RESPONSE")
	require_NoError(t, err)
	nc.Flush()

	// Now connect and send responses from EFG in cloud.
	nc, _ = jsClientConnect(t, sc.randomServer(), nats.UserInfo("efg", "p"))

	for i := 0; i < 100; i++ {
		require_NoError(t, nc.Publish("RESPONSE", []byte("OK")))
	}
	nc.Flush()

	checkAllRespReceived := func() error {
		p := pending(rsubs)
		if p == 100 {
			return nil
		}
		return fmt.Errorf("Not all responses received: %d vs %d", p, 100)
	}

	checkFor(t, time.Second, 200*time.Millisecond, checkAllRespReceived)
}

func TestLeafNodeWithWeightedDQResponsesWithStreamImportAccountsWithUnsub(t *testing.T) {
	var tmpl = `
	listen: 127.0.0.1:-1

	server_name: %s
	jetstream: {max_mem_store: 256MB, max_file_store: 2GB, store_dir: '%s'}

	leaf { listen: 127.0.0.1:-1 }

	cluster {
		name: %s
		listen: 127.0.0.1:%d
		routes = [%s]
	}

	accounts {
		EFG {
			users = [ { user: "efg", pass: "p" } ]
			jetstream: enabled
			exports [ { stream: "RESPONSE" } ]
		}
		STL {
			users = [ { user: "stl", pass: "p" } ]
			imports [ { stream: { account: EFG, subject: "RESPONSE"} } ]
		}
		KSC {
			users = [ { user: "ksc", pass: "p" } ]
			imports [ { stream: { account: EFG, subject: "RESPONSE"} } ]
		}
		$SYS { users = [ { user: "admin", pass: "s3cr3t!" } ] }
	}`

	c := createJetStreamClusterWithTemplate(t, tmpl, "US-CENTRAL", 3)
	defer c.shutdown()

	// Now create a leafnode cluster that has 2 LNs, one to each cluster but on separate accounts, STL and KSC.
	var lnTmpl = `
		listen: 127.0.0.1:-1
		server_name: %s
		jetstream: {max_mem_store: 256MB, max_file_store: 2GB, store_dir: '%s'}

		{{leaf}}

		cluster {
			name: %s
			listen: 127.0.0.1:%d
			routes = [%s]
		}

		accounts { $SYS { users = [ { user: "admin", pass: "s3cr3t!" } ] }}
		`

	var leafFrag = `
		leaf {
			listen: 127.0.0.1:-1
			remotes [ { urls: [ %s ] } ]
		}`

	genLeafTmpl := func(tmpl string) string {
		t.Helper()

		var ln []string
		for _, s := range c.servers {
			lno := s.getOpts().LeafNode
			ln = append(ln, fmt.Sprintf("nats://ksc:p@%s:%d", lno.Host, lno.Port))
		}
		return strings.Replace(tmpl, "{{leaf}}", fmt.Sprintf(leafFrag, strings.Join(ln, ", ")), 1)
	}

	tmpl = strings.Replace(lnTmpl, "store_dir:", fmt.Sprintf(`domain: "%s", store_dir:`, "SA"), 1)
	tmpl = genLeafTmpl(tmpl)

	ln := createJetStreamCluster(t, tmpl, "SA", "SA-", 3, 22280, false)
	ln.waitOnClusterReady()
	defer ln.shutdown()

	for _, s := range ln.servers {
		checkLeafNodeConnectedCount(t, s, 1)
	}

	// Create 10 subscribers.
	var rsubs []*nats.Subscription

	closeSubs := func(subs []*nats.Subscription) {
		for _, sub := range subs {
			sub.Unsubscribe()
		}
	}

	checkAllRespReceived := func() error {
		t.Helper()
		var total int
		for _, sub := range rsubs {
			n, _, err := sub.Pending()
			require_NoError(t, err)
			total += n
		}
		if total == 100 {
			return nil
		}
		return fmt.Errorf("Not all responses received: %d vs %d", total, 100)
	}

	s := ln.randomServer()
	for i := 0; i < 4; i++ {
		nc, _ := jsClientConnect(t, s)
		defer nc.Close()
		sub, err := nc.QueueSubscribeSync("RESPONSE", "SA")
		require_NoError(t, err)
		nc.Flush()
		rsubs = append(rsubs, sub)
	}

	// Now connect and send responses from EFG in cloud.
	nc, _ := jsClientConnect(t, c.randomServer(), nats.UserInfo("efg", "p"))
	for i := 0; i < 100; i++ {
		require_NoError(t, nc.Publish("RESPONSE", []byte("OK")))
	}
	nc.Flush()

	// Make sure all received.
	checkFor(t, time.Second, 200*time.Millisecond, checkAllRespReceived)

	checkAccountInterest := func(s *Server, accName string) *SublistResult {
		t.Helper()
		acc, err := s.LookupAccount(accName)
		require_NoError(t, err)
		acc.mu.RLock()
		r := acc.sl.Match("RESPONSE")
		acc.mu.RUnlock()
		return r
	}

	checkInterest := func() error {
		t.Helper()
		for _, s := range c.servers {
			if r := checkAccountInterest(s, "KSC"); len(r.psubs)+len(r.qsubs) > 0 {
				return fmt.Errorf("Subs still present for %q: %+v", "KSC", r)
			}
			if r := checkAccountInterest(s, "EFG"); len(r.psubs)+len(r.qsubs) > 0 {
				return fmt.Errorf("Subs still present for %q: %+v", "EFG", r)
			}
		}
		return nil
	}

	// Now unsub them and create new ones on a different server.
	closeSubs(rsubs)
	rsubs = rsubs[:0]

	// Also restart the server that we had all the rsubs on.
	s.Shutdown()
	s.WaitForShutdown()
	s = ln.restartServer(s)
	ln.waitOnClusterReady()
	ln.waitOnServerCurrent(s)

	checkFor(t, time.Second, 200*time.Millisecond, checkInterest)

	for i := 0; i < 4; i++ {
		nc, _ := jsClientConnect(t, s)
		defer nc.Close()
		sub, err := nc.QueueSubscribeSync("RESPONSE", "SA")
		require_NoError(t, err)
		nc.Flush()
		rsubs = append(rsubs, sub)
	}

	for i := 0; i < 100; i++ {
		require_NoError(t, nc.Publish("RESPONSE", []byte("OK")))
	}
	nc.Flush()

	// Make sure all received.
	checkFor(t, time.Second, 200*time.Millisecond, checkAllRespReceived)

	closeSubs(rsubs)
	checkFor(t, time.Second, 200*time.Millisecond, checkInterest)
}

func TestLeafNodeTwoRemotesToSameHubAccount(t *testing.T) {
	conf1 := createConfFile(t, []byte(`
		port: -1
		server_name: "hub"
		accounts {
			HA { users: [{user: ha, password: pwd}] }
		}
		leafnodes {
			port: -1
		}
	`))
	s1, o1 := RunServerWithConfig(conf1)
	defer s1.Shutdown()

	conf2 := createConfFile(t, []byte(fmt.Sprintf(`
		port: -1
		server_name: "spoke"
		accounts {
			A { users: [{user: A, password: pwd}] }
			B { users: [{user: B, password: pwd}] }
			C { users: [{user: C, password: pwd}] }
		}
		leafnodes {
			remotes [
				{
					url: "nats://ha:pwd@127.0.0.1:%d"
					local: "A"
				}
				{
					url: "nats://ha:pwd@127.0.0.1:%d"
					local: "C"
				}
			]
		}
	`, o1.LeafNode.Port, o1.LeafNode.Port)))
	s2, _ := RunServerWithConfig(conf2)
	defer s2.Shutdown()

	l := &captureErrorLogger{errCh: make(chan string, 10)}
	s2.SetLogger(l, false, false)

	checkLeafNodeConnectedCount(t, s2, 2)

	// Make sure we don't get duplicate leafnode connection errors
	deadline := time.NewTimer(1500 * time.Millisecond)
	for done := false; !done; {
		select {
		case err := <-l.errCh:
			if strings.Contains(err, DuplicateRemoteLeafnodeConnection.String()) {
				t.Fatalf("Got error: %v", err)
			}
		case <-deadline.C:
			done = true
		}
	}

	nca := natsConnect(t, s2.ClientURL(), nats.UserInfo("A", "pwd"))
	defer nca.Close()
	suba := natsSubSync(t, nca, "A")
	ncb := natsConnect(t, s2.ClientURL(), nats.UserInfo("B", "pwd"))
	defer ncb.Close()
	subb := natsSubSync(t, ncb, "B")
	ncc := natsConnect(t, s2.ClientURL(), nats.UserInfo("C", "pwd"))
	defer ncc.Close()
	subc := natsSubSync(t, ncc, "C")
	subs := map[string]*nats.Subscription{"A": suba, "B": subb, "C": subc}

	for _, subj := range []string{"A", "C"} {
		checkSubInterest(t, s1, "HA", subj, time.Second)
	}

	nc := natsConnect(t, s1.ClientURL(), nats.UserInfo("ha", "pwd"))
	defer nc.Close()

	for _, subj := range []string{"A", "B", "C"} {
		natsPub(t, nc, subj, []byte("hello"))
	}

	for _, subj := range []string{"A", "B", "C"} {
		var expected bool
		if subj != "B" {
			expected = true
		}
		sub := subs[subj]
		if expected {
			natsNexMsg(t, sub, time.Second)
		} else {
			if _, err := sub.NextMsg(50 * time.Millisecond); err != nats.ErrTimeout {
				t.Fatalf("Expected timeout error, got %v", err)
			}
		}
	}
}

func TestLeafNodeTwoRemotesToSameHubAccountWithClusters(t *testing.T) {
	hubTmpl := `
		port: -1
		server_name: "%s"
		accounts {
			HA { users: [{user: HA, password: pwd}] }
		}
		cluster {
			name: "hub"
			port: -1
			%s
		}
		leafnodes {
			port: -1
		}
	`
	confH1 := createConfFile(t, []byte(fmt.Sprintf(hubTmpl, "H1", _EMPTY_)))
	sh1, oh1 := RunServerWithConfig(confH1)
	defer sh1.Shutdown()

	confH2 := createConfFile(t, []byte(fmt.Sprintf(hubTmpl, "H2", fmt.Sprintf("routes: [\"nats://127.0.0.1:%d\"]", oh1.Cluster.Port))))
	sh2, oh2 := RunServerWithConfig(confH2)
	defer sh2.Shutdown()

	checkClusterFormed(t, sh1, sh2)

	spokeTmpl := `
		port: -1
		server_name: "%s"
		accounts {
			A { users: [{user: A, password: pwd}] }
			B { users: [{user: B, password: pwd}] }
		}
		cluster {
			name: "spoke"
			port: -1
			%s
		}
		leafnodes {
			remotes [
				{
					url: "nats://HA:pwd@127.0.0.1:%d"
					local: "A"
				}
				{
					url: "nats://HA:pwd@127.0.0.1:%d"
					local: "B"
				}
			]
		}
	`
	for _, test := range []struct {
		name        string
		sp2Leafport int
	}{
		{"connect to different hub servers", oh2.LeafNode.Port},
		{"connect to same hub server", oh1.LeafNode.Port},
	} {
		t.Run(test.name, func(t *testing.T) {
			confSP1 := createConfFile(t, []byte(fmt.Sprintf(spokeTmpl, "SP1", _EMPTY_, oh1.LeafNode.Port, oh1.LeafNode.Port)))
			sp1, osp1 := RunServerWithConfig(confSP1)
			defer sp1.Shutdown()

			confSP2 := createConfFile(t, []byte(fmt.Sprintf(spokeTmpl, "SP2",
				fmt.Sprintf("routes: [\"nats://127.0.0.1:%d\"]", osp1.Cluster.Port), test.sp2Leafport, test.sp2Leafport)))
			sp2, _ := RunServerWithConfig(confSP2)
			defer sp2.Shutdown()

			checkClusterFormed(t, sp1, sp2)
			checkLeafNodeConnectedCount(t, sp1, 2)
			checkLeafNodeConnectedCount(t, sp2, 2)

			var conns []*nats.Conn
			createConn := func(s *Server, user string) {
				t.Helper()
				nc := natsConnect(t, s.ClientURL(), nats.UserInfo(user, "pwd"))
				conns = append(conns, nc)
			}
			for _, nc := range conns {
				defer nc.Close()
			}
			createConn(sh1, "HA")
			createConn(sh2, "HA")
			createConn(sp1, "A")
			createConn(sp2, "A")
			createConn(sp1, "B")
			createConn(sp2, "B")

			check := func(subConn *nats.Conn, subj string, checkA, checkB bool) {
				t.Helper()
				sub := natsSubSync(t, subConn, subj)
				defer sub.Unsubscribe()

				checkSubInterest(t, sh1, "HA", subj, time.Second)
				checkSubInterest(t, sh2, "HA", subj, time.Second)
				if checkA {
					checkSubInterest(t, sp1, "A", subj, time.Second)
					checkSubInterest(t, sp2, "A", subj, time.Second)
				}
				if checkB {
					checkSubInterest(t, sp1, "B", subj, time.Second)
					checkSubInterest(t, sp2, "B", subj, time.Second)
				}

				for i, ncp := range conns {
					// Don't publish from account "A" connections if we are
					// dealing with account "B", and vice-versa.
					if !checkA && i >= 2 && i <= 3 {
						continue
					}
					if !checkB && i >= 4 {
						continue
					}
					natsPub(t, ncp, subj, []byte("hello"))
					natsNexMsg(t, sub, time.Second)
					// Make sure we don't get a duplicate
					if msg, err := sub.NextMsg(50 * time.Millisecond); err != nats.ErrTimeout {
						t.Fatalf("Unexpected message or error: msg=%v - err=%v", msg, err)
					}
				}
			}
			check(conns[0], "HA.1", true, true)
			check(conns[1], "HA.2", true, true)
			check(conns[2], "SPA.1", true, false)
			check(conns[3], "SPA.2", true, false)
			check(conns[4], "SPB.1", false, true)
			check(conns[5], "SPB.2", false, true)
		})
	}
}

func TestLeafNodeSameLocalAccountToMultipleHubs(t *testing.T) {
	hub1Conf := createConfFile(t, []byte(`
		port: -1
		server_name: hub1
		accounts {
			hub1 { users: [{user: hub1, password: pwd}] }
		}
		leafnodes {
			port: -1
		}
	`))
	sh1, oh1 := RunServerWithConfig(hub1Conf)
	defer sh1.Shutdown()

	hub2Conf := createConfFile(t, []byte(`
		port: -1
		server_name: hub2
		accounts {
			hub2 { users: [{user: hub2, password: pwd}] }
		}
		leafnodes {
			port: -1
		}
	`))
	sh2, oh2 := RunServerWithConfig(hub2Conf)
	defer sh2.Shutdown()

	lconf := createConfFile(t, []byte(fmt.Sprintf(`
		port: -1
		server_name: leaf
		accounts {
			A { users: [{user: A, password: pwd}] }
			B { users: [{user: B, password: pwd}] }
			C { users: [{user: C, password: pwd}] }
		}
		leafnodes {
			port: -1
			remotes [
				{
					url: nats://hub1:pwd@127.0.0.1:%[1]d
					local: "A"
				}
				{
					url: nats://hub1:pwd@127.0.0.1:%[1]d
					local: "C"
				}
				{
					url: nats://hub2:pwd@127.0.0.1:%[2]d
					local: "A"
				}
				{
					url: nats://hub2:pwd@127.0.0.1:%[2]d
					local: "B"
				}
			]
		}
	`, oh1.LeafNode.Port, oh2.LeafNode.Port)))
	s, _ := RunServerWithConfig(lconf)
	defer s.Shutdown()

	// The leafnode to hub1 should have 2 connections (A and C)
	// while the one to hub2 should have 2 connections (A and B)
	checkLeafNodeConnectedCount(t, sh1, 2)
	checkLeafNodeConnectedCount(t, sh2, 2)
	checkLeafNodeConnectedCount(t, s, 4)

	nca := natsConnect(t, s.ClientURL(), nats.UserInfo("A", "pwd"))
	defer nca.Close()
	suba := natsSubSync(t, nca, "A")
	ncb := natsConnect(t, s.ClientURL(), nats.UserInfo("B", "pwd"))
	defer ncb.Close()
	subb := natsSubSync(t, ncb, "B")
	ncc := natsConnect(t, s.ClientURL(), nats.UserInfo("C", "pwd"))
	defer ncc.Close()
	subc := natsSubSync(t, ncc, "C")

	checkSubInterest(t, sh1, "hub1", "A", time.Second)
	checkSubNoInterest(t, sh1, "hub1", "B", time.Second)
	checkSubInterest(t, sh1, "hub1", "C", time.Second)

	checkSubInterest(t, sh2, "hub2", "A", time.Second)
	checkSubInterest(t, sh2, "hub2", "B", time.Second)
	checkSubNoInterest(t, sh2, "hub2", "C", time.Second)

	nch1 := natsConnect(t, sh1.ClientURL(), nats.UserInfo("hub1", "pwd"))
	defer nch1.Close()
	nch2 := natsConnect(t, sh2.ClientURL(), nats.UserInfo("hub2", "pwd"))
	defer nch2.Close()

	checkNoMsg := func(sub *nats.Subscription) {
		t.Helper()
		if msg, err := sub.NextMsg(50 * time.Millisecond); err != nats.ErrTimeout {
			t.Fatalf("Unexpected message: %s", msg.Data)
		}
	}

	checkSub := func(sub *nats.Subscription, subj, payload string) {
		t.Helper()
		msg := natsNexMsg(t, sub, time.Second)
		require_Equal(t, subj, msg.Subject)
		require_Equal(t, payload, string(msg.Data))
		// Make sure we don't get duplicates
		checkNoMsg(sub)
	}

	natsPub(t, nch1, "A", []byte("msgA1"))
	checkSub(suba, "A", "msgA1")
	natsPub(t, nch1, "B", []byte("msgB1"))
	checkNoMsg(subb)
	natsPub(t, nch1, "C", []byte("msgC1"))
	checkSub(subc, "C", "msgC1")

	natsPub(t, nch2, "A", []byte("msgA2"))
	checkSub(suba, "A", "msgA2")
	natsPub(t, nch2, "B", []byte("msgB2"))
	checkSub(subb, "B", "msgB2")
	natsPub(t, nch2, "C", []byte("msgC2"))
	checkNoMsg(subc)
}

func TestLeafNodeSlowConsumer(t *testing.T) {
	ao := DefaultOptions()
	ao.LeafNode.Host = "127.0.0.1"
	ao.LeafNode.Port = -1
	ao.WriteDeadline = 1 * time.Millisecond
	a := RunServer(ao)
	defer a.Shutdown()

	c, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", ao.LeafNode.Port))
	if err != nil {
		t.Fatalf("Error connecting: %v", err)
	}
	time.Sleep(5 * time.Millisecond)
	a.mu.Lock()
	checkFor(t, 2*time.Second, 15*time.Millisecond, func() error {
		a.grMu.Lock()
		defer a.grMu.Unlock()
		for _, cli := range a.grTmpClients {
			cli.out.wdl = time.Nanosecond
			return nil
		}
		return nil
	})
	a.mu.Unlock()

	// Only leafnode slow consumers that made it past connect are tracked
	// in the slow consumers counter.
	if _, err := c.Write([]byte("CONNECT {}\r\n")); err != nil {
		t.Fatalf("Error writing connect: %v", err)
	}
	// Read info
	br := bufio.NewReader(c)
	br.ReadLine()
	for i := 0; i < 10; i++ {
		if _, err := c.Write([]byte("PING\r\n")); err != nil {
			t.Fatalf("Unexpected error writing PING: %v", err)
		}
	}
	defer c.Close()
	timeout := time.Now().Add(time.Second)
	var (
		got      uint64
		expected uint64 = 1
	)
	for time.Now().Before(timeout) {
		got = a.NumSlowConsumersLeafs()
		if got == expected {
			return
		}
		time.Sleep(1 * time.Millisecond)
	}
	t.Fatalf("Timed out waiting for slow consumer leafnodes, got: %v, expected: %v", got, expected)
}

// https://github.com/nats-io/nats-server/issues/4367
func TestLeafNodeDQMultiAccountExportImport(t *testing.T) {
	bConf := createConfFile(t, []byte(`
		listen: 127.0.0.1:-1
		server_name: cluster-b-0
		accounts {
			$SYS: { users: [ { user: admin, password: pwd } ] },
			AGG: {
				exports: [ { service: "PING.>" } ]
				users: [ { user: agg, password: agg } ]
			}
		}
		leaf { listen: 127.0.0.1:-1 }
	`))

	sb, ob := RunServerWithConfig(bConf)
	defer sb.Shutdown()

	tmpl := `
		listen: 127.0.0.1:-1
		server_name: %s
		jetstream: { store_dir: '%s' }
		cluster {
			name: %s
			listen: 127.0.0.1:%d
			routes = [%s]
		}
		accounts {
			$SYS: { users: [ { user: admin, password: pwd } ] },
			A: {
				mappings: { "A.>" : ">" }
				exports: [ { service: A.> } ]
				users: [ { user: a, password: a } ]
			},
			AGG: {
				imports: [ { service: { subject: A.>, account: A } } ]
				users: [ { user: agg, password: agg } ]
			},
		}
		leaf {
			remotes: [ {
				urls: [ nats-leaf://agg:agg@127.0.0.1:{LEAF_PORT} ]
				account: AGG
			} ]
		}
	`
	tmpl = strings.Replace(tmpl, "{LEAF_PORT}", fmt.Sprintf("%d", ob.LeafNode.Port), 1)
	c := createJetStreamCluster(t, tmpl, "cluster-a", "cluster-a-", 3, 22110, false)
	defer c.shutdown()

	// Make sure all servers are connected via leafnode to the hub, the b server.
	for _, s := range c.servers {
		checkLeafNodeConnectedCount(t, s, 1)
	}

	// Connect to a server in the cluster and create a DQ listener.
	nc, _ := jsClientConnect(t, c.randomServer(), nats.UserInfo("a", "a"))
	defer nc.Close()

	var got atomic.Int32

	natsQueueSub(t, nc, "PING", "Q", func(m *nats.Msg) {
		got.Add(1)
		m.Respond([]byte("REPLY"))
	})

	// Now connect to B and send the request.
	ncb, _ := jsClientConnect(t, sb, nats.UserInfo("agg", "agg"))
	defer ncb.Close()

	_, err := ncb.Request("A.PING", []byte("REQUEST"), time.Second)
	require_NoError(t, err)
	require_Equal(t, got.Load(), 1)
}
