diff --git a/policyutils/rtree/rtreebuilder.go b/policyutils/rtree/rtreebuilder.go index bfc9243..ceb242d 100644 --- a/policyutils/rtree/rtreebuilder.go +++ b/policyutils/rtree/rtreebuilder.go @@ -4,6 +4,8 @@ import ( "bytes" "context" "fmt" + "math/bits" + "sort" "github.com/davecgh/go-spew/spew" "github.com/immesys/wave/engine" @@ -165,6 +167,412 @@ type Node struct { Edges []*Edge } +// SolutionExists is utility function that checks if a solution is possible +//complexity is O(n) where n is the size of the solution slice +func (n *Node) SolutionExists(v uint64) bool { + var i uint64 + for _, sol := range n.Solutions { + i = i | sol.Bits + } + return i&v == v +} + +// GetAllSolutionsForOnePermission is a utility function that returns all solutions in n that match a given permission +func (n *Node) GetAllSolutionsForOnePermission(v uint64) []*Solution { + rv := []*Solution{} + for _, sol := range n.Solutions { + if sol.Bits == v { + rv = append(rv, sol) + } + } + return rv +} + +// GetAllSolutionsForPermissions is a utility function that takes a subset of permissions that matches a given target +// and finds all solutions in n.Solutions that combine these permissions +// if there are solutions in n that have the same permission there will be multiple solutions +func (n *Node) GetAllSolutionsForPermissions(arr []uint64, v uint64) []*Solution { + s_arr := len(arr) + rv := []*Solution{} + if s_arr <= 0 { + return rv + } + twodsolns := make([][]*Solution, s_arr, s_arr) + for i, p := range arr { + twodsolns[i] = n.GetAllSolutionsForOnePermission(p) + } + // keep track of the size of each inner array + sizearr := make([]int, s_arr, s_arr) + // keep track of the index of each inner array which will be used + // to make the next combination + cntarr := make([]int, s_arr, s_arr) + + // Discover the size of each inner array and populate sizearr. + // Also calculate the total number of combinations possible using the + // inner array sizes. + total := 1 + for i := range arr { + sizearr[i] = len(twodsolns[i]) + total *= len(twodsolns[i]) + } + for cntdn := total; cntdn > 0; cntdn-- { + // Run through the inner arrays, grabbing the member from the index + // specified by the cntarr for each inner array, and build a + // combination solution. + combination := []*Solution{} + for i := 0; i < s_arr; i++ { + combination = append(combination, twodsolns[i][cntarr[i]]) + } + if len(combination) > 0 { + csol := combination[0] + ADDSOLS: + for i := 1; i < len(combination); i++ { + csol = csol.Combine(combination[i]) + if csol == nil { + break ADDSOLS + } + } + if csol != nil && ((csol.Bits & v) == v) { + rv = append(rv, csol) + } + } + // Now we need to increment the cntarr so that the next + // combination is taken on the next iteration of this loop. + for incidx := s_arr - 1; incidx >= 0; incidx-- { + if cntarr[incidx]+1 < sizearr[incidx] { + cntarr[incidx]++ + // None of the indices of higher significance need to be + // incremented, so jump out of this for loop at this point. + break + } + // The index at this position is at its max value, so zero it + // and continue this loop to increment the index which is more + // significant than this one. + cntarr[incidx] = 0 + } + } + return rv +} + +//BitBacktrackSubsetSum returns a 2d slice where each slice is a subset solution +//for a given target using backtracking +func BitBacktrackSubsetSum(arr []uint64, n int, t uint64) [][]uint64 { + // get sum (or) of all elements from X0->Xn and pass as array + arrsum := make([]uint64, n) + sum := uint64(0) + for i := 0; i < n; i++ { + sum = sum | arr[i] + arrsum[i] = sum + } + if t&arrsum[n-1] != t { + return nil + } + return BitBacktrackRecurse(arr, arrsum, n-1, t) +} + +//BitBacktrackRecurse is the internal recursive function for BitBacktrackSubsetSum +func BitBacktrackRecurse(arr []uint64, sum []uint64, n int, t uint64) [][]uint64 { + // if zero target or empty set + if t == uint64(0) || n < 0 { + return nil + } + + // if the set has a single element return it if it matches the target + if n == 0 { + if arr[n]&t == t { + return [][]uint64{[]uint64{arr[n]}} + } + return nil + } + + // if target is greater than sum return + if sum[n]&t != t { + return nil + } + + // if target equals Xn append Xn to solution set and return BitSubsetSum([X0,Xn-1]) + var s [][]uint64 + if arr[n]&t == t { + s = append(s, []uint64{arr[n]}) + if t == sum[n-1]&t { + y := BitBacktrackRecurse(arr, sum, n-1, t) + if y != nil { + for _, i := range y { + s = append(s, i) + } + } + } + return s + } + + // if Xn adds more permission then try including it + if arr[n]&t != uint64(0) { + rt := (t &^ arr[n]) + if sum[n-1]&rt == rt { + y := BitBacktrackRecurse(arr, sum, n-1, rt) + if y != nil { + for _, i := range y { + i = append(i, arr[n]) + s = append(s, i) + } + } + } + } + if t == sum[n-1]&t { + y := BitBacktrackRecurse(arr, sum, n-1, t) + if y != nil { + for _, i := range y { + s = append(s, i) + } + } + } + + return s +} + +//BitDPSubsetSum returns a 2d slice where each slice is a subset solution +//for a given target using dynamic programming +func BitDPSubsetSum(arr []uint64, n int, t uint64) [][]uint64 { + // if zero target or empty set + if t == 0 || n <= 0 { + return nil + } + + // if target is greater than sum of all permissions return + sum := uint64(0) + for i := 0; i < n; i++ { + sum = sum | arr[i] + } + if t&sum != t { + return nil + } + + // add different permission combinations that form the target permission + var tarr []uint64 + tmap := make(map[uint64]int) + + idx := 0 + for p := uint64(0); p < t+1; p++ { + if p&^t == uint64(0) { + tmap[p] = idx + idx++ + tarr = append(tarr, p) + } + } + tlen := len(tarr) + dp := make([][]bool, n) + for j, _ := range dp { + dp[j] = make([]bool, tlen) + } + + // 0 permission can be made with an empty set + for i := 0; i < n; i++ { + dp[i][0] = true + } + + // fill in first row if permission can be made using just first element only then set to true + for j := 1; j < tlen; j++ { + dp[0][j] = (arr[0]&tarr[j] == tarr[j]) + } + + // fill in the rest of dp + for i := 1; i < n; i++ { + for j := 1; j < tlen; j++ { + if arr[i]&tarr[j] == 0 { //if permission cannot be made using this element then set to above value + dp[i][j] = dp[i-1][j] + } else { //if all or part of the permission can be made using this element set to above value or to value of the remaining permission + // fmt.Printf("t:%04b arr:%04b %4b\n", t&^tarr[i]) + dp[i][j] = dp[i-1][j] || dp[i-1][tmap[tarr[j]&^arr[i]]] + } + } + } + if dp[n-1][tlen-1] == false { + return nil + } + return BitDPRecurse(arr, n-1, t, dp, tmap) +} + +//BitDPRecurse is the internal recursive function for BitDPSubsetSum +func BitDPRecurse(arr []uint64, n int, t uint64, dp [][]bool, tmap map[uint64]int) [][]uint64 { + // if empty set return + if n < 0 { + return nil + } + + // if target is 0 return empty set + if t == 0 { + return [][]uint64{[]uint64{}} + } + // if there is a single element return it if it matches the target + if n == 0 && dp[n][tmap[t]] { + return [][]uint64{[]uint64{arr[n]}} + } + + var s [][]uint64 + //if target can be made using this element try including it + if (arr[n]&t != 0 || arr[n]&t == t) && dp[n-1][tmap[t&^arr[n]]] { + y := BitDPRecurse(arr, n-1, t&^arr[n], dp, tmap) + if y != nil { + for _, i := range y { + i = append(i, arr[n]) + s = append(s, i) + } + } + } + + // don't include this element + if dp[n-1][tmap[t]] { + y := BitDPRecurse(arr, n-1, t, dp, tmap) + if y != nil { + for _, i := range y { + s = append(s, i) + } + } + } + return s +} + +//FastestSolutionsFor returns combination of max TTL and max permission but not min weight +//complexity is in O(n*l) + O(n*lg(n)) + O(n) where n is the size of the solution slice, l is the size of the permission vector +func (n *Node) FastestSolutionsFor(v uint64) []*Solution { + // if no solution is possible return empty slice + if !n.SolutionExists(v) { + return []*Solution{} + } + trv := []*Solution{} + var rvcnt map[uint64]int + + // loop over solutions and add max TTL and count for each bit O(n*l) + for i := uint64(1); i <= v; i = i << 1 { + mx := 0 + //find max TTL for each bit + for _, sol := range n.Solutions { + if sol.Bits&i == i { + if sol.TTL > mx { + mx = sol.TTL + } + } + } + //add all solutions with max TTL and increment count if solution is max for more than one bit + for _, sol := range n.Solutions { + if sol.TTL == mx && sol.Bits&i == i { + if rvcnt[sol.Bits] == 0 { + trv = append(trv, sol) + rvcnt[sol.Bits] = 1 + } else { + rvcnt[sol.Bits]++ + } + } + } + } + + //reduce trv list by sorting and returning max TTL and max count for tied TTL + // sort trv list by TTL then by count O(n*lg(n)) + sort.Slice(trv, func(i, j int) bool { + if trv[i].TTL < trv[j].TTL { + return true + } + if trv[i].TTL > trv[j].TTL { + return false + } + return rvcnt[trv[i].Bits] < rvcnt[trv[j].Bits] + }) + + // add elements while target is not met O(n) + rv := []*Solution{} + var i uint64 + for st := len(trv) - 1; st >= 0; st-- { + if i&v == v { + break + } + rv = append(rv, trv[st]) + i = i | trv[st].Bits + } + + // combine all elements to one solution and return it + // TODO: Michael is this the proper way to combine multiple solutions into a single one? + if len(rv) > 0 { + csol := rv[0] + for j := 1; j < len(rv); j++ { + csol = csol.Combine(rv[j]) + // This should never happen + if csol == nil { + return []*Solution{} + } + } + // This should never happen + if (csol.Bits & v) != v { + return []*Solution{} + } + //fmt.Printf("Node %s FastestSolutionsFor %x:\n", n.Ref(), v) + //fmt.Printf(" - %s\n", csol.String()) + return []*Solution{csol} + } + return []*Solution{} +} + +//BestSolutionsForSmallNorT returns the best solutions for either a small list of permissions of size n +//or a large list of size n but with a small target v +//if bt is true it will use the backtracking algorithm for small n +//if bt is false it will use the dynamic programming algorithm for small v +func (n *Node) BestSolutionsForSmallNorT(v uint64, bt bool) []*Solution { + // if no solution is possible return empty slice + if !n.SolutionExists(v) { + return []*Solution{} + } + // trv := []*Solution{} + // extract bit array from n.Solutions + // n.Solutions might have multiple solutions with the same permission, only add unique ones + u := make([]uint64, 0, len(n.Solutions)) + m := make(map[uint64]bool) + for _, sol := range n.Solutions { + if _, ok := m[sol.Bits]; !ok { + m[sol.Bits] = true + u = append(u, sol.Bits) + } + } + // sort array by largest permissions to speed up subsetsum and avoid duplicates + sort.Slice(u, func(i, j int) bool { + if bits.OnesCount64(u[i]) < bits.OnesCount64(u[j]) { + return true + } + return false + }) + + // do bt or dp BitSubsetSum based on bool flag + var y [][]uint64 + if bt { + y = BitBacktrackSubsetSum(u, len(u), v) + } else { + y = BitDPSubsetSum(u, len(u), v) + } + // if no solution found for subsetsum return an empty set + if y == nil { + return []*Solution{} + } + // create solutions for each subset and add them to the list of possible solutions + rv := []*Solution{} + + for _, i := range y { + solns := n.GetAllSolutionsForPermissions(i, v) + for _, sol := range solns { + rv = append(rv, sol) + } + } + + //fmt.Printf("Node %s BestSolutionsFor %x, prereduction:\n", n.Ref(), v) + for _, _ = range rv { + //fmt.Printf(" - %s\n", el.String()) + } + // reduce list + reduced := reduceSolutionList(rv) + //fmt.Printf("Post reduction:\n") + for _, _ = range reduced { + //fmt.Printf(" - %s\n", el.String()) + } + return reduced +} + func (n *Node) BestSolutionsFor(v uint64) []*Solution { //Placeholder: calculate all solutions requiring 1 or 2 combos rv := []*Solution{} @@ -193,6 +601,45 @@ func (n *Node) BestSolutionsFor(v uint64) []*Solution { return reduced } +// BruteForceSolutionsFor finds all combinations and adds them if they match the target permission +func (n *Node) BruteForceSolutionsFor(v uint64) []*Solution { + rv := []*Solution{} + s := len(n.Solutions) + for num := 0; num < (1 << uint(s)); num++ { + combination := []*Solution{} + for ndx := 0; ndx < s; ndx++ { + // (is the bit "on" in this number?) + if num&(1< 0 { + csol := combination[0] + ADDSOLS: + for i := 1; i < len(combination); i++ { + csol = csol.Combine(combination[i]) + if csol == nil { + break ADDSOLS + } + } + if csol != nil && ((csol.Bits & v) == v) { + rv = append(rv, csol) + } + } + } + //fmt.Printf("Node %s BestSolutionsFor %x, prereduction:\n", n.Ref(), v) + for _, _ = range rv { + //fmt.Printf(" - %s\n", el.String()) + } + reduced := reduceSolutionList(rv) + //fmt.Printf("Post reduction:\n") + for _, _ = range reduced { + //fmt.Printf(" - %s\n", el.String()) + } + return reduced +} + func (e *Edge) Dst() *Node { subject, _ := e.LRes.Attestation.Subject() rv, ok := e.tb.nodes[subject.MultihashString()]