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