diff --git a/storagegroup/storagegroup.go b/storagegroup/storagegroup.go new file mode 100644 index 00000000..c5ddb633 --- /dev/null +++ b/storagegroup/storagegroup.go @@ -0,0 +1,135 @@ +package storagegroup + +import ( + "github.com/nspcc-dev/neofs-api-go/v2/refs" + "github.com/nspcc-dev/neofs-api-go/v2/storagegroup" + "github.com/nspcc-dev/neofs-sdk-go/checksum" + "github.com/nspcc-dev/neofs-sdk-go/object" +) + +// StorageGroup represents v2-compatible storage group. +type StorageGroup storagegroup.StorageGroup + +// NewFromV2 wraps v2 StorageGroup message to StorageGroup. +// +// Nil storagegroup.StorageGroup converts to nil. +func NewFromV2(aV2 *storagegroup.StorageGroup) *StorageGroup { + return (*StorageGroup)(aV2) +} + +// New creates and initializes blank StorageGroup. +// +// Defaults: +// - size: 0; +// - exp: 0; +// - members: nil; +// - hash: nil. +func New() *StorageGroup { + return NewFromV2(new(storagegroup.StorageGroup)) +} + +// ValidationDataSize returns total size of the payloads +// of objects in the storage group. +func (sg *StorageGroup) ValidationDataSize() uint64 { + return (*storagegroup.StorageGroup)(sg).GetValidationDataSize() +} + +// SetValidationDataSize sets total size of the payloads +// of objects in the storage group. +func (sg *StorageGroup) SetValidationDataSize(epoch uint64) { + (*storagegroup.StorageGroup)(sg).SetValidationDataSize(epoch) +} + +// ValidationDataHash returns homomorphic hash from the +// concatenation of the payloads of the storage group members. +func (sg *StorageGroup) ValidationDataHash() *checksum.Checksum { + return checksum.NewFromV2( + (*storagegroup.StorageGroup)(sg).GetValidationHash()) +} + +// SetValidationDataHash sets homomorphic hash from the +// concatenation of the payloads of the storage group members. +func (sg *StorageGroup) SetValidationDataHash(hash *checksum.Checksum) { + (*storagegroup.StorageGroup)(sg).SetValidationHash(hash.ToV2()) +} + +// ExpirationEpoch returns last NeoFS epoch number +// of the storage group lifetime. +func (sg *StorageGroup) ExpirationEpoch() uint64 { + return (*storagegroup.StorageGroup)(sg).GetExpirationEpoch() +} + +// SetExpirationEpoch sets last NeoFS epoch number +// of the storage group lifetime. +func (sg *StorageGroup) SetExpirationEpoch(epoch uint64) { + (*storagegroup.StorageGroup)(sg).SetExpirationEpoch(epoch) +} + +// Members returns strictly ordered list of +// storage group member objects. +func (sg *StorageGroup) Members() []*object.ID { + mV2 := (*storagegroup.StorageGroup)(sg).GetMembers() + + if mV2 == nil { + return nil + } + + m := make([]*object.ID, len(mV2)) + + for i := range mV2 { + m[i] = object.NewIDFromV2(mV2[i]) + } + + return m +} + +// SetMembers sets strictly ordered list of +// storage group member objects. +func (sg *StorageGroup) SetMembers(members []*object.ID) { + mV2 := (*storagegroup.StorageGroup)(sg).GetMembers() + + if members == nil { + mV2 = nil + } else { + ln := len(members) + + if cap(mV2) >= ln { + mV2 = mV2[:0] + } else { + mV2 = make([]*refs.ObjectID, 0, ln) + } + + for i := 0; i < ln; i++ { + mV2 = append(mV2, members[i].ToV2()) + } + } + + (*storagegroup.StorageGroup)(sg).SetMembers(mV2) +} + +// ToV2 converts StorageGroup to v2 StorageGroup message. +// +// Nil StorageGroup converts to nil. +func (sg *StorageGroup) ToV2() *storagegroup.StorageGroup { + return (*storagegroup.StorageGroup)(sg) +} + +// Marshal marshals StorageGroup into a protobuf binary form. +func (sg *StorageGroup) Marshal() ([]byte, error) { + return (*storagegroup.StorageGroup)(sg).StableMarshal(nil) +} + +// Unmarshal unmarshals protobuf binary representation of StorageGroup. +func (sg *StorageGroup) Unmarshal(data []byte) error { + return (*storagegroup.StorageGroup)(sg).Unmarshal(data) +} + +// MarshalJSON encodes StorageGroup to protobuf JSON format. +func (sg *StorageGroup) MarshalJSON() ([]byte, error) { + return (*storagegroup.StorageGroup)(sg).MarshalJSON() +} + +// UnmarshalJSON decodes StorageGroup from protobuf JSON format. +func (sg *StorageGroup) UnmarshalJSON(data []byte) error { + return (*storagegroup.StorageGroup)(sg).UnmarshalJSON(data) +} diff --git a/storagegroup/storagegroup_test.go b/storagegroup/storagegroup_test.go new file mode 100644 index 00000000..b49621c3 --- /dev/null +++ b/storagegroup/storagegroup_test.go @@ -0,0 +1,93 @@ +package storagegroup_test + +import ( + "testing" + + storagegroupV2 "github.com/nspcc-dev/neofs-api-go/v2/storagegroup" + checksumtest "github.com/nspcc-dev/neofs-sdk-go/checksum/test" + "github.com/nspcc-dev/neofs-sdk-go/object" + objecttest "github.com/nspcc-dev/neofs-sdk-go/object/test" + "github.com/nspcc-dev/neofs-sdk-go/storagegroup" + storagegrouptest "github.com/nspcc-dev/neofs-sdk-go/storagegroup/test" + "github.com/stretchr/testify/require" +) + +func TestStorageGroup(t *testing.T) { + sg := storagegroup.New() + + sz := uint64(13) + sg.SetValidationDataSize(sz) + require.Equal(t, sz, sg.ValidationDataSize()) + + cs := checksumtest.Checksum() + sg.SetValidationDataHash(cs) + require.Equal(t, cs, sg.ValidationDataHash()) + + exp := uint64(33) + sg.SetExpirationEpoch(exp) + require.Equal(t, exp, sg.ExpirationEpoch()) + + members := []*object.ID{objecttest.ID(), objecttest.ID()} + sg.SetMembers(members) + require.Equal(t, members, sg.Members()) +} + +func TestStorageGroupEncoding(t *testing.T) { + sg := storagegrouptest.Generate() + + t.Run("binary", func(t *testing.T) { + data, err := sg.Marshal() + require.NoError(t, err) + + sg2 := storagegroup.New() + require.NoError(t, sg2.Unmarshal(data)) + + require.Equal(t, sg, sg2) + }) + + t.Run("json", func(t *testing.T) { + data, err := sg.MarshalJSON() + require.NoError(t, err) + + sg2 := storagegroup.New() + require.NoError(t, sg2.UnmarshalJSON(data)) + + require.Equal(t, sg, sg2) + }) +} + +func TestNewFromV2(t *testing.T) { + t.Run("from nil", func(t *testing.T) { + var x *storagegroupV2.StorageGroup + + require.Nil(t, storagegroup.NewFromV2(x)) + }) +} + +func TestStorageGroup_ToV2(t *testing.T) { + t.Run("nil", func(t *testing.T) { + var x *storagegroup.StorageGroup + + require.Nil(t, x.ToV2()) + }) +} + +func TestNew(t *testing.T) { + t.Run("default values", func(t *testing.T) { + sg := storagegroup.New() + + // check initial values + require.Nil(t, sg.Members()) + require.Nil(t, sg.ValidationDataHash()) + require.Zero(t, sg.ExpirationEpoch()) + require.Zero(t, sg.ValidationDataSize()) + + // convert to v2 message + sgV2 := sg.ToV2() + + require.Nil(t, sgV2.GetMembers()) + require.Nil(t, sgV2.GetValidationHash()) + require.Zero(t, sgV2.GetExpirationEpoch()) + require.Zero(t, sgV2.GetValidationDataSize()) + }) +} diff --git a/storagegroup/test/generate.go b/storagegroup/test/generate.go new file mode 100644 index 00000000..ad572d83 --- /dev/null +++ b/storagegroup/test/generate.go @@ -0,0 +1,20 @@ +package storagegrouptest + +import ( + checksumtest "github.com/nspcc-dev/neofs-sdk-go/checksum/test" + "github.com/nspcc-dev/neofs-sdk-go/object" + objecttest "github.com/nspcc-dev/neofs-sdk-go/object/test" + "github.com/nspcc-dev/neofs-sdk-go/storagegroup" +) + +// Generate returns random storagegroup.StorageGroup. +func Generate() *storagegroup.StorageGroup { + x := storagegroup.New() + + x.SetExpirationEpoch(66) + x.SetValidationDataSize(322) + x.SetValidationDataHash(checksumtest.Checksum()) + x.SetMembers([]*object.ID{objecttest.ID(), objecttest.ID()}) + + return x +}