// Copyright 2018 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 queue

import (
	"encoding/binary"
	"slices"
	"testing"

	"gvisor.dev/gvisor/pkg/atomicbitops"
	"gvisor.dev/gvisor/pkg/tcpip/link/sharedmem/pipe"
)

func TestBasicTxQueue(t *testing.T) {
	// Tests that a basic transmit on a queue works, and that completion
	// gets properly reported as well.
	pb1 := make([]byte, 100)
	pb2 := make([]byte, 100)

	var rxp pipe.Rx
	rxp.Init(pb1)

	var txp pipe.Tx
	txp.Init(pb2)

	var q Tx
	var state atomicbitops.Uint32
	q.Init(pb1, pb2, &state)

	// Enqueue two buffers.
	b := []TxBuffer{
		{nil, 100, 60},
		{nil, 200, 40},
	}

	b[0].Next = &b[1]

	const usedID = 1002
	const usedTotalSize = 100
	if !q.Enqueue(usedID, usedTotalSize, 2, &b[0]) {
		t.Fatalf("Enqueue failed on empty queue")
	}

	// Check the contents of the pipe.
	d := rxp.Pull()
	if d == nil {
		t.Fatalf("Tx pipe is empty after Enqueue")
	}

	want := []byte{
		234, 3, 0, 0, 0, 0, 0, 0, // id
		100, 0, 0, 0, // total size
		0, 0, 0, 0, // reserved
		100, 0, 0, 0, 0, 0, 0, 0, // offset 1
		60, 0, 0, 0, // size 1
		200, 0, 0, 0, 0, 0, 0, 0, // offset 2
		40, 0, 0, 0, // size 2
	}

	if !slices.Equal(want, d) {
		t.Fatalf("Bad posted packet: got %v, want %v", d, want)
	}

	rxp.Flush()

	// Check that there are no completions yet.
	if _, ok := q.CompletedPacket(); ok {
		t.Fatalf("Packet reported as completed too soon")
	}

	// Post a completion.
	d = txp.Push(8)
	if d == nil {
		t.Fatalf("Unable to push to rx pipe")
	}
	binary.LittleEndian.PutUint64(d, usedID)
	txp.Flush()

	// Check that completion is properly reported.
	id, ok := q.CompletedPacket()
	if !ok {
		t.Fatalf("Completion not reported")
	}

	if id != usedID {
		t.Fatalf("Bad completion id: got %v, want %v", id, usedID)
	}
}

