// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package nettest

import (
	"bytes"
	"encoding/binary"
	"io"
	"io/ioutil"
	"math/rand"
	"net"
	"runtime"
	"sync"
	"testing"
	"time"
)

// MakePipe creates a connection between two endpoints and returns the pair
// as c1 and c2, such that anything written to c1 is read by c2 and vice-versa.
// The stop function closes all resources, including c1, c2, and the underlying
// net.Listener (if there is one), and should not be nil.
type MakePipe func() (c1, c2 net.Conn, stop func(), err error)

// TestConn tests that a net.Conn implementation properly satisfies the interface.
// The tests should not produce any false positives, but may experience
// false negatives. Thus, some issues may only be detected when the test is
// run multiple times. For maximal effectiveness, run the tests under the
// race detector.
func ( *testing.T,  MakePipe) {
	.Run("BasicIO", func( *testing.T) { timeoutWrapper(, , testBasicIO) })
	.Run("PingPong", func( *testing.T) { timeoutWrapper(, , testPingPong) })
	.Run("RacyRead", func( *testing.T) { timeoutWrapper(, , testRacyRead) })
	.Run("RacyWrite", func( *testing.T) { timeoutWrapper(, , testRacyWrite) })
	.Run("ReadTimeout", func( *testing.T) { timeoutWrapper(, , testReadTimeout) })
	.Run("WriteTimeout", func( *testing.T) { timeoutWrapper(, , testWriteTimeout) })
	.Run("PastTimeout", func( *testing.T) { timeoutWrapper(, , testPastTimeout) })
	.Run("PresentTimeout", func( *testing.T) { timeoutWrapper(, , testPresentTimeout) })
	.Run("FutureTimeout", func( *testing.T) { timeoutWrapper(, , testFutureTimeout) })
	.Run("CloseTimeout", func( *testing.T) { timeoutWrapper(, , testCloseTimeout) })
	.Run("ConcurrentMethods", func( *testing.T) { timeoutWrapper(, , testConcurrentMethods) })
}

type connTester func(t *testing.T, c1, c2 net.Conn)

func timeoutWrapper( *testing.T,  MakePipe,  connTester) {
	.Helper()
	, , ,  := ()
	if  != nil {
		.Fatalf("unable to make pipe: %v", )
	}
	var  sync.Once
	defer .Do(func() { () })
	 := time.AfterFunc(time.Minute, func() {
		.Do(func() {
			.Error("test timed out; terminating pipe")
			()
		})
	})
	defer .Stop()
	(, , )
}

// testBasicIO tests that the data sent on c1 is properly received on c2.
func testBasicIO( *testing.T, ,  net.Conn) {
	 := make([]byte, 1<<20)
	rand.New(rand.NewSource(0)).Read()

	 := make(chan []byte)
	go func() {
		 := bytes.NewReader()
		if  := chunkedCopy(, );  != nil {
			.Errorf("unexpected c1.Write error: %v", )
		}
		if  := .Close();  != nil {
			.Errorf("unexpected c1.Close error: %v", )
		}
	}()

	go func() {
		 := new(bytes.Buffer)
		if  := chunkedCopy(, );  != nil {
			.Errorf("unexpected c2.Read error: %v", )
		}
		if  := .Close();  != nil {
			.Errorf("unexpected c2.Close error: %v", )
		}
		 <- .Bytes()
	}()

	if  := <-; !bytes.Equal(, ) {
		.Error("transmitted data differs")
	}
}

// testPingPong tests that the two endpoints can synchronously send data to
// each other in a typical request-response pattern.
func testPingPong( *testing.T, ,  net.Conn) {
	var  sync.WaitGroup
	defer .Wait()

	 := func( net.Conn) {
		defer .Done()
		 := make([]byte, 8)
		var  uint64
		for {
			if ,  := io.ReadFull(, );  != nil {
				if  == io.EOF {
					break
				}
				.Errorf("unexpected Read error: %v", )
			}

			 := binary.LittleEndian.Uint64()
			binary.LittleEndian.PutUint64(, +1)
			if  != 0 && +2 !=  {
				.Errorf("mismatching value: got %d, want %d", , +2)
			}
			 = 
			if  == 1000 {
				break
			}

			if ,  := .Write();  != nil {
				.Errorf("unexpected Write error: %v", )
				break
			}
		}
		if  := .Close();  != nil {
			.Errorf("unexpected Close error: %v", )
		}
	}

	.Add(2)
	go ()
	go ()

	// Start off the chain reaction.
	if ,  := .Write(make([]byte, 8));  != nil {
		.Errorf("unexpected c1.Write error: %v", )
	}
}

