diff --git a/pkg/core/object/fmt.go b/pkg/core/object/fmt.go index 483a9e6be..a8c82f967 100644 --- a/pkg/core/object/fmt.go +++ b/pkg/core/object/fmt.go @@ -61,6 +61,8 @@ var errNoExpirationEpoch = errors.New("missing expiration epoch attribute") var errTombstoneExpiration = errors.New("tombstone body and header contain different expiration values") +var errEmptySGMembers = errors.New("storage group with empty members list") + func defaultCfg() *cfg { return new(cfg) } @@ -227,6 +229,22 @@ func (v *FormatValidator) ValidateContent(o *object.Object) error { if err := sg.Unmarshal(o.Payload()); err != nil { return fmt.Errorf("(%T) could not unmarshal SG content: %w", v, err) } + + mm := sg.Members() + lenMM := len(mm) + if lenMM == 0 { + return errEmptySGMembers + } + + uniqueFilter := make(map[oid.ID]struct{}, lenMM) + + for i := 0; i < lenMM; i++ { + if _, alreadySeen := uniqueFilter[mm[i]]; alreadySeen { + return fmt.Errorf("storage group contains non-unique member: %s", mm[i]) + } + + uniqueFilter[mm[i]] = struct{}{} + } case object.TypeLock: if len(o.Payload()) == 0 { return errors.New("empty payload in lock") diff --git a/pkg/core/object/fmt_test.go b/pkg/core/object/fmt_test.go index f2fcad002..3bc9eed05 100644 --- a/pkg/core/object/fmt_test.go +++ b/pkg/core/object/fmt_test.go @@ -156,25 +156,42 @@ func TestFormatValidator_Validate(t *testing.T) { obj := object.New() obj.SetType(object.TypeStorageGroup) - require.Error(t, v.ValidateContent(obj)) + t.Run("empty payload", func(t *testing.T) { + require.Error(t, v.ValidateContent(obj)) + }) var content storagegroup.StorageGroup + content.SetExpirationEpoch(1) // some non-default value - data, err := content.Marshal() - require.NoError(t, err) + t.Run("empty members", func(t *testing.T) { + data, err := content.Marshal() + require.NoError(t, err) - obj.SetPayload(data) + obj.SetPayload(data) + require.ErrorIs(t, v.ValidateContent(obj), errEmptySGMembers) + }) - require.Error(t, v.ValidateContent(obj)) + t.Run("non-unique members", func(t *testing.T) { + id := oidtest.ID() - content.SetMembers([]oid.ID{oidtest.ID()}) + content.SetMembers([]oid.ID{id, id}) - data, err = content.Marshal() - require.NoError(t, err) + data, err := content.Marshal() + require.NoError(t, err) - obj.SetPayload(data) + obj.SetPayload(data) + require.Error(t, v.ValidateContent(obj)) + }) - require.NoError(t, v.ValidateContent(obj)) + t.Run("correct SG", func(t *testing.T) { + content.SetMembers([]oid.ID{oidtest.ID(), oidtest.ID()}) + + data, err := content.Marshal() + require.NoError(t, err) + + obj.SetPayload(data) + require.NoError(t, v.ValidateContent(obj)) + }) }) t.Run("expiration", func(t *testing.T) {