Skip to content

Commit 963ee5b

Browse files
committed
[FAB-7378] tree utilities for service discovery
This change set adds tree utilities for service discovery, in order to do signature policy analysis. Tests with code coverage of 100% are included. Change-Id: I1d42e2e45f4b25f953bb5adceb73245186a1e5d2 Signed-off-by: yacovm <yacovm@il.ibm.com>
1 parent 7cbba98 commit 963ee5b

File tree

6 files changed

+514
-0
lines changed

6 files changed

+514
-0
lines changed

common/graph/choose.go

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
Copyright IBM Corp. All Rights Reserved.
3+
4+
SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
package graph
8+
9+
type orderedSet struct {
10+
elements []interface{}
11+
}
12+
13+
func (s *orderedSet) add(o interface{}) {
14+
s.elements = append(s.elements, o)
15+
}
16+
17+
type indiceSet struct {
18+
indices []int
19+
}
20+
21+
type indiceSets []*indiceSet
22+
23+
func factorial(n int) int {
24+
m := 1
25+
for i := 1; i <= n; i++ {
26+
m *= i
27+
}
28+
return m
29+
}
30+
31+
func nChooseK(n, k int) int {
32+
a := factorial(n)
33+
b := factorial(n-k) * factorial(k)
34+
return a / b
35+
}
36+
37+
func chooseKoutOfN(n, k int) indiceSets {
38+
var res indiceSets
39+
subGroups := &orderedSet{}
40+
choose(n, k, 0, nil, subGroups)
41+
for _, el := range subGroups.elements {
42+
res = append(res, el.(*indiceSet))
43+
}
44+
return res
45+
}
46+
47+
func choose(n int, targetAmount int, i int, currentSubGroup []int, subGroups *orderedSet) {
48+
// Check if we have enough elements in our current subgroup
49+
if len(currentSubGroup) == targetAmount {
50+
subGroups.add(&indiceSet{indices: currentSubGroup})
51+
return
52+
}
53+
// Return early if not enough remaining candidates to pick from
54+
itemsLeftToPick := n - i
55+
if targetAmount-len(currentSubGroup) > itemsLeftToPick {
56+
return
57+
}
58+
// We either pick the current element
59+
choose(n, targetAmount, i+1, append(currentSubGroup, i), subGroups)
60+
// Or don't pick it
61+
choose(n, targetAmount, i+1, currentSubGroup, subGroups)
62+
}

common/graph/choose_test.go

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
Copyright IBM Corp. All Rights Reserved.
3+
4+
SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
package graph
8+
9+
import (
10+
"testing"
11+
12+
"github.com/stretchr/testify/assert"
13+
)
14+
15+
func TestChoose(t *testing.T) {
16+
assert.Equal(t, 24, factorial(4))
17+
assert.Equal(t, 1, factorial(0))
18+
assert.Equal(t, 1, factorial(1))
19+
assert.Equal(t, 15504, nChooseK(20, 5))
20+
for n := 1; n < 20; n++ {
21+
for k := 1; k < n; k++ {
22+
g := chooseKoutOfN(n, k)
23+
assert.Equal(t, nChooseK(n, k), len(g), "n=%d, k=%d", n, k)
24+
}
25+
}
26+
}

common/graph/perm.go

