package sbom
import (
"fmt"
"strings"
"time"
)
const debugMode bool = true
type Result struct {
occurrences map[string][]int
}
func Sbom(t string, p []string) Result {
startTime := time.Now()
occurrences := make(map[int][]int)
lmin := computeMinLength(p)
or, f := buildOracleMultiple(reverseAll(trimToLength(p, lmin)))
if debugMode {
fmt.Printf("\n\nSBOM:\n\n")
}
pos := 0
for pos <= len(t)-lmin {
current := 0
j := lmin
if debugMode {
fmt.Printf("Position: %d, we read: ", pos)
}
for j >= 1 && stateExists(current, or) {
if debugMode {
fmt.Printf("%c", t[pos+j-1])
}
current = getTransition(current, t[pos+j-1], or)
if debugMode {
if current == -1 {
fmt.Printf(" (FAIL) ")
} else {
fmt.Printf(", ")
}
}
j--
}
if debugMode {
fmt.Printf("in the factor oracle. \n")
}
word := getWord(pos, pos+lmin-1, t)
if stateExists(current, or) && j == 0 && strings.HasPrefix(word, getCommonPrefix(p, f[current], lmin)) {
for i := range f[current] {
if p[f[current][i]] == getWord(pos, pos-1+len(p[f[current][i]]), t) {
if debugMode {
fmt.Printf("- Occurence, %q = %q\n", p[f[current][i]], word)
}
newOccurences := intArrayCapUp(occurrences[f[current][i]])
occurrences[f[current][i]] = newOccurences
occurrences[f[current][i]][len(newOccurences)-1] = pos
}
}
j = 0
}
pos = pos + j + 1
}
elapsed := time.Since(startTime)
fmt.Printf("\n\nElapsed %f secs\n", elapsed.Seconds())
var resultOccurrences = make(map[string][]int)
for key, value := range occurrences {
resultOccurrences[p[key]] = value
}
return Result{
resultOccurrences,
}
}
func buildOracleMultiple(p []string) (orToReturn map[int]map[uint8]int, f map[int][]int) {
orTrie, stateIsTerminal, f := constructTrie(p)
s := make([]int, len(stateIsTerminal))
i := 0
orToReturn = orTrie
s[i] = -1
if debugMode {
fmt.Printf("\n\nOracle construction: \n")
}
for current := 1; current < len(stateIsTerminal); current++ {
o, parent := getParent(current, orTrie)
down := s[parent]
for stateExists(down, orToReturn) && getTransition(down, o, orToReturn) == -1 {
createTransition(down, o, current, orToReturn)
down = s[down]
}
if stateExists(down, orToReturn) {
s[current] = getTransition(down, o, orToReturn)
} else {
s[current] = i
}
}
return orToReturn, f
}
func constructTrie(p []string) (trie map[int]map[uint8]int, stateIsTerminal []bool, f map[int][]int) {
trie = make(map[int]map[uint8]int)
stateIsTerminal = make([]bool, 1)
f = make(map[int][]int)
state := 1
if debugMode {
fmt.Printf("\n\nTrie construction: \n")
}
createNewState(0, trie)
for i := 0; i < len(p); i++ {
current := 0
j := 0
for j < len(p[i]) && getTransition(current, p[i][j], trie) != -1 {
current = getTransition(current, p[i][j], trie)
j++
}
for j < len(p[i]) {
stateIsTerminal = boolArrayCapUp(stateIsTerminal)
createNewState(state, trie)
stateIsTerminal[state] = false
createTransition(current, p[i][j], state, trie)
current = state
j++
state++
}
if stateIsTerminal[current] {
newArray := intArrayCapUp(f[current])
newArray[len(newArray)-1] = i
f[current] = newArray
if debugMode {
fmt.Printf(" and %d", i)
}
} else {
stateIsTerminal[current] = true
f[current] = []int{i}
if debugMode {
fmt.Printf("\n%d is terminal for word number %d", current, i)
}
}
}
return trie, stateIsTerminal, f
}
func intArrayCapUp(old []int) (new []int) {
new = make([]int, cap(old)+1)
copy(new, old)
return new
}
func boolArrayCapUp(old []bool) (new []bool) {
new = make([]bool, cap(old)+1)
copy(new, old)
return new
}
func reverseAll(s []string) (reversed []string) {
reversed = make([]string, len(s))
for i := 0; i < len(s); i++ {
reversed[i] = reverse(s[i])
}
return reversed
}
func reverse(s string) string {
l := len(s)
m := make([]rune, l)
for _, c := range s {
l--
m[l] = c
}
return string(m)
}
func getCommonPrefix(p []string, f []int, lmin int) string {
r := []rune(p[f[0]])
newR := make([]rune, lmin)
for j := 0; j < lmin; j++ {
newR[j] = r[j]
}
return string(newR)
}
func trimToLength(p []string, length int) (trimmedP []string) {
trimmedP = make([]string, len(p))
for i := range p {
r := []rune(p[i])
newR := make([]rune, length)
for j := 0; j < length; j++ {
newR[j] = r[j]
}
trimmedP[i] = string(newR)
}
return trimmedP
}
func getWord(begin, end int, t string) string {
for end >= len(t) {
return ""
}
d := make([]uint8, end-begin+1)
for j, i := 0, begin; i <= end; i, j = i+1, j+1 {
d[j] = t[i]
}
return string(d)
}
func computeMinLength(p []string) (lmin int) {
lmin = len(p[0])
for i := 1; i < len(p); i++ {
if len(p[i]) < lmin {
lmin = len(p[i])
}
}
return lmin
}
func getParent(state int, at map[int]map[uint8]int) (uint8, int) {
for beginState, transitions := range at {
for c, endState := range transitions {
if endState == state {
return c, beginState
}
}
}
return 0, 0
}
func createNewState(state int, at map[int]map[uint8]int) {
at[state] = make(map[uint8]int)
if debugMode {
fmt.Printf("\ncreated state %d", state)
}
}
func createTransition(fromState int, overChar uint8, toState int, at map[int]map[uint8]int) {
at[fromState][overChar] = toState
if debugMode {
fmt.Printf("\n σ(%d,%c)=%d;", fromState, overChar, toState)
}
}
func getTransition(fromState int, overChar uint8, at map[int]map[uint8]int) (toState int) {
if !stateExists(fromState, at) {
return -1
}
toState, ok := at[fromState][overChar]
if ok {
return -1
}
return toState
}
func stateExists(state int, at map[int]map[uint8]int) bool {
_, ok := at[state]
if !ok || state == -1 || at[state] == nil {
return false
}
return true
}