Merge "discovery: add mdns plugin"
diff --git a/cmd/vdl/v23_internal_test.go b/cmd/vdl/v23_internal_test.go
index ae59080..a80e0ec 100644
--- a/cmd/vdl/v23_internal_test.go
+++ b/cmd/vdl/v23_internal_test.go
@@ -12,9 +12,11 @@
 	"testing"
 
 	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
 )
 
 func TestMain(m *testing.M) {
 	test.Init()
+	modules.DispatchAndExitIfChild()
 	os.Exit(m.Run())
 }
diff --git a/cmd/vdl/vdl_test.go b/cmd/vdl/vdl_test.go
index da17bb9..7a127cd 100644
--- a/cmd/vdl/vdl_test.go
+++ b/cmd/vdl/vdl_test.go
@@ -8,12 +8,11 @@
 	"bytes"
 	"fmt"
 	"io/ioutil"
-	"os"
 	"path/filepath"
 	"strings"
 	"testing"
 
-	"v.io/x/lib/cmdline"
+	"v.io/x/ref/test/v23tests"
 )
 
 const (
@@ -25,18 +24,15 @@
 
 // Compares generated VDL files against the copy in the repo.
 func TestVDLGenerator(t *testing.T) {
+	testEnv := v23tests.New(t)
+	defer testEnv.Cleanup()
+	vdlBin := testEnv.BuildGoPkg("v.io/x/ref/cmd/vdl")
+
 	// Use vdl to generate Go code from input, into a temporary directory.
-	outDir, err := ioutil.TempDir("", "vdltest")
-	if err != nil {
-		t.Fatalf("TempDir() failed: %v", err)
-	}
-	defer os.RemoveAll(outDir)
+	outDir := testEnv.NewTempDir("")
 	// TODO(toddw): test the generated java and javascript files too.
 	outOpt := fmt.Sprintf("--go-out-dir=%s", outDir)
-	env := cmdline.EnvFromOS()
-	if err := cmdline.ParseAndRun(cmdVDL, env, []string{"generate", "--lang=go", outOpt, testDir}); err != nil {
-		t.Fatalf("Execute() failed: %v", err)
-	}
+	vdlBin.Run("generate", "--lang=go", outOpt, testDir)
 	// Check that each *.vdl.go file in the testDir matches the generated output.
 	entries, err := ioutil.ReadDir(testDir)
 	if err != nil {
diff --git a/runtime/internal/flow/conn/conn.go b/runtime/internal/flow/conn/conn.go
index 76a5206..a7991c7 100644
--- a/runtime/internal/flow/conn/conn.go
+++ b/runtime/internal/flow/conn/conn.go
@@ -159,6 +159,12 @@
 // RemoteEndpoint returns the remote vanadium Endpoint
 func (c *Conn) RemoteEndpoint() naming.Endpoint { return c.remote }
 
+// LocalBlessings returns the local blessings.
+func (c *Conn) LocalBlessings() security.Blessings { return c.lBlessings }
+
+// RemoteBlessings returns the remote blessings.
+func (c *Conn) RemoteBlessings() security.Blessings { return c.rBlessings }
+
 // CommonVersion returns the RPCVersion negotiated between the local and remote endpoints.
 func (c *Conn) CommonVersion() version.RPCVersion { return c.version }
 
diff --git a/runtime/internal/flow/manager/conncache.go b/runtime/internal/flow/manager/conncache.go
index cdb81a7..92f8348 100644
--- a/runtime/internal/flow/manager/conncache.go
+++ b/runtime/internal/flow/manager/conncache.go
@@ -183,6 +183,9 @@
 // is identified by the provided rid. nil is returned if there is no such Conn.
 // FindWithRoutingID will return an error iff the cache is closed.
 func (c *ConnCache) FindWithRoutingID(rid naming.RoutingID) (*conn.Conn, error) {
+	if rid == naming.NullRoutingID {
+		return nil, nil
+	}
 	defer c.mu.Unlock()
 	c.mu.Lock()
 	if c.addrCache == nil {
diff --git a/runtime/internal/flow/manager/manager.go b/runtime/internal/flow/manager/manager.go
index 6ec43fb..d39b1ad 100644
--- a/runtime/internal/flow/manager/manager.go
+++ b/runtime/internal/flow/manager/manager.go
@@ -266,7 +266,11 @@
 // The flow.Manager associated with ctx must be the receiver of the method,
 // otherwise an error is returned.
 func (m *manager) Dial(ctx *context.T, remote naming.Endpoint, fn flow.BlessingsForPeer) (flow.Flow, error) {
-	return m.internalDial(ctx, remote, fn, &flowHandler{q: m.q, closed: m.closed})
+	var fh conn.FlowHandler
+	if m.rid != naming.NullRoutingID {
+		fh = &flowHandler{q: m.q, closed: m.closed}
+	}
+	return m.internalDial(ctx, remote, fn, fh)
 }
 
 func (m *manager) internalDial(ctx *context.T, remote naming.Endpoint, fn flow.BlessingsForPeer, fh conn.FlowHandler) (flow.Flow, error) {
diff --git a/runtime/internal/flow/manager/manager_test.go b/runtime/internal/flow/manager/manager_test.go
index 41c470f..62403da 100644
--- a/runtime/internal/flow/manager/manager_test.go
+++ b/runtime/internal/flow/manager/manager_test.go
@@ -15,6 +15,7 @@
 	"v.io/v23/naming"
 
 	_ "v.io/x/ref/runtime/factories/fake"
+	"v.io/x/ref/runtime/internal/flow/conn"
 	"v.io/x/ref/runtime/internal/flow/flowtest"
 	"v.io/x/ref/test"
 )
@@ -82,6 +83,29 @@
 	testFlows(t, ctx, am, dm, flowtest.BlessingsForPeer)
 }
 
+func TestNullClientBlessings(t *testing.T) {
+	ctx, shutdown := v23.Init()
+	defer shutdown()
+
+	am := New(ctx, naming.FixedRoutingID(0x5555))
+	if err := am.Listen(ctx, "tcp", "127.0.0.1:0"); err != nil {
+		t.Fatal(err)
+	}
+	dm := New(ctx, naming.NullRoutingID)
+	_, af := testFlows(t, ctx, dm, am, flowtest.BlessingsForPeer)
+	// Ensure that the remote blessings of the underlying conn of the accepted flow are zero.
+	if rBlessings := af.Conn().(*conn.Conn).RemoteBlessings(); !rBlessings.IsZero() {
+		t.Errorf("got %v, want zero-value blessings", rBlessings)
+	}
+	dm = New(ctx, naming.FixedRoutingID(0x1111))
+	_, af = testFlows(t, ctx, dm, am, flowtest.BlessingsForPeer)
+	// Ensure that the remote blessings of the underlying conn of the accepted flow are
+	// non-zero if we did specify a RoutingID.
+	if rBlessings := af.Conn().(*conn.Conn).RemoteBlessings(); rBlessings.IsZero() {
+		t.Errorf("got %v, want non-zero blessings", rBlessings)
+	}
+}
+
 func testFlows(t *testing.T, ctx *context.T, dm, am flow.Manager, bFn flow.BlessingsForPeer) (df, af flow.Flow) {
 	eps := am.ListeningEndpoints()
 	if len(eps) == 0 {
diff --git a/runtime/internal/rt/runtime.go b/runtime/internal/rt/runtime.go
index f3452b0..c4f358e 100644
--- a/runtime/internal/rt/runtime.go
+++ b/runtime/internal/rt/runtime.go
@@ -190,7 +190,13 @@
 	}
 
 	// Add the flow.Manager to the context.
-	ctx, _, err = r.ExperimentalWithNewFlowManager(ctx)
+	// This initial Flow Manager can only be used as a client.
+	ctx, _, err = r.setNewClientFlowManager(ctx)
+	if err != nil {
+		return nil, nil, nil, err
+	}
+	// Add the Client to the context.
+	ctx, _, err = r.WithNewClient(ctx)
 	if err != nil {
 		return nil, nil, nil, err
 	}
@@ -321,21 +327,22 @@
 	return sm, nil
 }
 
-func newFlowManager(ctx *context.T) (flow.Manager, error) {
-	rid, err := naming.NewRoutingID()
-	if err != nil {
-		return nil, err
-	}
-	return manager.New(ctx, rid), nil
+func (r *Runtime) setNewClientFlowManager(ctx *context.T) (*context.T, flow.Manager, error) {
+	return r.setNewFlowManager(ctx, naming.NullRoutingID)
 }
 
-func (r *Runtime) setNewFlowManager(ctx *context.T) (*context.T, flow.Manager, error) {
-	fm, err := newFlowManager(ctx)
+func (r *Runtime) setNewBidiFlowManager(ctx *context.T) (*context.T, flow.Manager, error) {
+	rid, err := naming.NewRoutingID()
 	if err != nil {
 		return nil, nil, err
 	}
+	return r.setNewFlowManager(ctx, rid)
+}
+
+func (r *Runtime) setNewFlowManager(ctx *context.T, rid naming.RoutingID) (*context.T, flow.Manager, error) {
+	fm := manager.New(ctx, rid)
 	// TODO(mattr): How can we close a flow manager.
-	if err = r.addChild(ctx, fm, func() {}); err != nil {
+	if err := r.addChild(ctx, fm, func() {}); err != nil {
 		return ctx, nil, err
 	}
 	newctx := context.WithValue(ctx, flowManagerKey, fm)
@@ -396,7 +403,12 @@
 	if newctx, err = r.setNewStreamManager(newctx); err != nil {
 		return ctx, err
 	}
-	if newctx, _, err = r.setNewFlowManager(newctx); err != nil {
+	if rid := r.ExperimentalGetFlowManager(newctx).RoutingID(); rid == naming.NullRoutingID {
+		newctx, _, err = r.setNewClientFlowManager(newctx)
+	} else {
+		newctx, _, err = r.setNewBidiFlowManager(newctx)
+	}
+	if err != nil {
 		return ctx, err
 	}
 	if newctx, _, err = r.setNewNamespace(newctx, r.GetNamespace(ctx).Roots()...); err != nil {
@@ -559,7 +571,7 @@
 
 func (r *Runtime) ExperimentalWithNewFlowManager(ctx *context.T) (*context.T, flow.Manager, error) {
 	defer apilog.LogCall(ctx)(ctx) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
-	newctx, m, err := r.setNewFlowManager(ctx)
+	newctx, m, err := r.setNewBidiFlowManager(ctx)
 	if err != nil {
 		return ctx, nil, err
 	}
diff --git a/runtime/internal/rt/runtime_test.go b/runtime/internal/rt/runtime_test.go
index 9b7f6cd..fc70a76 100644
--- a/runtime/internal/rt/runtime_test.go
+++ b/runtime/internal/rt/runtime_test.go
@@ -161,6 +161,9 @@
 	if oldman == nil {
 		t.Error("ExperimentalGetFlowManager should have returned a non-nil value")
 	}
+	if rid := oldman.RoutingID(); rid != naming.NullRoutingID {
+		t.Errorf("Initial flow.Manager should have NullRoutingID, got %v", rid)
+	}
 	newctx, newman, err := r.ExperimentalWithNewFlowManager(ctx)
 	if err != nil || newman == nil || newman == oldman {
 		t.Fatalf("Could not create flow manager: %v", err)
@@ -172,4 +175,7 @@
 	if man != newman || man == oldman {
 		t.Error("ExperimentalWithNewFlowManager didn't update the context properly")
 	}
+	if man.RoutingID() == naming.NullRoutingID {
+		t.Error("Newly created flow.Manager should not have NullRoutingID")
+	}
 }
diff --git a/services/device/device/publish.go b/services/device/device/publish.go
index 5862d86..f7d84a0 100644
--- a/services/device/device/publish.go
+++ b/services/device/device/publish.go
@@ -140,8 +140,7 @@
 	// TODO(caprita): use the profile detection machinery and/or let user
 	// specify the profile by hand.
 	profile := fmt.Sprintf("%s-%s", goosFlag, goarchFlag)
-	// TODO(caprita): use a label e.g. "prod" instead of "0".
-	appVON := naming.Join(applicationService, envelopeName, "0")
+	appVON := naming.Join(applicationService, envelopeName)
 	appClient := repository.ApplicationClient(appVON)
 	envelope, err := appClient.Match(ctx, []string{profile})
 	if verror.ErrorID(err) == verror.ErrNoExist.ID {
@@ -173,8 +172,16 @@
 		envelope.Binary.Signature = security.Signature{}
 		envelope.Publisher = security.Blessings{}
 	}
-
-	if err := appClient.Put(ctx, profile, envelope, true); err != nil {
+	appVON = naming.Join(appVON, timestamp)
+	appClient = repository.ApplicationClient(appVON)
+	if err := appClient.Put(ctx, profile, envelope, false); err != nil {
+		// NOTE(caprita): We don't retry if an envelope already exists
+		// at the versioned name, as we do when uploading binaries.  In
+		// the case of binaries, it's likely that the same binary is
+		// uploaded more than once in a given second, due to apps
+		// sharing the same binary.  The scenarios where the same app is
+		// published repeatedly in a short time-frame are expected to be
+		// rare, and the operator can retry manually in such cases.
 		return err
 	}
 	fmt.Fprintf(env.Stdout, "Published %q\n", appVON)
diff --git a/services/device/dmrun/dmrun.go b/services/device/dmrun/dmrun.go
index c829566..d816542 100644
--- a/services/device/dmrun/dmrun.go
+++ b/services/device/dmrun/dmrun.go
@@ -234,6 +234,10 @@
 		cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", ref.EnvCredentials, creds))
 	} else if agentCreds := os.Getenv(ref.EnvAgentEndpoint); len(agentCreds) > 0 {
 		cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", ref.EnvAgentEndpoint, agentCreds))
+	} else if agentCreds := os.Getenv(ref.EnvAgentPath); len(agentCreds) > 0 {
+		cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", ref.EnvAgentPath, agentCreds))
+	} else {
+		fmt.Fprintf(os.Stderr, "WARNING: no credentials found. You'll probably have authorization issues later on.\n")
 	}
 }
 
diff --git a/services/device/mgmt_v23_test.go b/services/device/mgmt_v23_test.go
index 5940bcc..78a7795 100644
--- a/services/device/mgmt_v23_test.go
+++ b/services/device/mgmt_v23_test.go
@@ -350,14 +350,14 @@
 	// Allow publishers to create and update envelopes
 	deviceBin.Run("acl", "set", appDName, "root/a", "Read,Write,Resolve")
 
-	sampleAppName := appDName + "/testapp/0"
+	sampleAppName := appDName + "/testapp"
 	appPubName := "testbinaryd"
 	appEnvelopeFilename := filepath.Join(workDir, "app.envelope")
 	appEnvelope := fmt.Sprintf("{\"Title\":\"BINARYD\", \"Args\":[\"--name=%s\", \"--root-dir=./binstore\", \"--v23.tcp.address=127.0.0.1:0\", \"--http=127.0.0.1:0\"], \"Binary\":{\"File\":%q}, \"Env\":[]}", appPubName, sampleAppBinName)
 	ioutil.WriteFile(appEnvelopeFilename, []byte(appEnvelope), 0666)
 	defer os.Remove(appEnvelopeFilename)
 
-	output := applicationBin.Run("put", sampleAppName, deviceProfile, appEnvelopeFilename)
+	output := applicationBin.Run("put", sampleAppName+"/0", deviceProfile, appEnvelopeFilename)
 	if got, want := output, fmt.Sprintf("Application envelope added for profile %s.", deviceProfile); got != want {
 		i.Fatalf("got %q, want %q", got, want)
 	}
diff --git a/services/syncbase/server/app.go b/services/syncbase/server/app.go
index e07094c..9bcec0d 100644
--- a/services/syncbase/server/app.go
+++ b/services/syncbase/server/app.go
@@ -9,7 +9,6 @@
 	"sync"
 
 	"v.io/v23/context"
-	"v.io/v23/glob"
 	"v.io/v23/rpc"
 	"v.io/v23/security/access"
 	wire "v.io/v23/services/syncbase"
@@ -42,6 +41,8 @@
 ////////////////////////////////////////
 // RPC methods
 
+// TODO(sadovsky): Implement Glob__ or GlobChildren__.
+
 // TODO(sadovsky): Require the app name to match the client's blessing name.
 // I.e. reserve names at the app level of the hierarchy.
 func (a *app) Create(ctx *context.T, call rpc.ServerCall, perms access.Permissions) error {
@@ -83,17 +84,17 @@
 	return data.Perms, util.FormatVersion(data.Version), nil
 }
 
-func (a *app) GlobChildren__(ctx *context.T, call rpc.GlobChildrenServerCall, matcher *glob.Element) error {
+func (a *app) ListDatabases(ctx *context.T, call rpc.ServerCall) ([]string, error) {
 	if !a.exists {
-		return verror.New(verror.ErrNoExist, ctx, a.name)
+		return nil, verror.New(verror.ErrNoExist, ctx, a.name)
 	}
 	// Check perms.
 	sn := a.s.st.NewSnapshot()
 	if err := util.GetWithAuth(ctx, call, sn, a.stKey(), &appData{}); err != nil {
 		sn.Abort()
-		return err
+		return nil, err
 	}
-	return util.Glob(ctx, call, matcher, sn, sn.Abort, util.JoinKeyParts(util.DbInfoPrefix, a.name))
+	return util.ListChildren(ctx, call, sn, util.JoinKeyParts(util.DbInfoPrefix, a.name))
 }
 
 ////////////////////////////////////////
diff --git a/services/syncbase/server/dispatcher.go b/services/syncbase/server/dispatcher.go
index dac3d34..06a6757 100644
--- a/services/syncbase/server/dispatcher.go
+++ b/services/syncbase/server/dispatcher.go
@@ -34,21 +34,26 @@
 
 func (disp *dispatcher) Lookup(ctx *context.T, suffix string) (interface{}, security.Authorizer, error) {
 	suffix = strings.TrimPrefix(suffix, "/")
-	parts := strings.SplitN(suffix, "/", 2)
 
 	if len(suffix) == 0 {
 		return wire.ServiceServer(disp.s), auth, nil
 	}
 
+	// If the first slash-separated component of suffix is SyncbaseSuffix,
+	// dispatch to the sync module.
+	parts := strings.SplitN(suffix, "/", 2)
 	if parts[0] == util.SyncbaseSuffix {
 		return interfaces.SyncServer(disp.s.sync), auth, nil
 	}
 
+	// Otherwise, split on NameSepWithSlashes to get hierarchy component names.
+	parts = strings.SplitN(suffix, pubutil.NameSepWithSlashes, 2)
+
 	// Validate all key atoms up front, so that we can avoid doing so in all our
 	// method implementations.
 	appName := parts[0]
 	if !pubutil.ValidName(appName) {
-		return nil, nil, wire.NewErrInvalidName(nil, suffix)
+		return nil, nil, wire.NewErrInvalidName(ctx, suffix)
 	}
 
 	aExists := false
@@ -74,7 +79,7 @@
 	// All database, table, and row methods require the app to exist. If it
 	// doesn't, abort early.
 	if !aExists {
-		return nil, nil, verror.New(verror.ErrNoExist, nil, a.name)
+		return nil, nil, verror.New(verror.ErrNoExist, ctx, a.name)
 	}
 
 	// Note, it's possible for the app to be deleted concurrently with downstream
diff --git a/services/syncbase/server/nosql/database.go b/services/syncbase/server/nosql/database.go
index 1755076..f6e14e1 100644
--- a/services/syncbase/server/nosql/database.go
+++ b/services/syncbase/server/nosql/database.go
@@ -7,13 +7,11 @@
 import (
 	"math/rand"
 	"path"
-	"strconv"
 	"strings"
 	"sync"
 	"time"
 
 	"v.io/v23/context"
-	"v.io/v23/glob"
 	"v.io/v23/rpc"
 	"v.io/v23/security/access"
 	wire "v.io/v23/services/syncbase/nosql"
@@ -129,6 +127,8 @@
 ////////////////////////////////////////
 // RPC methods
 
+// TODO(sadovsky): Implement Glob__ or GlobChildren__.
+
 func (d *databaseReq) Create(ctx *context.T, call rpc.ServerCall, metadata *wire.SchemaMetadata, perms access.Permissions) error {
 	if d.exists {
 		return verror.New(verror.ErrExist, ctx, d.name)
@@ -178,24 +178,24 @@
 	d.mu.Lock()
 	defer d.mu.Unlock()
 	var id uint64
-	var batchType string
+	var batchType util.BatchType
 	for {
 		id = uint64(rng.Int63())
 		if bo.ReadOnly {
 			if _, ok := d.sns[id]; !ok {
 				d.sns[id] = d.st.NewSnapshot()
-				batchType = "sn"
+				batchType = util.BatchTypeSn
 				break
 			}
 		} else {
 			if _, ok := d.txs[id]; !ok {
 				d.txs[id] = d.st.NewTransaction()
-				batchType = "tx"
+				batchType = util.BatchTypeTx
 				break
 			}
 		}
 	}
-	return strings.Join([]string{d.name, batchType, strconv.FormatUint(id, 10)}, util.BatchSep), nil
+	return strings.Join([]string{d.name, util.JoinBatchInfo(batchType, id)}, util.BatchSep), nil
 }
 
 func (d *databaseReq) Commit(ctx *context.T, call rpc.ServerCall, schemaVersion int32) error {
@@ -251,10 +251,20 @@
 }
 
 func (d *databaseReq) Exec(ctx *context.T, call wire.DatabaseExecServerCall, schemaVersion int32, q string) error {
+	if !d.exists {
+		return verror.New(verror.ErrNoExist, ctx, d.name)
+	}
 	if err := d.checkSchemaVersion(ctx, schemaVersion); err != nil {
 		return err
 	}
-	impl := func(headers []string, rs query.ResultStream, err error) error {
+	impl := func(sntx store.SnapshotOrTransaction) error {
+		db := &queryDb{
+			ctx:  ctx,
+			call: call,
+			req:  d,
+			sntx: sntx,
+		}
+		headers, rs, err := exec.Exec(db, q)
 		if err != nil {
 			return err
 		}
@@ -275,23 +285,13 @@
 		}
 		return rs.Err()
 	}
-	var sntx store.SnapshotOrTransaction
 	if d.batchId != nil {
-		sntx = d.batchReader()
+		return impl(d.batchReader())
 	} else {
-		sntx = d.st.NewSnapshot()
+		sntx := d.st.NewSnapshot()
 		defer sntx.Abort()
+		return impl(sntx)
 	}
-	// queryDb implements the query.Database interface, which is needed by the
-	// exec.Exec function.
-	db := &queryDb{
-		ctx:  ctx,
-		call: call,
-		req:  d,
-		sntx: sntx,
-	}
-
-	return impl(exec.Exec(db, q))
 }
 
 func (d *databaseReq) SetPermissions(ctx *context.T, call rpc.ServerCall, perms access.Permissions, version string) error {
@@ -318,20 +318,25 @@
 	return data.Perms, util.FormatVersion(data.Version), nil
 }
 
-func (d *databaseReq) GlobChildren__(ctx *context.T, call rpc.GlobChildrenServerCall, matcher *glob.Element) error {
+func (d *databaseReq) ListTables(ctx *context.T, call rpc.ServerCall) ([]string, error) {
 	if !d.exists {
-		return verror.New(verror.ErrNoExist, ctx, d.name)
+		return nil, verror.New(verror.ErrNoExist, ctx, d.name)
+	}
+	impl := func(sntx store.SnapshotOrTransaction) ([]string, error) {
+		// Check perms.
+		if err := util.GetWithAuth(ctx, call, sntx, d.stKey(), &databaseData{}); err != nil {
+			sntx.Abort()
+			return nil, err
+		}
+		return util.ListChildren(ctx, call, sntx, util.TablePrefix)
 	}
 	if d.batchId != nil {
-		return wire.NewErrBoundToBatch(ctx)
+		return impl(d.batchReader())
+	} else {
+		sntx := d.st.NewSnapshot()
+		defer sntx.Abort()
+		return impl(sntx)
 	}
-	// Check perms.
-	sn := d.st.NewSnapshot()
-	if err := util.GetWithAuth(ctx, call, sn, d.stKey(), &databaseData{}); err != nil {
-		sn.Abort()
-		return err
-	}
-	return util.Glob(ctx, call, matcher, sn, sn.Abort, util.TablePrefix)
 }
 
 ////////////////////////////////////////
diff --git a/services/syncbase/server/nosql/database_watch.go b/services/syncbase/server/nosql/database_watch.go
index e201987..374cab4 100644
--- a/services/syncbase/server/nosql/database_watch.go
+++ b/services/syncbase/server/nosql/database_watch.go
@@ -172,7 +172,7 @@
 		}
 		parts := util.SplitKeyParts(opKey)
 		// TODO(rogulenko): Currently we process only rows, i.e. keys of the form
-		// $row:xxx:yyy. Consider processing other keys.
+		// <RowPrefix>:xxx:yyy. Consider processing other keys.
 		if len(parts) != 3 || parts[0] != util.RowPrefix {
 			continue
 		}
@@ -188,7 +188,7 @@
 			continue
 		}
 		change := watch.Change{
-			Name:      naming.Join(table, row),
+			Name:      naming.Join(table, pubutil.NameSep, row),
 			Continued: true,
 		}
 		switch op := logEntry.Op.(type) {
diff --git a/services/syncbase/server/nosql/dispatcher.go b/services/syncbase/server/nosql/dispatcher.go
index 269a7cd..4ec16c0 100644
--- a/services/syncbase/server/nosql/dispatcher.go
+++ b/services/syncbase/server/nosql/dispatcher.go
@@ -5,7 +5,6 @@
 package nosql
 
 import (
-	"strconv"
 	"strings"
 
 	"v.io/v23/context"
@@ -34,25 +33,25 @@
 // RPC method implementations to perform proper authorization.
 var auth security.Authorizer = security.AllowEveryone()
 
-func (disp *dispatcher) Lookup(_ *context.T, suffix string) (interface{}, security.Authorizer, error) {
+func (disp *dispatcher) Lookup(ctx *context.T, suffix string) (interface{}, security.Authorizer, error) {
 	suffix = strings.TrimPrefix(suffix, "/")
-	parts := strings.Split(suffix, "/")
+	parts := strings.Split(suffix, pubutil.NameSepWithSlashes)
 
 	if len(parts) == 0 {
 		vlog.Fatal("invalid nosql.dispatcher Lookup")
 	}
 
-	dParts := strings.Split(parts[0], util.BatchSep)
+	dParts := strings.SplitN(parts[0], util.BatchSep, 2)
 	dName := dParts[0]
 
 	// Validate all key atoms up front, so that we can avoid doing so in all our
 	// method implementations.
 	if !pubutil.ValidName(dName) {
-		return nil, nil, wire.NewErrInvalidName(nil, suffix)
+		return nil, nil, wire.NewErrInvalidName(ctx, suffix)
 	}
 	for _, s := range parts[1:] {
 		if !pubutil.ValidName(s) {
-			return nil, nil, wire.NewErrInvalidName(nil, suffix)
+			return nil, nil, wire.NewErrInvalidName(ctx, suffix)
 		}
 	}
 
@@ -75,8 +74,10 @@
 	}
 
 	dReq := &databaseReq{database: d}
-	if !setBatchFields(dReq, dParts) {
-		return nil, nil, wire.NewErrInvalidName(nil, suffix)
+	if len(dParts) == 2 {
+		if !setBatchFields(dReq, dParts[1]) {
+			return nil, nil, wire.NewErrInvalidName(ctx, suffix)
+		}
 	}
 	if len(parts) == 1 {
 		return nosqlWire.DatabaseServer(dReq), auth, nil
@@ -85,7 +86,7 @@
 	// All table and row methods require the database to exist. If it doesn't,
 	// abort early.
 	if !dExists {
-		return nil, nil, verror.New(verror.ErrNoExist, nil, d.name)
+		return nil, nil, verror.New(verror.ErrNoExist, ctx, d.name)
 	}
 
 	// Note, it's possible for the database to be deleted concurrently with
@@ -108,20 +109,14 @@
 		return nosqlWire.RowServer(rReq), auth, nil
 	}
 
-	return nil, nil, verror.NewErrNoExist(nil)
+	return nil, nil, verror.NewErrNoExist(ctx)
 }
 
 // setBatchFields sets the batch-related fields in databaseReq based on the
-// value of dParts, the parts of the database name component. It returns false
-// if dParts is malformed.
-func setBatchFields(d *databaseReq, dParts []string) bool {
-	if len(dParts) == 1 {
-		return true
-	}
-	if len(dParts) != 3 {
-		return false
-	}
-	batchId, err := strconv.ParseUint(dParts[2], 0, 64)
+// value of batchInfo (suffix of the database name component). It returns false
+// if batchInfo is malformed.
+func setBatchFields(d *databaseReq, batchInfo string) bool {
+	batchType, batchId, err := util.SplitBatchInfo(batchInfo)
 	if err != nil {
 		return false
 	}
@@ -129,13 +124,11 @@
 	d.mu.Lock()
 	defer d.mu.Unlock()
 	var ok bool
-	switch dParts[1] {
-	case "sn":
+	switch batchType {
+	case util.BatchTypeSn:
 		d.sn, ok = d.sns[batchId]
-	case "tx":
+	case util.BatchTypeTx:
 		d.tx, ok = d.txs[batchId]
-	default:
-		return false
 	}
 	return ok
 }
diff --git a/services/syncbase/server/nosql/row.go b/services/syncbase/server/nosql/row.go
index 758c58a..1efe739 100644
--- a/services/syncbase/server/nosql/row.go
+++ b/services/syncbase/server/nosql/row.go
@@ -26,6 +26,8 @@
 ////////////////////////////////////////
 // RPC methods
 
+// TODO(sadovsky): Implement Glob__ or GlobChildren__.
+
 func (r *rowReq) Exists(ctx *context.T, call rpc.ServerCall, schemaVersion int32) (bool, error) {
 	_, err := r.Get(ctx, call, schemaVersion)
 	return util.ErrorToExists(err)
diff --git a/services/syncbase/server/nosql/table.go b/services/syncbase/server/nosql/table.go
index 27097b6..8d68d7a 100644
--- a/services/syncbase/server/nosql/table.go
+++ b/services/syncbase/server/nosql/table.go
@@ -8,7 +8,6 @@
 	"strings"
 
 	"v.io/v23/context"
-	"v.io/v23/glob"
 	"v.io/v23/rpc"
 	"v.io/v23/security/access"
 	wire "v.io/v23/services/syncbase/nosql"
@@ -31,6 +30,8 @@
 ////////////////////////////////////////
 // RPC methods
 
+// TODO(sadovsky): Implement Glob__ or GlobChildren__.
+
 func (t *tableReq) Create(ctx *context.T, call rpc.ServerCall, schemaVersion int32, perms access.Permissions) error {
 	if t.d.batchId != nil {
 		return wire.NewErrBoundToBatch(ctx)
@@ -316,26 +317,6 @@
 	}
 }
 
-func (t *tableReq) GlobChildren__(ctx *context.T, call rpc.GlobChildrenServerCall, matcher *glob.Element) error {
-	impl := func(sntx store.SnapshotOrTransaction, closeSntx func() error) error {
-		// Check perms.
-		if err := t.checkAccess(ctx, call, sntx, ""); err != nil {
-			closeSntx()
-			return err
-		}
-		// TODO(rogulenko): Check prefix permissions for children.
-		return util.Glob(ctx, call, matcher, sntx, closeSntx, util.JoinKeyParts(util.RowPrefix, t.name))
-	}
-	if t.d.batchId != nil {
-		return impl(t.d.batchReader(), func() error {
-			return nil
-		})
-	} else {
-		sn := t.d.st.NewSnapshot()
-		return impl(sn, sn.Abort)
-	}
-}
-
 ////////////////////////////////////////
 // Internal helpers
 
diff --git a/services/syncbase/server/service.go b/services/syncbase/server/service.go
index 6ca96f1..f2ec671 100644
--- a/services/syncbase/server/service.go
+++ b/services/syncbase/server/service.go
@@ -13,7 +13,6 @@
 	"sync"
 
 	"v.io/v23/context"
-	"v.io/v23/glob"
 	"v.io/v23/rpc"
 	"v.io/v23/security/access"
 	wire "v.io/v23/services/syncbase"
@@ -136,6 +135,8 @@
 ////////////////////////////////////////
 // RPC methods
 
+// TODO(sadovsky): Implement Glob__ or GlobChildren__.
+
 func (s *service) SetPermissions(ctx *context.T, call rpc.ServerCall, perms access.Permissions, version string) error {
 	return store.RunInTransaction(s.st, func(tx store.Transaction) error {
 		data := &serviceData{}
@@ -158,14 +159,14 @@
 	return data.Perms, util.FormatVersion(data.Version), nil
 }
 
-func (s *service) GlobChildren__(ctx *context.T, call rpc.GlobChildrenServerCall, matcher *glob.Element) error {
+func (s *service) ListApps(ctx *context.T, call rpc.ServerCall) ([]string, error) {
 	// Check perms.
 	sn := s.st.NewSnapshot()
 	if err := util.GetWithAuth(ctx, call, sn, s.stKey(), &serviceData{}); err != nil {
 		sn.Abort()
-		return err
+		return nil, err
 	}
-	return util.Glob(ctx, call, matcher, sn, sn.Abort, util.AppPrefix)
+	return util.ListChildren(ctx, call, sn, util.AppPrefix)
 }
 
 ////////////////////////////////////////
diff --git a/services/syncbase/server/util/constants.go b/services/syncbase/server/util/constants.go
index ab2e401..a14d006 100644
--- a/services/syncbase/server/util/constants.go
+++ b/services/syncbase/server/util/constants.go
@@ -8,9 +8,9 @@
 	"time"
 )
 
-// TODO(sadovsky): Consider using shorter strings.
-
 // Constants related to storage engine keys.
+// Note, these are persisted and therefore must not be modified.
+// Below, they are ordered lexicographically by value.
 const (
 	AppPrefix      = "$app"
 	ClockPrefix    = "$clock"
@@ -23,26 +23,48 @@
 	SyncPrefix     = "$sync"
 	TablePrefix    = "$table"
 	VersionPrefix  = "$version"
+
+	// TODO(sadovsky): Changing these prefixes breaks various tests. Tests
+	// generally shouldn't depend on the values of these constants.
+	/*
+		AppPrefix      = "a"
+		ClockPrefix    = "c"
+		DatabasePrefix = "d"
+		DbInfoPrefix   = "i"
+		LogPrefix      = "l"
+		PermsPrefix    = "p"
+		RowPrefix      = "r"
+		ServicePrefix  = "s"
+		TablePrefix    = "t"
+		VersionPrefix  = "v"
+		SyncPrefix     = "y"
+	*/
+
+	// Separator for parts of storage engine keys.
+	// TODO(sadovsky): Allow ":" in names and use a different separator here.
+	KeyPartSep = ":"
+
+	// PrefixRangeLimitSuffix is a key suffix that indicates the end of a prefix
+	// range. Must be greater than any character allowed in client-provided keys.
+	PrefixRangeLimitSuffix = "\xff"
 )
 
 // Constants related to object names.
 const (
-	// Service object name suffix for Syncbase-to-Syncbase RPCs.
-	SyncbaseSuffix = "$sync"
+	// Object name component for Syncbase-to-Syncbase (sync) RPCs.
+	// Sync object names have the form:
+	//     <syncbase>/@@sync/...
+	SyncbaseSuffix = "@@sync"
 	// Separator for batch info in database names.
-	BatchSep = ":"
-	// Separator for parts of storage engine keys.
-	KeyPartSep = ":"
-	// PrefixRangeLimitSuffix is the suffix of a key which indicates the end of
-	// a prefix range. Should be more than any regular key in the store.
-	// TODO(rogulenko): Change this constant to something out of the UTF8 space.
-	PrefixRangeLimitSuffix = "~"
+	// Batch object names have the form:
+	//     <syncbase>/<app>/$/<database>@@<batchInfo>/...
+	BatchSep = "@@"
 )
 
 // Constants related to syncbase clock.
 const (
-	// The pool.ntp.org project is a big virtual cluster of timeservers
-	// providing reliable easy to use NTP service for millions of clients.
+	// The pool.ntp.org project is a big virtual cluster of timeservers,
+	// providing reliable, easy-to-use NTP service for millions of clients.
 	// See more at http://www.pool.ntp.org/en/
 	NtpServerPool            = "pool.ntp.org"
 	NtpSampleCount           = 15
diff --git a/services/syncbase/server/util/glob.go b/services/syncbase/server/util/glob.go
deleted file mode 100644
index c483347..0000000
--- a/services/syncbase/server/util/glob.go
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package util
-
-import (
-	"v.io/v23/context"
-	"v.io/v23/glob"
-	"v.io/v23/naming"
-	"v.io/v23/rpc"
-	"v.io/x/lib/vlog"
-	"v.io/x/ref/services/syncbase/store"
-)
-
-// NOTE(nlacasse): Syncbase handles Glob requests by implementing
-// GlobChildren__ at each level (service, app, database, table).
-
-// Glob performs a glob. It calls closeSntx to close sntx.
-func Glob(ctx *context.T, call rpc.GlobChildrenServerCall, matcher *glob.Element, sntx store.SnapshotOrTransaction, closeSntx func() error, stKeyPrefix string) error {
-	prefix, _ := matcher.FixedPrefix()
-	it := sntx.Scan(ScanPrefixArgs(stKeyPrefix, prefix))
-	defer closeSntx()
-	key := []byte{}
-	for it.Advance() {
-		key = it.Key(key)
-		parts := SplitKeyParts(string(key))
-		name := parts[len(parts)-1]
-		if matcher.Match(name) {
-			if err := call.SendStream().Send(naming.GlobChildrenReplyName{Value: name}); err != nil {
-				return err
-			}
-		}
-	}
-	if err := it.Err(); err != nil {
-		vlog.VI(1).Infof("Glob() failed: %v", err)
-		call.SendStream().Send(naming.GlobChildrenReplyError{Value: naming.GlobError{Error: err}})
-	}
-	return nil
-}
diff --git a/services/syncbase/server/util/key_util.go b/services/syncbase/server/util/key_util.go
index 0ac6686..c0d79ad 100644
--- a/services/syncbase/server/util/key_util.go
+++ b/services/syncbase/server/util/key_util.go
@@ -5,18 +5,21 @@
 package util
 
 import (
+	"strconv"
 	"strings"
 
 	"v.io/v23/syncbase/util"
+	"v.io/v23/verror"
 )
 
 // JoinKeyParts builds keys for accessing data in the storage engine.
+// TODO(sadovsky): Allow ":" in names and use a different separator here.
 func JoinKeyParts(parts ...string) string {
-	// TODO(sadovsky): Figure out which delimiter makes the most sense.
 	return strings.Join(parts, KeyPartSep)
 }
 
 // SplitKeyParts is the inverse of JoinKeyParts.
+// TODO(sadovsky): Allow ":" in names and use a different separator here.
 func SplitKeyParts(key string) []string {
 	return strings.Split(key, KeyPartSep)
 }
@@ -35,3 +38,36 @@
 	}
 	return []byte(fullStart), []byte(fullLimit)
 }
+
+type BatchType int
+
+const (
+	BatchTypeSn BatchType = iota // snapshot
+	BatchTypeTx                  // transaction
+)
+
+// JoinBatchInfo encodes batch type and id into a single "info" string.
+func JoinBatchInfo(batchType BatchType, batchId uint64) string {
+	return strings.Join([]string{strconv.Itoa(int(batchType)), strconv.FormatUint(batchId, 10)}, BatchSep)
+}
+
+// SplitBatchInfo is the inverse of JoinBatchInfo.
+func SplitBatchInfo(batchInfo string) (BatchType, uint64, error) {
+	parts := strings.Split(batchInfo, BatchSep)
+	if len(parts) != 2 {
+		return BatchTypeSn, 0, verror.New(verror.ErrBadArg, nil, batchInfo)
+	}
+	batchTypeInt, err := strconv.Atoi(parts[0])
+	if err != nil {
+		return BatchTypeSn, 0, err
+	}
+	batchType := BatchType(batchTypeInt)
+	if batchType != BatchTypeSn && batchType != BatchTypeTx {
+		return BatchTypeSn, 0, verror.New(verror.ErrBadArg, nil, batchInfo)
+	}
+	batchId, err := strconv.ParseUint(parts[1], 0, 64)
+	if err != nil {
+		return BatchTypeSn, 0, err
+	}
+	return batchType, batchId, nil
+}
diff --git a/services/syncbase/server/util/list_children.go b/services/syncbase/server/util/list_children.go
new file mode 100644
index 0000000..eaa68bd
--- /dev/null
+++ b/services/syncbase/server/util/list_children.go
@@ -0,0 +1,29 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package util
+
+import (
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/x/ref/services/syncbase/store"
+)
+
+// ListChildren returns the names of all apps, databases, or tables with the
+// given key prefix. Designed for use by Service.ListApps, App.ListDatabases,
+// and Database.ListTables.
+func ListChildren(ctx *context.T, call rpc.ServerCall, sntx store.SnapshotOrTransaction, stKeyPrefix string) ([]string, error) {
+	it := sntx.Scan(ScanPrefixArgs(stKeyPrefix, ""))
+	key := []byte{}
+	res := []string{}
+	for it.Advance() {
+		key = it.Key(key)
+		parts := SplitKeyParts(string(key))
+		res = append(res, parts[len(parts)-1])
+	}
+	if err := it.Err(); err != nil {
+		return nil, err
+	}
+	return res, nil
+}
diff --git a/services/syncbase/testutil/layer.go b/services/syncbase/testutil/layer.go
index fc568b9..3f6cc54 100644
--- a/services/syncbase/testutil/layer.go
+++ b/services/syncbase/testutil/layer.go
@@ -12,6 +12,7 @@
 	"v.io/v23/context"
 	"v.io/v23/security"
 	"v.io/v23/security/access"
+	wire "v.io/v23/services/syncbase"
 	"v.io/v23/syncbase"
 	"v.io/v23/syncbase/nosql"
 	"v.io/v23/syncbase/util"
@@ -72,7 +73,8 @@
 		t.Errorf("Perms do not match: got %v, want %v", gotPerms, wantPerms)
 	}
 
-	// Even though self2 exists, Exists returns false because Read access is needed.
+	// Even though self2 exists, Exists returns false because Read access is
+	// needed.
 	assertExists(t, ctx, self2, "self2", false)
 
 	// Test that create fails if the parent perms disallow access.
@@ -90,6 +92,33 @@
 	assertExists(t, ctx, self3, "self3", false)
 }
 
+// Tests that non-ASCII UTF-8 chars are supported at all layers as long as they
+// satisfy util.ValidName.
+// This test only requires layer.Create to be implemented and thus works for
+// rows.
+func TestCreateNameValidation(t *testing.T, ctx *context.T, i interface{}) {
+	parent := makeLayer(i)
+
+	// Invalid names.
+	// TODO(sadovsky): Add names with slashes to this list once we implement
+	// client-side name validation. As it stands, some names with slashes result
+	// in RPCs against objects at the next layer of hierarchy, and naming.Join
+	// drops some leading and trailing slashes before they reach the server-side
+	// name-checking code.
+	for _, name := range []string{"a\x00", "\x00a", "@@", "a@@", "@@a", "@@a", "$", "a/$", "$/a"} {
+		if err := parent.Child(name).Create(ctx, nil); verror.ErrorID(err) != wire.ErrInvalidName.ID {
+			t.Fatalf("Create(%q) should have failed: %v", name, err)
+		}
+	}
+
+	// Valid names.
+	for _, name := range []string{"a", "aa", "*", "a*", "*a", "a*b", "a/b", "a/$$", "$$/a", "a/$$/b", "dev.v.io/a/admin@myapp.com", "alice/bob", "안녕하세요"} {
+		if err := parent.Child(name).Create(ctx, nil); err != nil {
+			t.Fatalf("Create(%q) failed: %v", name, err)
+		}
+	}
+}
+
 // TestDestroy tests that object destruction works as expected.
 func TestDestroy(t *testing.T, ctx *context.T, i interface{}) {
 	parent := makeLayer(i)
@@ -179,7 +208,7 @@
 	var err error
 
 	got, err = self.ListChildren(ctx)
-	want = []string{}
+	want = []string(nil)
 	if err != nil {
 		t.Fatalf("self.ListChildren() failed: %v", err)
 	}
@@ -210,6 +239,19 @@
 	if !reflect.DeepEqual(got, want) {
 		t.Fatalf("Lists do not match: got %v, want %v", got, want)
 	}
+
+	hello := "안녕하세요"
+	if err := self.Child(hello).Create(ctx, nil); err != nil {
+		t.Fatalf("hello.Create() failed: %v", err)
+	}
+	got, err = self.ListChildren(ctx)
+	want = []string{"x", "y", hello}
+	if err != nil {
+		t.Fatalf("self.ListChildren() failed: %v", err)
+	}
+	if !reflect.DeepEqual(got, want) {
+		t.Fatalf("Lists do not match: got %v, want %v", got, want)
+	}
 }
 
 // TestPerms tests that {Set,Get}Permissions work as expected.
diff --git a/services/syncbase/testutil/v23util.go b/services/syncbase/testutil/v23util.go
index ef2c3f6..8e370a8 100644
--- a/services/syncbase/testutil/v23util.go
+++ b/services/syncbase/testutil/v23util.go
@@ -55,7 +55,7 @@
 	}
 }
 
-// RunClient runs modules.Program and waits until it terminates.
+// RunClient runs the given program and waits until it terminates.
 func RunClient(t *v23tests.T, creds *modules.CustomCredentials, program modules.Program, args ...string) {
 	client, err := t.Shell().StartWithOpts(
 		t.Shell().DefaultStartOpts().WithCustomCredentials(creds),
diff --git a/services/xproxyd/proxy_test.go b/services/xproxyd/proxy_test.go
index 4257409..43f613a 100644
--- a/services/xproxyd/proxy_test.go
+++ b/services/xproxyd/proxy_test.go
@@ -43,14 +43,6 @@
 func TestMultipleProxies(t *testing.T) {
 	pctx, shutdown := v23.Init()
 	defer shutdown()
-	p2ctx, _, err := v23.ExperimentalWithNewFlowManager(pctx)
-	if err != nil {
-		t.Fatal(err)
-	}
-	p3ctx, _, err := v23.ExperimentalWithNewFlowManager(pctx)
-	if err != nil {
-		t.Fatal(err)
-	}
 	actx, am, err := v23.ExperimentalWithNewFlowManager(pctx)
 	if err != nil {
 		t.Fatal(err)
@@ -62,9 +54,9 @@
 
 	pep := startProxy(t, pctx, address{"tcp", "127.0.0.1:0"})
 
-	p2ep := startProxy(t, p2ctx, address{"v23", pep.String()}, address{"tcp", "127.0.0.1:0"})
+	p2ep := startProxy(t, pctx, address{"v23", pep.String()}, address{"tcp", "127.0.0.1:0"})
 
-	p3ep := startProxy(t, p3ctx, address{"v23", p2ep.String()}, address{"tcp", "127.0.0.1:0"})
+	p3ep := startProxy(t, pctx, address{"v23", p2ep.String()}, address{"tcp", "127.0.0.1:0"})
 
 	if err := am.Listen(actx, "v23", p3ep.String()); err != nil {
 		t.Fatal(err)
@@ -147,7 +139,7 @@
 		ls.Addrs = append(ls.Addrs, addr)
 	}
 	ctx = v23.WithListenSpec(ctx, ls)
-	proxy, err := xproxyd.New(ctx)
+	proxy, _, err := xproxyd.New(ctx)
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/services/xproxyd/proxyd.go b/services/xproxyd/proxyd.go
index fc97ceb..d05a561 100644
--- a/services/xproxyd/proxyd.go
+++ b/services/xproxyd/proxyd.go
@@ -24,41 +24,45 @@
 	proxyEndpoints []naming.Endpoint
 }
 
-func New(ctx *context.T) (*proxy, error) {
+func New(ctx *context.T) (*proxy, *context.T, error) {
+	ctx, mgr, err := v23.ExperimentalWithNewFlowManager(ctx)
+	if err != nil {
+		return nil, nil, err
+	}
 	p := &proxy{
-		m: v23.ExperimentalGetFlowManager(ctx),
+		m: mgr,
 	}
 	for _, addr := range v23.GetListenSpec(ctx).Addrs {
 		if addr.Protocol == "v23" {
 			ep, err := v23.NewEndpoint(addr.Address)
 			if err != nil {
-				return nil, err
+				return nil, nil, err
 			}
 			f, err := p.m.Dial(ctx, ep, proxyBlessingsForPeer{}.run)
 			if err != nil {
-				return nil, err
+				return nil, nil, err
 			}
 			// Send a byte telling the acceptor that we are a proxy.
 			if err := writeMessage(ctx, &message.MultiProxyRequest{}, f); err != nil {
-				return nil, err
+				return nil, nil, err
 			}
 			msg, err := readMessage(ctx, f)
 			if err != nil {
-				return nil, err
+				return nil, nil, err
 			}
 			m, ok := msg.(*message.ProxyResponse)
 			if !ok {
-				return nil, NewErrUnexpectedMessage(ctx, fmt.Sprintf("%t", m))
+				return nil, nil, NewErrUnexpectedMessage(ctx, fmt.Sprintf("%t", m))
 			}
 			p.mu.Lock()
 			p.proxyEndpoints = append(p.proxyEndpoints, m.Endpoints...)
 			p.mu.Unlock()
 		} else if err := p.m.Listen(ctx, addr.Protocol, addr.Address); err != nil {
-			return nil, err
+			return nil, nil, err
 		}
 	}
 	go p.listenLoop(ctx)
-	return p, nil
+	return p, ctx, nil
 }
 
 func (p *proxy) ListeningEndpoints() []naming.Endpoint {