func TestBasicRxQueue(t *testing.T) {
	// Tests that a basic receive on a queue works.
	pb1 := make([]byte, 100)
	pb2 := make([]byte, 100)

	var rxp pipe.Rx
	rxp.Init(pb1)

	var txp pipe.Tx
	txp.Init(pb2)

	var q Rx
	q.Init(pb1, pb2, nil)

	// Post two buffers.
	b := []RxBuffer{
		{100, 60, 1077, 0},
		{200, 40, 2123, 0},
	}

	if !q.PostBuffers(b) {
		t.Fatalf("PostBuffers failed on empty queue")
	}

	// Check the contents of the pipe.
	want := [][]byte{
		{
			100, 0, 0, 0, 0, 0, 0, 0, // Offset1
			60, 0, 0, 0, // Size1
			0, 0, 0, 0, // Remaining in group 1
			0, 0, 0, 0, 0, 0, 0, 0, // User data 1
			53, 4, 0, 0, 0, 0, 0, 0, // ID 1
		},
		{
			200, 0, 0, 0, 0, 0, 0, 0, // Offset2
			40, 0, 0, 0, // Size2
			0, 0, 0, 0, // Remaining in group 2
			0, 0, 0, 0, 0, 0, 0, 0, // User data 2
			75, 8, 0, 0, 0, 0, 0, 0, // ID 2
		},
	}

	for i := range b {
		d := rxp.Pull()
		if d == nil {
			t.Fatalf("Tx pipe is empty after PostBuffers")
		}

		if !slices.Equal(want[i], d) {
			t.Fatalf("Bad posted packet: got %v, want %v", d, want[i])
		}

		rxp.Flush()
	}

	// Check that there are no completions.
	if _, n := q.Dequeue(nil); n != 0 {
		t.Fatalf("Packet reported as received too soon")
	}

	// Post a completion.
	d := txp.Push(sizeOfConsumedPacketHeader + 2*sizeOfConsumedBuffer)
	if d == nil {
		t.Fatalf("Unable to push to rx pipe")
	}

	copy(d, []byte{
		100, 0, 0, 0, // packet size
		0, 0, 0, 0, // reserved

		100, 0, 0, 0, 0, 0, 0, 0, // offset 1
		60, 0, 0, 0, // size 1
		0, 0, 0, 0, 0, 0, 0, 0, // user data 1
		53, 4, 0, 0, 0, 0, 0, 0, // ID 1

		200, 0, 0, 0, 0, 0, 0, 0, // offset 2
		40, 0, 0, 0, // size 2
		0, 0, 0, 0, 0, 0, 0, 0, // user data 2
		75, 8, 0, 0, 0, 0, 0, 0, // ID 2
	})

	txp.Flush()

	// Check that completion is properly reported.
	bufs, n := q.Dequeue(nil)
	if n != 100 {
		t.Fatalf("Bad packet size: got %v, want %v", n, 100)
	}

	if !slices.Equal(bufs, b) {
		t.Fatalf("Bad returned buffers: got %v, want %v", bufs, b)
	}
}

func TestBadTxCompletion(t *testing.T) {
	// Check that tx completions with bad sizes are properly ignored.
	pb1 := make([]byte, 100)
	pb2 := make([]byte, 100)

	var rxp pipe.Rx
	rxp.Init(pb1)

	var txp pipe.Tx
	txp.Init(pb2)

	var q Tx
	var state atomicbitops.Uint32
	q.Init(pb1, pb2, &state)

	// Post a completion that is too short, and check that it is ignored.
	if d := txp.Push(7); d == nil {
		t.Fatalf("Unable to push to rx pipe")
	}
	txp.Flush()

	if _, ok := q.CompletedPacket(); ok {
		t.Fatalf("Bad completion not ignored")
	}

	// Post a completion that is too long, and check that it is ignored.
	if d := txp.Push(10); d == nil {
		t.Fatalf("Unable to push to rx pipe")
	}
	txp.Flush()

	if _, ok := q.CompletedPacket(); ok {
		t.Fatalf("Bad completion not ignored")
	}
}

