diff --git a/link/grpc/types.go b/link/grpc/types.go new file mode 100644 index 00000000..ce8f949f --- /dev/null +++ b/link/grpc/types.go @@ -0,0 +1,18 @@ +package link + +import grpc "github.com/nspcc-dev/neofs-api-go/v2/refs/grpc" + +// SetId sets object ID. +func (x *Link_MeasuredObject) SetId(v *grpc.ObjectID) { + x.Id = v +} + +// SetSize sets object size. +func (x *Link_MeasuredObject) SetSize(v uint32) { + x.Size = v +} + +// SetChildren sets object's children. +func (x *Link) SetChildren(v []*Link_MeasuredObject) { + x.Children = v +} diff --git a/object/convert.go b/object/convert.go index c4b08685..fcf7f81e 100644 --- a/object/convert.go +++ b/object/convert.go @@ -183,6 +183,7 @@ func (h *SplitHeader) ToGRPCMessage() grpc.Message { m.SetParentSignature(h.parSig.ToGRPCMessage().(*refsGRPC.Signature)) m.SetChildren(refs.ObjectIDListToGRPCMessage(h.children)) m.SetSplitId(h.splitID) + m.SetFirst(h.first.ToGRPCMessage().(*refsGRPC.ObjectID)) } return m @@ -224,6 +225,20 @@ func (h *SplitHeader) FromGRPCMessage(m grpc.Message) error { } } + first := v.GetFirst() + if first == nil { + h.first = nil + } else { + if h.first == nil { + h.first = new(refs.ObjectID) + } + + err = h.first.FromGRPCMessage(first) + if err != nil { + return err + } + } + parHdr := v.GetParentHeader() if parHdr == nil { h.parHdr = nil @@ -529,6 +544,7 @@ func (s *SplitInfo) ToGRPCMessage() grpc.Message { m.SetLastPart(s.lastPart.ToGRPCMessage().(*refsGRPC.ObjectID)) m.SetLink(s.link.ToGRPCMessage().(*refsGRPC.ObjectID)) + m.SetFirstPart(s.firstPart.ToGRPCMessage().(*refsGRPC.ObjectID)) m.SetSplitId(s.splitID) } @@ -571,6 +587,20 @@ func (s *SplitInfo) FromGRPCMessage(m grpc.Message) error { } } + first := v.GetFirstPart() + if first == nil { + s.firstPart = nil + } else { + if s.firstPart == nil { + s.firstPart = new(refs.ObjectID) + } + + err = s.firstPart.FromGRPCMessage(first) + if err != nil { + return err + } + } + s.splitID = v.GetSplitId() return nil diff --git a/object/grpc/types.go b/object/grpc/types.go index 10e063a8..86079902 100644 --- a/object/grpc/types.go +++ b/object/grpc/types.go @@ -45,6 +45,10 @@ func (m *Header_Split) SetSplitId(v []byte) { m.SplitId = v } +func (m *Header_Split) SetFirst(v *refs.ObjectID) { + m.First = v +} + // SetContainerId sets identifier of the container. func (m *Header) SetContainerId(v *refs.ContainerID) { m.ContainerId = v @@ -170,6 +174,11 @@ func (m *SplitInfo) SetLink(v *refs.ObjectID) { m.Link = v } +// SetFirstPart sets id of initializing object in split hierarchy. +func (m *SplitInfo) SetFirstPart(v *refs.ObjectID) { + m.FirstPart = v +} + // FromString parses ObjectType from a string representation, // It is a reverse action to String(). // diff --git a/object/link.go b/object/link.go new file mode 100644 index 00000000..f370d697 --- /dev/null +++ b/object/link.go @@ -0,0 +1,230 @@ +package object + +import ( + "errors" + "fmt" + + link "github.com/nspcc-dev/neofs-api-go/v2/link/grpc" + "github.com/nspcc-dev/neofs-api-go/v2/refs" + refsGRPC "github.com/nspcc-dev/neofs-api-go/v2/refs/grpc" + "github.com/nspcc-dev/neofs-api-go/v2/rpc/grpc" + "github.com/nspcc-dev/neofs-api-go/v2/rpc/message" + "github.com/nspcc-dev/neofs-api-go/v2/util/proto" +) + +// Link represents object Link message from NeoFS API V2 protocol. +type Link struct { + children []MeasuredObject +} + +// NumberOfChildren returns length of children list. +func (x *Link) NumberOfChildren() int { + if x != nil { + return len(x.children) + } + + return 0 +} + +// IterateChildren passes members of the link list to f. +func (x *Link) IterateChildren(f func(MeasuredObject)) { + if x != nil { + for i := range x.children { + f(x.children[i]) + } + } +} + +// SetChildren sets a list of the child objects. +// Arg must not be mutated for the duration of the Link. +func (x *Link) SetChildren(chs []MeasuredObject) { + x.children = chs +} + +const ( + _ = iota + linkFNumChildren +) + +const ( + _ = iota + measuredObjectFNumID + measuredObjectFNumSize +) + +// StableMarshal encodes the Link into Protocol Buffers binary format +// with direct field order. +func (x *Link) StableMarshal(buf []byte) []byte { + if x == nil || len(x.children) == 0 { + return []byte{} + } + + if buf == nil { + buf = make([]byte, x.StableSize()) + } + + var offset int + + for i := range x.children { + offset += proto.NestedStructureMarshal(linkFNumChildren, buf[offset:], &x.children[i]) + } + + return buf +} + +// StableSize size of the buffer required to write the Link in Protocol Buffers +// binary format. +func (x *Link) StableSize() int { + var size int + + if x != nil { + for i := range x.children { + size += proto.NestedStructureSize(linkFNumChildren, &x.children[i]) + } + } + + return size +} + +// Unmarshal decodes the Link from its Protocol Buffers binary format. +func (x *Link) Unmarshal(data []byte) error { + return message.Unmarshal(x, data, new(link.Link)) +} + +func (x *Link) ToGRPCMessage() grpc.Message { + var m *link.Link + + if x != nil { + m = new(link.Link) + + var children []*link.Link_MeasuredObject + + if x.children != nil { + children = make([]*link.Link_MeasuredObject, len(x.children)) + + for i := range x.children { + children[i] = x.children[i].ToGRPCMessage().(*link.Link_MeasuredObject) + } + } + + m.Children = children + } + + return m +} + +func (x *Link) FromGRPCMessage(m grpc.Message) error { + v, ok := m.(*link.Link) + if !ok { + return message.NewUnexpectedMessageType(m, v) + } + + children := v.GetChildren() + if children == nil { + x.children = nil + } else { + x.children = make([]MeasuredObject, len(children)) + var err error + + for i := range x.children { + err = x.children[i].FromGRPCMessage(children[i]) + if err != nil { + return err + } + } + } + + return nil +} + +// MeasuredObject groups object descriptor and object's length. +type MeasuredObject struct { + ID refs.ObjectID + Size uint32 +} + +// StableSize size of the buffer required to write the MeasuredObject in Protocol Buffers +// binary format. +func (x *MeasuredObject) StableSize() int { + var size int + + size += proto.NestedStructureSize(measuredObjectFNumID, &x.ID) + size += proto.UInt32Size(measuredObjectFNumSize, x.Size) + + return size +} + +// StableMarshal encodes the MeasuredObject into Protocol Buffers binary format +// with direct field order. +func (x *MeasuredObject) StableMarshal(buf []byte) []byte { + if buf == nil { + buf = make([]byte, x.StableSize()) + } + + var offset int + + offset += proto.NestedStructureMarshal(measuredObjectFNumID, buf[offset:], &x.ID) + proto.UInt32Marshal(measuredObjectFNumSize, buf[offset:], x.Size) + + return buf +} + +// Unmarshal decodes the Link from its Protocol Buffers binary format. +func (x *MeasuredObject) Unmarshal(data []byte) error { + return message.Unmarshal(x, data, new(link.Link_MeasuredObject)) +} + +func (x *MeasuredObject) FromGRPCMessage(m grpc.Message) error { + v, ok := m.(*link.Link_MeasuredObject) + if !ok { + return message.NewUnexpectedMessageType(m, v) + } + + if v.Id != nil { + err := x.ID.FromGRPCMessage(v.Id) + if err != nil { + return err + } + } + + x.Size = v.Size + + return nil +} + +func (x *MeasuredObject) ToGRPCMessage() grpc.Message { + m := new(link.Link_MeasuredObject) + + m.Id = x.ID.ToGRPCMessage().(*refsGRPC.ObjectID) + m.Size = x.Size + + return m +} + +// WriteLink writes Link to the Object as a payload content. +// The object must not be nil. +func WriteLink(obj *Object, link Link) { + hdr := obj.GetHeader() + if hdr == nil { + hdr = new(Header) + obj.SetHeader(hdr) + } + + payload := link.StableMarshal(nil) + obj.SetPayload(payload) +} + +// ReadLink reads Link from the Object payload content. +func ReadLink(link *Link, obj Object) error { + payload := obj.GetPayload() + if len(payload) == 0 { + return errors.New("empty payload") + } + + err := link.Unmarshal(payload) + if err != nil { + return fmt.Errorf("decode link content from payload: %w", err) + } + + return nil +} diff --git a/object/link_test.go b/object/link_test.go new file mode 100644 index 00000000..d71ba259 --- /dev/null +++ b/object/link_test.go @@ -0,0 +1,26 @@ +package object_test + +import ( + "testing" + + "github.com/nspcc-dev/neofs-api-go/v2/object" + objecttest "github.com/nspcc-dev/neofs-api-go/v2/object/test" + "github.com/stretchr/testify/require" +) + +func TestLinkRW(t *testing.T) { + var l object.Link + var obj object.Object + + require.Error(t, object.ReadLink(&l, obj)) + + l = *objecttest.GenerateLink(false) + + object.WriteLink(&obj, l) + + var l2 object.Link + + require.NoError(t, object.ReadLink(&l2, obj)) + + require.Equal(t, l, l2) +} diff --git a/object/marshal.go b/object/marshal.go index 03ba265a..e32164e6 100644 --- a/object/marshal.go +++ b/object/marshal.go @@ -25,6 +25,7 @@ const ( splitHdrParentHeaderField = 4 splitHdrChildrenField = 5 splitHdrSplitIDField = 6 + splitHdrFirstIDField = 7 hdrVersionField = 1 hdrContainerIDField = 2 @@ -49,6 +50,7 @@ const ( splitInfoSplitIDField = 1 splitInfoLastPartField = 2 splitInfoLinkField = 3 + splitInfoFirstField = 4 getReqBodyAddressField = 1 getReqBodyRawFlagField = 2 @@ -202,7 +204,8 @@ func (h *SplitHeader) StableMarshal(buf []byte) []byte { offset += proto.NestedStructureMarshal(splitHdrParentSignatureField, buf[offset:], h.parSig) offset += proto.NestedStructureMarshal(splitHdrParentHeaderField, buf[offset:], h.parHdr) offset += refs.ObjectIDNestedListMarshal(splitHdrChildrenField, buf[offset:], h.children) - proto.BytesMarshal(splitHdrSplitIDField, buf[offset:], h.splitID) + offset += proto.BytesMarshal(splitHdrSplitIDField, buf[offset:], h.splitID) + proto.NestedStructureMarshal(splitHdrFirstIDField, buf[offset:], h.first) return buf } @@ -218,6 +221,7 @@ func (h *SplitHeader) StableSize() (size int) { size += proto.NestedStructureSize(splitHdrParentHeaderField, h.parHdr) size += refs.ObjectIDNestedListSize(splitHdrChildrenField, h.children) size += proto.BytesSize(splitHdrSplitIDField, h.splitID) + size += proto.NestedStructureSize(splitHdrFirstIDField, h.first) return size } @@ -363,7 +367,8 @@ func (s *SplitInfo) StableMarshal(buf []byte) []byte { offset += proto.BytesMarshal(splitInfoSplitIDField, buf[offset:], s.splitID) offset += proto.NestedStructureMarshal(splitInfoLastPartField, buf[offset:], s.lastPart) - proto.NestedStructureMarshal(splitInfoLinkField, buf[offset:], s.link) + offset += proto.NestedStructureMarshal(splitInfoLinkField, buf[offset:], s.link) + proto.NestedStructureMarshal(splitInfoFirstField, buf[offset:], s.firstPart) return buf } @@ -376,6 +381,7 @@ func (s *SplitInfo) StableSize() (size int) { size += proto.BytesSize(splitInfoSplitIDField, s.splitID) size += proto.NestedStructureSize(splitInfoLastPartField, s.lastPart) size += proto.NestedStructureSize(splitInfoLinkField, s.link) + size += proto.NestedStructureSize(splitInfoFirstField, s.firstPart) return size } diff --git a/object/message_test.go b/object/message_test.go index 98461dff..7ef4a67b 100644 --- a/object/message_test.go +++ b/object/message_test.go @@ -51,5 +51,6 @@ func TestMessageConvert(t *testing.T) { func(empty bool) message.Message { return objecttest.GenerateGetRangeHashResponseBody(empty) }, func(empty bool) message.Message { return objecttest.GenerateGetRangeHashResponse(empty) }, func(empty bool) message.Message { return objecttest.GenerateLock(empty) }, + func(empty bool) message.Message { return objecttest.GenerateLink(empty) }, ) } diff --git a/object/test/generate.go b/object/test/generate.go index 0c6ffab4..9264ef15 100644 --- a/object/test/generate.go +++ b/object/test/generate.go @@ -610,3 +610,22 @@ func GenerateLock(empty bool) *object.Lock { return m } + +func GenerateLink(empty bool) *object.Link { + m := new(object.Link) + + if !empty { + m.SetChildren([]object.MeasuredObject{ + { + ID: *refstest.GenerateObjectID(false), + Size: 1, + }, + { + ID: *refstest.GenerateObjectID(false), + Size: 2, + }, + }) + } + + return m +} diff --git a/object/types.go b/object/types.go index 15477c14..9ff4e13c 100644 --- a/object/types.go +++ b/object/types.go @@ -28,7 +28,7 @@ type Attribute struct { } type SplitHeader struct { - par, prev *refs.ObjectID + par, prev, first *refs.ObjectID parSig *refs.Signature @@ -83,6 +83,8 @@ type SplitInfo struct { lastPart *refs.ObjectID link *refs.ObjectID + + firstPart *refs.ObjectID } type GetRequestBody struct { @@ -306,6 +308,7 @@ const ( TypeTombstone TypeStorageGroup TypeLock + TypeLink ) const ( @@ -446,6 +449,18 @@ func (h *SplitHeader) GetPrevious() *refs.ObjectID { return nil } +func (h *SplitHeader) SetFirst(v *refs.ObjectID) { + h.first = v +} + +func (h *SplitHeader) GetFirst() *refs.ObjectID { + if h != nil { + return h.first + } + + return nil +} + func (h *SplitHeader) SetPrevious(v *refs.ObjectID) { h.prev = v } @@ -740,6 +755,18 @@ func (s *SplitInfo) SetLink(v *refs.ObjectID) { s.link = v } +func (s *SplitInfo) GetFirstPart() *refs.ObjectID { + if s != nil { + return s.firstPart + } + + return nil +} + +func (s *SplitInfo) SetFirstPart(v *refs.ObjectID) { + s.firstPart = v +} + func (s *SplitInfo) getObjectPart() {} func (s *SplitInfo) getHeaderPart() {}