// Copyright 2019 The gVisor 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 flipcall

import (
	"runtime"
	"testing"
	"time"

	"gvisor.dev/gvisor/pkg/sync"
)

var testPacketWindowSize = pageSize

type testConnection struct {
	pwa      PacketWindowAllocator
	clientEP Endpoint
	serverEP Endpoint
}

func newTestConnectionWithOptions(tb testing.TB, clientOpts, serverOpts []EndpointOption) *testConnection {
	c := &testConnection{}
	if err := c.pwa.Init(); err != nil {
		tb.Fatalf("failed to create PacketWindowAllocator: %v", err)
	}
	pwd, err := c.pwa.Allocate(testPacketWindowSize)
	if err != nil {
		c.pwa.Destroy()
		tb.Fatalf("PacketWindowAllocator.Allocate() failed: %v", err)
	}
	if err := c.clientEP.Init(ClientSide, pwd, clientOpts...); err != nil {
		c.pwa.Destroy()
		tb.Fatalf("failed to create client Endpoint: %v", err)
	}
	if err := c.serverEP.Init(ServerSide, pwd, serverOpts...); err != nil {
		c.pwa.Destroy()
		c.clientEP.Destroy()
		tb.Fatalf("failed to create server Endpoint: %v", err)
	}
	return c
}

func newTestConnection(tb testing.TB) *testConnection {
	return newTestConnectionWithOptions(tb, nil, nil)
}

func (c *testConnection) destroy() {
	c.pwa.Destroy()
	c.clientEP.Destroy()
	c.serverEP.Destroy()
}

func testSendRecv(t *testing.T, c *testConnection) {
	// This shared variable is used to confirm that synchronization between
	// flipcall endpoints is visible to the Go race detector.
	state := 0
	var serverRun sync.WaitGroup
	serverRun.Add(1)
	go func() {
		defer serverRun.Done()
		t.Logf("server Endpoint waiting for packet 1")
		if _, err := c.serverEP.RecvFirst(); err != nil {
			t.Errorf("server Endpoint.RecvFirst() failed: %v", err)
			return
		}
		state++
		if state != 2 {
			t.Errorf("shared state counter: got %d, wanted 2", state)
		}
		t.Logf("server Endpoint got packet 1, sending packet 2 and waiting for packet 3")
		if _, err := c.serverEP.SendRecv(0); err != nil {
			t.Errorf("server Endpoint.SendRecv() failed: %v", err)
			return
		}
		state++
		if state != 4 {
			t.Errorf("shared state counter: got %d, wanted 4", state)
		}
		t.Logf("server Endpoint got packet 3")
	}()
	defer func() {
		// Ensure that the server goroutine is cleaned up before
		// c.serverEP.Destroy(), even if the test fails.
		c.serverEP.Shutdown()
		serverRun.Wait()
	}()

	t.Logf("client Endpoint establishing connection")
	if err := c.clientEP.Connect(); err != nil {
		t.Fatalf("client Endpoint.Connect() failed: %v", err)
	}
	state++
	if state != 1 {
		t.Errorf("shared state counter: got %d, wanted 1", state)
	}
	t.Logf("client Endpoint sending packet 1 and waiting for packet 2")
	if _, err := c.clientEP.SendRecv(0); err != nil {
		t.Fatalf("client Endpoint.SendRecv() failed: %v", err)
	}
	state++
	if state != 3 {
		t.Errorf("shared state counter: got %d, wanted 3", state)
	}
	t.Logf("client Endpoint got packet 2, sending packet 3")
	if err := c.clientEP.SendLast(0); err != nil {
		t.Fatalf("client Endpoint.SendLast() failed: %v", err)
	}
	t.Logf("waiting for server goroutine to complete")
	serverRun.Wait()
}

func TestSendRecv(t *testing.T) {
	c := newTestConnection(t)
	defer c.destroy()
	testSendRecv(t, c)
}

func testShutdownBeforeConnect(t *testing.T, c *testConnection, remoteShutdown bool) {
	if remoteShutdown {
		c.serverEP.Shutdown()
	} else {
		c.clientEP.Shutdown()
	}
	if err := c.clientEP.Connect(); err == nil {
		t.Errorf("client Endpoint.Connect() succeeded unexpectedly")
	}
}

func TestShutdownBeforeConnectLocal(t *testing.T) {
	c := newTestConnection(t)
	defer c.destroy()
	testShutdownBeforeConnect(t, c, false)
}

