feat(bsr): create checksum writer
This commit is contained in:
1 parent
4f42dbf59f
commit
450da59686
6 files changed
+827
-10
No files matched your search
@@ -8,13 +8,13 @@ package checksum
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/bsr/internal/is"
|
||||
"github.com/hashicorp/go-kms-wrapping/v2/extras/crypto"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -45,9 +45,9 @@ type File struct {
|
||||
// std interfaces that to not take a context as an arg.
|
||||
ctx context.Context
|
||||
|
||||
// Performs the checksuming, provides implementations for Write and
|
||||
// WriteString
|
||||
*crypto.Sha256SumWriter
|
||||
// Performs the checksuming, provides implementations for Write,
|
||||
// WriteString, and WriteAndClose
|
||||
*Sha256SumWriter
|
||||
|
||||
checksumWriter io.Writer
|
||||
|
||||
@@ -66,7 +66,7 @@ func NewFile(ctx context.Context, f writerFile, cs io.Writer) (*File, error) {
|
||||
return nil, fmt.Errorf("%s: missing checksum writer: %w", op, errInvalidParameter)
|
||||
}
|
||||
|
||||
wrapped, err := crypto.NewSha256SumWriter(ctx, f)
|
||||
wrapped, err := NewSha256SumWriter(ctx, f, sha256.New())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -107,7 +107,7 @@ func (f *File) Close() error {
|
||||
closeErrors = errors.Join(closeErrors, fmt.Errorf("%s: %w", op, err))
|
||||
}
|
||||
|
||||
sum, err := f.Sha256SumWriter.Sum(f.ctx, crypto.WithHexEncoding(true))
|
||||
sum, err := f.Sha256SumWriter.Sum(f.ctx, WithHexEncoding(true))
|
||||
if err != nil {
|
||||
closeErrors = errors.Join(closeErrors, fmt.Errorf("%s: %w", op, err))
|
||||
return closeErrors
|
||||
@@ -133,12 +133,13 @@ func (f *File) WriteAndClose(b []byte) (int, error) {
|
||||
return 0, closeErrors
|
||||
}
|
||||
|
||||
n, err := f.underlying.WriteAndClose(b)
|
||||
// f.Sha256SumWriter will close f.underlying
|
||||
n, err := f.Sha256SumWriter.WriteAndClose(b)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("%s: %w", op, err)
|
||||
closeErrors = errors.Join(closeErrors, fmt.Errorf("%s: %w", op, err))
|
||||
}
|
||||
|
||||
sum, err := f.Sha256SumWriter.Sum(f.ctx, crypto.WithHexEncoding(true))
|
||||
sum, err := f.Sha256SumWriter.Sum(f.ctx, WithHexEncoding(true))
|
||||
if err != nil {
|
||||
closeErrors = errors.Join(closeErrors, fmt.Errorf("%s: %w", op, err))
|
||||
return 0, closeErrors
|
||||
|
||||
@@ -87,7 +87,7 @@ func TestFile(t *testing.T) {
|
||||
testString,
|
||||
testSum + " test\n",
|
||||
nil,
|
||||
errors.New("checksum.(File).Close: crypto.(Sha256SumWriter).Close: close error"),
|
||||
errors.New("checksum.(File).Close: checksum.(Sha256SumWriter).Close: close error"),
|
||||
true,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package checksum
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// Option - a type that wraps an interface for compile-time safety but can
|
||||
// contain an option for this package or for wrappers implementing this
|
||||
// interface.
|
||||
type Option func() interface{}
|
||||
|
||||
// getOpts iterates the inbound Options and returns a struct
|
||||
func getOpts(opt ...Option) (*options, error) {
|
||||
// First, separate out options into local and global
|
||||
opts := getDefaultOptions()
|
||||
for _, o := range opt {
|
||||
if o == nil {
|
||||
continue
|
||||
}
|
||||
iface := o()
|
||||
switch to := iface.(type) {
|
||||
case OptionFunc:
|
||||
if err := to(&opts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, errors.New("option passed into util wrapping options handler" +
|
||||
" that is not from this package; this is likely due to the wrapper being" +
|
||||
" invoked as a plugin but options being sent from a specific wrapper package;" +
|
||||
" use WithConfigMap to send options via the plugin interface")
|
||||
}
|
||||
|
||||
}
|
||||
return &opts, nil
|
||||
}
|
||||
|
||||
// OptionFunc holds a function with local options
|
||||
type OptionFunc func(*options) error
|
||||
|
||||
// options = how options are represented
|
||||
type options struct {
|
||||
withPrefix string
|
||||
withPrk []byte
|
||||
withEd25519 bool
|
||||
withBase64Encoding bool
|
||||
withBase58Encoding bool
|
||||
withMarshaledSigInfo bool
|
||||
withSalt []byte
|
||||
withInfo []byte
|
||||
WithHexEncoding bool
|
||||
}
|
||||
|
||||
func getDefaultOptions() options {
|
||||
return options{}
|
||||
}
|
||||
|
||||
// WithPrefix allows an optional prefix to be specified for the data returned
|
||||
func WithPrefix(prefix string) Option {
|
||||
return func() interface{} {
|
||||
return OptionFunc(func(o *options) error {
|
||||
o.withPrefix = prefix
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithPrk allows an optional PRK (pseudorandom key) to be specified for an
|
||||
// operation. If you're using this option with HmacSha256, you might consider
|
||||
// using HmacSha256WithPrk instead.
|
||||
func WithPrk(prk []byte) Option {
|
||||
return func() interface{} {
|
||||
return OptionFunc(func(o *options) error {
|
||||
o.withPrk = prk
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithEd25519 allows an optional request to use ed25519 during the operation
|
||||
func WithEd25519() Option {
|
||||
return func() interface{} {
|
||||
return OptionFunc(func(o *options) error {
|
||||
o.withEd25519 = true
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithBase64Encoding allows an optional request to base64 encode the data returned
|
||||
func WithBase64Encoding() Option {
|
||||
return func() interface{} {
|
||||
return OptionFunc(func(o *options) error {
|
||||
o.withBase64Encoding = true
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithBase58Encoding allows an optional request to base58 encode the data returned
|
||||
func WithBase58Encoding() Option {
|
||||
return func() interface{} {
|
||||
return OptionFunc(func(o *options) error {
|
||||
o.withBase58Encoding = true
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithMarshaledSigInfo allows an optional request to wrap the returned data into
|
||||
// a marshaled wrapping.SigInfo protobuf
|
||||
func WithMarshaledSigInfo() Option {
|
||||
return func() interface{} {
|
||||
return OptionFunc(func(o *options) error {
|
||||
o.withMarshaledSigInfo = true
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithSalt allows optional salt to be specified for an operation.
|
||||
func WithSalt(salt []byte) Option {
|
||||
return func() interface{} {
|
||||
return OptionFunc(func(o *options) error {
|
||||
o.withSalt = salt
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithInfo allows optional info to be specified for an operation.
|
||||
func WithInfo(info []byte) Option {
|
||||
return func() interface{} {
|
||||
return OptionFunc(func(o *options) error {
|
||||
o.withInfo = info
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithHexEncoding allows an optional request to use hex encoding.
|
||||
func WithHexEncoding(withHexEncoding bool) Option {
|
||||
return func() interface{} {
|
||||
return OptionFunc(func(o *options) error {
|
||||
o.WithHexEncoding = withHexEncoding
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package checksum
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_getOpts(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
t.Run("WithPrefix", func(t *testing.T) {
|
||||
testOpts, err := getOpts()
|
||||
require.NoError(err)
|
||||
assert.Empty(testOpts.withPrefix)
|
||||
|
||||
opts := getDefaultOptions()
|
||||
opts.withPrefix = "test"
|
||||
testOpts, err = getOpts(WithPrefix("test"))
|
||||
require.NoError(err)
|
||||
assert.EqualValues(opts, *testOpts)
|
||||
})
|
||||
t.Run("WithPrk", func(t *testing.T) {
|
||||
testOpts, err := getOpts()
|
||||
require.NoError(err)
|
||||
assert.Empty(testOpts.withPrk)
|
||||
|
||||
opts := getDefaultOptions()
|
||||
opts.withPrk = []byte("test")
|
||||
testOpts, err = getOpts(WithPrk([]byte("test")))
|
||||
require.NoError(err)
|
||||
assert.EqualValues(opts, *testOpts)
|
||||
})
|
||||
t.Run("WithEd25519", func(t *testing.T) {
|
||||
testOpts, err := getOpts()
|
||||
require.NoError(err)
|
||||
assert.False(testOpts.withEd25519)
|
||||
|
||||
opts := getDefaultOptions()
|
||||
opts.withEd25519 = true
|
||||
testOpts, err = getOpts(WithEd25519())
|
||||
require.NoError(err)
|
||||
assert.EqualValues(opts, *testOpts)
|
||||
})
|
||||
t.Run("WithBase64Encoding", func(t *testing.T) {
|
||||
testOpts, err := getOpts()
|
||||
require.NoError(err)
|
||||
assert.False(testOpts.withBase64Encoding)
|
||||
|
||||
opts := getDefaultOptions()
|
||||
opts.withBase64Encoding = true
|
||||
testOpts, err = getOpts(WithBase64Encoding())
|
||||
require.NoError(err)
|
||||
assert.EqualValues(opts, *testOpts)
|
||||
})
|
||||
t.Run("WithBase58Encoding", func(t *testing.T) {
|
||||
testOpts, err := getOpts()
|
||||
require.NoError(err)
|
||||
assert.False(testOpts.withBase58Encoding)
|
||||
|
||||
opts := getDefaultOptions()
|
||||
opts.withBase58Encoding = true
|
||||
testOpts, err = getOpts(WithBase58Encoding())
|
||||
require.NoError(err)
|
||||
assert.EqualValues(opts, *testOpts)
|
||||
})
|
||||
t.Run("WithMarshaledSigInfo", func(t *testing.T) {
|
||||
testOpts, err := getOpts()
|
||||
require.NoError(err)
|
||||
assert.False(testOpts.withMarshaledSigInfo)
|
||||
|
||||
opts := getDefaultOptions()
|
||||
opts.withMarshaledSigInfo = true
|
||||
testOpts, err = getOpts(WithMarshaledSigInfo())
|
||||
require.NoError(err)
|
||||
assert.EqualValues(opts, *testOpts)
|
||||
})
|
||||
t.Run("WithSalt", func(t *testing.T) {
|
||||
testOpts, err := getOpts()
|
||||
require.NoError(err)
|
||||
assert.Empty(testOpts.withSalt)
|
||||
|
||||
opts := getDefaultOptions()
|
||||
opts.withSalt = []byte("test")
|
||||
testOpts, err = getOpts(WithSalt([]byte("test")))
|
||||
require.NoError(err)
|
||||
assert.EqualValues(opts, *testOpts)
|
||||
})
|
||||
t.Run("WithInfo", func(t *testing.T) {
|
||||
testOpts, err := getOpts()
|
||||
require.NoError(err)
|
||||
assert.Empty(testOpts.withInfo)
|
||||
|
||||
opts := getDefaultOptions()
|
||||
opts.withInfo = []byte("test")
|
||||
testOpts, err = getOpts(WithInfo([]byte("test")))
|
||||
require.NoError(err)
|
||||
assert.EqualValues(opts, *testOpts)
|
||||
})
|
||||
t.Run("WithHexEncoding", func(t *testing.T) {
|
||||
testOpts, err := getOpts()
|
||||
require.NoError(err)
|
||||
assert.False(testOpts.WithHexEncoding)
|
||||
|
||||
opts := getDefaultOptions()
|
||||
opts.WithHexEncoding = true
|
||||
testOpts, err = getOpts(WithHexEncoding(true))
|
||||
require.NoError(err)
|
||||
assert.EqualValues(opts, *testOpts)
|
||||
})
|
||||
}
|
||||
@@ -5,11 +5,25 @@ package checksum
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/storage"
|
||||
"github.com/hashicorp/boundary/internal/util"
|
||||
)
|
||||
|
||||
// ErrShortWrite represents an error where the number of written bytes between two writers do not match
|
||||
var ErrShortWrite = errors.New("short write")
|
||||
|
||||
// ErrInvalidParameter represents an error where an invalid parameter value was provided
|
||||
var ErrInvalidParameter = errors.New("invalid parameter")
|
||||
|
||||
const (
|
||||
sha256sumSep = " "
|
||||
sha256sumBinarySep = " *"
|
||||
@@ -70,3 +84,114 @@ func (s Sha256Sums) Sum(f string) ([]byte, error) {
|
||||
|
||||
return sum, nil
|
||||
}
|
||||
|
||||
// Sha256SumWriter is used to write to a hash and produce a sum.
|
||||
// It implements io.WriterCloser, io.StringWriter and a storage.Writer.
|
||||
type Sha256SumWriter struct {
|
||||
l sync.Mutex
|
||||
hash hash.Hash
|
||||
underlying storage.Writer
|
||||
}
|
||||
|
||||
// NewSha256SumWriter creates a new Sha256SumWriter
|
||||
func NewSha256SumWriter(ctx context.Context, file storage.Writer, hash hash.Hash) (*Sha256SumWriter, error) {
|
||||
const op = "checksum.NewSha256SumWriter"
|
||||
if util.IsNil(file) {
|
||||
return nil, fmt.Errorf("%s: missing writer: %w", op, ErrInvalidParameter)
|
||||
}
|
||||
if util.IsNil(hash) {
|
||||
return nil, fmt.Errorf("%s: missing hash: %w", op, ErrInvalidParameter)
|
||||
}
|
||||
return &Sha256SumWriter{
|
||||
hash: hash,
|
||||
underlying: file,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Write will write the bytes to the hash. Implements the required io.Writer
|
||||
// func.
|
||||
func (w *Sha256SumWriter) Write(b []byte) (int, error) {
|
||||
const op = "checksum.(Sha256SumWriter).Write"
|
||||
w.l.Lock()
|
||||
defer w.l.Unlock()
|
||||
n, err := w.hash.Write(b)
|
||||
if err != nil {
|
||||
return n, fmt.Errorf("%s: %w", op, err)
|
||||
}
|
||||
m, err := w.underlying.Write(b)
|
||||
if err != nil {
|
||||
return m, fmt.Errorf("%s: %w", op, err)
|
||||
}
|
||||
if n != m {
|
||||
return m, ErrShortWrite
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// WriteString will write the string to hash.
|
||||
func (w *Sha256SumWriter) WriteString(s string) (int, error) {
|
||||
const op = "checksum.(Sha256SumWriter).WriteString"
|
||||
n, err := w.hash.Write([]byte(s))
|
||||
if err != nil {
|
||||
return n, fmt.Errorf("%s: %w", op, err)
|
||||
}
|
||||
m, err := w.underlying.Write([]byte(s))
|
||||
if err != nil {
|
||||
return m, fmt.Errorf("%s: %w", op, err)
|
||||
}
|
||||
if n != m {
|
||||
return m, ErrShortWrite
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// WriteAndClose will write the bytes to the hash and then will the Close
|
||||
func (w *Sha256SumWriter) WriteAndClose(b []byte) (int, error) {
|
||||
const op = "checksum.(Sha256SumWriter).WriteAndClose"
|
||||
w.l.Lock()
|
||||
defer w.l.Unlock()
|
||||
n, err := w.hash.Write(b)
|
||||
if err != nil {
|
||||
return n, fmt.Errorf("%s: %w", op, err)
|
||||
}
|
||||
m, err := w.underlying.WriteAndClose(b)
|
||||
if err != nil {
|
||||
return m, fmt.Errorf("%s: %w", op, err)
|
||||
}
|
||||
if n != m {
|
||||
return m, ErrShortWrite
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Close checks to see if the Sha256SumWriter implements the optional io.Closer
|
||||
// and if so, then Close() is called; otherwise this is a noop
|
||||
func (w *Sha256SumWriter) Close() error {
|
||||
const op = "checksum.(Sha256SumWriter).Close"
|
||||
var i interface{} = w.underlying
|
||||
if v, ok := i.(io.Closer); ok {
|
||||
if err := v.Close(); err != nil {
|
||||
return fmt.Errorf("%s: %w", op, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sum will sum the hash. Options supported: WithHexEncoding
|
||||
func (w *Sha256SumWriter) Sum(_ context.Context, opt ...Option) ([]byte, error) {
|
||||
const op = "checksum.(Sha256SumWriter).Sum"
|
||||
opts, err := getOpts(opt...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", op, err)
|
||||
}
|
||||
w.l.Lock()
|
||||
defer w.l.Unlock()
|
||||
h := w.hash.Sum(nil)
|
||||
switch {
|
||||
case opts.WithHexEncoding:
|
||||
encodedHex := hex.EncodeToString(h[:])
|
||||
return []byte(encodedHex), nil
|
||||
default:
|
||||
return h, nil
|
||||
}
|
||||
}
|
||||
@@ -4,16 +4,440 @@
|
||||
package checksum_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/bsr/internal/checksum"
|
||||
"github.com/hashicorp/boundary/internal/storage"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var _ storage.Writer = (*testFile)(nil)
|
||||
var _ hash.Hash = (*testHash)(nil)
|
||||
|
||||
type testFile struct {
|
||||
b strings.Builder
|
||||
closed bool
|
||||
|
||||
withWrittenBytes int
|
||||
withWriteError error
|
||||
}
|
||||
|
||||
func (t *testFile) Write(b []byte) (int, error) {
|
||||
if t.withWriteError != nil || t.withWrittenBytes > 0 {
|
||||
return t.withWrittenBytes, t.withWriteError
|
||||
}
|
||||
return t.b.Write(b)
|
||||
}
|
||||
|
||||
func (t *testFile) WriteString(s string) (int, error) {
|
||||
if t.withWriteError != nil || t.withWrittenBytes > 0 {
|
||||
return t.withWrittenBytes, t.withWriteError
|
||||
}
|
||||
return t.b.WriteString(s)
|
||||
}
|
||||
|
||||
func (t *testFile) WriteAndClose(b []byte) (int, error) {
|
||||
if t.withWriteError != nil || t.withWrittenBytes > 0 {
|
||||
return t.withWrittenBytes, t.withWriteError
|
||||
}
|
||||
t.closed = true
|
||||
return t.b.Write(b)
|
||||
}
|
||||
|
||||
func (t *testFile) Close() error {
|
||||
t.closed = true
|
||||
return nil
|
||||
}
|
||||
|
||||
type testHash struct {
|
||||
withWrittenBytes int
|
||||
withWriteError error
|
||||
}
|
||||
|
||||
func (t *testHash) Write(b []byte) (int, error) {
|
||||
return t.withWrittenBytes, t.withWriteError
|
||||
}
|
||||
|
||||
func (t *testHash) Sum(b []byte) []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *testHash) Reset() {}
|
||||
|
||||
func (t *testHash) Size() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (t *testHash) BlockSize() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func Test_NewSha256SumWriter(t *testing.T) {
|
||||
t.Parallel()
|
||||
cases := []struct {
|
||||
name string
|
||||
writer storage.Writer
|
||||
hash hash.Hash
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
name: "missing writer",
|
||||
expectedErr: "checksum.NewSha256SumWriter: missing writer: invalid parameter",
|
||||
},
|
||||
{
|
||||
name: "missing hash",
|
||||
writer: &testFile{},
|
||||
expectedErr: "checksum.NewSha256SumWriter: missing hash: invalid parameter",
|
||||
},
|
||||
{
|
||||
name: "success",
|
||||
writer: &testFile{},
|
||||
hash: sha256.New(),
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
w, err := checksum.NewSha256SumWriter(context.Background(), tc.writer, tc.hash)
|
||||
if tc.expectedErr != "" {
|
||||
require.Error(err)
|
||||
assert.Nil(w)
|
||||
assert.ErrorContains(err, tc.expectedErr)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
assert.NotNil(w)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSha256SumWriter_Write(t *testing.T) {
|
||||
t.Parallel()
|
||||
cases := []struct {
|
||||
name string
|
||||
writer *checksum.Sha256SumWriter
|
||||
expectedErr string
|
||||
data []byte
|
||||
}{
|
||||
{
|
||||
name: "with-hash-error",
|
||||
writer: func() *checksum.Sha256SumWriter {
|
||||
writer, err := checksum.NewSha256SumWriter(context.Background(), &testFile{}, &testHash{
|
||||
withWriteError: fmt.Errorf("failed hash write"),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return writer
|
||||
}(),
|
||||
data: []byte{},
|
||||
expectedErr: "failed hash write",
|
||||
},
|
||||
{
|
||||
name: "with-file-error",
|
||||
writer: func() *checksum.Sha256SumWriter {
|
||||
writer, err := checksum.NewSha256SumWriter(context.Background(), &testFile{
|
||||
withWriteError: fmt.Errorf("failed file write"),
|
||||
}, &testHash{
|
||||
withWrittenBytes: 10,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return writer
|
||||
}(),
|
||||
data: []byte{},
|
||||
expectedErr: "failed file write",
|
||||
},
|
||||
{
|
||||
name: "with-short-write-error",
|
||||
writer: func() *checksum.Sha256SumWriter {
|
||||
writer, err := checksum.NewSha256SumWriter(context.Background(), &testFile{
|
||||
withWrittenBytes: 9,
|
||||
}, &testHash{
|
||||
withWrittenBytes: 10,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return writer
|
||||
}(),
|
||||
data: []byte{},
|
||||
expectedErr: "short write",
|
||||
},
|
||||
{
|
||||
name: "success",
|
||||
writer: func() *checksum.Sha256SumWriter {
|
||||
writer, err := checksum.NewSha256SumWriter(context.Background(), &testFile{}, sha256.New())
|
||||
require.NoError(t, err)
|
||||
return writer
|
||||
}(),
|
||||
data: []byte("hello_world"),
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
_, err := tc.writer.Write(tc.data)
|
||||
if tc.expectedErr != "" {
|
||||
require.Error(err)
|
||||
assert.ErrorContains(err, tc.expectedErr)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSha256SumWriter_WriteString(t *testing.T) {
|
||||
t.Parallel()
|
||||
cases := []struct {
|
||||
name string
|
||||
writer *checksum.Sha256SumWriter
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
name: "with-hash-error",
|
||||
writer: func() *checksum.Sha256SumWriter {
|
||||
writer, err := checksum.NewSha256SumWriter(context.Background(), &testFile{}, &testHash{
|
||||
withWriteError: fmt.Errorf("failed hash write"),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return writer
|
||||
}(),
|
||||
expectedErr: "failed hash write",
|
||||
},
|
||||
{
|
||||
name: "with-file-error",
|
||||
writer: func() *checksum.Sha256SumWriter {
|
||||
writer, err := checksum.NewSha256SumWriter(context.Background(), &testFile{
|
||||
withWriteError: fmt.Errorf("failed file write"),
|
||||
}, &testHash{
|
||||
withWrittenBytes: 10,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return writer
|
||||
}(),
|
||||
expectedErr: "failed file write",
|
||||
},
|
||||
{
|
||||
name: "with-short-write-error",
|
||||
writer: func() *checksum.Sha256SumWriter {
|
||||
writer, err := checksum.NewSha256SumWriter(context.Background(), &testFile{
|
||||
withWrittenBytes: 9,
|
||||
}, &testHash{
|
||||
withWrittenBytes: 10,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return writer
|
||||
}(),
|
||||
expectedErr: "short write",
|
||||
},
|
||||
{
|
||||
name: "success",
|
||||
writer: func() *checksum.Sha256SumWriter {
|
||||
writer, err := checksum.NewSha256SumWriter(context.Background(), &testFile{
|
||||
withWrittenBytes: 10,
|
||||
}, &testHash{
|
||||
withWrittenBytes: 10,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return writer
|
||||
}(),
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
_, err := tc.writer.WriteString("")
|
||||
if tc.expectedErr != "" {
|
||||
require.Error(err)
|
||||
assert.ErrorContains(err, tc.expectedErr)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSha256SumWriter_WriteAndClose(t *testing.T) {
|
||||
t.Parallel()
|
||||
cases := []struct {
|
||||
name string
|
||||
writer *checksum.Sha256SumWriter
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
name: "with-hash-error",
|
||||
writer: func() *checksum.Sha256SumWriter {
|
||||
writer, err := checksum.NewSha256SumWriter(context.Background(), &testFile{}, &testHash{
|
||||
withWriteError: fmt.Errorf("failed hash write"),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return writer
|
||||
}(),
|
||||
expectedErr: "failed hash write",
|
||||
},
|
||||
{
|
||||
name: "with-file-error",
|
||||
writer: func() *checksum.Sha256SumWriter {
|
||||
writer, err := checksum.NewSha256SumWriter(context.Background(), &testFile{
|
||||
withWriteError: fmt.Errorf("failed file write"),
|
||||
}, &testHash{
|
||||
withWrittenBytes: 10,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return writer
|
||||
}(),
|
||||
expectedErr: "failed file write",
|
||||
},
|
||||
{
|
||||
name: "with-short-write-error",
|
||||
writer: func() *checksum.Sha256SumWriter {
|
||||
writer, err := checksum.NewSha256SumWriter(context.Background(), &testFile{
|
||||
withWrittenBytes: 9,
|
||||
}, &testHash{
|
||||
withWrittenBytes: 10,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return writer
|
||||
}(),
|
||||
expectedErr: "short write",
|
||||
},
|
||||
{
|
||||
name: "success",
|
||||
writer: func() *checksum.Sha256SumWriter {
|
||||
writer, err := checksum.NewSha256SumWriter(context.Background(), &testFile{
|
||||
withWrittenBytes: 10,
|
||||
}, &testHash{
|
||||
withWrittenBytes: 10,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return writer
|
||||
}(),
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
_, err := tc.writer.WriteAndClose([]byte{})
|
||||
if tc.expectedErr != "" {
|
||||
require.Error(err)
|
||||
assert.ErrorContains(err, tc.expectedErr)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSha256SumWriter_Sum(t *testing.T) {
|
||||
t.Parallel()
|
||||
testCtx := context.Background()
|
||||
testBytes := []byte("test-bytes")
|
||||
tests := []struct {
|
||||
name string
|
||||
data []byte
|
||||
sumWriter *checksum.Sha256SumWriter
|
||||
opt []checksum.Option
|
||||
wantSum []byte
|
||||
wantErr bool
|
||||
wantErrIs error
|
||||
wantErrContains string
|
||||
}{
|
||||
{
|
||||
name: "success",
|
||||
data: testBytes,
|
||||
sumWriter: func() *checksum.Sha256SumWriter {
|
||||
w, err := checksum.NewSha256SumWriter(testCtx, &testFile{}, sha256.New())
|
||||
require.NoError(t, err)
|
||||
return w
|
||||
}(),
|
||||
wantSum: func() []byte {
|
||||
hasher := sha256.New()
|
||||
_, err := hasher.Write(testBytes)
|
||||
require.NoError(t, err)
|
||||
_, err = hasher.Write(testBytes)
|
||||
require.NoError(t, err)
|
||||
return hasher.Sum(nil)
|
||||
}(),
|
||||
},
|
||||
{
|
||||
name: "success-with-hex-encoding",
|
||||
data: testBytes,
|
||||
sumWriter: func() *checksum.Sha256SumWriter {
|
||||
w, err := checksum.NewSha256SumWriter(testCtx, &testFile{}, sha256.New())
|
||||
require.NoError(t, err)
|
||||
return w
|
||||
}(),
|
||||
opt: []checksum.Option{checksum.WithHexEncoding(true)},
|
||||
wantSum: func() []byte {
|
||||
hasher := sha256.New()
|
||||
_, err := hasher.Write(testBytes)
|
||||
require.NoError(t, err)
|
||||
_, err = hasher.Write(testBytes)
|
||||
require.NoError(t, err)
|
||||
h := hasher.Sum(nil)
|
||||
return []byte(hex.EncodeToString(h[:]))
|
||||
}(),
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
_, err := tc.sumWriter.Write(tc.data)
|
||||
require.NoError(err)
|
||||
_, err = tc.sumWriter.WriteString(string(tc.data))
|
||||
require.NoError(err)
|
||||
sum, err := tc.sumWriter.Sum(testCtx, tc.opt...)
|
||||
if tc.wantErr {
|
||||
require.Error(err)
|
||||
assert.Empty(sum)
|
||||
if tc.wantErrIs != nil {
|
||||
assert.ErrorIs(err, tc.wantErrIs)
|
||||
}
|
||||
if tc.wantErrContains != "" {
|
||||
assert.Contains(err.Error(), tc.wantErrContains)
|
||||
}
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
assert.Equal(tc.wantSum, sum)
|
||||
require.NoError(tc.sumWriter.Close())
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("success-with-closer", func(t *testing.T) {
|
||||
c := testFile{
|
||||
b: strings.Builder{},
|
||||
closed: false,
|
||||
}
|
||||
w, err := checksum.NewSha256SumWriter(testCtx, &c, sha256.New())
|
||||
require.NoError(t, err)
|
||||
|
||||
hasher := sha256.New()
|
||||
_, err = hasher.Write(testBytes)
|
||||
require.NoError(t, err)
|
||||
_, err = hasher.Write(testBytes)
|
||||
require.NoError(t, err)
|
||||
wantSum := hasher.Sum(nil)
|
||||
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
_, err = w.Write(testBytes)
|
||||
require.NoError(err)
|
||||
_, err = w.WriteString(string(testBytes))
|
||||
require.NoError(err)
|
||||
sum, err := w.Sum(testCtx)
|
||||
require.NoError(err)
|
||||
assert.Equal(wantSum, sum)
|
||||
require.NoError(w.Close())
|
||||
|
||||
require.True(c.closed)
|
||||
})
|
||||
}
|
||||
|
||||
func TestLoadSha256Sums(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
|
||||
Reference in new issue
Block a user