func TestConnectRejectsVersions(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	g := newTestingGateway(t)
	defer g.Close()
	listener, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		t.Fatal(err)
	}
	defer listener.Close()
	tests := []struct {
		version             string
		errWant             string
		localErrWant        string
		invalidVersion      bool
		insufficientVersion bool
		msg                 string
		versionRequired string
		genesisID types.BlockID
		uniqueID  gatewayID
	}{
		{
			version: "reject",
			errWant: errPeerRejectedConn.Error(),
			msg:     "Connect should fail when the remote peer rejects the connection",
		},
		{
			version:        "foobar",
			invalidVersion: true,
			msg:            "Connect should fail when the remote peer's version is ascii gibberish",
		},
		{
			version:        "世界",
			invalidVersion: true,
			msg:            "Connect should fail when the remote peer's version is utf8 gibberish",
		},
		{
			version:             "0",
			insufficientVersion: true,
			msg:                 "Connect should fail when the remote peer's version is 0",
		},
		{
			version:             "0.0.0",
			insufficientVersion: true,
			msg:                 "Connect should fail when the remote peer's version is 0.0.0",
		},
		{
			version:             "0000.0000.0000",
			insufficientVersion: true,
			msg:                 "Connect should fail when the remote peer's version is 0000.0000.0000",
		},
		{
			version:             "0.3.9",
			insufficientVersion: true,
			msg:                 "Connect should fail when the remote peer's version is 0.3.9",
		},
		{
			version:             "0.3.9999",
			insufficientVersion: true,
			msg:                 "Connect should fail when the remote peer's version is 0.3.9999",
		},
		{
			version:             "0.3.9.9.9",
			insufficientVersion: true,
			msg:                 "Connect should fail when the remote peer's version is 0.3.9.9.9",
		},
		{
			version: "0.4.0",
			msg:     "Connect should succeed when the remote peer's version is 0.4.0",
		},
		{
			version: "0.9.0",
			msg:     "Connect should succeed when the remote peer's version is 0.9.0",
		},
		{
			version:         minimumAcceptablePeerVersion,
			msg:             "Connect should succeed when the remote peer's version is 1.3.0 and sessionHeader checks out",
			uniqueID:        func() (id gatewayID) { fastrand.Read(id[:]); return }(),
			genesisID:       types.GenesisID,
			versionRequired: minimumAcceptablePeerVersion,
		},
		{
			version:         minimumAcceptablePeerVersion,
			msg:             "Connect should not succeed when peer is connecting to itself",
			uniqueID:        g.staticId,
			genesisID:       types.GenesisID,
			errWant:         errOurAddress.Error(),
			localErrWant:    errOurAddress.Error(),
			versionRequired: minimumAcceptablePeerVersion,
		},
	}
	for testIndex, tt := range tests {
		if tt.versionRequired != "" && build.VersionCmp(build.Version, tt.versionRequired) < 0 {
			continue  
		}
		doneChan := make(chan struct{})
		go func() {
			defer close(doneChan)
			conn, err := listener.Accept()
			if err != nil {
				panic(fmt.Sprintf("test #%d failed: %s", testIndex, err))
			}
			remoteVersion, err := acceptVersionHandshake(conn, tt.version)
			if err != nil {
				panic(fmt.Sprintf("test #%d failed: %s", testIndex, err))
			}
			if remoteVersion != build.Version {
				panic(fmt.Sprintf("test #%d failed: remoteVersion != build.Version", testIndex))
			}
			if build.VersionCmp(tt.version, minimumAcceptablePeerVersion) >= 0 {
				ourHeader := sessionHeader{
					GenesisID:  tt.genesisID,
					UniqueID:   tt.uniqueID,
					NetAddress: modules.NetAddress(conn.LocalAddr().String()),
				}
				_, err = exchangeRemoteHeader(conn, ourHeader)
				exchangeOurHeader(conn, ourHeader)
			} else if build.VersionCmp(tt.version, handshakeUpgradeVersion) >= 0 {
				var dialbackPort string
				err = encoding.ReadObject(conn, &dialbackPort, 13)
			} else {
			}
			if (err == nil && tt.localErrWant != "") || (err != nil && !strings.Contains(err.Error(), tt.localErrWant)) {
				panic(fmt.Sprintf("test #%d failed: %v != %v", testIndex, tt.localErrWant, err))
			}
		}()
		err = g.Connect(modules.NetAddress(listener.Addr().String()))
		switch {
		case tt.invalidVersion:
			if _, ok := err.(invalidVersionError); !ok {
				t.Fatalf("expected Connect to error with invalidVersionError: %s", tt.msg)
			}
		case tt.insufficientVersion:
			if _, ok := err.(insufficientVersionError); !ok {
				t.Fatalf("expected Connect to error with insufficientVersionError: %s", tt.msg)
			}
		default:
			if (err == nil && tt.errWant != "") || (err != nil && !strings.Contains(err.Error(), tt.errWant)) {
				t.Fatalf("expected Connect to error with '%v', but got '%v': %s", tt.errWant, err, tt.msg)
			}
		}
		<-doneChan
		g.Disconnect(modules.NetAddress(listener.Addr().String()))
	}
}
TestConnectRejectsVersions tests that Gateway.Connect only accepts peers
with sufficient and valid versions.
package gateway
import (
	"errors"
	"fmt"
	"net"
	"strconv"
	"strings"
	"testing"
	"time"
	"github.com/NebulousLabs/Sia/build"
	"github.com/NebulousLabs/Sia/encoding"
	"github.com/NebulousLabs/Sia/modules"
	"github.com/NebulousLabs/Sia/types"
	"github.com/NebulousLabs/fastrand"
)
type dummyConn struct {
	net.Conn
}
func (dc *dummyConn) Read(p []byte) (int, error)       { return len(p), nil }
func (dc *dummyConn) Write(p []byte) (int, error)      { return len(p), nil }
func (dc *dummyConn) Close() error                     { return nil }
func (dc *dummyConn) SetReadDeadline(time.Time) error  { return nil }
func (dc *dummyConn) SetWriteDeadline(time.Time) error { return nil }
func TestAddPeer(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	t.Parallel()
	g := newTestingGateway(t)
	defer g.Close()
	g.mu.Lock()
	defer g.mu.Unlock()
	g.addPeer(&peer{
		Peer: modules.Peer{
			NetAddress: "foo.com:123",
		},
		sess: newClientStream(new(dummyConn), build.Version),
	})
	if len(g.peers) != 1 {
		t.Fatal("gateway did not add peer")
	}
}
func TestAcceptPeer(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	t.Parallel()
	g := newTestingGateway(t)
	defer g.Close()
	g.mu.Lock()
	defer g.mu.Unlock()
	var unkickablePeers []*peer
	for i := 0; i < fullyConnectedThreshold+1; i++ {
		addr := modules.NetAddress(fmt.Sprintf("1.2.3.%d", i))
		p := &peer{
			Peer: modules.Peer{
				NetAddress: addr,
				Inbound:    false,
				Local:      false,
			},
			sess: newClientStream(new(dummyConn), build.Version),
		}
		unkickablePeers = append(unkickablePeers, p)
	}
	for i := 0; i < fullyConnectedThreshold+1; i++ {
		addr := modules.NetAddress(fmt.Sprintf("127.0.0.1:%d", i))
		p := &peer{
			Peer: modules.Peer{
				NetAddress: addr,
				Inbound:    true,
				Local:      true,
			},
			sess: newClientStream(new(dummyConn), build.Version),
		}
		unkickablePeers = append(unkickablePeers, p)
	}
	for _, p := range unkickablePeers {
		g.addPeer(p)
	}
	g.acceptPeer(&peer{
		Peer: modules.Peer{
			NetAddress: "9.9.9.9",
			Inbound:    true,
		},
		sess: newClientStream(new(dummyConn), build.Version),
	})
	for _, p := range unkickablePeers {
		if _, exists := g.peers[p.NetAddress]; !exists {
			t.Error("accept peer kicked an outbound or local peer")
		}
	}
	g.addPeer(&peer{
		Peer: modules.Peer{
			NetAddress: "9.9.9.9",
			Inbound:    true,
		},
		sess: newClientStream(new(dummyConn), build.Version),
	})
	g.acceptPeer(&peer{
		Peer: modules.Peer{
			NetAddress: "127.0.0.1:99",
			Inbound:    true,
			Local:      true,
		},
		sess: newClientStream(new(dummyConn), build.Version),
	})
	if _, exists := g.peers["9.9.9.9"]; exists {
		t.Error("acceptPeer didn't kick a peer to make room for a local peer")
	}
}
func TestRandomOutboundPeer(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	t.Parallel()
	g := newTestingGateway(t)
	defer g.Close()
	g.mu.Lock()
	defer g.mu.Unlock()
	_, err := g.randomOutboundPeer()
	if err != errNoPeers {
		t.Fatal("expected errNoPeers, got", err)
	}
	g.addPeer(&peer{
		Peer: modules.Peer{
			NetAddress: "foo.com:123",
			Inbound:    false,
		},
		sess: newClientStream(new(dummyConn), build.Version),
	})
	if len(g.peers) != 1 {
		t.Fatal("gateway did not add peer")
	}
	addr, err := g.randomOutboundPeer()
	if err != nil || addr != "foo.com:123" {
		t.Fatal("gateway did not select random peer")
	}
}
func TestListen(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	t.Parallel()
	g := newTestingGateway(t)
	defer g.Close()
	conn, err := net.Dial("tcp", string(g.Address()))
	if err != nil {
		t.Fatal("dial failed:", err)
	}
	addr := modules.NetAddress(conn.LocalAddr().String())
	ack, err := connectVersionHandshake(conn, "0.1")
	if err != errPeerRejectedConn {
		t.Fatal(err)
	}
	if ack != "" {
		t.Fatal("gateway should have rejected old version")
	}
	for i := 0; i < 10; i++ {
		g.mu.RLock()
		_, ok := g.peers[addr]
		g.mu.RUnlock()
		if ok {
			t.Fatal("gateway should not have added an old peer")
		}
		time.Sleep(20 * time.Millisecond)
	}
	newClientStream(conn, build.Version).Close()
	conn, err = net.Dial("tcp", string(g.Address()))
	if err != nil {
		t.Fatal("dial failed:", err)
	}
	addr = modules.NetAddress(conn.LocalAddr().String())
	ack, err = connectVersionHandshake(conn, build.Version)
	if err != nil {
		t.Fatal(err)
	}
	if ack != build.Version {
		t.Fatal("gateway should have given ack")
	}
	header := sessionHeader{
		GenesisID:  types.GenesisID,
		UniqueID:   gatewayID{},
		NetAddress: "fake",
	}
	err = exchangeOurHeader(conn, header)
	if err == nil {
		t.Fatal("expected error, got nil")
	}
	conn.Close()
	conn, err = net.Dial("tcp", string(g.Address()))
	if err != nil {
		t.Fatal("dial failed:", err)
	}
	addr = modules.NetAddress(conn.LocalAddr().String())
	ack, err = connectVersionHandshake(conn, build.Version)
	if err != nil {
		t.Fatal(err)
	}
	if ack != build.Version {
		t.Fatal("gateway should have given ack")
	}
	header.NetAddress = modules.NetAddress(conn.LocalAddr().String())
	err = exchangeOurHeader(conn, header)
	if err != nil {
		t.Fatal(err)
	}
	_, err = exchangeRemoteHeader(conn, header)
	if err != nil {
		t.Fatal(err)
	}
	err = build.Retry(50, 100*time.Millisecond, func() error {
		g.mu.RLock()
		_, ok := g.peers[addr]
		g.mu.RUnlock()
		if !ok {
			return errors.New("g should have added the peer")
		}
		return nil
	})
	if err != nil {
		t.Fatal(err)
	}
	newClientStream(conn, build.Version).Close()
	err = build.Retry(50, 100*time.Millisecond, func() error {
		g.mu.RLock()
		_, ok := g.peers[addr]
		g.mu.RUnlock()
		if ok {
			return errors.New("g should have removed the peer")
		}
		return nil
	})
	if err != nil {
		t.Fatal(err)
	}
	conn, err = net.Dial("tcp", string(g.Address()))
	if err != nil {
		t.Fatal("dial failed:", err)
	}
	if _, err := conn.Write([]byte("missing length prefix")); err != nil {
		t.Fatal("couldn't write malformed header")
	}
	if n, err := conn.Write([]byte("closed")); err != nil && n > 0 {
		t.Error("write succeeded after closed connection")
	}
}
func TestConnect(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	t.Parallel()
	bootstrap := newNamedTestingGateway(t, "1")
	defer bootstrap.Close()
	bootstrap.mu.Lock()
	bootstrap.addNode(dummyNode)
	bootstrap.mu.Unlock()
	g := newNamedTestingGateway(t, "2")
	defer g.Close()
	bootstrap.mu.Lock()
	bootstrap.handlers[handlerName("ShareNodes")] = func(modules.PeerConn) error {
		return nil
	}
	bootstrap.mu.Unlock()
	err := g.Connect(bootstrap.Address())
	if err != nil {
		t.Fatal(err)
	}
	if g.removeNode(dummyNode) == nil {
		t.Fatal("bootstrapper should not have received dummyNode:", g.nodes)
	}
	g.Disconnect(bootstrap.Address())
	bootstrap.Disconnect(g.Address())
	bootstrap.mu.Lock()
	bootstrap.handlers[handlerName("ShareNodes")] = bootstrap.shareNodes
	bootstrap.mu.Unlock()
	err = g.Connect(bootstrap.Address())
	if err != nil {
		t.Fatal(err)
	}
	time.Sleep(200 * time.Millisecond)
	g.mu.RLock()
	if _, ok := g.nodes[dummyNode]; !ok {
		g.mu.RUnlock()  
		t.Fatal("bootstrapper should have received dummyNode:", g.nodes)
	}
	g.mu.RUnlock()
}
func TestUnitAcceptableVersion(t *testing.T) {
	invalidVersions := []string{
		"foobar",
		"foobar.0",
		"foobar.9",
		"0.foobar",
		"9.foobar",
		"foobar.0.0",
		"foobar.9.9",
		"0.foobar.0",
		"9.foobar.9",
		"0.0.foobar",
		"9.9.foobar",
		"世界",
		"世界.0",
		"世界.9",
		"0.世界",
		"9.世界",
		"世界.0.0",
		"世界.9.9",
		"0.世界.0",
		"9.世界.9",
		"0.0.世界",
		"9.9.世界",
		".",
		"..",
		"...",
		"0.",
		".1",
		"2..",
		".3.",
		"..4",
		"5.6.",
		".7.8",
		".9.0.",
	}
	for _, v := range invalidVersions {
		err := acceptableVersion(v)
		if _, ok := err.(invalidVersionError); err == nil || !ok {
			t.Errorf("acceptableVersion returned %q for version %q, but expected invalidVersionError", err, v)
		}
	}
	insufficientVersions := []string{
		"0",
		"00",
		"0000000000",
		"0.0",
		"0000000000.0",
		"0.0000000000",
		"0.0.0.0.0.0.0.0",
		"0.0.9",
		"0.0.999",
		"0.0.99999999999",
		"0.1.2",
		"0.1.2.3.4.5.6.7.8.9",
		"0.3.3",
		"0.3.9.9.9.9.9.9.9.9.9.9",
		"0.3.9999999999",
		"1.3.0",
	}
	for _, v := range insufficientVersions {
		err := acceptableVersion(v)
		if _, ok := err.(insufficientVersionError); err == nil || !ok {
			t.Errorf("acceptableVersion returned %q for version %q, but expected insufficientVersionError", err, v)
		}
	}
	validVersions := []string{
		minimumAcceptablePeerVersion,
		"1.4.0",
		"1.3.1",
		"1.6.0",
		"1.6.1",
		"1.9",
		"1.999",
		"1.9999999999",
		"2",
		"2.0",
		"2.0.0",
		"9",
		"9.0",
		"9.0.0",
		"9.9.9",
	}
	for _, v := range validVersions {
		err := acceptableVersion(v)
		if err != nil {
			t.Errorf("acceptableVersion returned %q for version %q, but expected nil", err, v)
		}
	}
}
func TestConnectRejectsInvalidAddrs(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	t.Parallel()
	g := newNamedTestingGateway(t, "1")
	defer g.Close()
	g2 := newNamedTestingGateway(t, "2")
	defer g2.Close()
	_, g2Port, err := net.SplitHostPort(string(g2.Address()))
	if err != nil {
		t.Fatal(err)
	}
	tests := []struct {
		addr    modules.NetAddress
		wantErr bool
		msg     string
	}{
		{
			addr:    "127.0.0.1:123",
			wantErr: true,
			msg:     "Connect should reject unreachable addresses",
		},
		{
			addr:    "111.111.111.111:0",
			wantErr: true,
			msg:     "Connect should reject invalid NetAddresses",
		},
		{
			addr:    modules.NetAddress(net.JoinHostPort("localhost", g2Port)),
			wantErr: true,
			msg:     "Connect should reject non-IP addresses",
		},
		{
			addr: g2.Address(),
			msg:  "Connect failed to connect to another gateway",
		},
		{
			addr:    g2.Address(),
			wantErr: true,
			msg:     "Connect should reject an address it's already connected to",
		},
	}
	for _, tt := range tests {
		err := g.Connect(tt.addr)
		if tt.wantErr != (err != nil) {
			t.Errorf("%v, wantErr: %v, err: %v", tt.msg, tt.wantErr, err)
		}
	}
}