func TestShutdownBeforeConnectRemote(t *testing.T) {
	c := newTestConnection(t)
	defer c.destroy()
	testShutdownBeforeConnect(t, c, true)
}

func testShutdownDuringConnect(t *testing.T, c *testConnection, remoteShutdown bool) {
	var clientRun sync.WaitGroup
	clientRun.Add(1)
	go func() {
		defer clientRun.Done()
		if err := c.clientEP.Connect(); err == nil {
			t.Errorf("client Endpoint.Connect() succeeded unexpectedly")
		}
	}()
	time.Sleep(time.Second) // to allow c.clientEP.Connect() to block
	if remoteShutdown {
		c.serverEP.Shutdown()
	} else {
		c.clientEP.Shutdown()
	}
	clientRun.Wait()
}

func TestShutdownDuringConnectLocal(t *testing.T) {
	c := newTestConnection(t)
	defer c.destroy()
	testShutdownDuringConnect(t, c, false)
}

func TestShutdownDuringConnectRemote(t *testing.T) {
	c := newTestConnection(t)
	defer c.destroy()
	testShutdownDuringConnect(t, c, true)
}

func testShutdownBeforeRecvFirst(t *testing.T, c *testConnection, remoteShutdown bool) {
	if remoteShutdown {
		c.clientEP.Shutdown()
	} else {
		c.serverEP.Shutdown()
	}
	if _, err := c.serverEP.RecvFirst(); err == nil {
		t.Errorf("server Endpoint.RecvFirst() succeeded unexpectedly")
	}
}

func TestShutdownBeforeRecvFirstLocal(t *testing.T) {
	c := newTestConnection(t)
	defer c.destroy()
	testShutdownBeforeRecvFirst(t, c, false)
}

func TestShutdownBeforeRecvFirstRemote(t *testing.T) {
	c := newTestConnection(t)
	defer c.destroy()
	testShutdownBeforeRecvFirst(t, c, true)
}

func testShutdownDuringRecvFirstBeforeConnect(t *testing.T, c *testConnection, remoteShutdown bool) {
	var serverRun sync.WaitGroup
	serverRun.Add(1)
	go func() {
		defer serverRun.Done()
		if _, err := c.serverEP.RecvFirst(); err == nil {
			t.Errorf("server Endpoint.RecvFirst() succeeded unexpectedly")
		}
	}()
	time.Sleep(time.Second) // to allow c.serverEP.RecvFirst() to block
	if remoteShutdown {
		c.clientEP.Shutdown()
	} else {
		c.serverEP.Shutdown()
	}
	serverRun.Wait()
}

func TestShutdownDuringRecvFirstBeforeConnectLocal(t *testing.T) {
	c := newTestConnection(t)
	defer c.destroy()
	testShutdownDuringRecvFirstBeforeConnect(t, c, false)
}

func TestShutdownDuringRecvFirstBeforeConnectRemote(t *testing.T) {
	c := newTestConnection(t)
	defer c.destroy()
	testShutdownDuringRecvFirstBeforeConnect(t, c, true)
}

func testShutdownDuringRecvFirstAfterConnect(t *testing.T, c *testConnection, remoteShutdown bool) {
	var serverRun sync.WaitGroup
	serverRun.Add(1)
	go func() {
		defer serverRun.Done()
		if _, err := c.serverEP.RecvFirst(); err == nil {
			t.Errorf("server Endpoint.RecvFirst() succeeded unexpectedly")
		}
	}()
	defer func() {
		// Ensure that the server goroutine is cleaned up before
		// c.serverEP.Destroy(), even if the test fails.
		c.serverEP.Shutdown()
		serverRun.Wait()
	}()
	if err := c.clientEP.Connect(); err != nil {
		t.Fatalf("client Endpoint.Connect() failed: %v", err)
	}
	if remoteShutdown {
		c.clientEP.Shutdown()
	} else {
		c.serverEP.Shutdown()
	}
	serverRun.Wait()
}

func TestShutdownDuringRecvFirstAfterConnectLocal(t *testing.T) {
	c := newTestConnection(t)
	defer c.destroy()
	testShutdownDuringRecvFirstAfterConnect(t, c, false)
}

func TestShutdownDuringRecvFirstAfterConnectRemote(t *testing.T) {
	c := newTestConnection(t)
	defer c.destroy()
	testShutdownDuringRecvFirstAfterConnect(t, c, true)
}

