github.com/chwjbn/xclash@v0.2.0/config/utils.go (about) 1 package config 2 3 import ( 4 "fmt" 5 "strings" 6 7 "github.com/chwjbn/xclash/adapter/outboundgroup" 8 "github.com/chwjbn/xclash/common/structure" 9 ) 10 11 func trimArr(arr []string) (r []string) { 12 for _, e := range arr { 13 r = append(r, strings.Trim(e, " ")) 14 } 15 return 16 } 17 18 // Check if ProxyGroups form DAG(Directed Acyclic Graph), and sort all ProxyGroups by dependency order. 19 // Meanwhile, record the original index in the config file. 20 // If loop is detected, return an error with location of loop. 21 func proxyGroupsDagSort(groupsConfig []map[string]interface{}) error { 22 type graphNode struct { 23 indegree int 24 // topological order 25 topo int 26 // the original data in `groupsConfig` 27 data map[string]interface{} 28 // `outdegree` and `from` are used in loop locating 29 outdegree int 30 option *outboundgroup.GroupCommonOption 31 from []string 32 } 33 34 decoder := structure.NewDecoder(structure.Option{TagName: "group", WeaklyTypedInput: true}) 35 graph := make(map[string]*graphNode) 36 37 // Step 1.1 build dependency graph 38 for _, mapping := range groupsConfig { 39 option := &outboundgroup.GroupCommonOption{} 40 if err := decoder.Decode(mapping, option); err != nil { 41 return fmt.Errorf("ProxyGroup %s: %s", option.Name, err.Error()) 42 } 43 44 groupName := option.Name 45 if node, ok := graph[groupName]; ok { 46 if node.data != nil { 47 return fmt.Errorf("ProxyGroup %s: duplicate group name", groupName) 48 } 49 node.data = mapping 50 node.option = option 51 } else { 52 graph[groupName] = &graphNode{0, -1, mapping, 0, option, nil} 53 } 54 55 for _, proxy := range option.Proxies { 56 if node, ex := graph[proxy]; ex { 57 node.indegree++ 58 } else { 59 graph[proxy] = &graphNode{1, -1, nil, 0, nil, nil} 60 } 61 } 62 } 63 // Step 1.2 Topological Sort 64 // topological index of **ProxyGroup** 65 index := 0 66 queue := make([]string, 0) 67 for name, node := range graph { 68 // in the beginning, put nodes that have `node.indegree == 0` into queue. 69 if node.indegree == 0 { 70 queue = append(queue, name) 71 } 72 } 73 // every element in queue have indegree == 0 74 for ; len(queue) > 0; queue = queue[1:] { 75 name := queue[0] 76 node := graph[name] 77 if node.option != nil { 78 index++ 79 groupsConfig[len(groupsConfig)-index] = node.data 80 if len(node.option.Proxies) == 0 { 81 delete(graph, name) 82 continue 83 } 84 85 for _, proxy := range node.option.Proxies { 86 child := graph[proxy] 87 child.indegree-- 88 if child.indegree == 0 { 89 queue = append(queue, proxy) 90 } 91 } 92 } 93 delete(graph, name) 94 } 95 96 // no loop is detected, return sorted ProxyGroup 97 if len(graph) == 0 { 98 return nil 99 } 100 101 // if loop is detected, locate the loop and throw an error 102 // Step 2.1 rebuild the graph, fill `outdegree` and `from` filed 103 for name, node := range graph { 104 if node.option == nil { 105 continue 106 } 107 108 if len(node.option.Proxies) == 0 { 109 continue 110 } 111 112 for _, proxy := range node.option.Proxies { 113 node.outdegree++ 114 child := graph[proxy] 115 if child.from == nil { 116 child.from = make([]string, 0, child.indegree) 117 } 118 child.from = append(child.from, name) 119 } 120 } 121 // Step 2.2 remove nodes outside the loop. so that we have only the loops remain in `graph` 122 queue = make([]string, 0) 123 // initialize queue with node have outdegree == 0 124 for name, node := range graph { 125 if node.outdegree == 0 { 126 queue = append(queue, name) 127 } 128 } 129 // every element in queue have outdegree == 0 130 for ; len(queue) > 0; queue = queue[1:] { 131 name := queue[0] 132 node := graph[name] 133 for _, f := range node.from { 134 graph[f].outdegree-- 135 if graph[f].outdegree == 0 { 136 queue = append(queue, f) 137 } 138 } 139 delete(graph, name) 140 } 141 // Step 2.3 report the elements in loop 142 loopElements := make([]string, 0, len(graph)) 143 for name := range graph { 144 loopElements = append(loopElements, name) 145 delete(graph, name) 146 } 147 return fmt.Errorf("loop is detected in ProxyGroup, please check following ProxyGroups: %v", loopElements) 148 }