// Copyright 2024 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 cryptotest

import (
	
	
	
	
)

var lengths = []int{0, 156, 8192, 8193, 8208}

// MakeAEAD returns a cipher.AEAD instance.
//
// Multiple calls to MakeAEAD must return equivalent instances, so for example
// the key must be fixed.
type MakeAEAD func() (cipher.AEAD, error)

// TestAEAD performs a set of tests on cipher.AEAD implementations, checking
// the documented requirements of NonceSize, Overhead, Seal and Open.
func ( *testing.T,  MakeAEAD) {
	,  := ()
	if  != nil {
		.Fatal()
	}

	.Run("Roundtrip", func( *testing.T) {

		// Test all combinations of plaintext and additional data lengths.
		for ,  := range lengths {
			for ,  := range lengths {
				.Run(fmt.Sprintf("Plaintext-Length=%d,AddData-Length=%d", , ), func( *testing.T) {
					 := newRandReader()

					 := make([]byte, .NonceSize())
					.Read()

					,  := make([]byte, ), make([]byte, )
					.Read()
					.Read()

					 := sealMsg(, , nil, , , )
					 := openWithoutError(, , nil, , , )

					if !bytes.Equal(, ) {
						.Errorf("plaintext is different after a seal/open cycle; got %s, want %s", truncateHex(), truncateHex())
					}
				})
			}
		}
	})

	.Run("InputNotModified", func( *testing.T) {

		// Test all combinations of plaintext and additional data lengths.
		for ,  := range lengths {
			for ,  := range lengths {
				.Run(fmt.Sprintf("Plaintext-Length=%d,AddData-Length=%d", , ), func( *testing.T) {
					.Run("Seal", func( *testing.T) {
						 := newRandReader()

						 := make([]byte, .NonceSize())
						.Read()

						,  := make([]byte, ), make([]byte, )
						.Read()
						copy(, )

						 := make([]byte, )
						.Read()

						sealMsg(, , nil, , , )
						if !bytes.Equal(, ) {
							.Errorf("Seal modified src; got %s, want %s", truncateHex(), truncateHex())
						}
					})

					.Run("Open", func( *testing.T) {
						 := newRandReader()

						 := make([]byte, .NonceSize())
						.Read()

						,  := make([]byte, ), make([]byte, )
						.Read()
						.Read()

						// Record the ciphertext that shouldn't be modified as the input of
						// Open.
						 := sealMsg(, , nil, , , )
						 := make([]byte, len())
						copy(, )

						openWithoutError(, , nil, , , )
						if !bytes.Equal(, ) {
							.Errorf("Open modified src; got %s, want %s", truncateHex(), truncateHex())
						}
					})
				})
			}
		}
	})

	.Run("BufferOverlap", func( *testing.T) {

		// Test all combinations of plaintext and additional data lengths.
		for ,  := range lengths {
			if  <= 1 { // We need enough room for an inexact overlap to occur.
				continue
			}
			for ,  := range lengths {
				.Run(fmt.Sprintf("Plaintext-Length=%d,AddData-Length=%d", , ), func( *testing.T) {
					.Run("Seal", func( *testing.T) {
						 := newRandReader()

						 := make([]byte, .NonceSize())
						.Read()

						// Make a buffer that can hold a plaintext and ciphertext as we
						// overlap their slices to check for panic on inexact overlaps.
						 :=  + .Overhead()
						 := make([]byte, +)
						.Read()

						 := make([]byte, )
						.Read()

						// Make plaintext and dst slices point to same array with inexact overlap.
						 := [:]
						 := [1:1] // Shift dst to not start at start of plaintext.
						mustPanic(, "invalid buffer overlap", func() { sealMsg(, , , , , ) })

						// Only overlap on one byte
						 = [:]
						 = [-1 : -1]
						mustPanic(, "invalid buffer overlap", func() { sealMsg(, , , , , ) })
					})

					.Run("Open", func( *testing.T) {
						 := newRandReader()

						 := make([]byte, .NonceSize())
						.Read()

						// Create a valid ciphertext to test Open with.
						 := make([]byte, )
						.Read()
						 := make([]byte, )
						.Read()
						 := sealMsg(, , nil, , , )

						// Make a buffer that can hold a plaintext and ciphertext as we
						// overlap their slices to check for panic on inexact overlaps.
						 := make([]byte, +len())

						// Make ciphertext and dst slices point to same array with inexact overlap.
						 := [:len()]
						copy(, )
						 := [1:1] // Shift dst to not start at start of ciphertext.
						mustPanic(, "invalid buffer overlap", func() { .Open(, , , ) })

						// Only overlap on one byte.
						 = [:len()]
						copy(, )
						// Make sure it is the actual ciphertext being overlapped and not
						// the hash digest which might be extracted/truncated in some
						// implementations: Go one byte past the hash digest/tag and into
						// the ciphertext.
						 := len() - .Overhead()
						 = [-1 : -1]
						mustPanic(, "invalid buffer overlap", func() { .Open(, , , ) })
					})
				})
			}
		}
	})

	.Run("AppendDst", func( *testing.T) {

		// Test all combinations of plaintext and additional data lengths.
		for ,  := range lengths {
			for ,  := range lengths {
				.Run(fmt.Sprintf("Plaintext-Length=%d,AddData-Length=%d", , ), func( *testing.T) {

					.Run("Seal", func( *testing.T) {
						 := newRandReader()

						 := make([]byte, .NonceSize())
						.Read()

						 := []byte("a")
						 := make([]byte, 512)
						.Read()
						 := [][]byte{, }

						// Check each prefix gets appended to by Seal without altering them.
						for ,  := range  {
							,  := make([]byte, ), make([]byte, )
							.Read()
							.Read()
							 := sealMsg(, , , , , )

							// Check that Seal didn't alter the prefix
							if !bytes.Equal([:len()], ) {
								.Errorf("Seal alters dst instead of appending; got %s, want %s", truncateHex([:len()]), truncateHex())
							}

							if isDeterministic() {
								 := [len():]
								// Check that the appended ciphertext wasn't affected by the prefix
								if  := sealMsg(, , nil, , , ); !bytes.Equal(, ) {
									.Errorf("Seal behavior affected by pre-existing data in dst; got %s, want %s", truncateHex(), truncateHex())
								}
							}
						}
					})

					.Run("Open", func( *testing.T) {
						 := newRandReader()

						 := make([]byte, .NonceSize())
						.Read()

						 := []byte("a")
						 := make([]byte, 512)
						.Read()
						 := [][]byte{, }

						// Check each prefix gets appended to by Open without altering them.
						for ,  := range  {
							,  := make([]byte, ), make([]byte, )
							.Read()
							.Read()
							 := sealMsg(, , nil, , , )

							 := openWithoutError(, , , , , )

							// Check that Open didn't alter the prefix
							if !bytes.Equal([:len()], ) {
								.Errorf("Open alters dst instead of appending; got %s, want %s", truncateHex([:len()]), truncateHex())
							}

							 := [len():]
							// Check that the appended plaintext wasn't affected by the prefix
							if !bytes.Equal(, ) {
								.Errorf("Open behavior affected by pre-existing data in dst; got %s, want %s", truncateHex(), truncateHex())
							}
						}
					})
				})
			}
		}
	})

	.Run("WrongNonce", func( *testing.T) {
		if .NonceSize() == 0 {
			.Skip("AEAD does not use a nonce")
		}
		// Test all combinations of plaintext and additional data lengths.
		for ,  := range lengths {
			for ,  := range lengths {
				.Run(fmt.Sprintf("Plaintext-Length=%d,AddData-Length=%d", , ), func( *testing.T) {
					 := newRandReader()

					 := make([]byte, .NonceSize())
					.Read()

					,  := make([]byte, ), make([]byte, )
					.Read()
					.Read()

					 := sealMsg(, , nil, , , )

					// Perturb the nonce and check for an error when Opening
					 := make([]byte, .NonceSize())
					copy(, )
					[len()-1] += 1
					,  := .Open(nil, , , )

					if  == nil {
						.Errorf("Open did not error when given different nonce than Sealed with")
					}
				})
			}
		}
	})

	.Run("WrongAddData", func( *testing.T) {

		// Test all combinations of plaintext and additional data lengths.
		for ,  := range lengths {
			for ,  := range lengths {
				if  == 0 {
					continue
				}

				.Run(fmt.Sprintf("Plaintext-Length=%d,AddData-Length=%d", , ), func( *testing.T) {
					 := newRandReader()

					 := make([]byte, .NonceSize())
					.Read()

					,  := make([]byte, ), make([]byte, )
					.Read()
					.Read()

					 := sealMsg(, , nil, , , )

					// Perturb the Additional Data and check for an error when Opening
					 := make([]byte, )
					copy(, )
					[len()-1] += 1
					,  := .Open(nil, , , )

					if  == nil {
						.Errorf("Open did not error when given different Additional Data than Sealed with")
					}
				})
			}
		}
	})

	.Run("WrongCiphertext", func( *testing.T) {

		// Test all combinations of plaintext and additional data lengths.
		for ,  := range lengths {
			for ,  := range lengths {

				.Run(fmt.Sprintf("Plaintext-Length=%d,AddData-Length=%d", , ), func( *testing.T) {
					 := newRandReader()

					 := make([]byte, .NonceSize())
					.Read()

					,  := make([]byte, ), make([]byte, )
					.Read()
					.Read()

					 := sealMsg(, , nil, , , )

					// Perturb the ciphertext and check for an error when Opening
					 := make([]byte, len())
					copy(, )
					[len()-1] += 1
					,  := .Open(nil, , , )

					if  == nil {
						.Errorf("Open did not error when given different ciphertext than was produced by Seal")
					}
				})
			}
		}
	})
}

