Merge "ref: Added lame duck timeout option"
diff --git a/cmd/sb/doc.go b/cmd/sb/doc.go
index 691b2fe..76a0dc5 100644
--- a/cmd/sb/doc.go
+++ b/cmd/sb/doc.go
@@ -10,8 +10,9 @@
 // The user can then enter the following at the command line:
 //     1. dump - to get a dump of the database
 //     2. a syncbase select statement - which is executed and results printed to stdout
-//     3. make-demo - to create demo tables in the database to experiment with, equivalent to -make-demo flag
-//     4. exit (or quit) - to exit the program
+//     3. a syncbase delete statement - which is executed to delete k/v pairs from a table
+//     4. make-demo - to create demo tables in the database to experiment with, equivalent to -make-demo flag
+//     5. exit (or quit) - to exit the program
 //
 // When the shell is running non-interactively (stdin not connected to a tty),
 // errors cause the shell to exit with a non-zero status.
@@ -50,6 +51,12 @@
 //     002002,"{CustId: 2, InvoiceNum: 1002, Amount: 243, ShipTo: {Street: ""888 Any St."", City: ""Collins"", State: ""IA"", Zip: ""50055""}}"
 //     002003,"{CustId: 2, InvoiceNum: 1004, Amount: 787, ShipTo: {Street: ""999 Any St."", City: ""Collins"", State: ""IA"", Zip: ""50055""}}"
 //     002004,"{CustId: 2, InvoiceNum: 1006, Amount: 88, ShipTo: {Street: ""101010 Any St."", City: ""Collins"", State: ""IA"", Zip: ""50055""}}"
+//     ? delete from Customers where k = "001002";
+//     +-------+
+//     | Count |
+//     +-------+
+//     |     1 |
+//     +-------+
 //     ? exit;
 //     >
 package main
diff --git a/cmd/sb/shell.go b/cmd/sb/shell.go
index 1469503..e768e84 100644
--- a/cmd/sb/shell.go
+++ b/cmd/sb/shell.go
@@ -127,7 +127,7 @@
 				case "make-demo":
 					// TODO(jkline): add an "Are you sure prompt" to give the user a 2nd chance.
 					err = makeDemoDB(ctx, env.Stdout, d)
-				case "select":
+				case "select", "delete":
 					err = queryExec(ctx, env.Stdout, d, q)
 				case "destroy":
 					if len(tq) == 3 {
diff --git a/cmd/servicerunner/servicerunner_test.go b/cmd/servicerunner/servicerunner_test.go
index c3d3a31..c6a0e0a 100644
--- a/cmd/servicerunner/servicerunner_test.go
+++ b/cmd/servicerunner/servicerunner_test.go
@@ -23,12 +23,6 @@
 	"v.io/x/ref"
 )
 
-// We provide our own TestMain, rather than allowing jiri test generate to
-// create one for us, to ensure all files require the "wspr" build tag.
-func TestMain(m *testing.M) {
-	os.Exit(m.Run())
-}
-
 func TestServiceRunner(t *testing.T) {
 	ref.EnvClearCredentials()
 	tmpdir, err := ioutil.TempDir("", "servicerunner_test")
diff --git a/examples/rps/internal/common.go b/examples/rps/internal/common.go
index c777878..ca771a7 100644
--- a/examples/rps/internal/common.go
+++ b/examples/rps/internal/common.go
@@ -30,8 +30,8 @@
 }
 
 // FindJudge returns a random rock-paper-scissors judge from the mount table.