// testRacyRead tests that it is safe to mutate the input Read buffer
// immediately after cancelation has occurred.
func testRacyRead( *testing.T, ,  net.Conn) {
	go chunkedCopy(, rand.New(rand.NewSource(0)))

	var  sync.WaitGroup
	defer .Wait()

	.SetReadDeadline(time.Now().Add(time.Millisecond))
	for  := 0;  < 10; ++ {
		.Add(1)
		go func() {
			defer .Done()

			 := make([]byte, 1024)
			 := make([]byte, 1024)
			for  := 0;  < 100; ++ {
				,  := .Read()
				copy(, ) // Mutate b1 to trigger potential race
				if  != nil {
					checkForTimeoutError(, )
					.SetReadDeadline(time.Now().Add(time.Millisecond))
				}
			}
		}()
	}
}

// testRacyWrite tests that it is safe to mutate the input Write buffer
// immediately after cancelation has occurred.
func testRacyWrite( *testing.T, ,  net.Conn) {
	go chunkedCopy(ioutil.Discard, )

	var  sync.WaitGroup
	defer .Wait()

	.SetWriteDeadline(time.Now().Add(time.Millisecond))
	for  := 0;  < 10; ++ {
		.Add(1)
		go func() {
			defer .Done()

			 := make([]byte, 1024)
			 := make([]byte, 1024)
			for  := 0;  < 100; ++ {
				,  := .Write()
				copy(, ) // Mutate b1 to trigger potential race
				if  != nil {
					checkForTimeoutError(, )
					.SetWriteDeadline(time.Now().Add(time.Millisecond))
				}
			}
		}()
	}
}

// testReadTimeout tests that Read timeouts do not affect Write.
func testReadTimeout( *testing.T, ,  net.Conn) {
	go chunkedCopy(ioutil.Discard, )

	.SetReadDeadline(aLongTimeAgo)
	,  := .Read(make([]byte, 1024))
	checkForTimeoutError(, )
	if ,  := .Write(make([]byte, 1024));  != nil {
		.Errorf("unexpected Write error: %v", )
	}
}

// testWriteTimeout tests that Write timeouts do not affect Read.
func testWriteTimeout( *testing.T, ,  net.Conn) {
	go chunkedCopy(, rand.New(rand.NewSource(0)))

	.SetWriteDeadline(aLongTimeAgo)
	,  := .Write(make([]byte, 1024))
	checkForTimeoutError(, )
	if ,  := .Read(make([]byte, 1024));  != nil {
		.Errorf("unexpected Read error: %v", )
	}
}

// testPastTimeout tests that a deadline set in the past immediately times out
// Read and Write requests.
func testPastTimeout( *testing.T, ,  net.Conn) {
	go chunkedCopy(, )

	testRoundtrip(, )

	.SetDeadline(aLongTimeAgo)
	,  := .Write(make([]byte, 1024))
	if  != 0 {
		.Errorf("unexpected Write count: got %d, want 0", )
	}
	checkForTimeoutError(, )
	,  = .Read(make([]byte, 1024))
	if  != 0 {
		.Errorf("unexpected Read count: got %d, want 0", )
	}
	checkForTimeoutError(, )

	testRoundtrip(, )
}

// testPresentTimeout tests that a past deadline set while there are pending
// Read and Write operations immediately times out those operations.
func testPresentTimeout( *testing.T, ,  net.Conn) {
	var  sync.WaitGroup
	defer .Wait()
	.Add(3)

	 := make(chan bool, 1)
	go func() {
		defer .Done()
		time.Sleep(100 * time.Millisecond)
		 <- true
		.SetReadDeadline(aLongTimeAgo)
		.SetWriteDeadline(aLongTimeAgo)
	}()
	go func() {
		defer .Done()
		,  := .Read(make([]byte, 1024))
		if  != 0 {
			.Errorf("unexpected Read count: got %d, want 0", )
		}
		checkForTimeoutError(, )
		if len() == 0 {
			.Error("Read timed out before deadline is set")
		}
	}()
	go func() {
		defer .Done()
		var  error
		for  == nil {
			_,  = .Write(make([]byte, 1024))
		}
		checkForTimeoutError(, )
		if len() == 0 {
			.Error("Write timed out before deadline is set")
		}
	}()
}

