diff --git a/accounting/grpc/service.pb.go b/accounting/grpc/service.pb.go index 82858c65..dbb27b13 100644 Binary files a/accounting/grpc/service.pb.go and b/accounting/grpc/service.pb.go differ diff --git a/accounting/grpc/service_grpc.pb.go b/accounting/grpc/service_grpc.pb.go index 7139614c..8ed19565 100644 Binary files a/accounting/grpc/service_grpc.pb.go and b/accounting/grpc/service_grpc.pb.go differ diff --git a/accounting/grpc/types.pb.go b/accounting/grpc/types.pb.go index 33df6412..a36188ba 100644 Binary files a/accounting/grpc/types.pb.go and b/accounting/grpc/types.pb.go differ diff --git a/acl/grpc/types.pb.go b/acl/grpc/types.pb.go index f7b6734a..e71a8934 100644 Binary files a/acl/grpc/types.pb.go and b/acl/grpc/types.pb.go differ diff --git a/audit/grpc/types.pb.go b/audit/grpc/types.pb.go index 3da71a8d..64d32066 100644 Binary files a/audit/grpc/types.pb.go and b/audit/grpc/types.pb.go differ diff --git a/container/grpc/service.pb.go b/container/grpc/service.pb.go index 4af32983..d7fa78e2 100644 Binary files a/container/grpc/service.pb.go and b/container/grpc/service.pb.go differ diff --git a/container/grpc/service_grpc.pb.go b/container/grpc/service_grpc.pb.go index cbac3551..cf9c4356 100644 Binary files a/container/grpc/service_grpc.pb.go and b/container/grpc/service_grpc.pb.go differ diff --git a/container/grpc/types.pb.go b/container/grpc/types.pb.go index 7cb6be5a..3e2ed0d1 100644 Binary files a/container/grpc/types.pb.go and b/container/grpc/types.pb.go differ diff --git a/container/status.go b/container/status.go new file mode 100644 index 00000000..0535716c --- /dev/null +++ b/container/status.go @@ -0,0 +1,28 @@ +package container + +import ( + "github.com/nspcc-dev/neofs-api-go/v2/status" + statusgrpc "github.com/nspcc-dev/neofs-api-go/v2/status/grpc" +) + +// LocalizeFailStatus checks if passed global status.Code is related to container failure and: +// then localizes the code and returns true, +// else leaves the code unchanged and returns false. +// +// Arg must not be nil. +func LocalizeFailStatus(c *status.Code) bool { + return status.LocalizeIfInSection(c, uint32(statusgrpc.Section_SECTION_CONTAINER)) +} + +// GlobalizeFail globalizes local code of container failure. +// +// Arg must not be nil. +func GlobalizeFail(c *status.Code) { + c.GlobalizeSection(uint32(statusgrpc.Section_SECTION_CONTAINER)) +} + +const ( + // StatusNotFound is a local status.Code value for + // CONTAINER_NOT_FOUND container failure. + StatusNotFound status.Code = iota +) diff --git a/container/status_test.go b/container/status_test.go new file mode 100644 index 00000000..0826d8a1 --- /dev/null +++ b/container/status_test.go @@ -0,0 +1,14 @@ +package container_test + +import ( + "testing" + + "github.com/nspcc-dev/neofs-api-go/v2/container" + statustest "github.com/nspcc-dev/neofs-api-go/v2/status/test" +) + +func TestStatusCodes(t *testing.T) { + statustest.TestCodes(t, container.LocalizeFailStatus, container.GlobalizeFail, + container.StatusNotFound, 3072, + ) +} diff --git a/lock/grpc/types.go b/lock/grpc/types.go new file mode 100644 index 00000000..f10684c2 --- /dev/null +++ b/lock/grpc/types.go @@ -0,0 +1,8 @@ +package lock + +import refs "github.com/nspcc-dev/neofs-api-go/v2/refs/grpc" + +// SetMembers sets `members` field. +func (x *Lock) SetMembers(ids []*refs.ObjectID) { + x.Members = ids +} diff --git a/lock/grpc/types.pb.go b/lock/grpc/types.pb.go new file mode 100644 index 00000000..046249d2 Binary files /dev/null and b/lock/grpc/types.pb.go differ diff --git a/netmap/grpc/service.pb.go b/netmap/grpc/service.pb.go index 3f407d74..670afc76 100644 Binary files a/netmap/grpc/service.pb.go and b/netmap/grpc/service.pb.go differ diff --git a/netmap/grpc/service_grpc.pb.go b/netmap/grpc/service_grpc.pb.go index babf2e78..e8e5be47 100644 Binary files a/netmap/grpc/service_grpc.pb.go and b/netmap/grpc/service_grpc.pb.go differ diff --git a/netmap/grpc/types.pb.go b/netmap/grpc/types.pb.go index 7c44efd6..4f7b976a 100644 Binary files a/netmap/grpc/types.pb.go and b/netmap/grpc/types.pb.go differ diff --git a/object/grpc/service.pb.go b/object/grpc/service.pb.go index 0a34a841..f75b580d 100644 Binary files a/object/grpc/service.pb.go and b/object/grpc/service.pb.go differ diff --git a/object/grpc/service_grpc.pb.go b/object/grpc/service_grpc.pb.go index 4a2ea285..376d95d3 100644 Binary files a/object/grpc/service_grpc.pb.go and b/object/grpc/service_grpc.pb.go differ diff --git a/object/grpc/types.pb.go b/object/grpc/types.pb.go index 210977c8..6263a06a 100644 Binary files a/object/grpc/types.pb.go and b/object/grpc/types.pb.go differ diff --git a/object/lock.go b/object/lock.go new file mode 100644 index 00000000..660c0026 --- /dev/null +++ b/object/lock.go @@ -0,0 +1,170 @@ +package object + +import ( + "errors" + "fmt" + + lock "github.com/nspcc-dev/neofs-api-go/v2/lock/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" +) + +// Lock represents object Lock message from NeoFS API V2 protocol. +type Lock struct { + members []refs.ObjectID +} + +// NumberOfMembers returns length of lock list. +func (x *Lock) NumberOfMembers() int { + if x != nil { + return len(x.members) + } + + return 0 +} + +// IterateMembers passes members of the lock list to f. +func (x *Lock) IterateMembers(f func(refs.ObjectID)) { + if x != nil { + for i := range x.members { + f(x.members[i]) + } + } +} + +// SetMembers sets list of locked members. +// Arg must not be mutated for the duration of the Lock. +func (x *Lock) SetMembers(ids []refs.ObjectID) { + x.members = ids +} + +const ( + _ = iota + fNumLockMembers +) + +// StableMarshal encodes the Lock into Protocol Buffers binary format +// with direct field order. +func (x *Lock) StableMarshal(buf []byte) ([]byte, error) { + if x == nil || len(x.members) == 0 { + return []byte{}, nil + } + + if buf == nil { + buf = make([]byte, x.StableSize()) + } + + var offset, n int + var err error + + for i := range x.members { + n, err = proto.NestedStructureMarshal(fNumLockMembers, buf[offset:], &x.members[i]) + if err != nil { + return nil, err + } + + offset += n + } + + return buf, nil +} + +// StableSize size of the buffer required to write the Lock in Protocol Buffers +// binary format. +func (x *Lock) StableSize() (sz int) { + if x != nil { + for i := range x.members { + sz += proto.NestedStructureSize(fNumLockMembers, &x.members[i]) + } + } + + return +} + +// Unmarshal decodes the Lock from its Protocol Buffers binary format. +func (x *Lock) Unmarshal(data []byte) error { + return message.Unmarshal(x, data, new(lock.Lock)) +} + +func (x *Lock) ToGRPCMessage() grpc.Message { + var m *lock.Lock + + if x != nil { + m = new(lock.Lock) + + var members []*refsGRPC.ObjectID + + if x.members != nil { + members = make([]*refsGRPC.ObjectID, len(x.members)) + + for i := range x.members { + members[i] = x.members[i].ToGRPCMessage().(*refsGRPC.ObjectID) + } + } + + m.SetMembers(members) + } + + return m +} + +func (x *Lock) FromGRPCMessage(m grpc.Message) error { + v, ok := m.(*lock.Lock) + if !ok { + return message.NewUnexpectedMessageType(m, v) + } + + members := v.GetMembers() + if members == nil { + x.members = nil + } else { + x.members = make([]refs.ObjectID, len(members)) + var err error + + for i := range x.members { + err = x.members[i].FromGRPCMessage(members[i]) + if err != nil { + return err + } + } + } + + return nil +} + +// WriteLock writes Lock to the Object as a payload content. +// The object must not be nil. +func WriteLock(obj *Object, lock Lock) { + hdr := obj.GetHeader() + if hdr == nil { + hdr = new(Header) + obj.SetHeader(hdr) + } + + hdr.SetObjectType(TypeLock) + + payload, err := lock.StableMarshal(nil) + if err != nil { + panic(fmt.Sprintf("encode lock content: %v", err)) + } + + obj.SetPayload(payload) +} + +// ReadLock reads Lock from the Object payload content. +func ReadLock(lock *Lock, obj Object) error { + payload := obj.GetPayload() + if len(payload) == 0 { + return errors.New("empty payload") + } + + err := lock.Unmarshal(payload) + if err != nil { + return fmt.Errorf("decode lock content from payload: %w", err) + } + + return nil +} diff --git a/object/lock_test.go b/object/lock_test.go new file mode 100644 index 00000000..17e9e46f --- /dev/null +++ b/object/lock_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 TestLockRW(t *testing.T) { + var l object.Lock + var obj object.Object + + require.Error(t, object.ReadLock(&l, obj)) + + l = *objecttest.GenerateLock(false) + + object.WriteLock(&obj, l) + + var l2 object.Lock + + require.NoError(t, object.ReadLock(&l2, obj)) + + require.Equal(t, l, l2) +} diff --git a/object/message_test.go b/object/message_test.go index 145c22c3..98461dff 100644 --- a/object/message_test.go +++ b/object/message_test.go @@ -50,5 +50,6 @@ func TestMessageConvert(t *testing.T) { func(empty bool) message.Message { return objecttest.GenerateGetRangeHashRequest(empty) }, 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) }, ) } diff --git a/object/status.go b/object/status.go new file mode 100644 index 00000000..067c372d --- /dev/null +++ b/object/status.go @@ -0,0 +1,87 @@ +package object + +import ( + "github.com/nspcc-dev/neofs-api-go/v2/status" + statusgrpc "github.com/nspcc-dev/neofs-api-go/v2/status/grpc" +) + +// LocalizeFailStatus checks if passed global status.Code is related to object failure and: +// then localizes the code and returns true, +// else leaves the code unchanged and returns false. +// +// Arg must not be nil. +func LocalizeFailStatus(c *status.Code) bool { + return status.LocalizeIfInSection(c, uint32(statusgrpc.Section_SECTION_OBJECT)) +} + +// GlobalizeFail globalizes local code of object failure. +// +// Arg must not be nil. +func GlobalizeFail(c *status.Code) { + c.GlobalizeSection(uint32(statusgrpc.Section_SECTION_OBJECT)) +} + +const ( + // StatusAccessDenied is a local status.Code value for + // ACCESS_DENIED object failure. + StatusAccessDenied status.Code = iota + // StatusNotFound is a local status.Code value for + // OBJECT_NOT_FOUND object failure. + StatusNotFound + // StatusLocked is a local status.Code value for + // LOCKED object failure. + StatusLocked + // StatusLockNonRegularObject is a local status.Code value for + // LOCK_NON_REGULAR_OBJECT object failure. + StatusLockNonRegularObject + // StatusAlreadyRemoved is a local status.Code value for + // OBJECT_ALREADY_REMOVED object failure. + StatusAlreadyRemoved +) + +const ( + // detailAccessDeniedDesc is a StatusAccessDenied detail ID for + // human-readable description. + detailAccessDeniedDesc = iota +) + +// WriteAccessDeniedDesc writes human-readable description of StatusAccessDenied +// into status.Status as a detail. The status must not be nil. +// +// Existing details are expected to be ID-unique, otherwise undefined behavior. +func WriteAccessDeniedDesc(st *status.Status, desc string) { + var found bool + + st.IterateDetails(func(d *status.Detail) bool { + if d.ID() == detailAccessDeniedDesc { + found = true + d.SetValue([]byte(desc)) + } + + return found + }) + + if !found { + var d status.Detail + + d.SetID(detailAccessDeniedDesc) + d.SetValue([]byte(desc)) + + st.AppendDetails(&d) + } +} + +// ReadAccessDeniedDesc looks up for status detail with human-readable description +// of StatusAccessDenied. Returns empty string if detail is missing. +func ReadAccessDeniedDesc(st status.Status) (desc string) { + st.IterateDetails(func(d *status.Detail) bool { + if d.ID() == detailAccessDeniedDesc { + desc = string(d.Value()) + return true + } + + return false + }) + + return +} diff --git a/object/status_test.go b/object/status_test.go new file mode 100644 index 00000000..86fcc823 --- /dev/null +++ b/object/status_test.go @@ -0,0 +1,34 @@ +package object_test + +import ( + "testing" + + "github.com/nspcc-dev/neofs-api-go/v2/object" + "github.com/nspcc-dev/neofs-api-go/v2/status" + statustest "github.com/nspcc-dev/neofs-api-go/v2/status/test" + "github.com/stretchr/testify/require" +) + +func TestStatusCodes(t *testing.T) { + statustest.TestCodes(t, object.LocalizeFailStatus, object.GlobalizeFail, + object.StatusAccessDenied, 2048, + object.StatusNotFound, 2049, + object.StatusLocked, 2050, + object.StatusLockNonRegularObject, 2051, + object.StatusAlreadyRemoved, 2052, + ) +} + +func TestAccessDeniedDesc(t *testing.T) { + var st status.Status + + require.Empty(t, object.ReadAccessDeniedDesc(st)) + + const desc = "some description" + + object.WriteAccessDeniedDesc(&st, desc) + require.Equal(t, desc, object.ReadAccessDeniedDesc(st)) + + object.WriteAccessDeniedDesc(&st, desc+"1") + require.Equal(t, desc+"1", object.ReadAccessDeniedDesc(st)) +} diff --git a/object/test/generate.go b/object/test/generate.go index 9070272a..d2f80cb3 100644 --- a/object/test/generate.go +++ b/object/test/generate.go @@ -2,6 +2,7 @@ package objecttest import ( "github.com/nspcc-dev/neofs-api-go/v2/object" + "github.com/nspcc-dev/neofs-api-go/v2/refs" refstest "github.com/nspcc-dev/neofs-api-go/v2/refs/test" sessiontest "github.com/nspcc-dev/neofs-api-go/v2/session/test" ) @@ -571,3 +572,16 @@ func GenerateGetRangeHashResponse(empty bool) *object.GetRangeHashResponse { return m } + +func GenerateLock(empty bool) *object.Lock { + m := new(object.Lock) + + if !empty { + m.SetMembers([]refs.ObjectID{ + *refstest.GenerateObjectID(false), + *refstest.GenerateObjectID(false), + }) + } + + return m +} diff --git a/object/types.go b/object/types.go index 94e9ed9c..0c101b50 100644 --- a/object/types.go +++ b/object/types.go @@ -305,6 +305,7 @@ const ( TypeRegular Type = iota TypeTombstone TypeStorageGroup + TypeLock ) const ( diff --git a/reputation/grpc/service.pb.go b/reputation/grpc/service.pb.go index 78936772..865de18a 100644 Binary files a/reputation/grpc/service.pb.go and b/reputation/grpc/service.pb.go differ diff --git a/reputation/grpc/service_grpc.pb.go b/reputation/grpc/service_grpc.pb.go index b260785b..1bd9b79a 100644 Binary files a/reputation/grpc/service_grpc.pb.go and b/reputation/grpc/service_grpc.pb.go differ diff --git a/reputation/grpc/types.pb.go b/reputation/grpc/types.pb.go index b4396e8e..b27aa42f 100644 Binary files a/reputation/grpc/types.pb.go and b/reputation/grpc/types.pb.go differ diff --git a/session/grpc/service.pb.go b/session/grpc/service.pb.go index 07be9420..33510adf 100644 Binary files a/session/grpc/service.pb.go and b/session/grpc/service.pb.go differ diff --git a/session/grpc/service_grpc.pb.go b/session/grpc/service_grpc.pb.go index cb72826c..787a5644 100644 Binary files a/session/grpc/service_grpc.pb.go and b/session/grpc/service_grpc.pb.go differ diff --git a/session/grpc/types.pb.go b/session/grpc/types.pb.go index 9a3dd872..eacccfa8 100644 Binary files a/session/grpc/types.pb.go and b/session/grpc/types.pb.go differ diff --git a/session/status.go b/session/status.go new file mode 100644 index 00000000..bbcd3e2a --- /dev/null +++ b/session/status.go @@ -0,0 +1,31 @@ +package session + +import ( + "github.com/nspcc-dev/neofs-api-go/v2/status" + statusgrpc "github.com/nspcc-dev/neofs-api-go/v2/status/grpc" +) + +// LocalizeFailStatus checks if passed global status.Code is related to session failure and: +// then localizes the code and returns true, +// else leaves the code unchanged and returns false. +// +// Arg must not be nil. +func LocalizeFailStatus(c *status.Code) bool { + return status.LocalizeIfInSection(c, uint32(statusgrpc.Section_SECTION_SESSION)) +} + +// GlobalizeFail globalizes local code of session failure. +// +// Arg must not be nil. +func GlobalizeFail(c *status.Code) { + c.GlobalizeSection(uint32(statusgrpc.Section_SECTION_SESSION)) +} + +const ( + // StatusTokenNotFound is a local status.Code value for + // TOKEN_NOT_FOUND session failure. + StatusTokenNotFound status.Code = iota + // StatusTokenExpired is a local status.Code value for + // TOKEN_EXPIRED session failure. + StatusTokenExpired +) diff --git a/session/status_test.go b/session/status_test.go new file mode 100644 index 00000000..a2847f8f --- /dev/null +++ b/session/status_test.go @@ -0,0 +1,15 @@ +package session_test + +import ( + "testing" + + "github.com/nspcc-dev/neofs-api-go/v2/session" + statustest "github.com/nspcc-dev/neofs-api-go/v2/status/test" +) + +func TestStatusCodes(t *testing.T) { + statustest.TestCodes(t, session.LocalizeFailStatus, session.GlobalizeFail, + session.StatusTokenNotFound, 4096, + session.StatusTokenExpired, 4097, + ) +} diff --git a/status/grpc/types.pb.go b/status/grpc/types.pb.go index e6d571f0..168e1837 100644 Binary files a/status/grpc/types.pb.go and b/status/grpc/types.pb.go differ diff --git a/status/status.go b/status/status.go index 4b1f3700..b610b8b1 100644 --- a/status/status.go +++ b/status/status.go @@ -80,3 +80,17 @@ func LocalizeCommonFail(c *Code) { func GlobalizeCommonFail(c *Code) { c.GlobalizeSection(sectionAfterSuccess(sectionCommon)) } + +// LocalizeIfInSection checks if passed global status.Code belongs to the section and: +// then localizes the code and returns true, +// else leaves the code unchanged and returns false. +// +// Arg must not be nil. +func LocalizeIfInSection(c *Code, sec uint32) bool { + if IsInSection(*c, sec) { + c.LocalizeSection(sec) + return true + } + + return false +} diff --git a/status/test/codes.go b/status/test/codes.go new file mode 100644 index 00000000..acc5a503 --- /dev/null +++ b/status/test/codes.go @@ -0,0 +1,28 @@ +package statustest + +import ( + "testing" + + "github.com/nspcc-dev/neofs-api-go/v2/status" + "github.com/stretchr/testify/require" +) + +// TestCodes checks mapping of status codes to the numbers. +// Args must be pairs (status.Code, int). +func TestCodes(t *testing.T, + localizer func(*status.Code) bool, + globalizer func(code *status.Code), + vals ...interface{}, +) { + for i := 0; i < len(vals); i += 2 { + c := vals[i].(status.Code) + cp := c + + globalizer(&cp) + require.True(t, cp.EqualNumber(uint32(vals[i+1].(int))), c) + + require.True(t, localizer(&cp), c) + + require.Equal(t, cp, c, c) + } +} diff --git a/status/types.go b/status/types.go index 61a05d08..6fe233b0 100644 --- a/status/types.go +++ b/status/types.go @@ -42,6 +42,11 @@ func (x *Detail) SetValue(val []byte) { // Code represents NeoFS API V2-compatible status code. type Code uint32 +// EqualNumber checks if the numerical Code equals num. +func (x Code) EqualNumber(num uint32) bool { + return uint32(x) == num +} + // Status represents structure of NeoFS API V2-compatible status return message. type Status struct { code Code diff --git a/storagegroup/grpc/types.pb.go b/storagegroup/grpc/types.pb.go index d6584f5c..e0fd05cf 100644 Binary files a/storagegroup/grpc/types.pb.go and b/storagegroup/grpc/types.pb.go differ diff --git a/subnet/grpc/types.pb.go b/subnet/grpc/types.pb.go index bab81bf9..10e2e950 100644 Binary files a/subnet/grpc/types.pb.go and b/subnet/grpc/types.pb.go differ diff --git a/tombstone/grpc/types.pb.go b/tombstone/grpc/types.pb.go index 60aacc2a..421b284a 100644 Binary files a/tombstone/grpc/types.pb.go and b/tombstone/grpc/types.pb.go differ diff --git a/util/proto/test/test.pb.go b/util/proto/test/test.pb.go index b0ef1356..edefcc3d 100644 Binary files a/util/proto/test/test.pb.go and b/util/proto/test/test.pb.go differ