Skip to content

Commit

Permalink
eacl: Support numeric matchers (#554)
Browse files Browse the repository at this point in the history
  • Loading branch information
cthulhu-rider authored Feb 21, 2024
2 parents 8a0be3f + a31bd91 commit cbaf23c
Show file tree
Hide file tree
Showing 5 changed files with 238 additions and 35 deletions.
52 changes: 40 additions & 12 deletions eacl/enums.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,21 @@ const (

// MatchStringNotEqual is a Match of string inequality.
MatchStringNotEqual

// MatchNotPresent is an operator for attribute absence.
MatchNotPresent

// MatchNumGT is a numeric "greater than" operator.
MatchNumGT

// MatchNumGE is a numeric "greater or equal than" operator.
MatchNumGE

// MatchNumLT is a numeric "less than" operator.
MatchNumLT

// MatchNumLE is a numeric "less or equal than" operator.
MatchNumLE
)

// FilterHeaderType indicates source of headers to make matches.
Expand Down Expand Up @@ -317,34 +332,47 @@ func (r *Role) DecodeString(s string) bool {
// ToV2 converts Match to v2 MatchType enum value.
func (m Match) ToV2() v2acl.MatchType {
switch m {
case MatchStringEqual:
return v2acl.MatchTypeStringEqual
case MatchStringNotEqual:
return v2acl.MatchTypeStringNotEqual
case
MatchStringEqual,
MatchStringNotEqual,
MatchNotPresent,
MatchNumGT,
MatchNumGE,
MatchNumLT,
MatchNumLE:
return v2acl.MatchType(m)
default:
return v2acl.MatchTypeUnknown
}
}

// MatchFromV2 converts v2 MatchType enum value to Match.
func MatchFromV2(match v2acl.MatchType) (m Match) {
func MatchFromV2(match v2acl.MatchType) Match {
switch match {
case v2acl.MatchTypeStringEqual:
m = MatchStringEqual
case v2acl.MatchTypeStringNotEqual:
m = MatchStringNotEqual
case
v2acl.MatchTypeStringEqual,
v2acl.MatchTypeStringNotEqual,
v2acl.MatchTypeNotPresent,
v2acl.MatchTypeNumGT,
v2acl.MatchTypeNumGE,
v2acl.MatchTypeNumLT,
v2acl.MatchTypeNumLE:
return Match(match)
default:
m = MatchUnknown
return MatchUnknown
}

return m
}

// EncodeToString returns string representation of Match.
//
// String mapping:
// - MatchStringEqual: STRING_EQUAL;
// - MatchStringNotEqual: STRING_NOT_EQUAL;
// - MatchNotPresent: NOT_PRESENT;
// - MatchNumGT: NUM_GT;
// - MatchNumGE: NUM_GE;
// - MatchNumLT: NUM_LT;
// - MatchNumLE: NUM_LE;
// - MatchUnknown, default: MATCH_TYPE_UNSPECIFIED.
func (m Match) EncodeToString() string {
return m.ToV2().String()
Expand Down
14 changes: 12 additions & 2 deletions eacl/enums_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ var (
eacl.MatchUnknown: v2acl.MatchTypeUnknown,
eacl.MatchStringEqual: v2acl.MatchTypeStringEqual,
eacl.MatchStringNotEqual: v2acl.MatchTypeStringNotEqual,
eacl.MatchNotPresent: v2acl.MatchTypeNotPresent,
eacl.MatchNumGT: v2acl.MatchTypeNumGT,
eacl.MatchNumGE: v2acl.MatchTypeNumGE,
eacl.MatchNumLT: v2acl.MatchTypeNumLT,
eacl.MatchNumLE: v2acl.MatchTypeNumLE,
}

eqV2HeaderTypes = map[eacl.FilterHeaderType]v2acl.HeaderType{
Expand Down Expand Up @@ -98,8 +103,8 @@ func TestMatch(t *testing.T) {
})

t.Run("unknown matches", func(t *testing.T) {
require.Equal(t, (eacl.MatchStringNotEqual + 1).ToV2(), v2acl.MatchTypeUnknown)
require.Equal(t, eacl.MatchFromV2(v2acl.MatchTypeStringNotEqual+1), eacl.MatchUnknown)
require.Equal(t, (eacl.MatchNumLE + 1).ToV2(), v2acl.MatchTypeUnknown)
require.Equal(t, eacl.MatchFromV2(v2acl.MatchTypeNumLE+1), eacl.MatchUnknown)
})
}

Expand Down Expand Up @@ -198,6 +203,11 @@ func TestMatch_String(t *testing.T) {
{val: toPtr(eacl.MatchStringEqual), str: "STRING_EQUAL"},
{val: toPtr(eacl.MatchStringNotEqual), str: "STRING_NOT_EQUAL"},
{val: toPtr(eacl.MatchUnknown), str: "MATCH_TYPE_UNSPECIFIED"},
{val: toPtr(eacl.MatchNotPresent), str: "NOT_PRESENT"},
{val: toPtr(eacl.MatchNumGT), str: "NUM_GT"},
{val: toPtr(eacl.MatchNumGE), str: "NUM_GE"},
{val: toPtr(eacl.MatchNumLT), str: "NUM_LT"},
{val: toPtr(eacl.MatchNumLE), str: "NUM_LE"},
})
}

Expand Down
24 changes: 24 additions & 0 deletions eacl/record.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,56 +123,80 @@ func (r *Record) addObjectReservedFilter(m Match, typ filterKeyType, val stringE
}

// AddFilter adds generic filter.
//
// If matcher is [MatchNotPresent], the value must be empty. If matcher is
// numeric (e.g. [MatchNumGT]), value must be a base-10 integer.
func (r *Record) AddFilter(from FilterHeaderType, matcher Match, name, value string) {
r.addFilter(from, matcher, 0, name, staticStringer(value))
}

// AddObjectAttributeFilter adds filter by object attribute.
//
// If m is [MatchNotPresent], the value must be empty. If matcher is numeric
// (e.g. [MatchNumGT]), value must be a base-10 integer.
func (r *Record) AddObjectAttributeFilter(m Match, key, value string) {
r.addObjectFilter(m, 0, key, staticStringer(value))
}

// AddObjectVersionFilter adds filter by object version.
//
// The m must not be [MatchNotPresent] or numeric (e.g. [MatchNumGT]).
func (r *Record) AddObjectVersionFilter(m Match, v *version.Version) {
r.addObjectReservedFilter(m, fKeyObjVersion, staticStringer(version.EncodeToString(*v)))
}

// AddObjectIDFilter adds filter by object ID.
//
// The m must not be [MatchNotPresent] or numeric (e.g. [MatchNumGT]).
func (r *Record) AddObjectIDFilter(m Match, id oid.ID) {
r.addObjectReservedFilter(m, fKeyObjID, id)
}

// AddObjectContainerIDFilter adds filter by object container ID.
//
// The m must not be [MatchNotPresent] or numeric (e.g. [MatchNumGT]).
func (r *Record) AddObjectContainerIDFilter(m Match, id cid.ID) {
r.addObjectReservedFilter(m, fKeyObjContainerID, id)
}

// AddObjectOwnerIDFilter adds filter by object owner ID.
//
// The m must not be [MatchNotPresent] or numeric (e.g. [MatchNumGT]).
func (r *Record) AddObjectOwnerIDFilter(m Match, id *user.ID) {
r.addObjectReservedFilter(m, fKeyObjOwnerID, id)
}

// AddObjectCreationEpoch adds filter by object creation epoch.
//
// The m must not be [MatchNotPresent].
func (r *Record) AddObjectCreationEpoch(m Match, epoch uint64) {
r.addObjectReservedFilter(m, fKeyObjCreationEpoch, u64Stringer(epoch))
}

// AddObjectPayloadLengthFilter adds filter by object payload length.
//
// The m must not be [MatchNotPresent].
func (r *Record) AddObjectPayloadLengthFilter(m Match, size uint64) {
r.addObjectReservedFilter(m, fKeyObjPayloadLength, u64Stringer(size))
}

// AddObjectPayloadHashFilter adds filter by object payload hash value.
//
// The m must not be [MatchNotPresent] or numeric (e.g. [MatchNumGT]).
func (r *Record) AddObjectPayloadHashFilter(m Match, h checksum.Checksum) {
r.addObjectReservedFilter(m, fKeyObjPayloadHash, staticStringer(h.String()))
}

// AddObjectTypeFilter adds filter by object type.
//
// The m must not be [MatchNotPresent] or numeric (e.g. [MatchNumGT]).
func (r *Record) AddObjectTypeFilter(m Match, t object.Type) {
r.addObjectReservedFilter(m, fKeyObjType, staticStringer(t.EncodeToString()))
}

// AddObjectHomomorphicHashFilter adds filter by object payload homomorphic hash value.
//
// The m must not be [MatchNotPresent] or numeric (e.g. [MatchNumGT]).
func (r *Record) AddObjectHomomorphicHashFilter(m Match, h checksum.Checksum) {
r.addObjectReservedFilter(m, fKeyObjHomomorphicHash, staticStringer(h.String()))
}
Expand Down
69 changes: 51 additions & 18 deletions eacl/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package eacl

import (
"bytes"
"math/big"
)

// Validator is a tool that calculates
Expand All @@ -25,6 +26,9 @@ func NewValidator() *Validator {
//
// If no matching table entry is found or some filters are missing,
// ActionAllow is returned and the second return value is false.
//
// Note that if some rule imposes requirements on the format of values (like
// numeric), but they do not comply with it - such a rule does not match.
func (v *Validator) CalculateAction(unit *ValidationUnit) (Action, bool) {
for _, record := range unit.table.Records() {
// check type of operation
Expand Down Expand Up @@ -56,13 +60,22 @@ func (v *Validator) CalculateAction(unit *ValidationUnit) (Action, bool) {
// - negative value if the headers of at least one filter cannot be obtained.
func matchFilters(hdrSrc TypedHeaderSource, filters []Filter) int {
matched := 0
var nv, nf big.Int

nextFilter:
for _, filter := range filters {
headers, ok := hdrSrc.HeadersOfType(filter.From())
if !ok {
return -1
}

m := filter.Matcher()
if m == MatchNumGT || m == MatchNumGE || m == MatchNumLT || m == MatchNumLE {
if _, ok = nf.SetString(filter.Value(), 10); !ok {
continue
}
}

// get headers of filtering type
for _, header := range headers {
// prevent NPE
Expand All @@ -75,22 +88,53 @@ func matchFilters(hdrSrc TypedHeaderSource, filters []Filter) int {
continue
}

// get match function
matchFn, ok := mMatchFns[filter.Matcher()]
if !ok {
continue
}

// check match
if !matchFn(header, &filter) {
switch m {
default:
continue
case MatchNotPresent:
continue nextFilter
case MatchStringEqual:
if header.Value() != filter.Value() {
continue
}
case MatchStringNotEqual:
if header.Value() == filter.Value() {
continue
}
case MatchNumGT, MatchNumGE, MatchNumLT, MatchNumLE:
// TODO: big math simplifies coding but almost always not efficient
// enough, try to optimize
if _, ok = nv.SetString(header.Value(), 10); !ok {
continue
}
switch nf.Cmp(&nv) {
default:
continue // should never happen but just in case
case -1:
if m == MatchNumGT || m == MatchNumGE {
continue
}
case 0:
if m == MatchNumGT || m == MatchNumLT {
continue
}
case 1:
if m == MatchNumLT || m == MatchNumLE {
continue
}
}
}

// increment match counter
matched++

break
}

if m == MatchNotPresent {
matched++
}
}

return len(filters) - matched
Expand Down Expand Up @@ -123,14 +167,3 @@ func targetMatches(unit *ValidationUnit, record *Record) bool {

return false
}

// Maps match type to corresponding function.
var mMatchFns = map[Match]func(Header, *Filter) bool{
MatchStringEqual: func(header Header, filter *Filter) bool {
return header.Value() == filter.Value()
},

MatchStringNotEqual: func(header Header, filter *Filter) bool {
return header.Value() != filter.Value()
},
}
Loading

0 comments on commit cbaf23c

Please sign in to comment.