frostfs-node/pkg/services/object/put/validation.go

146 lines
3.6 KiB
Go

package putsvc
import (
"bytes"
"context"
"crypto/sha256"
"errors"
"fmt"
"hash"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/object"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/checksum"
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/transformer"
"git.frostfs.info/TrueCloudLab/tzhash/tz"
)
// validatingTarget validates unprepared object format and content (streaming PUT case).
type validatingTarget struct {
nextTarget transformer.ChunkedObjectWriter
fmt *object.FormatValidator
}
// validatingPreparedTarget validates prepared object format and content.
type validatingPreparedTarget struct {
nextTarget transformer.ChunkedObjectWriter
fmt *object.FormatValidator
hash hash.Hash
checksum []byte
maxPayloadSz uint64 // network config
payloadSz uint64 // payload size of the streaming object from header
writtenPayload uint64 // number of already written payload bytes
}
var (
// ErrExceedingMaxSize is returned when payload size is greater than the limit.
ErrExceedingMaxSize = errors.New("payload size is greater than the limit")
// ErrWrongPayloadSize is returned when chunk payload size is greater than the length declared in header.
ErrWrongPayloadSize = errors.New("wrong payload size")
)
func (t *validatingTarget) WriteHeader(ctx context.Context, obj *objectSDK.Object) error {
if err := t.fmt.Validate(ctx, obj, true); err != nil {
return fmt.Errorf("(%T) could not validate object format: %w", t, err)
}
return t.nextTarget.WriteHeader(ctx, obj)
}
func (t *validatingTarget) Write(ctx context.Context, p []byte) (n int, err error) {
return t.nextTarget.Write(ctx, p)
}
func (t *validatingTarget) Close(ctx context.Context) (*transformer.AccessIdentifiers, error) {
return t.nextTarget.Close(ctx)
}
func (t *validatingPreparedTarget) WriteHeader(ctx context.Context, obj *objectSDK.Object) error {
t.payloadSz = obj.PayloadSize()
chunkLn := uint64(len(obj.Payload()))
// check chunk size
if chunkLn > t.payloadSz {
return ErrWrongPayloadSize
}
// check payload size limit
if t.payloadSz > t.maxPayloadSz {
return ErrExceedingMaxSize
}
cs, csSet := obj.PayloadChecksum()
if !csSet {
return errors.New("missing payload checksum")
}
switch typ := cs.Type(); typ {
default:
return fmt.Errorf("(%T) unsupported payload checksum type %v", t, typ)
case checksum.SHA256:
t.hash = sha256.New()
case checksum.TZ:
t.hash = tz.New()
}
t.checksum = cs.Value()
if err := t.fmt.Validate(ctx, obj, false); err != nil {
return fmt.Errorf("(%T) could not validate object format: %w", t, err)
}
err := t.nextTarget.WriteHeader(ctx, obj)
if err != nil {
return err
}
// update written bytes
//
// Note: we MUST NOT add obj.PayloadSize() since obj
// can carry only the chunk of the full payload
t.writtenPayload += chunkLn
return nil
}
func (t *validatingPreparedTarget) Write(ctx context.Context, p []byte) (n int, err error) {
chunkLn := uint64(len(p))
// check if new chunk will overflow payload size
if t.writtenPayload+chunkLn > t.payloadSz {
return 0, ErrWrongPayloadSize
}
_, err = t.hash.Write(p)
if err != nil {
return
}
n, err = t.nextTarget.Write(ctx, p)
if err == nil {
t.writtenPayload += uint64(n)
}
return
}
func (t *validatingPreparedTarget) Close(ctx context.Context) (*transformer.AccessIdentifiers, error) {
// check payload size correctness
if t.payloadSz != t.writtenPayload {
return nil, ErrWrongPayloadSize
}
if !bytes.Equal(t.hash.Sum(nil), t.checksum) {
return nil, fmt.Errorf("(%T) incorrect payload checksum", t)
}
return t.nextTarget.Close(ctx)
}