veyron2/naming: add a field to endpoints to reflect whether they serve a mounttable or not.

Change-Id: I16813298daa96c7f4db258498847b85cb1e745ed
diff --git a/runtimes/google/ipc/server.go b/runtimes/google/ipc/server.go
index 610e2a6..ddefad2 100644
--- a/runtimes/google/ipc/server.go
+++ b/runtimes/google/ipc/server.go
@@ -152,6 +152,8 @@
 			return nil, err
 		}
 	}
+	// TODO(cnicolaou): pass ServesMountTableOpt to streamMgr.Listen so that
+	// it can more cleanly set the IsMountTable bit in the endpoint.
 	ln, ep, err := s.streamMgr.Listen(protocol, address, s.listenerOpts...)
 	if err != nil {
 		vlog.Errorf("ipc: Listen on %v %v failed: %v", protocol, address, err)
@@ -197,18 +199,18 @@
 	// Each flow is served from its own goroutine.
 	s.active.Add(1)
 	if protocol == inaming.Network {
-		go func(ln stream.Listener, ep naming.Endpoint, proxy string) {
+		go func(ln stream.Listener, ep *inaming.Endpoint, proxy string) {
 			s.proxyListenLoop(ln, ep, proxy)
 			s.active.Done()
-		}(ln, ep, proxyName)
+		}(ln, iep, proxyName)
 	} else {
 		go func(ln stream.Listener, ep naming.Endpoint) {
 			s.listenLoop(ln, ep)
 			s.active.Done()
-		}(ln, ep)
+		}(ln, iep)
 	}
 	s.Unlock()
-	s.publisher.AddServer(s.publishEP(ep), s.servesMountTable)
+	s.publisher.AddServer(s.publishEP(iep, s.servesMountTable), s.servesMountTable)
 	return ep, nil
 }
 
@@ -221,7 +223,6 @@
 	if !ok {
 		return nil, nil, fmt.Errorf("failed translating internal endpoint data types")
 	}
-
 	switch iep.Protocol {
 	case "tcp", "tcp4", "tcp6":
 		host, port, err := net.SplitHostPort(iep.Address)
@@ -338,41 +339,48 @@
 			vlog.Errorf("ipc: Listen on %v %v failed: %v", protocol, address, err)
 			return nil, err
 		}
+		ipep, ok := pep.(*inaming.Endpoint)
+		if !ok {
+			return nil, fmt.Errorf("failed translating internal endpoint data types")
+		}
 		// We have a goroutine for listening on proxy connections.
 		s.active.Add(1)
-		go func(ln stream.Listener, ep naming.Endpoint, proxy string) {
+		go func(ln stream.Listener, ep *inaming.Endpoint, proxy string) {
 			s.proxyListenLoop(ln, ep, proxy)
 			s.active.Done()
-		}(pln, pep, listenSpec.Proxy)
+		}(pln, ipep, listenSpec.Proxy)
 		s.listeners[pln] = nil
-		s.publisher.AddServer(s.publishEP(pep), s.servesMountTable)
+		// TODO(cnicolaou,p): AddServer no longer needs to take the
+		// servesMountTable bool since it can be extracted from the endpoint.
+		s.publisher.AddServer(s.publishEP(ipep, s.servesMountTable), s.servesMountTable)
 	} else {
-		s.publisher.AddServer(s.publishEP(ep), s.servesMountTable)
+		s.publisher.AddServer(s.publishEP(ep, s.servesMountTable), s.servesMountTable)
 	}
 	s.Unlock()
 	return ep, nil
 }
 
