runtimes/google/ipc: fix ugly sort endpoints code.

Change-Id: Ib5b7f6f8bf9b68a317b80574ee4fc9f963fafec3
diff --git a/runtimes/google/ipc/sort_endpoints.go b/runtimes/google/ipc/sort_endpoints.go
index ab9f730..c31e072 100644
--- a/runtimes/google/ipc/sort_endpoints.go
+++ b/runtimes/google/ipc/sort_endpoints.go
@@ -73,30 +73,27 @@
 	return se
 }
 
-func sortByProtocol(eps []*serverEndpoint) map[string][]*serverEndpoint {
+// sortByProtocols sorts the supplied slice of serverEndpoints into a hash
+// map keyed by the protocol of each of those endpoints where that protocol
+// is listed in the protocols parameter and keyed by '*' if not so listed.
+func sortByProtocol(eps []*serverEndpoint, protocols []string) (bool, map[string][]*serverEndpoint) {
 	byProtocol := make(map[string][]*serverEndpoint)
+	matched := false
 	for _, ep := range eps {
-		p := ep.iep.Protocol
-		byProtocol[p] = append(byProtocol[p], ep)
-	}
-	return byProtocol
-}
-
-func unmatchedProtocols(hashed map[string][]*serverEndpoint, protocols []string) []*serverEndpoint {
-	unmatched := make([]*serverEndpoint, 0, 10)
-	for p, eps := range hashed {
 		found := false
-		for _, preferred := range protocols {
-			if p == preferred {
+		for _, p := range protocols {
+			if ep.iep.Protocol == p || (p == "tcp" && strings.HasPrefix(ep.iep.Protocol, "tcp")) {
+				byProtocol[p] = append(byProtocol[p], ep)
 				found = true
+				matched = true
 				break
 			}
 		}
 		if !found {
-			unmatched = append(unmatched, eps...)
+			byProtocol["*"] = append(byProtocol["*"], ep)
 		}
 	}
-	return unmatched
+	return matched, byProtocol
 }
 
 func orderByLocality(ifcs netstate.AddrList, eps []*serverEndpoint) []*serverEndpoint {
@@ -167,8 +164,21 @@
 	return r
 }
 
-var defaultPreferredProtocolOrder = []string{"unixfd", "tcp4", "tcp", "tcp6"}
+var defaultPreferredProtocolOrder = []string{"unixfd", "tcp4", "tcp", "*"}
 
+// filterAndOrderServers returns a set of servers that are compatible with
+// the current client in order of 'preference' specified by the supplied
+// protocols and a notion of 'locality' according to the supplied protocol
+// list as follows:
+// - if the protocol parameter is non-empty, then only servers matching those
+// protocols are returned and the endpoints are ordered first by protocol
+// and then by locality within each protocol. If tcp4 and unixfd are requested
+// for example then only protocols that match tcp4 and unixfd will returned
+// with the tcp4 ones preceeding the unixfd ones.
+// - if the protocol parameter is empty, then a default protocol ordering
+// will be used, but unlike the previous case, any servers that don't support
+// these protocols will be returned also, but following the default
+// preferences.
 func filterAndOrderServers(servers []string, protocols []string) ([]string, error) {
 	errs := newErrorAccumulator()
 	vlog.VI(3).Infof("Candidates[%v]: %v", protocols, servers)
@@ -178,39 +188,25 @@
 	}
 	vlog.VI(3).Infof("Version Compatible: %v", compatible)
 
+	defaultOrdering := len(protocols) == 0
+
 	// put the server endpoints into per-protocol lists
-	byProtocol := sortByProtocol(compatible)
+	matched, byProtocol := sortByProtocol(compatible, protocols)
 
-	if len(protocols) > 0 {
-		found := 0
-		for _, p := range protocols {
-			found += len(byProtocol[p])
-		}
-		if found == 0 {
-			return nil, fmt.Errorf("failed to find any servers compatible with %v from %s", protocols, servers)
-		}
+	if !defaultOrdering && !matched {
+		return nil, fmt.Errorf("failed to find any servers compatible with %v from %s", protocols, servers)
 	}
-
-	// If a set of protocols is specified, then we will order
-	// and return endpoints that contain those protocols alone.
-	// However, if no protocols are supplied we'll order by
-	// a default ordering but append any endpoints that don't belong
-	// to that default ordering set to the returned endpoints.
-	remaining := []*serverEndpoint{}
 	preferredProtocolOrder := defaultPreferredProtocolOrder
-	if len(protocols) > 0 {
+	if !defaultOrdering {
 		preferredProtocolOrder = protocols
-	} else {
-		remaining = unmatchedProtocols(byProtocol, preferredProtocolOrder)
 	}
 
 	vlog.VI(3).Infof("Have Protocols(%v): %v", protocols, byProtocol)
 
 	networks, err := netstate.GetAll()
 	if err != nil {
-		r := sliceByProtocol(byProtocol, preferredProtocolOrder)
-		r = append(r, slice(remaining)...)
-		return r, nil
+		// return whatever we have now, just not sorted by locality.
+		return sliceByProtocol(byProtocol, preferredProtocolOrder), nil
 	}
 
 	ordered := make([]*serverEndpoint, 0, len(byProtocol))
@@ -219,9 +215,6 @@
 		ordered = append(ordered, o...)
 	}
 
-	if len(protocols) == 0 {
-		ordered = append(ordered, remaining...)
-	}
 	vlog.VI(2).Infof("Ordered By Locality: %v", ordered)
 	return slice(ordered), nil
 }
diff --git a/runtimes/google/ipc/sort_internal_test.go b/runtimes/google/ipc/sort_internal_test.go
index 5b9b4cf..c234557 100644
--- a/runtimes/google/ipc/sort_internal_test.go
+++ b/runtimes/google/ipc/sort_internal_test.go
@@ -56,23 +56,44 @@
 		name := naming.JoinAddressName(naming.FormatEndpoint("foobar", a), "")
 		servers = append(servers, name)
 	}
+	for _, a := range []string{"127.0.0.7", "127.0.0.8"} {
+		name := naming.JoinAddressName(naming.FormatEndpoint("tcp6", a), "")
+		servers = append(servers, name)
+	}
 
-	got, err := filterAndOrderServers(servers, []string{"foobar", "tcp"})
+	got, err := filterAndOrderServers(servers, []string{"batman"})
+	if err == nil {
+		t.Fatalf("expected an error")
+	}
+
+	got, err = filterAndOrderServers(servers, []string{"foobar", "tcp4"})
 	if err != nil {
 		t.Fatalf("unexpected error: %s", err)
 	}
 
-	// Just foobar and tcp
+	// Just foobar and tcp4
 	want := []string{
 		"/@2@foobar@127.0.0.10@00000000000000000000000000000000@@@@",
 		"/@2@foobar@127.0.0.11@00000000000000000000000000000000@@@@",
-		"/@2@tcp@127.0.0.3@00000000000000000000000000000000@@@@",
-		"/@2@tcp@127.0.0.4@00000000000000000000000000000000@@@@",
+		"/@2@tcp4@127.0.0.1@00000000000000000000000000000000@@@@",
+		"/@2@tcp4@127.0.0.2@00000000000000000000000000000000@@@@",
 	}
 	if !reflect.DeepEqual(got, want) {
 		t.Errorf("got: %v, want %v", got, want)
 	}
 
+	got, err = filterAndOrderServers(servers, []string{"tcp"})
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	// tcp or tcp4
+	want = []string{
+		"/@2@tcp4@127.0.0.1@00000000000000000000000000000000@@@@",
+		"/@2@tcp4@127.0.0.2@00000000000000000000000000000000@@@@",
+		"/@2@tcp@127.0.0.3@00000000000000000000000000000000@@@@",
+		"/@2@tcp@127.0.0.4@00000000000000000000000000000000@@@@",
+	}
+
 	// Everything, since we didn't specify a protocol
 	got, err = filterAndOrderServers(servers, []string{})
 	if err != nil {
@@ -82,10 +103,12 @@
 	// original ordering within each protocol, with protocols that
 	// are not in the default ordering list at the end.
 	want = []string{
-		"/@2@tcp4@127.0.0.1@00000000000000000000000000000000@@@@",
-		"/@2@tcp4@127.0.0.2@00000000000000000000000000000000@@@@",
 		"/@2@tcp@127.0.0.3@00000000000000000000000000000000@@@@",
 		"/@2@tcp@127.0.0.4@00000000000000000000000000000000@@@@",
+		"/@2@tcp4@127.0.0.1@00000000000000000000000000000000@@@@",
+		"/@2@tcp4@127.0.0.2@00000000000000000000000000000000@@@@",
+		"/@2@tcp6@127.0.0.7@00000000000000000000000000000000@@@@",
+		"/@2@tcp6@127.0.0.8@00000000000000000000000000000000@@@@",
 		"/@2@foobar@127.0.0.10@00000000000000000000000000000000@@@@",
 		"/@2@foobar@127.0.0.11@00000000000000000000000000000000@@@@",
 	}