// Helper function to Seal a plaintext with additional data. Checks that
// ciphertext isn't bigger than the plaintext length plus Overhead()
func sealMsg( *testing.T,  cipher.AEAD, , , ,  []byte) []byte {
	.Helper()

	 := len()

	 = .Seal(, , , )

	 := len() - 

	// Appended ciphertext shouldn't ever be longer than the length of the
	// plaintext plus Overhead
	if  > len()+.Overhead() {
		.Errorf("length of ciphertext from Seal exceeds length of plaintext by more than Overhead(); got %d, want <=%d", , len()+.Overhead())
	}

	return 
}

func isDeterministic( cipher.AEAD) bool {
	// Check if the AEAD is deterministic by checking if the same plaintext
	// encrypted with the same nonce and additional data produces the same
	// ciphertext.
	 := make([]byte, .NonceSize())
	 := []byte("additional data")
	 := []byte("plaintext")
	 := .Seal(nil, , , )
	 := .Seal(nil, , , )
	return bytes.Equal(, )
}

// Helper function to Open and authenticate ciphertext. Checks that Open
// doesn't error (assuming ciphertext was well-formed with corresponding nonce
// and additional data).
func openWithoutError( *testing.T,  cipher.AEAD, , , ,  []byte) []byte {
	.Helper()

	,  := .Open(, , , )
	if  != nil {
		.Fatalf("Open returned error on properly formed ciphertext; got \"%s\", want \"nil\"", )
	}

	return 
}