-func (s *server) publishEP(ep naming.Endpoint) string {
+func (s *server) publishEP(ep *inaming.Endpoint, servesMountTable bool) string {
 	var name string
 	if !s.servesMountTable {
 		// Make sure that client MountTable code doesn't try and
 		// ResolveStep past this final address.
 		name = "//"
 	}
+	ep.IsMountTable = servesMountTable
 	return naming.JoinAddressName(ep.String(), name)
 }
 
-func (s *server) proxyListenLoop(ln stream.Listener, ep naming.Endpoint, proxy string) {
+func (s *server) proxyListenLoop(ln stream.Listener, iep *inaming.Endpoint, proxy string) {
 	const (
 		min = 5 * time.Millisecond
 		max = 5 * time.Minute
 	)
 	for {
-		s.listenLoop(ln, ep)
+		s.listenLoop(ln, iep)
 		// The listener is done, so:
 		// (1) Unpublish its name
-		s.publisher.RemoveServer(s.publishEP(ep))
+		s.publisher.RemoveServer(s.publishEP(iep, s.servesMountTable))
 		// (2) Reconnect to the proxy unless the server has been stopped
 		backoff := min
 		ln = nil
@@ -384,9 +392,17 @@
 					vlog.VI(1).Infof("Failed to resolve proxy %q (%v), will retry in %v", proxy, err, backoff)
 					break
 				}
+				var ep naming.Endpoint
 				ln, ep, err = s.streamMgr.Listen(inaming.Network, resolved, s.listenerOpts...)
 				if err == nil {
-					vlog.VI(1).Infof("Reconnected to proxy at %q listener: (%v, %v)", proxy, ln, ep)
+					var ok bool
+					iep, ok = ep.(*inaming.Endpoint)
+					if !ok {
+						vlog.Errorf("failed translating internal endpoint data types")
+						ln = nil
+						continue
+					}
+					vlog.VI(1).Infof("Reconnected to proxy at %q listener: (%v, %v)", proxy, ln, iep)
 					break
 				}
 				if backoff = backoff * 2; backoff > max {
@@ -397,8 +413,13 @@
 				return
 			}
 		}
+		// TODO(cnicolaou,ashankar): this won't work when we are both
+		// proxying and publishing locally, which is the common case.
+		// listenLoop, dhcpLoop and the original publish are all publishing
+		// addresses to the same name, but the client is not smart enough
+		// to choose sensibly between them.
 		// (3) reconnected, publish new address
-		s.publisher.AddServer(s.publishEP(ep), s.servesMountTable)
+		s.publisher.AddServer(s.publishEP(iep, s.servesMountTable), s.servesMountTable)
 		s.Lock()
 		s.listeners[ln] = nil
 		s.Unlock()
@@ -438,7 +459,7 @@
 	for _, a := range addrs {
 		if ip := netstate.AsIP(a); ip != nil {
 			dhcpl.ep.Address = net.JoinHostPort(ip.String(), dhcpl.port)
-			fn(s.publishEP(dhcpl.ep))
+			fn(s.publishEP(dhcpl.ep, s.servesMountTable))
 		}
 	}
 }
@@ -459,6 +480,11 @@
 				s.Unlock()
 				return
 			}
+			// TODO(cnicolaou,ashankar): this won't work when we are both
+			// proxying and publishing locally, which is the common case.
+			// listenLoop, dhcpLoop and the original publish are all publishing
+			// addresses to the same name, but the client is not smart enough
+			// to choose sensibly between them.
 			publisher := s.publisher
 			s.Unlock()
 			switch setting.Name() {
diff --git a/runtimes/google/ipc/stream/vc/vc_test.go b/runtimes/google/ipc/stream/vc/vc_test.go
index 086cc23..c469fe6 100644
--- a/runtimes/google/ipc/stream/vc/vc_test.go
+++ b/runtimes/google/ipc/stream/vc/vc_test.go
@@ -446,6 +446,8 @@
 type endpoint naming.RoutingID
 
 func (e endpoint) Network() string             { return "test" }
+func (e endpoint) VersionedString(int) string  { return e.String() }
 func (e endpoint) String() string              { return naming.RoutingID(e).String() }
 func (e endpoint) RoutingID() naming.RoutingID { return naming.RoutingID(e) }
 func (e endpoint) Addr() net.Addr              { return nil }
+func (e endpoint) ServesMountTable() bool      { return false }
diff --git a/runtimes/google/naming/endpoint.go b/runtimes/google/naming/endpoint.go
index 48979b5..e2fefd9 100644
--- a/runtimes/google/naming/endpoint.go
+++ b/runtimes/google/naming/endpoint.go
@@ -29,6 +29,7 @@
 	RID           naming.RoutingID
 	MinIPCVersion version.IPCVersion
 	MaxIPCVersion version.IPCVersion
+	IsMountTable  bool
 }
 
 // NewEndpoint creates a new endpoint from a string as per naming.NewEndpoint
@@ -54,6 +55,8 @@
 		err = ep.parseV1(parts)
 	case 2:
 		err = ep.parseV2(parts)
+	case 3:
+		err = ep.parseV3(parts)
 	default:
 		err = errInvalidEndpointString
 	}
@@ -112,6 +115,20 @@
 	return strconv.FormatUint(uint64(v), 10)
 }
 
