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) }