func TestBadRxCompletion(t *testing.T) {
	// Check that bad rx completions are properly ignored.
	pb1 := make([]byte, 100)
	pb2 := make([]byte, 100)

	var rxp pipe.Rx
	rxp.Init(pb1)

	var txp pipe.Tx
	txp.Init(pb2)

	var q Rx
	q.Init(pb1, pb2, nil)

	// Post a completion that is too short, and check that it is ignored.
	if d := txp.Push(7); d == nil {
		t.Fatalf("Unable to push to rx pipe")
	}
	txp.Flush()

	if b, _ := q.Dequeue(nil); b != nil {
		t.Fatalf("Bad completion not ignored")
	}

	// Post a completion whose buffer sizes add up to less than the total
	// size.
	d := txp.Push(sizeOfConsumedPacketHeader + 2*sizeOfConsumedBuffer)
	if d == nil {
		t.Fatalf("Unable to push to rx pipe")
	}

	copy(d, []byte{
		100, 0, 0, 0, // packet size
		0, 0, 0, 0, // reserved

		100, 0, 0, 0, 0, 0, 0, 0, // offset 1
		10, 0, 0, 0, // size 1
		0, 0, 0, 0, 0, 0, 0, 0, // user data 1
		53, 4, 0, 0, 0, 0, 0, 0, // ID 1

		200, 0, 0, 0, 0, 0, 0, 0, // offset 2
		10, 0, 0, 0, // size 2
		0, 0, 0, 0, 0, 0, 0, 0, // user data 2
		75, 8, 0, 0, 0, 0, 0, 0, // ID 2
	})

	txp.Flush()
	if b, _ := q.Dequeue(nil); b != nil {
		t.Fatalf("Bad completion not ignored")
	}

	// Post a completion whose buffer sizes will cause a 32-bit overflow,
	// but adds up to the right number.
	d = txp.Push(sizeOfConsumedPacketHeader + 2*sizeOfConsumedBuffer)
	if d == nil {
		t.Fatalf("Unable to push to rx pipe")
	}

	copy(d, []byte{
		100, 0, 0, 0, // packet size
		0, 0, 0, 0, // reserved

		100, 0, 0, 0, 0, 0, 0, 0, // offset 1
		255, 255, 255, 255, // size 1
		0, 0, 0, 0, 0, 0, 0, 0, // user data 1
		53, 4, 0, 0, 0, 0, 0, 0, // ID 1

		200, 0, 0, 0, 0, 0, 0, 0, // offset 2
		101, 0, 0, 0, // size 2
		0, 0, 0, 0, 0, 0, 0, 0, // user data 2
		75, 8, 0, 0, 0, 0, 0, 0, // ID 2
	})

	txp.Flush()
	if b, _ := q.Dequeue(nil); b != nil {
		t.Fatalf("Bad completion not ignored")
	}
}

func TestFillTxPipe(t *testing.T) {
	// Check that transmitting a new buffer when the buffer pipe is full
	// fails gracefully.
	pb1 := make([]byte, 104)
	pb2 := make([]byte, 104)

	var rxp pipe.Rx
	rxp.Init(pb1)

	var txp pipe.Tx
	txp.Init(pb2)

	var q Tx
	var state atomicbitops.Uint32
	q.Init(pb1, pb2, &state)

	// Transmit twice, which should fill the tx pipe.
	b := []TxBuffer{
		{nil, 100, 60},
		{nil, 200, 40},
	}

	b[0].Next = &b[1]

	const usedID = 1002
	const usedTotalSize = 100
	for i := uint64(0); i < 2; i++ {
		if !q.Enqueue(usedID+i, usedTotalSize, 2, &b[0]) {
			t.Fatalf("Failed to transmit buffer")
		}
	}

	// Transmit another packet now that the tx pipe is full.
	if q.Enqueue(usedID+2, usedTotalSize, 2, &b[0]) {
		t.Fatalf("Enqueue succeeded when tx pipe is full")
	}
}

func TestFillRxPipe(t *testing.T) {
	// Check that posting a new buffer when the buffer pipe is full fails
	// gracefully.
	pb1 := make([]byte, 100)
	pb2 := make([]byte, 100)

	var rxp pipe.Rx
	rxp.Init(pb1)

	var txp pipe.Tx
	txp.Init(pb2)

	var q Rx
	q.Init(pb1, pb2, nil)

	// Post a buffer twice, it should fill the tx pipe.
	b := []RxBuffer{
		{100, 60, 1077, 0},
	}

	for i := 0; i < 2; i++ {
		if !q.PostBuffers(b) {
			t.Fatalf("PostBuffers failed on non-full queue")
		}
	}

	// Post another buffer now that the tx pipe is full.
	if q.PostBuffers(b) {
		t.Fatalf("PostBuffers succeeded on full queue")
	}
}