-func FindJudge(ctx *context.T) (string, error) {
-	judges, err := findAll(ctx, "judge")
+func FindJudge(ctx *context.T, prefix string) (string, error) {
+	judges, err := findAll(ctx, prefix, "judge")
 	if err != nil {
 		return "", err
 	}
@@ -42,8 +42,8 @@
 }
 
 // FindPlayer returns a random rock-paper-scissors player from the mount table.
-func FindPlayer(ctx *context.T) (string, error) {
-	players, err := findAll(ctx, "player")
+func FindPlayer(ctx *context.T, prefix string) (string, error) {
+	players, err := findAll(ctx, prefix, "player")
 	if err != nil {
 		return "", err
 	}
@@ -55,18 +55,18 @@
 
 // FindScoreKeepers returns all the rock-paper-scissors score keepers from the
 // mount table.
-func FindScoreKeepers(ctx *context.T) ([]string, error) {
-	sKeepers, err := findAll(ctx, "scorekeeper")
+func FindScoreKeepers(ctx *context.T, prefix string) ([]string, error) {
+	sKeepers, err := findAll(ctx, prefix, "scorekeeper")
 	if err != nil {
 		return nil, err
 	}
 	return sKeepers, nil
 }
 
-func findAll(ctx *context.T, t string) ([]string, error) {
+func findAll(ctx *context.T, prefix, t string) ([]string, error) {
 	start := time.Now()
 	ns := v23.GetNamespace(ctx)
-	c, err := ns.Glob(ctx, "rps/"+t+"/*")
+	c, err := ns.Glob(ctx, naming.Join(prefix, "rps", t, "*"))
 	if err != nil {
 		ctx.Infof("mt.Glob failed: %v", err)
 		return nil, err
@@ -77,7 +77,8 @@
 		case *naming.GlobReplyError:
 			ctx.VI(1).Infof("findAll(%q) error for %q: %v", t, v.Value.Name, v.Value.Error)
 		case *naming.GlobReplyEntry:
-			servers = append(servers, v.Value.Name)
+
+			servers = append(servers, strings.TrimPrefix(v.Value.Name, naming.Clean(prefix)+"/"))
 		}
 	}
 	ctx.VI(1).Infof("findAll(%q) elapsed: %s", t, time.Now().Sub(start))
diff --git a/examples/rps/rpsbot/doc.go b/examples/rps/rpsbot/doc.go
index 2a663a1..8d7e8ee 100644
--- a/examples/rps/rpsbot/doc.go
+++ b/examples/rps/rpsbot/doc.go
@@ -16,6 +16,10 @@
 The rpsbot flags are:
  -acl-file=
    File containing JSON-encoded Permissions.
+ -mount-prefix=vlab
+   The mount prefix to use. The published names will be
+   <mount-prefix>/rps/player/<name>, <mount-prefix>/rps/judge/<name>, and
+   <mount-prefix>/rps/scorekeeper/<name>.
  -name=
    Identifier to publish as (defaults to principal's blessing names).
  -num-games=-1
diff --git a/examples/rps/rpsbot/judge.go b/examples/rps/rpsbot/judge.go
index f63e15f..78d855d 100644
--- a/examples/rps/rpsbot/judge.go
+++ b/examples/rps/rpsbot/judge.go
@@ -12,6 +12,7 @@
 	"time"
 
 	"v.io/v23/context"
+	"v.io/v23/naming"
 
 	"v.io/x/ref/examples/rps"
 	"v.io/x/ref/examples/rps/internal"
@@ -230,7 +231,7 @@
 	// Send the score card to the score keepers.
 	scoreCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
 	defer cancel()
-	keepers, err := internal.FindScoreKeepers(scoreCtx)
+	keepers, err := internal.FindScoreKeepers(scoreCtx, mountPrefix)
 	if err != nil || len(keepers) == 0 {
 		ctx.Infof("No score keepers: %v", err)
 		return
@@ -310,8 +311,7 @@
 
 func (j *Judge) sendScore(ctx *context.T, address string, score rps.ScoreCard, wg *sync.WaitGroup) error {
 	defer wg.Done()
-	k := rps.RockPaperScissorsClient(address)
-	if err := k.Record(ctx, score); err != nil {
+	if err := rps.ScoreKeeperClient(naming.Join(mountPrefix, address)).Record(ctx, score); err != nil {
 		logger.Global().Infof("Record: %v", err)
 		return err
 	}
diff --git a/examples/rps/rpsbot/main.go b/examples/rps/rpsbot/main.go
index b1283f4..8a493b0 100644
--- a/examples/rps/rpsbot/main.go
+++ b/examples/rps/rpsbot/main.go
@@ -14,9 +14,10 @@
 
 	"v.io/x/lib/cmdline"
 
-	"v.io/v23/context"
-
 	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/naming"
+
 	"v.io/x/ref/examples/rps"
 	"v.io/x/ref/examples/rps/internal"
 	"v.io/x/ref/lib/signals"
@@ -28,12 +29,14 @@
 var (
 	name, aclFile string
 	numGames      int
+	mountPrefix   string
 )
 
 func main() {
 	cmdRoot.Flags.StringVar(&name, "name", "", "Identifier to publish as (defaults to principal's blessing names).")
 	cmdRoot.Flags.StringVar(&aclFile, "acl-file", "", "File containing JSON-encoded Permissions.")
 	cmdRoot.Flags.IntVar(&numGames, "num-games", -1, "Number of games to play (-1 means unlimited).")
+	cmdRoot.Flags.StringVar(&mountPrefix, "mount-prefix", "vlab", "The mount prefix to use. The published names will be <mount-prefix>/rps/player/<name>, <mount-prefix>/rps/judge/<name>, and <mount-prefix>/rps/scorekeeper/<name>.")
 	cmdline.HideGlobalFlagsExcept()
 	cmdline.Main(cmdRoot)
 }
@@ -57,9 +60,9 @@
 		name = internal.CreateName(ctx)
 	}
 	names := []string{
-		fmt.Sprintf("rps/judge/%s", name),
-		fmt.Sprintf("rps/player/%s", name),
-		fmt.Sprintf("rps/scorekeeper/%s", name),
+		naming.Join(mountPrefix, "rps", "judge", name),
+		naming.Join(mountPrefix, "rps", "player", name),
+		naming.Join(mountPrefix, "rps", "scorekeeper", name),
 	}
 	ctx, server, err := v23.WithNewServer(ctx, names[0], rps.RockPaperScissorsServer(rpsService), auth)
 	if err != nil {
diff --git a/examples/rps/rpsbot/player.go b/examples/rps/rpsbot/player.go
index 592ffcb..aaa4488 100644
--- a/examples/rps/rpsbot/player.go
+++ b/examples/rps/rpsbot/player.go
@@ -9,6 +9,7 @@
 	"time"
 
 	"v.io/v23/context"
+	"v.io/v23/naming"
 
 	"v.io/x/ref/examples/rps"
 	"v.io/x/ref/examples/rps/internal"
@@ -44,7 +45,7 @@
 }
 
 func (p *Player) InitiateGame(ctx *context.T) error {
-	judge, err := internal.FindJudge(ctx)
+	judge, err := internal.FindJudge(ctx, mountPrefix)
 	if err != nil {
 		ctx.Infof("FindJudge: %v", err)
 		return err
@@ -57,7 +58,7 @@
 	ctx.VI(1).Infof("Created gameID %q on %q", gameID, judge)
 
 	for {
-		opponent, err := internal.FindPlayer(ctx)
+		opponent, err := internal.FindPlayer(ctx, mountPrefix)
 		if err != nil {
 			ctx.Infof("FindPlayer: %v", err)
 			return err
@@ -81,21 +82,19 @@
 	return nil
 }
 
-func (p *Player) createGame(ctx *context.T, server string) (rps.GameId, rps.GameOptions, error) {
-	j := rps.RockPaperScissorsClient(server)
+func (p *Player) createGame(ctx *context.T, judge string) (rps.GameId, rps.GameOptions, error) {
 	numRounds := 3 + rand.Intn(3)
 	gameType := rps.Classic
 	if rand.Intn(2) == 1 {
 		gameType = rps.LizardSpock
 	}
 	gameOpts := rps.GameOptions{NumRounds: int32(numRounds), GameType: gameType}
-	gameId, err := j.CreateGame(ctx, gameOpts)
+	gameId, err := rps.JudgeClient(naming.Join(mountPrefix, judge)).CreateGame(ctx, gameOpts)
 	return gameId, gameOpts, err
 }
 
 func (p *Player) sendChallenge(ctx *context.T, opponent, judge string, gameID rps.GameId, gameOpts rps.GameOptions) error {
-	o := rps.RockPaperScissorsClient(opponent)
-	return o.Challenge(ctx, judge, gameID, gameOpts)
+	return rps.PlayerClient(naming.Join(mountPrefix, opponent)).Challenge(ctx, judge, gameID, gameOpts)
 }
 
 // challenge receives an incoming challenge and starts to play a new game.
@@ -113,8 +112,7 @@
 	defer cancel()
 	p.gamesInProgress.Incr(1)
 	defer p.gamesInProgress.Incr(-1)
-	j := rps.RockPaperScissorsClient(judge)
-	game, err := j.Play(ctx, gameID)
+	game, err := rps.JudgeClient(naming.Join(mountPrefix, judge)).Play(ctx, gameID)
 	if err != nil {
 		return rps.PlayResult{}, err
 	}
diff --git a/examples/rps/rpsplayer/doc.go b/examples/rps/rpsplayer/doc.go
index 18d1d1a..e25f7de 100644
--- a/examples/rps/rpsplayer/doc.go
+++ b/examples/rps/rpsplayer/doc.go
@@ -15,6 +15,9 @@
 The rpsplayer flags are:
  -acl-file=
    File containing JSON-encoded Permissions.
+ -mount-prefix=vlab
+   The mount prefix to use. The published name will be
+   <mount-prefix>/rps/player/<name>.
  -name=
    Identifier to publish as (defaults to principal's blessing names).
 
diff --git a/examples/rps/rpsplayer/main.go b/examples/rps/rpsplayer/main.go
index d388699..76551ec 100644
--- a/examples/rps/rpsplayer/main.go
+++ b/examples/rps/rpsplayer/main.go
@@ -29,11 +29,12 @@
 	_ "v.io/x/ref/runtime/factories/roaming"
 )
 
-var name, aclFile string
+var name, aclFile, mountPrefix string
 
 func main() {
 	cmdRoot.Flags.StringVar(&name, "name", "", "Identifier to publish as (defaults to principal's blessing names).")
 	cmdRoot.Flags.StringVar(&aclFile, "acl-file", "", "File containing JSON-encoded Permissions.")
+	cmdRoot.Flags.StringVar(&mountPrefix, "mount-prefix", "vlab", "The mount prefix to use. The published name will be <mount-prefix>/rps/player/<name>.")
 	cmdline.HideGlobalFlagsExcept()
 	cmdline.Main(cmdRoot)
 }
@@ -125,7 +126,7 @@
 	if name == "" {
 		name = internal.CreateName(ctx)
 	}
-	fullname := fmt.Sprintf("rps/player/%s", name)
+	fullname := naming.Join(mountPrefix, "rps", "player", name)
 	service := rps.PlayerServer(&impl{ch: ch})
 	auth := internal.NewAuthorizer(aclFile)
 	ctx, server, err := v23.WithNewServer(ctx, fullname, service, auth)
@@ -188,22 +189,19 @@
 	return nil
 }
 
-func createGame(ctx *context.T, server string, opts rps.GameOptions) (rps.GameId, error) {
-	j := rps.RockPaperScissorsClient(server)
-	return j.CreateGame(ctx, opts)
+func createGame(ctx *context.T, judge string, opts rps.GameOptions) (rps.GameId, error) {
+	return rps.JudgeClient(naming.Join(mountPrefix, judge)).CreateGame(ctx, opts)
 }
 
 func sendChallenge(ctx *context.T, opponent, judge string, gameID rps.GameId, gameOpts rps.GameOptions) error {
-	o := rps.RockPaperScissorsClient(opponent)
-	return o.Challenge(ctx, judge, gameID, gameOpts)
+	return rps.PlayerClient(naming.Join(mountPrefix, opponent)).Challenge(ctx, judge, gameID, gameOpts)
 }
 
 func playGame(outer *context.T, judge string, gameID rps.GameId) (rps.PlayResult, error) {
 	ctx, cancel := context.WithTimeout(outer, 10*time.Minute)
 	defer cancel()
 	fmt.Println()
-	j := rps.RockPaperScissorsClient(judge)
-	game, err := j.Play(ctx, gameID)
+	game, err := rps.JudgeClient(naming.Join(mountPrefix, judge)).Play(ctx, gameID)
 	if err != nil {
 		return rps.PlayResult{}, err
 	}
@@ -294,7 +292,7 @@
 func findAll(ctx *context.T, t string, out chan []string) {
 	ns := v23.GetNamespace(ctx)
 	var result []string
-	c, err := ns.Glob(ctx, "rps/"+t+"/*")
+	c, err := ns.Glob(ctx, naming.Join(mountPrefix, "rps", t, "*"))
 	if err != nil {
 		ctx.Infof("ns.Glob failed: %v", err)
 		out <- result
@@ -306,7 +304,7 @@
 			fmt.Print("E")
 		case *naming.GlobReplyEntry:
 			fmt.Print(".")
-			result = append(result, v.Value.Name)
+			result = append(result, strings.TrimPrefix(v.Value.Name, naming.Clean(mountPrefix)+"/"))
 		}
 	}
 	if len(result) == 0 {
diff --git a/examples/rps/rpsscorekeeper/doc.go b/examples/rps/rpsscorekeeper/doc.go
index 88ad4c1..0e9a6ea 100644
--- a/examples/rps/rpsscorekeeper/doc.go
+++ b/examples/rps/rpsscorekeeper/doc.go
@@ -16,6 +16,9 @@
 The rpsscorekeeper flags are:
  -acl-file=
    File containing JSON-encoded Permissions.
+ -mount-prefix=vlab
+   The mount prefix to use. The published name will be
+   <mount-prefix>/rps/scorekeeper/<name>.
 
 The global flags are:
  -alsologtostderr=true
diff --git a/examples/rps/rpsscorekeeper/main.go b/examples/rps/rpsscorekeeper/main.go
index 3f4ae27..a35bb15 100644
--- a/examples/rps/rpsscorekeeper/main.go
+++ b/examples/rps/rpsscorekeeper/main.go
@@ -12,11 +12,12 @@
 
 	"v.io/x/lib/cmdline"
 
+	"v.io/v23"
 	"v.io/v23/context"
+	"v.io/v23/naming"
 	"v.io/v23/rpc"
 	"v.io/v23/security"
 
-	"v.io/v23"
 	"v.io/x/ref/examples/rps"
 	"v.io/x/ref/examples/rps/internal"
 	"v.io/x/ref/lib/v23cmd"
@@ -24,10 +25,11 @@
 	_ "v.io/x/ref/runtime/factories/roaming"
 )
 
-var aclFile string
+var aclFile, mountPrefix string
 
 func main() {
 	cmdRoot.Flags.StringVar(&aclFile, "acl-file", "", "File containing JSON-encoded Permissions.")
+	cmdRoot.Flags.StringVar(&mountPrefix, "mount-prefix", "vlab", "The mount prefix to use. The published name will be <mount-prefix>/rps/scorekeeper/<name>.")
 	cmdline.HideGlobalFlagsExcept()
 	cmdline.Main(cmdRoot)
 }
@@ -57,7 +59,7 @@
 func runScoreKeeper(ctx *context.T, env *cmdline.Env, args []string) error {
 	ch := make(chan rps.ScoreCard)
 	rpsService := &impl{ch}
-	name := fmt.Sprintf("rps/scorekeeper/%s", internal.CreateName(ctx))
+	name := naming.Join(mountPrefix, "rps", "scorekeeper", internal.CreateName(ctx))
 	service := rps.ScoreKeeperServer(rpsService)
 	authorizer := internal.NewAuthorizer(aclFile)
 	ctx, server, err := v23.WithNewServer(ctx, name, service, authorizer)
diff --git a/lib/security/serialization/serialization_test.go b/lib/security/serialization/serialization_test.go
index 4b7b58e..6b99196 100644
--- a/lib/security/serialization/serialization_test.go
+++ b/lib/security/serialization/serialization_test.go
@@ -13,7 +13,6 @@
 	"io"
 	"io/ioutil"
 	mrand "math/rand"
-	"os"
 	"reflect"
 	"strings"
 	"testing"
@@ -23,12 +22,6 @@
 	"v.io/x/ref/test/testutil"
 )
 
-// We call our own TestMain here because jiri test generate causes an import cycle
-// in this package.
-func TestMain(m *testing.M) {
-	os.Exit(m.Run())
-}
-
 type bufferCloser struct {
 	bytes.Buffer
 }
diff --git a/lib/signals/signals_test.go b/lib/signals/signals_test.go
index 085a7f4..6e2c125 100644
--- a/lib/signals/signals_test.go
+++ b/lib/signals/signals_test.go
@@ -116,7 +116,7 @@
 	cmd, stdinPipe := startFn(t, sh, handleDefaults, false)
 	cmd.S.Expect("ready")
 	checkSignalIsDefault(t, syscall.SIGINT)
-	syscall.Kill(cmd.Process().Pid, syscall.SIGINT)
+	cmd.Signal(syscall.SIGINT)
 	cmd.S.Expectf("received signal %s", syscall.SIGINT)
 	fmt.Fprintf(stdinPipe, "close\n")
 	cmd.Wait()
@@ -182,10 +182,10 @@
 	cmd, _ := startFn(t, sh, handleDefaults, true)
 	cmd.S.Expect("ready")
 	checkSignalIsDefault(t, syscall.SIGTERM)
-	syscall.Kill(cmd.Process().Pid, syscall.SIGTERM)
+	cmd.Signal(syscall.SIGTERM)
 	cmd.S.Expectf("received signal %s", syscall.SIGTERM)
 	checkSignalIsDefault(t, syscall.SIGINT)
-	syscall.Kill(cmd.Process().Pid, syscall.SIGINT)
+	cmd.Signal(syscall.SIGINT)
 	cmd.Wait()
 	checkExitStatus(t, cmd, DoubleStopExitCode)
 }
@@ -200,7 +200,7 @@
 	cmd, stdinPipe := startFn(t, sh, handleDefaults, true)
 	cmd.S.Expect("ready")
 	checkSignalIsDefault(t, syscall.SIGTERM)
-	syscall.Kill(cmd.Process().Pid, syscall.SIGTERM)
+	cmd.Signal(syscall.SIGTERM)
 	cmd.S.Expectf("received signal %s", syscall.SIGTERM)
 	fmt.Fprintf(stdinPipe, "stop\n")
 	cmd.Wait()
@@ -232,7 +232,7 @@
 	cmd, _ := startFn(t, sh, handleDefaults, true)
 	cmd.S.Expect("ready")
 	checkSignalIsNotDefault(t, syscall.SIGABRT)
-	syscall.Kill(cmd.Process().Pid, syscall.SIGABRT)
+	cmd.Signal(syscall.SIGABRT)
 	cmd.Wait()
 	checkExitStatus(t, cmd, 2)
 }
@@ -250,9 +250,9 @@
 	// Even if we ignore the channel that ShutdownOnSignals returns,
 	// sending two signals should still cause the process to exit.
 	checkSignalIsDefault(t, syscall.SIGTERM)
-	syscall.Kill(cmd.Process().Pid, syscall.SIGTERM)
+	cmd.Signal(syscall.SIGTERM)
 	checkSignalIsDefault(t, syscall.SIGINT)
-	syscall.Kill(cmd.Process().Pid, syscall.SIGINT)
+	cmd.Signal(syscall.SIGINT)
 	cmd.Wait()
 	checkExitStatus(t, cmd, DoubleStopExitCode)
 }
@@ -266,7 +266,7 @@
 	cmd, stdinPipe := startFn(t, sh, handleCustom, true)
 	cmd.S.Expect("ready")
 	checkSignalIsNotDefault(t, syscall.SIGABRT)
-	syscall.Kill(cmd.Process().Pid, syscall.SIGABRT)
+	cmd.Signal(syscall.SIGABRT)
 	cmd.S.Expectf("received signal %s", syscall.SIGABRT)
 	fmt.Fprintf(stdinPipe, "stop\n")
 	cmd.Wait()
@@ -284,7 +284,7 @@
 			cmd, stdinPipe := startFn(t, sh, handleCustomWithStop, true)
 			cmd.S.Expect("ready")
 			checkSignalIsNotDefault(t, signal)
-			syscall.Kill(cmd.Process().Pid, signal)
+			cmd.Signal(signal)
 			cmd.S.Expectf("received signal %s", signal)
 			fmt.Fprintf(stdinPipe, "close\n")
 			cmd.Wait()
diff --git a/lib/v23test/v23test.go b/lib/v23test/v23test.go
index 2f97adf..fa1408e 100644
--- a/lib/v23test/v23test.go
+++ b/lib/v23test/v23test.go
@@ -7,8 +7,6 @@
 // StartRootMountTable, and StartSyncbase.
 package v23test
 
-// TODO(sadovsky): Add DebugSystemShell command.
-
 import (
 	"errors"
 	"fmt"
@@ -18,12 +16,14 @@
 	"runtime"
 	"runtime/debug"
 	"strings"
+	"syscall"
 	"testing"
 	"time"
 
 	"v.io/v23"
 	"v.io/v23/context"
 	"v.io/v23/rpc"
+	"v.io/x/lib/envvar"
 	"v.io/x/lib/gosh"
 	"v.io/x/ref"
 	"v.io/x/ref/test"
@@ -42,7 +42,7 @@
 // - Eliminate test.V23Init() and either add v23test.Init() or have v23.Init()
 //   check for an env var and perform test-specific configuration.
 // - Switch to using the testing package's -test.short flag and eliminate
-//   SkipUnlessRunningIntegrationTests, the -v23tests flag, and the "jiri test"
+//   SkipUnlessRunningIntegrationTests, the -v23.tests flag, and the "jiri test"
 //   implementation that parses test code to identify integration tests.
 
 // Cmd wraps gosh.Cmd and provides Vanadium-specific functionality.
@@ -92,12 +92,9 @@
 func NewShell(t *testing.T, opts Opts) *Shell {
 	fillDefaults(t, &opts)
 
-	if t != nil {
-		if !calledRun {
-			t.Fatal("must call v23test.Run(m.Run) from TestMain")
-			return nil
-		}
-
+	if t != nil && !calledRun {
+		t.Fatal("must call v23test.Run(m.Run) from TestMain")
+		return nil
 	}
 
 	// Note: On error, NewShell returns a *Shell with Opts.Fatalf initialized to
@@ -157,6 +154,16 @@
 	return ctx
 }
 
+// Cleanup cleans up all resources associated with this Shell.
+// See gosh.Shell.Cleanup for detailed description.
+func (sh *Shell) Cleanup() {
+	// Run sh.Shell.Cleanup even if DebugSystemShell panics.
+	defer sh.Shell.Cleanup()
+	if sh.t != nil && sh.t.Failed() && test.IntegrationTestsDebugShellOnError {
+		sh.DebugSystemShell()
+	}
+}
+
 // Run does some initialization work, then returns run(). Exported so that
 // TestMain functions can simply call os.Exit(v23test.Run(m.Run)).
 func Run(run func() int) int {
@@ -229,10 +236,7 @@
 }
 
 // Main returns a Cmd for an invocation of the given registered main() function.
-// Intended usage: Have your program's main() call RealMain, then write a parent
-// program that uses Shell.Main to run RealMain in a child process. With this
-// approach, RealMain can be compiled into the parent program's binary. Caveat:
-// potential flag collisions.
+// See gosh.Shell.Main for detailed description.
 func (sh *Shell) Main(fn *gosh.Fn, args ...string) *Cmd {
 	c := sh.Shell.Main(fn, args...)
 	if sh.Err != nil {
@@ -242,6 +246,74 @@
 }
 
 ////////////////////////////////////////////////////////////////////////////////
+// DebugSystemShell
+
+// DebugSystemShell drops the user into a debug system shell (e.g. bash) that
+// includes all environment variables from sh, and sets V23_BIN_DIR to
+// sh.Opts.BinDir. If there is no controlling TTY, DebugSystemShell does
+// nothing.
+func (sh *Shell) DebugSystemShell() {
+	// Make sure we have non-nil Fatalf and Logf functions.
+	opts := Opts{Fatalf: sh.Opts.Fatalf, Logf: sh.Opts.Logf}
+	fillDefaults(sh.t, &opts)
+	fatalf, logf := opts.Fatalf, opts.Logf
+
+	cwd, err := os.Getwd()
+	if err != nil {
+		fatalf("Getwd() failed: %v\n", err)
+		return
+	}
+
+	// Transfer stdin, stdout, and stderr to the new process, and set target
+	// directory for the system shell to start in.
+	devtty := "/dev/tty"
+	fd, err := syscall.Open(devtty, syscall.O_RDWR, 0)
+	if err != nil {
+		logf("WARNING: Open(%q) failed: %v\n", devtty, err)
+		return
+	}
+
+	file := os.NewFile(uintptr(fd), devtty)
+	attr := os.ProcAttr{
+		Files: []*os.File{file, file, file},
+		Dir:   cwd,
+	}
+	env := envvar.MergeMaps(envvar.SliceToMap(os.Environ()), sh.Vars)
+	env[ref.EnvCredentials] = sh.ForkCredentials("debug").Handle
+	env[envBinDir] = sh.Opts.BinDir
+	attr.Env = envvar.MapToSlice(env)
+
+	write := func(s string) {
+		if _, err := file.WriteString(s); err != nil {
+			fatalf("WriteString(%q) failed: %v\n", s, err)
+			return
+		}
+	}
+
+	write(">> Starting a new interactive shell\n")
+	write(">> Hit CTRL-D to resume the test\n")
+
+	shellPath := "/bin/sh"
+	if shellPathFromEnv := os.Getenv("SHELL"); shellPathFromEnv != "" {
+		shellPath = shellPathFromEnv
+	}
+	proc, err := os.StartProcess(shellPath, []string{}, &attr)
+	if err != nil {
+		fatalf("StartProcess(%q) failed: %v\n", shellPath, err)
+		return
+	}
+
+	// Wait until the user exits the shell.
+	state, err := proc.Wait()
+	if err != nil {
+		fatalf("Wait() failed: %v\n", err)
+		return
+	}
+
+	write(fmt.Sprintf(">> Exited shell: %s\n", state.String()))
+}
+
+////////////////////////////////////////////////////////////////////////////////
 // Internals
 
 func callerName() (string, error) {
diff --git a/runtime/factories/android/android.go b/runtime/factories/android/android.go
index e09c89d..deabcd4 100644
--- a/runtime/factories/android/android.go
+++ b/runtime/factories/android/android.go
@@ -28,25 +28,18 @@
 	"v.io/x/ref/runtime/internal"
 	"v.io/x/ref/runtime/internal/lib/appcycle"
 	"v.io/x/ref/runtime/internal/lib/roaming"
-	"v.io/x/ref/runtime/internal/lib/websocket"
 	"v.io/x/ref/runtime/internal/lib/xwebsocket"
 	"v.io/x/ref/runtime/internal/rt"
 	_ "v.io/x/ref/runtime/protocols/tcp"
 	_ "v.io/x/ref/runtime/protocols/ws"
 	_ "v.io/x/ref/runtime/protocols/wsh"
 	"v.io/x/ref/services/debug/debuglib"
-
-	// TODO(suharshs): Remove these once we switch to the flow protocols.
-	_ "v.io/x/ref/runtime/internal/rpc/protocols/tcp"
-	_ "v.io/x/ref/runtime/internal/rpc/protocols/ws"
-	_ "v.io/x/ref/runtime/internal/rpc/protocols/wsh"
 )
 
 var commonFlags *flags.Flags
 
 func init() {
 	v23.RegisterRuntimeFactory(Init)
-	rpc.RegisterUnknownProtocol("wsh", websocket.HybridDial, websocket.HybridResolve, websocket.HybridListener)
 	flow.RegisterUnknownProtocol("wsh", xwebsocket.WSH{})
 	commonFlags = flags.CreateAndRegister(flag.CommandLine, flags.Runtime, flags.Listen)
 }
diff --git a/runtime/factories/chrome/chrome.go b/runtime/factories/chrome/chrome.go
index 9408d82..f40d965 100644
--- a/runtime/factories/chrome/chrome.go
+++ b/runtime/factories/chrome/chrome.go
@@ -16,22 +16,16 @@
 
 	"v.io/x/ref/lib/flags"
 	"v.io/x/ref/runtime/internal"
-	"v.io/x/ref/runtime/internal/lib/websocket"
 	"v.io/x/ref/runtime/internal/lib/xwebsocket"
 	grt "v.io/x/ref/runtime/internal/rt"
 	_ "v.io/x/ref/runtime/protocols/ws"
 	_ "v.io/x/ref/runtime/protocols/wsh_nacl"
-
-	// TODO(suharshs): Remove this after we switch to the flow protocols.
-	_ "v.io/x/ref/runtime/internal/rpc/protocols/ws"
-	_ "v.io/x/ref/runtime/internal/rpc/protocols/wsh_nacl"
 )
 
 var commonFlags *flags.Flags
 
 func init() {
 	v23.RegisterRuntimeFactory(Init)
-	rpc.RegisterUnknownProtocol("wsh", websocket.Dial, websocket.Resolve, websocket.Listener)
 	flow.RegisterUnknownProtocol("wsh", xwebsocket.WS{})
 	commonFlags = flags.CreateAndRegister(flag.CommandLine, flags.Runtime)
 }
diff --git a/runtime/factories/fake/fake.go b/runtime/factories/fake/fake.go
index 4fd788e..89ec1d7 100644
--- a/runtime/factories/fake/fake.go
+++ b/runtime/factories/fake/fake.go
@@ -14,20 +14,13 @@
 	"v.io/v23"
 	"v.io/v23/context"
 	"v.io/v23/flow"
-	"v.io/v23/rpc"
 
 	"v.io/x/ref/runtime/internal"
-	"v.io/x/ref/runtime/internal/lib/websocket"
 	"v.io/x/ref/runtime/internal/lib/xwebsocket"
+	_ "v.io/x/ref/runtime/protocols/local"
 	_ "v.io/x/ref/runtime/protocols/tcp"
 	_ "v.io/x/ref/runtime/protocols/ws"
 	_ "v.io/x/ref/runtime/protocols/wsh"
-
-	// TODO(suharshs): Remove these once we switch to the flow protocols.
-	_ "v.io/x/ref/runtime/internal/rpc/protocols/tcp"
-	_ "v.io/x/ref/runtime/internal/rpc/protocols/ws"
-	_ "v.io/x/ref/runtime/internal/rpc/protocols/wsh"
-	_ "v.io/x/ref/runtime/protocols/local"
 )
 
 var (
@@ -41,7 +34,6 @@
 
 func init() {
 	v23.RegisterRuntimeFactory(Init)
-	rpc.RegisterUnknownProtocol("wsh", websocket.HybridDial, websocket.HybridResolve, websocket.HybridListener)
 	flow.RegisterUnknownProtocol("wsh", xwebsocket.WSH{})
 }
 
diff --git a/runtime/factories/gce/gce.go b/runtime/factories/gce/gce.go
index eb892c3..aee5a41 100644
--- a/runtime/factories/gce/gce.go
+++ b/runtime/factories/gce/gce.go
@@ -23,24 +23,17 @@
 	"v.io/x/ref/runtime/internal"
 	"v.io/x/ref/runtime/internal/gce"
 	"v.io/x/ref/runtime/internal/lib/appcycle"
-	"v.io/x/ref/runtime/internal/lib/websocket"
 	"v.io/x/ref/runtime/internal/lib/xwebsocket"
 	grt "v.io/x/ref/runtime/internal/rt"
 	_ "v.io/x/ref/runtime/protocols/tcp"
 	_ "v.io/x/ref/runtime/protocols/ws"
 	_ "v.io/x/ref/runtime/protocols/wsh"
-
-	// TODO(suharshs): Remove these once we switch to the flow protocols.
-	_ "v.io/x/ref/runtime/internal/rpc/protocols/tcp"
-	_ "v.io/x/ref/runtime/internal/rpc/protocols/ws"
-	_ "v.io/x/ref/runtime/internal/rpc/protocols/wsh"
 )
 
 var commonFlags *flags.Flags
 
 func init() {
 	v23.RegisterRuntimeFactory(Init)
-	rpc.RegisterUnknownProtocol("wsh", websocket.HybridDial, websocket.HybridResolve, websocket.HybridListener)
 	flow.RegisterUnknownProtocol("wsh", xwebsocket.WSH{})
 	commonFlags = flags.CreateAndRegister(flag.CommandLine, flags.Runtime, flags.Listen)
 }
diff --git a/runtime/factories/generic/generic.go b/runtime/factories/generic/generic.go
index d6513dc..cf4b404 100644
--- a/runtime/factories/generic/generic.go
+++ b/runtime/factories/generic/generic.go
@@ -18,24 +18,17 @@
 	"v.io/x/ref/lib/flags"
 	"v.io/x/ref/runtime/internal"
 	"v.io/x/ref/runtime/internal/lib/appcycle"
-	"v.io/x/ref/runtime/internal/lib/websocket"
 	"v.io/x/ref/runtime/internal/lib/xwebsocket"
 	grt "v.io/x/ref/runtime/internal/rt"
 	_ "v.io/x/ref/runtime/protocols/tcp"
 	_ "v.io/x/ref/runtime/protocols/ws"
 	_ "v.io/x/ref/runtime/protocols/wsh"
-
-	// TODO(suharshs): Remove these once we switch to the flow protocols.
-	_ "v.io/x/ref/runtime/internal/rpc/protocols/tcp"
-	_ "v.io/x/ref/runtime/internal/rpc/protocols/ws"
-	_ "v.io/x/ref/runtime/internal/rpc/protocols/wsh"
 )
 
 var commonFlags *flags.Flags
 
 func init() {
 	v23.RegisterRuntimeFactory(Init)
-	rpc.RegisterUnknownProtocol("wsh", websocket.HybridDial, websocket.HybridResolve, websocket.HybridListener)
 	flow.RegisterUnknownProtocol("wsh", xwebsocket.WSH{})
 	flags.SetDefaultHostPort(":0")
 	commonFlags = flags.CreateAndRegister(flag.CommandLine, flags.Runtime, flags.Listen)
diff --git a/runtime/factories/roaming/roaming.go b/runtime/factories/roaming/roaming.go
index c6c0fe3..5d0ce6b 100644
--- a/runtime/factories/roaming/roaming.go
+++ b/runtime/factories/roaming/roaming.go
@@ -29,25 +29,18 @@
 	"v.io/x/ref/runtime/internal"
 	"v.io/x/ref/runtime/internal/lib/appcycle"
 	"v.io/x/ref/runtime/internal/lib/roaming"
-	"v.io/x/ref/runtime/internal/lib/websocket"
 	"v.io/x/ref/runtime/internal/lib/xwebsocket"
 	"v.io/x/ref/runtime/internal/rt"
 	_ "v.io/x/ref/runtime/protocols/tcp"
 	_ "v.io/x/ref/runtime/protocols/ws"
 	_ "v.io/x/ref/runtime/protocols/wsh"
 	"v.io/x/ref/services/debug/debuglib"
-
-	// TODO(suharshs): Remove these once we switch to the flow protocols.
-	_ "v.io/x/ref/runtime/internal/rpc/protocols/tcp"
-	_ "v.io/x/ref/runtime/internal/rpc/protocols/ws"
-	_ "v.io/x/ref/runtime/internal/rpc/protocols/wsh"
 )
 
 var commonFlags *flags.Flags
 
 func init() {
 	v23.RegisterRuntimeFactory(Init)
-	rpc.RegisterUnknownProtocol("wsh", websocket.HybridDial, websocket.HybridResolve, websocket.HybridListener)
 	flow.RegisterUnknownProtocol("wsh", xwebsocket.WSH{})
 	commonFlags = flags.CreateAndRegister(flag.CommandLine, flags.Runtime, flags.Listen)
 }
diff --git a/runtime/internal/flow/conn/auth.go b/runtime/internal/flow/conn/auth.go
index 910cbc9..4e8363e 100644
--- a/runtime/internal/flow/conn/auth.go
+++ b/runtime/internal/flow/conn/auth.go
@@ -32,7 +32,7 @@
 )
 
 func (c *Conn) dialHandshake(ctx *context.T, versions version.RPCVersionRange, auth flow.PeerAuthorizer) error {
-	binding, remoteEndpoint, err := c.setup(ctx, versions)
+	binding, remoteEndpoint, err := c.setup(ctx, versions, true)
 	if err != nil {
 		return err
 	}
@@ -83,7 +83,7 @@
 }
 
 func (c *Conn) acceptHandshake(ctx *context.T, versions version.RPCVersionRange, authorizedPeers []security.BlessingPattern) error {
-	binding, remoteEndpoint, err := c.setup(ctx, versions)
+	binding, remoteEndpoint, err := c.setup(ctx, versions, false)
 	if err != nil {
 		return err
 	}
@@ -110,7 +110,7 @@
 	return err
 }
 
-func (c *Conn) setup(ctx *context.T, versions version.RPCVersionRange) ([]byte, naming.Endpoint, error) {
+func (c *Conn) setup(ctx *context.T, versions version.RPCVersionRange, dialer bool) ([]byte, naming.Endpoint, error) {
 	pk, sk, err := box.GenerateKey(rand.Reader)
 	if err != nil {
 		return nil, nil, err
@@ -119,6 +119,8 @@
 		Versions:          versions,
 		PeerLocalEndpoint: c.local,
 		PeerNaClPublicKey: pk,
+		Mtu:               defaultMtu,
+		SharedTokens:      DefaultBytesBufferedPerFlow,
 	}
 	if c.remote != nil {
 		lSetup.PeerRemoteEndpoint = c.remote
@@ -152,10 +154,41 @@
 	if c.local == nil {
 		c.local = rSetup.PeerRemoteEndpoint
 	}
+	if rSetup.Mtu != 0 {
+		c.mtu = rSetup.Mtu
+	} else {
+		c.mtu = defaultMtu
+	}
+	c.lshared = lSetup.SharedTokens
+	if rSetup.SharedTokens != 0 && rSetup.SharedTokens < c.lshared {
+		c.lshared = rSetup.SharedTokens
+	}
 	if rSetup.PeerNaClPublicKey == nil {
 		return nil, nil, NewErrMissingSetupOption(ctx, "peerNaClPublicKey")
 	}
 	binding := c.mp.setupEncryption(ctx, pk, sk, rSetup.PeerNaClPublicKey)
+	if c.version >= version.RPCVersion14 {
+		// We include the setup messages in the channel binding to prevent attacks
+		// where a man in the middle changes fields in the Setup message (e.g. a
+		// downgrade attack wherein a MITM attacker changes the Version field of
+		// the Setup message to a lower-security version.)
+		// We always put the dialer first in the binding.
+		if dialer {
+			if binding, err = message.Append(ctx, lSetup, nil); err != nil {
+				return nil, nil, err
+			}
+			if binding, err = message.Append(ctx, rSetup, binding); err != nil {
+				return nil, nil, err
+			}
+		} else {
+			if binding, err = message.Append(ctx, rSetup, nil); err != nil {
+				return nil, nil, err
+			}
+			if binding, err = message.Append(ctx, lSetup, binding); err != nil {
+				return nil, nil, err
+			}
+		}
+	}
 	// if we're encapsulated in another flow, tell that flow to stop
 	// encrypting now that we've started.
 	if f, ok := c.mp.rw.(*flw); ok {
@@ -169,7 +202,10 @@
 	if dialer {
 		tag = authAcceptorTag
 	}
-	var rauth *message.Auth
+	var (
+		rauth *message.Auth
+		err   error
+	)
 	for {
 		msg, err := c.mp.readMsg(ctx)
 		if err != nil {
@@ -188,7 +224,6 @@
 	var rBlessings security.Blessings
 	var rDischarges map[string]security.Discharge
 	if rauth.BlessingsKey != 0 {
-		var err error
 		// TODO(mattr): Make sure we cancel out of this at some point.
 		rBlessings, rDischarges, err = c.blessingsFlow.getRemote(ctx, rauth.BlessingsKey, rauth.DischargeKey)
 		if err != nil {
diff --git a/runtime/internal/flow/conn/conn.go b/runtime/internal/flow/conn/conn.go
index dd9f70a..55a8197 100644
--- a/runtime/internal/flow/conn/conn.go
+++ b/runtime/internal/flow/conn/conn.go
@@ -38,7 +38,7 @@
 )
 
 const (
-	mtu                         = 1 << 16
+	defaultMtu                  = 1 << 16
 	defaultChannelTimeout       = 30 * time.Minute
 	DefaultBytesBufferedPerFlow = 1 << 20
 	proxyOverhead               = 32
@@ -88,6 +88,7 @@
 	unopenedFlows sync.WaitGroup
 	cancel        context.CancelFunc
 	handler       FlowHandler
+	mtu           uint64
 
 	mu sync.Mutex // All the variables below here are protected by mu.
 
@@ -158,22 +159,19 @@
 		channelTimeout = defaultChannelTimeout
 	}
 	c := &Conn{
-		mp:           newMessagePipe(conn),
-		handler:      handler,
-		lBlessings:   lBlessings,
-		local:        endpointCopy(local),
-		remote:       endpointCopy(remote),
-		closed:       make(chan struct{}),
-		lameDucked:   make(chan struct{}),
-		nextFid:      reservedFlows,
-		flows:        map[uint64]*flw{},
-		lastUsedTime: time.Now(),
-		toRelease:    map[uint64]uint64{},
-		borrowing:    map[uint64]bool{},
-		cancel:       cancel,
-		// TODO(mattr): We should negotiate the shared counter pool size with the
-		// other end.
-		lshared:              DefaultBytesBufferedPerFlow,
+		mp:                   newMessagePipe(conn),
+		handler:              handler,
+		lBlessings:           lBlessings,
+		local:                endpointCopy(local),
+		remote:               endpointCopy(remote),
+		closed:               make(chan struct{}),
+		lameDucked:           make(chan struct{}),
+		nextFid:              reservedFlows,
+		flows:                map[uint64]*flw{},
+		lastUsedTime:         time.Now(),
+		toRelease:            map[uint64]uint64{},
+		borrowing:            map[uint64]bool{},
+		cancel:               cancel,
 		outstandingBorrowed:  make(map[uint64]uint64),
 		activeWriters:        make([]writer, numPriorities),
 		acceptChannelTimeout: channelTimeout,
@@ -250,7 +248,6 @@
 		toRelease:            map[uint64]uint64{},
 		borrowing:            map[uint64]bool{},
 		cancel:               cancel,
-		lshared:              DefaultBytesBufferedPerFlow,
 		outstandingBorrowed:  make(map[uint64]uint64),
 		activeWriters:        make([]writer, numPriorities),
 		acceptChannelTimeout: channelTimeout,
@@ -406,6 +403,12 @@
 	return blessings
 }
 
+func (c *Conn) RemoteDischarges() map[string]security.Discharge {
+	// Its safe to ignore this error. It means that this conn is closed.
+	_, discharges, _ := c.blessingsFlow.getLatestRemote(nil, c.rBKey)
+	return discharges
+}
+
 // 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/conn/flow.go b/runtime/internal/flow/conn/flow.go
index 96bfb8e..60236a6 100644
--- a/runtime/internal/flow/conn/flow.go
+++ b/runtime/internal/flow/conn/flow.go
@@ -142,7 +142,7 @@
 // the number of shared counters for the conn if we are sending on a just
 // dialed flow.
 func (f *flw) tokensLocked() (int, func(int)) {
-	max := uint64(mtu)
+	max := f.conn.mtu
 	// When	our flow is proxied (i.e. encapsulated), the proxy has added overhead
 	// when forwarding the message. This means we must reduce our mtu to ensure
 	// that dialer framing reaches the acceptor without being truncated by the
diff --git a/runtime/internal/flow/conn/flowcontrol_test.go b/runtime/internal/flow/conn/flowcontrol_test.go
index 81af175..5529940 100644
--- a/runtime/internal/flow/conn/flowcontrol_test.go
+++ b/runtime/internal/flow/conn/flowcontrol_test.go
@@ -116,18 +116,18 @@
 	defer wg.Wait()
 	for _, f := range flows {
 		go func(fl flow.Flow) {
-			if _, err := fl.WriteMsg(randData[:mtu*nmessages]); err != nil {
+			if _, err := fl.WriteMsg(randData[:defaultMtu*nmessages]); err != nil {
 				panic(err)
 			}
 			wg.Done()
 		}(f)
 		go func() {
 			fl := <-accept
-			buf := make([]byte, mtu*nmessages)
+			buf := make([]byte, defaultMtu*nmessages)
 			if _, err := io.ReadFull(fl, buf); err != nil {
 				panic(err)
 			}
-			if !bytes.Equal(buf, randData[:mtu*nmessages]) {
+			if !bytes.Equal(buf, randData[:defaultMtu*nmessages]) {
 				t.Fatal("unequal data")
 			}
 			wg.Done()
diff --git a/runtime/internal/flow/conn/message.go b/runtime/internal/flow/conn/message.go
index 529079f..8e50e12 100644
--- a/runtime/internal/flow/conn/message.go
+++ b/runtime/internal/flow/conn/message.go
@@ -30,7 +30,7 @@
 func newMessagePipe(rw flow.MsgReadWriteCloser) *messagePipe {
 	return &messagePipe{
 		rw:       rw,
-		writeBuf: make([]byte, mtu),
+		writeBuf: make([]byte, defaultMtu),
 		cipher:   &crypto.NullControlCipher{},
 	}
 }
diff --git a/runtime/internal/flow/flowtest/flowtest.go b/runtime/internal/flow/flowtest/flowtest.go
index 5c3a996..5685a03 100644
--- a/runtime/internal/flow/flowtest/flowtest.go
+++ b/runtime/internal/flow/flowtest/flowtest.go
@@ -5,6 +5,7 @@
 package flowtest
 
 import (
+	"fmt"
 	"testing"
 	"time"
 
@@ -53,3 +54,42 @@
 	security.Blessings, map[string]security.Discharge, error) {
 	return v23.GetPrincipal(ctx).BlessingStore().Default(), nil, nil
 }
+
+type peersAuthorizer []string
+
+func NewPeerAuthorizer(names []string) flow.PeerAuthorizer {
+	if len(names) == 0 {
+		return AllowAllPeersAuthorizer{}
+	}
+	return peersAuthorizer(names)
+}
+
+func (p peersAuthorizer) AuthorizePeer(
+	ctx *context.T,
+	localEndpoint, remoteEndpoint naming.Endpoint,
+	remoteBlessings security.Blessings,
+	remoteDischarges map[string]security.Discharge,
+) ([]string, []security.RejectedBlessing, error) {
+	call := security.NewCall(&security.CallParams{
+		Timestamp:        time.Now(),
+		LocalPrincipal:   v23.GetPrincipal(ctx),
+		LocalEndpoint:    localEndpoint,
+		RemoteBlessings:  remoteBlessings,
+		RemoteDischarges: remoteDischarges,
+		RemoteEndpoint:   remoteEndpoint,
+	})
+	peerNames, rejectedPeerNames := security.RemoteBlessingNames(ctx, call)
+	ctx.Infof("validating against %v", peerNames)
+	for _, pattern := range p {
+		if security.BlessingPattern(pattern).MatchedBy(peerNames...) {
+			return peerNames, rejectedPeerNames, nil
+		}
+		ctx.Infof("no match %v", pattern)
+	}
+	return peerNames, rejectedPeerNames, fmt.Errorf("not authorized")
+}
+
+func (peersAuthorizer) BlessingsForPeer(ctx *context.T, _ []string) (
+	security.Blessings, map[string]security.Discharge, error) {
+	return v23.GetPrincipal(ctx).BlessingStore().Default(), nil, nil
+}
diff --git a/runtime/internal/flow/manager/conncache.go b/runtime/internal/flow/manager/conncache.go
index 78a5365..0e4467d 100644
--- a/runtime/internal/flow/manager/conncache.go
+++ b/runtime/internal/flow/manager/conncache.go
@@ -5,10 +5,13 @@
 package manager
 
 import (
+	"bytes"
+	"fmt"
 	"sort"
 	"sync"
 
 	"v.io/v23/context"
+	"v.io/v23/flow"
 	"v.io/v23/naming"
 	"v.io/x/ref/runtime/internal/flow/conn"
 )
@@ -26,11 +29,10 @@
 }
 
 type connEntry struct {
-	conn          *conn.Conn
-	rid           naming.RoutingID
-	addrKey       string
-	blessingNames []string
-	proxy         bool
+	conn    *conn.Conn
+	rid     naming.RoutingID
+	addrKey string
+	proxy   bool
 }
 
 func NewConnCache() *ConnCache {
@@ -46,6 +48,19 @@
 	}
 }
 
+func (c *ConnCache) String() string {
+	buf := &bytes.Buffer{}
+	fmt.Fprintln(buf, "AddressCache:")
+	for k, v := range c.addrCache {
+		fmt.Fprintf(buf, "%v: %p\n", k, v.conn)
+	}
+	fmt.Fprintln(buf, "RIDCache:")
+	for k, v := range c.ridCache {
+		fmt.Fprintf(buf, "%v: %p\n", k, v.conn)
+	}
+	return buf.String()
+}
+
 // Insert adds conn to the cache, keyed by both (protocol, address) and (routingID)
 // An error will be returned iff the cache has been closed.
 func (c *ConnCache) Insert(conn *conn.Conn, protocol, address string, proxy bool) error {
@@ -62,9 +77,6 @@
 		addrKey: k,
 		proxy:   proxy,
 	}
-	if !entry.proxy {
-		entry.blessingNames = ep.BlessingNames()
-	}
 	if old := c.ridCache[entry.rid]; old != nil {
 		c.unmappedConns[old] = true
 	}
@@ -86,9 +98,6 @@
 		rid:   ep.RoutingID(),
 		proxy: proxy,
 	}
-	if !entry.proxy {
-		entry.blessingNames = ep.BlessingNames()
-	}
 	if old := c.ridCache[entry.rid]; old != nil {
 		c.unmappedConns[old] = true
 	}
@@ -96,19 +105,19 @@
 	return nil
 }
 
-func (c *ConnCache) Find(protocol, address string, rid naming.RoutingID, blessingNames []string) (*conn.Conn, error) {
+func (c *ConnCache) Find(ctx *context.T, protocol, address string, rid naming.RoutingID, auth flow.PeerAuthorizer) (*conn.Conn, error) {
 	defer c.mu.Unlock()
 	c.mu.Lock()
 	if c.addrCache == nil {
 		return nil, NewErrCacheClosed(nil)
 	}
 	if rid != naming.NullRoutingID {
-		if entry := c.removeUndialable(c.ridCache[rid], blessingNames); entry != nil {
+		if entry := c.removeUndialable(ctx, c.ridCache[rid], auth); entry != nil {
 			return entry, nil
 		}
 	}
 	k := key(protocol, address)
-	return c.removeUndialable(c.addrCache[k], blessingNames), nil
+	return c.removeUndialable(ctx, c.addrCache[k], auth), nil
 }
 
 // ReservedFind returns a Conn based on the input remoteEndpoint.
@@ -120,14 +129,14 @@
 // the arguments provided to ReservedFind.
 // All new ReservedFind calls for the (protocol, address) will Block
 // until the corresponding Unreserve call is made.
-func (c *ConnCache) ReservedFind(protocol, address string, rid naming.RoutingID, blessingNames []string) (*conn.Conn, error) {
+func (c *ConnCache) ReservedFind(ctx *context.T, protocol, address string, rid naming.RoutingID, auth flow.PeerAuthorizer) (*conn.Conn, error) {
 	defer c.mu.Unlock()
 	c.mu.Lock()
 	if c.addrCache == nil {
 		return nil, NewErrCacheClosed(nil)
 	}
 	if rid != naming.NullRoutingID {
-		if entry := c.removeUndialable(c.ridCache[rid], blessingNames); entry != nil {
+		if entry := c.removeUndialable(ctx, c.ridCache[rid], auth); entry != nil {
 			return entry, nil
 		}
 	}
@@ -139,7 +148,7 @@
 		}
 	}
 	c.started[k] = true
-	return c.removeUndialable(c.addrCache[k], blessingNames), nil
+	return c.removeUndialable(ctx, c.addrCache[k], auth), nil
 }
 
 // Unreserve marks the status of the (protocol, address) as no longer started, and
@@ -166,8 +175,8 @@
 }
 
 // removeUndialable filters connections that are closed, lameducked, or non-proxied
-// connections whose blessings dont match blessingNames.
-func (c *ConnCache) removeUndialable(e *connEntry, blessingNames []string) *conn.Conn {
+// connections that do not authorize.
+func (c *ConnCache) removeUndialable(ctx *context.T, e *connEntry, auth flow.PeerAuthorizer) *conn.Conn {
 	if e == nil {
 		return nil
 	}
@@ -179,8 +188,15 @@
 		}
 		return nil
 	}
-	if !e.proxy && len(blessingNames) > 0 && !matchBlessings(e.blessingNames, blessingNames) {
-		return nil
+	if !e.proxy && auth != nil {
+		_, _, err := auth.AuthorizePeer(ctx,
+			e.conn.LocalEndpoint(),
+			e.conn.RemoteEndpoint(),
+			e.conn.RemoteBlessings(),
+			e.conn.RemoteDischarges())
+		if err != nil {
+			return nil
+		}
 	}
 	return e.conn
 }
@@ -200,7 +216,7 @@
 	err := NewErrConnKilledToFreeResources(ctx)
 	pq := make(connEntries, 0, len(c.ridCache))
 	for _, e := range c.ridCache {
-		if c.removeUndialable(e, nil) == nil {
+		if c.removeUndialable(ctx, e, nil) == nil {
 			continue
 		}
 		if e.conn.IsEncapsulated() {
diff --git a/runtime/internal/flow/manager/conncache_test.go b/runtime/internal/flow/manager/conncache_test.go
index bf1a739..260a4a4 100644
--- a/runtime/internal/flow/manager/conncache_test.go
+++ b/runtime/internal/flow/manager/conncache_test.go
@@ -14,16 +14,18 @@
 	"v.io/v23/flow"
 	"v.io/v23/naming"
 	"v.io/v23/rpc/version"
+	"v.io/v23/security"
 	connpackage "v.io/x/ref/runtime/internal/flow/conn"
 	"v.io/x/ref/runtime/internal/flow/flowtest"
 	inaming "v.io/x/ref/runtime/internal/naming"
 	_ "v.io/x/ref/runtime/protocols/local"
+	"v.io/x/ref/test"
 	"v.io/x/ref/test/goroutines"
 )
 
 func TestCache(t *testing.T) {
 	defer goroutines.NoLeaks(t, leakWaitTime)()
-	ctx, shutdown := v23.Init()
+	ctx, shutdown := test.V23Init()
 	defer shutdown()
 
 	c := NewConnCache()
@@ -31,8 +33,10 @@
 		Protocol:  "tcp",
 		Address:   "127.0.0.1:1111",
 		RID:       naming.FixedRoutingID(0x5555),
-		Blessings: []string{"A", "B", "C"},
+		Blessings: unionBlessing(ctx, "A", "B", "C"),
 	}
+
+	auth := flowtest.NewPeerAuthorizer(remote.Blessings)
 	caf := makeConnAndFlow(t, ctx, remote)
 	defer caf.stop(ctx)
 	conn := caf.c
@@ -40,32 +44,32 @@
 		t.Fatal(err)
 	}
 	// We should be able to find the conn in the cache.
-	if got, err := c.ReservedFind(remote.Protocol, remote.Address, naming.NullRoutingID, remote.Blessings); err != nil || got != conn {
+	if got, err := c.ReservedFind(ctx, remote.Protocol, remote.Address, naming.NullRoutingID, auth); err != nil || got != conn {
 		t.Errorf("got %v, want %v, err: %v", got, conn, err)
 	}
 	c.Unreserve(remote.Protocol, remote.Address)
 	// Changing the protocol should fail.
-	if got, err := c.ReservedFind("wrong", remote.Address, naming.NullRoutingID, remote.Blessings); err != nil || got != nil {
+	if got, err := c.ReservedFind(ctx, "wrong", remote.Address, naming.NullRoutingID, auth); err != nil || got != nil {
 		t.Errorf("got %v, want <nil>, err: %v", got, err)
 	}
 	c.Unreserve("wrong", remote.Address)
 	// Changing the address should fail.
-	if got, err := c.ReservedFind(remote.Protocol, "wrong", naming.NullRoutingID, remote.Blessings); err != nil || got != nil {
+	if got, err := c.ReservedFind(ctx, remote.Protocol, "wrong", naming.NullRoutingID, auth); err != nil || got != nil {
 		t.Errorf("got %v, want <nil>, err: %v", got, err)
 	}
 	c.Unreserve(remote.Protocol, "wrong")
 	// Changing the blessingNames should fail.
-	if got, err := c.ReservedFind(remote.Protocol, remote.Address, naming.NullRoutingID, []string{"wrong"}); err != nil || got != nil {
+	if got, err := c.ReservedFind(ctx, remote.Protocol, remote.Address, naming.NullRoutingID, flowtest.NewPeerAuthorizer([]string{"wrong"})); err != nil || got != nil {
 		t.Errorf("got %v, want <nil>, err: %v", got, err)
 	}
 	c.Unreserve(remote.Protocol, remote.Address)
-	// But finding a set of blessings that has at least on blessings in remote.Blessings should succeed.
-	if got, err := c.ReservedFind(remote.Protocol, remote.Address, naming.NullRoutingID, []string{"foo", "A"}); err != nil || got != conn {
+	// But finding a set of blessings that has at least one blessings in remote.Blessings should succeed.
+	if got, err := c.ReservedFind(ctx, remote.Protocol, remote.Address, naming.NullRoutingID, flowtest.NewPeerAuthorizer([]string{"foo", remote.Blessings[0]})); err != nil || got != conn {
 		t.Errorf("got %v, want %v, err: %v", got, conn, err)
 	}
 	c.Unreserve(remote.Protocol, remote.Address)
 	// Finding by routing ID should work.
-	if got, err := c.ReservedFind("wrong", "wrong", remote.RID, remote.Blessings); err != nil || got != conn {
+	if got, err := c.ReservedFind(ctx, "wrong", "wrong", remote.RID, auth); err != nil || got != conn {
 		t.Errorf("got %v, want %v, err: %v", got, conn, err)
 	}
 	c.Unreserve("wrong", "wrong")
@@ -76,7 +80,7 @@
 		Protocol:  "tcp",
 		Address:   "127.0.0.1:2222",
 		RID:       naming.FixedRoutingID(0x5555),
-		Blessings: []string{"A", "B", "C"},
+		Blessings: unionBlessing(ctx, "A", "B", "C"),
 	}
 	caf = makeConnAndFlow(t, ctx, proxyep)
 	defer caf.stop(ctx)
@@ -85,7 +89,7 @@
 		t.Fatal(err)
 	}
 	// Wrong blessingNames should still work
-	if got, err := c.ReservedFind(proxyep.Protocol, proxyep.Address, naming.NullRoutingID, []string{"wrong"}); err != nil || got != proxyConn {
+	if got, err := c.ReservedFind(ctx, proxyep.Protocol, proxyep.Address, naming.NullRoutingID, flowtest.NewPeerAuthorizer([]string{"wrong"})); err != nil || got != proxyConn {
 		t.Errorf("got %v, want %v, err: %v", got, proxyConn, err)
 	}
 	c.Unreserve(proxyep.Protocol, proxyep.Address)
@@ -95,20 +99,21 @@
 		Protocol:  "ridonly",
 		Address:   "ridonly",
 		RID:       naming.FixedRoutingID(0x1111),
-		Blessings: []string{"ridonly"},
+		Blessings: unionBlessing(ctx, "ridonly"),
 	}
+	ridauth := flowtest.NewPeerAuthorizer(ridEP.Blessings)
 	caf = makeConnAndFlow(t, ctx, ridEP)
 	defer caf.stop(ctx)
 	ridConn := caf.c
 	if err := c.InsertWithRoutingID(ridConn, false); err != nil {
 		t.Fatal(err)
 	}
-	if got, err := c.ReservedFind(ridEP.Protocol, ridEP.Address, naming.NullRoutingID, ridEP.Blessings); err != nil || got != nil {
+	if got, err := c.ReservedFind(ctx, ridEP.Protocol, ridEP.Address, naming.NullRoutingID, ridauth); err != nil || got != nil {
 		t.Errorf("got %v, want <nil>, err: %v", got, err)
 	}
 	c.Unreserve(ridEP.Protocol, ridEP.Address)
 	// Finding by routing ID should work.
-	if got, err := c.ReservedFind("wrong", "wrong", ridEP.RID, ridEP.Blessings); err != nil || got != ridConn {
+	if got, err := c.ReservedFind(ctx, "wrong", "wrong", ridEP.RID, ridauth); err != nil || got != ridConn {
 		t.Errorf("got %v, want %v, err: %v", got, ridConn, err)
 	}
 	c.Unreserve("wrong", "wrong")
@@ -117,20 +122,21 @@
 		Protocol:  "other",
 		Address:   "other",
 		RID:       naming.FixedRoutingID(0x2222),
-		Blessings: []string{"other"},
+		Blessings: unionBlessing(ctx, "other"),
 	}
+	otherAuth := flowtest.NewPeerAuthorizer(otherEP.Blessings)
 	caf = makeConnAndFlow(t, ctx, otherEP)
 	defer caf.stop(ctx)
 	otherConn := caf.c
 
 	// Looking up a not yet inserted endpoint should fail.
-	if got, err := c.ReservedFind(otherEP.Protocol, otherEP.Address, naming.NullRoutingID, otherEP.Blessings); err != nil || got != nil {
+	if got, err := c.ReservedFind(ctx, otherEP.Protocol, otherEP.Address, naming.NullRoutingID, otherAuth); err != nil || got != nil {
 		t.Errorf("got %v, want <nil>, err: %v", got, err)
 	}
 	// Looking it up again should block until a matching Unreserve call is made.
 	ch := make(chan *connpackage.Conn, 1)
 	go func(ch chan *connpackage.Conn) {
-		conn, err := c.ReservedFind(otherEP.Protocol, otherEP.Address, naming.NullRoutingID, otherEP.Blessings)
+		conn, err := c.ReservedFind(ctx, otherEP.Protocol, otherEP.Address, naming.NullRoutingID, otherAuth)
 		if err != nil {
 			t.Fatal(err)
 		}
@@ -177,7 +183,7 @@
 
 func TestLRU(t *testing.T) {
 	defer goroutines.NoLeaks(t, leakWaitTime)()
-	ctx, shutdown := v23.Init()
+	ctx, shutdown := test.V23Init()
 	defer shutdown()
 
 	// Ensure that the least recently created conns are killed by KillConnections.
@@ -202,13 +208,14 @@
 		if status := conn.c.Status(); status == connpackage.Closed {
 			t.Errorf("conn %v should not have been closed", conn)
 		}
-		if !isInCache(t, c, conn.c) {
-			t.Errorf("conn %v should still be in cache", conn)
+		if !isInCache(t, ctx, c, conn.c) {
+			t.Errorf("conn %v(%p) should still be in cache:\n%s",
+				conn.c.RemoteEndpoint(), conn.c, c)
 		}
 	}
 	for _, conn := range conns[:3] {
 		<-conn.c.Closed()
-		if isInCache(t, c, conn.c) {
+		if isInCache(t, ctx, c, conn.c) {
 			t.Errorf("conn %v should not be in cache", conn)
 		}
 	}
@@ -238,13 +245,13 @@
 		if status := conn.c.Status(); status == connpackage.Closed {
 			t.Errorf("conn %v should not have been closed", conn)
 		}
-		if !isInCache(t, c, conn.c) {
+		if !isInCache(t, ctx, c, conn.c) {
 			t.Errorf("conn %v should still be in cache", conn)
 		}
 	}
 	for _, conn := range conns[7:] {
 		<-conn.c.Closed()
-		if isInCache(t, c, conn.c) {
+		if isInCache(t, ctx, c, conn.c) {
 			t.Errorf("conn %v should not be in cache", conn)
 		}
 	}
@@ -274,21 +281,22 @@
 		if status := conn.c.Status(); status == connpackage.Closed {
 			t.Errorf("conn %v should not have been closed", conn)
 		}
-		if !isInCache(t, c, conn.c) {
-			t.Errorf("conn %v should still be in cache", conn)
+		if !isInCache(t, ctx, c, conn.c) {
+			t.Errorf("conn %v(%p) should still be in cache:\n%s",
+				conn.c.RemoteEndpoint(), conn.c, c)
 		}
 	}
 	for _, conn := range conns[7:] {
 		<-conn.c.Closed()
-		if isInCache(t, c, conn.c) {
+		if isInCache(t, ctx, c, conn.c) {
 			t.Errorf("conn %v should not be in cache", conn)
 		}
 	}
 }
 
-func isInCache(t *testing.T, c *ConnCache, conn *connpackage.Conn) bool {
+func isInCache(t *testing.T, ctx *context.T, c *ConnCache, conn *connpackage.Conn) bool {
 	rep := conn.RemoteEndpoint()
-	rfconn, err := c.ReservedFind(rep.Addr().Network(), rep.Addr().String(), rep.RoutingID(), rep.BlessingNames())
+	rfconn, err := c.ReservedFind(ctx, rep.Addr().Network(), rep.Addr().String(), rep.RoutingID(), flowtest.NewPeerAuthorizer(rep.BlessingNames()))
 	if err != nil {
 		t.Error(err)
 	}
@@ -392,3 +400,25 @@
 	}()
 	return nil
 }
+
+func unionBlessing(ctx *context.T, names ...string) []string {
+	principal := v23.GetPrincipal(ctx)
+	blessings := make([]security.Blessings, len(names))
+	for i, name := range names {
+		var err error
+		if blessings[i], err = principal.BlessSelf(name); err != nil {
+			panic(err)
+		}
+	}
+	union, err := security.UnionOfBlessings(blessings...)
+	if err != nil {
+		panic(err)
+	}
+	if err := security.AddToRoots(principal, union); err != nil {
+		panic(err)
+	}
+	if err := principal.BlessingStore().SetDefault(union); err != nil {
+		panic(err)
+	}
+	return security.BlessingNames(principal, principal.BlessingStore().Default())
+}
diff --git a/runtime/internal/flow/manager/manager.go b/runtime/internal/flow/manager/manager.go
index 8ac1742..e35abc8 100644
--- a/runtime/internal/flow/manager/manager.go
+++ b/runtime/internal/flow/manager/manager.go
@@ -657,8 +657,8 @@
 
 func (m *manager) internalDial(ctx *context.T, remote naming.Endpoint, auth flow.PeerAuthorizer, channelTimeout time.Duration, proxy bool) (flow.Flow, error) {
 	// Fast path, look for the conn based on unresolved network, address, and routingId first.
-	addr, rid, blessingNames := remote.Addr(), remote.RoutingID(), remote.BlessingNames()
-	c, err := m.cache.Find(addr.Network(), addr.String(), rid, blessingNames)
+	addr, rid := remote.Addr(), remote.RoutingID()
+	c, err := m.cache.Find(ctx, addr.Network(), addr.String(), rid, auth)
 	if err != nil {
 		return nil, iflow.MaybeWrapError(flow.ErrBadState, ctx, err)
 	}
@@ -680,7 +680,7 @@
 		if err != nil {
 			return nil, iflow.MaybeWrapError(flow.ErrResolveFailed, ctx, err)
 		}
-		c, err = m.cache.ReservedFind(network, address, rid, blessingNames)
+		c, err = m.cache.ReservedFind(ctx, network, address, rid, auth)
 		if err != nil {
 			return nil, iflow.MaybeWrapError(flow.ErrBadState, ctx, err)
 		}
diff --git a/runtime/internal/lib/tcputil/tcputil.go b/runtime/internal/lib/tcputil/tcputil.go
index c6891f1..c0c8f78 100644
--- a/runtime/internal/lib/tcputil/tcputil.go
+++ b/runtime/internal/lib/tcputil/tcputil.go
@@ -13,7 +13,7 @@
 	"v.io/v23/context"
 	"v.io/v23/flow"
 
-	"v.io/x/ref/runtime/internal/lib/framer"
+	"v.io/x/ref/runtime/protocols/lib/framer"
 )
 
 const keepAlivePeriod = 30 * time.Second
diff --git a/runtime/internal/lib/websocket/conn.go b/runtime/internal/lib/websocket/conn.go
deleted file mode 100644
index 77c7e99..0000000
--- a/runtime/internal/lib/websocket/conn.go
+++ /dev/null
@@ -1,171 +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.
-
-// +build !nacl
-
-package websocket
-
-import (
-	"fmt"
-	"io"
-	"net"
-	"sync"
-	"time"
-
-	"github.com/gorilla/websocket"
-)
-
-// WebsocketConn provides a net.Conn interface for a websocket connection.
-func WebsocketConn(ws *websocket.Conn) net.Conn {
-	return &wrappedConn{ws: ws}
-}
-
-// wrappedConn provides a net.Conn interface to a websocket.
-// The underlying websocket connection needs regular calls to Read to make sure
-// websocket control messages (such as pings) are processed by the websocket
-// library.
-type wrappedConn struct {
-	ws         *websocket.Conn
-	currReader io.Reader
-
-	// The gorilla docs aren't explicit about reading and writing from
-	// different goroutines.  It is explicit that only one goroutine can
-	// do a write at any given time and only one goroutine can do a read
-	// at any given time.  Based on inspection it seems that using a reader
-	// and writer simultaneously is safe, but this might change with
-	// future changes.  We can't actually share the lock, because this means
-	// that we can't write while we are waiting for a message, causing some
-	// deadlocks where a write is need to unblock a read.
-	writeLock sync.Mutex
-	readLock  sync.Mutex
-}
-
-func (c *wrappedConn) readFromCurrReader(b []byte) (int, error) {
-	n, err := c.currReader.Read(b)
-	if err == io.EOF {
-		err = nil
-		c.currReader = nil
-	}
-	return n, err
-}
-
-func (c *wrappedConn) Read(b []byte) (int, error) {
-	c.readLock.Lock()
-	defer c.readLock.Unlock()
-	var n int
-	var err error
-
-	// TODO(bjornick): It would be nice to be able to read multiple messages at
-	// a time in case the first message is not big enough to fill b and another
-	// message is ready.
-	// Loop until we either get data or an error.  This exists
-	// mostly to avoid return 0, nil.
-	for n == 0 && err == nil {
-		if c.currReader == nil {
-			t, r, err := c.ws.NextReader()
-			if err != nil {
-				return 0, err
-			}
-			if t != websocket.BinaryMessage {
-				return 0, fmt.Errorf("Unexpected message type %d", t)
-			}
-			c.currReader = r
-		}
-		n, err = c.readFromCurrReader(b)
-	}
-	return n, err
-}
-
-func (c *wrappedConn) Write(b []byte) (int, error) {
-	c.writeLock.Lock()
-	defer c.writeLock.Unlock()
-	if err := c.ws.WriteMessage(websocket.BinaryMessage, b); err != nil {
-		return 0, err
-	}
-	return len(b), nil
-}
-
-func (c *wrappedConn) Close() error {
-	c.writeLock.Lock()
-	defer c.writeLock.Unlock()
-	// Send an EOF control message to the remote end so that it can
-	// handle the close gracefully.
-	msg := websocket.FormatCloseMessage(websocket.CloseGoingAway, "EOF")
-	c.ws.WriteControl(websocket.CloseMessage, msg, time.Now().Add(time.Second))
-	return c.ws.Close()
-}
-
-func (c *wrappedConn) LocalAddr() net.Addr {
-	return &addr{"ws", c.ws.LocalAddr().String()}
-}
-
-func (c *wrappedConn) RemoteAddr() net.Addr {
-	return &addr{"ws", c.ws.RemoteAddr().String()}
-}
-
-func (c *wrappedConn) SetDeadline(t time.Time) error {
-	if err := c.SetReadDeadline(t); err != nil {
-		return err
-	}
-	return c.SetWriteDeadline(t)
-}
-
-func (c *wrappedConn) SetReadDeadline(t time.Time) error {
-	return c.ws.SetReadDeadline(t)
-}
-
-func (c *wrappedConn) SetWriteDeadline(t time.Time) error {
-	return c.ws.SetWriteDeadline(t)
-}
-
-// hybridConn is used by the 'hybrid' protocol that can accept
-// either 'tcp' or 'websocket' connections. In particular, it allows
-// for the reader to peek and buffer the first n bytes of a stream
-// in order to determine what the connection type is.
-type hybridConn struct {
-	conn     net.Conn
-	buffered []byte
-}
-
-func (wc *hybridConn) Read(b []byte) (int, error) {
-	lbuf := len(wc.buffered)
-	if lbuf == 0 {
-		return wc.conn.Read(b)
-	}
-	copyn := copy(b, wc.buffered)
-	wc.buffered = wc.buffered[copyn:]
-	if len(b) > copyn {
-		n, err := wc.conn.Read(b[copyn:])
-		return copyn + n, err
-	}
-	return copyn, nil
-}
-
-func (wc *hybridConn) Write(b []byte) (n int, err error) {
-	return wc.conn.Write(b)
-}
-
-func (wc *hybridConn) Close() error {
-	return wc.conn.Close()
-}
-
-func (wc *hybridConn) LocalAddr() net.Addr {
-	return &addr{"wsh", wc.conn.LocalAddr().String()}
-}
-
-func (wc *hybridConn) RemoteAddr() net.Addr {
-	return &addr{"wsh", wc.conn.RemoteAddr().String()}
-}
-
-func (wc *hybridConn) SetDeadline(t time.Time) error {
-	return wc.conn.SetDeadline(t)
-}
-
-func (wc *hybridConn) SetReadDeadline(t time.Time) error {
-	return wc.conn.SetReadDeadline(t)
-}
-
-func (wc *hybridConn) SetWriteDeadline(t time.Time) error {
-	return wc.conn.SetWriteDeadline(t)
-}
diff --git a/runtime/internal/lib/websocket/conn_nacl.go b/runtime/internal/lib/websocket/conn_nacl.go
deleted file mode 100644
index ca7e16d..0000000
--- a/runtime/internal/lib/websocket/conn_nacl.go
+++ /dev/null
@@ -1,115 +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.
-
-// +build nacl
-
-package websocket
-
-import (
-	"net"
-	"net/url"
-	"runtime/ppapi"
-	"sync"
-	"time"
-
-	"v.io/v23/context"
-)
-
-// Ppapi instance which must be set before the Dial is called.
-var PpapiInstance ppapi.Instance
-
-func WebsocketConn(address string, ws *ppapi.WebsocketConn) net.Conn {
-	return &wrappedConn{
-		address: address,
-		ws:      ws,
-	}
-}
-
-type wrappedConn struct {
-	address    string
-	ws         *ppapi.WebsocketConn
-	readLock   sync.Mutex
-	writeLock  sync.Mutex
-	currBuffer []byte
-}
-
-func Dial(ctx *context.T, protocol, address string, timeout time.Duration) (net.Conn, error) {
-	inst := PpapiInstance
-	u, err := url.Parse("ws://" + address)
-	if err != nil {
-		return nil, err
-	}
-
-	ws, err := inst.DialWebsocket(u.String())
-	if err != nil {
-		return nil, err
-	}
-	return WebsocketConn(address, ws), nil
-}
-
-func Resolve(ctx *context.T, protocol, address string) (string, string, error) {
-	return "ws", address, nil
-}
-
-func (c *wrappedConn) Read(b []byte) (int, error) {
-	c.readLock.Lock()
-	defer c.readLock.Unlock()
-
-	var err error
-	if len(c.currBuffer) == 0 {
-		c.currBuffer, err = c.ws.ReceiveMessage()
-		if err != nil {
-			return 0, err
-		}
-	}
-
-	n := copy(b, c.currBuffer)
-	c.currBuffer = c.currBuffer[n:]
-	return n, nil
-}
-
-func (c *wrappedConn) Write(b []byte) (int, error) {
-	c.writeLock.Lock()
-	defer c.writeLock.Unlock()
-	if err := c.ws.SendMessage(b); err != nil {
-		return 0, err
-	}
-	return len(b), nil
-}
-
-func (c *wrappedConn) Close() error {
-	return c.ws.Close()
-}
-
-func (c *wrappedConn) LocalAddr() net.Addr {
-	return websocketAddr{s: c.address}
-}
-
-func (c *wrappedConn) RemoteAddr() net.Addr {
-	return websocketAddr{s: c.address}
-}
-
-func (c *wrappedConn) SetDeadline(t time.Time) error {
-	panic("SetDeadline not implemented.")
-}
-
-func (c *wrappedConn) SetReadDeadline(t time.Time) error {
-	panic("SetReadDeadline not implemented.")
-}
-
-func (c *wrappedConn) SetWriteDeadline(t time.Time) error {
-	panic("SetWriteDeadline not implemented.")
-}
-
-type websocketAddr struct {
-	s string
-}
-
-func (websocketAddr) Network() string {
-	return "ws"
-}
-
-func (w websocketAddr) String() string {
-	return w.s
-}
diff --git a/runtime/internal/lib/websocket/conn_test.go b/runtime/internal/lib/websocket/conn_test.go
deleted file mode 100644
index 292f616..0000000
--- a/runtime/internal/lib/websocket/conn_test.go
+++ /dev/null
@@ -1,120 +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.
-
-// +build !nacl
-
-package websocket
-
-import (
-	"bytes"
-	"net"
-	"net/http"
-	"sync"
-	"testing"
-	"time"
-
-	"github.com/gorilla/websocket"
-
-	"v.io/v23/context"
-)
-
-func writer(c net.Conn, data []byte, times int, wg *sync.WaitGroup) {
-	defer wg.Done()
-	b := []byte{byte(len(data))}
-	b = append(b, data...)
-	for i := 0; i < times; i++ {
-		c.Write(b)
-	}
-}
-
-func readMessage(c net.Conn) ([]byte, error) {
-	var length [1]byte
-	// Read the size
-	for {
-		n, err := c.Read(length[:])
-		if err != nil {
-			return nil, err
-		}
-		if n == 1 {
-			break
-		}
-	}
-	size := int(length[0])
-	buf := make([]byte, size)
-	n := 0
-	for n < size {
-		nn, err := c.Read(buf[n:])
-		if err != nil {
-			return buf, err
-		}
-		n += nn
-	}
-
-	return buf, nil
-}
-
-func reader(t *testing.T, c net.Conn, expected []byte, totalWrites int) {
-	totalReads := 0
-	for buf, err := readMessage(c); err == nil; buf, err = readMessage(c) {
-		totalReads++
-		if !bytes.Equal(buf, expected) {
-			t.Errorf("Unexpected message %v, expected %v", buf, expected)
-		}
-	}
-	if totalReads != totalWrites {
-		t.Errorf("wrong number of messages expected %v, got %v", totalWrites, totalReads)
-	}
-}
-
-func TestMultipleGoRoutines(t *testing.T) {
-	l, err := net.Listen("tcp", "127.0.0.1:0")
-	if err != nil {
-		t.Fatalf("Failed to listen: %v", err)
-	}
-	addr := l.Addr()
-	input := []byte("no races here")
-	const numWriters int = 12
-	const numWritesPerWriter int = 1000
-	const totalWrites int = numWriters * numWritesPerWriter
-	s := &http.Server{
-		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-			if r.Method != "GET" {
-				http.Error(w, "Method not allowed.", http.StatusMethodNotAllowed)
-				return
-			}
-			ws, err := websocket.Upgrade(w, r, nil, 1024, 1024)
-			if _, ok := err.(websocket.HandshakeError); ok {
-				http.Error(w, "Not a websocket handshake", 400)
-				return
-			} else if err != nil {
-				http.Error(w, "Internal Error", 500)
-				return
-			}
-			reader(t, WebsocketConn(ws), input, totalWrites)
-		}),
-	}
-	// Dial out in another go routine
-	go func() {
-		ctx, _ := context.RootContext()
-		conn, err := Dial(ctx, "tcp", addr.String(), time.Second)
-		numTries := 0
-		for err != nil && numTries < 5 {
-			numTries++
-			time.Sleep(time.Second)
-		}
-
-		if err != nil {
-			t.Fatalf("failed to connect to server: %v", err)
-		}
-		var writers sync.WaitGroup
-		writers.Add(numWriters)
-		for i := 0; i < numWriters; i++ {
-			go writer(conn, input, numWritesPerWriter, &writers)
-		}
-		writers.Wait()
-		conn.Close()
-		l.Close()
-	}()
-	s.Serve(l)
-}
diff --git a/runtime/internal/lib/websocket/dialer.go b/runtime/internal/lib/websocket/dialer.go
deleted file mode 100644
index f7a3b21..0000000
--- a/runtime/internal/lib/websocket/dialer.go
+++ /dev/null
@@ -1,47 +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.
-
-// +build !nacl
-
-package websocket
-
-import (
-	"net"
-	"net/http"
-	"net/url"
-	"time"
-
-	"github.com/gorilla/websocket"
-
-	"v.io/x/ref/runtime/internal/lib/tcputil"
-
-	"v.io/v23/context"
-)
-
-func Dial(ctx *context.T, protocol, address string, timeout time.Duration) (net.Conn, error) {
-	var then time.Time
-	if timeout > 0 {
-		then = time.Now().Add(timeout)
-	}
-	tcp := mapWebSocketToTCP[protocol]
-	conn, err := net.DialTimeout(tcp, address, timeout)
-	if err != nil {
-		return nil, err
-	}
-	conn.SetReadDeadline(then)
-	if err := tcputil.EnableTCPKeepAlive(conn); err != nil {
-		return nil, err
-	}
-	u, err := url.Parse("ws://" + address)
-	if err != nil {
-		return nil, err
-	}
-	ws, _, err := websocket.NewClient(conn, u, http.Header{}, 4096, 4096)
-	if err != nil {
-		return nil, err
-	}
-	var zero time.Time
-	conn.SetDeadline(zero)
-	return WebsocketConn(ws), nil
-}
diff --git a/runtime/internal/lib/websocket/hybrid.go b/runtime/internal/lib/websocket/hybrid.go
deleted file mode 100644
index fb92d0a..0000000
--- a/runtime/internal/lib/websocket/hybrid.go
+++ /dev/null
@@ -1,56 +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 websocket
-
-import (
-	"net"
-	"time"
-
-	"v.io/x/ref/runtime/internal/lib/tcputil"
-
-	"v.io/v23/context"
-)
-
-// TODO(jhahn): Figure out a way for this mapping to be shared.
-var mapWebSocketToTCP = map[string]string{"ws": "tcp", "ws4": "tcp4", "ws6": "tcp6", "wsh": "tcp", "wsh4": "tcp4", "wsh6": "tcp6", "tcp": "tcp", "tcp4": "tcp4", "tcp6": "tcp6"}
-
-// HybridDial returns net.Conn that can be used with a HybridListener but
-// always uses tcp. A client must specifically elect to use websockets by
-// calling websocket.Dialer. The returned net.Conn will report 'tcp' as its
-// Network.
-func HybridDial(ctx *context.T, network, address string, timeout time.Duration) (net.Conn, error) {
-	tcp := mapWebSocketToTCP[network]
-	conn, err := net.DialTimeout(tcp, address, timeout)
-	if err != nil {
-		return nil, err
-	}
-	if err := tcputil.EnableTCPKeepAlive(conn); err != nil {
-		return nil, err
-	}
-	return conn, nil
-}
-
-// HybridResolve performs a DNS resolution on the network, address and always
-// returns tcp as its Network.
-func HybridResolve(ctx *context.T, network, address string) (string, string, error) {
-	tcp := mapWebSocketToTCP[network]
-	tcpAddr, err := net.ResolveTCPAddr(tcp, address)
-	if err != nil {
-		return "", "", err
-	}
-	return tcp, tcpAddr.String(), nil
-}
-
-// HybridListener returns a net.Listener that supports both tcp and
-// websockets over the same, single, port. A listen address of
-// --v23.tcp.protocol=wsh --v23.tcp.address=127.0.0.1:8101 means
-// that port 8101 can accept connections that use either tcp or websocket.
-// The listener looks at the first 4 bytes of the incoming data stream
-// to decide if it's a websocket protocol or not. These must be 'GET ' for
-// websockets, all other protocols must guarantee to not send 'GET ' as the
-// first four bytes of the payload.
-func HybridListener(ctx *context.T, protocol, address string) (net.Listener, error) {
-	return listener(protocol, address, true)
-}
diff --git a/runtime/internal/lib/websocket/listener.go b/runtime/internal/lib/websocket/listener.go
deleted file mode 100644
index f31f096..0000000
--- a/runtime/internal/lib/websocket/listener.go
+++ /dev/null
@@ -1,233 +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.
-
-// +build !nacl
-
-package websocket
-
-import (
-	"errors"
-	"io"
-	"net"
-	"net/http"
-	"sync"
-	"time"
-
-	"github.com/gorilla/websocket"
-
-	"v.io/x/ref/internal/logger"
-	"v.io/x/ref/runtime/internal/lib/tcputil"
-
-	"v.io/v23/context"
-)
-
-var errListenerIsClosed = errors.New("Listener has been Closed")
-
-const (
-	bufferSize         = 4096
-	classificationTime = 10 * time.Second
-)
-
-// A listener that is able to handle either raw tcp or websocket requests.
-type wsTCPListener struct {
-	closed bool // GUARDED_BY(mu)
-	mu     sync.Mutex
-
-	acceptQ chan interface{} // net.Conn or error returned by netLn.Accept
-	httpQ   chan net.Conn    // Candidates for websocket upgrades before being added to acceptQ
-	netLn   net.Listener     // The underlying listener
-	httpReq sync.WaitGroup   // Number of active HTTP requests
-	hybrid  bool             // true if running in 'hybrid' mode
-}
-
-// chanListener implements net.Listener, with Accept reading from c.
-type chanListener struct {
-	net.Listener // Embedded for all other net.Listener functionality.
-	c            <-chan net.Conn
-}
-
-func (ln *chanListener) Accept() (net.Conn, error) {
-	conn, ok := <-ln.c
-	if !ok {
-		return nil, errListenerIsClosed
-	}
-	return conn, nil
-}
-
-func Listener(ctx *context.T, protocol, address string) (net.Listener, error) {
-	return listener(protocol, address, false)
-}
-
-func listener(protocol, address string, hybrid bool) (net.Listener, error) {
-	netLn, err := net.Listen(mapWebSocketToTCP[protocol], address)
-	if err != nil {
-		return nil, err
-	}
-	ln := &wsTCPListener{
-		acceptQ: make(chan interface{}),
-		httpQ:   make(chan net.Conn),
-		netLn:   netLn,
-		hybrid:  hybrid,
-	}
-	go ln.netAcceptLoop()
-	httpsrv := http.Server{Handler: ln}
-	go httpsrv.Serve(&chanListener{Listener: ln, c: ln.httpQ})
-	return ln, nil
-}
-
-func (ln *wsTCPListener) Accept() (net.Conn, error) {
-	for {
-		item, ok := <-ln.acceptQ
-		if !ok {
-			return nil, errListenerIsClosed
-		}
-		switch v := item.(type) {
-		case net.Conn:
-			return v, nil
-		case error:
-			return nil, v
-		default:
-			logger.Global().Errorf("Unexpected type %T in channel (%v)", v, v)
-		}
-	}
-}
-
-func (ln *wsTCPListener) Close() error {
-	ln.mu.Lock()
-	if ln.closed {
-		ln.mu.Unlock()
-		return errListenerIsClosed
-	}
-	ln.closed = true
-	ln.mu.Unlock()
-	addr := ln.netLn.Addr()
-	err := ln.netLn.Close()
-	logger.Global().VI(1).Infof("Closed net.Listener on (%q, %q): %v", addr.Network(), addr, err)
-	// netAcceptLoop might be trying to push new TCP connections that
-	// arrived while the listener was being closed. Drop those.
-	drainChan(ln.acceptQ)
-	return nil
-}
-
-func (ln *wsTCPListener) netAcceptLoop() {
-	var classifications sync.WaitGroup
-	defer func() {
-		// This sequence of closures is carefully curated based on the
-		// following invariants:
-		// (1) All calls to ln.classify have been added to classifications.
-		// (2) Only ln.classify sends on ln.httpQ
-		// (3) All calls to ln.ServeHTTP have been added to ln.httpReq
-		// (4) Sends on ln.acceptQ are done by either ln.netAcceptLoop ro ln.ServeHTTP
-		classifications.Wait()
-		close(ln.httpQ)
-		ln.httpReq.Wait()
-		close(ln.acceptQ)
-	}()
-	for {
-		conn, err := ln.netLn.Accept()
-		if err != nil {
-			// If the listener has been closed, quit - otherwise
-			// propagate the error.
-			ln.mu.Lock()
-			closed := ln.closed
-			ln.mu.Unlock()
-			if closed {
-				return
-			}
-			ln.acceptQ <- err
-			continue
-		}
-		logger.Global().VI(1).Infof("New net.Conn accepted from %s (local address: %s)", conn.RemoteAddr(), conn.LocalAddr())
-		if err := tcputil.EnableTCPKeepAlive(conn); err != nil {
-			logger.Global().Errorf("Failed to enable TCP keep alive: %v", err)
-		}
-		classifications.Add(1)
-		go ln.classify(conn, &classifications)
-	}
-}
-
-// classify classifies conn as either an HTTP connection or a non-HTTP one.
-//
-// If the latter, then the connection is added to ln.acceptQ.
-// If the former, then the connection is queued up for a websocket upgrade.
-func (ln *wsTCPListener) classify(conn net.Conn, done *sync.WaitGroup) {
-	defer done.Done()
-	isHTTP := true
-	if ln.hybrid {
-		conn.SetReadDeadline(time.Now().Add(classificationTime))
-		defer conn.SetReadDeadline(time.Time{})
-		var magic [1]byte
-		n, err := io.ReadFull(conn, magic[:])
-		if err != nil {
-			// Unable to classify, ignore this connection.
-			logger.Global().VI(1).Infof("Shutting down connection from %v since the magic bytes could not be read: %v", conn.RemoteAddr(), err)
-			conn.Close()
-			return
-		}
-		conn = &hybridConn{conn: conn, buffered: magic[:n]}
-		isHTTP = magic[0] == 'G'
-	}
-	if isHTTP {
-		ln.httpReq.Add(1)
-		ln.httpQ <- conn
-		return
-	}
-	ln.acceptQ <- conn
-}
-
-func (ln *wsTCPListener) ServeHTTP(w http.ResponseWriter, r *http.Request) {
-	defer ln.httpReq.Done()
-	if r.Method != "GET" {
-		http.Error(w, "Method not allowed.", http.StatusMethodNotAllowed)
-		return
-	}
-	ws, err := websocket.Upgrade(w, r, nil, bufferSize, bufferSize)
-	if _, ok := err.(websocket.HandshakeError); ok {
-		// Close the connection to not serve HTTP requests from this connection
-		// any more. Otherwise panic from negative httpReq counter can occur.
-		// Although go http.Server gracefully shutdowns the server from a panic,
-		// it would be nice to avoid it.
-		w.Header().Set("Connection", "close")
-		http.Error(w, "Not a websocket handshake", http.StatusBadRequest)
-		logger.Global().Errorf("Rejected a non-websocket request: %v", err)
-		return
-	}
-	if err != nil {
-		w.Header().Set("Connection", "close")
-		http.Error(w, "Internal Error", http.StatusInternalServerError)
-		logger.Global().Errorf("Rejected a non-websocket request: %v", err)
-		return
-	}
-	ln.acceptQ <- WebsocketConn(ws)
-}
-
-type addr struct{ n, a string }
-
-func (a addr) Network() string {
-	return a.n
-}
-
-func (a addr) String() string {
-	return a.a
-}
-
-func (ln *wsTCPListener) Addr() net.Addr {
-	protocol := "ws"
-	if ln.hybrid {
-		protocol = "wsh"
-	}
-	return addr{protocol, ln.netLn.Addr().String()}
-}
-
-func drainChan(c <-chan interface{}) {
-	for {
-		item, ok := <-c
-		if !ok {
-			return
-		}
-		if conn, ok := item.(net.Conn); ok {
-			conn.Close()
-		}
-	}
-}
diff --git a/runtime/internal/lib/websocket/listener_nacl.go b/runtime/internal/lib/websocket/listener_nacl.go
deleted file mode 100644
index ebf6255..0000000
--- a/runtime/internal/lib/websocket/listener_nacl.go
+++ /dev/null
@@ -1,24 +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.
-
-// +build nacl
-
-package websocket
-
-import (
-	"fmt"
-	"net"
-
-	"v.io/v23/context"
-)
-
-// Websocket listeners are not supported in NaCl.
-// This file is needed for compilation only.
-func listener(protocol, address string, hybrid bool) (net.Listener, error) {
-	return nil, fmt.Errorf("Websocket Listener called in nacl code!")
-}
-
-func Listener(ctx *context.T, protocol, address string) (net.Listener, error) {
-	return nil, fmt.Errorf("Websocket Listener called in nacl code!")
-}
diff --git a/runtime/internal/lib/websocket/listener_test.go b/runtime/internal/lib/websocket/listener_test.go
deleted file mode 100644
index f19ddd3..0000000
--- a/runtime/internal/lib/websocket/listener_test.go
+++ /dev/null
@@ -1,98 +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.
-
-// +build !nacl
-
-package websocket
-
-import (
-	"bytes"
-	"log"
-	"net"
-	"strings"
-	"testing"
-	"time"
-
-	"v.io/v23/context"
-)
-
-func TestAcceptsAreNotSerialized(t *testing.T) {
-	ctx, _ := context.RootContext()
-	ln, err := HybridListener(ctx, "wsh", "127.0.0.1:0")
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer func() { go ln.Close() }()
-	portscan := make(chan struct{})
-
-	// Goroutine that continuously accepts connections.
-	go func() {
-		for {
-			conn, err := ln.Accept()
-			if err != nil {
-				return
-			}
-			defer conn.Close()
-		}
-	}()
-
-	// Imagine some client was port scanning and thus opened a TCP
-	// connection (but never sent the bytes)
-	go func() {
-		conn, err := net.Dial("tcp", ln.Addr().String())
-		if err != nil {
-			t.Error(err)
-		}
-		close(portscan)
-		// Keep the connection alive by blocking on a read.  (The read
-		// should return once the test exits).
-		conn.Read(make([]byte, 1024))
-	}()
-	// Another client that dials a legitimate connection should not be
-	// blocked on the portscanner.
-	// (Wait for the portscanner to establish the TCP connection first).
-	<-portscan
-	conn, err := Dial(ctx, ln.Addr().Network(), ln.Addr().String(), time.Second)
-	if err != nil {
-		t.Fatal(err)
-	}
-	conn.Close()
-}
-
-func TestNonWebsocketRequest(t *testing.T) {
-	ctx, _ := context.RootContext()
-	ln, err := HybridListener(ctx, "wsh", "127.0.0.1:0")
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer func() { go ln.Close() }()
-
-	// Goroutine that continuously accepts connections.
-	go func() {
-		for {
-			_, err := ln.Accept()
-			if err != nil {
-				return
-			}
-		}
-	}()
-
-	var out bytes.Buffer
-	log.SetOutput(&out)
-
-	// Imagine some client keeps sending non-websocket requests.
-	conn, err := net.Dial("tcp", ln.Addr().String())
-	if err != nil {
-		t.Error(err)
-	}
-	for i := 0; i < 2; i++ {
-		conn.Write([]byte("GET / HTTP/1.1\r\n\r\n"))
-		conn.Read(make([]byte, 1024))
-	}
-
-	logs := out.String()
-	if strings.Contains(logs, "panic") {
-		t.Errorf("Unexpected panic:\n%s", logs)
-	}
-}
diff --git a/runtime/internal/lib/websocket/resolver.go b/runtime/internal/lib/websocket/resolver.go
deleted file mode 100644
index 5a99c23..0000000
--- a/runtime/internal/lib/websocket/resolver.go
+++ /dev/null
@@ -1,23 +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.
-
-// +build !nacl
-
-package websocket
-
-import (
-	"net"
-
-	"v.io/v23/context"
-)
-
-// Resolve performs a DNS resolution on the provided protocol and address.
-func Resolve(ctx *context.T, protocol, address string) (string, string, error) {
-	tcp := mapWebSocketToTCP[protocol]
-	tcpAddr, err := net.ResolveTCPAddr(tcp, address)
-	if err != nil {
-		return "", "", err
-	}
-	return "ws", tcpAddr.String(), nil
-}
diff --git a/runtime/internal/lib/websocket/util_test.go b/runtime/internal/lib/websocket/util_test.go
deleted file mode 100644
index f7a7c4e..0000000
--- a/runtime/internal/lib/websocket/util_test.go
+++ /dev/null
@@ -1,290 +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 websocket_test
-
-import (
-	"encoding/gob"
-	"fmt"
-	"hash/crc64"
-	"io"
-	"math/rand"
-	"net"
-	"sync"
-	"testing"
-	"time"
-
-	"v.io/v23/context"
-	"v.io/v23/rpc"
-)
-
-var crcTable *crc64.Table
-
-func init() {
-	crcTable = crc64.MakeTable(crc64.ISO)
-}
-
-func newSender(t *testing.T, dialer rpc.DialerFunc, protocol, address string) net.Conn {
-	ctx, _ := context.RootContext()
-	conn, err := dialer(ctx, protocol, address, time.Minute)
-	if err != nil {
-		t.Fatalf("unexpected error: %s", err)
-		return nil
-	}
-	return conn
-}
-
-func checkProtocols(conn net.Conn, tx string) error {
-	expectedProtocol := map[string]string{
-		"ws": "ws", "wsh": "tcp", "tcp": "tcp",
-	}
-	if got, want := conn.LocalAddr().Network(), expectedProtocol[tx]; got != want {
-		return fmt.Errorf("wrong local protocol: got %q, want %q", got, want)
-	}
-	// Can't tell that the remote protocol is really 'wsh'
-	if got, want := conn.RemoteAddr().Network(), expectedProtocol[tx]; got != want {
-		return fmt.Errorf("wrong remote protocol: got %q, want %q", got, want)
-	}
-	return nil
-}
-
-type packet struct {
-	Data  []byte
-	Size  int
-	CRC64 uint64
-}
-
-func createPacket() *packet {
-	p := &packet{}
-	p.Size = rand.Intn(4 * 1024)
-	p.Data = make([]byte, p.Size)
-	for i := 0; i < p.Size; i++ {
-		p.Data[i] = byte(rand.Int() & 0xff)
-	}
-	p.CRC64 = crc64.Checksum([]byte(p.Data), crcTable)
-	return p
-}
-
-func checkPacket(p *packet) error {
-	if got, want := len(p.Data), p.Size; got != want {
-		return fmt.Errorf("wrong sizes: got %d, want %d", got, want)
-	}
-	crc := crc64.Checksum(p.Data, crcTable)
-	if got, want := crc, p.CRC64; got != want {
-		return fmt.Errorf("wrong crc: got %d, want %d", got, want)
-	}
-	return nil
-}
-
-type backChannel struct {
-	crcChan  chan uint64
-	byteChan chan []byte
-	errChan  chan error
-}
-
-type bcTable struct {
-	ready *sync.Cond
-	sync.Mutex
-	bc map[string]*backChannel
-}
-
-var globalBCTable bcTable
-
-func init() {
-	globalBCTable.ready = sync.NewCond(&globalBCTable)
-	globalBCTable.bc = make(map[string]*backChannel)
-}
-
-func (bt *bcTable) waitfor(key string) *backChannel {
-	bt.Lock()
-	defer bt.Unlock()
-	for {
-		bc := bt.bc[key]
-		if bc != nil {
-			delete(bt.bc, key)
-			return bc
-		}
-		bt.ready.Wait()
-	}
-}
-
-func (bt *bcTable) add(key string, bc *backChannel) {
-	bt.Lock()
-	bt.bc[key] = bc
-	bt.Unlock()
-	bt.ready.Broadcast()
-}
-
-func packetReceiver(t *testing.T, ln net.Listener, bc *backChannel) {
-	conn, err := ln.Accept()
-	if err != nil {
-		close(bc.crcChan)
-		close(bc.errChan)
-		return
-	}
-
-	globalBCTable.add(conn.RemoteAddr().String(), bc)
-
-	defer conn.Close()
-	dec := gob.NewDecoder(conn)
-	rxed := 0
-	for {
-		var p packet
-		err := dec.Decode(&p)
-		if err != nil {
-			if err != io.EOF {
-				bc.errChan <- fmt.Errorf("unexpected error: %s", err)
-			}
-			close(bc.crcChan)
-			close(bc.errChan)
-			return
-		}
-		if err := checkPacket(&p); err != nil {
-			bc.errChan <- fmt.Errorf("unexpected error: %s", err)
-		}
-		bc.crcChan <- p.CRC64
-		rxed++
-	}
-}
-
-func packetSender(t *testing.T, nPackets int, conn net.Conn) {
-	txCRCs := make([]uint64, nPackets)
-	enc := gob.NewEncoder(conn)
-	for i := 0; i < nPackets; i++ {
-		p := createPacket()
-		txCRCs[i] = p.CRC64
-		if err := enc.Encode(p); err != nil {
-			t.Fatalf("unexpected error: %s", err)
-		}
-	}
-	conn.Close() // Close the connection so that the receiver quits.
-
-	bc := globalBCTable.waitfor(conn.LocalAddr().String())
-	for err := range bc.errChan {
-		if err != nil {
-			t.Fatalf(err.Error())
-		}
-	}
-
-	rxed := 0
-	for rxCRC := range bc.crcChan {
-		if got, want := rxCRC, txCRCs[rxed]; got != want {
-			t.Errorf("%s -> %s: packet %d: mismatched CRCs: got %d, want %d", conn.LocalAddr().String(), conn.RemoteAddr().String(), rxed, got, want)
-		}
-		rxed++
-	}
-	if got, want := rxed, nPackets; got != want {
-		t.Fatalf("%s -> %s: got %d, want %d", conn.LocalAddr().String(), conn.RemoteAddr().String(), got, want)
-	}
-}
-
-func packetRunner(t *testing.T, ln net.Listener, dialer rpc.DialerFunc, protocol, address string) {
-	nPackets := 100
-	go packetReceiver(t, ln, &backChannel{
-		crcChan: make(chan uint64, nPackets),
-		errChan: make(chan error, nPackets),
-	})
-
-	conn := newSender(t, dialer, protocol, address)
-	if err := checkProtocols(conn, protocol); err != nil {
-		t.Fatalf(err.Error())
-	}
-	packetSender(t, nPackets, conn)
-}
-
-func byteReceiver(t *testing.T, ln net.Listener, bc *backChannel) {
-	conn, err := ln.Accept()
-	if err != nil {
-		close(bc.byteChan)
-		close(bc.errChan)
-		return
-	}
-	globalBCTable.add(conn.RemoteAddr().String(), bc)
-
-	defer conn.Close()
-	rxed := 0
-	for {
-		buf := make([]byte, rxed+1)
-		n, err := conn.Read(buf)
-		if err != nil {
-			if err != io.EOF {
-				bc.errChan <- fmt.Errorf("unexpected error: %s", err)
-			}
-			close(bc.byteChan)
-			close(bc.errChan)
-			return
-		}
-		if got, want := n, len(buf[:n]); got != want {
-			bc.errChan <- fmt.Errorf("%s -> %s: got %d bytes, expected %d", conn.LocalAddr().String(), conn.RemoteAddr().String(), got, want)
-		}
-		if got, want := buf[0], byte(0xff); got != want {
-			bc.errChan <- fmt.Errorf("%s -> %s: got %x, want %x", conn.LocalAddr().String(), conn.RemoteAddr().String(), got, want)
-		}
-		bc.byteChan <- buf[:n]
-		rxed++
-	}
-}
-
-func byteSender(t *testing.T, nIterations int, conn net.Conn) {
-	txBytes := make([][]byte, nIterations+1)
-	for i := 0; i < nIterations; i++ {
-		p := make([]byte, i+1)
-		p[0] = 0xff
-		for j := 1; j <= i; j++ {
-			p[j] = byte(64 + i) // start at ASCII A
-		}
-		txBytes[i] = p
-		n, err := conn.Write(p)
-		if err != nil {
-			t.Fatalf("unexpected error: %s", err)
-		}
-		if got, want := n, i+1; got != want {
-			t.Fatalf("wrote %d, not %d bytes", got, want)
-		}
-	}
-	conn.Close()
-
-	bc := globalBCTable.waitfor(conn.LocalAddr().String())
-
-	for err := range bc.errChan {
-		if err != nil {
-			t.Fatalf(err.Error())
-		}
-	}
-
-	addr := fmt.Sprintf("%s -> %s", conn.LocalAddr().String(), conn.RemoteAddr().String())
-	rxed := 0
-	for rxBytes := range bc.byteChan {
-		if got, want := len(rxBytes), rxed+1; got != want {
-			t.Fatalf("%s: got %d, want %d bytes", addr, got, want)
-		}
-		if got, want := rxBytes[0], byte(0xff); got != want {
-			t.Fatalf("%s: got %x, want %x", addr, got, want)
-		}
-		for i := 0; i < len(rxBytes); i++ {
-			if got, want := rxBytes[i], txBytes[rxed][i]; got != want {
-				t.Fatalf("%s: got %c, want %c", addr, got, want)
-			}
-		}
-		rxed++
-	}
-	if got, want := rxed, nIterations; got != want {
-		t.Fatalf("%s: got %d, want %d", addr, got, want)
-	}
-}
-
-func byteRunner(t *testing.T, ln net.Listener, dialer rpc.DialerFunc, protocol, address string) {
-	nIterations := 10
-	go byteReceiver(t, ln, &backChannel{
-		byteChan: make(chan []byte, nIterations),
-		errChan:  make(chan error, nIterations),
-	})
-
-	conn := newSender(t, dialer, protocol, address)
-	defer conn.Close()
-	if err := checkProtocols(conn, protocol); err != nil {
-		t.Fatalf(err.Error())
-	}
-	byteSender(t, nIterations, conn)
-}
diff --git a/runtime/internal/lib/websocket/ws_test.go b/runtime/internal/lib/websocket/ws_test.go
deleted file mode 100644
index a193885..0000000
--- a/runtime/internal/lib/websocket/ws_test.go
+++ /dev/null
@@ -1,106 +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 websocket_test
-
-import (
-	"net"
-	"sync"
-	"testing"
-	"time"
-
-	"v.io/v23/context"
-	"v.io/v23/rpc"
-	"v.io/x/ref/runtime/internal/lib/websocket"
-)
-
-func packetTester(t *testing.T, dialer rpc.DialerFunc, listener rpc.ListenerFunc, txProtocol, rxProtocol string) {
-	ctx, _ := context.RootContext()
-	ln, err := listener(ctx, rxProtocol, "127.0.0.1:0")
-	if err != nil {
-		t.Fatalf("unexpected error: %s", err)
-	}
-	defer ln.Close()
-	if got, want := ln.Addr().Network(), rxProtocol; got != want {
-		t.Fatalf("got %q, want %q", got, want)
-	}
-
-	packetRunner(t, ln, dialer, txProtocol, ln.Addr().String())
-	packetRunner(t, ln, dialer, txProtocol, ln.Addr().String())
-}
-
-func byteTester(t *testing.T, dialer rpc.DialerFunc, listener rpc.ListenerFunc, txProtocol, rxProtocol string) {
-	ctx, _ := context.RootContext()
-	ln, err := listener(ctx, rxProtocol, "127.0.0.1:0")
-	if err != nil {
-		t.Fatalf("unexpected error: %s", err)
-	}
-	defer ln.Close()
-	if got, want := ln.Addr().Network(), rxProtocol; got != want {
-		t.Fatalf("got %q, want %q", got, want)
-	}
-
-	byteRunner(t, ln, dialer, txProtocol, ln.Addr().String())
-	byteRunner(t, ln, dialer, txProtocol, ln.Addr().String())
-
-}
-
-func simpleDial(ctx *context.T, p, a string, timeout time.Duration) (net.Conn, error) {
-	return net.DialTimeout(p, a, timeout)
-}
-
-func TestWSToWS(t *testing.T) {
-	byteTester(t, websocket.Dial, websocket.Listener, "ws", "ws")
-	packetTester(t, websocket.Dial, websocket.Listener, "ws", "ws")
-}
-
-func TestWSToWSH(t *testing.T) {
-	byteTester(t, websocket.Dial, websocket.HybridListener, "ws", "wsh")
-	//packetTester(t, websocket.Dial, websocket.HybridListener, "ws", "wsh")
-}
-
-func TestWSHToWSH(t *testing.T) {
-	byteTester(t, websocket.HybridDial, websocket.HybridListener, "wsh", "wsh")
-	packetTester(t, websocket.HybridDial, websocket.HybridListener, "wsh", "wsh")
-}
-
-func TestTCPToWSH(t *testing.T) {
-	byteTester(t, simpleDial, websocket.HybridListener, "tcp", "wsh")
-	packetTester(t, simpleDial, websocket.HybridListener, "tcp", "wsh")
-}
-
-func TestMixed(t *testing.T) {
-	ctx, _ := context.RootContext()
-	ln, err := websocket.HybridListener(ctx, "wsh", "127.0.0.1:0")
-	if err != nil {
-		t.Fatalf("unexpected error: %s", err)
-	}
-	defer ln.Close()
-
-	var pwg sync.WaitGroup
-	packetTest := func(dialer rpc.DialerFunc, protocol string) {
-		packetRunner(t, ln, dialer, protocol, ln.Addr().String())
-		pwg.Done()
-	}
-
-	pwg.Add(4)
-	go packetTest(websocket.Dial, "ws")
-	go packetTest(simpleDial, "tcp")
-	go packetTest(websocket.Dial, "ws")
-	go packetTest(websocket.HybridDial, "wsh")
-	pwg.Wait()
-
-	var bwg sync.WaitGroup
-	byteTest := func(dialer rpc.DialerFunc, protocol string) {
-		byteRunner(t, ln, dialer, protocol, ln.Addr().String())
-		bwg.Done()
-	}
-	bwg.Add(4)
-	go byteTest(websocket.Dial, "ws")
-	go byteTest(simpleDial, "tcp")
-	go byteTest(websocket.Dial, "ws")
-	go byteTest(websocket.HybridDial, "wsh")
-
-	bwg.Wait()
-}
diff --git a/runtime/internal/rpc/benchmark/simple/AndroidManifest.xml b/runtime/internal/rpc/benchmark/simple/AndroidManifest.xml
new file mode 100644
index 0000000..41917b0
--- /dev/null
+++ b/runtime/internal/rpc/benchmark/simple/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+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.
+-->
+<manifest
+	xmlns:android="http://schemas.android.com/apk/res/android"
+	package="io.v.x.ref.runtime.internal.rpc.benchmark.simple"
+	android:versionCode="1"
+	android:versionName="1.0">
+
+	<!-- http://developer.android.com/guide/topics/manifest/manifest-intro.html#perms  -->
+	<uses-permission android:name="android.permission.INTERNET" />
+
+	<application android:label="v23rpc" android:debuggable="true">
+
+	<activity android:name="org.golang.app.GoNativeActivity"
+		android:label="v23rpc"
+		android:configChanges="orientation|keyboardHidden">
+		<meta-data android:name="android.app.lib_name" android:value="v23rpc" />
+		<intent-filter>
+			<action android:name="android.intent.action.MAIN" />
+			<category android:name="android.intent.category.LAUNCHER" />
+		</intent-filter>
+	</activity>
+	</application>
+</manifest>
diff --git a/runtime/internal/rpc/benchmark/simple/benchmark.go b/runtime/internal/rpc/benchmark/simple/benchmark.go
new file mode 100644
index 0000000..482b0fb
--- /dev/null
+++ b/runtime/internal/rpc/benchmark/simple/benchmark.go
@@ -0,0 +1,208 @@
+// 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 main
+
+import (
+	"flag"
+	"fmt"
+	"runtime"
+	"testing"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/options"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/x/lib/ibe"
+	"v.io/x/ref/lib/security/bcrypter"
+	"v.io/x/ref/lib/security/securityflag"
+	_ "v.io/x/ref/runtime/factories/roaming"
+	"v.io/x/ref/runtime/internal/flow/flowtest"
+	fmanager "v.io/x/ref/runtime/internal/flow/manager"
+	"v.io/x/ref/runtime/internal/rpc/benchmark/internal"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/benchmark"
+	"v.io/x/ref/test/testutil"
+)
+
+const (
+	payloadSize = 1000
+	chunkCnt    = 10000
+
+	bulkPayloadSize = 1000000
+
+	numCPUs          = 2
+	defaultBenchTime = 5 * time.Second
+)
+
+var ctx *context.T
+
+type benchFun func(b *testing.B)
+
+func newServer(ctx *context.T, opts ...rpc.ServerOpt) (*context.T, naming.Endpoint) {
+	ctx, server, err := v23.WithNewServer(ctx, "", internal.NewService(), securityflag.NewAuthorizerOrDie(), opts...)
+	if err != nil {
+		ctx.Fatalf("NewServer failed: %v", err)
+	}
+	return ctx, server.Status().Endpoints[0]
+}
+
+// Benchmark for measuring RPC connection time including authentication.
+//
+// rpc.Client doesn't export an interface for closing connection. So we
+// use the stream manager directly here.
+func benchmarkRPCConnection(b *testing.B) {
+	mp := runtime.GOMAXPROCS(numCPUs)
+	defer runtime.GOMAXPROCS(mp)
+
+	ctx, serverEP := newServer(ctx)
+
+	principal := testutil.NewPrincipal("test")
+	nctx, _ := v23.WithPrincipal(ctx, principal)
+
+	b.StopTimer()
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		mctx, cancel := context.WithCancel(nctx)
+		m := fmanager.New(mctx, naming.FixedRoutingID(0xc), nil, 0)
+		b.StartTimer()
+		_, err := m.Dial(mctx, serverEP, flowtest.AllowAllPeersAuthorizer{}, 0)
+		if err != nil {
+			ctx.Fatalf("Dial failed: %v", err)
+		}
+		b.StopTimer()
+		cancel()
+		<-m.Closed()
+	}
+}
+
+// Benchmark for measuring RPC connection time when using private mutual
+// authentication. 'serverAuth' is the authorization policy used by the
+// server while revealing its blessings, and 'clientBlessing' is the blessing
+// used by the client.
+//
+// The specific protocol being benchmarked is Protocol 3 from the doc:
+// https://docs.google.com/document/d/1FpLJSiKy4sXxRUSZh1BQrhUEn7io-dGW7y-DMszI21Q/edit
+func benchmarkPrivateRPCConnection(ctx *context.T, serverAuth []security.BlessingPattern, clientBlessing string) benchFun {
+	return func(b *testing.B) {
+		mp := runtime.GOMAXPROCS(numCPUs)
+		defer runtime.GOMAXPROCS(mp)
+
+		ctx, privateServerEP := newServer(ctx, options.ServerPeers(serverAuth))
+
+		principal := testutil.NewPrincipal(clientBlessing)
+		nctx, _ := v23.WithPrincipal(ctx, principal)
+
+		b.StopTimer()
+		b.ResetTimer()
+		for i := 0; i < b.N; i++ {
+			mctx, cancel := context.WithCancel(nctx)
+			m := fmanager.New(mctx, naming.FixedRoutingID(0xc), nil, 0)
+			b.StartTimer()
+			_, err := m.Dial(nctx, privateServerEP, flowtest.AllowAllPeersAuthorizer{}, 0)
+			if err != nil {
+				ctx.Fatalf("Dial failed: %v", err)
+			}
+			b.StopTimer()
+			cancel()
+			<-m.Closed()
+		}
+	}
+}
+
+// Benchmark for non-streaming RPC.
+func benchmarkRPC(b *testing.B) {
+	ctx, serverEP := newServer(ctx)
+	mp := runtime.GOMAXPROCS(numCPUs)
+	defer runtime.GOMAXPROCS(mp)
+	internal.CallEcho(b, ctx, serverEP.Name(), b.N, payloadSize, benchmark.NewStats(1))
+}
+
+// Benchmark for streaming RPC.
+func benchmarkStreamingRPC(b *testing.B) {
+	ctx, serverEP := newServer(ctx)
+	mp := runtime.GOMAXPROCS(numCPUs)
+	defer runtime.GOMAXPROCS(mp)
+	internal.CallEchoStream(b, ctx, serverEP.Name(), b.N, chunkCnt, payloadSize, benchmark.NewStats(1))
+}
+
+// Benchmark for measuring throughput in streaming RPC.
+func benchmarkStreamingRPCThroughput(b *testing.B) {
+	ctx, serverEP := newServer(ctx)
+	mp := runtime.GOMAXPROCS(numCPUs)
+	defer runtime.GOMAXPROCS(mp)
+	internal.CallEchoStream(b, ctx, serverEP.Name(), 1, b.N, bulkPayloadSize, benchmark.NewStats(1))
+}
+
+func msPerRPC(r testing.BenchmarkResult) float64 {
+	return r.T.Seconds() / float64(r.N) * 1000
+}
+
+func rpcPerSec(r testing.BenchmarkResult) float64 {
+	return float64(r.N) / r.T.Seconds()
+}
+func mbPerSec(r testing.BenchmarkResult) float64 {
+	return (float64(r.Bytes) * float64(r.N) / 1e6) / r.T.Seconds()
+}
+
+func runBenchmarks() {
+	r := testing.Benchmark(benchmarkRPCConnection)
+	fmt.Printf("RPC Connection\t%.2f ms/rpc\n", msPerRPC(r))
+
+	master, err := ibe.SetupBB1()
+	if err != nil {
+		ctx.Fatalf("ibe.SetupBB1 failed: %v", err)
+	}
+	root := bcrypter.NewRoot("root", master)
+	clientBlessing := "root:alice:client"
+
+	// Attach a crypter to the context, and add a blessing private
+	// key to the for 'clientBlesing'.
+	crypter := bcrypter.NewCrypter()
+	cctx := bcrypter.WithCrypter(ctx, crypter)
+	key, err := root.Extract(ctx, clientBlessing)
+	if err != nil {
+		ctx.Fatalf("could not extract private key: %v", err)
+	}
+	if err := crypter.AddKey(cctx, key); err != nil {
+		ctx.Fatalf("could not add key to crypter: %v", err)
+	}
+
+	serverAuthPatterns := [][]security.BlessingPattern{
+		[]security.BlessingPattern{"root:alice"},
+		[]security.BlessingPattern{"root:bob:friend", "root:carol:friend", "root:alice:client"},
+		[]security.BlessingPattern{"root:bob:spouse", "root:bob:enemy", "root:carol:spouse", "root:carol:enemy", "root:alice:client:$"},
+	}
+	for _, serverAuth := range serverAuthPatterns {
+		r = testing.Benchmark(benchmarkPrivateRPCConnection(cctx, serverAuth, clientBlessing))
+		fmt.Printf("Private RPC Connection with server authorization policy %v and client blessing %v \t%.2f ms/rpc\n", serverAuth, clientBlessing, msPerRPC(r))
+	}
+
+	// Create a connection to exclude the setup time from the following benchmarks.
+	ctx, serverEP := newServer(ctx)
+	internal.CallEcho(&testing.B{}, ctx, serverEP.Name(), 1, 0, benchmark.NewStats(1))
+
+	r = testing.Benchmark(benchmarkRPC)
+	fmt.Printf("RPC (echo %vB)\t%.2f ms/rpc (%.2f qps)\n", payloadSize, msPerRPC(r), rpcPerSec(r))
+
+	r = testing.Benchmark(benchmarkStreamingRPC)
+	fmt.Printf("RPC Streaming (echo %vB)\t%.2f ms/rpc\n", payloadSize, msPerRPC(r)/chunkCnt)
+
+	r = testing.Benchmark(benchmarkStreamingRPCThroughput)
+	fmt.Printf("RPC Streaming Throughput (echo %vMB)\t%.2f MB/s\n", bulkPayloadSize/1e6, mbPerSec(r))
+}
+
+func realMain() {
+	// Set the default benchmark time.
+	flag.Set("test.benchtime", defaultBenchTime.String())
+
+	var shutdown v23.Shutdown
+	ctx, shutdown = test.V23Init()
+	defer shutdown()
+
+	runBenchmarks()
+}
diff --git a/runtime/internal/rpc/benchmark/simple/main.go b/runtime/internal/rpc/benchmark/simple/main.go
index 9da3d9e..19e4ac1 100644
--- a/runtime/internal/rpc/benchmark/simple/main.go
+++ b/runtime/internal/rpc/benchmark/simple/main.go
@@ -2,207 +2,10 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+// +build !android
+
 package main
 
-import (
-	"flag"
-	"fmt"
-	"runtime"
-	"testing"
-	"time"
-
-	"v.io/v23"
-	"v.io/v23/context"
-	"v.io/v23/naming"
-	"v.io/v23/options"
-	"v.io/v23/rpc"
-	"v.io/v23/security"
-	"v.io/x/lib/ibe"
-	"v.io/x/ref/lib/security/bcrypter"
-	"v.io/x/ref/lib/security/securityflag"
-	_ "v.io/x/ref/runtime/factories/roaming"
-	"v.io/x/ref/runtime/internal/flow/flowtest"
-	fmanager "v.io/x/ref/runtime/internal/flow/manager"
-	"v.io/x/ref/runtime/internal/rpc/benchmark/internal"
-	"v.io/x/ref/test"
-	"v.io/x/ref/test/benchmark"
-	"v.io/x/ref/test/testutil"
-)
-
-const (
-	payloadSize = 1000
-	chunkCnt    = 10000
-
-	bulkPayloadSize = 1000000
-
-	numCPUs          = 2
-	defaultBenchTime = 5 * time.Second
-)
-
-var ctx *context.T
-
-type benchFun func(b *testing.B)
-
-func newServer(ctx *context.T, opts ...rpc.ServerOpt) (*context.T, naming.Endpoint) {
-	ctx, server, err := v23.WithNewServer(ctx, "", internal.NewService(), securityflag.NewAuthorizerOrDie(), opts...)
-	if err != nil {
-		ctx.Fatalf("NewServer failed: %v", err)
-	}
-	return ctx, server.Status().Endpoints[0]
-}
-
-// Benchmark for measuring RPC connection time including authentication.
-//
-// rpc.Client doesn't export an interface for closing connection. So we
-// use the stream manager directly here.
-func benchmarkRPCConnection(b *testing.B) {
-	mp := runtime.GOMAXPROCS(numCPUs)
-	defer runtime.GOMAXPROCS(mp)
-
-	ctx, serverEP := newServer(ctx)
-
-	principal := testutil.NewPrincipal("test")
-	nctx, _ := v23.WithPrincipal(ctx, principal)
-
-	b.StopTimer()
-	b.ResetTimer()
-	for i := 0; i < b.N; i++ {
-		mctx, cancel := context.WithCancel(nctx)
-		m := fmanager.New(mctx, naming.FixedRoutingID(0xc), nil, 0)
-		b.StartTimer()
-		_, err := m.Dial(mctx, serverEP, flowtest.AllowAllPeersAuthorizer{}, 0)
-		if err != nil {
-			ctx.Fatalf("Dial failed: %v", err)
-		}
-		b.StopTimer()
-		cancel()
-		<-m.Closed()
-	}
-}
-
-// Benchmark for measuring RPC connection time when using private mutual
-// authentication. 'serverAuth' is the authorization policy used by the
-// server while revealing its blessings, and 'clientBlessing' is the blessing
-// used by the client.
-//
-// The specific protocol being benchmarked is Protocol 3 from the doc:
-// https://docs.google.com/document/d/1FpLJSiKy4sXxRUSZh1BQrhUEn7io-dGW7y-DMszI21Q/edit
-func benchmarkPrivateRPCConnection(ctx *context.T, serverAuth []security.BlessingPattern, clientBlessing string) benchFun {
-	return func(b *testing.B) {
-		mp := runtime.GOMAXPROCS(numCPUs)
-		defer runtime.GOMAXPROCS(mp)
-
-		ctx, privateServerEP := newServer(ctx, options.ServerPeers(serverAuth))
-
-		principal := testutil.NewPrincipal(clientBlessing)
-		nctx, _ := v23.WithPrincipal(ctx, principal)
-
-		b.StopTimer()
-		b.ResetTimer()
-		for i := 0; i < b.N; i++ {
-			mctx, cancel := context.WithCancel(nctx)
-			m := fmanager.New(mctx, naming.FixedRoutingID(0xc), nil, 0)
-			b.StartTimer()
-			_, err := m.Dial(nctx, privateServerEP, flowtest.AllowAllPeersAuthorizer{}, 0)
-			if err != nil {
-				ctx.Fatalf("Dial failed: %v", err)
-			}
-			b.StopTimer()
-			cancel()
-			<-m.Closed()
-		}
-	}
-}
-
-// Benchmark for non-streaming RPC.
-func benchmarkRPC(b *testing.B) {
-	ctx, serverEP := newServer(ctx)
-	mp := runtime.GOMAXPROCS(numCPUs)
-	defer runtime.GOMAXPROCS(mp)
-	internal.CallEcho(b, ctx, serverEP.Name(), b.N, payloadSize, benchmark.NewStats(1))
-}
-
-// Benchmark for streaming RPC.
-func benchmarkStreamingRPC(b *testing.B) {
-	ctx, serverEP := newServer(ctx)
-	mp := runtime.GOMAXPROCS(numCPUs)
-	defer runtime.GOMAXPROCS(mp)
-	internal.CallEchoStream(b, ctx, serverEP.Name(), b.N, chunkCnt, payloadSize, benchmark.NewStats(1))
-}
-
-// Benchmark for measuring throughput in streaming RPC.
-func benchmarkStreamingRPCThroughput(b *testing.B) {
-	ctx, serverEP := newServer(ctx)
-	mp := runtime.GOMAXPROCS(numCPUs)
-	defer runtime.GOMAXPROCS(mp)
-	internal.CallEchoStream(b, ctx, serverEP.Name(), 1, b.N, bulkPayloadSize, benchmark.NewStats(1))
-}
-
-func msPerRPC(r testing.BenchmarkResult) float64 {
-	return r.T.Seconds() / float64(r.N) * 1000
-}
-
-func rpcPerSec(r testing.BenchmarkResult) float64 {
-	return float64(r.N) / r.T.Seconds()
-}
-func mbPerSec(r testing.BenchmarkResult) float64 {
-	return (float64(r.Bytes) * float64(r.N) / 1e6) / r.T.Seconds()
-}
-
-func runBenchmarks() {
-	r := testing.Benchmark(benchmarkRPCConnection)
-	fmt.Printf("RPC Connection\t%.2f ms/rpc\n", msPerRPC(r))
-
-	master, err := ibe.SetupBB1()
-	if err != nil {
-		ctx.Fatalf("ibe.SetupBB1 failed: %v", err)
-	}
-	root := bcrypter.NewRoot("root", master)
-	clientBlessing := "root:alice:client"
-
-	// Attach a crypter to the context, and add a blessing private
-	// key to the for 'clientBlesing'.
-	crypter := bcrypter.NewCrypter()
-	cctx := bcrypter.WithCrypter(ctx, crypter)
-	key, err := root.Extract(ctx, clientBlessing)
-	if err != nil {
-		ctx.Fatalf("could not extract private key: %v", err)
-	}
-	if err := crypter.AddKey(cctx, key); err != nil {
-		ctx.Fatalf("could not add key to crypter: %v", err)
-	}
-
-	serverAuthPatterns := [][]security.BlessingPattern{
-		[]security.BlessingPattern{"root:alice"},
-		[]security.BlessingPattern{"root:bob:friend", "root:carol:friend", "root:alice:client"},
-		[]security.BlessingPattern{"root:bob:spouse", "root:bob:enemy", "root:carol:spouse", "root:carol:enemy", "root:alice:client:$"},
-	}
-	for _, serverAuth := range serverAuthPatterns {
-		r = testing.Benchmark(benchmarkPrivateRPCConnection(cctx, serverAuth, clientBlessing))
-		fmt.Printf("Private RPC Connection with server authorization policy %v and client blessing %v \t%.2f ms/rpc\n", serverAuth, clientBlessing, msPerRPC(r))
-	}
-
-	// Create a connection to exclude the setup time from the following benchmarks.
-	ctx, serverEP := newServer(ctx)
-	internal.CallEcho(&testing.B{}, ctx, serverEP.Name(), 1, 0, benchmark.NewStats(1))
-
-	r = testing.Benchmark(benchmarkRPC)
-	fmt.Printf("RPC (echo %vB)\t%.2f ms/rpc (%.2f qps)\n", payloadSize, msPerRPC(r), rpcPerSec(r))
-
-	r = testing.Benchmark(benchmarkStreamingRPC)
-	fmt.Printf("RPC Streaming (echo %vB)\t%.2f ms/rpc\n", payloadSize, msPerRPC(r)/chunkCnt)
-
-	r = testing.Benchmark(benchmarkStreamingRPCThroughput)
-	fmt.Printf("RPC Streaming Throughput (echo %vMB)\t%.2f MB/s\n", bulkPayloadSize/1e6, mbPerSec(r))
-}
-
 func main() {
-	// Set the default benchmark time.
-	flag.Set("test.benchtime", defaultBenchTime.String())
-
-	var shutdown v23.Shutdown
-	ctx, shutdown = test.V23Init()
-	defer shutdown()
-
-	runBenchmarks()
+	realMain()
 }
diff --git a/runtime/internal/rpc/benchmark/simple/main_android.go b/runtime/internal/rpc/benchmark/simple/main_android.go
new file mode 100644
index 0000000..d07fee2
--- /dev/null
+++ b/runtime/internal/rpc/benchmark/simple/main_android.go
@@ -0,0 +1,61 @@
+// 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.
+
+// +build android
+
+// Android "app" to run the RPC benchmarks.
+//
+// Usage: See run-android.sh
+package main
+
+import (
+	"golang.org/x/mobile/app"
+	"golang.org/x/mobile/event/lifecycle"
+	"golang.org/x/mobile/event/paint"
+	"golang.org/x/mobile/gl"
+	"time"
+)
+
+func main() {
+	done := make(chan struct{})
+	go func() {
+		realMain()
+		close(done)
+	}()
+	app.Main(func(a app.App) {
+		var glctx gl.Context
+		ticks := time.Tick(time.Second / 2)
+		black := false
+		for {
+			select {
+			case <-done:
+				done = nil
+				a.Send(paint.Event{})
+			case <-ticks:
+				black = !black
+				a.Send(paint.Event{})
+			case e := <-a.Events():
+				switch e := a.Filter(e).(type) {
+				case lifecycle.Event:
+					glctx, _ = e.DrawContext.(gl.Context)
+				case paint.Event:
+					if glctx == nil {
+						continue
+					}
+					// solid green        success
+					// flashing red/blue: working
+					if done == nil {
+						glctx.ClearColor(0, 1, 0, 1)
+					} else if black {
+						glctx.ClearColor(0, 0, 0, 1)
+					} else {
+						glctx.ClearColor(0, 0, 1, 1)
+					}
+					glctx.Clear(gl.COLOR_BUFFER_BIT)
+					a.Publish()
+				}
+			}
+		}
+	})
+}
diff --git a/runtime/internal/rpc/benchmark/simple/run-android.sh b/runtime/internal/rpc/benchmark/simple/run-android.sh
new file mode 100755
index 0000000..694d152
--- /dev/null
+++ b/runtime/internal/rpc/benchmark/simple/run-android.sh
@@ -0,0 +1,16 @@
+#!/bin/bash
+# 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.
+
+set -e
+set -x
+
+[ -z "${GOPATH}" ] && echo "Must set GOPATH, for example: export GOPATH=\$HOME/go so that gomobile can be installed there" && exit 1
+go get golang.org/x/mobile/cmd/gomobile
+GOMOBILE=${GOPATH}/bin/gomobile
+jiri run ${GOMOBILE} build v.io/x/ref/runtime/internal/rpc/benchmark/simple
+adb install -r ./simple.apk
+echo "Start the v23rpc app on the phone connected to adb"
+adb logcat *:S GoLog:*
+
diff --git a/runtime/internal/rpc/protocols/tcp/init.go b/runtime/internal/rpc/protocols/tcp/init.go
deleted file mode 100644
index a78117f..0000000
--- a/runtime/internal/rpc/protocols/tcp/init.go
+++ /dev/null
@@ -1,76 +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 tcp
-
-import (
-	"fmt"
-	"net"
-	"time"
-
-	"v.io/v23/context"
-	"v.io/v23/rpc"
-
-	"v.io/x/ref/runtime/internal/lib/tcputil"
-)
-
-func init() {
-	rpc.RegisterProtocol("tcp", tcpDial, tcpResolve, tcpListen, "tcp4", "tcp6")
-	rpc.RegisterProtocol("tcp4", tcpDial, tcpResolve, tcpListen)
-	rpc.RegisterProtocol("tcp6", tcpDial, tcpResolve, tcpListen)
-}
-
-func tcpDial(ctx *context.T, network, address string, timeout time.Duration) (net.Conn, error) {
-	conn, err := net.DialTimeout(network, address, timeout)
-	if err != nil {
-		return nil, err
-	}
-	if err := tcputil.EnableTCPKeepAlive(conn); err != nil {
-		return nil, err
-	}
-	return conn, nil
-}
-
-// tcpResolve performs a DNS resolution on the provided network and address.
-func tcpResolve(ctx *context.T, network, address string) (string, string, error) {
-	tcpAddr, err := net.ResolveTCPAddr(network, address)
-	if err != nil {
-		return "", "", err
-	}
-	return tcpAddr.Network(), tcpAddr.String(), nil
-}
-
-// tcpListen returns a listener that sets KeepAlive on all accepted connections.
-func tcpListen(ctx *context.T, network, laddr string) (net.Listener, error) {
-	ln, err := net.Listen(network, laddr)
-	if err != nil {
-		return nil, err
-	}
-	return &tcpListener{ln}, nil
-}
-
-// tcpListener is a wrapper around net.Listener that sets KeepAlive on all
-// accepted connections.
-type tcpListener struct {
-	netLn net.Listener
-}
-
-func (ln *tcpListener) Accept() (net.Conn, error) {
-	conn, err := ln.netLn.Accept()
-	if err != nil {
-		return nil, err
-	}
-	if err := tcputil.EnableTCPKeepAlive(conn); err != nil {
-		return nil, fmt.Errorf("Failed to enable TCP keep alive: %v", err)
-	}
-	return conn, nil
-}
-
-func (ln *tcpListener) Close() error {
-	return ln.netLn.Close()
-}
-
-func (ln *tcpListener) Addr() net.Addr {
-	return ln.netLn.Addr()
-}
diff --git a/runtime/internal/rpc/protocols/ws/init.go b/runtime/internal/rpc/protocols/ws/init.go
deleted file mode 100644
index 5aac575..0000000
--- a/runtime/internal/rpc/protocols/ws/init.go
+++ /dev/null
@@ -1,18 +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 websocket
-
-import (
-	"v.io/v23/rpc"
-
-	"v.io/x/ref/runtime/internal/lib/websocket"
-)
-
-func init() {
-	// ws, ws4, ws6 represent websocket protocol instances.
-	rpc.RegisterProtocol("ws", websocket.Dial, websocket.Resolve, websocket.Listener, "ws4", "ws6")
-	rpc.RegisterProtocol("ws4", websocket.Dial, websocket.Resolve, websocket.Listener)
-	rpc.RegisterProtocol("ws6", websocket.Dial, websocket.Resolve, websocket.Listener)
-}
diff --git a/runtime/internal/rpc/protocols/wsh/init.go b/runtime/internal/rpc/protocols/wsh/init.go
deleted file mode 100644
index 26cc680..0000000
--- a/runtime/internal/rpc/protocols/wsh/init.go
+++ /dev/null
@@ -1,19 +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 wsh registers the websocket 'hybrid' protocol.
-// We prefer to use tcp whenever we can to avoid the overhead of websockets.
-package wsh
-
-import (
-	"v.io/v23/rpc"
-
-	"v.io/x/ref/runtime/internal/lib/websocket"
-)
-
-func init() {
-	rpc.RegisterProtocol("wsh", websocket.HybridDial, websocket.HybridResolve, websocket.HybridListener, "tcp4", "tcp6", "ws4", "ws6")
-	rpc.RegisterProtocol("wsh4", websocket.HybridDial, websocket.HybridResolve, websocket.HybridListener, "tcp4", "ws4")
-	rpc.RegisterProtocol("wsh6", websocket.HybridDial, websocket.HybridResolve, websocket.HybridListener, "tcp6", "ws6")
-}
diff --git a/runtime/internal/rpc/protocols/wsh_nacl/init.go b/runtime/internal/rpc/protocols/wsh_nacl/init.go
deleted file mode 100644
index 276a567..0000000
--- a/runtime/internal/rpc/protocols/wsh_nacl/init.go
+++ /dev/null
@@ -1,21 +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 wsh_nacl registers the websocket 'hybrid' protocol for nacl
-// architectures.
-package wsh_nacl
-
-import (
-	"v.io/v23/rpc"
-
-	"v.io/x/ref/runtime/internal/lib/websocket"
-)
-
-func init() {
-	// We limit wsh to ws since in general nacl does not allow direct access
-	// to TCP/UDP networking.
-	rpc.RegisterProtocol("wsh", websocket.Dial, websocket.Resolve, websocket.Listener, "ws4", "ws6")
-	rpc.RegisterProtocol("wsh4", websocket.Dial, websocket.Resolve, websocket.Listener, "ws4")
-	rpc.RegisterProtocol("wsh6", websocket.Dial, websocket.Resolve, websocket.Listener, "ws6")
-}
diff --git a/runtime/internal/rpc/test/cancel_test.go b/runtime/internal/rpc/test/cancel_test.go
index 69edbcb..9a97291 100644
--- a/runtime/internal/rpc/test/cancel_test.go
+++ b/runtime/internal/rpc/test/cancel_test.go
@@ -299,21 +299,6 @@
 }
 
 func registerDisProtocol(wrap string, conns chan disconnect) {
-	dial, resolve, listen, protonames := rpc.RegisteredProtocol(wrap)
-	rpc.RegisterProtocol("dis", func(ctx *context.T, p, a string, t time.Duration) (net.Conn, error) {
-		conn, err := dial(ctx, protonames[0], a, t)
-		if err == nil {
-			dc := &disConn{Conn: conn}
-			conns <- dc
-			conn = dc
-		}
-		return conn, err
-	}, func(ctx *context.T, protocol, address string) (string, string, error) {
-		_, a, err := resolve(ctx, protonames[0], address)
-		return "dis", a, err
-	}, func(ctx *context.T, protocol, address string) (net.Listener, error) {
-		return listen(ctx, protonames[0], address)
-	})
 	// We only register this flow protocol to make the test work in xclients mode.
 	protocol, _ := flow.RegisteredProtocol("tcp")
 	flow.RegisterProtocol("dis", &flowdis{base: protocol})
diff --git a/runtime/internal/rpc/test/full_test.go b/runtime/internal/rpc/test/full_test.go
index 855c601..85d9c47 100644
--- a/runtime/internal/rpc/test/full_test.go
+++ b/runtime/internal/rpc/test/full_test.go
@@ -19,11 +19,13 @@
 
 	"v.io/v23"
 	"v.io/v23/context"
+	"v.io/v23/flow"
 	"v.io/v23/flow/message"
 	"v.io/v23/i18n"
 	"v.io/v23/naming"
 	"v.io/v23/options"
 	"v.io/v23/rpc"
+	"v.io/v23/rpc/version"
 	"v.io/v23/security"
 	"v.io/v23/security/access"
 	"v.io/v23/vdl"
@@ -36,6 +38,7 @@
 	"v.io/x/ref/runtime/internal/lib/tcputil"
 	inaming "v.io/x/ref/runtime/internal/naming"
 	"v.io/x/ref/runtime/internal/rpc/stream/crypto"
+	"v.io/x/ref/runtime/protocols/debug"
 	"v.io/x/ref/test"
 	"v.io/x/ref/test/testutil"
 )
@@ -1262,3 +1265,48 @@
 		time.Sleep(50 * time.Millisecond)
 	}
 }
+
+type manInMiddleConn struct {
+	flow.Conn
+	ctx *context.T
+}
+
+// manInMiddleConn changes the versions in any setup message sent over the wire.
+func (c *manInMiddleConn) ReadMsg() ([]byte, error) {
+	b, err := c.Conn.ReadMsg()
+	if len(b) > 0 {
+		m, _ := message.Read(c.ctx, b)
+		switch msg := m.(type) {
+		case *message.Setup:
+			// The malicious man in the middle changes the max version to a bad version.
+			msg.Versions = version.RPCVersionRange{Min: version.RPCVersion10, Max: 100}
+			b, err = message.Append(c.ctx, msg, nil)
+		}
+	}
+	return b, err
+}
+
+func TestSetupAttack(t *testing.T) {
+	ctx, shutdown := test.V23InitWithMounttable()
+	defer shutdown()
+
+	ctx = debug.WithFilter(ctx, func(c flow.Conn) flow.Conn {
+		return &manInMiddleConn{c, ctx}
+	})
+
+	ctx = v23.WithListenSpec(ctx, rpc.ListenSpec{
+		Addrs: rpc.ListenAddrs{{Protocol: "debug", Address: "tcp/127.0.0.1:0"}},
+	})
+	ctx, cancel := context.WithCancel(ctx)
+	_, server, err := v23.WithNewServer(ctx, "mountpoint/server", &testServer{}, nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer func() { <-server.Closed() }()
+	defer cancel()
+	// Connection establishment should fail during the RPC because the channel binding
+	// check should fail since the Setup message has been altered.
+	if err := v23.GetClient(ctx).Call(ctx, "mountpoint/server", "Closure", nil, nil, options.NoRetry{}); err == nil {
+		t.Errorf("expected error but got <nil>")
+	}
+}
diff --git a/runtime/internal/rpc/version/version.go b/runtime/internal/rpc/version/version.go
index f08e1ef..25d02ef 100644
--- a/runtime/internal/rpc/version/version.go
+++ b/runtime/internal/rpc/version/version.go
@@ -8,16 +8,10 @@
 	"fmt"
 
 	"v.io/v23/rpc/version"
-	"v.io/v23/verror"
 	"v.io/x/lib/metadata"
 )
 
-// Range represents a range of RPC versions.
-type Range struct {
-	Min, Max version.RPCVersion
-}
-
-// SupportedRange represents the range of protocol verions supported by this
+// Supported represents the range of protocol verions supported by this
 // implementation.
 //
 // Max is incremented whenever we make a protocol change that's not both forward
@@ -25,77 +19,9 @@
 //
 // Min is incremented whenever we want to remove support for old protocol
 // versions.
-var SupportedRange = &Range{Min: version.RPCVersion10, Max: version.RPCVersion12}
-var Supported = version.RPCVersionRange{Min: version.RPCVersion10, Max: version.RPCVersion13}
+var Supported = version.RPCVersionRange{Min: version.RPCVersion10, Max: version.RPCVersion14}
 
 func init() {
-	metadata.Insert("v23.RPCVersionMax", fmt.Sprint(SupportedRange.Max))
-	metadata.Insert("v23.RPCVersionMin", fmt.Sprint(SupportedRange.Min))
-}
-
-const pkgPath = "v.io/x/ref/runtime/internal/rpc/version"
-
-func reg(id, msg string) verror.IDAction {
-	return verror.Register(verror.ID(pkgPath+id), verror.NoRetry, msg)
-}
-
-var (
-	// These errors are intended to be used as arguments to higher
-	// level errors and hence {1}{2} is omitted from their format
-	// strings to avoid repeating these n-times in the final error
-	// message visible to the user.
-	ErrNoCompatibleVersion = reg(".errNoCompatibleVersionErr", "no compatible RPC version available{:3} not in range {4}..{5}")
-	ErrUnknownVersion      = reg(".errUnknownVersionErr", "there was not enough information to determine a version")
-	ErrDeprecatedVersion   = reg(".errDeprecatedVersionError", "some of the provided version information is deprecated")
-)
-
-// IsVersionError returns true if err is a versioning related error.
-func IsVersionError(err error) bool {
-	id := verror.ErrorID(err)
-	return id == ErrNoCompatibleVersion.ID || id == ErrUnknownVersion.ID || id == ErrDeprecatedVersion.ID
-}
-
-// intersectRanges finds the intersection between ranges
-// supported by two endpoints.  We make an assumption here that if one
-// of the endpoints has an UnknownVersion we assume it has the same
-// extent as the other endpoint. If both endpoints have Unknown for a
-// version number, an error is produced.
-// For example:
-//   a == (2, 4) and b == (Unknown, Unknown), intersect(a,b) == (2, 4)
-//   a == (2, Unknown) and b == (3, 4), intersect(a,b) == (3, 4)
-func intersectRanges(amin, amax, bmin, bmax version.RPCVersion) (min, max version.RPCVersion, err error) {
-	// TODO(mattr): this may be incorrect.  Ensure that when we talk to a server who
-	// advertises (5,8) and we support (5, 9) but v5 EPs (so we may get d, d here) that
-	// we use v8 and don't send setupVC.
-	d := version.DeprecatedRPCVersion
-	if amin == d || amax == d || bmin == d || bmax == d {
-		return d, d, verror.New(ErrDeprecatedVersion, nil)
-	}
-
-	u := version.UnknownRPCVersion
-
-	min = amin
-	if min == u || (bmin != u && bmin > min) {
-		min = bmin
-	}
-	max = amax
-	if max == u || (bmax != u && bmax < max) {
-		max = bmax
-	}
-
-	if min == u || max == u {
-		err = verror.New(ErrUnknownVersion, nil)
-	} else if min > max {
-		err = verror.New(ErrNoCompatibleVersion, nil, u, min, max)
-	}
-	return
-}
-
-func (r1 *Range) Intersect(r2 *Range) (*Range, error) {
-	min, max, err := intersectRanges(r1.Min, r1.Max, r2.Min, r2.Max)
-	if err != nil {
-		return nil, err
-	}
-	r := &Range{Min: min, Max: max}
-	return r, nil
+	metadata.Insert("v23.RPCVersionMax", fmt.Sprint(Supported.Max))
+	metadata.Insert("v23.RPCVersionMin", fmt.Sprint(Supported.Min))
 }
diff --git a/runtime/internal/rpc/version/version_test.go b/runtime/internal/rpc/version/version_test.go
deleted file mode 100644
index 652baf8..0000000
--- a/runtime/internal/rpc/version/version_test.go
+++ /dev/null
@@ -1,51 +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 version
-
-import (
-	"testing"
-
-	"v.io/v23/rpc/version"
-	"v.io/v23/verror"
-)
-
-func TestIntersect(t *testing.T) {
-	type testCase struct {
-		localMin, localMax   version.RPCVersion
-		remoteMin, remoteMax version.RPCVersion
-		expected             *Range
-		expectedErr          verror.IDAction
-	}
-	tests := []testCase{
-		{0, 0, 0, 0, nil, ErrUnknownVersion},
-		{0, 2, 3, 4, nil, ErrNoCompatibleVersion},
-		{3, 4, 0, 2, nil, ErrNoCompatibleVersion},
-		{0, 6, 6, 7, nil, ErrNoCompatibleVersion},
-		{0, 3, 3, 5, &Range{3, 3}, verror.ErrUnknown},
-		{0, 3, 2, 4, &Range{2, 3}, verror.ErrUnknown},
-		{2, 4, 2, 4, &Range{2, 4}, verror.ErrUnknown},
-		{4, 4, 4, 4, &Range{4, 4}, verror.ErrUnknown},
-	}
-	for _, tc := range tests {
-		local := &Range{
-			Min: tc.localMin,
-			Max: tc.localMax,
-		}
-		remote := &Range{
-			Min: tc.remoteMin,
-			Max: tc.remoteMax,
-		}
-		intersection, err := local.Intersect(remote)
-
-		if (tc.expected != nil && *tc.expected != *intersection) ||
-			(err != nil && verror.ErrorID(err) != tc.expectedErr.ID) {
-			t.Errorf("Unexpected result for local: %v, remote: %v.  Got (%v, %v) wanted (%v, %v)",
-				local, remote, intersection, err,
-				tc.expected, tc.expectedErr)
-		}
-		if err != nil {
-			t.Logf("%s", err)
-		}
-	}
-}
diff --git a/runtime/internal/lib/framer/errors.vdl b/runtime/protocols/lib/framer/errors.vdl
similarity index 100%
rename from runtime/internal/lib/framer/errors.vdl
rename to runtime/protocols/lib/framer/errors.vdl
diff --git a/runtime/internal/lib/framer/errors.vdl.go b/runtime/protocols/lib/framer/errors.vdl.go
similarity index 79%
rename from runtime/internal/lib/framer/errors.vdl.go
rename to runtime/protocols/lib/framer/errors.vdl.go
index 2da6252..1c077bf 100644
--- a/runtime/internal/lib/framer/errors.vdl.go
+++ b/runtime/protocols/lib/framer/errors.vdl.go
@@ -15,7 +15,7 @@
 )
 
 var (
-	ErrLargerThan3ByteUInt = verror.Register("v.io/x/ref/runtime/internal/lib/framer.LargerThan3ByteUInt", verror.NoRetry, "{1:}{2:} integer too large to represent in 3 bytes")
+	ErrLargerThan3ByteUInt = verror.Register("v.io/x/ref/runtime/protocols/lib/framer.LargerThan3ByteUInt", verror.NoRetry, "{1:}{2:} integer too large to represent in 3 bytes")
 )
 
 func init() {
diff --git a/runtime/internal/lib/framer/framer.go b/runtime/protocols/lib/framer/framer.go
similarity index 100%
rename from runtime/internal/lib/framer/framer.go
rename to runtime/protocols/lib/framer/framer.go
diff --git a/runtime/internal/lib/framer/framer_test.go b/runtime/protocols/lib/framer/framer_test.go
similarity index 100%
rename from runtime/internal/lib/framer/framer_test.go
rename to runtime/protocols/lib/framer/framer_test.go
diff --git a/services/cluster/vkube/vkube_v23_test.go b/services/cluster/vkube/vkube_v23_test.go
index 8a1a0ae..9dec1fb 100644
--- a/services/cluster/vkube/vkube_v23_test.go
+++ b/services/cluster/vkube/vkube_v23_test.go
@@ -5,19 +5,19 @@
 package main_test
 
 import (
+	"bytes"
 	"flag"
 	"fmt"
-	"io"
 	"io/ioutil"
 	"math/rand"
 	"os"
 	"os/exec"
 	"path/filepath"
 	"strings"
+	"sync"
 	"testing"
 	"text/template"
 
-	"v.io/x/lib/textutil"
 	"v.io/x/ref/lib/v23test"
 	"v.io/x/ref/test/testutil"
 )
@@ -65,16 +65,16 @@
 				// Note, creds do not affect non-Vanadium commands.
 				c := sh.Cmd(name, args...).WithCredentials(creds)
 				c.ExitErrorIsOk = true
-				// Wrap os.Stdout in a MultiWriter so that PrefixLineWriter.Close
-				// doesn't close it.
-				c.AddStdoutWriter(textutil.PrefixLineWriter(io.MultiWriter(os.Stdout), filepath.Base(name)+"> "))
-				stdout := c.Stdout()
+				w := &writer{name: filepath.Base(name)}
+				c.AddStdoutWriter(w)
+				c.AddStderrWriter(w)
+				c.Run()
 				if expectSuccess && c.Err != nil {
 					t.Error(testutil.FormatLogLine(2, "Unexpected failure: %s %s :%v", name, strings.Join(args, " "), c.Err))
 				} else if !expectSuccess && c.Err == nil {
 					t.Error(testutil.FormatLogLine(2, "Unexpected success %d: %s %s", name, strings.Join(args, " ")))
 				}
-				return stdout
+				return w.output()
 			}
 		}
 		gsutil      = cmd("gsutil", true)
@@ -145,7 +145,13 @@
 
 	// Find the pod running tunneld, get the server's addr from its stdout.
 	podName := kubectlOK("get", "pod", "-l", "application=tunneld", "--template={{range .items}}{{.metadata.name}}{{end}}")
-	addr := strings.TrimPrefix(strings.TrimSpace(kubectlOK("logs", podName, "-c", "tunneld")), "NAME=")
+	var addr string
+	for _, log := range strings.Split(kubectlOK("logs", podName, "-c", "tunneld"), "\n") {
+		if strings.HasPrefix(log, "NAME=") {
+			addr = strings.TrimPrefix(log, "NAME=")
+			break
+		}
+	}
 	if got, expected := vshOK(addr, "echo", "hello", "world"), "hello world\n"; got != expected {
 		t.Errorf("Unexpected output. Got %q, expected %q", got, expected)
 	}
@@ -255,6 +261,51 @@
 }`)).Execute(f, params)
 }
 
+// writer is an io.Writer that sends everything to stdout, each line prefixed
+// with "name> ".
+type writer struct {
+	sync.Mutex
+	name string
+	line bytes.Buffer
+	out  bytes.Buffer
+}
+
+func (w *writer) Write(p []byte) (n int, err error) {
+	w.Lock()
+	defer w.Unlock()
+	n = len(p)
+	w.out.Write(p)
+	for len(p) > 0 {
+		if w.line.Len() == 0 {
+			fmt.Fprintf(&w.line, "%s> ", w.name)
+		}
+		if off := bytes.IndexByte(p, '\n'); off != -1 {
+			off += 1
+			w.line.Write(p[:off])
+			w.line.WriteTo(os.Stdout)
+			p = p[off:]
+			continue
+		}
+		w.line.Write(p)
+		break
+	}
+	return
+}
+
+func (w *writer) Close() error {
+	return nil
+}
+
+func (w *writer) output() string {
+	w.Lock()
+	defer w.Unlock()
+	if w.line.Len() != 0 {
+		w.line.WriteString(" [no \\n at EOL]\n")
+		w.line.WriteTo(os.Stdout)
+	}
+	return w.out.String()
+}
+
 func TestMain(m *testing.M) {
 	os.Exit(v23test.Run(m.Run))
 }
diff --git a/services/device/deviced/internal/impl/restart_policy_test.go b/services/device/deviced/internal/impl/restart_policy_test.go
index 046377e..0ccf7ad 100644
--- a/services/device/deviced/internal/impl/restart_policy_test.go
+++ b/services/device/deviced/internal/impl/restart_policy_test.go
@@ -23,6 +23,8 @@
 		decision bool
 	}
 
+	testNow := time.Now()
+
 	testVectors := []tV{
 		// -1 means always restart.
 		{
@@ -73,11 +75,11 @@
 			},
 			&instanceInfo{
 				Restarts:           1,
-				RestartWindowBegan: time.Date(2015, time.December, 25, 12, 0, 0, 0, time.UTC),
+				RestartWindowBegan: testNow,
 			},
 			&instanceInfo{
 				Restarts:           1,
-				RestartWindowBegan: time.Date(2015, time.December, 25, 12, 0, 0, 0, time.UTC),
+				RestartWindowBegan: testNow,
 			},
 			false,
 		},
diff --git a/services/device/mgmt_v23_test.go b/services/device/mgmt_v23_test.go
index ef6e4e4..9ffb6b7 100644
--- a/services/device/mgmt_v23_test.go
+++ b/services/device/mgmt_v23_test.go
@@ -50,6 +50,7 @@
 	"testing"
 	"time"
 
+	"v.io/x/lib/gosh"
 	"v.io/x/ref"
 	"v.io/x/ref/lib/v23test"
 	_ "v.io/x/ref/runtime/factories/generic"
@@ -281,7 +282,7 @@
 			// if the name doesn't exist.
 			c := withArgs(namespaceBin, "resolve", name)
 			c.ExitErrorIsOk = true
-			c.AddStderrWriter(os.Stderr)
+			c.AddStderrWriter(gosh.NopWriteCloser(os.Stderr))
 			if res = tr(c.Stdout()); len(res) > 0 {
 				return nil
 			}
@@ -473,7 +474,7 @@
 			// if the name doesn't exist.
 			c := withArgs(namespaceBin, "resolve", name)
 			c.ExitErrorIsOk = true
-			c.AddStderrWriter(os.Stderr)
+			c.AddStderrWriter(gosh.NopWriteCloser(os.Stderr))
 			switch res = tr(c.Stdout()); {
 			case res == "":
 				return testutil.TryAgain(errors.New("resolve returned nothing"))
@@ -535,7 +536,7 @@
 			// if the name doesn't exist.
 			c := withArgs(namespaceBin, "resolve", name)
 			c.ExitErrorIsOk = true
-			c.AddStderrWriter(os.Stderr)
+			c.AddStderrWriter(gosh.NopWriteCloser(os.Stderr))
 			if res = tr(c.Stdout()); len(res) == 0 {
 				return nil
 			}
diff --git a/services/profile/profiled/impl_test.go b/services/profile/profiled/impl_test.go
index c7d00e3..6150dfb 100644
--- a/services/profile/profiled/impl_test.go
+++ b/services/profile/profiled/impl_test.go
@@ -30,8 +30,6 @@
 	}
 )
 
-//go:generate jiri test generate
-
 // TestInterface tests that the implementation correctly implements
 // the Profile interface.
 func TestInterface(t *testing.T) {
diff --git a/services/syncbase/syncbased/main.go b/services/syncbase/syncbased/main.go
index d78abfa..06d4cf6 100644
--- a/services/syncbase/syncbased/main.go
+++ b/services/syncbase/syncbased/main.go
@@ -67,8 +67,8 @@
 		vlog.Info("Mounted at: ", *name)
 	}
 	if eps := s.Status().Endpoints; len(eps) > 0 {
-		// Our v23tests will wait for this to be printed before trying to access
-		// the service.
+		// Integration tests wait for this to be printed before trying to access the
+		// service.
 		fmt.Printf("ENDPOINT=%s\n", eps[0].Name())
 	}
 
diff --git a/services/syncbase/testutil/util.go b/services/syncbase/testutil/util.go
index b7f3594..7866602 100644
--- a/services/syncbase/testutil/util.go
+++ b/services/syncbase/testutil/util.go
@@ -70,6 +70,7 @@
 	return
 }
 
+// TODO(sadovsky): Switch unit tests to v23test.Shell, then delete this.
 func SetupOrDieCustom(clientSuffix, serverSuffix string, perms access.Permissions) (ctx, clientCtx *context.T, serverName string, rootp security.Principal, cleanup func()) {
 	ctx, shutdown := test.V23Init()
 	rootp = tsecurity.NewPrincipal("root")
@@ -252,6 +253,7 @@
 // Creates a new context object with blessing "root:<suffix>", configured to
 // present this blessing when acting as a server as well as when acting as a
 // client and talking to a server that presents a blessing rooted at "root".
+// TODO(sadovsky): Switch unit tests to v23test.Shell, then delete this.
 func NewCtx(ctx *context.T, rootp security.Principal, suffix string) *context.T {
 	// Principal for the new context.
 	p := tsecurity.NewPrincipal(suffix)
diff --git a/services/syncbase/testutil/v23util.go b/services/syncbase/testutil/v23util.go
deleted file mode 100644
index bd35c65..0000000
--- a/services/syncbase/testutil/v23util.go
+++ /dev/null
@@ -1,107 +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 testutil
-
-import (
-	"bytes"
-	"io/ioutil"
-	"log"
-	"os"
-	"runtime/debug"
-	"syscall"
-	"time"
-
-	"v.io/x/ref/test/modules"
-	"v.io/x/ref/test/v23tests"
-)
-
-// StartSyncbased starts a syncbased process, intended to be accessed from an
-// integration test (run using --v23.tests). The returned cleanup function
-// should be called once the syncbased process is no longer needed.  See
-// StartKillableSyncbased for killing the syncbase with an arbitrary signal.
-func StartSyncbased(t *v23tests.T, creds *modules.CustomCredentials, name, rootDir, permsLiteral string) (cleanup func()) {
-	f := StartKillableSyncbased(t, creds, name, rootDir, permsLiteral)
-	return func() {
-		f(syscall.SIGINT)
-	}
-}
-
-// StartKillableSyncbased starts a syncbased process, intended to be accessed
-// from an integration test (run using --v23.tests). The returned cleanup
-// function should be called once the syncbased process is no longer needed.
-func StartKillableSyncbased(t *v23tests.T, creds *modules.CustomCredentials,
-	name, rootDir, permsLiteral string) (cleanup func(signal syscall.Signal)) {
-
-	syncbased := t.BuildV23Pkg("v.io/x/ref/services/syncbase/syncbased")
-	// Create root dir for the store.
-	rmRootDir := false
-	if rootDir == "" {
-		var err error
-		rootDir, err = ioutil.TempDir("", "syncbase_leveldb")
-		if err != nil {
-			V23Fatalf(t, "can't create temp dir: %v", err)
-		}
-		rmRootDir = true
-	}
-
-	// Start syncbased. Run with --dev to enable development mode methods such as
-	// DevModeUpdateVClock.
-	invocation := syncbased.WithStartOpts(syncbased.StartOpts().WithCustomCredentials(creds).WithSessions(t, 5*time.Second)).Start(
-		//"--v=5",
-		//"--vpath=vsync*=5",
-		//"--alsologtostderr=true",
-		"--v23.tcp.address=127.0.0.1:0",
-		"--v23.permissions.literal", permsLiteral,
-		"--name="+name,
-		"--root-dir="+rootDir,
-		"--dev")
-
-	cleanup = func(signal syscall.Signal) {
-		go invocation.Kill(signal)
-		stdout, stderr := bytes.NewBuffer(nil), bytes.NewBuffer(nil)
-		if err := invocation.Shutdown(stdout, stderr); err != nil {
-			log.Printf("syncbased terminated with an error: %v\nstdout: %v\nstderr: %v\n", err, stdout, stderr)
-		} else {
-			// To debug sync (for example), uncomment this line as well as the logging
-			// flags in the invocation above.
-			//log.Printf("syncbased terminated cleanly\nstdout: %v\nstderr: %v\n", stdout, stderr)
-		}
-		if rmRootDir {
-			if err := os.RemoveAll(rootDir); err != nil {
-				V23Fatalf(t, "failed to remove dir %v: %v", rootDir, err)
-			}
-		}
-	}
-
-	// Wait for syncbased to start.  If syncbased fails to start, this will time
-	// out after 5 seconds and return "".
-	endpoint := invocation.ExpectVar("ENDPOINT")
-	if endpoint == "" {
-		cleanup(syscall.SIGKILL)
-		t.Fatalf("syncbased failed to start")
-	}
-	return cleanup
-}
-
-// RunClient runs the given program and waits for it to terminate.
-// TODO(sadovsky): This function will soon go away. Do not depend on it.
-func RunClient(t *v23tests.T, creds *modules.CustomCredentials, program modules.Program, args ...string) {
-	client, err := t.Shell().StartWithOpts(
-		t.Shell().DefaultStartOpts().WithCustomCredentials(creds),
-		nil,
-		program, args...)
-	if err != nil {
-		V23Fatalf(t, "unable to start the client: %v", err)
-	}
-	stdout, stderr := bytes.NewBuffer(nil), bytes.NewBuffer(nil)
-	if err := client.Shutdown(stdout, stderr); err != nil {
-		V23Fatalf(t, "client failed: %v\nstdout: %v\nstderr: %v\n", err, stdout, stderr)
-	}
-}
-
-func V23Fatalf(t *v23tests.T, format string, args ...interface{}) {
-	debug.PrintStack()
-	t.Fatalf(format, args...)
-}
diff --git a/services/syncbase/vsync/watcher.go b/services/syncbase/vsync/watcher.go
index cf15573..f1aeb3e 100644
--- a/services/syncbase/vsync/watcher.go
+++ b/services/syncbase/vsync/watcher.go
@@ -121,7 +121,12 @@
 	// Get a batch of watch log entries, if any, after this resume marker.
 	logs, nextResmark, err := watchable.ReadBatchFromLog(st, resMark)
 	if err != nil {
-		vlog.Fatalf("sync: processDatabase: %s, %s: cannot get watch log batch: %v", appName, dbName, verror.DebugString(err))
+		// An error here (scan stream cancelled) is possible when the
+		// watcher is in the middle of processing a database and the
+		// app/db is detroyed. Hence, we just ignore this database and
+		// proceed.
+		vlog.Errorf("sync: processDatabase: %s, %s: cannot get watch log batch: %v", appName, dbName, verror.DebugString(err))
+		return false
 	}
 	if logs != nil {
 		s.processWatchLogBatch(ctx, appName, dbName, st, logs, nextResmark)
diff --git a/services/wspr/browsprd/main_nacl.go b/services/wspr/browsprd/main_nacl.go
index 81c7865..b4b705b 100644
--- a/services/wspr/browsprd/main_nacl.go
+++ b/services/wspr/browsprd/main_nacl.go
@@ -22,7 +22,6 @@
 	"v.io/x/ref/internal/logger"
 	vsecurity "v.io/x/ref/lib/security"
 	_ "v.io/x/ref/runtime/factories/chrome"
-	"v.io/x/ref/runtime/internal/lib/websocket"
 	"v.io/x/ref/runtime/internal/lib/xwebsocket"
 	"v.io/x/ref/services/wspr/internal/app"
 	"v.io/x/ref/services/wspr/internal/browspr"
@@ -63,7 +62,6 @@
 	browsprInst.initFileSystem()
 
 	// Give the websocket interface the ppapi instance.
-	websocket.PpapiInstance = inst
 	xwebsocket.PpapiInstance = inst
 
 	// Set up the channel and register start rpc handler.
diff --git a/test/doc.go b/test/doc.go
index 35bee24..f9a5d7f 100644
--- a/test/doc.go
+++ b/test/doc.go
@@ -14,31 +14,18 @@
 //    ...
 // }
 //
-// This package also defines flags for enabling and controlling
-// the v23 integration tests in package v23tests:
-// --v23.tests - run the integration tests
-// --v23.tests.shell-on-fail - drop into a debug shell if the test fails.
+// This package also defines flags for enabling and controlling the Vanadium
+// integration tests in package v.io/x/ref/lib/v23test:
+//   --v23.tests - run the integration tests
+//   --v23.tests.shell-on-fail - drop into a debug shell if the test fails
 //
 // Typical usage is:
 // $ jiri go test . --v23.tests
 //
-// Note that, like all flags not recognised by the go testing package, the
-// v23.tests flags must follow the package spec.
+// Note that, like all flags not recognized by the go testing package, the
+// --v23.tests flags must follow the package spec.
 //
-// The sub-directories of this package provide either functionality that
-// can be used within traditional go tests, or support for the v23 integration
-// test framework. The jiri command is able to generate boilerplate code
-// to support these tests. In summary, 'jiri test generate' will generate
-// go files to be checked in that include appropriate TestMain functions,
-// registration calls for modules commands and wrapper functions for v23test
-// tests. More detailed documentation is available via:
-//
-// $ jiri test generate --help
-//
-// Vanadium tests often need to run subprocesses to provide either common
-// services that they depend (e.g. a mount table) and/or services that are
-// specific to the tests. The modules and v23tests subdirectories contain
-// packages that simplify this process.
+// Subdirectories provide utilities for unit and integration tests.
 //
 // The subdirectories are:
 // benchmark  - support for writing benchmarks.
@@ -46,10 +33,9 @@
 // security   - security related utility functions used in tests.
 // timekeeper - an implementation of the timekeeper interface for use within
 //              tests.
-// modules    - support for running subprocesses using a single binary
-// v23tests   - support for integration tests, including compiling and running
-//              arbirtrary go code and pre-existing system commands.
-// expect     - support for testing the contents of input streams. The methods
-//              provided are embedded in the types used by modules and v23tests
-//              so this package is generally not used directly.
+// modules    - deprecated, use v.io/x/ref/lib/v23test or v.io/x/lib/gosh to run
+//              and manage subprocesses.
+// expect     - support for testing the contents of of an input stream (an
+//              io.Reader). v23test.Cmd contains an expect.Session, so this
+//              package is generally not used directly.
 package test
diff --git a/test/hello/hello_v23_test.go b/test/hello/hello_v23_test.go
index 0409586..dd65494 100644
--- a/test/hello/hello_v23_test.go
+++ b/test/hello/hello_v23_test.go
@@ -5,43 +5,34 @@
 package hello_test
 
 import (
-	"fmt"
 	"os"
-	"time"
+	"testing"
 
 	"v.io/x/ref"
 	"v.io/x/ref/lib/security"
+	"v.io/x/ref/lib/v23test"
 	_ "v.io/x/ref/runtime/factories/generic"
-	"v.io/x/ref/test/modules"
 	"v.io/x/ref/test/testutil"
-	"v.io/x/ref/test/v23tests"
 )
 
-//go:generate jiri test generate
-
 func init() {
 	ref.EnvClearCredentials()
 }
 
-var opts = modules.StartOpts{
-	StartTimeout:    20 * time.Second,
-	ShutdownTimeout: 20 * time.Second,
-	ExpectTimeout:   20 * time.Second,
-	ExecProtocol:    false,
-	External:        true,
+func withCreds(dir string, c *v23test.Cmd) *v23test.Cmd {
+	c.Vars[ref.EnvCredentials] = dir
+	return c
 }
 
-// setupCredentials makes a bunch of credentials directories.
-// Note that I do this myself instead of allowing the test framework
-// to do it because I really want to use the agentd binary, not
-// the agent that is locally hosted inside v23Tests.T.
-// This is important for regression tests where we want to test against
-// old agent binaries.
-func setupCredentials(i *v23tests.T, names ...string) (map[string]string, error) {
+// setupCreds makes a bunch of credentials directories.
+// We do this ourselves instead of using v23test's credentials APIs because we
+// want to use the actual agentd binary, so that for regression tests we can
+// test against old agents.
+func setupCreds(sh *v23test.Shell, names ...string) (map[string]string, error) {
 	idp := testutil.NewIDProvider("root")
 	out := make(map[string]string, len(names))
 	for _, name := range names {
-		dir := i.NewTempDir("")
+		dir := sh.MakeTempDir()
 		p, err := security.CreatePersistentPrincipal(dir, nil)
 		if err != nil {
 			return nil, err
@@ -49,94 +40,107 @@
 		if err := idp.Bless(p, name); err != nil {
 			return nil, err
 		}
-		out[name] = fmt.Sprintf("%s=%s", ref.EnvCredentials, dir)
+		out[name] = dir
 	}
 	return out, nil
 }
 
-func V23TestHelloDirect(i *v23tests.T) {
-	creds, err := setupCredentials(i, "helloclient", "helloserver")
+func TestV23HelloDirect(t *testing.T) {
+	v23test.SkipUnlessRunningIntegrationTests(t)
+	sh := v23test.NewShell(t, v23test.Opts{})
+	defer sh.Cleanup()
+
+	creds, err := setupCreds(sh, "helloclient", "helloserver")
 	if err != nil {
-		i.Fatalf("Could not create credentials: %v", err)
+		t.Fatalf("Could not create credentials: %v", err)
 	}
-	clientbin := i.BuildGoPkg("v.io/x/ref/test/hello/helloclient")
-	serverbin := i.BuildGoPkg("v.io/x/ref/test/hello/helloserver")
-	server := serverbin.WithStartOpts(opts).WithEnv(creds["helloserver"]).Start()
-	name := server.ExpectVar("SERVER_NAME")
-	if server.Failed() {
-		server.Wait(os.Stdout, os.Stderr)
-		i.Fatalf("Could not get SERVER_NAME: %v", server.Error())
+	clientbin := sh.BuildGoPkg("v.io/x/ref/test/hello/helloclient")
+	serverbin := sh.BuildGoPkg("v.io/x/ref/test/hello/helloserver")
+
+	server := withCreds(creds["helloserver"], sh.Cmd(serverbin))
+	server.Start()
+	name := server.S.ExpectVar("SERVER_NAME")
+	if server.S.Failed() {
+		t.Fatalf("Could not get SERVER_NAME: %v", server.S.Error())
 	}
-	clientbin.WithEnv(creds["helloclient"]).WithStartOpts(opts).Run("--name", name)
+	withCreds(creds["helloclient"], sh.Cmd(clientbin, "--name", name)).Run()
 }
 
-func V23TestHelloAgentd(i *v23tests.T) {
-	creds, err := setupCredentials(i, "helloclient", "helloserver")
+func TestV23HelloAgentd(t *testing.T) {
+	v23test.SkipUnlessRunningIntegrationTests(t)
+	sh := v23test.NewShell(t, v23test.Opts{})
+	defer sh.Cleanup()
+
+	creds, err := setupCreds(sh, "helloclient", "helloserver")
 	if err != nil {
-		i.Fatalf("Could not create credentials: %v", err)
+		t.Fatalf("Could not create credentials: %v", err)
 	}
-	agentdbin := i.BuildGoPkg("v.io/x/ref/services/agent/agentd").WithStartOpts(opts)
-	serverbin := i.BuildGoPkg("v.io/x/ref/test/hello/helloserver")
-	clientbin := i.BuildGoPkg("v.io/x/ref/test/hello/helloclient")
-	server := agentdbin.WithEnv(creds["helloserver"]).Start(serverbin.Path())
-	name := server.ExpectVar("SERVER_NAME")
-	if server.Failed() {
-		server.Wait(os.Stdout, os.Stderr)
-		i.Fatalf("Could not get SERVER_NAME: %v", server.Error())
+	agentdbin := sh.BuildGoPkg("v.io/x/ref/services/agent/agentd")
+	serverbin := sh.BuildGoPkg("v.io/x/ref/test/hello/helloserver")
+	clientbin := sh.BuildGoPkg("v.io/x/ref/test/hello/helloclient")
+
+	server := withCreds(creds["helloserver"], sh.Cmd(serverbin))
+	server.Start()
+	name := server.S.ExpectVar("SERVER_NAME")
+	if server.S.Failed() {
+		t.Fatalf("Could not get SERVER_NAME: %v", server.S.Error())
 	}
-	agentdbin.WithEnv(creds["helloclient"]).Run(clientbin.Path(), "--name", name)
+	withCreds(creds["helloclient"], sh.Cmd(agentdbin, clientbin, "--name", name)).Run()
 }
 
-func V23TestHelloMounttabled(i *v23tests.T) {
-	creds, err := setupCredentials(i, "helloclient", "helloserver", "mounttabled")
+func TestV23HelloMounttabled(t *testing.T) {
+	v23test.SkipUnlessRunningIntegrationTests(t)
+	sh := v23test.NewShell(t, v23test.Opts{})
+	defer sh.Cleanup()
+
+	creds, err := setupCreds(sh, "helloclient", "helloserver", "mounttabled")
 	if err != nil {
-		i.Fatalf("Could not create credentials: %v", err)
+		t.Fatalf("Could not create credentials: %v", err)
 	}
-	agentdbin := i.BuildGoPkg("v.io/x/ref/services/agent/agentd").WithStartOpts(opts)
-	mounttabledbin := i.BuildGoPkg("v.io/x/ref/services/mounttable/mounttabled")
-	serverbin := i.BuildGoPkg("v.io/x/ref/test/hello/helloserver")
-	clientbin := i.BuildGoPkg("v.io/x/ref/test/hello/helloclient")
+	agentdbin := sh.BuildGoPkg("v.io/x/ref/services/agent/agentd")
+	mounttabledbin := sh.BuildGoPkg("v.io/x/ref/services/mounttable/mounttabled")
+	serverbin := sh.BuildGoPkg("v.io/x/ref/test/hello/helloserver")
+	clientbin := sh.BuildGoPkg("v.io/x/ref/test/hello/helloclient")
+
 	name := "hello"
-	mounttabled := agentdbin.WithEnv(creds["mounttabled"]).Start(mounttabledbin.Path(),
-		"--v23.tcp.address", "127.0.0.1:0")
-	mtname := mounttabled.ExpectVar("NAME")
-	if mounttabled.Failed() {
-		mounttabled.Wait(os.Stdout, os.Stderr)
-		i.Fatalf("Could not get NAME: %v", mounttabled.Error())
+	mounttabled := withCreds(creds["mounttabled"], sh.Cmd(agentdbin, mounttabledbin, "--v23.tcp.address", "127.0.0.1:0"))
+	mounttabled.Start()
+	mtname := mounttabled.S.ExpectVar("NAME")
+	if mounttabled.S.Failed() {
+		t.Fatalf("Could not get NAME: %v", mounttabled.S.Error())
 	}
-	agentdbin.WithEnv(creds["helloserver"]).Start(serverbin.Path(), "--name", name,
-		"--v23.namespace.root", mtname)
-	agentdbin.WithEnv(creds["helloclient"]).Run(clientbin.Path(), "--name", name,
-		"--v23.namespace.root", mtname)
+	withCreds(creds["helloserver"], sh.Cmd(agentdbin, serverbin, "--name", name, "--v23.namespace.root", mtname)).Start()
+	withCreds(creds["helloclient"], sh.Cmd(agentdbin, clientbin, "--name", name, "--v23.namespace.root", mtname)).Run()
 }
 
-func V23TestHelloProxy(i *v23tests.T) {
-	creds, err := setupCredentials(i, "helloclient", "helloserver",
-		"mounttabled", "proxyd", "xproxyd")
+func TestV23HelloProxy(t *testing.T) {
+	v23test.SkipUnlessRunningIntegrationTests(t)
+	sh := v23test.NewShell(t, v23test.Opts{})
+	defer sh.Cleanup()
+
+	creds, err := setupCreds(sh, "helloclient", "helloserver", "mounttabled", "proxyd", "xproxyd")
 	if err != nil {
-		i.Fatalf("Could not create credentials: %v", err)
+		t.Fatalf("Could not create credentials: %v", err)
 	}
-	agentdbin := i.BuildGoPkg("v.io/x/ref/services/agent/agentd").WithStartOpts(opts)
-	mounttabledbin := i.BuildGoPkg("v.io/x/ref/services/mounttable/mounttabled")
-	xproxydbin := i.BuildGoPkg("v.io/x/ref/services/xproxy/xproxyd")
-	serverbin := i.BuildGoPkg("v.io/x/ref/test/hello/helloserver")
-	clientbin := i.BuildGoPkg("v.io/x/ref/test/hello/helloclient")
+	agentdbin := sh.BuildGoPkg("v.io/x/ref/services/agent/agentd")
+	mounttabledbin := sh.BuildGoPkg("v.io/x/ref/services/mounttable/mounttabled")
+	xproxydbin := sh.BuildGoPkg("v.io/x/ref/services/xproxy/xproxyd")
+	serverbin := sh.BuildGoPkg("v.io/x/ref/test/hello/helloserver")
+	clientbin := sh.BuildGoPkg("v.io/x/ref/test/hello/helloclient")
+
+	name := "hello"
+	mounttabled := withCreds(creds["mounttabled"], sh.Cmd(agentdbin, mounttabledbin, "--v23.tcp.address", "127.0.0.1:0"))
+	mounttabled.Start()
+	mtname := mounttabled.S.ExpectVar("NAME")
+	if mounttabled.S.Failed() {
+		t.Fatalf("Could not get NAME: %v", mounttabled.S.Error())
+	}
 	proxyname := "proxy"
-	name := "hello"
-	mounttabled := agentdbin.WithEnv(creds["mounttabled"]).Start(mounttabledbin.Path(),
-		"--v23.tcp.address", "127.0.0.1:0")
-	mtname := mounttabled.ExpectVar("NAME")
-	if mounttabled.Failed() {
-		mounttabled.Wait(os.Stdout, os.Stderr)
-		i.Fatalf("Could not get NAME: %v", mounttabled.Error())
-	}
-	agentdbin.WithEnv(creds["xproxyd"]).Start(xproxydbin.Path(),
-		"--name", proxyname, "--v23.tcp.address", "127.0.0.1:0",
-		"--v23.namespace.root", mtname,
-		"--access-list", "{\"In\":[\"root\"]}")
-	agentdbin.WithEnv(creds["helloserver"]).Start(serverbin.Path(),
-		"--name", name, "--v23.proxy", proxyname, "--v23.tcp.address", "",
-		"--v23.namespace.root", mtname)
-	agentdbin.WithEnv(creds["helloclient"]).Run(clientbin.Path(), "--name", name,
-		"--v23.namespace.root", mtname)
+	withCreds(creds["xproxyd"], sh.Cmd(agentdbin, xproxydbin, "--name", proxyname, "--v23.tcp.address", "127.0.0.1:0", "--v23.namespace.root", mtname, "--access-list", "{\"In\":[\"root\"]}")).Start()
+	withCreds(creds["helloserver"], sh.Cmd(agentdbin, serverbin, "--name", name, "--v23.proxy", proxyname, "--v23.tcp.address", "", "--v23.namespace.root", mtname)).Start()
+	withCreds(creds["helloclient"], sh.Cmd(agentdbin, clientbin, "--name", name, "--v23.proxy", proxyname, "--v23.tcp.address", "", "--v23.namespace.root", mtname)).Run()
+}
+
+func TestMain(m *testing.M) {
+	os.Exit(v23test.Run(m.Run))
 }
diff --git a/test/hello/v23_test.go b/test/hello/v23_test.go
deleted file mode 100644
index 4429031..0000000
--- a/test/hello/v23_test.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.
-
-// This file was auto-generated via go generate.
-// DO NOT UPDATE MANUALLY
-
-package hello_test
-
-import (
-	"os"
-	"testing"
-
-	"v.io/x/ref/test/modules"
-	"v.io/x/ref/test/v23tests"
-)
-
-func TestMain(m *testing.M) {
-	modules.DispatchAndExitIfChild()
-	cleanup := v23tests.UseSharedBinDir()
-	r := m.Run()
-	cleanup()
-	os.Exit(r)
-}
-
-func TestV23HelloDirect(t *testing.T) {
-	v23tests.RunTest(t, V23TestHelloDirect)
-}
-
-func TestV23HelloAgentd(t *testing.T) {
-	v23tests.RunTest(t, V23TestHelloAgentd)
-}
-
-func TestV23HelloMounttabled(t *testing.T) {
-	v23tests.RunTest(t, V23TestHelloMounttabled)
-}
-
-func TestV23HelloProxy(t *testing.T) {
-	v23tests.RunTest(t, V23TestHelloProxy)
-}
diff --git a/test/modules/modules_test.go b/test/modules/modules_test.go
index b6243ac..4344359 100644
--- a/test/modules/modules_test.go
+++ b/test/modules/modules_test.go
@@ -32,8 +32,6 @@
 	_ "v.io/x/ref/runtime/factories/generic"
 )
 
-// We must call TestMain ourselves because using jiri test generate
-// creates an import cycle for this package.
 func TestMain(m *testing.M) {
 	modules.DispatchAndExitIfChild()
 	os.Exit(m.Run())
diff --git a/test/modules/shell.go b/test/modules/shell.go
index 77d259b..c34d3b1 100644
--- a/test/modules/shell.go
+++ b/test/modules/shell.go
@@ -41,13 +41,6 @@
 //   Dispatch: must be called in the child process to lookup and invoke the
 //     requested function.  Typically called from TestMain.
 //
-// The jiri tool can automate generation of TestMain.  Adding the comment below
-// to a test file will generate the appropriate code.
-//
-//   //go:generate jiri test generate .
-//
-// Use 'jiri test generate --help' to get a complete explanation.
-//
 // In all cases programs are started by invoking the StartWithOpts method on the
 // Shell with the name of the program to run. An instance of the Handle
 // interface is returned which can be used to interact with the function or
diff --git a/test/v23tests/binary.go b/test/v23tests/binary.go
deleted file mode 100644
index 915a6ac..0000000
--- a/test/v23tests/binary.go
+++ /dev/null
@@ -1,151 +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 v23tests
-
-import (
-	"bytes"
-	"io"
-	"os"
-	"strings"
-
-	"v.io/x/ref/test/modules"
-)
-
-// Binary represents an executable program that will be executed during a
-// test. A binary may be invoked multiple times by calling Start, each call
-// will return a new Invocation.
-//
-// Binary instances are typically obtained from a T by calling BuldV23Pkg,
-// BuildGoPkg (for Vanadium and other Go binaries) or BinaryFromPath (to
-// start binaries that are already present on the system).
-type Binary struct {
-	// The environment to which this binary belongs.
-	env *T
-
-	// The path to the binary.
-	path string
-
-	// StartOpts
-	opts modules.StartOpts
-
-	// Environment variables that will be used when creating invocations
-	// via Start.
-	envVars []string
-
-	// Optional prefix arguments are added to each invocation.
-	prefixArgs []string
-}
-
-// StartOpts returns the current the StartOpts
-func (b *Binary) StartOpts() modules.StartOpts {
-	return b.opts
-}
-
-// Path returns the path to the binary.
-func (b *Binary) Path() string {
-	return b.path
-}
-
-// Start starts the given binary with the given arguments.
-func (b *Binary) Start(args ...string) *Invocation {
-	return b.start(1, args...)
-}
-
-func (b *Binary) start(skip int, oargs ...string) *Invocation {
-	args := make([]string, len(b.prefixArgs), len(oargs)+len(b.prefixArgs))
-	copy(args, b.prefixArgs)
-	args = append(args, oargs...)
-	b.env.ctx.Infof("%s: starting %s %s", Caller(skip+1), b.Path(), strings.Join(args, " "))
-	opts := b.opts
-	if opts.ExecProtocol && opts.Credentials == nil {
-		opts.Credentials, opts.Error = b.env.shell.NewChildCredentials("child")
-	}
-	opts.ExpectTesting = b.env.TB
-	handle, err := b.env.shell.StartWithOpts(opts, b.envVars, modules.Program(b.Path()), args...)
-	if err != nil {
-		if handle != nil {
-			b.env.ctx.Infof("%s: start failed", Caller(skip+1))
-			handle.Shutdown(nil, os.Stderr)
-		}
-		// TODO(cnicolaou): calling Fatalf etc from a goroutine often leads
-		// to deadlock. Need to make sure that we handle this here. Maybe
-		// it's best to just return an error? Or provide a StartWithError
-		// call for use from goroutines.
-		b.env.Fatalf("%s: StartWithOpts(%v, %v) failed: %v", Caller(skip+1), b.Path(), strings.Join(args, ", "), err)
-	}
-	b.env.ctx.Infof("started PID %d\n", handle.Pid())
-	inv := &Invocation{
-		env:           b.env,
-		path:          b.path,
-		args:          args,
-		shutdownErr:   errNotShutdown,
-		privateHandle: handle,
-	}
-	b.env.appendInvocation(inv)
-	return inv
-}
-
-func (b *Binary) run(args ...string) string {
-	inv := b.start(2, args...)
-	var stdout, stderr bytes.Buffer
-	err := inv.Wait(&stdout, &stderr)
-	if err != nil {
-		a := strings.Join(args, ", ")
-		b.env.Fatalf("%s: Run(%s): failed: %v: \n%s\n", Caller(2), a, err, stderr.String())
-	}
-	return strings.TrimRight(stdout.String(), "\n")
-}
-
-// Run runs the binary with the specified arguments to completion. On
-// success it returns the contents of stdout, on failure it terminates the
-// test with an error message containing the error and the contents of
-// stderr.
-func (b *Binary) Run(args ...string) string {
-	return b.run(args...)
-}
-
-// WithStdin returns a copy of this binary that, when Start is called,
-// will read its input from the given reader. Once the reader returns
-// EOF, the returned invocation's standard input will be closed (see
-// Invocation.CloseStdin).
-func (b *Binary) WithStdin(r io.Reader) *Binary {
-	opts := b.opts
-	opts.Stdin = r
-	return b.WithStartOpts(opts)
-}
-
-// WithEnv returns a copy of this binary that, when Start is called, will use
-// the given environment variables. Each environment variable should be
-// in "key=value" form. For example:
-//
-// bin.WithEnv("EXAMPLE_ENV=/tmp/something").Start(...)
-func (b *Binary) WithEnv(env ...string) *Binary {
-	newBin := *b
-	newBin.envVars = env
-	return &newBin
-}
-
-// WithStartOpts eturns a copy of this binary that, when Start is called, will
-// use the given StartOpts.
-//
-// bin.WithStartOpts(opts).Start(...)
-// or
-// bin.WithStartOpts().Run(...)
-func (b *Binary) WithStartOpts(opts modules.StartOpts) *Binary {
-	newBin := *b
-	newBin.opts = opts
-	return &newBin
-}
-
-// WithPrefixArgs returns a copy of this binary that, when Start or Run
-// is called, will use the given additional arguments. For example: given
-// a Binary b built from "git", then b2 := WithPrefixArgs("checkout")
-// will let one run git checkout a; git checkout b with b2.Run("a"),
-// b2.Run("b").
-func (b *Binary) WithPrefixArgs(prefixArgs ...string) *Binary {
-	newBin := *b
-	newBin.prefixArgs = prefixArgs
-	return &newBin
-}
diff --git a/test/v23tests/doc.go b/test/v23tests/doc.go
deleted file mode 100644
index 3ca7429..0000000
--- a/test/v23tests/doc.go
+++ /dev/null
@@ -1,75 +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 v23tests implements support for writing writing end-to-end
-// integration tests. In particular, support is provided for building binaries,
-// running processes, making assertions about their output/state and ensuring
-// that no processes or files are left behind on exit. Since such tests are
-// often difficult to debug facilities are provided to help do so.
-//
-// The preferred usage of this integration test framework is via the v23
-// tool which generates supporting code. The primary reason for doing so is
-// to cleanly separate integration tests, which can be very expensive to run,
-// from normal unit tests which are intended to be fast and used constantly.
-// However, it still beneficial to be able to always compile the integration
-// test code with the normal test code, just not to run it. Similarly, it
-// is beneficial to share as much of the existing go test infrastructure as
-// possible, so the generated code uses a flag and a naming convention to
-// separate the tests. Integration tests may be run in addition to unit tests
-// by supplying the --v23.tests flag; the -run flag can be used
-// to avoid running unit tests by specifying a prefix of TestV23 since
-// the generated test functions names always start with TestV23. Thus:
-//
-// v23 go test -v <pkgs> --v23.tests  // runs both unit and integration tests
-// v23 go test -v -run=TestV23 <pkgs> --v23.tests // runs just integration tests
-//
-// The go generate mechanism is used to generate the test code, thus the
-// comment:
-//
-// //go:generate jiri test generate
-//
-// will generate the files v23_test.go and internal_v23_test.go for the
-// package in which it occurs. Run v23 test generate --help for full
-// details and options. In short, any function in an external
-// (i.e. <pgk>_test) test package of the following form:
-//
-// V23Test<x>(t *v23tests.T)
-//
-// will be invoked as integration test if the --v23.tests flag is used.
-//
-// The generated code makes use of the RunTest function.
-//
-// The test environment is implemented by an instance of T.
-// It is constructed with an instance of another interface TB, a mirror
-// of testing.TB. Thus, the integration test environment can be used
-// directly as follows:
-//
-//   func TestFoo(t *testing.T) {
-//     env := v23tests.New(t)
-//     defer env.Cleanup()
-//
-//     ...
-//   }
-//
-// The methods in this API typically do not return error in the case of
-// failure. Instead, the current test will fail with an appropriate error
-// message. This avoids the need to handle errors inline in the test.
-//
-// The test environment manages all built packages, subprocesses and a
-// set of environment variables that are passed to subprocesses.
-//
-// Debugging is supported as follows:
-// 1. The DebugShell method creates an interative shell at that point in
-//    the tests execution that has access to all of the running processes
-//    and environment of those processes. The developer can interact with
-//    those processes to determine the state of the test.
-// 2. Calls to methods on Test (e.g. FailNow, Fatalf) that fail the test
-//    cause the Cleanup method to print out the status of all invocations.
-// 3. Similarly, if the --v23.tests.shell-on-error flag is set then the
-//    cleanup method will invoke a DebugShell on a test failure allowing
-//    the developer to inspect the state of the test.
-// 4. The implementation of this package uses filenames that start with v23test
-//    to allow for easy tracing with --vmodule=v23test*=2 for example.
-//
-package v23tests
diff --git a/test/v23tests/internal/cached_test.go b/test/v23tests/internal/cached_test.go
deleted file mode 100644
index 99951d7..0000000
--- a/test/v23tests/internal/cached_test.go
+++ /dev/null
@@ -1,97 +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 internal_test
-
-import (
-	"fmt"
-	"io/ioutil"
-	"os"
-	"path/filepath"
-	"runtime"
-	"testing"
-	"time"
-
-	_ "v.io/x/ref/runtime/factories/generic"
-	"v.io/x/ref/test/v23tests"
-)
-
-//go:generate jiri test generate
-
-func init() {
-	dir, err := ioutil.TempDir("./", "v23test-internal")
-	if err != nil {
-		panic(err.Error())
-	}
-	os.Setenv("V23_BIN_DIR", dir)
-	tmpDir = dir
-}
-
-var tmpDir string
-var modTimes []time.Time
-
-// build build's a binary and appends it's modtime to the
-// global slice modTimes
-func build(i *v23tests.T) {
-	nsBin := i.BuildGoPkg("v.io/x/ref/cmd/namespace")
-	fi, err := os.Stat(nsBin.Path())
-	if err != nil {
-		i.Fatal(err)
-	}
-	modTimes = append(modTimes, fi.ModTime())
-}
-
-func fmtTimes() string {
-	r := ""
-	for _, t := range modTimes {
-		r += t.String() + "\n"
-	}
-	return r
-}
-
-// checkTimes returns true if modTimes has at least one value
-// and the values are all the same.
-func checkTimes(i *v23tests.T) bool {
-	_, file, line, _ := runtime.Caller(1)
-	loc := fmt.Sprintf("%s:%d", filepath.Dir(file), line)
-	if len(modTimes) == 0 {
-		i.Fatalf("%s: nothing has been built", loc)
-	}
-	first := modTimes[0]
-	for _, t := range modTimes[1:] {
-		if t != first {
-			i.Fatalf("%s: binary not cached: build times: %s", loc, fmtTimes())
-		}
-	}
-	i.Logf("%d entries", len(modTimes))
-	return true
-}
-
-func V23TestOne(i *v23tests.T) {
-	build(i)
-	checkTimes(i)
-}
-
-func V23TestTwo(i *v23tests.T) {
-	build(i)
-	checkTimes(i)
-}
-
-func V23TestThree(i *v23tests.T) {
-	build(i)
-	checkTimes(i)
-}
-
-func V23TestFour(i *v23tests.T) {
-	build(i)
-	checkTimes(i)
-}
-
-func TestMain(m *testing.M) {
-	r := m.Run()
-	if len(tmpDir) > 0 {
-		os.RemoveAll(tmpDir)
-	}
-	os.Exit(r)
-}
diff --git a/test/v23tests/internal/dummy.go b/test/v23tests/internal/dummy.go
deleted file mode 100644
index 9272cb6..0000000
--- a/test/v23tests/internal/dummy.go
+++ /dev/null
@@ -1,5 +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 internal
diff --git a/test/v23tests/internal/v23_test.go b/test/v23tests/internal/v23_test.go
deleted file mode 100644
index aad6a7a..0000000
--- a/test/v23tests/internal/v23_test.go
+++ /dev/null
@@ -1,30 +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.
-
-// This file was auto-generated via go generate.
-// DO NOT UPDATE MANUALLY
-
-package internal_test
-
-import (
-	"testing"
-
-	"v.io/x/ref/test/v23tests"
-)
-
-func TestV23One(t *testing.T) {
-	v23tests.RunTest(t, V23TestOne)
-}
-
-func TestV23Two(t *testing.T) {
-	v23tests.RunTest(t, V23TestTwo)
-}
-
-func TestV23Three(t *testing.T) {
-	v23tests.RunTest(t, V23TestThree)
-}
-
-func TestV23Four(t *testing.T) {
-	v23tests.RunTest(t, V23TestFour)
-}
diff --git a/test/v23tests/invocation.go b/test/v23tests/invocation.go
deleted file mode 100644
index c5201c9..0000000
--- a/test/v23tests/invocation.go
+++ /dev/null
@@ -1,118 +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 v23tests
-
-import (
-	"bytes"
-	"container/list"
-	"io"
-	"syscall"
-
-	"v.io/x/ref/test/modules"
-)
-
-type privateHandle modules.Handle
-
-// Invocation represents a single invocation of a Binary.
-//
-// Any bytes written by the invocation to its standard error may be recovered
-// using the Wait or WaitOrDie functions.
-//
-// For example:
-//   bin := env.BinaryFromPath("/bin/bash")
-//   inv := bin.Start("-c", "echo hello world 1>&2")
-//   var stderr bytes.Buffer
-//   inv.WaitOrDie(nil, &stderr)
-//   // stderr.Bytes() now contains 'hello world\n'
-type Invocation struct {
-	// The handle to the process that was run when this invocation was started.
-	privateHandle
-
-	// The environment to which this invocation belongs.
-	env *T
-
-	// The element representing this invocation in the list of
-	// invocations stored in the environment
-	el *list.Element
-
-	// The path of the binary used for this invocation.
-	path string
-
-	// The args the binary was started with
-	args []string
-
-	// True if the process has been shutdown
-	hasShutdown bool
-
-	// The error, if any, as determined when the invocation was
-	// shutdown. It must be set to a default initial value of
-	// errNotShutdown rather than nil to allow us to distinguish between
-	// a successful shutdown or an error.
-	shutdownErr error
-}
-
-// Path returns the path to the binary that was used for this invocation.
-func (i *Invocation) Path() string {
-	return i.path
-}
-
-// Exists returns true if the invocation still exists.
-func (i *Invocation) Exists() bool {
-	return syscall.Kill(i.privateHandle.Pid(), 0) == nil
-}
-
-// Kill sends the given signal to this invocation. It is up to the test
-// author to decide whether failure to deliver the signal is fatal to
-// the test.
-func (i *Invocation) Kill(sig syscall.Signal) error {
-	pid := i.privateHandle.Pid()
-	i.env.ctx.VI(1).Infof("sending signal %v to PID %d", sig, pid)
-	return syscall.Kill(pid, sig)
-}
-
-// Output reads the invocation's stdout until EOF and then returns what
-// was read as a string.
-func (i *Invocation) Output() string {
-	buf := bytes.Buffer{}
-	_, err := buf.ReadFrom(i.Stdout())
-	if err != nil {
-		i.env.Fatalf("%s: ReadFrom() failed: %v", Caller(1), err)
-	}
-	return buf.String()
-}
-
-// Wait waits for this invocation to finish. If either stdout or stderr
-// is non-nil, any remaining unread output from those sources will be
-// written to the corresponding writer. The returned error represents
-// the exit status of the underlying command.
-func (i *Invocation) Wait(stdout, stderr io.Writer) error {
-	err := i.privateHandle.Shutdown(stdout, stderr)
-	i.hasShutdown = true
-	i.shutdownErr = err
-	return err
-}
-
-// Shutdown is the same as Wait, but hides the Shutdown method on
-// the embedded modules.Handle.
-func (i *Invocation) Shutdown(stdout, stderr io.Writer) error {
-	return i.Wait(stdout, stderr)
-}
-
-// WaitOrDie waits for this invocation to finish. If either stdout or stderr
-// is non-nil, any remaining unread output from those sources will be
-// written to the corresponding writer. If the underlying command
-// exited with anything but success (exit status 0), this function will
-// cause the current test to fail.
-func (i *Invocation) WaitOrDie(stdout, stderr io.Writer) {
-	if err := i.Wait(stdout, stderr); err != nil {
-		i.env.Fatalf("%s: FATAL: Wait() for pid %d failed: %v", Caller(1), i.privateHandle.Pid(), err)
-	}
-}
-
-// Environment returns the instance of the test environment that this
-// invocation was from.
-func (i *Invocation) Environment() *T {
-	return i.env
-}
diff --git a/test/v23tests/v23tests.go b/test/v23tests/v23tests.go
deleted file mode 100644
index 46473c7..0000000
--- a/test/v23tests/v23tests.go
+++ /dev/null
@@ -1,658 +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 v23tests
-
-import (
-	"errors"
-	"fmt"
-	"io/ioutil"
-	"math/rand"
-	"os"
-	"os/exec"
-	"path"
-	"path/filepath"
-	"runtime"
-	"strings"
-	"syscall"
-	"testing"
-	"time"
-
-	"v.io/v23"
-	"v.io/v23/context"
-
-	"v.io/x/ref"
-	"v.io/x/ref/test"
-	"v.io/x/ref/test/modules"
-	"v.io/x/ref/test/testutil"
-)
-
-// TB is an exact mirror of testing.TB. It is provided to allow for testing
-// of this package using a mock implementation. As per testing.TB, it is not
-// intended to be implemented outside of this package.
-type TB interface {
-	Error(args ...interface{})
-	Errorf(format string, args ...interface{})
-	Fail()
-	FailNow()
-	Failed() bool
-	Fatal(args ...interface{})
-	Fatalf(format string, args ...interface{})
-	Log(args ...interface{})
-	Logf(format string, args ...interface{})
-	Skip(args ...interface{})
-	SkipNow()
-	Skipf(format string, args ...interface{})
-	Skipped() bool
-}
-
-// T represents an integration test environment.
-type T struct {
-	// The embedded TB
-	TB
-
-	ctx *context.T
-
-	// The function to shutdown the context used to create the environment.
-	shutdown v23.Shutdown
-
-	// The shell to use to start commands.
-	shell *modules.Shell
-
-	// Maps path to Binary.
-	builtBinaries map[string]*Binary
-
-	tempFiles            []*os.File
-	tempDirs             []string
-	binDir, cachedBinDir string
-	dirStack             []string
-
-	invocations []*Invocation
-}
-
-var errNotShutdown = errors.New("has not been shutdown")
-
-// Caller returns a string of the form <filename>:<lineno> for the
-// caller specified by skip, where skip is as per runtime.Caller.
-func Caller(skip int) string {
-	_, file, line, _ := runtime.Caller(skip + 1)
-	return fmt.Sprintf("%s:%d", filepath.Base(file), line)
-}
-
-// SkipInRegressionBefore skips the test if this is being run in a regression
-// test and some of the binaries are older than the specified date.
-// This is useful when we're breaking compatibility and we know it.
-// The parameter is a string formatted date YYYY-MM-DD.
-func (t *T) SkipInRegressionBefore(dateStr string) {
-	testStr := os.Getenv("V23_REGTEST_DATE")
-	if testStr == "" {
-		return
-	}
-	testDate, err := time.Parse("2006-01-02", testStr)
-	if err != nil {
-		t.Fatalf("%s: could not parse V23_REGTEST_DATE=%q: %v", Caller(1), testStr, err)
-	}
-	date, err := time.Parse("2006-01-02", dateStr)
-	if err != nil {
-		t.Fatalf("%s: could not parse date %q: %v", Caller(1), dateStr, err)
-	}
-	if testDate.Before(date) {
-		t.Skipf("Skipping regression test with binary from %s", testStr)
-	}
-}
-
-// Run constructs a Binary for path and invokes Run on it.
-func (t *T) Run(path string, args ...string) string {
-	return t.BinaryFromPath(path).run(args...)
-}
-
-// Run constructs a Binary for path and invokes Run on it using
-// the specified StartOpts
-func (t *T) RunWithOpts(opts modules.StartOpts, path string, args ...string) string {
-	b := t.BinaryFromPath(path)
-	return b.WithStartOpts(opts).run(args...)
-}
-
-// WaitFunc is the type of the functions to be used in conjunction
-// with WaitFor and WaitForAsync. It should return a value or an error
-// when it wants those functions to terminate, returning a nil value
-// and nil error will result in it being called again after the specified
-// delay time specified in the calls to WaitFor and WaitForAsync.
-type WaitFunc func() (interface{}, error)
-
-// WaitFor calls fn at least once with the specified delay value
-// between iterations until the first of the following is encountered:
-// 1. fn returns a non-nil value.
-// 2. fn returns an error value
-// 3. fn is executed at least once and the specified timeout is exceeded.
-//
-// WaitFor returns the non-nil value for the first case and calls e.Fatalf for
-// the other two cases.
-// WaitFor will always run fn at least once to completion and hence it will
-// hang if that first iteration of fn hangs. If this behaviour is not
-// appropriate, then WaitForAsync should be used.
-func (t *T) WaitFor(fn WaitFunc, delay, timeout time.Duration) interface{} {
-	deadline := time.Now().Add(timeout)
-	for {
-		val, err := fn()
-		if val != nil {
-			return val
-		}
-		if err != nil {
-			t.Fatalf("%s: the WaitFunc returned an error: %v", Caller(1), err)
-		}
-		if time.Now().After(deadline) {
-			t.Fatalf("%s: timed out after %s", Caller(1), timeout)
-		}
-		time.Sleep(delay)
-	}
-}
-
-// WaitForAsync is like WaitFor except that it calls fn in a goroutine
-// and can timeout during the execution of fn.
-func (t *T) WaitForAsync(fn WaitFunc, delay, timeout time.Duration) interface{} {
-	resultCh := make(chan interface{})
-	errCh := make(chan interface{})
-	go func() {
-		for {
-			val, err := fn()
-			if val != nil {
-				resultCh <- val
-				return
-			}
-			if err != nil {
-				errCh <- err
-				return
-			}
-			time.Sleep(delay)
-		}
-	}()
-	select {
-	case err := <-errCh:
-		t.Fatalf("%s: the WaitFunc returned error: %v", Caller(1), err)
-	case result := <-resultCh:
-		return result
-	case <-time.After(timeout):
-		t.Fatalf("%s: timed out after %s", Caller(1), timeout)
-	}
-	return nil
-}
-
-// Pushd pushes the current working directory to the stack of
-// directories, returning it as its result, and changes the working
-// directory to dir.
-func (t *T) Pushd(dir string) string {
-	cwd, err := os.Getwd()
-	if err != nil {
-		t.Fatalf("%s: Getwd failed: %s", Caller(1), err)
-	}
-	if err := os.Chdir(dir); err != nil {
-		t.Fatalf("%s: Chdir failed: %s", Caller(1), err)
-	}
-	t.ctx.VI(1).Infof("Pushd: %s -> %s", cwd, dir)
-	t.dirStack = append(t.dirStack, cwd)
-	return cwd
-}
-
-// Popd pops the most recent entry from the directory stack and changes
-// the working directory to that directory. It returns the new working
-// directory as its result.
-func (t *T) Popd() string {
-	if len(t.dirStack) == 0 {
-		t.Fatalf("%s: directory stack empty", Caller(1))
-	}
-	dir := t.dirStack[len(t.dirStack)-1]
-	t.dirStack = t.dirStack[:len(t.dirStack)-1]
-	if err := os.Chdir(dir); err != nil {
-		t.Fatalf("%s: Chdir failed: %s", Caller(1), err)
-	}
-	t.ctx.VI(1).Infof("Popd: -> %s", dir)
-	return dir
-}
-
-// Caller returns a string of the form <filename>:<lineno> for the
-// caller specified by skip, where skip is as per runtime.Caller.
-func (t *T) Caller(skip int) string {
-	return Caller(skip + 1)
-}
-
-// Context returns the root context of this environment.
-func (t *T) Context() *context.T {
-	return t.ctx
-}
-
-// Cleanup cleans up the environment, deletes all its artifacts and
-// kills all subprocesses. It will kill subprocesses in LIFO order.
-// Cleanup checks to see if the test has failed and logs information
-// as to the state of the processes it was asked to invoke up to that
-// point and optionally, if the --v23.tests.shell-on-fail flag is set
-// then it will run a debug shell before cleaning up its state.
-func (t *T) Cleanup() {
-	if t.Failed() {
-		if test.IntegrationTestsDebugShellOnError {
-			t.DebugSystemShell()
-		}
-		// Print out a summary of the invocations and their status.
-		for i, inv := range t.invocations {
-			if inv.hasShutdown && inv.Exists() {
-				m := fmt.Sprintf("%d: %s has been shutdown but still exists: %v", i, inv.path, inv.shutdownErr)
-				t.Log(m)
-				t.ctx.VI(1).Info(m)
-				t.ctx.VI(2).Infof("%d: %s %v", i, inv.path, inv.args)
-				continue
-			}
-			if inv.shutdownErr != nil {
-				m := fmt.Sprintf("%d: %s: shutdown status: %v", i, inv.path, inv.shutdownErr)
-				t.Log(m)
-				t.ctx.VI(1).Info(m)
-				t.ctx.VI(2).Infof("%d: %s %v", i, inv.path, inv.args)
-			}
-		}
-	}
-
-	t.ctx.VI(1).Infof("V23Test.Cleanup")
-	// Shut down all processes in LIFO order before attempting to delete any
-	// files/directories to avoid potential 'file system busy' problems
-	// on non-unix systems.
-	for i := len(t.invocations); i > 0; i-- {
-		inv := t.invocations[i-1]
-		if inv.hasShutdown {
-			t.ctx.VI(1).Infof("V23Test.Cleanup: %q has been shutdown", inv.Path())
-			continue
-		}
-		t.ctx.VI(1).Infof("V23Test.Cleanup: Kill: %q", inv.Path())
-		err := inv.Kill(syscall.SIGTERM)
-		inv.Wait(os.Stdout, os.Stderr)
-		t.ctx.VI(1).Infof("V23Test.Cleanup: Killed: %q: %v", inv.Path(), err)
-	}
-	t.ctx.VI(1).Infof("V23Test.Cleanup: all invocations taken care of.")
-
-	if err := t.shell.Cleanup(os.Stdout, os.Stderr); err != nil {
-		t.Fatalf("WARNING: could not clean up shell (%v)", err)
-	}
-
-	t.ctx.VI(1).Infof("V23Test.Cleanup: cleaning up binaries & files")
-
-	for _, tempFile := range t.tempFiles {
-		t.ctx.VI(1).Infof("V23Test.Cleanup: cleaning up %s", tempFile.Name())
-		if err := tempFile.Close(); err != nil {
-			t.ctx.Errorf("WARNING: Close(%q) failed: %v", tempFile.Name(), err)
-		}
-		if err := os.RemoveAll(tempFile.Name()); err != nil {
-			t.ctx.Errorf("WARNING: RemoveAll(%q) failed: %v", tempFile.Name(), err)
-		}
-	}
-
-	for _, tempDir := range t.tempDirs {
-		t.ctx.VI(1).Infof("V23Test.Cleanup: cleaning up %s", tempDir)
-		t.ctx.Infof("V23Test.Cleanup: cleaning up %s", tempDir)
-		if err := os.RemoveAll(tempDir); err != nil {
-			t.ctx.Errorf("WARNING: RemoveAll(%q) failed: %v", tempDir, err)
-		}
-	}
-
-	// shutdown the runtime
-	t.shutdown()
-}
-
-// GetVar returns the variable associated with the specified key
-// and an indication of whether it is defined or not.
-func (t *T) GetVar(key string) (string, bool) {
-	return t.shell.GetVar(key)
-}
-
-// SetVar sets the value to be associated with key.
-func (t *T) SetVar(key, value string) {
-	t.shell.SetVar(key, value)
-}
-
-// ClearVar removes the speficied variable from the Shell's environment
-func (t *T) ClearVar(key string) {
-	t.shell.ClearVar(key)
-}
-
-func writeStringOrDie(t *T, f *os.File, s string) {
-	if _, err := f.WriteString(s); err != nil {
-		t.Fatalf("Write() failed: %v", err)
-	}
-}
-
-// DebugSystemShell drops the user into a debug system shell (e.g. bash)
-// with any environment variables specified in env... (in VAR=VAL format)
-// available to it.
-// If there is no controlling TTY, DebugSystemShell will emit a warning message
-// and take no futher action. The DebugSystemShell also sets some environment
-// variables that relate to the running test:
-// - V23_TMP_DIR<#> contains the name of each temp directory created.
-// - V23_BIN_DIR contains the name of the directory containing binaries.
-func (t *T) DebugSystemShell(env ...string) {
-	// Get the current working directory.
-	cwd, err := os.Getwd()
-	if err != nil {
-		t.Fatalf("Getwd() failed: %v", err)
-	}
-
-	// Transfer stdin, stdout, and stderr to the new process
-	// and also set target directory for the shell to start in.
-	dev := "/dev/tty"
-	fd, err := syscall.Open(dev, syscall.O_RDWR, 0)
-	if err != nil {
-		t.ctx.Errorf("WARNING: Open(%v) failed, was asked to create a debug shell but cannot: %v", dev, err)
-		return
-	}
-
-	var agentPath string
-	if creds, err := t.shell.NewChildCredentials("debug"); err == nil {
-		agentPath = creds.Path()
-	} else {
-		t.ctx.Errorf("WARNING: failed to obtain credentials for the debug shell: %v", err)
-	}
-
-	file := os.NewFile(uintptr(fd), dev)
-	attr := os.ProcAttr{
-		Files: []*os.File{file, file, file},
-		Dir:   cwd,
-	}
-	// Set up agent for Child.
-	attr.Env = append(attr.Env, fmt.Sprintf("%s=%v", ref.EnvAgentPath, agentPath))
-
-	// Set up environment for Child.
-	for _, v := range t.shell.Env() {
-		attr.Env = append(attr.Env, v)
-	}
-
-	for i, td := range t.tempDirs {
-		attr.Env = append(attr.Env, fmt.Sprintf("V23_TMP_DIR%d=%s", i, td))
-	}
-
-	if len(t.cachedBinDir) > 0 {
-		attr.Env = append(attr.Env, "V23_BIN_DIR="+t.BinDir())
-	}
-	attr.Env = append(attr.Env, env...)
-
-	// Start up a new shell.
-	writeStringOrDie(t, file, ">> Starting a new interactive shell\n")
-	writeStringOrDie(t, file, "Hit CTRL-D to resume the test\n")
-	if len(t.builtBinaries) > 0 {
-		writeStringOrDie(t, file, "Built binaries:\n")
-		for _, value := range t.builtBinaries {
-			writeStringOrDie(t, file, "\t"+value.Path()+"\n")
-		}
-	}
-	if len(t.cachedBinDir) > 0 {
-		writeStringOrDie(t, file, fmt.Sprintf("Binaries are cached in %q\n", t.cachedBinDir))
-	} else {
-		writeStringOrDie(t, file, fmt.Sprintf("Caching of binaries was not enabled, being written to %q\n", t.binDir))
-	}
-
-	shellPath := "/bin/sh"
-	if shellPathFromEnv := os.Getenv("SHELL"); shellPathFromEnv != "" {
-		shellPath = shellPathFromEnv
-	}
-	proc, err := os.StartProcess(shellPath, []string{}, &attr)
-	if err != nil {
-		t.Fatalf("StartProcess(%q) failed: %v", shellPath, err)
-	}
-
-	// Wait until user exits the shell
-	state, err := proc.Wait()
-	if err != nil {
-		t.Fatalf("Wait(%v) failed: %v", shellPath, err)
-	}
-
-	writeStringOrDie(t, file, fmt.Sprintf("<< Exited shell: %s\n", state.String()))
-}
-
-// BinaryFromPath returns a new Binary that, when started, will execute the
-// executable or script at the given path. The binary is assumed to not
-// implement the exec protocol defined in v.io/x/ref/lib/exec.
-//
-// E.g. env.BinaryFromPath("/bin/bash").Start("-c", "echo hello world").Output() -> "hello world"
-func (t *T) BinaryFromPath(path string) *Binary {
-	return &Binary{
-		env:     t,
-		envVars: nil,
-		path:    path,
-		opts:    t.shell.DefaultStartOpts().NoExecProgram(),
-	}
-}
-
-// BuildGoPkg expects a Go package path that identifies a "main" package, and
-// any build flags to pass to "go build", and returns a Binary representing the
-// newly built binary.
-//
-// The resulting binary is assumed to not use the exec protocol defined in
-// v.io/x/ref/lib/exec and in particular will not have access to the security
-// agent or any other shared file descriptors.  Environment variables and
-// command line arguments are the only means of communicating with the
-// invocations of this binary.
-//
-// Use this for non-Vanadium command-line tools and servers.
-func (t *T) BuildGoPkg(pkg string, flags ...string) *Binary {
-	return t.buildPkg(pkg, flags...)
-}
-
-// BuildV23 is like BuildGoPkg, but instead assumes that the resulting binary is
-// a Vanadium application and does implement the exec protocol defined in
-// v.io/x/ref/lib/exec.  The invocations of this binary will have access to the
-// security agent configured for the parent process, to shared file descriptors,
-// the config shared by the exec package.
-//
-// Use this for Vanadium servers. Note that some vanadium client only binaries,
-// that do not call v23.Init and hence do not implement the exec protocol cannot
-// be used via BuildV23Pkg.
-func (t *T) BuildV23Pkg(pkg string, flags ...string) *Binary {
-	b := t.buildPkg(pkg, flags...)
-	b.opts = t.shell.DefaultStartOpts().ExternalProgram()
-	return b
-}
-
-func (t *T) buildPkg(pkg string, flags ...string) *Binary {
-	then := time.Now()
-	loc := Caller(2)
-	cached, built_path, err := buildPkg(t, t.BinDir(), pkg, flags)
-	if err != nil {
-		t.Fatalf("%s: buildPkg(%s, %v) failed: %v", loc, pkg, flags, err)
-		return nil
-	}
-	if _, err := os.Stat(built_path); err != nil {
-		t.Fatalf("%s: buildPkg(%s, %v) failed to stat %q", loc, pkg, flags, built_path)
-	}
-	taken := time.Now().Sub(then)
-	if cached {
-		t.ctx.Infof("%s: using %s, from %s in %s.", loc, pkg, built_path, taken)
-	} else {
-		t.ctx.Infof("%s: built %s, written to %s in %s.", loc, pkg, built_path, taken)
-	}
-	binary := &Binary{
-		env:     t,
-		envVars: nil,
-		path:    built_path,
-		opts:    t.shell.DefaultStartOpts().NoExecProgram(),
-	}
-	t.builtBinaries[pkg] = binary
-	return binary
-}
-
-// NewTempFile creates a temporary file. Temporary files will be deleted
-// by Cleanup.
-func (t *T) NewTempFile() *os.File {
-	loc := Caller(1)
-	f, err := ioutil.TempFile("", "")
-	if err != nil {
-		t.Fatalf("%s: TempFile() failed: %v", loc, err)
-	}
-	t.ctx.Infof("%s: created temporary file at %s", loc, f.Name())
-	t.tempFiles = append(t.tempFiles, f)
-	return f
-}
-
-// NewTempDir creates a temporary directory. Temporary directories and
-// their contents will be deleted by Cleanup.
-func (t *T) NewTempDir(dir string) string {
-	loc := Caller(1)
-	f, err := ioutil.TempDir(dir, "")
-	if err != nil {
-		t.Fatalf("%s: TempDir() failed: %v", loc, err)
-	}
-	t.ctx.Infof("%s: created temporary directory at %s", loc, f)
-	t.tempDirs = append(t.tempDirs, f)
-	return f
-}
-
-func (t *T) appendInvocation(inv *Invocation) {
-	t.invocations = append(t.invocations, inv)
-}
-
-// Creates a new local testing environment. A local testing environment has a
-// a security principal available via v23.GetPrincipal(t.Context()).
-//
-// You should clean up the returned environment using the env.Cleanup() method.
-// A typical end-to-end test will begin like:
-//
-//   func TestFoo(t *testing.T) {
-//     env := integration.NewT(t)
-//     defer env.Cleanup()
-//
-//     ...
-//   }
-func New(t TB) *T {
-	ctx, shutdown := v23.Init()
-
-	principal := testutil.NewPrincipal("root")
-	ctx, err := v23.WithPrincipal(ctx, principal)
-	if err != nil {
-		t.Fatalf("failed to set principal: %v", err)
-	}
-
-	ctx.Infof("created root principal: %v", principal)
-
-	shell, err := modules.NewShell(ctx, principal, testing.Verbose(), t)
-	if err != nil {
-		t.Fatalf("NewShell() failed: %v", err)
-	}
-	opts := modules.DefaultStartOpts()
-	opts.StartTimeout = time.Minute
-	opts.ShutdownTimeout = 5 * time.Minute
-	shell.SetDefaultStartOpts(opts)
-
-	// The V23_BIN_DIR environment variable can be
-	// used to identify a directory that multiple integration
-	// tests can use to share binaries. Whoever sets this
-	// environment variable is responsible for cleaning up the
-	// directory it points to.
-	cachedBinDir := os.Getenv("V23_BIN_DIR")
-	e := &T{
-		TB:            t,
-		ctx:           ctx,
-		builtBinaries: make(map[string]*Binary),
-		shell:         shell,
-		tempFiles:     []*os.File{},
-		tempDirs:      []string{},
-		cachedBinDir:  cachedBinDir,
-		shutdown:      shutdown,
-	}
-	if len(e.cachedBinDir) == 0 {
-		e.binDir = e.NewTempDir("")
-	}
-	return e
-}
-
-// Shell returns the underlying modules.Shell used by v23tests.
-func (t *T) Shell() *modules.Shell {
-	return t.shell
-}
-
-// BinDir returns the directory that binarie files are stored in.
-func (t *T) BinDir() string {
-	if len(t.cachedBinDir) > 0 {
-		return t.cachedBinDir
-	}
-	return t.binDir
-}
-
-// buildPkg returns a path to a directory that contains the built binary for
-// the given packages and a function that should be invoked to clean up the
-// build artifacts. Note that the clients of this function should not modify
-// the contents of this directory directly and instead defer to the cleanup
-// function.
-func buildPkg(t *T, binDir, pkg string, flags []string) (bool, string, error) {
-	binFile := filepath.Join(binDir, path.Base(pkg))
-	t.ctx.Infof("buildPkg: %v .. %v %v", binDir, pkg, flags)
-	if _, err := os.Stat(binFile); err != nil {
-		if !os.IsNotExist(err) {
-			return false, "", err
-		}
-		baseName := path.Base(binFile)
-		tmpdir, err := ioutil.TempDir(binDir, baseName+"-")
-		if err != nil {
-			return false, "", err
-		}
-		defer os.RemoveAll(tmpdir)
-		uniqueBinFile := filepath.Join(tmpdir, baseName)
-
-		buildArgs := []string{"go", "build", "-x", "-o", uniqueBinFile}
-		buildArgs = append(buildArgs, flags...)
-		buildArgs = append(buildArgs, pkg)
-		cmd := exec.Command("jiri", buildArgs...)
-		if output, err := cmd.CombinedOutput(); err != nil {
-			t.ctx.VI(1).Infof("\n%v:\n%v\n", strings.Join(cmd.Args, " "), string(output))
-			return false, "", err
-		}
-		if err := os.Rename(uniqueBinFile, binFile); err != nil {
-			// It seems that on some systems a rename may fail if another rename
-			// is in progress in the same directory. We back a random amount of time
-			// in the hope that a second attempt will succeed.
-			time.Sleep(time.Duration(rand.Int63n(1000)) * time.Millisecond)
-			if err := os.Rename(uniqueBinFile, binFile); err != nil {
-				return false, "", err
-			}
-		}
-		return false, binFile, nil
-	}
-	return true, binFile, nil
-}
-
-// RunTest runs a single Vanadium 'v23 style' integration test.
-func RunTest(t *testing.T, fn func(i *T)) {
-	if !test.IntegrationTestsEnabled {
-		t.Skip()
-	}
-	i := New(t)
-	// defer the Cleanup method so that it will be called even if
-	// t.Fatalf/FailNow etc are called and can print out useful information.
-	defer i.Cleanup()
-	fn(i)
-}
-
-// RunRootMT builds and runs a root mount table instance. It populates the
-// ref.EnvNamespacePrefix variable in the test environment so that all
-// subsequent invocations will access this root mount table.  It also
-// modifies i's context to use this mount table as a namespace root.
-func RunRootMT(i *T, args ...string) (*Binary, *Invocation) {
-	b := i.BuildV23Pkg("v.io/x/ref/services/mounttable/mounttabled")
-	inv := b.start(1, args...)
-	name := inv.ExpectVar("NAME")
-	inv.Environment().SetVar(ref.EnvNamespacePrefix, name)
-	i.ctx.Infof("Running root mount table: %q", name)
-	v23.GetNamespace(i.ctx).SetRoots(name)
-	return b, inv
-}
-
-// UseSharedBinDir ensures that a shared directory is used for binaries
-// across multiple instances of the test environment. This is achieved
-// by setting the V23_BIN_DIR environment variable if it is not already
-// set in the test processes environment (as will typically be the case when
-// these tests are run from the jiri tool). It is intended to be called
-// from TestMain.
-func UseSharedBinDir() func() {
-	if v23BinDir := os.Getenv("V23_BIN_DIR"); len(v23BinDir) == 0 {
-		v23BinDir, err := ioutil.TempDir("", "bin-")
-		if err == nil {
-			os.Setenv("V23_BIN_DIR", v23BinDir)
-			return func() { os.RemoveAll(v23BinDir) }
-		}
-	}
-	return func() {}
-}
diff --git a/test/v23tests/v23tests_test.go b/test/v23tests/v23tests_test.go
deleted file mode 100644
index bd79acc..0000000
--- a/test/v23tests/v23tests_test.go
+++ /dev/null
@@ -1,448 +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 v23tests_test
-
-import (
-	"bytes"
-	"crypto/sha1"
-	"fmt"
-	"os"
-	"regexp"
-	"strings"
-	"syscall"
-	"testing"
-	"time"
-
-	"v.io/v23/naming"
-	"v.io/v23/security"
-
-	"v.io/x/ref"
-	"v.io/x/ref/internal/logger"
-	_ "v.io/x/ref/runtime/factories/generic"
-	"v.io/x/ref/test/modules"
-	"v.io/x/ref/test/testutil"
-	"v.io/x/ref/test/v23tests"
-)
-
-func TestBinaryFromPath(t *testing.T) {
-	env := v23tests.New(t)
-	defer env.Cleanup()
-
-	bash := env.BinaryFromPath("/bin/bash")
-	if want, got := "hello world\n", bash.Start("-c", "echo hello world").Output(); want != got {
-		t.Fatalf("unexpected output, want %s, got %s", want, got)
-	}
-
-	inv := bash.Start("-c", "echo hello world")
-	var buf bytes.Buffer
-	inv.WaitOrDie(&buf, nil)
-	if want, got := "hello world\n", buf.String(); want != got {
-		t.Fatalf("unexpected output, want %s, got %s", want, got)
-	}
-}
-
-func TestMountTable(t *testing.T) {
-	env := v23tests.New(t)
-	defer env.Cleanup()
-
-	v23tests.RunRootMT(env, "--v23.tcp.address=127.0.0.1:0")
-	proxyBin := env.BuildV23Pkg("v.io/x/ref/services/xproxy/xproxyd")
-	nsBin := env.BuildGoPkg("v.io/x/ref/cmd/namespace")
-
-	mt, ok := env.GetVar(ref.EnvNamespacePrefix)
-	if !ok || len(mt) == 0 {
-		t.Fatalf("expected a mount table name")
-	}
-
-	proxy := proxyBin.Start("--v23.tcp.address=127.0.0.1:0", "-name=proxyd")
-	proxyName := proxy.ExpectVar("NAME")
-	proxyAddress, _ := naming.SplitAddressName(proxyName)
-
-	re := regexp.MustCompile("proxyd (.*) \\(.*")
-	for i := 0; i < 10; i++ {
-		time.Sleep(100 * time.Millisecond)
-		inv := nsBin.Start("glob", "...")
-		line, _ := inv.ReadAll()
-		parts := re.FindStringSubmatch(line)
-		if len(parts) == 2 {
-			if want, got := security.JoinPatternName("root/child", proxyAddress), parts[1]; got != want {
-				t.Fatalf("got: %v, want: %v", got, want)
-			} else {
-				break
-			}
-		}
-	}
-	if got, want := proxy.Exists(), true; got != want {
-		t.Fatalf("got: %v, want: %v", got, want)
-	}
-}
-
-// The next set of tests are a complicated dance to test the correct
-// detection and logging of failed integration tests. The complication
-// is that we need to run these tests in a child process so that we
-// can examine their output, but not in the parent process. We use the
-// modules framework to do so, with the added twist that we need access
-// to an instance of testing.T which we obtain via a global variable.
-func IntegrationTestInChild(i *v23tests.T) {
-	fmt.Println("Hello")
-	sleep := i.BinaryFromPath("/bin/sleep")
-	sleep.Start("3600")
-	s2 := sleep.Start("6400")
-	sleep.Start("21600")
-	s2.Kill(syscall.SIGTERM)
-	s2.Wait(nil, nil)
-	i.FailNow()
-	panic("should never get here")
-}
-
-var globalT *testing.T
-
-func TestHelperProcess(t *testing.T) {
-	if modules.IsChildProcess() {
-		globalT = t
-		if err := modules.Dispatch(); err != nil {
-			t.Errorf("modules.Dispatch failed: %v", err)
-		}
-	}
-}
-
-var RunIntegrationTestInChild = modules.Register(func(env *modules.Env, args ...string) error {
-	v23tests.RunTest(globalT, IntegrationTestInChild)
-	return nil
-}, "RunIntegrationTestInChild")
-
-func TestDeferHandling(t *testing.T) {
-	t.Skip("https://v.io/i/686 -- test is flaky in Go1.5")
-	sh, _ := modules.NewShell(nil, nil, testing.Verbose(), t)
-	child, err := sh.Start(nil, RunIntegrationTestInChild, "--test.run=TestHelperProcess", "--v23.tests")
-	if err != nil {
-		t.Fatal(err)
-	}
-	child.Expect("Hello")
-	child.ExpectRE("--- FAIL: TestHelperProcess", -1)
-	for _, e := range []string{
-		".* 0: /bin/sleep: shutdown status: has not been shutdown",
-		".* 1: /bin/sleep: shutdown status: signal: terminated",
-		".* 2: /bin/sleep: shutdown status: has not been shutdown",
-	} {
-		child.ExpectRE(e, -1)
-	}
-	var stderr bytes.Buffer
-	if err := child.Shutdown(nil, &stderr); err != nil {
-		if !strings.Contains(err.Error(), "exit status 1") {
-			t.Fatal(err)
-		}
-	}
-	logger.Global().Infof("Child\n=============\n%s", stderr.String())
-	logger.Global().Infof("-----------------")
-}
-
-func TestInputRedirection(t *testing.T) {
-	testutil.InitRandGenerator(t.Logf)
-	env := v23tests.New(t)
-	defer env.Cleanup()
-
-	echo := env.BinaryFromPath("/bin/echo")
-	cat := env.BinaryFromPath("/bin/cat")
-
-	if want, got := "Hello, world!\n", cat.WithStdin(echo.Start("Hello, world!").Stdout()).Start().Output(); want != got {
-		t.Fatalf("unexpected output, got %q, want %q", got, want)
-	}
-
-	// Read something from a file.
-	{
-		want := "Hello from a file!"
-		f := env.NewTempFile()
-		f.WriteString(want)
-		f.Seek(0, 0)
-		if got := cat.WithStdin(f).Start().Output(); want != got {
-			t.Fatalf("unexpected output, got %q, want %q", got, want)
-		}
-	}
-
-	// Try it again with 1Mb.
-	{
-		want := testutil.RandomBytes(1 << 20)
-		expectedSum := sha1.Sum(want)
-		f := env.NewTempFile()
-		f.Write(want)
-		f.Seek(0, 0)
-		got := cat.WithStdin(f).Start().Output()
-		if len(got) != len(want) {
-			t.Fatalf("length mismatch, got %d but wanted %d", len(want), len(got))
-		}
-		actualSum := sha1.Sum([]byte(got))
-		if actualSum != expectedSum {
-			t.Fatalf("SHA-1 mismatch, got %x but wanted %x", actualSum, expectedSum)
-		}
-	}
-}
-
-func TestDirStack(t *testing.T) {
-	env := v23tests.New(t)
-	defer env.Cleanup()
-
-	home := os.Getenv("HOME")
-	if len(home) == 0 {
-		t.Fatalf("failed to read HOME environment variable")
-	}
-
-	getwd := func() string {
-		cwd, err := os.Getwd()
-		if err != nil {
-			t.Fatalf("Getwd() failed: %v", err)
-		}
-		return cwd
-	}
-
-	cwd := getwd()
-	if got, want := env.Pushd("/"), cwd; got != want {
-		t.Fatalf("got %v, want %v", got, want)
-	}
-	if got, want := env.Pushd(home), "/"; got != want {
-		t.Fatalf("got %v, want %v", got, want)
-	}
-	tcwd := getwd()
-	if got, want := tcwd, home; got != want {
-		t.Fatalf("got %v, want %v", got, want)
-	}
-	if got, want := env.Popd(), "/"; got != want {
-		t.Fatalf("got %v, want %v", got, want)
-	}
-	if got, want := env.Popd(), cwd; got != want {
-		t.Fatalf("got %v, want %v", got, want)
-	}
-	ncwd := getwd()
-	if got, want := ncwd, cwd; got != want {
-		t.Fatalf("got %v, want %v", got, want)
-	}
-}
-
-func TestRun(t *testing.T) {
-	env := v23tests.New(t)
-	defer env.Cleanup()
-
-	if got, want := env.Run("/bin/echo", "hello world"), "hello world"; got != want {
-		t.Fatalf("got %v, want %v", got, want)
-	}
-
-	echo := env.BinaryFromPath("/bin/echo")
-	if got, want := echo.Run("hello", "world"), "hello world"; got != want {
-		t.Fatalf("got %v, want %v", got, want)
-	}
-
-	sadEcho := echo.WithPrefixArgs("sad")
-	if got, want := sadEcho.Run("hello", "world"), "sad hello world"; got != want {
-		t.Fatalf("got %v, want %v", got, want)
-	}
-
-	happyEcho := echo.WithPrefixArgs("happy")
-	if got, want := happyEcho.Run("hello", "world"), "happy hello world"; got != want {
-		t.Fatalf("got %v, want %v", got, want)
-	}
-}
-
-type mockT struct {
-	msg    string
-	failed bool
-}
-
-func (m *mockT) Error(args ...interface{}) {
-	m.msg = fmt.Sprint(args...)
-	m.failed = true
-}
-
-func (m *mockT) Errorf(format string, args ...interface{}) {
-	m.msg = fmt.Sprintf(format, args...)
-	m.failed = true
-}
-
-func (m *mockT) Fail() { panic("Fail") }
-
-func (m *mockT) FailNow() { panic("FailNow") }
-
-func (m *mockT) Failed() bool { return m.failed }
-
-func (m *mockT) Fatal(args ...interface{}) {
-	panic(fmt.Sprint(args...))
-}
-
-func (m *mockT) Fatalf(format string, args ...interface{}) {
-	panic(fmt.Sprintf(format, args...))
-}
-
-func (m *mockT) Log(args ...interface{}) {}
-
-func (m *mockT) Logf(format string, args ...interface{}) {}
-
-func (m *mockT) Skip(args ...interface{}) {}
-
-func (m *mockT) SkipNow() {}
-
-func (m *mockT) Skipf(format string, args ...interface{}) {}
-
-func (m *mockT) Skipped() bool { return false }
-
-func TestRunFailFromPath(t *testing.T) {
-	mock := &mockT{}
-	env := v23tests.New(mock)
-	defer env.Cleanup()
-
-	defer func() {
-		msg := recover().(string)
-		// this, and the tests below are intended to ensure that line #s
-		// are captured and reported correctly.
-		if got, want := msg, "v23tests_test.go:304"; !strings.Contains(got, want) {
-			t.Fatalf("%q does not contain %q", got, want)
-		}
-		if got, want := msg, "fork/exec /bin/echox: no such file or directory"; !strings.Contains(got, want) {
-			t.Fatalf("%q does not contain %q", got, want)
-		}
-	}()
-	env.Run("/bin/echox", "hello world")
-}
-
-func TestRunFail(t *testing.T) {
-	mock := &mockT{}
-	env := v23tests.New(mock)
-	defer env.Cleanup()
-
-	// Fail fast.
-	sh := env.Shell()
-	opts := sh.DefaultStartOpts()
-	opts.StartTimeout = 100 * time.Millisecond
-	sh.SetDefaultStartOpts(opts)
-	defer func() {
-		msg := recover().(string)
-		if got, want := msg, "v23tests_test.go:326"; !strings.Contains(got, want) {
-			t.Fatalf("%q does not contain %q", got, want)
-		}
-		if got, want := msg, "StartWithOpts"; !strings.Contains(got, want) {
-			t.Fatalf("%q does not contain %q", got, want)
-		}
-	}()
-	v23tests.RunRootMT(env, "--xxv23.tcp.address=127.0.0.1:0")
-}
-
-func TestWaitTimeout(t *testing.T) {
-	env := v23tests.New(&mockT{})
-	defer env.Cleanup()
-
-	iterations := 0
-	sleeper := func() (interface{}, error) {
-		iterations++
-		return nil, nil
-	}
-
-	defer func() {
-		if iterations == 0 {
-			t.Fatalf("our sleeper didn't get to run")
-		}
-		if got, want := recover().(string), "v23tests_test.go:347: timed out"; !strings.Contains(got, want) {
-			t.Fatalf("%q does not contain %q", got, want)
-		}
-	}()
-	env.WaitFor(sleeper, time.Millisecond, 50*time.Millisecond)
-}
-
-func TestWaitAsyncTimeout(t *testing.T) {
-	env := v23tests.New(&mockT{})
-	defer env.Cleanup()
-
-	iterations := 0
-	sleeper := func() (interface{}, error) {
-		time.Sleep(time.Minute)
-		iterations++
-		return nil, nil
-	}
-
-	defer func() {
-		if iterations != 0 {
-			t.Fatalf("our sleeper got to run")
-		}
-		if got, want := recover().(string), "v23tests_test.go:369: timed out"; !strings.Contains(got, want) {
-			t.Fatalf("%q does not contain %q", got, want)
-		}
-	}()
-	env.WaitForAsync(sleeper, time.Millisecond, 50*time.Millisecond)
-}
-
-func TestWaitFor(t *testing.T) {
-	env := v23tests.New(t)
-	defer env.Cleanup()
-	iterations := 0
-	countIn5s := func() (interface{}, error) {
-		iterations++
-		if iterations%5 == 0 {
-			return iterations, nil
-		}
-		return nil, nil
-	}
-
-	r := env.WaitFor(countIn5s, time.Millisecond, 50*time.Millisecond)
-	if got, want := r.(int), 5; got != want {
-		env.Fatalf("got %d, want %d", got, want)
-	}
-
-	r = env.WaitForAsync(countIn5s, time.Millisecond, 50*time.Millisecond)
-	if got, want := r.(int), 10; got != want {
-		env.Fatalf("got %d, want %d", got, want)
-	}
-}
-
-func builder(t *testing.T) (string, string) {
-	env := v23tests.New(t)
-	defer env.Cleanup()
-	bin := env.BuildGoPkg("v.io/x/ref/test/v23tests")
-	return env.BinDir(), bin.Path()
-}
-
-func TestCachedBuild(t *testing.T) {
-	cleanup := v23tests.UseSharedBinDir()
-	defer cleanup()
-	defer os.Setenv("V23_BIN_DIR", "")
-
-	bin1, path1 := builder(t)
-	bin2, path2 := builder(t)
-
-	if bin1 != bin2 {
-		t.Fatalf("caching failed, bin dirs differ: %q != %q", bin1, bin2)
-	}
-
-	if path1 != path2 {
-		t.Fatalf("caching failed, paths differ: %q != %q", path1, path2)
-	}
-}
-
-func TestUncachedBuild(t *testing.T) {
-	bin1, path1 := builder(t)
-	bin2, path2 := builder(t)
-
-	if bin1 == bin2 {
-		t.Fatalf("failed, bin dirs are the same: %q != %q", bin1, bin2)
-	}
-
-	if path1 == path2 {
-		t.Fatalf("failed, paths are the same: %q != %q", path1, path2)
-	}
-}
-
-func TestShutdownAndCleanupTogetherDontHang(t *testing.T) {
-	env := v23tests.New(t)
-	defer env.Cleanup()
-
-	bash := env.BinaryFromPath("/bin/bash")
-	if want, got := "hello world\n", bash.Start("-c", "echo hello world").Output(); want != got {
-		t.Fatalf("unexpected output, want %s, got %s", want, got)
-	}
-
-	inv := bash.Start("-c", "echo hello world")
-	var buf bytes.Buffer
-	inv.Shutdown(&buf, nil)
-	if want, got := "hello world\n", buf.String(); want != got {
-		t.Fatalf("unexpected output, want %s, got %s", want, got)
-	}
-	// Make sure that we can call Shutdown and Cleanup without hanging.
-}