From 8f81e22b9ae33983bb62a7053c37ab593829684a Mon Sep 17 00:00:00 2001 From: Ahrav Dutta Date: Wed, 25 Sep 2024 19:10:53 -0700 Subject: [PATCH 1/8] Add hasher interface and fnv + sha256 implemenations --- pkg/hasher/fnv.go | 13 ++ pkg/hasher/hasher.go | 86 +++++++++ pkg/hasher/hasher_test.go | 389 ++++++++++++++++++++++++++++++++++++++ pkg/hasher/mutex.go | 38 ++++ pkg/hasher/sha256.go | 13 ++ 5 files changed, 539 insertions(+) create mode 100644 pkg/hasher/fnv.go create mode 100644 pkg/hasher/hasher.go create mode 100644 pkg/hasher/hasher_test.go create mode 100644 pkg/hasher/mutex.go create mode 100644 pkg/hasher/sha256.go diff --git a/pkg/hasher/fnv.go b/pkg/hasher/fnv.go new file mode 100644 index 000000000000..3a42e931cf1e --- /dev/null +++ b/pkg/hasher/fnv.go @@ -0,0 +1,13 @@ +package hasher + +import "hash/fnv" + +// FNVHasher implements the Hasher interface using FNV algorithm. +type FNVHasher struct{ baseHasher } + +// NewFNVHasher creates a new FNVHasher. +func NewFNVHasher() *FNVHasher { + return &FNVHasher{ + baseHasher: baseHasher{hash: fnv.New64a()}, + } +} diff --git a/pkg/hasher/hasher.go b/pkg/hasher/hasher.go new file mode 100644 index 000000000000..fe55c0361e0b --- /dev/null +++ b/pkg/hasher/hasher.go @@ -0,0 +1,86 @@ +// Package hasher provides a generic interface and base implementation for hashing data. +package hasher + +import ( + "errors" + "fmt" + "hash" + + "github.com/trufflesecurity/trufflehog/v3/pkg/context" +) + +// Hasher defines a generic interface for hashing data. +// Implementations of this interface may choose to be safe for concurrent use, +// but it is not a requirement. Users should check the documentation of specific +// implementations for concurrent safety guarantees. +type Hasher interface { + // Hash takes input data and returns the hashed result. + // It returns an error if the input is empty or if the hashing process fails. + // The function is idempotent - calling it multiple times with the same input + // will produce the same output, assuming the underlying hash function is deterministic. + Hash(data []byte) ([]byte, error) + + // HashWithContext performs the same operation as Hash but with a context. + HashWithContext(ctx context.Context, data []byte) ([]byte, error) +} + +// baseHasher provides a base implementation for the Hasher interface. +// It uses the hash.Hash interface from the standard library to perform the actual hashing. +// This implementation is not safe for concurrent use. Each goroutine/worker should +// use its own instance of baseHasher for concurrent operations. +// Implementations that require concurrent access should wrap baseHasher with a mutex. (e.g., MutexHasher) +type baseHasher struct{ hash hash.Hash } + +// InputTooLargeError is returned when the input data exceeds the maximum allowed size. +type InputTooLargeError struct { + inputSize int + maxSize int +} + +func (e *InputTooLargeError) Error() string { + return fmt.Sprintf("input data exceeds the maximum allowed size: %d > %d", e.inputSize, e.maxSize) +} + +// ErrEmptyData is returned when the input data is empty. +var ErrEmptyData = errors.New("data cannot be empty") + +const maxInputSize = 1 << 13 // 8 KB + +// HashWithContext computes the hash of the given data, respecting the provided context for cancellation. +func (b *baseHasher) HashWithContext(ctx context.Context, data []byte) ([]byte, error) { + if err := b.validateInput(data); err != nil { + return nil, err + } + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + return b.Hash(data) + } +} + +// Hash computes the hash of the given data. +// It returns an ErrEmptyData if the input is empty or if writing to the hash fails. +// This method resets the underlying hash before each computation to ensure +// that previous hashing operations do not affect the result. +func (b *baseHasher) Hash(data []byte) ([]byte, error) { + if err := b.validateInput(data); err != nil { + return nil, err + } + b.hash.Reset() + _, err := b.hash.Write(data) + if err != nil { + return nil, err + } + return b.hash.Sum(nil), nil +} + +func (b *baseHasher) validateInput(data []byte) error { + if len(data) == 0 { + return ErrEmptyData + } + if len(data) > maxInputSize { + return &InputTooLargeError{inputSize: len(data), maxSize: maxInputSize} + } + return nil +} diff --git a/pkg/hasher/hasher_test.go b/pkg/hasher/hasher_test.go new file mode 100644 index 000000000000..767c0950f526 --- /dev/null +++ b/pkg/hasher/hasher_test.go @@ -0,0 +1,389 @@ +package hasher + +import ( + "bytes" + "encoding/hex" + "errors" + "fmt" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "github.com/trufflesecurity/trufflehog/v3/pkg/context" +) + +func TestHasherHash(t *testing.T) { + testCases := []struct { + name string + hasher Hasher + input []byte + expectedHex string + expectError error + }{ + { + name: "FNV-64a with 'Hello, World!'", + hasher: NewFNVHasher(), + input: []byte("Hello, World!"), + expectedHex: "6ef05bd7cc857c54", + }, + { + name: "SHA-256 with 'Hello, World!'", + hasher: NewSHA256Hasher(), + input: []byte("Hello, World!"), + expectedHex: "dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f", + }, + { + name: "SHA-256 input at max size", + hasher: NewSHA256Hasher(), + input: bytes.Repeat([]byte("a"), maxInputSize), + expectedHex: "dd4e6730520932767ec0a9e33fe19c4ce24399d6eba4ff62f13013c9ed30ef87", + }, + { + name: "FN-64a input exceeds max size", + hasher: NewFNVHasher(), + input: bytes.Repeat([]byte("a"), maxInputSize+1), + expectError: &InputTooLargeError{}, + }, + { + name: "FNV-64a with empty input", + hasher: NewFNVHasher(), + input: []byte(""), + expectedHex: "", + expectError: ErrEmptyData, + }, + { + name: "SHA-256 with empty input", + hasher: NewSHA256Hasher(), + input: []byte(""), + expectedHex: "", + expectError: ErrEmptyData, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + got, err := tc.hasher.Hash(tc.input) + checkError(t, err, tc.expectError, len(tc.input)) + + if tc.expectError != nil { + return + } + + expected, err := hex.DecodeString(tc.expectedHex) + if err != nil { + t.Fatalf("invalid expected hex string '%s': %v", tc.expectedHex, err) + } + + if !bytes.Equal(got, expected) { + t.Errorf("hash mismatch.\nGot: %x\nExpected: %x", got, expected) + } + }) + } +} + +func checkError(t *testing.T, err, expectError error, inputSize int) { + t.Helper() + + if expectError != nil { + var inputTooLargeError *InputTooLargeError + if errors.As(expectError, &inputTooLargeError) { + var inputTooLargeErr *InputTooLargeError + if assert.ErrorAs(t, err, &inputTooLargeErr) { + assert.Equal(t, inputSize, inputTooLargeErr.inputSize) + assert.Equal(t, maxInputSize, inputTooLargeErr.maxSize) + } + } + } else { + assert.NoError(t, err) + } +} + +func TestBaseHasherHashIdempotency(t *testing.T) { + t.Parallel() + + hasher := NewFNVHasher() + input := bytes.Repeat([]byte("a"), maxInputSize) + + hash1, err1 := hasher.Hash(input) + assert.NoError(t, err1, "unexpected error on first hash") + + hash2, err2 := hasher.Hash(input) + assert.NoError(t, err2, "unexpected error on second hash") + + if !bytes.Equal(hash1, hash2) { + t.Errorf("hash results are not identical.\nFirst: %x\nSecond: %x", hash1, hash2) + } +} + +// TestHasherHashWithContextIdempotency confirms that multiple invocations +// of HashWithContext with the same input produce identical results. +func TestHasherHashWithContextIdempotency(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + hasher Hasher + input []byte + expectedHex string + expectError bool + }{ + { + name: "FNVHasher idempotency", + hasher: NewFNVHasher(), + input: []byte("Idempotency test"), + expectedHex: "7a011bc470bdd8d6", + }, + { + name: "SHA256Hasher idempotency", + hasher: NewSHA256Hasher(), + input: []byte("Idempotency test"), + expectedHex: "75bc5c31f67c5f9f3bbf2edbe30af0319b137a6128eaa9f33e006f30eeff9f5b", + }, + { + name: "MutexHasher wrapping FNVHasher idempotency", + hasher: NewMutexHasher(NewFNVHasher()), + input: []byte("Idempotency test"), + expectedHex: "7a011bc470bdd8d6", + }, + { + name: "MutexHasher wrapping SHA256Hasher idempotency", + hasher: NewMutexHasher(NewSHA256Hasher()), + input: []byte("Idempotency test"), + expectedHex: "75bc5c31f67c5f9f3bbf2edbe30af0319b137a6128eaa9f33e006f30eeff9f5b", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + hash1, err1 := tc.hasher.HashWithContext(context.Background(), tc.input) + assert.NoError(t, err1, "unexpected error on first hash") + + hash2, err2 := tc.hasher.HashWithContext(context.Background(), tc.input) + assert.NoError(t, err2, "unexpected error on second hash") + + // Verify that both hash results are identical. + if !bytes.Equal(hash1, hash2) { + t.Errorf("hash results are not identical.\nFirst: %x\nSecond: %x", hash1, hash2) + } + + // Additionally, verify against the expected hash. + expected, err := hex.DecodeString(tc.expectedHex) + assert.NoError(t, err, "invalid expected hex string") + + assert.True(t, bytes.Equal(hash1, expected), "first hash does not match expected hash") + assert.True(t, bytes.Equal(hash2, expected), "second hash does not match expected hash") + }) + } +} + +const ( + numGoroutines = 512 + numIterations = 10_000 +) + +// TestMutexHasherConcurrentHash verifies that MutexHasher is thread-safe +// and produces consistent hash results when used concurrently. +func TestMutexHasherConcurrentHash(t *testing.T) { + t.Parallel() + + mutexHasher := NewMutexHasher(NewSHA256Hasher()) + + input := []byte("Concurrent Hashing Test") + + // Compute the expected hash once for comparison. + expectedHash, err := mutexHasher.Hash(input) + assert.NoError(t, err, "unexpected error computing expected hash") + + // Channel to collect errors from goroutines. + // Buffered to prevent goroutines from blocking if the main thread is slow. + errs := make(chan error, numGoroutines*numIterations) + + var wg sync.WaitGroup + wg.Add(numGoroutines) + + // Launch multiple goroutines to perform hashing concurrently. + for i := range numGoroutines { + go func(goroutineID int) { + defer wg.Done() + for j := range numIterations { + hash, err := mutexHasher.Hash(input) + if err != nil { + errs <- fmt.Errorf("goroutine %d: hash error: %v", goroutineID, err) + continue + } + if !bytes.Equal(hash, expectedHash) { + errs <- fmt.Errorf("goroutine %d: hash mismatch on iteration %d", goroutineID, j) + } + } + }(i) + } + + wg.Wait() + close(errs) + + for err := range errs { + t.Error(err) + } +} + +// TestMutexHasherHashWithContextConcurrent verifies that MutexHasher correctly +// synchronizes access when multiple goroutines concurrently invoke HashWithContext. +func TestMutexHasherHashWithContextConcurrent(t *testing.T) { + t.Parallel() + + mutexHasher := NewMutexHasher(NewSHA256Hasher()) + + input := []byte("Concurrent Hashing with Context Test") + + // Compute the expected hash once for comparison. + expectedHash, err := mutexHasher.Hash(input) + assert.NoError(t, err, "unexpected error computing expected hash") + + // Channel to collect errors from goroutines. + // Buffered to prevent goroutines from blocking if the main thread is slow. + errs := make(chan error, numGoroutines*numIterations) + + // WaitGroup to synchronize all goroutines. + var wg sync.WaitGroup + wg.Add(numGoroutines) + + // Launch multiple goroutines to perform hashing concurrently. + for i := range numGoroutines { + go func(goroutineID int) { + defer wg.Done() + // Each goroutine uses a background context. + ctx := context.Background() + for j := range numIterations { + hash, err := mutexHasher.HashWithContext(ctx, input) + if err != nil { + errs <- fmt.Errorf("goroutine %d: hash error on iteration %d: %v", goroutineID, j, err) + continue + } + if !bytes.Equal(hash, expectedHash) { + errs <- fmt.Errorf("goroutine %d: hash mismatch on iteration %d", goroutineID, j) + } + } + }(i) + } + + wg.Wait() + close(errs) + + for err := range errs { + t.Error(err) + } +} + +// TestHasherHashWithContextCanceled ensures that HashWithContext returns promptly +// with ctx.Err() when the context is canceled before hashing. +func TestHasherHashWithContextCanceled(t *testing.T) { + t.Parallel() + + mutexHasher := NewMutexHasher(NewSHA256Hasher()) + + input := []byte("Canceled context test") + + // Create a canceled context. + ctx, cancel := context.WithCancel(context.Background()) + cancel() + + hash, err := mutexHasher.HashWithContext(ctx, input) + + assert.Error(t, err, "expected an error due to canceled context") + assert.Nil(t, hash, "expected no hash result on error") +} + +// TestHasherHashWithContextTimeout checks that HashWithContext respects context deadlines. +func TestHasherHashWithContextTimeout(t *testing.T) { + t.Parallel() + + mutexHasher := NewMutexHasher(NewSHA256Hasher()) + + input := []byte("Timeout context test") + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Microsecond) + defer cancel() + + // Allow the context to timeout. + time.Sleep(5 * time.Microsecond) + + hash, err := mutexHasher.HashWithContext(ctx, input) + + // Expect an error due to context deadline exceeded. + assert.Error(t, err, "expected an error due to context deadline exceeded") + assert.Nil(t, hash, "expected no hash result on error") +} + +// BenchmarkHasherWithMutex benchmarks hashing using a single SHA-256 Hasher instance +// protected by a sync.Mutex across multiple goroutines. +func BenchmarkHasherWithMutex_SHA256(b *testing.B) { + sampleData := []byte("The quick brown fox jumps over the lazy dog") + + mutexHasher := NewMutexHasher(NewSHA256Hasher()) + + b.ReportAllocs() + b.ResetTimer() + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _, err := mutexHasher.Hash(sampleData) + assert.NoError(b, err) + } + }) +} + +// BenchmarkHasherPerGoroutine benchmarks hashing using separate SHA-256 Hasher instances +// for each goroutine, eliminating the need for synchronization. +func BenchmarkHasherPerGoroutine_SHA256(b *testing.B) { + sampleData := []byte("The quick brown fox jumps over the lazy dog") + + b.ReportAllocs() + b.ResetTimer() + + b.RunParallel(func(pb *testing.PB) { + // Each goroutine maintains its own Hasher instance. + hasher := NewSHA256Hasher() + for pb.Next() { + _, err := hasher.Hash(sampleData) + assert.NoError(b, err) + } + }) +} + +// BenchmarkHasherWithMutex benchmarks hashing using a single FNV-64a Hasher instance +// protected by a sync.Mutex across multiple goroutines. +func BenchmarkHasherWithMutex_FNV(b *testing.B) { + sampleData := []byte("The quick brown fox jumps over the lazy dog") + + mutexHasher := NewMutexHasher(NewFNVHasher()) + + b.ReportAllocs() + b.ResetTimer() + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _, err := mutexHasher.Hash(sampleData) + assert.NoError(b, err) + } + }) +} + +// BenchmarkHasherPerGoroutine benchmarks hashing using separate FNV-64a Hasher instances +// for each goroutine, eliminating the need for synchronization. +func BenchmarkHasherPerGoroutine_FNV(b *testing.B) { + sampleData := []byte("The quick brown fox jumps over the lazy dog") + + b.ReportAllocs() + b.ResetTimer() + + b.RunParallel(func(pb *testing.PB) { + hasher := NewFNVHasher() + for pb.Next() { + _, err := hasher.Hash(sampleData) + assert.NoError(b, err) + } + }) +} diff --git a/pkg/hasher/mutex.go b/pkg/hasher/mutex.go new file mode 100644 index 000000000000..fd9bbeebb360 --- /dev/null +++ b/pkg/hasher/mutex.go @@ -0,0 +1,38 @@ +package hasher + +import ( + "sync" + + "github.com/trufflesecurity/trufflehog/v3/pkg/context" +) + +// MutexHasher wraps a Hasher with a sync.Mutex to ensure thread-safe access. +// This implementation is safe for concurrent use. +type MutexHasher struct { + hasher Hasher + mu sync.Mutex +} + +// NewMutexHasher creates a new MutexHasher wrapping the provided Hasher. +func NewMutexHasher(hasher Hasher) *MutexHasher { + return &MutexHasher{hasher: hasher} +} + +// HashWithContext synchronizes access to the underlying Hasher using a mutex and respects context cancellation. +func (m *MutexHasher) HashWithContext(ctx context.Context, data []byte) ([]byte, error) { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + m.mu.Lock() + defer m.mu.Unlock() + return m.hasher.HashWithContext(ctx, data) + } +} + +// Hash synchronizes access to the underlying Hasher using a mutex. +func (m *MutexHasher) Hash(data []byte) ([]byte, error) { + m.mu.Lock() + defer m.mu.Unlock() + return m.hasher.Hash(data) +} diff --git a/pkg/hasher/sha256.go b/pkg/hasher/sha256.go new file mode 100644 index 000000000000..501d9d21081e --- /dev/null +++ b/pkg/hasher/sha256.go @@ -0,0 +1,13 @@ +package hasher + +import "crypto/sha256" + +// SHA256Hasher implements the Hasher interface using SHA-256 algorithm. +type SHA256Hasher struct{ baseHasher } + +// NewSHA256Hasher creates a new SHA256Hasher. +func NewSHA256Hasher() *SHA256Hasher { + return &SHA256Hasher{ + baseHasher: baseHasher{hash: sha256.New()}, + } +} From f3142b9cff5b704c9bf69304cc6a12920e38121a Mon Sep 17 00:00:00 2001 From: Ahrav Dutta Date: Thu, 26 Sep 2024 09:49:43 -0700 Subject: [PATCH 2/8] update --- pkg/hasher/blake2b.go | 17 +++++++++++++++++ pkg/hasher/hasher.go | 8 ++++---- 2 files changed, 21 insertions(+), 4 deletions(-) create mode 100644 pkg/hasher/blake2b.go diff --git a/pkg/hasher/blake2b.go b/pkg/hasher/blake2b.go new file mode 100644 index 000000000000..b81a22206ec2 --- /dev/null +++ b/pkg/hasher/blake2b.go @@ -0,0 +1,17 @@ +package hasher + +import "golang.org/x/crypto/blake2b" + +// Blaker2bHasher implements the Hasher interface using Blake2b algorithm. +type Blaker2bHasher struct{ baseHasher } + +// NewBlaker2bHasher creates a new Blaker2bHasher. +func NewBlaker2bHasher() (*Blaker2bHasher, error) { + h, err := blake2b.New256(nil) + if err != nil { + return nil, err + } + return &Blaker2bHasher{ + baseHasher: baseHasher{hash: h}, + }, nil +} diff --git a/pkg/hasher/hasher.go b/pkg/hasher/hasher.go index fe55c0361e0b..5532880a82c6 100644 --- a/pkg/hasher/hasher.go +++ b/pkg/hasher/hasher.go @@ -68,10 +68,10 @@ func (b *baseHasher) Hash(data []byte) ([]byte, error) { return nil, err } b.hash.Reset() - _, err := b.hash.Write(data) - if err != nil { - return nil, err - } + // nolint:errcheck + // The hash.Hash interface does not return errors on Write. + // (https://cs.opensource.google/go/go/+/refs/tags/go1.23.1:src/hash/hash.go;l=27-28) + _, _ = b.hash.Write(data) return b.hash.Sum(nil), nil } From 2a39a5a78b006df545ddf8fca19bdbc66edd38bf Mon Sep 17 00:00:00 2001 From: Ahrav Dutta Date: Thu, 26 Sep 2024 10:04:49 -0700 Subject: [PATCH 3/8] remove --- pkg/hasher/hasher.go | 40 +-------- pkg/hasher/hasher_test.go | 165 -------------------------------------- pkg/hasher/mutex.go | 14 ---- 3 files changed, 4 insertions(+), 215 deletions(-) diff --git a/pkg/hasher/hasher.go b/pkg/hasher/hasher.go index 5532880a82c6..67a1b4ada0a7 100644 --- a/pkg/hasher/hasher.go +++ b/pkg/hasher/hasher.go @@ -2,11 +2,8 @@ package hasher import ( - "errors" "fmt" "hash" - - "github.com/trufflesecurity/trufflehog/v3/pkg/context" ) // Hasher defines a generic interface for hashing data. @@ -15,13 +12,10 @@ import ( // implementations for concurrent safety guarantees. type Hasher interface { // Hash takes input data and returns the hashed result. - // It returns an error if the input is empty or if the hashing process fails. + // It returns an error if the input data is too large. // The function is idempotent - calling it multiple times with the same input // will produce the same output, assuming the underlying hash function is deterministic. Hash(data []byte) ([]byte, error) - - // HashWithContext performs the same operation as Hash but with a context. - HashWithContext(ctx context.Context, data []byte) ([]byte, error) } // baseHasher provides a base implementation for the Hasher interface. @@ -41,31 +35,15 @@ func (e *InputTooLargeError) Error() string { return fmt.Sprintf("input data exceeds the maximum allowed size: %d > %d", e.inputSize, e.maxSize) } -// ErrEmptyData is returned when the input data is empty. -var ErrEmptyData = errors.New("data cannot be empty") - -const maxInputSize = 1 << 13 // 8 KB - -// HashWithContext computes the hash of the given data, respecting the provided context for cancellation. -func (b *baseHasher) HashWithContext(ctx context.Context, data []byte) ([]byte, error) { - if err := b.validateInput(data); err != nil { - return nil, err - } - select { - case <-ctx.Done(): - return nil, ctx.Err() - default: - return b.Hash(data) - } -} +const maxInputSize = 1 << 14 // 16KB // Hash computes the hash of the given data. // It returns an ErrEmptyData if the input is empty or if writing to the hash fails. // This method resets the underlying hash before each computation to ensure // that previous hashing operations do not affect the result. func (b *baseHasher) Hash(data []byte) ([]byte, error) { - if err := b.validateInput(data); err != nil { - return nil, err + if len(data) > maxInputSize { + return nil, &InputTooLargeError{inputSize: len(data), maxSize: maxInputSize} } b.hash.Reset() // nolint:errcheck @@ -74,13 +52,3 @@ func (b *baseHasher) Hash(data []byte) ([]byte, error) { _, _ = b.hash.Write(data) return b.hash.Sum(nil), nil } - -func (b *baseHasher) validateInput(data []byte) error { - if len(data) == 0 { - return ErrEmptyData - } - if len(data) > maxInputSize { - return &InputTooLargeError{inputSize: len(data), maxSize: maxInputSize} - } - return nil -} diff --git a/pkg/hasher/hasher_test.go b/pkg/hasher/hasher_test.go index 767c0950f526..dc72e595f739 100644 --- a/pkg/hasher/hasher_test.go +++ b/pkg/hasher/hasher_test.go @@ -7,11 +7,8 @@ import ( "fmt" "sync" "testing" - "time" "github.com/stretchr/testify/assert" - - "github.com/trufflesecurity/trufflehog/v3/pkg/context" ) func TestHasherHash(t *testing.T) { @@ -46,20 +43,6 @@ func TestHasherHash(t *testing.T) { input: bytes.Repeat([]byte("a"), maxInputSize+1), expectError: &InputTooLargeError{}, }, - { - name: "FNV-64a with empty input", - hasher: NewFNVHasher(), - input: []byte(""), - expectedHex: "", - expectError: ErrEmptyData, - }, - { - name: "SHA-256 with empty input", - hasher: NewSHA256Hasher(), - input: []byte(""), - expectedHex: "", - expectError: ErrEmptyData, - }, } for _, tc := range testCases { @@ -119,67 +102,6 @@ func TestBaseHasherHashIdempotency(t *testing.T) { } } -// TestHasherHashWithContextIdempotency confirms that multiple invocations -// of HashWithContext with the same input produce identical results. -func TestHasherHashWithContextIdempotency(t *testing.T) { - t.Parallel() - - testCases := []struct { - name string - hasher Hasher - input []byte - expectedHex string - expectError bool - }{ - { - name: "FNVHasher idempotency", - hasher: NewFNVHasher(), - input: []byte("Idempotency test"), - expectedHex: "7a011bc470bdd8d6", - }, - { - name: "SHA256Hasher idempotency", - hasher: NewSHA256Hasher(), - input: []byte("Idempotency test"), - expectedHex: "75bc5c31f67c5f9f3bbf2edbe30af0319b137a6128eaa9f33e006f30eeff9f5b", - }, - { - name: "MutexHasher wrapping FNVHasher idempotency", - hasher: NewMutexHasher(NewFNVHasher()), - input: []byte("Idempotency test"), - expectedHex: "7a011bc470bdd8d6", - }, - { - name: "MutexHasher wrapping SHA256Hasher idempotency", - hasher: NewMutexHasher(NewSHA256Hasher()), - input: []byte("Idempotency test"), - expectedHex: "75bc5c31f67c5f9f3bbf2edbe30af0319b137a6128eaa9f33e006f30eeff9f5b", - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - hash1, err1 := tc.hasher.HashWithContext(context.Background(), tc.input) - assert.NoError(t, err1, "unexpected error on first hash") - - hash2, err2 := tc.hasher.HashWithContext(context.Background(), tc.input) - assert.NoError(t, err2, "unexpected error on second hash") - - // Verify that both hash results are identical. - if !bytes.Equal(hash1, hash2) { - t.Errorf("hash results are not identical.\nFirst: %x\nSecond: %x", hash1, hash2) - } - - // Additionally, verify against the expected hash. - expected, err := hex.DecodeString(tc.expectedHex) - assert.NoError(t, err, "invalid expected hex string") - - assert.True(t, bytes.Equal(hash1, expected), "first hash does not match expected hash") - assert.True(t, bytes.Equal(hash2, expected), "second hash does not match expected hash") - }) - } -} - const ( numGoroutines = 512 numIterations = 10_000 @@ -230,93 +152,6 @@ func TestMutexHasherConcurrentHash(t *testing.T) { } } -// TestMutexHasherHashWithContextConcurrent verifies that MutexHasher correctly -// synchronizes access when multiple goroutines concurrently invoke HashWithContext. -func TestMutexHasherHashWithContextConcurrent(t *testing.T) { - t.Parallel() - - mutexHasher := NewMutexHasher(NewSHA256Hasher()) - - input := []byte("Concurrent Hashing with Context Test") - - // Compute the expected hash once for comparison. - expectedHash, err := mutexHasher.Hash(input) - assert.NoError(t, err, "unexpected error computing expected hash") - - // Channel to collect errors from goroutines. - // Buffered to prevent goroutines from blocking if the main thread is slow. - errs := make(chan error, numGoroutines*numIterations) - - // WaitGroup to synchronize all goroutines. - var wg sync.WaitGroup - wg.Add(numGoroutines) - - // Launch multiple goroutines to perform hashing concurrently. - for i := range numGoroutines { - go func(goroutineID int) { - defer wg.Done() - // Each goroutine uses a background context. - ctx := context.Background() - for j := range numIterations { - hash, err := mutexHasher.HashWithContext(ctx, input) - if err != nil { - errs <- fmt.Errorf("goroutine %d: hash error on iteration %d: %v", goroutineID, j, err) - continue - } - if !bytes.Equal(hash, expectedHash) { - errs <- fmt.Errorf("goroutine %d: hash mismatch on iteration %d", goroutineID, j) - } - } - }(i) - } - - wg.Wait() - close(errs) - - for err := range errs { - t.Error(err) - } -} - -// TestHasherHashWithContextCanceled ensures that HashWithContext returns promptly -// with ctx.Err() when the context is canceled before hashing. -func TestHasherHashWithContextCanceled(t *testing.T) { - t.Parallel() - - mutexHasher := NewMutexHasher(NewSHA256Hasher()) - - input := []byte("Canceled context test") - - // Create a canceled context. - ctx, cancel := context.WithCancel(context.Background()) - cancel() - - hash, err := mutexHasher.HashWithContext(ctx, input) - - assert.Error(t, err, "expected an error due to canceled context") - assert.Nil(t, hash, "expected no hash result on error") -} - -// TestHasherHashWithContextTimeout checks that HashWithContext respects context deadlines. -func TestHasherHashWithContextTimeout(t *testing.T) { - t.Parallel() - - mutexHasher := NewMutexHasher(NewSHA256Hasher()) - - input := []byte("Timeout context test") - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Microsecond) - defer cancel() - - // Allow the context to timeout. - time.Sleep(5 * time.Microsecond) - - hash, err := mutexHasher.HashWithContext(ctx, input) - - // Expect an error due to context deadline exceeded. - assert.Error(t, err, "expected an error due to context deadline exceeded") - assert.Nil(t, hash, "expected no hash result on error") -} - // BenchmarkHasherWithMutex benchmarks hashing using a single SHA-256 Hasher instance // protected by a sync.Mutex across multiple goroutines. func BenchmarkHasherWithMutex_SHA256(b *testing.B) { diff --git a/pkg/hasher/mutex.go b/pkg/hasher/mutex.go index fd9bbeebb360..89194a15ce91 100644 --- a/pkg/hasher/mutex.go +++ b/pkg/hasher/mutex.go @@ -2,8 +2,6 @@ package hasher import ( "sync" - - "github.com/trufflesecurity/trufflehog/v3/pkg/context" ) // MutexHasher wraps a Hasher with a sync.Mutex to ensure thread-safe access. @@ -18,18 +16,6 @@ func NewMutexHasher(hasher Hasher) *MutexHasher { return &MutexHasher{hasher: hasher} } -// HashWithContext synchronizes access to the underlying Hasher using a mutex and respects context cancellation. -func (m *MutexHasher) HashWithContext(ctx context.Context, data []byte) ([]byte, error) { - select { - case <-ctx.Done(): - return nil, ctx.Err() - default: - m.mu.Lock() - defer m.mu.Unlock() - return m.hasher.HashWithContext(ctx, data) - } -} - // Hash synchronizes access to the underlying Hasher using a mutex. func (m *MutexHasher) Hash(data []byte) ([]byte, error) { m.mu.Lock() From ab4080410b9b7c84797863f3ac8956ea09966d69 Mon Sep 17 00:00:00 2001 From: Ahrav Dutta Date: Thu, 26 Sep 2024 10:18:19 -0700 Subject: [PATCH 4/8] fix test --- pkg/hasher/hasher_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/hasher/hasher_test.go b/pkg/hasher/hasher_test.go index dc72e595f739..873a3f505cfc 100644 --- a/pkg/hasher/hasher_test.go +++ b/pkg/hasher/hasher_test.go @@ -35,7 +35,7 @@ func TestHasherHash(t *testing.T) { name: "SHA-256 input at max size", hasher: NewSHA256Hasher(), input: bytes.Repeat([]byte("a"), maxInputSize), - expectedHex: "dd4e6730520932767ec0a9e33fe19c4ce24399d6eba4ff62f13013c9ed30ef87", + expectedHex: "f3336bea752b5a28743033dd2c844a4a63fba08871aaee2586a2bf2d69be83a2", }, { name: "FN-64a input exceeds max size", From e5c62675b8827037cb2916e51ef9134f21d5868e Mon Sep 17 00:00:00 2001 From: Ahrav Dutta Date: Thu, 26 Sep 2024 10:51:26 -0700 Subject: [PATCH 5/8] update --- pkg/hasher/blake2b.go | 9 +++---- pkg/hasher/hasher_test.go | 49 +++++++++++++++++++++++++++++---------- 2 files changed, 40 insertions(+), 18 deletions(-) diff --git a/pkg/hasher/blake2b.go b/pkg/hasher/blake2b.go index b81a22206ec2..5e91fa348ccd 100644 --- a/pkg/hasher/blake2b.go +++ b/pkg/hasher/blake2b.go @@ -6,12 +6,9 @@ import "golang.org/x/crypto/blake2b" type Blaker2bHasher struct{ baseHasher } // NewBlaker2bHasher creates a new Blaker2bHasher. -func NewBlaker2bHasher() (*Blaker2bHasher, error) { - h, err := blake2b.New256(nil) - if err != nil { - return nil, err - } +func NewBlaker2bHasher() *Blaker2bHasher { + h, _ := blake2b.New256(nil) return &Blaker2bHasher{ baseHasher: baseHasher{hash: h}, - }, nil + } } diff --git a/pkg/hasher/hasher_test.go b/pkg/hasher/hasher_test.go index 873a3f505cfc..7ee413b6cfcf 100644 --- a/pkg/hasher/hasher_test.go +++ b/pkg/hasher/hasher_test.go @@ -152,11 +152,11 @@ func TestMutexHasherConcurrentHash(t *testing.T) { } } -// BenchmarkHasherWithMutex benchmarks hashing using a single SHA-256 Hasher instance +var sampleData = []byte("The quick brown fox jumps over the lazy dog") + +// BenchmarkHasherWithMutex_SHA256 benchmarks hashing using a single SHA-256 Hasher instance // protected by a sync.Mutex across multiple goroutines. func BenchmarkHasherWithMutex_SHA256(b *testing.B) { - sampleData := []byte("The quick brown fox jumps over the lazy dog") - mutexHasher := NewMutexHasher(NewSHA256Hasher()) b.ReportAllocs() @@ -170,11 +170,9 @@ func BenchmarkHasherWithMutex_SHA256(b *testing.B) { }) } -// BenchmarkHasherPerGoroutine benchmarks hashing using separate SHA-256 Hasher instances +// BenchmarkHasherPerGoroutine_SHA256 benchmarks hashing using separate SHA-256 Hasher instances // for each goroutine, eliminating the need for synchronization. func BenchmarkHasherPerGoroutine_SHA256(b *testing.B) { - sampleData := []byte("The quick brown fox jumps over the lazy dog") - b.ReportAllocs() b.ResetTimer() @@ -188,11 +186,9 @@ func BenchmarkHasherPerGoroutine_SHA256(b *testing.B) { }) } -// BenchmarkHasherWithMutex benchmarks hashing using a single FNV-64a Hasher instance +// BenchmarkHasherWithMutex_FNV benchmarks hashing using a single FNV-64a Hasher instance // protected by a sync.Mutex across multiple goroutines. func BenchmarkHasherWithMutex_FNV(b *testing.B) { - sampleData := []byte("The quick brown fox jumps over the lazy dog") - mutexHasher := NewMutexHasher(NewFNVHasher()) b.ReportAllocs() @@ -206,11 +202,9 @@ func BenchmarkHasherWithMutex_FNV(b *testing.B) { }) } -// BenchmarkHasherPerGoroutine benchmarks hashing using separate FNV-64a Hasher instances +// BenchmarkHasherPerGoroutine_FNV benchmarks hashing using separate FNV-64a Hasher instances // for each goroutine, eliminating the need for synchronization. func BenchmarkHasherPerGoroutine_FNV(b *testing.B) { - sampleData := []byte("The quick brown fox jumps over the lazy dog") - b.ReportAllocs() b.ResetTimer() @@ -222,3 +216,34 @@ func BenchmarkHasherPerGoroutine_FNV(b *testing.B) { } }) } + +// BenchmarkHasherWithMutex_Blake2b benchmarks hashing using a single Blake2b Hasher instance +// protected by a sync.Mutex across multiple goroutines. +func BenchmarkHasherWithMutex_Blake2b(b *testing.B) { + mutexHasher := NewMutexHasher(NewBlaker2bHasher()) + + b.ReportAllocs() + b.ResetTimer() + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _, err := mutexHasher.Hash(sampleData) + assert.NoError(b, err) + } + }) +} + +// BenchmarkHasherPerGoroutine_Blake2b benchmarks hashing using separate Blake2b Hasher instances +// for each goroutine, eliminating the need for synchronization. +func BenchmarkHasherPerGoroutine_Blake2b(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + + b.RunParallel(func(pb *testing.PB) { + hasher := NewBlaker2bHasher() + for pb.Next() { + _, err := hasher.Hash(sampleData) + assert.NoError(b, err) + } + }) +} From 79c64ef7dbd113177513c84d93125f92671973c6 Mon Sep 17 00:00:00 2001 From: Ahrav Dutta Date: Thu, 26 Sep 2024 18:56:34 -0700 Subject: [PATCH 6/8] remove --- pkg/hasher/fnv.go | 13 ---- pkg/hasher/hasher.go | 2 +- pkg/hasher/hasher_test.go | 124 +------------------------------------- pkg/hasher/mutex.go | 24 -------- 4 files changed, 2 insertions(+), 161 deletions(-) delete mode 100644 pkg/hasher/fnv.go delete mode 100644 pkg/hasher/mutex.go diff --git a/pkg/hasher/fnv.go b/pkg/hasher/fnv.go deleted file mode 100644 index 3a42e931cf1e..000000000000 --- a/pkg/hasher/fnv.go +++ /dev/null @@ -1,13 +0,0 @@ -package hasher - -import "hash/fnv" - -// FNVHasher implements the Hasher interface using FNV algorithm. -type FNVHasher struct{ baseHasher } - -// NewFNVHasher creates a new FNVHasher. -func NewFNVHasher() *FNVHasher { - return &FNVHasher{ - baseHasher: baseHasher{hash: fnv.New64a()}, - } -} diff --git a/pkg/hasher/hasher.go b/pkg/hasher/hasher.go index 67a1b4ada0a7..ada04383bdd0 100644 --- a/pkg/hasher/hasher.go +++ b/pkg/hasher/hasher.go @@ -38,7 +38,7 @@ func (e *InputTooLargeError) Error() string { const maxInputSize = 1 << 14 // 16KB // Hash computes the hash of the given data. -// It returns an ErrEmptyData if the input is empty or if writing to the hash fails. +// It returns an InputTooLargeError if the input data exceeds the maximum allowed size. // This method resets the underlying hash before each computation to ensure // that previous hashing operations do not affect the result. func (b *baseHasher) Hash(data []byte) ([]byte, error) { diff --git a/pkg/hasher/hasher_test.go b/pkg/hasher/hasher_test.go index 7ee413b6cfcf..1bab08ee331c 100644 --- a/pkg/hasher/hasher_test.go +++ b/pkg/hasher/hasher_test.go @@ -4,8 +4,6 @@ import ( "bytes" "encoding/hex" "errors" - "fmt" - "sync" "testing" "github.com/stretchr/testify/assert" @@ -19,12 +17,6 @@ func TestHasherHash(t *testing.T) { expectedHex string expectError error }{ - { - name: "FNV-64a with 'Hello, World!'", - hasher: NewFNVHasher(), - input: []byte("Hello, World!"), - expectedHex: "6ef05bd7cc857c54", - }, { name: "SHA-256 with 'Hello, World!'", hasher: NewSHA256Hasher(), @@ -37,12 +29,6 @@ func TestHasherHash(t *testing.T) { input: bytes.Repeat([]byte("a"), maxInputSize), expectedHex: "f3336bea752b5a28743033dd2c844a4a63fba08871aaee2586a2bf2d69be83a2", }, - { - name: "FN-64a input exceeds max size", - hasher: NewFNVHasher(), - input: bytes.Repeat([]byte("a"), maxInputSize+1), - expectError: &InputTooLargeError{}, - }, } for _, tc := range testCases { @@ -88,7 +74,7 @@ func checkError(t *testing.T, err, expectError error, inputSize int) { func TestBaseHasherHashIdempotency(t *testing.T) { t.Parallel() - hasher := NewFNVHasher() + hasher := NewSHA256Hasher() input := bytes.Repeat([]byte("a"), maxInputSize) hash1, err1 := hasher.Hash(input) @@ -107,69 +93,8 @@ const ( numIterations = 10_000 ) -// TestMutexHasherConcurrentHash verifies that MutexHasher is thread-safe -// and produces consistent hash results when used concurrently. -func TestMutexHasherConcurrentHash(t *testing.T) { - t.Parallel() - - mutexHasher := NewMutexHasher(NewSHA256Hasher()) - - input := []byte("Concurrent Hashing Test") - - // Compute the expected hash once for comparison. - expectedHash, err := mutexHasher.Hash(input) - assert.NoError(t, err, "unexpected error computing expected hash") - - // Channel to collect errors from goroutines. - // Buffered to prevent goroutines from blocking if the main thread is slow. - errs := make(chan error, numGoroutines*numIterations) - - var wg sync.WaitGroup - wg.Add(numGoroutines) - - // Launch multiple goroutines to perform hashing concurrently. - for i := range numGoroutines { - go func(goroutineID int) { - defer wg.Done() - for j := range numIterations { - hash, err := mutexHasher.Hash(input) - if err != nil { - errs <- fmt.Errorf("goroutine %d: hash error: %v", goroutineID, err) - continue - } - if !bytes.Equal(hash, expectedHash) { - errs <- fmt.Errorf("goroutine %d: hash mismatch on iteration %d", goroutineID, j) - } - } - }(i) - } - - wg.Wait() - close(errs) - - for err := range errs { - t.Error(err) - } -} - var sampleData = []byte("The quick brown fox jumps over the lazy dog") -// BenchmarkHasherWithMutex_SHA256 benchmarks hashing using a single SHA-256 Hasher instance -// protected by a sync.Mutex across multiple goroutines. -func BenchmarkHasherWithMutex_SHA256(b *testing.B) { - mutexHasher := NewMutexHasher(NewSHA256Hasher()) - - b.ReportAllocs() - b.ResetTimer() - - b.RunParallel(func(pb *testing.PB) { - for pb.Next() { - _, err := mutexHasher.Hash(sampleData) - assert.NoError(b, err) - } - }) -} - // BenchmarkHasherPerGoroutine_SHA256 benchmarks hashing using separate SHA-256 Hasher instances // for each goroutine, eliminating the need for synchronization. func BenchmarkHasherPerGoroutine_SHA256(b *testing.B) { @@ -186,53 +111,6 @@ func BenchmarkHasherPerGoroutine_SHA256(b *testing.B) { }) } -// BenchmarkHasherWithMutex_FNV benchmarks hashing using a single FNV-64a Hasher instance -// protected by a sync.Mutex across multiple goroutines. -func BenchmarkHasherWithMutex_FNV(b *testing.B) { - mutexHasher := NewMutexHasher(NewFNVHasher()) - - b.ReportAllocs() - b.ResetTimer() - - b.RunParallel(func(pb *testing.PB) { - for pb.Next() { - _, err := mutexHasher.Hash(sampleData) - assert.NoError(b, err) - } - }) -} - -// BenchmarkHasherPerGoroutine_FNV benchmarks hashing using separate FNV-64a Hasher instances -// for each goroutine, eliminating the need for synchronization. -func BenchmarkHasherPerGoroutine_FNV(b *testing.B) { - b.ReportAllocs() - b.ResetTimer() - - b.RunParallel(func(pb *testing.PB) { - hasher := NewFNVHasher() - for pb.Next() { - _, err := hasher.Hash(sampleData) - assert.NoError(b, err) - } - }) -} - -// BenchmarkHasherWithMutex_Blake2b benchmarks hashing using a single Blake2b Hasher instance -// protected by a sync.Mutex across multiple goroutines. -func BenchmarkHasherWithMutex_Blake2b(b *testing.B) { - mutexHasher := NewMutexHasher(NewBlaker2bHasher()) - - b.ReportAllocs() - b.ResetTimer() - - b.RunParallel(func(pb *testing.PB) { - for pb.Next() { - _, err := mutexHasher.Hash(sampleData) - assert.NoError(b, err) - } - }) -} - // BenchmarkHasherPerGoroutine_Blake2b benchmarks hashing using separate Blake2b Hasher instances // for each goroutine, eliminating the need for synchronization. func BenchmarkHasherPerGoroutine_Blake2b(b *testing.B) { diff --git a/pkg/hasher/mutex.go b/pkg/hasher/mutex.go deleted file mode 100644 index 89194a15ce91..000000000000 --- a/pkg/hasher/mutex.go +++ /dev/null @@ -1,24 +0,0 @@ -package hasher - -import ( - "sync" -) - -// MutexHasher wraps a Hasher with a sync.Mutex to ensure thread-safe access. -// This implementation is safe for concurrent use. -type MutexHasher struct { - hasher Hasher - mu sync.Mutex -} - -// NewMutexHasher creates a new MutexHasher wrapping the provided Hasher. -func NewMutexHasher(hasher Hasher) *MutexHasher { - return &MutexHasher{hasher: hasher} -} - -// Hash synchronizes access to the underlying Hasher using a mutex. -func (m *MutexHasher) Hash(data []byte) ([]byte, error) { - m.mu.Lock() - defer m.mu.Unlock() - return m.hasher.Hash(data) -} From c8a708cff517a522e48bd0734709d3251b2c5acb Mon Sep 17 00:00:00 2001 From: Ahrav Dutta Date: Thu, 26 Sep 2024 19:02:09 -0700 Subject: [PATCH 7/8] remove --- pkg/hasher/blake2b.go | 10 +++++----- pkg/hasher/hasher_test.go | 41 +++++++++++++-------------------------- pkg/hasher/sha256.go | 13 ------------- 3 files changed, 18 insertions(+), 46 deletions(-) delete mode 100644 pkg/hasher/sha256.go diff --git a/pkg/hasher/blake2b.go b/pkg/hasher/blake2b.go index 5e91fa348ccd..6a6b70095adc 100644 --- a/pkg/hasher/blake2b.go +++ b/pkg/hasher/blake2b.go @@ -2,13 +2,13 @@ package hasher import "golang.org/x/crypto/blake2b" -// Blaker2bHasher implements the Hasher interface using Blake2b algorithm. -type Blaker2bHasher struct{ baseHasher } +// Blake2bHasher implements the Hasher interface using Blake2b algorithm. +type Blake2bHasher struct{ baseHasher } -// NewBlaker2bHasher creates a new Blaker2bHasher. -func NewBlaker2bHasher() *Blaker2bHasher { +// NewBlaker2bHasher creates a new Blake2bHasher. +func NewBlaker2bHasher() *Blake2bHasher { h, _ := blake2b.New256(nil) - return &Blaker2bHasher{ + return &Blake2bHasher{ baseHasher: baseHasher{hash: h}, } } diff --git a/pkg/hasher/hasher_test.go b/pkg/hasher/hasher_test.go index 1bab08ee331c..9ac67b22f3ec 100644 --- a/pkg/hasher/hasher_test.go +++ b/pkg/hasher/hasher_test.go @@ -18,16 +18,22 @@ func TestHasherHash(t *testing.T) { expectError error }{ { - name: "SHA-256 with 'Hello, World!'", - hasher: NewSHA256Hasher(), + name: "Blake2b with 'Hello, World!'", + hasher: NewBlaker2bHasher(), input: []byte("Hello, World!"), - expectedHex: "dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f", + expectedHex: "511bc81dde11180838c562c82bb35f3223f46061ebde4a955c27b3f489cf1e03", }, { - name: "SHA-256 input at max size", - hasher: NewSHA256Hasher(), + name: "Blake2b input at max size", + hasher: NewBlaker2bHasher(), input: bytes.Repeat([]byte("a"), maxInputSize), - expectedHex: "f3336bea752b5a28743033dd2c844a4a63fba08871aaee2586a2bf2d69be83a2", + expectedHex: "605fd8458957df95394e9bf812f385264267c679e4899dc198ca67db4029d0ea", + }, + { + name: "Blake2b empty input", + hasher: NewBlaker2bHasher(), + input: []byte(""), + expectedHex: "0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8", }, } @@ -74,7 +80,7 @@ func checkError(t *testing.T, err, expectError error, inputSize int) { func TestBaseHasherHashIdempotency(t *testing.T) { t.Parallel() - hasher := NewSHA256Hasher() + hasher := NewBlaker2bHasher() input := bytes.Repeat([]byte("a"), maxInputSize) hash1, err1 := hasher.Hash(input) @@ -88,29 +94,8 @@ func TestBaseHasherHashIdempotency(t *testing.T) { } } -const ( - numGoroutines = 512 - numIterations = 10_000 -) - var sampleData = []byte("The quick brown fox jumps over the lazy dog") -// BenchmarkHasherPerGoroutine_SHA256 benchmarks hashing using separate SHA-256 Hasher instances -// for each goroutine, eliminating the need for synchronization. -func BenchmarkHasherPerGoroutine_SHA256(b *testing.B) { - b.ReportAllocs() - b.ResetTimer() - - b.RunParallel(func(pb *testing.PB) { - // Each goroutine maintains its own Hasher instance. - hasher := NewSHA256Hasher() - for pb.Next() { - _, err := hasher.Hash(sampleData) - assert.NoError(b, err) - } - }) -} - // BenchmarkHasherPerGoroutine_Blake2b benchmarks hashing using separate Blake2b Hasher instances // for each goroutine, eliminating the need for synchronization. func BenchmarkHasherPerGoroutine_Blake2b(b *testing.B) { diff --git a/pkg/hasher/sha256.go b/pkg/hasher/sha256.go deleted file mode 100644 index 501d9d21081e..000000000000 --- a/pkg/hasher/sha256.go +++ /dev/null @@ -1,13 +0,0 @@ -package hasher - -import "crypto/sha256" - -// SHA256Hasher implements the Hasher interface using SHA-256 algorithm. -type SHA256Hasher struct{ baseHasher } - -// NewSHA256Hasher creates a new SHA256Hasher. -func NewSHA256Hasher() *SHA256Hasher { - return &SHA256Hasher{ - baseHasher: baseHasher{hash: sha256.New()}, - } -} From 222f6b5e632965a638b770782d81058be6499beb Mon Sep 17 00:00:00 2001 From: Ahrav Dutta Date: Thu, 26 Sep 2024 19:04:00 -0700 Subject: [PATCH 8/8] fix spelling --- pkg/hasher/blake2b.go | 12 +++++------- pkg/hasher/hasher_test.go | 12 ++++++------ 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/pkg/hasher/blake2b.go b/pkg/hasher/blake2b.go index 6a6b70095adc..70e5cdafaae5 100644 --- a/pkg/hasher/blake2b.go +++ b/pkg/hasher/blake2b.go @@ -2,13 +2,11 @@ package hasher import "golang.org/x/crypto/blake2b" -// Blake2bHasher implements the Hasher interface using Blake2b algorithm. -type Blake2bHasher struct{ baseHasher } +// Blake2b implements the Hasher interface using Blake2b algorithm. +type Blake2b struct{ baseHasher } -// NewBlaker2bHasher creates a new Blake2bHasher. -func NewBlaker2bHasher() *Blake2bHasher { +// NewBlake2B creates a new Blake2b hasher. +func NewBlake2B() *Blake2b { h, _ := blake2b.New256(nil) - return &Blake2bHasher{ - baseHasher: baseHasher{hash: h}, - } + return &Blake2b{baseHasher: baseHasher{hash: h}} } diff --git a/pkg/hasher/hasher_test.go b/pkg/hasher/hasher_test.go index 9ac67b22f3ec..0b6f8dccca80 100644 --- a/pkg/hasher/hasher_test.go +++ b/pkg/hasher/hasher_test.go @@ -19,19 +19,19 @@ func TestHasherHash(t *testing.T) { }{ { name: "Blake2b with 'Hello, World!'", - hasher: NewBlaker2bHasher(), + hasher: NewBlake2B(), input: []byte("Hello, World!"), expectedHex: "511bc81dde11180838c562c82bb35f3223f46061ebde4a955c27b3f489cf1e03", }, { name: "Blake2b input at max size", - hasher: NewBlaker2bHasher(), + hasher: NewBlake2B(), input: bytes.Repeat([]byte("a"), maxInputSize), expectedHex: "605fd8458957df95394e9bf812f385264267c679e4899dc198ca67db4029d0ea", }, { name: "Blake2b empty input", - hasher: NewBlaker2bHasher(), + hasher: NewBlake2B(), input: []byte(""), expectedHex: "0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8", }, @@ -77,10 +77,10 @@ func checkError(t *testing.T, err, expectError error, inputSize int) { } } -func TestBaseHasherHashIdempotency(t *testing.T) { +func TestBlake2bHashIdempotency(t *testing.T) { t.Parallel() - hasher := NewBlaker2bHasher() + hasher := NewBlake2B() input := bytes.Repeat([]byte("a"), maxInputSize) hash1, err1 := hasher.Hash(input) @@ -103,7 +103,7 @@ func BenchmarkHasherPerGoroutine_Blake2b(b *testing.B) { b.ResetTimer() b.RunParallel(func(pb *testing.PB) { - hasher := NewBlaker2bHasher() + hasher := NewBlake2B() for pb.Next() { _, err := hasher.Hash(sampleData) assert.NoError(b, err)