func TestLotsOfTransmissions(t *testing.T) {
	// Make sure pipes are being properly flushed when transmitting packets.
	pb1 := make([]byte, 100)
	pb2 := make([]byte, 100)

	var rxp pipe.Rx
	rxp.Init(pb1)

	var txp pipe.Tx
	txp.Init(pb2)

	var q Tx
	var state atomicbitops.Uint32
	q.Init(pb1, pb2, &state)

	// Prepare packet with two buffers.
	b := []TxBuffer{
		{nil, 100, 60},
		{nil, 200, 40},
	}

	b[0].Next = &b[1]

	const usedID = 1002
	const usedTotalSize = 100

	// Post 100000 packets and completions.
	for i := 100000; i > 0; i-- {
		if !q.Enqueue(usedID, usedTotalSize, 2, &b[0]) {
			t.Fatalf("Enqueue failed on non-full queue")
		}

		if d := rxp.Pull(); d == nil {
			t.Fatalf("Tx pipe is empty after Enqueue")
		}
		rxp.Flush()

		d := txp.Push(8)
		if d == nil {
			t.Fatalf("Unable to write to rx pipe")
		}
		binary.LittleEndian.PutUint64(d, usedID)
		txp.Flush()
		if _, ok := q.CompletedPacket(); !ok {
			t.Fatalf("Completion not returned")
		}
	}
}

func TestLotsOfReceptions(t *testing.T) {
	// Make sure pipes are being properly flushed when receiving packets.
	pb1 := make([]byte, 100)
	pb2 := make([]byte, 100)

	var rxp pipe.Rx
	rxp.Init(pb1)

	var txp pipe.Tx
	txp.Init(pb2)

	var q Rx
	q.Init(pb1, pb2, nil)

	// Prepare for posting two buffers.
	b := []RxBuffer{
		{100, 60, 1077, 0},
		{200, 40, 2123, 0},
	}

	// Post 100000 buffers and completions.
	for i := 100000; i > 0; i-- {
		if !q.PostBuffers(b) {
			t.Fatalf("PostBuffers failed on non-full queue")
		}

		if d := rxp.Pull(); d == nil {
			t.Fatalf("Tx pipe is empty after PostBuffers")
		}
		rxp.Flush()

		if d := rxp.Pull(); d == nil {
			t.Fatalf("Tx pipe is empty after PostBuffers")
		}
		rxp.Flush()

		d := txp.Push(sizeOfConsumedPacketHeader + 2*sizeOfConsumedBuffer)
		if d == nil {
			t.Fatalf("Unable to push to rx pipe")
		}

		copy(d, []byte{
			100, 0, 0, 0, // packet size
			0, 0, 0, 0, // reserved

			100, 0, 0, 0, 0, 0, 0, 0, // offset 1
			60, 0, 0, 0, // size 1
			0, 0, 0, 0, 0, 0, 0, 0, // user data 1
			53, 4, 0, 0, 0, 0, 0, 0, // ID 1

			200, 0, 0, 0, 0, 0, 0, 0, // offset 2
			40, 0, 0, 0, // size 2
			0, 0, 0, 0, 0, 0, 0, 0, // user data 2
			75, 8, 0, 0, 0, 0, 0, 0, // ID 2
		})

		txp.Flush()

		if _, n := q.Dequeue(nil); n == 0 {
			t.Fatalf("Dequeue failed when there is a completion")
		}
	}
}

func TestRxEnableNotification(t *testing.T) {
	// Check that enabling notifications results in properly updated state.
	pb1 := make([]byte, 100)
	pb2 := make([]byte, 100)

	var state atomicbitops.Uint32
	var q Rx
	q.Init(pb1, pb2, &state)

	q.EnableNotification()
	if state.Load() != EventFDEnabled {
		t.Fatalf("Bad value in shared state: got %v, want %v", state.Load(), EventFDEnabled)
	}
}

func TestRxDisableNotification(t *testing.T) {
	// Check that disabling notifications results in properly updated state.
	pb1 := make([]byte, 100)
	pb2 := make([]byte, 100)

	var state atomicbitops.Uint32
	var q Rx
	q.Init(pb1, pb2, &state)

	q.DisableNotification()
	if state.Load() != EventFDDisabled {
		t.Fatalf("Bad value in shared state: got %v, want %v", state.Load(), EventFDDisabled)
	}
}
