github.com/igoogolx/clash@v1.19.8/config/utils.go (about)

     1  package config
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/igoogolx/clash/adapter/outboundgroup"
     8  	"github.com/igoogolx/clash/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]any) error {
    22  	type graphNode struct {
    23  		indegree int
    24  		// topological order
    25  		topo int
    26  		// the original data in `groupsConfig`
    27  		data map[string]any
    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  }