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  }