Skip to content

Commit

Permalink
Set atom support (#29)
Browse files Browse the repository at this point in the history
This add a new Set type allowing to store a sequence of Atoms, and 2 constraints allowing to ensure it contains only / doesn't contains any of the constraint Set values.
  • Loading branch information
daeMOn63 committed Nov 25, 2020
1 parent 874839f commit 19073ab
Show file tree
Hide file tree
Showing 13 changed files with 930 additions and 355 deletions.
59 changes: 59 additions & 0 deletions datalog/datalog.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,36 @@ const (
IDTypeString
IDTypeDate
IDTypeBytes
IDTypeSet
)

type ID interface {
Type() IDType
Equal(ID) bool
}

type Set []ID

func (Set) Type() IDType { return IDTypeSet }
func (v Set) Equal(t ID) bool {
c, ok := t.(Set)
if !ok || len(c) != len(v) {
return false
}

cmap := make(map[ID]struct{}, len(c))
for _, v := range c {
cmap[v] = struct{}{}
}

for _, id := range v {
if _, ok := cmap[id]; !ok {
return false
}
}
return true
}

type Symbol uint64

func (Symbol) Type() IDType { return IDTypeSymbol }
Expand Down Expand Up @@ -117,6 +140,15 @@ type IntegerInChecker struct {
}

func (m IntegerInChecker) Check(id ID) bool {
if set, ok := id.(Set); ok {
for _, subID := range set {
if !m.Check(subID) {
return false
}
}
return true
}

i, ok := id.(Integer)
if !ok {
return false
Expand Down Expand Up @@ -186,6 +218,15 @@ type StringInChecker struct {
}

func (m StringInChecker) Check(id ID) bool {
if set, ok := id.(Set); ok {
for _, subID := range set {
if !m.Check(subID) {
return false
}
}
return true
}

s, ok := id.(String)
if !ok {
return false
Expand Down Expand Up @@ -289,6 +330,15 @@ type BytesInChecker struct {
}

func (m BytesInChecker) Check(id ID) bool {
if set, ok := id.(Set); ok {
for _, subID := range set {
if !m.Check(subID) {
return false
}
}
return true
}

b, ok := id.(Bytes)
if !ok {
return false
Expand Down Expand Up @@ -316,6 +366,15 @@ type SymbolInChecker struct {
}

func (m SymbolInChecker) Check(id ID) bool {
if set, ok := id.(Set); ok {
for _, subID := range set {
if !m.Check(subID) {
return false
}
}
return true
}

sym, ok := id.(Symbol)
if !ok {
return false
Expand Down
189 changes: 189 additions & 0 deletions datalog/datalog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,149 @@ func TestResource(t *testing.T) {
}
}

func TestSet(t *testing.T) {
syms := &SymbolTable{}

ambient := syms.Insert("ambient")
arg := syms.Insert("resource")
method := syms.Insert("method")
read := syms.Insert("read")

canRead := syms.Insert("can_read")

testCases := []struct {
Desc string
Set Set
Checker Checker
CanRead bool
}{
{
Desc: "strings - allow read on file1 and file2",
Set: Set{String("file1"), String("file2")},
Checker: StringInChecker{
Set: map[String]struct{}{String("file1"): {}, String("file2"): {}, String("file3"): {}},
Not: false,
},
CanRead: true,
},
{
Desc: "strings - disallow read because of file3",
Set: Set{String("file1"), String("file2"), String("file3")},
Checker: StringInChecker{
Set: map[String]struct{}{String("file1"): {}, String("file2"): {}},
Not: false,
},
CanRead: false,
},
{
Desc: "strings - allow read on anything but file3",
Set: Set{String("file1"), String("file2"), String("file4")},
Checker: StringInChecker{
Set: map[String]struct{}{String("file3"): {}},
Not: true,
},
CanRead: true,
},
{
Desc: "strings - disallow read because of file4",
Set: Set{String("file1"), String("file2"), String("file4")},
Checker: StringInChecker{
Set: map[String]struct{}{String("file3"): {}, String("file4"): {}},
Not: true,
},
CanRead: false,
},
{
Desc: "integers - allow read",
Set: Set{Integer(10), Integer(11)},
Checker: IntegerInChecker{
Set: map[Integer]struct{}{Integer(10): {}, Integer(11): {}, Integer(12): {}},
Not: false,
},
CanRead: true,
},
{
Desc: "integers - disallow read",
Set: Set{Integer(10), Integer(11)},
Checker: IntegerInChecker{
Set: map[Integer]struct{}{Integer(10): {}},
Not: false,
},
CanRead: false,
},
{
Desc: "bytes - allow read",
Set: Set{Bytes{0x41, 0x42, 0x43}, Bytes{0xDE, 0xAD, 0xBE, 0xEF}},
Checker: BytesInChecker{
Set: map[string]struct{}{string([]byte{0x41, 0x42, 0x43}): {}, string([]byte{0xDE, 0xAD, 0xBE, 0xEF}): {}},
Not: false,
},
CanRead: true,
},
{
Desc: "bytes - disallow read",
Set: Set{Bytes{0x41, 0x42, 0x43}, Bytes{0xDE, 0xAD, 0xBE, 0xEF}},
Checker: BytesInChecker{
Set: map[string]struct{}{string([]byte{0xDE, 0xAD, 0xBE, 0xEF}): {}},
Not: true,
},
CanRead: false,
},
{
Desc: "symbols - allow read",
Set: Set{Symbol(1), Symbol(2)},
Checker: SymbolInChecker{
Set: map[Symbol]struct{}{Symbol(1): {}, Symbol(2): {}},
Not: false,
},
CanRead: true,
},
{
Desc: "symbols - disallow read",
Set: Set{Symbol(1), Symbol(2)},
Checker: SymbolInChecker{
Set: map[Symbol]struct{}{},
Not: false,
},
CanRead: false,
},
{
Desc: "mixed types can't read",
Set: Set{Symbol(1), Integer(2), String("abc")},
Checker: SymbolInChecker{
Set: map[Symbol]struct{}{Symbol(1): {}},
Not: false,
},
CanRead: false,
},
}

for _, testCase := range testCases {
t.Run(testCase.Desc, func(t *testing.T) {
w := NewWorld()
w.AddFact(Fact{Predicate: Predicate{Name: method, IDs: []ID{ambient, read}}})
w.AddFact(Fact{Predicate: Predicate{Name: arg, IDs: []ID{ambient, testCase.Set}}})

res := w.QueryRule(Rule{
Head: Predicate{canRead, []ID{Variable(0)}},
Body: []Predicate{
{method, []ID{ambient, Variable(0)}},
{arg, []ID{ambient, Variable(1)}},
},
Constraints: []Constraint{
{Name: Variable(1), Checker: testCase.Checker},
},
})

if testCase.CanRead {
require.Equal(t, 1, len(*res))
} else {
require.Equal(t, 0, len(*res))
}
})
}
}

func TestCheckers(t *testing.T) {
tests := []struct {
Checker
Expand Down Expand Up @@ -506,3 +649,49 @@ func TestSymbolTableClone(t *testing.T) {
require.Equal(t, &SymbolTable{"a", "b", "c"}, s)
require.Equal(t, &SymbolTable{"a", "b", "c", "d", "e"}, s2)
}

func TestSetEqual(t *testing.T) {
testCases := []struct {
desc string
s1 Set
s2 Set
equal bool
}{
{
desc: "equal with same values in same order",
s1: Set{String("a"), String("b"), String("c")},
s2: Set{String("a"), String("b"), String("c")},
equal: true,
},
{
desc: "equal with same values different order",
s1: Set{String("a"), String("b"), String("c")},
s2: Set{String("b"), String("c"), String("a")},
equal: true,
},
{
desc: "not equal when length mismatch",
s1: Set{String("a"), String("b"), String("c")},
s2: Set{String("a"), String("b")},
equal: false,
},
{
desc: "not equal when length mismatch",
s1: Set{String("a"), String("b"), String("c")},
s2: Set{String("a"), String("b"), String("c"), String("d")},
equal: false,
},
{
desc: "not equal when same length but different values",
s1: Set{String("a"), String("b"), String("c")},
s2: Set{String("a"), String("b"), String("d")},
equal: false,
},
}

for _, testCase := range testCases {
t.Run(testCase.desc, func(t *testing.T) {
require.Equal(t, testCase.equal, testCase.s1.Equal(testCase.s2))
})
}
}
10 changes: 8 additions & 2 deletions parser/GRAMMAR.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ This document describes the currently supported Datalog grammar.

## Atom

Represents a Datalog type, can be one of: symbol, variable, integer, string, date, or bytes.
Represents a Datalog type, can be one of: symbol, variable, integer, string, date, bytes, or set.

- symbol is prefixed with a `#` sign followed by text, e.g. `#read`
- variable is prefixed with a `$` sign followed by an unsigned 32bit base-10 integer, e.g. `$0`
- integer is any base-10 int64
- string is any utf8 character sequence, between double quotes, e.g. `"/path/to/file.txt"`
- date is RFC3339 encoded, e.g. `2006-01-02T15:04:05Z07:00`
- bytes is an hexadecimal encoded string, prefixed with a `hex:` sequence
- set is a sequence of any of the above types, except variable, between brackets, e.g. `[#read, #write, #update, "file1", "file2"]`

## Predicate

Expand Down Expand Up @@ -56,6 +57,11 @@ Constraints allows performing checks on a variable, below is the list of availab
- In: `$0 in ["hex:3df97fb5", "hex:4a8feed1"]`
- Not in: `$0 not in ["hex:3df97fb5", "hex:4a8feed1"]`

### Set

- Any: `$0 in [#read, #write]`
- None: `$0 not in [#read, #write]`

## Fact

A fact is a single predicate that does not contain any variables, e.g. `right(#authority, "file1.txt", #read)`.
Expand All @@ -71,4 +77,4 @@ e.g. `*right(#authority, $1, #read) <- resource(#ambient, $1), owner(#ambient, $

# Caveat

A caveat is list of rules with the format: `[ rule0 || rule1 || ... || ruleN ]`
A caveat is a list of rules with the format: `[ rule0 || rule1 || ... || ruleN ]`
Loading

0 comments on commit 19073ab

Please sign in to comment.