func testShutdownDuringClientSendRecv(t *testing.T, c *testConnection, remoteShutdown bool) {
	var serverRun sync.WaitGroup
	serverRun.Add(1)
	go func() {
		defer serverRun.Done()
		if _, err := c.serverEP.RecvFirst(); err != nil {
			t.Errorf("server Endpoint.RecvFirst() failed: %v", err)
		}
		// At this point, the client must be blocked in c.clientEP.SendRecv().
		if remoteShutdown {
			c.serverEP.Shutdown()
		} else {
			c.clientEP.Shutdown()
		}
	}()
	defer func() {
		// Ensure that the server goroutine is cleaned up before
		// c.serverEP.Destroy(), even if the test fails.
		c.serverEP.Shutdown()
		serverRun.Wait()
	}()
	if err := c.clientEP.Connect(); err != nil {
		t.Fatalf("client Endpoint.Connect() failed: %v", err)
	}
	if _, err := c.clientEP.SendRecv(0); err == nil {
		t.Errorf("client Endpoint.SendRecv() succeeded unexpectedly")
	}
}

func TestShutdownDuringClientSendRecvLocal(t *testing.T) {
	c := newTestConnection(t)
	defer c.destroy()
	testShutdownDuringClientSendRecv(t, c, false)
}

func TestShutdownDuringClientSendRecvRemote(t *testing.T) {
	c := newTestConnection(t)
	defer c.destroy()
	testShutdownDuringClientSendRecv(t, c, true)
}

func testShutdownDuringServerSendRecv(t *testing.T, c *testConnection, remoteShutdown bool) {
	var serverRun sync.WaitGroup
	serverRun.Add(1)
	go func() {
		defer serverRun.Done()
		if _, err := c.serverEP.RecvFirst(); err != nil {
			t.Errorf("server Endpoint.RecvFirst() failed: %v", err)
			return
		}
		if _, err := c.serverEP.SendRecv(0); err == nil {
			t.Errorf("server Endpoint.SendRecv() succeeded unexpectedly")
		}
	}()
	defer func() {
		// Ensure that the server goroutine is cleaned up before
		// c.serverEP.Destroy(), even if the test fails.
		c.serverEP.Shutdown()
		serverRun.Wait()
	}()
	if err := c.clientEP.Connect(); err != nil {
		t.Fatalf("client Endpoint.Connect() failed: %v", err)
	}
	if _, err := c.clientEP.SendRecv(0); err != nil {
		t.Fatalf("client Endpoint.SendRecv() failed: %v", err)
	}
	time.Sleep(time.Second) // to allow serverEP.SendRecv() to block
	if remoteShutdown {
		c.clientEP.Shutdown()
	} else {
		c.serverEP.Shutdown()
	}
	serverRun.Wait()
}

func TestShutdownDuringServerSendRecvLocal(t *testing.T) {
	c := newTestConnection(t)
	defer c.destroy()
	testShutdownDuringServerSendRecv(t, c, false)
}

func TestShutdownDuringServerSendRecvRemote(t *testing.T) {
	c := newTestConnection(t)
	defer c.destroy()
	testShutdownDuringServerSendRecv(t, c, true)
}

func benchmarkSendRecv(b *testing.B, c *testConnection) {
	var serverRun sync.WaitGroup
	serverRun.Add(1)
	go func() {
		defer serverRun.Done()
		if b.N == 0 {
			return
		}
		if _, err := c.serverEP.RecvFirst(); err != nil {
			b.Errorf("server Endpoint.RecvFirst() failed: %v", err)
			return
		}
		for i := 1; i < b.N; i++ {
			if _, err := c.serverEP.SendRecv(0); err != nil {
				b.Errorf("server Endpoint.SendRecv() failed: %v", err)
				return
			}
		}
		if err := c.serverEP.SendLast(0); err != nil {
			b.Errorf("server Endpoint.SendLast() failed: %v", err)
		}
	}()
	defer func() {
		c.serverEP.Shutdown()
		serverRun.Wait()
	}()

	if err := c.clientEP.Connect(); err != nil {
		b.Fatalf("client Endpoint.Connect() failed: %v", err)
	}
	runtime.GC()
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		if _, err := c.clientEP.SendRecv(0); err != nil {
			b.Fatalf("client Endpoint.SendRecv() failed: %v", err)
		}
	}
	b.StopTimer()
}

func BenchmarkSendRecv(b *testing.B) {
	c := newTestConnection(b)
	defer c.destroy()
	benchmarkSendRecv(b, c)
}
