[#140] sdk/object: Refactor object types

Signed-off-by: Leonard Lyubich <leonard@nspcc.ru>
This commit is contained in:
Leonard Lyubich 2020-09-10 16:11:12 +03:00 committed by Stanislav Bogatyrev
parent e40995b5d4
commit 6ce70d4a18
4 changed files with 574 additions and 236 deletions

View file

@ -1,21 +1,10 @@
package object package object
import ( 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/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 // a convenient interface for working in isolation
// from the internal structure of an object. // from the internal structure of an object.
// //
@ -23,198 +12,28 @@ import (
// mode as a reflection of the immutability of objects // mode as a reflection of the immutability of objects
// in the system. // in the system.
type Object struct { type Object struct {
rwObject *rwObject
} }
type rwObject struct { // NewFromV2 wraps v2 Object message to Object.
fin bool func NewFromV2(oV2 *object.Object) *Object {
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()
return &Object{ return &Object{
rwObject: rwObject{ rwObject: (*rwObject)(oV2),
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
} }
// FromBytes restores Object from a binary representation. // New creates and initializes blank Object.
func FromBytes(data []byte) (*Object, error) { //
oV2 := new(object.Object) // Works similar as NewFromV2(new(Object)).
if err := oV2.StableUnmarshal(data); err != nil { func New() *Object {
return nil, errors.Wrap(err, "could not unmarshal 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
} }

View file

@ -1,45 +1,35 @@
package object package object
import ( 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/container"
"github.com/nspcc-dev/neofs-api-go/pkg/owner" "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 // a convenient interface to fill in the fields of
// an object in isolation from its internal structure. // an object in isolation from its internal structure.
type RawObject struct { type RawObject struct {
rwObject *rwObject
} }
func (o *RawObject) set(setter func()) { // NewRawFromV2 wraps v2 Object message to RawObject.
o.fin = false func NewRawFromV2(oV2 *object.Object) *RawObject {
setter() return &RawObject{
} rwObject: (*rwObject)(oV2),
// SetContainerID sets object's container identifier.
func (o *RawObject) SetContainerID(v *container.ID) {
if o != nil {
o.set(func() {
o.cid = v
})
} }
} }
// SetOwnerID sets identifier of the object's owner. // NewRaw creates and initializes blank RawObject.
func (o *RawObject) SetOwnerID(v *owner.ID) { //
if o != nil { // Works similar as NewRawFromV2(new(Object)).
o.set(func() { func NewRaw() *RawObject {
o.ownerID = v return NewRawFromV2(initObjectRecursive())
})
}
} }
// Release returns read-only Object instance. // Object returns read-only object instance.
func (o *RawObject) Release() *Object { func (o *RawObject) Object() *Object {
if o != nil { if o != nil {
return &Object{ return &Object{
rwObject: o.rwObject, rwObject: o.rwObject,
@ -49,16 +39,72 @@ func (o *RawObject) Release() *Object {
return nil return nil
} }
// SetPayloadChecksumSHA256 sets payload checksum as a SHA256 checksum. // SetID sets object identifier.
func (o *RawObject) SetPayloadChecksumSHA256(v [sha256.Size]byte) { func (o *RawObject) SetID(v *ID) {
if o != nil { o.setID(v)
o.set(func() { }
if o.payloadChecksum == nil {
o.payloadChecksum = new(refs.Checksum) // SetSignature sets signature of the object identifier.
} func (o *RawObject) SetSignature(v *pkg.Signature) {
o.setSignature(v)
o.payloadChecksum.SetType(refs.SHA256) }
o.payloadChecksum.SetSum(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)
} }

204
pkg/object/raw_test.go Normal file
View file

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

269
pkg/object/rw.go Normal file
View file

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