diff --git a/pkg/object/tombstone.go b/pkg/object/tombstone.go new file mode 100644 index 0000000..85fb569 --- /dev/null +++ b/pkg/object/tombstone.go @@ -0,0 +1,118 @@ +package object + +import ( + "github.com/nspcc-dev/neofs-api-go/v2/refs" + "github.com/nspcc-dev/neofs-api-go/v2/tombstone" +) + +// Tombstone represents v2-compatible tombstone structure. +type Tombstone tombstone.Tombstone + +// NewTombstoneFromV2 wraps v2 Tombstone message to Tombstone. +func NewTombstoneFromV2(tV2 *tombstone.Tombstone) *Tombstone { + return (*Tombstone)(tV2) +} + +// NewTombstone creates and initializes blank Tombstone. +func NewTombstone() *Tombstone { + return NewTombstoneFromV2(new(tombstone.Tombstone)) +} + +// ExpirationEpoch return number of tombstone expiration epoch. +func (t *Tombstone) ExpirationEpoch() uint64 { + return (*tombstone.Tombstone)(t). + GetExpirationEpoch() +} + +// SetExpirationEpoch sets number of tombstone expiration epoch. +func (t *Tombstone) SetExpirationEpoch(v uint64) { + (*tombstone.Tombstone)(t). + SetExpirationEpoch(v) +} + +// SplitID returns identifier of object split hierarchy. +func (t *Tombstone) SplitID() *SplitID { + return NewSplitIDFromV2( + (*tombstone.Tombstone)(t). + GetSplitID(), + ) +} + +// SetSplitID sets identifier of object split hierarchy. +func (t *Tombstone) SetSplitID(v *SplitID) { + (*tombstone.Tombstone)(t). + SetSplitID(v.ToV2()) +} + +// SplitID returns identifier of object split hierarchy. +func (t *Tombstone) Members() []*ID { + msV2 := (*tombstone.Tombstone)(t). + GetMembers() + + if msV2 == nil { + return nil + } + + ms := make([]*ID, 0, len(msV2)) + + for i := range msV2 { + ms = append(ms, NewIDFromV2(msV2[i])) + } + + return ms +} + +// SplitID returns identifier of object split hierarchy. +func (t *Tombstone) SetMembers(v []*ID) { + var ms []*refs.ObjectID + + if v != nil { + ms = (*tombstone.Tombstone)(t). + GetMembers() + + if ln := len(v); cap(ms) >= ln { + ms = ms[:0] + } else { + ms = make([]*refs.ObjectID, 0, ln) + } + + for i := range v { + ms = append(ms, v[i].ToV2()) + } + } + + (*tombstone.Tombstone)(t). + SetMembers(ms) +} + +// Marshal marshals Tombstone into a protobuf binary form. +// +// Buffer is allocated when the argument is empty. +// Otherwise, the first buffer is used. +func (t *Tombstone) Marshal(b ...[]byte) ([]byte, error) { + var buf []byte + if len(b) > 0 { + buf = b[0] + } + + return (*tombstone.Tombstone)(t). + StableMarshal(buf) +} + +// Unmarshal unmarshals protobuf binary representation of Tombstone. +func (t *Tombstone) Unmarshal(data []byte) error { + return (*tombstone.Tombstone)(t). + Unmarshal(data) +} + +// MarshalJSON encodes Tombstone to protobuf JSON format. +func (t *Tombstone) MarshalJSON() ([]byte, error) { + return (*tombstone.Tombstone)(t). + MarshalJSON() +} + +// UnmarshalJSON decodes Tombstone from protobuf JSON format. +func (t *Tombstone) UnmarshalJSON(data []byte) error { + return (*tombstone.Tombstone)(t). + UnmarshalJSON(data) +} diff --git a/pkg/object/tombstone_test.go b/pkg/object/tombstone_test.go new file mode 100644 index 0000000..21103ba --- /dev/null +++ b/pkg/object/tombstone_test.go @@ -0,0 +1,65 @@ +package object + +import ( + "crypto/rand" + "crypto/sha256" + "testing" + + "github.com/stretchr/testify/require" +) + +func generateIDList(sz int) []*ID { + res := make([]*ID, sz) + cs := [sha256.Size]byte{} + + for i := 0; i < sz; i++ { + res[i] = NewID() + rand.Read(cs[:]) + res[i].SetSHA256(cs) + } + + return res +} + +func TestTombstone(t *testing.T) { + ts := NewTombstone() + + exp := uint64(13) + ts.SetExpirationEpoch(exp) + require.Equal(t, exp, ts.ExpirationEpoch()) + + splitID := NewSplitID() + ts.SetSplitID(splitID) + require.Equal(t, splitID, ts.SplitID()) + + members := generateIDList(3) + ts.SetMembers(members) + require.Equal(t, members, ts.Members()) +} + +func TestTombstoneEncoding(t *testing.T) { + ts := NewTombstone() + ts.SetExpirationEpoch(13) + ts.SetSplitID(NewSplitID()) + ts.SetMembers(generateIDList(5)) + + t.Run("binary", func(t *testing.T) { + data, err := ts.Marshal() + require.NoError(t, err) + + ts2 := NewTombstone() + require.NoError(t, ts2.Unmarshal(data)) + + require.Equal(t, ts, ts2) + }) + + t.Run("json", func(t *testing.T) { + data, err := ts.MarshalJSON() + require.NoError(t, err) + + ts2 := NewTombstone() + require.NoError(t, ts2.UnmarshalJSON(data)) + + require.Equal(t, ts, ts2) + }) +}