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

type MakeHash func() hash.Hash

// TestHash performs a set of tests on hash.Hash implementations, checking the
// documented requirements of Write, Sum, Reset, Size, and BlockSize.
func ( *testing.T,  MakeHash) {

	// Test that Sum returns an appended digest matching output of Size
	.Run("SumAppend", func( *testing.T) {
		 := ()
		 := newRandReader()

		 := []byte("")
		 := []byte("a")
		 := make([]byte, .BlockSize()+1)
		.Read()

		// Set of example strings to append digest to
		 := [][]byte{nil, , , }

		// Go to each string and check digest gets appended to and is correct size.
		for ,  := range  {
			.Reset()

			 := getSum(, , ) // Append new digest to prefix

			// Check that Sum didn't alter the prefix
			if !bytes.Equal([0:len()], ) {
				.Errorf("Sum alters passed buffer instead of appending; got %x, want %x", [0:len()], )
			}

			// Check that the appended sum wasn't affected by the prefix
			if  := getSum(, , nil); !bytes.Equal([len():], ) {
				.Errorf("Sum behavior affected by data in the input buffer; got %x, want %x", [len():], )
			}

			// Check size of append
			if ,  := len()-len(), .Size();  !=  {
				.Errorf("Sum appends number of bytes != Size; got %v , want %v", , )
			}
		}
	})

	// Test that Hash.Write never returns error.
	.Run("WriteWithoutError", func( *testing.T) {
		 := ()
		 := newRandReader()

		 := []byte("")
		 := []byte("a")
		 := make([]byte, .BlockSize()+1)
		.Read()

		// Set of example strings to append digest to
		 := [][]byte{, , }

		for ,  := range  {
			writeToHash(, , ) // Writes and checks Write doesn't error
		}
	})

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

		 := getSum(, , nil)

		// Write to hash and then Reset it and see if Sum is same as emptySum
		 := make([]byte, .BlockSize())
		.Read()
		writeToHash(, , )
		.Reset()
		 := getSum(, , nil)

		if !bytes.Equal(, ) {
			.Errorf("Reset hash yields different Sum than new hash; got %x, want %x", , )
		}
	})

	// Check that Write isn't reading from beyond input slice's bounds
	.Run("OutOfBoundsRead", func( *testing.T) {
		 := ()
		 := .BlockSize()
		 := newRandReader()

		 := make([]byte, )
		.Read()
		writeToHash(, , )
		 := getSum(, , nil) // Record control digest

		.Reset()

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

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

		writeToHash(, , [:])
		 := getSum(, , nil)

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

	// Test that multiple calls to Write is stateful
	.Run("StatefulWrite", func( *testing.T) {
		 := ()
		 := newRandReader()

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

		// Write prefix then suffix sequentially and record resulting hash
		writeToHash(, , )
		writeToHash(, , )
		 := getSum(, , nil)

		.Reset()

		// Write prefix and suffix at the same time and record resulting hash
		writeToHash(, , append(, ...))
		 := getSum(, , nil)

		// Check that sequential writing results in the same as writing all at once
		if !bytes.Equal(, ) {
			.Errorf("two successive Write calls resulted in a different Sum than a single one; got %x, want %x", , )
		}
	})
}

// Helper function for writing. Verifies that Write does not error.
func writeToHash( *testing.T,  hash.Hash,  []byte) {
	.Helper()

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

	,  := .Write()
	if  != nil ||  != len() {
		.Errorf("Write returned error; got (%v, %v), want (nil, %v)", , , len())
	}

	if !bytes.Equal(, ) {
		.Errorf("Write modified input slice; got %x, want %x", , )
	}
}

// Helper function for getting Sum. Checks that Sum doesn't change hash state.
func getSum( *testing.T,  hash.Hash,  []byte) []byte {
	.Helper()

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

	 := .Sum()
	 := .Sum()

	// Check that Sum doesn't change underlying hash state
	if !bytes.Equal(, ) {
		.Errorf("successive calls to Sum yield different results; got %x, want %x", , )
	}

	return 
}

func newRandReader( *testing.T) io.Reader {
	 := time.Now().UnixNano()
	.Logf("Deterministic RNG seed: 0x%x", )
	return rand.New(rand.NewSource())
}