From 54ee6ba0b406bd213165792336f498edaeab5c3c Mon Sep 17 00:00:00 2001 From: Alexey Potapenko Date: Thu, 23 Oct 2025 15:42:06 +0300 Subject: [PATCH] hasher: implementation for sha1/sha256 This patch adds implementation of hasher types and interfaces for sha1 and sha256 algorithms. Closes #TNTP-4170 --- .gitignore | 2 + Makefile | 12 +++++ hasher/hasher.go | 80 +++++++++++++++++++++++++++++++ hasher/hasher_test.go | 107 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 201 insertions(+) create mode 100644 hasher/hasher.go create mode 100644 hasher/hasher_test.go diff --git a/.gitignore b/.gitignore index 2d83068..36e064c 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ coverage.out +/vendor/ + diff --git a/Makefile b/Makefile index 73bf4cf..f165ddb 100644 --- a/Makefile +++ b/Makefile @@ -34,3 +34,15 @@ coveralls-deps: @echo "Installing coveralls" @go get github.com/mattn/goveralls @go install github.com/mattn/goveralls + +.PHONY: lint-deps +lint-deps: + @echo "Installing lint deps" + @go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.5.0 + +.PHONY: lint +lint: lint-deps + @echo "Running go-linter" + @go mod tidy + @go mod vendor + @golangci-lint run --config=./.golangci.yml --modules-download-mode vendor --verbose diff --git a/hasher/hasher.go b/hasher/hasher.go new file mode 100644 index 0000000..30bbd2f --- /dev/null +++ b/hasher/hasher.go @@ -0,0 +1,80 @@ +// Package hasher provides types and interfaces for hash calculating. +package hasher + +import ( + "crypto/sha1" //nolint:gosec + "crypto/sha256" + "errors" + "fmt" + "hash" +) + +// ErrDataIsNil is returned if the passed data is nil. +var ErrDataIsNil = errors.New("data is nil") + +// Hasher is the interface that storage hashers must implement. +// It provides low-level operations for hash calculating. +type Hasher interface { + Name() string + Hash(data []byte) ([]byte, error) +} + +type sha256Hasher struct { + hash hash.Hash +} + +// NewSHA256Hasher creates a new sha256Hasher instance. +func NewSHA256Hasher() Hasher { + return &sha256Hasher{ + hash: sha256.New(), + } +} + +// Name implements Hasher interface. +func (h *sha256Hasher) Name() string { + return "sha256" +} + +// Hash implements Hasher interface. +func (h *sha256Hasher) Hash(data []byte) ([]byte, error) { + if data == nil { + return nil, ErrDataIsNil + } + + n, err := h.hash.Write(data) + if n < len(data) || err != nil { + return nil, fmt.Errorf("failed to write data: %w", err) + } + + return h.hash.Sum(nil), nil +} + +type sha1Hasher struct { + hash hash.Hash +} + +// NewSHA1Hasher creates a new NewSHA1Hasher instance. +func NewSHA1Hasher() Hasher { + return &sha1Hasher{ + hash: sha1.New(), //nolint:gosec + } +} + +// Name implements Hasher interface. +func (h *sha1Hasher) Name() string { + return "sha1" +} + +// Hash implements Hasher interface. +func (h *sha1Hasher) Hash(data []byte) ([]byte, error) { + if data == nil { + return nil, ErrDataIsNil + } + + n, err := h.hash.Write(data) + if n < len(data) || err != nil { + return nil, fmt.Errorf("failed to write data: %w", err) + } + + return h.hash.Sum(nil), nil +} diff --git a/hasher/hasher_test.go b/hasher/hasher_test.go new file mode 100644 index 0000000..a334dff --- /dev/null +++ b/hasher/hasher_test.go @@ -0,0 +1,107 @@ +package hasher_test + +import ( + "encoding/hex" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/tarantool/go-storage/hasher" +) + +func TestSHA1Hasher(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + in []byte + out string + }{ + {"empty", []byte(""), "da39a3ee5e6b4b0d3255bfef95601890afd80709"}, + {"abc", []byte("abc"), "a9993e364706816aba3e25717850c26c9cd0d89d"}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + h := hasher.NewSHA1Hasher() + + result, _ := h.Hash(test.in) + + assert.Equal(t, test.out, hex.EncodeToString(result)) + }) + } +} + +func TestSHA1Hasher_negative(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + in []byte + expectedErrorText string + }{ + {"nil", nil, "data is nil"}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + h := hasher.NewSHA1Hasher() + + _, err := h.Hash(test.in) + + assert.Contains(t, err.Error(), test.expectedErrorText) + }) + } +} + +func TestSHA256Hasher(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + in []byte + out string + }{ + {"empty", []byte(""), "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}, + {"abc", []byte("abc"), "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + h := hasher.NewSHA256Hasher() + + result, _ := h.Hash(test.in) + + assert.Equal(t, test.out, hex.EncodeToString(result)) + }) + } +} + +func TestSHA256Hasher_negative(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + in []byte + expectedErrorText string + }{ + {"nil", nil, "data is nil"}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + h := hasher.NewSHA256Hasher() + + _, err := h.Hash(test.in) + + assert.Contains(t, err.Error(), test.expectedErrorText) + }) + } +}