// testFutureTimeout tests that a future deadline will eventually time out
// Read and Write operations.
func testFutureTimeout( *testing.T, ,  net.Conn) {
	var  sync.WaitGroup
	.Add(2)

	.SetDeadline(time.Now().Add(100 * time.Millisecond))
	go func() {
		defer .Done()
		,  := .Read(make([]byte, 1024))
		checkForTimeoutError(, )
	}()
	go func() {
		defer .Done()
		var  error
		for  == nil {
			_,  = .Write(make([]byte, 1024))
		}
		checkForTimeoutError(, )
	}()
	.Wait()

	go chunkedCopy(, )
	resyncConn(, )
	testRoundtrip(, )
}

// testCloseTimeout tests that calling Close immediately times out pending
// Read and Write operations.
func testCloseTimeout( *testing.T, ,  net.Conn) {
	go chunkedCopy(, )

	var  sync.WaitGroup
	defer .Wait()
	.Add(3)

	// Test for cancelation upon connection closure.
	.SetDeadline(neverTimeout)
	go func() {
		defer .Done()
		time.Sleep(100 * time.Millisecond)
		.Close()
	}()
	go func() {
		defer .Done()
		var  error
		 := make([]byte, 1024)
		for  == nil {
			_,  = .Read()
		}
	}()
	go func() {
		defer .Done()
		var  error
		 := make([]byte, 1024)
		for  == nil {
			_,  = .Write()
		}
	}()
}

// testConcurrentMethods tests that the methods of net.Conn can safely
// be called concurrently.
func testConcurrentMethods( *testing.T, ,  net.Conn) {
	if runtime.GOOS == "plan9" {
		.Skip("skipping on plan9; see https://golang.org/issue/20489")
	}
	go chunkedCopy(, )

	// The results of the calls may be nonsensical, but this should
	// not trigger a race detector warning.
	var  sync.WaitGroup
	for  := 0;  < 100; ++ {
		.Add(7)
		go func() {
			defer .Done()
			.Read(make([]byte, 1024))
		}()
		go func() {
			defer .Done()
			.Write(make([]byte, 1024))
		}()
		go func() {
			defer .Done()
			.SetDeadline(time.Now().Add(10 * time.Millisecond))
		}()
		go func() {
			defer .Done()
			.SetReadDeadline(aLongTimeAgo)
		}()
		go func() {
			defer .Done()
			.SetWriteDeadline(aLongTimeAgo)
		}()
		go func() {
			defer .Done()
			.LocalAddr()
		}()
		go func() {
			defer .Done()
			.RemoteAddr()
		}()
	}
	.Wait() // At worst, the deadline is set 10ms into the future

	resyncConn(, )
	testRoundtrip(, )
}

// checkForTimeoutError checks that the error satisfies the Error interface
// and that Timeout returns true.
func checkForTimeoutError( *testing.T,  error) {
	.Helper()
	if ,  := .(net.Error);  {
		if !.Timeout() {
			.Errorf("err.Timeout() = false, want true")
		}
	} else {
		.Errorf("got %T, want net.Error", )
	}
}

// testRoundtrip writes something into c and reads it back.
// It assumes that everything written into c is echoed back to itself.
func testRoundtrip( *testing.T,  net.Conn) {
	.Helper()
	if  := .SetDeadline(neverTimeout);  != nil {
		.Errorf("roundtrip SetDeadline error: %v", )
	}

	const  = "Hello, world!"
	 := []byte()
	if ,  := .Write();  != nil {
		.Errorf("roundtrip Write error: %v", )
	}
	if ,  := io.ReadFull(, );  != nil {
		.Errorf("roundtrip Read error: %v", )
	}
	if string() !=  {
		.Errorf("roundtrip data mismatch: got %q, want %q", , )
	}
}

// resyncConn resynchronizes the connection into a sane state.
// It assumes that everything written into c is echoed back to itself.
// It assumes that 0xff is not currently on the wire or in the read buffer.
func resyncConn( *testing.T,  net.Conn) {
	.Helper()
	.SetDeadline(neverTimeout)
	 := make(chan error)
	go func() {
		,  := .Write([]byte{0xff})
		 <- 
	}()
	 := make([]byte, 1024)
	for {
		,  := .Read()
		if  > 0 && bytes.IndexByte([:], 0xff) == -1 {
			break
		}
		if  != nil {
			.Errorf("unexpected Read error: %v", )
			break
		}
	}
	if  := <-;  != nil {
		.Errorf("unexpected Write error: %v", )
	}
}

// chunkedCopy copies from r to w in fixed-width chunks to avoid
// causing a Write that exceeds the maximum packet size for packet-based
// connections like "unixpacket".
// We assume that the maximum packet size is at least 1024.
func chunkedCopy( io.Writer,  io.Reader) error {
	 := make([]byte, 1024)
	,  := io.CopyBuffer(struct{ io.Writer }{}, struct{ io.Reader }{}, )
	return 
}