diff --git a/pkg/object/object.go b/pkg/object/object.go index b0353c5f..c7434143 100644 --- a/pkg/object/object.go +++ b/pkg/object/object.go @@ -1,21 +1,10 @@ package object import ( - "bytes" - "crypto/ecdsa" - "crypto/sha256" - - "github.com/nspcc-dev/neofs-api-go/pkg/container" - "github.com/nspcc-dev/neofs-api-go/pkg/owner" - "github.com/nspcc-dev/neofs-api-go/util/signature" "github.com/nspcc-dev/neofs-api-go/v2/object" - "github.com/nspcc-dev/neofs-api-go/v2/refs" - signatureV2 "github.com/nspcc-dev/neofs-api-go/v2/signature" - crypto "github.com/nspcc-dev/neofs-crypto" - "github.com/pkg/errors" ) -// Object represents NeoFS object that provides +// Object represents v2-compatible NeoFS object that provides // a convenient interface for working in isolation // from the internal structure of an object. // @@ -23,198 +12,28 @@ import ( // mode as a reflection of the immutability of objects // in the system. type Object struct { - rwObject + *rwObject } -type rwObject struct { - fin bool - - id *ID - - key, sig []byte - - cid *container.ID - - ownerID *owner.ID - - payloadChecksum *refs.Checksum - - payload []byte - - // TODO: add other fields -} - -// Verify checks if object structure is correct. -func (o *Object) Verify() error { - if o == nil { - return nil - } - - hdr := o.v2Header() - - data, err := hdr.StableMarshal(nil) - if err != nil { - return errors.Wrap(err, "could not marshal header") - } - - hdrChecksum := sha256.Sum256(data) - - if !bytes.Equal(hdrChecksum[:], o.id.ToV2().GetValue()) { - return errors.New("invalid object identifier") - } - - if err := signature.VerifyDataWithSource( - signatureV2.StableMarshalerWrapper{ - SM: o.id.ToV2(), - }, - func() (key, sig []byte) { - return o.key, o.sig - }, - ); err != nil { - return errors.Wrap(err, "invalid object ID signature") - } - - return nil -} - -// GetPayload returns object payload bytes. -func (o *Object) GetPayload() []byte { - if o != nil { - return o.payload - } - - return nil -} - -// CutPayload copies object fields w/o payload. -func (o *Object) CutPayload() *Object { - if o != nil { - return &Object{ - rwObject: rwObject{ - fin: o.fin, - id: o.id, - key: o.key, - sig: o.sig, - cid: o.cid, - ownerID: o.ownerID, - payloadChecksum: o.payloadChecksum, - }, - } - } - - return nil -} - -func (o *rwObject) v2Header() *object.Header { - hV2 := new(object.Header) - hV2.SetContainerID(o.cid.ToV2()) - hV2.SetOwnerID(o.ownerID.ToV2()) - hV2.SetPayloadHash(o.payloadChecksum) - // TODO: set other fields - - return hV2 -} - -func (o *rwObject) complete(key *ecdsa.PrivateKey) (*object.Header, error) { - hdr := o.v2Header() - - hdrData, err := hdr.StableMarshal(nil) - if err != nil { - return nil, errors.Wrap(err, "could not marshal header") - } - - o.id = new(ID) - o.id.SetSHA256(sha256.Sum256(hdrData)) - - if err := signature.SignDataWithHandler( - key, - signatureV2.StableMarshalerWrapper{ - SM: o.id.ToV2(), - }, - func(key []byte, sig []byte) { - o.key, o.sig = key, sig - }, - ); err != nil { - return nil, errors.Wrap(err, "could sign object identifier") - } - - o.fin = true - - return hdr, nil -} - -// ToV2 calculates object identifier, signs structure and converts -// it to v2 Object message. -func (o *rwObject) ToV2(key *ecdsa.PrivateKey) (*object.Object, error) { - if o == nil { - return nil, nil - } - - var ( - hdr *object.Header - err error - ) - - if !o.fin { - if key == nil { - return nil, errors.Wrap(crypto.ErrEmptyPrivateKey, "could complete the object") - } - - if hdr, err = o.complete(key); err != nil { - return nil, errors.Wrapf(err, "could not complete the object") - } - } else { - hdr = o.v2Header() - } - - obj := new(object.Object) - obj.SetObjectID(o.id.ToV2()) - obj.SetHeader(hdr) - obj.SetPayload(o.payload) - - sig := new(refs.Signature) - sig.SetKey(o.key) - sig.SetSign(o.sig) - obj.SetSignature(sig) - - return obj, nil -} - -// FromV2 converts v2 Object message to Object instance. -// -// Returns any error encountered which prevented the -// recovery of object data from the message. -func FromV2(oV2 *object.Object) (*Object, error) { - if oV2 == nil { - return nil, nil - } - - hdr := oV2.GetHeader() - - // TODO: convert other fields - - sig := oV2.GetSignature() - +// NewFromV2 wraps v2 Object message to Object. +func NewFromV2(oV2 *object.Object) *Object { return &Object{ - rwObject: rwObject{ - fin: true, - id: NewIDFromV2(oV2.GetObjectID()), - key: sig.GetKey(), - sig: sig.GetSign(), - cid: container.NewIDFromV2(hdr.GetContainerID()), - ownerID: owner.NewIDFromV2(hdr.GetOwnerID()), - payloadChecksum: hdr.GetPayloadHash(), - payload: oV2.GetPayload(), - }, - }, nil + rwObject: (*rwObject)(oV2), + } } -// FromBytes restores Object from a binary representation. -func FromBytes(data []byte) (*Object, error) { - oV2 := new(object.Object) - if err := oV2.StableUnmarshal(data); err != nil { - return nil, errors.Wrap(err, "could not unmarshal object") +// New creates and initializes blank Object. +// +// Works similar as NewFromV2(new(Object)). +func New() *Object { + return NewFromV2(initObjectRecursive()) +} + +// ToV2 converts Object to v2 Object message. +func (o *Object) ToV2() *object.Object { + if o != nil { + return (*object.Object)(o.rwObject) } - return FromV2(oV2) + return nil } diff --git a/pkg/object/raw.go b/pkg/object/raw.go index ffd9f21c..18edac3b 100644 --- a/pkg/object/raw.go +++ b/pkg/object/raw.go @@ -1,45 +1,35 @@ package object import ( - "crypto/sha256" - + "github.com/nspcc-dev/neofs-api-go/pkg" "github.com/nspcc-dev/neofs-api-go/pkg/container" "github.com/nspcc-dev/neofs-api-go/pkg/owner" - "github.com/nspcc-dev/neofs-api-go/v2/refs" + "github.com/nspcc-dev/neofs-api-go/v2/object" ) -// RawObject represents NeoFS object that provides +// RawObject represents v2-compatible NeoFS object that provides // a convenient interface to fill in the fields of // an object in isolation from its internal structure. type RawObject struct { - rwObject + *rwObject } -func (o *RawObject) set(setter func()) { - o.fin = false - setter() -} - -// SetContainerID sets object's container identifier. -func (o *RawObject) SetContainerID(v *container.ID) { - if o != nil { - o.set(func() { - o.cid = v - }) +// NewRawFromV2 wraps v2 Object message to RawObject. +func NewRawFromV2(oV2 *object.Object) *RawObject { + return &RawObject{ + rwObject: (*rwObject)(oV2), } } -// SetOwnerID sets identifier of the object's owner. -func (o *RawObject) SetOwnerID(v *owner.ID) { - if o != nil { - o.set(func() { - o.ownerID = v - }) - } +// NewRaw creates and initializes blank RawObject. +// +// Works similar as NewRawFromV2(new(Object)). +func NewRaw() *RawObject { + return NewRawFromV2(initObjectRecursive()) } -// Release returns read-only Object instance. -func (o *RawObject) Release() *Object { +// Object returns read-only object instance. +func (o *RawObject) Object() *Object { if o != nil { return &Object{ rwObject: o.rwObject, @@ -49,16 +39,72 @@ func (o *RawObject) Release() *Object { return nil } -// SetPayloadChecksumSHA256 sets payload checksum as a SHA256 checksum. -func (o *RawObject) SetPayloadChecksumSHA256(v [sha256.Size]byte) { - if o != nil { - o.set(func() { - if o.payloadChecksum == nil { - o.payloadChecksum = new(refs.Checksum) - } - - o.payloadChecksum.SetType(refs.SHA256) - o.payloadChecksum.SetSum(v[:]) - }) - } +// SetID sets object identifier. +func (o *RawObject) SetID(v *ID) { + o.setID(v) +} + +// SetSignature sets signature of the object identifier. +func (o *RawObject) SetSignature(v *pkg.Signature) { + o.setSignature(v) +} + +// SetPayload sets payload bytes. +func (o *RawObject) SetPayload(v []byte) { + o.setPayload(v) +} + +// SetVersion sets version of the object. +func (o *RawObject) SetVersion(v *pkg.Version) { + o.setVersion(v) +} + +// SetPayloadSize sets payload length of the object. +func (o *RawObject) SetPayloadSize(v uint64) { + o.setPayloadSize(v) +} + +// SetContainerID sets identifier of the related container. +func (o *RawObject) SetContainerID(v *container.ID) { + o.setContainerID(v) +} + +// SetOwnerID sets identifier of the object owner. +func (o *RawObject) SetOwnerID(v *owner.ID) { + o.setOwnerID(v) +} + +// SetCreationEpoch sets epoch number in which object was created. +func (o *RawObject) SetCreationEpoch(v uint64) { + o.setCreationEpoch(v) +} + +// SetPayloadChecksum sets checksum of the object payload. +func (o *RawObject) SetPayloadChecksum(v *pkg.Checksum) { + o.setPayloadChecksum(v) +} + +// SetPayloadHomomorphicHash sets homomorphic hash of the object payload. +func (o *RawObject) SetPayloadHomomorphicHash(v *pkg.Checksum) { + o.setPayloadHomomorphicHash(v) +} + +// SetAttributes sets object attributes. +func (o *RawObject) SetAttributes(v ...*Attribute) { + o.setAttributes(v...) +} + +// SetPreviousID sets identifier of the previous sibling object. +func (o *RawObject) SetPreviousID(v *ID) { + o.setPreviousID(v) +} + +// SetChildren sets list of the identifiers of the child objects. +func (o *RawObject) SetChildren(v ...*ID) { + o.setChildren(v...) +} + +// SetParent sets parent object w/o payload. +func (o *RawObject) SetParent(v *Object) { + o.setParent(v) } diff --git a/pkg/object/raw_test.go b/pkg/object/raw_test.go new file mode 100644 index 00000000..e585d133 --- /dev/null +++ b/pkg/object/raw_test.go @@ -0,0 +1,204 @@ +package object + +import ( + "crypto/rand" + "crypto/sha256" + "testing" + + "github.com/nspcc-dev/neofs-api-go/pkg" + "github.com/nspcc-dev/neofs-api-go/pkg/container" + "github.com/nspcc-dev/neofs-api-go/pkg/owner" + "github.com/nspcc-dev/neofs-api-go/v2/object" + "github.com/stretchr/testify/require" +) + +func randID(t *testing.T) *ID { + id := NewID() + id.SetSHA256(randSHA256Checksum(t)) + + return id +} + +func randSHA256Checksum(t *testing.T) (cs [sha256.Size]byte) { + _, err := rand.Read(cs[:]) + require.NoError(t, err) + + return +} + +func randTZChecksum(t *testing.T) (cs [64]byte) { + _, err := rand.Read(cs[:]) + require.NoError(t, err) + + return +} + +func TestRawObject_SetID(t *testing.T) { + obj := NewRaw() + + id := randID(t) + + obj.SetID(id) + + require.Equal(t, id, obj.GetID()) +} + +func TestRawObject_SetSignature(t *testing.T) { + obj := NewRaw() + + sig := pkg.NewSignature() + sig.SetKey([]byte{1, 2, 3}) + sig.SetSign([]byte{4, 5, 6}) + + obj.SetSignature(sig) + + require.Equal(t, sig, obj.GetSignature()) +} + +func TestRawObject_SetPayload(t *testing.T) { + obj := NewRaw() + + payload := make([]byte, 10) + _, _ = rand.Read(payload) + + obj.SetPayload(payload) + + require.Equal(t, payload, obj.GetPayload()) +} + +func TestRawObject_SetVersion(t *testing.T) { + obj := NewRaw() + + ver := pkg.NewVersion() + ver.SetMajor(1) + ver.SetMinor(2) + + obj.SetVersion(ver) + + require.Equal(t, ver, obj.GetVersion()) +} + +func TestRawObject_SetPayloadSize(t *testing.T) { + obj := NewRaw() + + sz := uint64(133) + obj.SetPayloadSize(sz) + + require.Equal(t, sz, obj.GetPayloadSize()) +} + +func TestRawObject_SetContainerID(t *testing.T) { + obj := NewRaw() + + checksum := randSHA256Checksum(t) + + cid := container.NewID() + cid.SetSHA256(checksum) + + obj.SetContainerID(cid) + + require.Equal(t, cid, obj.GetContainerID()) +} + +func TestRawObject_SetOwnerID(t *testing.T) { + obj := NewRaw() + + w := new(owner.NEO3Wallet) + _, _ = rand.Read(w.Bytes()) + + ownerID := owner.NewID() + ownerID.SetNeo3Wallet(w) + + obj.SetOwnerID(ownerID) + + require.Equal(t, ownerID, obj.GetOwnerID()) +} + +func TestRawObject_SetCreationEpoch(t *testing.T) { + obj := NewRaw() + + creat := uint64(228) + obj.setCreationEpoch(creat) + + require.Equal(t, creat, obj.GetCreationEpoch()) +} + +func TestRawObject_SetPayloadChecksum(t *testing.T) { + obj := NewRaw() + + cs := pkg.NewChecksum() + cs.SetSHA256(randSHA256Checksum(t)) + + obj.SetPayloadChecksum(cs) + + require.Equal(t, cs, obj.GetPayloadChecksum()) +} + +func TestRawObject_SetPayloadHomomorphicHash(t *testing.T) { + obj := NewRaw() + + cs := pkg.NewChecksum() + cs.SetTillichZemor(randTZChecksum(t)) + + obj.SetPayloadHomomorphicHash(cs) + + require.Equal(t, cs, obj.GetPayloadHomomorphicHash()) +} + +func TestRawObject_SetAttributes(t *testing.T) { + obj := NewRaw() + + a1 := NewAttribute() + a1.SetKey("key1") + a1.SetValue("val1") + + a2 := NewAttribute() + a2.SetKey("key2") + a2.SetValue("val2") + + obj.SetAttributes(a1, a2) + + require.Equal(t, []*Attribute{a1, a2}, obj.GetAttributes()) +} + +func TestRawObject_SetPreviousID(t *testing.T) { + obj := NewRaw() + + prev := randID(t) + + obj.SetPreviousID(prev) + + require.Equal(t, prev, obj.GetPreviousID()) +} + +func TestRawObject_SetChildren(t *testing.T) { + obj := NewRaw() + + id1 := randID(t) + id2 := randID(t) + + obj.SetChildren(id1, id2) + + require.Equal(t, []*ID{id1, id2}, obj.GetChildren()) +} + +func TestRawObject_SetParent(t *testing.T) { + obj := NewRaw() + + par := NewRaw() + par.SetID(randID(t)) + parObj := par.Object() + + obj.SetParent(parObj) + + require.Equal(t, parObj, obj.GetParent()) +} + +func TestRawObject_ToV2(t *testing.T) { + objV2 := new(object.Object) + objV2.SetPayload([]byte{1, 2, 3}) + + obj := NewRawFromV2(objV2) + + require.Equal(t, objV2, obj.ToV2()) +} diff --git a/pkg/object/rw.go b/pkg/object/rw.go new file mode 100644 index 00000000..a9c7647e --- /dev/null +++ b/pkg/object/rw.go @@ -0,0 +1,269 @@ +package object + +import ( + "github.com/nspcc-dev/neofs-api-go/pkg" + "github.com/nspcc-dev/neofs-api-go/pkg/container" + "github.com/nspcc-dev/neofs-api-go/pkg/owner" + "github.com/nspcc-dev/neofs-api-go/v2/object" + "github.com/nspcc-dev/neofs-api-go/v2/refs" +) + +// wrapper over v2 Object that provides +// public getter and private setters. +type rwObject object.Object + +func initObjectRecursive() *object.Object { + obj := new(object.Object) + + hdr := new(object.Header) + + hdr.SetSplit(new(object.SplitHeader)) + + obj.SetHeader(hdr) + + return obj +} + +// TODO: add session token methods + +// ToV2 converts Object to v2 Object message. +func (o *rwObject) ToV2() *object.Object { + return (*object.Object)(o) +} + +// GetID returns object identifier. +func (o *rwObject) GetID() *ID { + return NewIDFromV2( + (*object.Object)(o). + GetObjectID(), + ) +} + +func (o *rwObject) setID(v *ID) { + (*object.Object)(o). + SetObjectID(v.ToV2()) +} + +// GetSignature returns signature of the object identifier. +func (o *rwObject) GetSignature() *pkg.Signature { + return pkg.NewSignatureFromV2( + (*object.Object)(o). + GetSignature(), + ) +} + +func (o *rwObject) setSignature(v *pkg.Signature) { + (*object.Object)(o). + SetSignature(v.ToV2()) +} + +// GetPayload returns payload bytes. +func (o *rwObject) GetPayload() []byte { + return (*object.Object)(o). + GetPayload() +} + +func (o *rwObject) setPayload(v []byte) { + (*object.Object)(o). + SetPayload(v) +} + +// GetVersion returns version of the object. +func (o *rwObject) GetVersion() *pkg.Version { + return pkg.NewVersionFromV2( + (*object.Object)(o). + GetHeader(). + GetVersion(), + ) +} + +func (o *rwObject) setVersion(v *pkg.Version) { + (*object.Object)(o). + GetHeader(). + SetVersion(v.ToV2()) +} + +// GetPayloadSize returns payload length of the object. +func (o *rwObject) GetPayloadSize() uint64 { + return (*object.Object)(o). + GetHeader(). + GetPayloadLength() +} + +func (o *rwObject) setPayloadSize(v uint64) { + (*object.Object)(o). + GetHeader(). + SetPayloadLength(v) +} + +// GetContainerID returns identifier of the related container. +func (o *rwObject) GetContainerID() *container.ID { + return container.NewIDFromV2( + (*object.Object)(o). + GetHeader(). + GetContainerID(), + ) +} + +func (o *rwObject) setContainerID(v *container.ID) { + (*object.Object)(o). + GetHeader(). + SetContainerID(v.ToV2()) +} + +// GetOwnerID returns identifier of the object owner. +func (o *rwObject) GetOwnerID() *owner.ID { + return owner.NewIDFromV2( + (*object.Object)(o). + GetHeader(). + GetOwnerID(), + ) +} + +func (o *rwObject) setOwnerID(v *owner.ID) { + (*object.Object)(o). + GetHeader(). + SetOwnerID(v.ToV2()) +} + +// GetCreationEpoch returns epoch number in which object was created. +func (o *rwObject) GetCreationEpoch() uint64 { + return (*object.Object)(o). + GetHeader(). + GetCreationEpoch() +} + +func (o *rwObject) setCreationEpoch(v uint64) { + (*object.Object)(o). + GetHeader(). + SetCreationEpoch(v) +} + +// GetPayloadChecksum returns checksum of the object payload. +func (o *rwObject) GetPayloadChecksum() *pkg.Checksum { + return pkg.NewChecksumFromV2( + (*object.Object)(o). + GetHeader(). + GetPayloadHash(), + ) +} + +func (o *rwObject) setPayloadChecksum(v *pkg.Checksum) { + (*object.Object)(o). + GetHeader(). + SetPayloadHash(v.ToV2()) +} + +// GetPayloadHomomorphicHash returns homomorphic hash of the object payload. +func (o *rwObject) GetPayloadHomomorphicHash() *pkg.Checksum { + return pkg.NewChecksumFromV2( + (*object.Object)(o). + GetHeader(). + GetHomomorphicHash(), + ) +} + +func (o *rwObject) setPayloadHomomorphicHash(v *pkg.Checksum) { + (*object.Object)(o). + GetHeader(). + SetHomomorphicHash(v.ToV2()) +} + +// GetAttributes returns object attributes. +func (o *rwObject) GetAttributes() []*Attribute { + attrs := (*object.Object)(o). + GetHeader(). + GetAttributes() + + res := make([]*Attribute, 0, len(attrs)) + + for i := range attrs { + res = append(res, NewAttributeFromV2(attrs[i])) + } + + return res +} + +func (o *rwObject) setAttributes(v ...*Attribute) { + h := (*object.Object)(o). + GetHeader() + + attrs := make([]*object.Attribute, 0, len(v)) + + for i := range v { + attrs = append(attrs, v[i].ToV2()) + } + + h.SetAttributes(attrs) +} + +// GetPreviousID returns identifier of the previous sibling object. +func (o *rwObject) GetPreviousID() *ID { + return NewIDFromV2( + (*object.Object)(o). + GetHeader(). + GetSplit(). + GetPrevious(), + ) +} + +func (o *rwObject) setPreviousID(v *ID) { + (*object.Object)(o). + GetHeader(). + GetSplit(). + SetPrevious(v.ToV2()) +} + +// GetChildren return list of the identifiers of the child objects. +func (o *rwObject) GetChildren() []*ID { + ids := (*object.Object)(o). + GetHeader(). + GetSplit(). + GetChildren() + + res := make([]*ID, 0, len(ids)) + + for i := range ids { + res = append(res, NewIDFromV2(ids[i])) + } + + return res +} + +func (o *rwObject) setChildren(v ...*ID) { + h := (*object.Object)(o). + GetHeader(). + GetSplit() + + ids := make([]*refs.ObjectID, 0, len(v)) + + for i := range v { + ids = append(ids, v[i].ToV2()) + } + + h.SetChildren(ids) +} + +// GetParent returns parent object w/o payload. +func (o *rwObject) GetParent() *Object { + h := (*object.Object)(o). + GetHeader(). + GetSplit() + + oV2 := new(object.Object) + oV2.SetObjectID(h.GetParent()) + oV2.SetSignature(h.GetParentSignature()) + oV2.SetHeader(h.GetParentHeader()) + + return NewFromV2(oV2) +} + +func (o *rwObject) setParent(v *Object) { + h := (*object.Object)(o). + GetHeader(). + GetSplit() + + h.SetParent((*object.Object)(v.rwObject).GetObjectID()) + h.SetParentSignature((*object.Object)(v.rwObject).GetSignature()) + h.SetParentHeader((*object.Object)(v.rwObject).GetHeader()) +}