Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

eacl: Support numeric matchers #554

Merged
merged 2 commits into from
Feb 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
carpawell marked this conversation as resolved.
Show resolved Hide resolved
// 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
Loading