// 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 (
	
	
	
	
	
	
)

// Each test is executed with each of the buffer lengths in bufLens.
var (
	bufLens = []int{0, 1, 3, 4, 8, 10, 15, 16, 20, 32, 50, 4096, 5000}
	bufCap  = 10000
)

// MakeStream returns a cipher.Stream instance.
//
// Multiple calls to MakeStream must return equivalent instances,
// so for example the key and/or IV must be fixed.
type MakeStream func() cipher.Stream

// TestStream performs a set of tests on cipher.Stream implementations,
// checking the documented requirements of XORKeyStream.
func ( *testing.T,  MakeStream) {

	.Run("XORSemantics", func( *testing.T) {
		if strings.Contains(.Name(), "TestCFBStream") {
			// This is ugly, but so is CFB's abuse of cipher.Stream.
			// Don't want to make it easier for anyone else to do that.
			.Skip("CFB implements cipher.Stream but does not follow XOR semantics")
		}

		// Test that XORKeyStream inverts itself for encryption/decryption.
		.Run("Roundtrip", func( *testing.T) {

			for ,  := range bufLens {
				.Run(fmt.Sprintf("BuffLength=%d", ), func( *testing.T) {
					 := newRandReader()

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

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

					().XORKeyStream(, ) // Encrypt plaintext
					().XORKeyStream(, ) // Decrypt ciphertext
					if !bytes.Equal(, ) {
						.Errorf("plaintext is different after an encrypt/decrypt cycle; got %s, want %s", truncateHex(), truncateHex())
					}
				})
			}
		})

		// Test that XORKeyStream behaves the same as directly XORing
		// plaintext with the stream.
		.Run("DirectXOR", func( *testing.T) {

			for ,  := range bufLens {
				.Run(fmt.Sprintf("BuffLength=%d", ), func( *testing.T) {
					 := newRandReader()

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

					// Encrypting all zeros should reveal the stream itself
					,  := make([]byte, ), make([]byte, )
					().XORKeyStream(, )
					// Encrypt plaintext by directly XORing the stream
					subtle.XORBytes(, , )

					// Encrypt plaintext with XORKeyStream
					 := make([]byte, )
					().XORKeyStream(, )
					if !bytes.Equal(, ) {
						.Errorf("xor semantics were not preserved; got %s, want %s", truncateHex(), truncateHex())
					}
				})
			}
		})
	})

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

		,  := make([]byte, 100), make([]byte, 100)
		.Read()
		 := bytes.Clone()

		().XORKeyStream(, [:0])
		if !bytes.Equal(, ) {
			.Errorf("XORKeyStream modified dst on empty input; got %s, want %s", truncateHex(), truncateHex())
		}
	})

	.Run("AlterInput", func( *testing.T) {
		 := newRandReader()
		, ,  := make([]byte, bufCap), make([]byte, bufCap), make([]byte, bufCap)
		.Read()

		for ,  := range bufLens {

			.Run(fmt.Sprintf("BuffLength=%d", ), func( *testing.T) {
				copy(, )

				().XORKeyStream([:], [:])
				if !bytes.Equal(, ) {
					.Errorf("XORKeyStream modified src; got %s, want %s", truncateHex(), truncateHex())
				}
			})
		}
	})

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

		,  := make([]byte, bufCap), make([]byte, bufCap)

		for ,  := range bufLens {
			// Record what output is when src and dst are different
			.Read()
			().XORKeyStream([:], [:])

			// Check that the same output is generated when src=dst alias to the same
			// memory
			().XORKeyStream([:], [:])
			if !bytes.Equal([:], [:]) {
				.Errorf("block cipher produced different output when dst = src; got %x, want %x", [:], [:])
			}
		}
	})

	.Run("OutOfBoundsWrite", func( *testing.T) { // Issue 21104
		 := newRandReader()

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

		for ,  := range bufLens {
			copy(, ) // Reset ciphertext buffer

			.Run(fmt.Sprintf("BuffLength=%d", ), func( *testing.T) {
				mustPanic(, "output smaller than input", func() { ().XORKeyStream([:], ) })

				if !bytes.Equal([:], [:]) {
					.Errorf("XORKeyStream did out of bounds write; got %s, want %s", truncateHex([:]), truncateHex([:]))
				}
			})
		}
	})

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

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

		for ,  := range bufLens {
			if  == 0 ||  == 1 {
				continue
			}

			.Run(fmt.Sprintf("BuffLength=%d", ), func( *testing.T) {
				// Make src and dst slices point to same array with inexact overlap
				 := [:]
				 := [1 : +1]
				mustPanic(, "invalid buffer overlap", func() { ().XORKeyStream(, ) })

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

				// src comes after dst with one byte overlap
				 = [-1 : 2*-1]
				 = [:]
				mustPanic(, "invalid buffer overlap", func() { ().XORKeyStream(, ) })
			})
		}
	})

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

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

		// Make one long call to XORKeyStream
		().XORKeyStream(, )

		for ,  := range bufLens {
			if  == 0 {
				continue
			}
			 := fmt.Sprintf("step %d: ", )

			 := make([]byte, bufCap)

			// Make a bunch of small calls to (stateful) XORKeyStream
			 := ()
			 := 0
			for + < len() {
				.XORKeyStream([:], [:+])
				 += 
			}
			.XORKeyStream([:], [:])

			if !bytes.Equal(, ) {
				.Errorf(+"successive XORKeyStream calls returned a different result than a single one; got %s, want %s", truncateHex(), truncateHex())
			}
		}
	})
}

// TestStreamFromBlock creates a Stream from a cipher.Block used in a
// cipher.BlockMode. It addresses Issue 68377 by checking for a panic when the
// BlockMode uses an IV with incorrect length.
// For a valid IV, it also runs all TestStream tests on the resulting stream.
func ( *testing.T,  cipher.Block,  func( cipher.Block,  []byte) cipher.Stream) {

	.Run("WrongIVLen", func( *testing.T) {
		.Skip("see Issue 68377")

		 := newRandReader()
		 := make([]byte, .BlockSize()+1)
		.Read()
		mustPanic(, "IV length must equal block size", func() { (, ) })
	})

	.Run("BlockModeStream", func( *testing.T) {
		 := newRandReader()
		 := make([]byte, .BlockSize())
		.Read()

		TestStream(, func() cipher.Stream { return (, ) })
	})
}

func truncateHex( []byte) string {
	 := 50

	if len() <=  {
		return fmt.Sprintf("%x", )
	}
	return fmt.Sprintf("%x...", [:])
}