diff --git a/pkg/client/object.go b/pkg/client/object.go index 04812da..df60f6f 100644 --- a/pkg/client/object.go +++ b/pkg/client/object.go @@ -445,6 +445,8 @@ func (c *Client) getObjectV2(ctx context.Context, p *GetObjectParams, opts ...Ca } else { payload = append(payload, v.GetChunk()...) } + case *v2object.SplitInfo: // what else can we do here? + return nil, errors.New("object not found, split info has been provided") default: panic(fmt.Sprintf("unexpected Get object part type %T", v)) } diff --git a/pkg/object/splitinfo.go b/pkg/object/splitinfo.go new file mode 100644 index 0000000..1f7494d --- /dev/null +++ b/pkg/object/splitinfo.go @@ -0,0 +1,49 @@ +package object + +import ( + "github.com/nspcc-dev/neofs-api-go/v2/object" +) + +type SplitInfo object.SplitInfo + +func NewSplitInfoFromV2(v2 *object.SplitInfo) *SplitInfo { + return (*SplitInfo)(v2) +} + +func NewSplitInfo() *SplitInfo { + return NewSplitInfoFromV2(new(object.SplitInfo)) +} + +func (s *SplitInfo) ToV2() *object.SplitInfo { + return (*object.SplitInfo)(s) +} + +func (s *SplitInfo) SplitID() *SplitID { + return NewSplitIDFromV2( + (*object.SplitInfo)(s).GetSplitID(), + ) +} + +func (s *SplitInfo) SetSplitID(v *SplitID) { + (*object.SplitInfo)(s).SetSplitID(v.ToV2()) +} + +func (s *SplitInfo) LastPart() *ID { + return NewIDFromV2( + (*object.SplitInfo)(s).GetLastPart(), + ) +} + +func (s *SplitInfo) SetLastPart(v *ID) { + (*object.SplitInfo)(s).SetLastPart(v.ToV2()) +} + +func (s *SplitInfo) Link() *ID { + return NewIDFromV2( + (*object.SplitInfo)(s).GetLink(), + ) +} + +func (s *SplitInfo) SetLink(v *ID) { + (*object.SplitInfo)(s).SetLink(v.ToV2()) +} diff --git a/pkg/object/splitinfo_test.go b/pkg/object/splitinfo_test.go new file mode 100644 index 0000000..854defe --- /dev/null +++ b/pkg/object/splitinfo_test.go @@ -0,0 +1,42 @@ +package object_test + +import ( + "crypto/rand" + "testing" + + "github.com/nspcc-dev/neofs-api-go/pkg/object" + "github.com/stretchr/testify/require" +) + +func TestSplitInfo(t *testing.T) { + s := object.NewSplitInfo() + splitID := object.NewSplitID() + lastPart := generateID() + link := generateID() + + s.SetSplitID(splitID) + require.Equal(t, splitID, s.SplitID()) + + s.SetLastPart(lastPart) + require.Equal(t, lastPart, s.LastPart()) + + s.SetLink(link) + require.Equal(t, link, s.Link()) + + t.Run("to and from v2", func(t *testing.T) { + v2 := s.ToV2() + newS := object.NewSplitInfoFromV2(v2) + + require.Equal(t, s, newS) + }) +} + +func generateID() *object.ID { + var buf [32]byte + _, _ = rand.Read(buf[:]) + + id := object.NewID() + id.SetSHA256(buf) + + return id +} diff --git a/v2/object/convert.go b/v2/object/convert.go index 0768689..ddeafb1 100644 --- a/v2/object/convert.go +++ b/v2/object/convert.go @@ -371,6 +371,46 @@ func ObjectFromGRPCMessage(m *object.Object) *Object { return o } +func SplitInfoToGRPCMessage(s *SplitInfo) *object.SplitInfo { + if s == nil { + return nil + } + + m := new(object.SplitInfo) + + m.SetSplitId(s.GetSplitID()) + + m.SetLastPart( + refs.ObjectIDToGRPCMessage(s.GetLastPart()), + ) + + m.SetLink( + refs.ObjectIDToGRPCMessage(s.GetLink()), + ) + + return m +} + +func SplitInfoFromGRPCMessage(m *object.SplitInfo) *SplitInfo { + if m == nil { + return nil + } + + r := new(SplitInfo) + + r.SetSplitID(m.GetSplitId()) + + r.SetLastPart( + refs.ObjectIDFromGRPCMessage(m.GetLastPart()), + ) + + r.SetLink( + refs.ObjectIDFromGRPCMessage(m.GetLink()), + ) + + return r +} + func GetRequestBodyToGRPCMessage(r *GetRequestBody) *object.GetRequest_Body { if r == nil { return nil @@ -520,6 +560,10 @@ func GetResponseBodyToGRPCMessage(r *GetResponseBody) *object.GetResponse_Body { m.SetChunk( GetObjectPartChunkToGRPCMessage(t), ) + case *SplitInfo: + m.SetSplitInfo( + SplitInfoToGRPCMessage(t), + ) default: panic(fmt.Sprintf("unknown object part %T", t)) } @@ -544,6 +588,10 @@ func GetResponseBodyFromGRPCMessage(m *object.GetResponse_Body) *GetResponseBody r.SetObjectPart( GetObjectPartChunkFromGRPCMessage(v), ) + case *object.GetResponse_Body_SplitInfo: + r.SetObjectPart( + SplitInfoFromGRPCMessage(v.SplitInfo), + ) default: panic(fmt.Sprintf("unknown object part %T", v)) } diff --git a/v2/object/grpc/service.go b/v2/object/grpc/service.go index aefa5b4..7acad62 100644 --- a/v2/object/grpc/service.go +++ b/v2/object/grpc/service.go @@ -93,6 +93,15 @@ func (m *GetResponse_Body) SetChunk(v *GetResponse_Body_Chunk) { } } +// SetSplitInfo sets part of the object payload. +func (m *GetResponse_Body) SetSplitInfo(v *SplitInfo) { + if m != nil { + m.ObjectPart = &GetResponse_Body_SplitInfo{ + SplitInfo: v, + } + } +} + // SetBody sets body of the response. func (m *GetResponse) SetBody(v *GetResponse_Body) { if m != nil { diff --git a/v2/object/grpc/service.pb.go b/v2/object/grpc/service.pb.go index 77f84ef..4d6c26a 100644 Binary files a/v2/object/grpc/service.pb.go and b/v2/object/grpc/service.pb.go differ diff --git a/v2/object/grpc/types.go b/v2/object/grpc/types.go index b1f46c8..df1151a 100644 --- a/v2/object/grpc/types.go +++ b/v2/object/grpc/types.go @@ -200,3 +200,24 @@ func (m *ShortHeader) SetPayloadLength(v uint64) { m.PayloadLength = v } } + +// SetSplitId sets id of split hierarchy. +func (m *SplitInfo) SetSplitId(v []byte) { + if m != nil { + m.SplitId = v + } +} + +// SetLastPart sets id of most right child in split hierarchy. +func (m *SplitInfo) SetLastPart(v *refs.ObjectID) { + if m != nil { + m.LastPart = v + } +} + +// SetLink sets id of linking object in split hierarchy. +func (m *SplitInfo) SetLink(v *refs.ObjectID) { + if m != nil { + m.Link = v + } +} diff --git a/v2/object/grpc/types.pb.go b/v2/object/grpc/types.pb.go index 8d5eede..48b2b73 100644 Binary files a/v2/object/grpc/types.pb.go and b/v2/object/grpc/types.pb.go differ diff --git a/v2/object/marshal.go b/v2/object/marshal.go index 4e54b34..9f91b67 100644 --- a/v2/object/marshal.go +++ b/v2/object/marshal.go @@ -43,6 +43,10 @@ const ( objHeaderField = 3 objPayloadField = 4 + splitInfoSplitIDField = 1 + splitInfoLastPartField = 2 + splitInfoLinkField = 3 + getReqBodyAddressField = 1 getReqBodyRawFlagField = 2 @@ -50,8 +54,9 @@ const ( getRespInitSignatureField = 2 getRespInitHeaderField = 3 - getRespBodyInitField = 1 - getRespBodyChunkField = 2 + getRespBodyInitField = 1 + getRespBodyChunkField = 2 + getRespBodySplitInfoField = 3 putReqInitObjectIDField = 1 putReqInitSignatureField = 2 @@ -576,6 +581,54 @@ func (o *Object) Unmarshal(data []byte) error { return nil } +func (s *SplitInfo) StableMarshal(buf []byte) ([]byte, error) { + if s == nil { + return []byte{}, nil + } + + if buf == nil { + buf = make([]byte, s.StableSize()) + } + + var ( + offset, n int + err error + ) + + n, err = proto.BytesMarshal(splitInfoSplitIDField, buf[offset:], s.splitID) + if err != nil { + return nil, err + } + + offset += n + + n, err = proto.NestedStructureMarshal(splitInfoLastPartField, buf[offset:], s.lastPart) + if err != nil { + return nil, err + } + + offset += n + + _, err = proto.NestedStructureMarshal(splitInfoLinkField, buf[offset:], s.link) + if err != nil { + return nil, err + } + + return buf, nil +} + +func (s *SplitInfo) StableSize() (size int) { + if s == nil { + return 0 + } + + size += proto.BytesSize(splitInfoSplitIDField, s.splitID) + size += proto.NestedStructureSize(splitInfoLastPartField, s.lastPart) + size += proto.NestedStructureSize(splitInfoLinkField, s.link) + + return size +} + func (r *GetRequestBody) StableMarshal(buf []byte) ([]byte, error) { if r == nil { return []byte{}, nil @@ -687,6 +740,11 @@ func (r *GetResponseBody) StableMarshal(buf []byte) ([]byte, error) { return nil, err } } + case *SplitInfo: + _, err := proto.NestedStructureMarshal(getRespBodySplitInfoField, buf, v) + if err != nil { + return nil, err + } default: panic("unknown one of object get response body type") } @@ -708,6 +766,8 @@ func (r *GetResponseBody) StableSize() (size int) { if v != nil { size += proto.BytesSize(getRespBodyChunkField, v.chunk) } + case *SplitInfo: + size += proto.NestedStructureSize(getRespBodySplitInfoField, v) default: panic("unknown one of object get response body type") } diff --git a/v2/object/marshal_test.go b/v2/object/marshal_test.go index 7f811af..142b423 100644 --- a/v2/object/marshal_test.go +++ b/v2/object/marshal_test.go @@ -107,8 +107,9 @@ func TestGetRequestBody_StableMarshal(t *testing.T) { } func TestGetResponseBody_StableMarshal(t *testing.T) { - initFrom := generateGetResponseBody(true) - chunkFrom := generateGetResponseBody(false) + initFrom := generateGetResponseBody(0) + chunkFrom := generateGetResponseBody(1) + splitInfoFrom := generateGetResponseBody(2) transport := new(grpc.GetResponse_Body) t.Run("init non empty", func(t *testing.T) { @@ -132,6 +133,17 @@ func TestGetResponseBody_StableMarshal(t *testing.T) { to := object.GetResponseBodyFromGRPCMessage(transport) require.Equal(t, chunkFrom, to) }) + + t.Run("split info non empty", func(t *testing.T) { + wire, err := splitInfoFrom.StableMarshal(nil) + require.NoError(t, err) + + err = goproto.Unmarshal(wire, transport) + require.NoError(t, err) + + to := object.GetResponseBodyFromGRPCMessage(transport) + require.Equal(t, splitInfoFrom, to) + }) } func TestPutRequestBody_StableMarshal(t *testing.T) { @@ -518,21 +530,29 @@ func generateGetRequestBody(cid, oid string) *object.GetRequestBody { return req } -func generateGetResponseBody(flag bool) *object.GetResponseBody { +func generateGetResponseBody(i int) *object.GetResponseBody { resp := new(object.GetResponseBody) var part object.GetObjectPart - if flag { + switch i { + case 0: init := new(object.GetObjectPartInit) init.SetObjectID(generateObjectID("Object ID")) init.SetSignature(generateSignature("Key", "Signature")) init.SetHeader(generateHeader(10)) part = init - } else { + case 1: chunk := new(object.GetObjectPartChunk) chunk.SetChunk([]byte("Some data chunk")) part = chunk + default: + splitInfo := new(object.SplitInfo) + splitInfo.SetSplitID([]byte("splitID")) + splitInfo.SetLastPart(generateObjectID("Right ID")) + splitInfo.SetLink(generateObjectID("Link ID")) + part = splitInfo } + resp.SetObjectPart(part) return resp diff --git a/v2/object/types.go b/v2/object/types.go index 6898cf6..9d50cbf 100644 --- a/v2/object/types.go +++ b/v2/object/types.go @@ -75,6 +75,14 @@ type Object struct { payload []byte } +type SplitInfo struct { + splitID []byte + + lastPart *refs.ObjectID + + link *refs.ObjectID +} + type GetRequestBody struct { addr *refs.Address @@ -634,6 +642,50 @@ func (o *Object) SetPayload(v []byte) { } } +func (s *SplitInfo) GetSplitID() []byte { + if s.splitID != nil { + return s.splitID + } + + return nil +} + +func (s *SplitInfo) SetSplitID(v []byte) { + if s != nil { + s.splitID = v + } +} + +func (s *SplitInfo) GetLastPart() *refs.ObjectID { + if s != nil { + return s.lastPart + } + + return nil +} + +func (s *SplitInfo) SetLastPart(v *refs.ObjectID) { + if s != nil { + s.lastPart = v + } +} + +func (s *SplitInfo) GetLink() *refs.ObjectID { + if s != nil { + return s.link + } + + return nil +} + +func (s *SplitInfo) SetLink(v *refs.ObjectID) { + if s != nil { + s.link = v + } +} + +func (s *SplitInfo) getObjectPart() {} // implement interface to be one of response body + func (r *GetRequestBody) GetAddress() *refs.Address { if r != nil { return r.addr