+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/*
2+
Copyright IBM Corp. All Rights Reserved.
3+
4+
SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
package graph
8+
9+
// treePermutations represents possible permutations
10+
// of a tree
11+
type treePermutations struct {
12+
originalRoot *TreeVertex // The root vertex of all sub-trees
13+
permutations []*TreeVertex // The accumulated permutations
14+
descendantPermutations map[*TreeVertex][][]*TreeVertex // Defines the combinations of sub-trees based on the threshold of the current vertex
15+
}
16+
17+
// newTreePermutation creates a new treePermutations object with a given root vertex
18+
func newTreePermutation(root *TreeVertex) *treePermutations {
19+
return &treePermutations{
20+
descendantPermutations: make(map[*TreeVertex][][]*TreeVertex),
21+
originalRoot: root,
22+
permutations: []*TreeVertex{root},
23+
}
24+
}
25+
26+
// permute returns Trees that their vertices and edges all exist in the original tree who's vertex
27+
// is the 'originalRoot' field of the treePermutations
28+
func (tp *treePermutations) permute() []*Tree {
29+
tp.computeDescendantPermutations()
30+
31+
it := newBFSIterator(tp.originalRoot)
32+
for {
33+
v := it.Next()
34+
if v == nil {
35+
break
36+
}
37+
38+
if len(v.Descendants) == 0 {
39+
continue
40+
}
41+
42+
// Iterate over all permutations where v exists
43+
// and separate them to 2 sets: a indiceSet where it exists and a indiceSet where it doesn't
44+
var permutationsWhereVexists []*TreeVertex
45+
var permutationsWhereVdoesntExist []*TreeVertex
46+
for _, perm := range tp.permutations {
47+
if perm.Exists(v.Id) {
48+
permutationsWhereVexists = append(permutationsWhereVexists, perm)
49+
} else {
50+
permutationsWhereVdoesntExist = append(permutationsWhereVdoesntExist, perm)
51+
}
52+
}
53+
54+
// Remove the permutations where v exists from the permutations
55+
tp.permutations = permutationsWhereVdoesntExist
56+
57+
// Next, we replace each occurrence of a permutation where v exists,
58+
// with multiple occurrences of its descendants permutations
59+
for _, perm := range permutationsWhereVexists {
60+
// For each permutation of v's descendants, clone the graph
61+
// and create a new graph with v replaced with the permutated graph
62+
// of v connected to the descendant permutation
63+
for _, permutation := range tp.descendantPermutations[v] {
64+
subGraph := &TreeVertex{
65+
Id: v.Id,
66+
Data: v.Data,
67+
Descendants: permutation,
68+
}
69+
newTree := perm.Clone()
70+
newTree.replace(v.Id, subGraph)
71+
// Add the new option to the permutations
72+
tp.permutations = append(tp.permutations, newTree)
73+
}
74+
}
75+
}
76+
77+
res := make([]*Tree, len(tp.permutations))
78+
for i, perm := range tp.permutations {
79+
res[i] = perm.ToTree()
80+
}
81+
return res
82+
}
83+
84+
// computeDescendantPermutations computes all possible combinations of sub-trees
85+
// for all vertices, based on the thresholds.
86+
func (tp *treePermutations) computeDescendantPermutations() {
87+
it := newBFSIterator(tp.originalRoot)
88+
for {
89+
v := it.Next()
90+
if v == nil {
91+
return
92+
}
93+
94+
// Skip leaves
95+
if len(v.Descendants) == 0 {
96+
continue
97+
}
98+
99+
// Iterate over all options of selecting the threshold out of the descendants
100+
for _, el := range chooseKoutOfN(len(v.Descendants), v.Threshold) {
101+
// And for each such option, append it to the current TreeVertex
102+
tp.descendantPermutations[v] = append(tp.descendantPermutations[v], v.selectDescendants(el.indices))
103+
}
104+
}
105+
}
106+
107+
// selectDescendants returns a subset of descendants according to given indices
108+
func (v *TreeVertex) selectDescendants(indices []int) []*TreeVertex {
109+
r := make([]*TreeVertex, len(indices))
110+
i := 0
111+
for _, index := range indices {
112+
r[i] = v.Descendants[index]
113+
i++
114+
}
115+
return r
116+
}

common/graph/perm_test.go

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
Copyright IBM Corp. All Rights Reserved.
3+
4+
SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
package graph
8+
9+
import (
10+
"testing"
11+
12+
"github.com/stretchr/testify/assert"
13+
)
14+
15+
func TestF(t *testing.T) {
16+
vR := NewTreeVertex("r", nil)
17+
vR.Threshold = 2
18+
19+
vD := vR.AddDescendant(NewTreeVertex("D", nil))
20+
vD.Threshold = 2
21+
for _, id := range []string{"A", "B", "C"} {
22+
vD.AddDescendant(NewTreeVertex(id, nil))
23+
}
24+
25+
vE := vR.AddDescendant(NewTreeVertex("E", nil))
26+
vE.Threshold = 2
27+
for _, id := range []string{"a", "b", "c"} {
28+
vE.AddDescendant(NewTreeVertex(id, nil))
29+
}
30+
31+
vF := vR.AddDescendant(NewTreeVertex("F", nil))
32+
vF.Threshold = 2
33+
for _, id := range []string{"1", "2", "3"} {
34+
vF.AddDescendant(NewTreeVertex(id, nil))
35+
}
36+
37+
permutations := vR.ToTree().Permute()
38+
// For a sub-tree with r-(D,E) we have 9 combinations (3 combinations of each sub-tree where D and E are the roots)
39+
// For a sub-tree with r-(D,F) we have 9 combinations from the same logic
40+
// For a sub-tree with r-(E,F) we have 9 combinations too
41+
// Total 27 combinations
42+
assert.Equal(t, 27, len(permutations))
43+
44+
listCombination := func(i Iterator) []string {
45+
var traversal []string
46+
for {
47+
v := i.Next()
48+
if v == nil {
49+
break
50+
}
51+
traversal = append(traversal, v.Id)
52+
}
53+
return traversal
54+
}
55+
56+
// First combination is a left most traversal on the combination graph
57+
expectedScan := []string{"r", "D", "E", "A", "B", "a", "b"}
58+
assert.Equal(t, expectedScan, listCombination(permutations[0].BFS()))
59+
60+
// Last combination is a right most traversal on the combination graph
61+
expectedScan = []string{"r", "E", "F", "b", "c", "2", "3"}
62+
assert.Equal(t, expectedScan, listCombination(permutations[26].BFS()))
63+
}

0 commit comments

Comments
 (0)