diff --git a/v2/acl/marshal.go b/v2/acl/marshal.go index d680b9cc..b1b6dcba 100644 --- a/v2/acl/marshal.go +++ b/v2/acl/marshal.go @@ -1,6 +1,8 @@ package acl import ( + "encoding/binary" + "github.com/nspcc-dev/neofs-api-go/util/proto" ) @@ -12,6 +14,11 @@ const ( TargetTypeField = 1 TargetKeysField = 2 + + RecordOperationField = 1 + RecordActionField = 2 + RecordFiltersField = 3 + RecordTargetsField = 4 ) func (t *Table) StableMarshal(buf []byte) ([]byte, error) { @@ -23,11 +30,92 @@ func (t *Table) StableSize() int { } func (r *Record) StableMarshal(buf []byte) ([]byte, error) { - panic("not implemented") + if r == nil { + return []byte{}, nil + } + + if buf == nil { + buf = make([]byte, r.StableSize()) + } + + var ( + offset, n int + prefix uint64 + err error + ) + + n, err = proto.EnumMarshal(RecordOperationField, buf, int32(r.op)) + if err != nil { + return nil, err + } + + offset += n + + n, err = proto.EnumMarshal(RecordActionField, buf[offset:], int32(r.action)) + if err != nil { + return nil, err + } + + offset += n + + prefix, _ = proto.NestedStructurePrefix(RecordFiltersField) + + for i := range r.filters { + offset += binary.PutUvarint(buf[offset:], prefix) + + n = r.filters[i].StableSize() + offset += binary.PutUvarint(buf[offset:], uint64(n)) + + _, err = r.filters[i].StableMarshal(buf[offset:]) + if err != nil { + return nil, err + } + + offset += n + } + + prefix, _ = proto.NestedStructurePrefix(RecordTargetsField) + + for i := range r.targets { + offset += binary.PutUvarint(buf[offset:], prefix) + + n = r.targets[i].StableSize() + offset += binary.PutUvarint(buf[offset:], uint64(n)) + + _, err = r.targets[i].StableMarshal(buf[offset:]) + if err != nil { + return nil, err + } + + offset += n + } + + return buf, nil } -func (r *Record) StableSize() int { - panic("not implemented") +func (r *Record) StableSize() (size int) { + if r == nil { + return 0 + } + + size += proto.EnumSize(RecordOperationField, int32(r.op)) + size += proto.EnumSize(RecordActionField, int32(r.op)) + + _, ln := proto.NestedStructurePrefix(RecordFiltersField) + + for i := range r.filters { + n := r.filters[i].StableSize() + size += ln + proto.VarUIntSize(uint64(n)) + n + } + + _, ln = proto.NestedStructurePrefix(RecordTargetsField) + + for i := range r.targets { + n := r.targets[i].StableSize() + size += ln + proto.VarUIntSize(uint64(n)) + n + } + + return size } func (f *HeaderFilter) StableMarshal(buf []byte) ([]byte, error) { diff --git a/v2/acl/marshal_test.go b/v2/acl/marshal_test.go index c040d266..eea5d049 100644 --- a/v2/acl/marshal_test.go +++ b/v2/acl/marshal_test.go @@ -1,6 +1,7 @@ package acl_test import ( + "fmt" "testing" "github.com/nspcc-dev/neofs-api-go/v2/acl" @@ -8,8 +9,61 @@ import ( "github.com/stretchr/testify/require" ) +func generateTarget(u acl.Target, k int) *acl.TargetInfo { + target := new(acl.TargetInfo) + target.SetTarget(u) + + keys := make([][]byte, k) + for i := 0; i < k; i++ { + s := fmt.Sprintf("Public Key %d", i+1) + keys[i] = []byte(s) + } + + return target +} + +func generateFilter(t acl.HeaderType, m acl.MatchType, k, v string) *acl.HeaderFilter { + filter := new(acl.HeaderFilter) + filter.SetHeaderType(t) + filter.SetMatchType(m) + filter.SetName(k) + filter.SetValue(v) + + return filter +} + +func generateRecord(another bool) *acl.Record { + var record = new(acl.Record) + switch another { + case true: + t1 := generateTarget(acl.TargetUser, 2) + f1 := generateFilter(acl.HeaderTypeObject, acl.MatchTypeStringEqual, + "OID", "ObjectID Value") + + record.SetOperation(acl.OperationHead) + record.SetAction(acl.ActionDeny) + record.SetTargets([]*acl.TargetInfo{t1}) + record.SetFilters([]*acl.HeaderFilter{f1}) + default: + t1 := generateTarget(acl.TargetUser, 2) + t2 := generateTarget(acl.TargetSystem, 0) + f1 := generateFilter(acl.HeaderTypeObject, acl.MatchTypeStringEqual, + "CID", "Container ID Value") + f2 := generateFilter(acl.HeaderTypeRequest, acl.MatchTypeStringEqual, + "X-Header-Key", "X-Header-Value") + + record.SetOperation(acl.OperationPut) + record.SetAction(acl.ActionAllow) + record.SetTargets([]*acl.TargetInfo{t1, t2}) + record.SetFilters([]*acl.HeaderFilter{f1, f2}) + } + + return record +} + func TestHeaderFilter_StableMarshal(t *testing.T) { - filterFrom := new(acl.HeaderFilter) + filterFrom := generateFilter(acl.HeaderTypeObject, acl.MatchTypeStringEqual, + "CID", "Container ID Value") transport := new(grpc.EACLRecord_FilterInfo) t.Run("non empty", func(t *testing.T) { @@ -30,7 +84,7 @@ func TestHeaderFilter_StableMarshal(t *testing.T) { } func TestTargetInfo_StableMarshal(t *testing.T) { - targetFrom := new(acl.TargetInfo) + targetFrom := generateTarget(acl.TargetUser, 2) transport := new(grpc.EACLRecord_TargetInfo) t.Run("non empty", func(t *testing.T) { @@ -50,3 +104,19 @@ func TestTargetInfo_StableMarshal(t *testing.T) { require.Equal(t, targetFrom, targetTo) }) } + +func TestRecord_StableMarshal(t *testing.T) { + recordFrom := generateRecord(false) + transport := new(grpc.EACLRecord) + + t.Run("non empty", func(t *testing.T) { + wire, err := recordFrom.StableMarshal(nil) + require.NoError(t, err) + + err = transport.Unmarshal(wire) + require.NoError(t, err) + + recordTo := acl.RecordFromGRPCMessage(transport) + require.Equal(t, recordFrom, recordTo) + }) +}