+func parseMountTableFlag(input string) (bool, error) {
+	if len(input) == 1 {
+		switch f := input[0]; f {
+		case 'm':
+			return true, nil
+		case 's':
+			return false, nil
+		default:
+			return false, fmt.Errorf("%c is not one of 'm' or 's'", f)
+		}
+	}
+	return false, fmt.Errorf("flag is either missing or too long")
+}
+
 func (ep *Endpoint) parseV2(parts []string) error {
 	var err error
 	if len(parts) != 6 {
@@ -129,6 +146,20 @@
 	return nil
 }
 
+func (ep *Endpoint) parseV3(parts []string) error {
+	var err error
+	if len(parts) != 7 {
+		return errInvalidEndpointString
+	}
+	if err = ep.parseV2(parts[:6]); err != nil {
+		return err
+	}
+	if ep.IsMountTable, err = parseMountTableFlag(parts[6]); err != nil {
+		return fmt.Errorf("invalid mount table flag: %v", err)
+	}
+	return nil
+}
+
 func (ep *Endpoint) RoutingID() naming.RoutingID {
 	//nologcall
 	return ep.RID
@@ -137,19 +168,46 @@
 	//nologcall
 	return Network
 }
+
+var defaultVersion = 2
+
+func (ep *Endpoint) VersionedString(version int) string {
+	switch version {
+	default:
+		return ep.VersionedString(defaultVersion)
+	case 1:
+		return fmt.Sprintf("@1@%s@%s@@", ep.Protocol, ep.Address)
+	case 2:
+		return fmt.Sprintf("@2@%s@%s@%s@%s@%s@@",
+			ep.Protocol, ep.Address, ep.RID,
+			printIPCVersion(ep.MinIPCVersion), printIPCVersion(ep.MaxIPCVersion))
+	case 3:
+		mt := "s"
+		if ep.IsMountTable {
+			mt = "m"
+		}
+		return fmt.Sprintf("@3@%s@%s@%s@%s@%s@%s@@",
+			ep.Protocol, ep.Address, ep.RID,
+			printIPCVersion(ep.MinIPCVersion), printIPCVersion(ep.MaxIPCVersion),
+			mt)
+	}
+}
+
 func (ep *Endpoint) String() string {
 	//nologcall
-	return fmt.Sprintf("%s2@%s@%s@%s@%s@%s@@",
-		separator, ep.Protocol, ep.Address, ep.RID,
-		printIPCVersion(ep.MinIPCVersion), printIPCVersion(ep.MaxIPCVersion))
+	return ep.VersionedString(defaultVersion)
 }
-func (ep *Endpoint) version() int { return 2 }
 
 func (ep *Endpoint) Addr() net.Addr {
 	//nologcall
 	return &addr{network: ep.Protocol, address: ep.Address}
 }
 
+func (ep *Endpoint) ServesMountTable() bool {
+	//nologcall
+	return true
+}
+
 type addr struct {
 	network, address string
 }
diff --git a/runtimes/google/naming/endpoint_test.go b/runtimes/google/naming/endpoint_test.go
index f0d036b..00135c5 100644
--- a/runtimes/google/naming/endpoint_test.go
+++ b/runtimes/google/naming/endpoint_test.go
@@ -29,6 +29,14 @@
 		MinIPCVersion: 2,
 		MaxIPCVersion: 3,
 	}
+	v3 := &Endpoint{
+		Protocol:      "tcp",
+		Address:       "batman.com:2345",
+		RID:           naming.FixedRoutingID(0x0),
+		MinIPCVersion: 2,
+		MaxIPCVersion: 3,
+		IsMountTable:  true,
+	}
 
 	testcasesA := []struct {
 		endpoint naming.Endpoint
@@ -53,10 +61,13 @@
 		String   string
 		Input    string
 		min, max version.IPCVersion
+		servesMT bool
 	}{
-		{v1, "@2@tcp@batman.com:1234@000000000000000000000000dabbad00@@@@", "", version.UnknownIPCVersion, version.UnknownIPCVersion},
-		{v2, "@2@tcp@batman.com:2345@000000000000000000000000dabbad00@1@10@@", "", 1, 10},
-		{v2hp, "@2@tcp@batman.com:2345@00000000000000000000000000000000@2@3@@", "batman.com:2345", 2, 3},
+		{v1, "@2@tcp@batman.com:1234@000000000000000000000000dabbad00@@@@", "", version.UnknownIPCVersion, version.UnknownIPCVersion, false},
+		{v2, "@2@tcp@batman.com:2345@000000000000000000000000dabbad00@1@10@@", "", 1, 10, false},
+		{v2hp, "@2@tcp@batman.com:2345@00000000000000000000000000000000@2@3@@", "batman.com:2345", 2, 3, false},
+		// the v3 format is ignored unless explicitly enabled.
+		{v3, "@2@tcp@batman.com:2345@00000000000000000000000000000000@2@3@@", "batman.com:2345", 2, 3, true},
 	}
 
 	for _, test := range testcasesB {
@@ -71,7 +82,8 @@
 			ep, err = NewEndpoint(str)
 		} else {
 			ep, err = NewEndpoint(naming.FormatEndpoint("tcp", str,
-				version.IPCVersionRange{test.min, test.max}))
+				version.IPCVersionRange{test.min, test.max},
+				naming.ServesMountTableOpt(test.servesMT)))
 		}
 		if err != nil {
 			t.Errorf("Endpoint(%q) failed with %v", str, err)
@@ -81,6 +93,25 @@
 			t.Errorf("Got endpoint %T = %#v, want %T = %#v for string %q", ep, ep, test.Endpoint, test.Endpoint, str)
 		}
 	}
+
+	// Enable V3 endpoints.
+	defaultVersion = 3
+	for _, c := range []struct {
+		servesMT bool
+		str      string
+	}{
+		{true, "@3@tcp@a:10@00000000000000000000000000000000@1@3@m@@"},
+		{false, "@3@tcp@a:10@00000000000000000000000000000000@1@3@s@@"},
+	} {
+		ep, _ := NewEndpoint(naming.FormatEndpoint("tcp", "a:10",
+			version.IPCVersionRange{1, 3},
+			naming.ServesMountTableOpt(c.servesMT)))
+		if got, want := ep.String(), c.str; got != want {
+			t.Errorf("got: %s, want: %s", got, want)
+		}
+	}
+	// Disable V3 endpoints.
+	defaultVersion = 2
 }
 
 type endpointTest struct {