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

// MakeBlockMode returns a cipher.BlockMode instance.
// It expects len(iv) == b.BlockSize().
type MakeBlockMode func(b cipher.Block, iv []byte) cipher.BlockMode

// TestBlockMode performs a set of tests on cipher.BlockMode implementations,
// checking the documented requirements of CryptBlocks.
func ( *testing.T,  cipher.Block, ,  MakeBlockMode) {
	 := newRandReader()
	 := make([]byte, .BlockSize())
	.Read()

	testBlockModePair(, , , , )
}

func testBlockModePair( *testing.T,  cipher.Block, ,  MakeBlockMode,  []byte) {
	.Run("Encryption", func( *testing.T) {
		testBlockMode(, , , )
	})

	.Run("Decryption", func( *testing.T) {
		testBlockMode(, , , )
	})

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

		 := (, ).BlockSize()
		if  := (, ).BlockSize();  !=  {
			.Errorf("decryption blocksize different than encryption's; got %d, want %d", , )
		}

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

		(, ).CryptBlocks(, )
		(, ).CryptBlocks(, )
		if !bytes.Equal(, ) {
			.Errorf("plaintext is different after an encrypt/decrypt cycle; got %x, want %x", , )
		}
	})
}

func testBlockMode( *testing.T,  MakeBlockMode,  cipher.Block,  []byte) {
	 := (, ).BlockSize()

	.Run("WrongIVLen", func( *testing.T) {
		 := make([]byte, .BlockSize()+1)
		mustPanic(, "IV length must equal block size", func() { (, ) })
	})

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

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

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

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

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

		for ,  := range []int{0, ,  * 2} {
			.Read()
			copy(, )

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

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

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

		for ,  := range []int{0, ,  * 2} {
			// Record what output is when src and dst are different
			.Read()
			(, ).CryptBlocks([:], [:])

			// Check that the same output is generated when src=dst alias to the same
			// memory
			(, ).CryptBlocks([:], [:])
			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, )
		.Read()

		// Make a buffer with dst in the middle and data on either end
		 := make([]byte, *3)
		,  := , *2
		.Read([:])
		.Read([:])
		 := [:]

		// Record the prefix and suffix data to make sure they aren't written to
		,  := make([]byte, ), make([]byte, )
		copy(, [:])
		copy(, [:])

		// Write to dst (the middle of the buffer) and make sure it doesn't write
		// beyond the dst slice on a valid CryptBlocks call
		(, ).CryptBlocks(, )
		if !bytes.Equal([:], ) {
			.Errorf("block cipher did out of bounds write after end of dst slice; got %x, want %x", [:], )
		}
		if !bytes.Equal([:], ) {
			.Errorf("block cipher did out of bounds write before beginning of dst slice; got %x, want %x", [:], )
		}

		// Check that dst isn't written to beyond len(src) even if there is room in
		// the slice
		 = [:] // Extend dst to include suffix
		(, ).CryptBlocks(, )
		if !bytes.Equal([:], ) {
			.Errorf("CryptBlocks modified dst past len(src); got %x, want %x", [:], )
		}

		// Issue 21104: Shouldn't write to anything outside of dst even if src is bigger
		 = make([]byte, *3)
		.Read()

		mustPanic(, "output smaller than input", func() {
			(, ).CryptBlocks(, )
		})

		if !bytes.Equal([:], ) {
			.Errorf("block cipher did out of bounds write after end of dst slice; got %x, want %x", [:], )
		}
		if !bytes.Equal([:], ) {
			.Errorf("block cipher did out of bounds write before beginning of dst slice; got %x, want %x", [:], )
		}
	})

	// Check that output of cipher isn't affected by adjacent data beyond input
	// slice scope
	.Run("OutOfBoundsRead", func( *testing.T) {
		 := newRandReader()

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

		// Make a buffer with src in the middle and data on either end
		 := make([]byte, *3)
		,  := , *2

		copy([:], )
		.Read([:])
		.Read([:])

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

		if !bytes.Equal(, ) {
			.Errorf("CryptBlocks affected by data outside of src slice bounds; got %x, want %x", , )
		}
	})

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

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

		// Make src and dst slices point to same array with inexact overlap
		 := [:]
		 := [1 : +1]
		mustPanic(, "invalid buffer overlap", func() { (, ).CryptBlocks(, ) })

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

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

	// Input to CryptBlocks should be a multiple of BlockSize
	.Run("PartialBlocks", func( *testing.T) {
		// Check a few cases of not being a multiple of BlockSize
		for ,  := range []int{ - 1,  + 1, 2* - 1, 2* + 1} {
			 := make([]byte, )
			 := make([]byte, 3*) // Make a dst large enough for all src
			mustPanic(, "input not full blocks", func() { (, ).CryptBlocks(, ) })
		}
	})

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

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

		,  := 2*, (, )
		.CryptBlocks(, [:])
		.CryptBlocks([:], [:])

		(, ).CryptBlocks(, )

		if !bytes.Equal(, ) {
			.Errorf("two successive CryptBlocks calls returned a different result than a single one; got %x, want %x", , )
		}
	})
}