Merge branch 'master' of /tmp/staging2/roadmap.go.syncbase into sb
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ebef272
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+/.v23
+**/.DS_Store
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..574583c
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,9 @@
+# This is the official list of Vanadium authors for copyright purposes.
+# This file is distinct from the CONTRIBUTORS files.
+# See the latter for an explanation.
+
+# Names should be added to this file as:
+#   Name or Organization <email address>
+# The email address is not required for organizations.
+
+# Please keep the list sorted.
diff --git a/CONTRIBUTORS b/CONTRIBUTORS
new file mode 100644
index 0000000..08a0d55
--- /dev/null
+++ b/CONTRIBUTORS
@@ -0,0 +1,46 @@
+# People who have agreed to one of the CLAs and can contribute patches.
+# The AUTHORS file lists the copyright holders; this file
+# lists people.  For example, Google employees are listed here
+# but not in AUTHORS, because Google holds the copyright.
+#
+# https://developers.google.com/open-source/cla/individual
+# https://developers.google.com/open-source/cla/corporate
+#
+# Names should be added to this file as:
+#     Name <email address>
+Adam Sadovsky <sadovsky@google.com>
+Alex Fandrianto <alexfandrianto@google.com>
+Ali Ghassemi <aghassemi@google.com>
+Andres Erbsen <andreser@google.com>
+Ankur Taly <ataly@google.com>
+Arup Mukherjee <arup@google.com>
+Asim Shankar <ashankar@google.com>
+Benjamin Prosnitz <bprosnitz@google.com>
+Bogdan Caprita <caprita@google.com>
+Cosmos Nicolaou <cnicolaou@google.com>
+David Presotto <p@google.com>
+Gautham Thambidorai <gauthamt@google.com>
+Himabindu Pucha <hpucha@google.com>
+James Home <jameshome@google.com>
+James Ring <sjr@google.com>
+Jason Campbell <jasoncampbell@google.com>
+Jason Hickey <jyh@google.com>
+Jeffrey Regan <jregan@google.com>
+Jing Jin <jingjin@google.com>
+Jiri Simsa <jsimsa@google.com>
+Jungho Ahn <jhahn@google.com>
+Ken Ashcraft <kash@google.com>
+Matt Rosencrantz <mattr@google.com>
+Mehrdad Afshari <leakycode@google.com>
+Mike Burrows <m3b@google.com>
+Nicolas LaCasse <nlacasse@google.com>
+Raja Daoud <rdaoud@google.com>
+Robert Kroeger <rjkroege@google.com>
+Robin Thellend <rthellend@google.com>
+Ryan Brown <ribrdb@google.com>
+Sergey Rogulenko <rogulenko@google.com>
+Shyam Jayaraman <bjornick@google.com>
+Srdjan Petrovic <spetrovic@google.com>
+Suharsh Sivakumar <suharshs@google.com>
+Tilak Sharma <tilaks@google.com>
+Todd Wang <toddw@google.com>
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..411db13
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2015 The Vanadium Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/PATENTS b/PATENTS
new file mode 100644
index 0000000..d52cc55
--- /dev/null
+++ b/PATENTS
@@ -0,0 +1,22 @@
+Additional IP Rights Grant (Patents)
+
+"This implementation" means the copyrightable works distributed by
+Google as part of the Vanadium project.
+
+Google hereby grants to You a perpetual, worldwide, non-exclusive,
+no-charge, royalty-free, irrevocable (except as stated in this section)
+patent license to make, have made, use, offer to sell, sell, import,
+transfer and otherwise run, modify and propagate the contents of this
+implementation of Vanadium, where such license applies only to those patent
+claims, both currently owned or controlled by Google and acquired in
+the future, licensable by Google that are necessarily infringed by this
+implementation of Vanadium. This grant does not include claims that would be
+infringed only as a consequence of further modification of this
+implementation. If you or your agent or exclusive licensee institute or
+order or agree to the institution of patent litigation against any
+entity (including a cross-claim or counterclaim in a lawsuit) alleging
+that this implementation of Vanadium or any code incorporated within this
+implementation of Vanadium constitutes direct or contributory patent
+infringement, or inducement of patent infringement, then any patent
+rights granted to you under this License for this implementation of Vanadium
+shall terminate as of the date such litigation is filed.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..4c8c809
--- /dev/null
+++ b/README.md
@@ -0,0 +1,9 @@
+# Vanadium
+
+This repository contains a reference implementation of the [Vanadium] APIs.
+
+Unlike the APIs in https://github.com/vanadium/go.v23, which promises to
+provide [backward compatibility] this repository makes no such promises.
+
+[Vanadium]: https://v.io
+[backward compatibility]: https://godoc.v.io/pkg/v.io/v23
diff --git a/VERSION b/VERSION
new file mode 100644
index 0000000..6d6ff85
--- /dev/null
+++ b/VERSION
@@ -0,0 +1 @@
+v23-0.1
diff --git a/cmd/gclogs/doc.go b/cmd/gclogs/doc.go
new file mode 100644
index 0000000..f9ede8b
--- /dev/null
+++ b/cmd/gclogs/doc.go
@@ -0,0 +1,39 @@
+// 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
+
+/*
+Command gclogs safely deletes old log files.
+
+It looks for file names that match the format of files produced by the vlog
+package, and deletes the ones that have not changed in the amount of time
+specified by the --cutoff flag.
+
+Only files produced by the same user as the one running the gclogs command are
+considered for deletion.
+
+Usage:
+   gclogs [flags] <dir> ...
+
+<dir> ... A list of directories where to look for log files.
+
+The gclogs flags are:
+ -cutoff=24h0m0s
+   The age cut-off for a log file to be considered for garbage collection.
+ -n=false
+   If true, log files that would be deleted are shown on stdout, but not
+   actually deleted.
+ -program=.*
+   A regular expression to apply to the program part of the log file name, e.g
+   ".*test".
+ -verbose=false
+   If true, each deleted file is shown on stdout.
+
+The global flags are:
+ -metadata=<just specify -metadata to activate>
+   Displays metadata for the program and exits.
+*/
+package main
diff --git a/cmd/gclogs/format.go b/cmd/gclogs/format.go
new file mode 100644
index 0000000..ed18f48
--- /dev/null
+++ b/cmd/gclogs/format.go
@@ -0,0 +1,73 @@
+// 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 (
+	"errors"
+	"os"
+	"path/filepath"
+	"regexp"
+	"strconv"
+	"syscall"
+	"time"
+)
+
+var (
+	// The format of the log file names is:
+	// program.host.user.log.logger.tag.YYYYMMDD-HHmmss.pid
+	logFileRE = regexp.MustCompile(`^(.*)\.([^.]*)\.([^.]*)\.log\.([^.]*)\.([^.]*)\.(........-......)\.([0-9]*)$`)
+)
+
+type logFile struct {
+	// TODO(rthellend): Some of these fields are not used by anything yet,
+	// but they will be eventually when we need to sort the log files
+	// associated with a given instance.
+	symlink                          bool
+	program, host, user, logger, tag string
+	time                             time.Time
+	pid                              int
+}
+
+func parseFileInfo(dir string, fileInfo os.FileInfo) (*logFile, error) {
+	fileName := fileInfo.Name()
+	if fileInfo.Mode()&os.ModeSymlink != 0 {
+		buf := make([]byte, syscall.NAME_MAX)
+		n, err := syscall.Readlink(filepath.Join(dir, fileName), buf)
+		if err != nil {
+			return nil, err
+		}
+		linkName := string(buf[:n])
+		lf, err := parseFileName(linkName)
+		if err != nil {
+			return nil, err
+		}
+		lf.symlink = true
+		return lf, nil
+	}
+	return parseFileName(fileName)
+}
+
+func parseFileName(fileName string) (*logFile, error) {
+	if m := logFileRE.FindStringSubmatch(fileName); len(m) != 0 {
+		t, err := time.ParseInLocation("20060102-150405", m[6], time.Local)
+		if err != nil {
+			return nil, err
+		}
+		pid, err := strconv.ParseInt(m[7], 10, 32)
+		if err != nil {
+			return nil, err
+		}
+		return &logFile{
+			program: m[1],
+			host:    m[2],
+			user:    m[3],
+			logger:  m[4],
+			tag:     m[5],
+			time:    t,
+			pid:     int(pid),
+		}, nil
+	}
+	return nil, errors.New("not a recognized log file name")
+}
diff --git a/cmd/gclogs/format_test.go b/cmd/gclogs/format_test.go
new file mode 100644
index 0000000..201c34a
--- /dev/null
+++ b/cmd/gclogs/format_test.go
@@ -0,0 +1,101 @@
+// 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 (
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"reflect"
+	"testing"
+	"time"
+)
+
+func TestParseFileNameNoError(t *testing.T) {
+	testcases := []struct {
+		filename string
+		lf       *logFile
+	}{
+		{
+			"program.host.user.log.vanadium.INFO.20141204-131502.12345",
+			&logFile{false, "program", "host", "user", "vanadium", "INFO", time.Date(2014, 12, 4, 13, 15, 2, 0, time.Local), 12345},
+		},
+		{
+			"prog.test.host-name.user.log.vanadium.ERROR.20141204-131502.12345",
+			&logFile{false, "prog.test", "host-name", "user", "vanadium", "ERROR", time.Date(2014, 12, 4, 13, 15, 2, 0, time.Local), 12345},
+		},
+	}
+	for _, tc := range testcases {
+		lf, err := parseFileName(tc.filename)
+		if err != nil {
+			t.Errorf("unexpected error for %q: %v", tc.filename, err)
+		}
+		if !reflect.DeepEqual(tc.lf, lf) {
+			t.Errorf("unexpected result: got %+v, expected %+v", lf, tc.lf)
+		}
+	}
+}
+
+func TestParseFileNameError(t *testing.T) {
+	testcases := []string{
+		"program.host.user.log.vanadium.INFO.20141204-131502",
+		"prog.test.host-name.user.log.vanadium.20141204-131502.12345",
+		"foo.txt",
+	}
+	for _, tc := range testcases {
+		if _, err := parseFileName(tc); err == nil {
+			t.Errorf("unexpected success for %q", tc)
+		}
+	}
+}
+
+func TestParseFileInfo(t *testing.T) {
+	tmpdir, err := ioutil.TempDir("", "parse-file-info-")
+	if err != nil {
+		t.Fatalf("ioutil.TempDir failed: %v", err)
+	}
+	defer os.RemoveAll(tmpdir)
+
+	name := "program.host.user.log.vanadium.INFO.20141204-131502.12345"
+	if err := ioutil.WriteFile(filepath.Join(tmpdir, name), []byte{}, 0644); err != nil {
+		t.Fatalf("ioutil.WriteFile failed: %v", err)
+	}
+	link := "program.INFO"
+	if err := os.Symlink(name, filepath.Join(tmpdir, link)); err != nil {
+		t.Fatalf("os.Symlink failed: %v", err)
+	}
+
+	// Test regular file.
+	{
+		fi, err := os.Lstat(filepath.Join(tmpdir, name))
+		if err != nil {
+			t.Fatalf("os.Lstat failed: %v", err)
+		}
+		lf, err := parseFileInfo(tmpdir, fi)
+		if err != nil {
+			t.Errorf("parseFileInfo(%v, %v) failed: %v", tmpdir, fi, err)
+		}
+		expected := &logFile{false, "program", "host", "user", "vanadium", "INFO", time.Date(2014, 12, 4, 13, 15, 2, 0, time.Local), 12345}
+		if !reflect.DeepEqual(lf, expected) {
+			t.Errorf("unexpected result: got %+v, expected %+v", lf, expected)
+		}
+	}
+
+	// Test symlink.
+	{
+		fi, err := os.Lstat(filepath.Join(tmpdir, link))
+		if err != nil {
+			t.Fatalf("os.Lstat failed: %v", err)
+		}
+		lf, err := parseFileInfo(tmpdir, fi)
+		if err != nil {
+			t.Errorf("parseFileInfo(%v, %v) failed: %v", tmpdir, fi, err)
+		}
+		expected := &logFile{true, "program", "host", "user", "vanadium", "INFO", time.Date(2014, 12, 4, 13, 15, 2, 0, time.Local), 12345}
+		if !reflect.DeepEqual(lf, expected) {
+			t.Errorf("unexpected result: got %+v, expected %+v", lf, expected)
+		}
+	}
+}
diff --git a/cmd/gclogs/gclogs.go b/cmd/gclogs/gclogs.go
new file mode 100644
index 0000000..144752e
--- /dev/null
+++ b/cmd/gclogs/gclogs.go
@@ -0,0 +1,168 @@
+// 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.
+
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go . -help
+
+package main
+
+import (
+	"fmt"
+	"io"
+	"os"
+	"os/user"
+	"path/filepath"
+	"regexp"
+	"time"
+
+	"v.io/x/lib/cmdline"
+)
+
+var (
+	flagCutoff   time.Duration
+	flagProgname string
+	flagVerbose  bool
+	flagDryrun   bool
+
+	cmdGCLogs = &cmdline.Command{
+		Runner: cmdline.RunnerFunc(garbageCollectLogs),
+		Name:   "gclogs",
+		Short:  "safely deletes old log files",
+		Long: `
+Command gclogs safely deletes old log files.
+
+It looks for file names that match the format of files produced by the vlog
+package, and deletes the ones that have not changed in the amount of time
+specified by the --cutoff flag.
+
+Only files produced by the same user as the one running the gclogs command
+are considered for deletion.
+`,
+		ArgsName: "<dir> ...",
+		ArgsLong: "<dir> ... A list of directories where to look for log files.",
+	}
+)
+
+func init() {
+	cmdGCLogs.Flags.DurationVar(&flagCutoff, "cutoff", 24*time.Hour, "The age cut-off for a log file to be considered for garbage collection.")
+	cmdGCLogs.Flags.StringVar(&flagProgname, "program", ".*", `A regular expression to apply to the program part of the log file name, e.g ".*test".`)
+	cmdGCLogs.Flags.BoolVar(&flagVerbose, "verbose", false, "If true, each deleted file is shown on stdout.")
+	cmdGCLogs.Flags.BoolVar(&flagDryrun, "n", false, "If true, log files that would be deleted are shown on stdout, but not actually deleted.")
+}
+
+func main() {
+	cmdline.Main(cmdGCLogs)
+}
+
+func garbageCollectLogs(env *cmdline.Env, args []string) error {
+	if len(args) == 0 {
+		env.UsageErrorf("gclogs requires at least one argument")
+	}
+	timeCutoff := time.Now().Add(-flagCutoff)
+	currentUser, err := user.Current()
+	if err != nil {
+		return err
+	}
+	programRE, err := regexp.Compile(flagProgname)
+	if err != nil {
+		return err
+	}
+	var lastErr error
+	for _, logdir := range args {
+		if err := processDirectory(env, logdir, timeCutoff, programRE, currentUser.Username); err != nil {
+			lastErr = err
+		}
+	}
+	return lastErr
+}
+
+func processDirectory(env *cmdline.Env, logdir string, timeCutoff time.Time, programRE *regexp.Regexp, username string) error {
+	fmt.Fprintf(env.Stdout, "Processing: %q\n", logdir)
+
+	f, err := os.Open(logdir)
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+
+	var lastErr error
+	deleted := 0
+	symlinks := []string{}
+	for {
+		fi, err := f.Readdir(100)
+		if err == io.EOF {
+			break
+		}
+		if err != nil {
+			lastErr = err
+			break
+		}
+		for _, file := range fi {
+			fullname := filepath.Join(logdir, file.Name())
+			if file.IsDir() {
+				if flagVerbose {
+					fmt.Fprintf(env.Stdout, "Skipped directory: %q\n", fullname)
+				}
+				continue
+			}
+			lf, err := parseFileInfo(logdir, file)
+			if err != nil {
+				if flagVerbose {
+					fmt.Fprintf(env.Stdout, "Not a log file: %q\n", fullname)
+				}
+				continue
+			}
+			if lf.user != username {
+				if flagVerbose {
+					fmt.Fprintf(env.Stdout, "Skipped log file created by other user: %q\n", fullname)
+				}
+				continue
+			}
+			if !programRE.MatchString(lf.program) {
+				if flagVerbose {
+					fmt.Fprintf(env.Stdout, "Skipped log file doesn't match %q: %q\n", flagProgname, fullname)
+				}
+				continue
+			}
+			if lf.symlink {
+				symlinks = append(symlinks, fullname)
+				continue
+			}
+			if file.ModTime().Before(timeCutoff) {
+				if flagDryrun {
+					fmt.Fprintf(env.Stdout, "Would delete %q\n", fullname)
+					continue
+				}
+				if flagVerbose {
+					fmt.Fprintf(env.Stdout, "Deleting %q\n", fullname)
+				}
+				if err := os.Remove(fullname); err != nil {
+					lastErr = err
+				} else {
+					deleted++
+				}
+			}
+		}
+	}
+	// Delete broken links.
+	for _, sl := range symlinks {
+		if _, err := os.Stat(sl); err != nil && os.IsNotExist(err) {
+			if flagDryrun {
+				fmt.Fprintf(env.Stdout, "Would delete symlink %q\n", sl)
+				continue
+			}
+			if flagVerbose {
+				fmt.Fprintf(env.Stdout, "Deleting symlink %q\n", sl)
+			}
+			if err := os.Remove(sl); err != nil {
+				lastErr = err
+			} else {
+				deleted++
+			}
+		}
+
+	}
+	fmt.Fprintf(env.Stdout, "Number of files deleted: %d\n", deleted)
+	return lastErr
+}
diff --git a/cmd/gclogs/gclogs_test.go b/cmd/gclogs/gclogs_test.go
new file mode 100644
index 0000000..c8923e4
--- /dev/null
+++ b/cmd/gclogs/gclogs_test.go
@@ -0,0 +1,180 @@
+// 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 (
+	"bytes"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"os/user"
+	"path/filepath"
+	"sort"
+	"strings"
+	"testing"
+	"time"
+
+	"v.io/x/lib/cmdline"
+)
+
+func setup(t *testing.T, workdir, username string) (tmpdir string) {
+	var err error
+	tmpdir, err = ioutil.TempDir(workdir, "gclogs-test-setup-")
+	if err != nil {
+		t.Fatalf("ioutil.TempDir failed: %v", err)
+	}
+	logfiles := []struct {
+		name string
+		link string
+		age  time.Duration
+	}{
+		{"prog1.host.%USER%.log.vanadium.INFO.20141204-131502.12345", "", 4 * time.Hour},
+		{"prog1.host.%USER%.log.vanadium.INFO.20141204-145040.23456", "prog1.INFO", 1 * time.Hour},
+		{"prog1.host.%USER%.log.vanadium.ERROR.20141204-145040.23456", "prog1.ERROR", 1 * time.Hour},
+		{"prog2.host.%USER%.log.vanadium.INFO.20141204-135040.23456", "prog2.INFO", 4 * time.Hour},
+		{"prog3.host.otheruser.log.vanadium.INFO.20141204-135040.23456", "prog3.INFO", 1 * time.Hour},
+		{"foo.txt", "", 1 * time.Hour},
+		{"bar.txt", "", 1 * time.Hour},
+	}
+	for _, l := range logfiles {
+		l.name = strings.Replace(l.name, "%USER%", username, -1)
+		filename := filepath.Join(tmpdir, l.name)
+		if err := ioutil.WriteFile(filename, []byte{}, 0644); err != nil {
+			t.Fatalf("ioutil.WriteFile failed: %v", err)
+		}
+		mtime := time.Now().Add(-l.age)
+		if err := os.Chtimes(filename, mtime, mtime); err != nil {
+			t.Fatalf("os.Chtimes failed: %v", err)
+		}
+		if l.link != "" {
+			if err := os.Symlink(l.name, filepath.Join(tmpdir, l.link)); err != nil {
+				t.Fatalf("os.Symlink failed: %v", err)
+			}
+		}
+	}
+	if err := os.Mkdir(filepath.Join(tmpdir, "subdir"), 0700); err != nil {
+		t.Fatalf("os.Mkdir failed: %v", err)
+	}
+	return
+}
+
+func TestGCLogs(t *testing.T) {
+	workdir, err := ioutil.TempDir("", "parse-file-info-")
+	if err != nil {
+		t.Fatalf("ioutil.TempDir failed: %v", err)
+	}
+	defer os.RemoveAll(workdir)
+
+	u, err := user.Current()
+	if err != nil {
+		t.Fatalf("user.Current failed: %v", err)
+	}
+
+	testcases := []struct {
+		cutoff   time.Duration
+		verbose  bool
+		dryrun   bool
+		expected []string
+	}{
+		{
+			cutoff:  6 * time.Hour,
+			verbose: false,
+			dryrun:  false,
+			expected: []string{
+				`Processing: "%TESTDIR%"`,
+				`Number of files deleted: 0`,
+			},
+		},
+		{
+			cutoff:  2 * time.Hour,
+			verbose: false,
+			dryrun:  true,
+			expected: []string{
+				`Processing: "%TESTDIR%"`,
+				`Would delete "%TESTDIR%/prog1.host.%USER%.log.vanadium.INFO.20141204-131502.12345"`,
+				`Would delete "%TESTDIR%/prog2.host.%USER%.log.vanadium.INFO.20141204-135040.23456"`,
+				`Number of files deleted: 0`,
+			},
+		},
+		{
+			cutoff:  2 * time.Hour,
+			verbose: false,
+			dryrun:  false,
+			expected: []string{
+				`Processing: "%TESTDIR%"`,
+				`Number of files deleted: 3`,
+			},
+		},
+		{
+			cutoff:  2 * time.Hour,
+			verbose: true,
+			dryrun:  false,
+			expected: []string{
+				`Processing: "%TESTDIR%"`,
+				`Deleting "%TESTDIR%/prog1.host.%USER%.log.vanadium.INFO.20141204-131502.12345"`,
+				`Deleting "%TESTDIR%/prog2.host.%USER%.log.vanadium.INFO.20141204-135040.23456"`,
+				`Deleting symlink "%TESTDIR%/prog2.INFO"`,
+				`Not a log file: "%TESTDIR%/bar.txt"`,
+				`Not a log file: "%TESTDIR%/foo.txt"`,
+				`Skipped directory: "%TESTDIR%/subdir"`,
+				`Skipped log file created by other user: "%TESTDIR%/prog3.INFO"`,
+				`Skipped log file created by other user: "%TESTDIR%/prog3.host.otheruser.log.vanadium.INFO.20141204-135040.23456"`,
+				`Number of files deleted: 3`,
+			},
+		},
+		{
+			cutoff:  time.Minute,
+			verbose: true,
+			dryrun:  false,
+			expected: []string{
+				`Processing: "%TESTDIR%"`,
+				`Deleting "%TESTDIR%/prog1.host.%USER%.log.vanadium.ERROR.20141204-145040.23456"`,
+				`Deleting "%TESTDIR%/prog1.host.%USER%.log.vanadium.INFO.20141204-131502.12345"`,
+				`Deleting "%TESTDIR%/prog1.host.%USER%.log.vanadium.INFO.20141204-145040.23456"`,
+				`Deleting "%TESTDIR%/prog2.host.%USER%.log.vanadium.INFO.20141204-135040.23456"`,
+				`Deleting symlink "%TESTDIR%/prog1.ERROR"`,
+				`Deleting symlink "%TESTDIR%/prog1.INFO"`,
+				`Deleting symlink "%TESTDIR%/prog2.INFO"`,
+				`Not a log file: "%TESTDIR%/bar.txt"`,
+				`Not a log file: "%TESTDIR%/foo.txt"`,
+				`Skipped directory: "%TESTDIR%/subdir"`,
+				`Skipped log file created by other user: "%TESTDIR%/prog3.INFO"`,
+				`Skipped log file created by other user: "%TESTDIR%/prog3.host.otheruser.log.vanadium.INFO.20141204-135040.23456"`,
+				`Number of files deleted: 7`,
+			},
+		},
+		{
+			cutoff:  time.Minute,
+			verbose: false,
+			dryrun:  false,
+			expected: []string{
+				`Processing: "%TESTDIR%"`,
+				`Number of files deleted: 7`,
+			},
+		},
+	}
+	for _, tc := range testcases {
+		testdir := setup(t, workdir, u.Username)
+		cutoff := fmt.Sprintf("--cutoff=%s", tc.cutoff)
+		verbose := fmt.Sprintf("--verbose=%v", tc.verbose)
+		dryrun := fmt.Sprintf("--n=%v", tc.dryrun)
+		var stdout, stderr bytes.Buffer
+		env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
+		if err := cmdline.ParseAndRun(cmdGCLogs, env, []string{cutoff, verbose, dryrun, testdir}); err != nil {
+			t.Fatalf("%v: %v", stderr.String(), err)
+		}
+		gotsl := strings.Split(stdout.String(), "\n")
+		if len(gotsl) >= 2 {
+			sort.Strings(gotsl[1 : len(gotsl)-2])
+		}
+		got := strings.Join(gotsl, "\n")
+		expected := strings.Join(tc.expected, "\n") + "\n"
+		expected = strings.Replace(expected, "%TESTDIR%", testdir, -1)
+		expected = strings.Replace(expected, "%USER%", u.Username, -1)
+		if got != expected {
+			t.Errorf("Unexpected result for (%v, %v): got %q, expected %q", tc.cutoff, tc.verbose, got, expected)
+		}
+	}
+}
diff --git a/cmd/mounttable/doc.go b/cmd/mounttable/doc.go
new file mode 100644
index 0000000..1ccdb36
--- /dev/null
+++ b/cmd/mounttable/doc.go
@@ -0,0 +1,138 @@
+// 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
+
+/*
+Command mounttable sends commands to Vanadium mounttable services.
+
+Usage:
+   mounttable <command>
+
+The mounttable commands are:
+   glob        returns all matching entries in the mount table
+   mount       Mounts a server <name> onto a mount table
+   unmount     removes server <name> from the mount table
+   resolvestep takes the next step in resolving a name.
+   help        Display help for commands or topics
+
+The global flags are:
+ -v23.namespace.root=[/(dev.v.io/role/vprod/service/mounttabled)@ns.dev.v.io:8101]
+   local namespace root; can be repeated to provided multiple roots
+
+ -alsologtostderr=true
+   log to standard error as well as files
+ -log_backtrace_at=:0
+   when logging hits line file:N, emit a stack trace
+ -log_dir=
+   if non-empty, write log files to this directory
+ -logtostderr=false
+   log to standard error instead of files
+ -max_stack_buf_size=4292608
+   max size in bytes of the buffer to use for logging stack traces
+ -metadata=<just specify -metadata to activate>
+   Displays metadata for the program and exits.
+ -stderrthreshold=2
+   logs at or above this threshold go to stderr
+ -v=0
+   log level for V logs
+ -v23.credentials=
+   directory to use for storing security credentials
+ -v23.i18n-catalogue=
+   18n catalogue files to load, comma separated
+ -v23.proxy=
+   object name of proxy service to use to export services across network
+   boundaries
+ -v23.tcp.address=
+   address to listen on
+ -v23.tcp.protocol=wsh
+   protocol to listen with
+ -v23.vtrace.cache-size=1024
+   The number of vtrace traces to store in memory.
+ -v23.vtrace.collect-regexp=
+   Spans and annotations that match this regular expression will trigger trace
+   collection.
+ -v23.vtrace.dump-on-shutdown=true
+   If true, dump all stored traces on runtime shutdown.
+ -v23.vtrace.sample-rate=0
+   Rate (from 0.0 to 1.0) to sample vtrace traces.
+ -vmodule=
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
+
+Mounttable glob - returns all matching entries in the mount table
+
+returns all matching entries in the mount table
+
+Usage:
+   mounttable glob [<mount name>] <pattern>
+
+<mount name> is a mount name on a mount table.  Defaults to namespace root.
+<pattern> is a glob pattern that is matched against all the entries below the
+specified mount name.
+
+Mounttable mount
+
+Mounts a server <name> onto a mount table
+
+Usage:
+   mounttable mount <mount name> <name> <ttl> [M|R]
+
+<mount name> is a mount name on a mount table.
+
+<name> is the rooted object name of the server.
+
+<ttl> is the TTL of the new entry. It is a decimal number followed by a unit
+suffix (s, m, h). A value of 0s represents an infinite duration.
+
+[M|R] are mount options. M indicates that <name> is a mounttable. R indicates
+that existing entries should be removed.
+
+Mounttable unmount
+
+removes server <name> from the mount table
+
+Usage:
+   mounttable unmount <mount name> <name>
+
+<mount name> is a mount name on a mount table. <name> is the rooted object name
+of the server.
+
+Mounttable resolvestep
+
+takes the next step in resolving a name.
+
+Usage:
+   mounttable resolvestep <mount name>
+
+<mount name> is a mount name on a mount table.
+
+Mounttable help - Display help for commands or topics
+
+Help with no args displays the usage of the parent command.
+
+Help with args displays the usage of the specified sub-command or help topic.
+
+"help ..." recursively displays help for all commands and topics.
+
+Usage:
+   mounttable help [flags] [command/topic ...]
+
+[command/topic ...] optionally identifies a specific sub-command or help topic.
+
+The mounttable help flags are:
+ -style=compact
+   The formatting style for help output:
+      compact - Good for compact cmdline output.
+      full    - Good for cmdline output, shows all global flags.
+      godoc   - Good for godoc processing.
+   Override the default by setting the CMDLINE_STYLE environment variable.
+ -width=<terminal width>
+   Format output to this target width in runes, or unlimited if width < 0.
+   Defaults to the terminal width if available.  Override the default by setting
+   the CMDLINE_WIDTH environment variable.
+*/
+package main
diff --git a/cmd/mounttable/impl.go b/cmd/mounttable/impl.go
new file mode 100644
index 0000000..3653e16
--- /dev/null
+++ b/cmd/mounttable/impl.go
@@ -0,0 +1,225 @@
+// 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.
+
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go .
+
+package main
+
+import (
+	"errors"
+	"fmt"
+	"regexp"
+	"time"
+
+	"v.io/x/lib/cmdline"
+
+	"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/ref/lib/v23cmd"
+	_ "v.io/x/ref/runtime/factories/generic"
+)
+
+func main() {
+	cmdline.HideGlobalFlagsExcept(regexp.MustCompile(`^v23\.namespace\.root$`))
+	cmdline.Main(cmdRoot)
+}
+
+var cmdGlob = &cmdline.Command{
+	Runner:   v23cmd.RunnerFunc(runGlob),
+	Name:     "glob",
+	Short:    "returns all matching entries in the mount table",
+	Long:     "returns all matching entries in the mount table",
+	ArgsName: "[<mount name>] <pattern>",
+	ArgsLong: `
+<mount name> is a mount name on a mount table.  Defaults to namespace root.
+<pattern> is a glob pattern that is matched against all the entries below the
+specified mount name.
+`,
+}
+
+func runGlob(ctx *context.T, env *cmdline.Env, args []string) error {
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
+	defer cancel()
+
+	if len(args) == 1 {
+		roots := v23.GetNamespace(ctx).Roots()
+		if len(roots) == 0 {
+			return errors.New("no namespace root")
+		}
+		args = append([]string{roots[0]}, args...)
+	}
+	if expected, got := 2, len(args); expected != got {
+		return env.UsageErrorf("glob: incorrect number of arguments, expected %d, got %d", expected, got)
+	}
+
+	name, pattern := args[0], args[1]
+	client := v23.GetClient(ctx)
+	call, err := client.StartCall(ctx, name, rpc.GlobMethod, []interface{}{pattern}, options.NoResolve{})
+	if err != nil {
+		return err
+	}
+	for {
+		var gr naming.GlobReply
+		if err := call.Recv(&gr); err != nil {
+			break
+		}
+		switch v := gr.(type) {
+		case naming.GlobReplyEntry:
+			fmt.Fprint(env.Stdout, v.Value.Name)
+			for _, s := range v.Value.Servers {
+				fmt.Fprintf(env.Stdout, " %s (Deadline %s)", s.Server, s.Deadline.Time)
+			}
+			fmt.Fprintln(env.Stdout)
+		}
+	}
+	if err := call.Finish(); err != nil {
+		return err
+	}
+	return nil
+}
+
+var cmdMount = &cmdline.Command{
+	Runner:   v23cmd.RunnerFunc(runMount),
+	Name:     "mount",
+	Short:    "Mounts a server <name> onto a mount table",
+	Long:     "Mounts a server <name> onto a mount table",
+	ArgsName: "<mount name> <name> <ttl> [M|R]",
+	ArgsLong: `
+<mount name> is a mount name on a mount table.
+
+<name> is the rooted object name of the server.
+
+<ttl> is the TTL of the new entry. It is a decimal number followed by a unit
+suffix (s, m, h). A value of 0s represents an infinite duration.
+
+[M|R] are mount options. M indicates that <name> is a mounttable. R indicates
+that existing entries should be removed.
+`,
+}
+
+func runMount(ctx *context.T, env *cmdline.Env, args []string) error {
+	got := len(args)
+	if got < 2 || got > 4 {
+		return env.UsageErrorf("mount: incorrect number of arguments, expected 2, 3, or 4, got %d", got)
+	}
+	name := args[0]
+	server := args[1]
+
+	var flags naming.MountFlag
+	var seconds uint32
+	if got >= 3 {
+		ttl, err := time.ParseDuration(args[2])
+		if err != nil {
+			return fmt.Errorf("TTL parse error: %v", err)
+		}
+		seconds = uint32(ttl.Seconds())
+	}
+	if got >= 4 {
+		for _, c := range args[3] {
+			switch c {
+			case 'M':
+				flags |= naming.MountFlag(naming.MT)
+			case 'R':
+				flags |= naming.MountFlag(naming.Replace)
+			}
+		}
+	}
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
+	defer cancel()
+	client := v23.GetClient(ctx)
+	if err := client.Call(ctx, name, "Mount", []interface{}{server, seconds, flags}, nil, options.NoResolve{}); err != nil {
+		return err
+	}
+	fmt.Fprintln(env.Stdout, "Name mounted successfully.")
+	return nil
+}
+
+var cmdUnmount = &cmdline.Command{
+	Runner:   v23cmd.RunnerFunc(runUnmount),
+	Name:     "unmount",
+	Short:    "removes server <name> from the mount table",
+	Long:     "removes server <name> from the mount table",
+	ArgsName: "<mount name> <name>",
+	ArgsLong: `
+<mount name> is a mount name on a mount table.
+<name> is the rooted object name of the server.
+`,
+}
+
+func runUnmount(ctx *context.T, env *cmdline.Env, args []string) error {
+	if expected, got := 2, len(args); expected != got {
+		return env.UsageErrorf("unmount: incorrect number of arguments, expected %d, got %d", expected, got)
+	}
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
+	defer cancel()
+	client := v23.GetClient(ctx)
+	if err := client.Call(ctx, args[0], "Unmount", []interface{}{args[1]}, nil, options.NoResolve{}); err != nil {
+		return err
+	}
+	fmt.Fprintln(env.Stdout, "Unmount successful or name not mounted.")
+	return nil
+}
+
+var cmdResolveStep = &cmdline.Command{
+	Runner:   v23cmd.RunnerFunc(runResolveStep),
+	Name:     "resolvestep",
+	Short:    "takes the next step in resolving a name.",
+	Long:     "takes the next step in resolving a name.",
+	ArgsName: "<mount name>",
+	ArgsLong: `
+<mount name> is a mount name on a mount table.
+`,
+}
+
+func runResolveStep(ctx *context.T, env *cmdline.Env, args []string) error {
+	if expected, got := 1, len(args); expected != got {
+		return env.UsageErrorf("mount: incorrect number of arguments, expected %d, got %d", expected, got)
+	}
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
+	defer cancel()
+	client := v23.GetClient(ctx)
+	var entry naming.MountEntry
+	if err := client.Call(ctx, args[0], "ResolveStep", nil, []interface{}{&entry}, options.NoResolve{}); err != nil {
+		return err
+	}
+	fmt.Fprintf(env.Stdout, "Servers: %v Suffix: %q MT: %v\n", entry.Servers, entry.Name, entry.ServesMountTable)
+	return nil
+}
+
+var cmdRoot = &cmdline.Command{
+	Name:  "mounttable",
+	Short: "sends commands to Vanadium mounttable services",
+	Long: `
+Command mounttable sends commands to Vanadium mounttable services.
+`,
+	Children: []*cmdline.Command{cmdGlob, cmdMount, cmdUnmount, cmdResolveStep},
+}
+
+func blessingPatternsFromServer(ctx *context.T, server string) ([]security.BlessingPattern, error) {
+	ctx.Infof("Contacting %q to determine the blessings presented by it", server)
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
+	defer cancel()
+	call, err := v23.GetClient(ctx).StartCall(ctx, server, rpc.ReservedSignature, nil)
+	if err != nil {
+		return nil, fmt.Errorf("Unable to extract blessings presented by %q: %v", server, err)
+	}
+	blessings, _ := call.RemoteBlessings()
+	if len(blessings) == 0 {
+		return nil, fmt.Errorf("No recognizable blessings presented by %q, it cannot be securely mounted", server)
+	}
+	// This translation between BlessingPattern and string is silly!
+	// Kill the BlessingPatterns type and make methods on that type
+	// functions instead!
+	patterns := make([]security.BlessingPattern, len(blessings))
+	for i := range blessings {
+		patterns[i] = security.BlessingPattern(blessings[i])
+	}
+	return patterns, nil
+}
diff --git a/cmd/mounttable/impl_test.go b/cmd/mounttable/impl_test.go
new file mode 100644
index 0000000..fad262e
--- /dev/null
+++ b/cmd/mounttable/impl_test.go
@@ -0,0 +1,163 @@
+// 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 (
+	"bytes"
+	"regexp"
+	"strings"
+	"testing"
+	"time"
+
+	"v.io/x/lib/cmdline"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/glob"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/security/access"
+	"v.io/v23/services/mounttable"
+	vdltime "v.io/v23/vdlroot/time"
+
+	"v.io/x/ref/lib/v23cmd"
+	"v.io/x/ref/lib/xrpc"
+	_ "v.io/x/ref/runtime/factories/generic"
+	"v.io/x/ref/test"
+)
+
+var now = time.Now()
+
+func init() {
+	test.Init()
+}
+
+func deadline(minutes int) vdltime.Deadline {
+	return vdltime.Deadline{
+		Time: now.Add(time.Minute * time.Duration(minutes)),
+	}
+}
+
+type server struct {
+	suffix string
+}
+
+func (s *server) Glob__(ctx *context.T, call rpc.GlobServerCall, g *glob.Glob) error {
+	ctx.VI(2).Infof("Glob() was called. suffix=%v pattern=%q", s.suffix, g.String())
+	sender := call.SendStream()
+	sender.Send(naming.GlobReplyEntry{
+		Value: naming.MountEntry{
+			Name:             "name1",
+			Servers:          []naming.MountedServer{{"server1", deadline(1)}},
+			ServesMountTable: false,
+			IsLeaf:           false,
+		},
+	})
+	sender.Send(naming.GlobReplyEntry{
+		Value: naming.MountEntry{
+			Name:             "name2",
+			Servers:          []naming.MountedServer{{"server2", deadline(2)}, {"server3", deadline(3)}},
+			ServesMountTable: false,
+			IsLeaf:           false,
+		},
+	})
+	return nil
+}
+
+func (s *server) Mount(ctx *context.T, _ rpc.ServerCall, server string, ttl uint32, flags naming.MountFlag) error {
+	ctx.VI(2).Infof("Mount() was called. suffix=%v server=%q ttl=%d", s.suffix, server, ttl)
+	return nil
+}
+
+func (s *server) Unmount(ctx *context.T, _ rpc.ServerCall, server string) error {
+	ctx.VI(2).Infof("Unmount() was called. suffix=%v server=%q", s.suffix, server)
+	return nil
+}
+
+func (s *server) ResolveStep(ctx *context.T, _ rpc.ServerCall) (entry naming.MountEntry, err error) {
+	ctx.VI(2).Infof("ResolveStep() was called. suffix=%v", s.suffix)
+	entry.Servers = []naming.MountedServer{{"server1", deadline(1)}}
+	entry.Name = s.suffix
+	return
+}
+
+func (s *server) Delete(ctx *context.T, _ rpc.ServerCall, _ bool) error {
+	ctx.VI(2).Infof("Delete() was called. suffix=%v", s.suffix)
+	return nil
+}
+func (s *server) SetPermissions(ctx *context.T, _ rpc.ServerCall, _ access.Permissions, _ string) error {
+	ctx.VI(2).Infof("SetPermissions() was called. suffix=%v", s.suffix)
+	return nil
+}
+
+func (s *server) GetPermissions(ctx *context.T, _ rpc.ServerCall) (access.Permissions, string, error) {
+	ctx.VI(2).Infof("GetPermissions() was called. suffix=%v", s.suffix)
+	return nil, "", nil
+}
+
+type dispatcher struct {
+}
+
+func (d *dispatcher) Lookup(_ *context.T, suffix string) (interface{}, security.Authorizer, error) {
+	return mounttable.MountTableServer(&server{suffix: suffix}), nil, nil
+}
+
+func TestMountTableClient(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	server, err := xrpc.NewDispatchingServer(ctx, "", new(dispatcher))
+	if err != nil {
+		t.Fatalf("NewServer failed: %v", err)
+	}
+	endpoint := server.Status().Endpoints[0]
+
+	// Make sure to use our newly created mounttable rather than the
+	// default.
+	v23.GetNamespace(ctx).SetRoots(endpoint.Name())
+
+	// Setup the command-line.
+	var stdout, stderr bytes.Buffer
+	env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
+
+	// Test the 'glob' command.
+	if err := v23cmd.ParseAndRunForTest(cmdRoot, ctx, env, []string{"glob", naming.JoinAddressName(endpoint.String(), ""), "*"}); err != nil {
+		t.Fatalf("%v", err)
+	}
+	const deadRE = `\(Deadline ([^)]+)\)`
+	if got, wantRE := strings.TrimSpace(stdout.String()), regexp.MustCompile("name1 server1 "+deadRE+"\nname2 server2 "+deadRE+" server3 "+deadRE); !wantRE.MatchString(got) {
+		t.Errorf("got %q, want regexp %q", got, wantRE)
+	}
+	stdout.Reset()
+
+	// Test the 'mount' command.
+	if err := v23cmd.ParseAndRunForTest(cmdRoot, ctx, env, []string{"mount", "server", endpoint.Name(), "123s"}); err != nil {
+		t.Fatalf("%v", err)
+	}
+	if got, want := strings.TrimSpace(stdout.String()), "Name mounted successfully."; got != want {
+		t.Errorf("got %q, want %q", got, want)
+	}
+	stdout.Reset()
+
+	// Test the 'unmount' command.
+	if err := v23cmd.ParseAndRunForTest(cmdRoot, ctx, env, []string{"unmount", "server", endpoint.Name()}); err != nil {
+		t.Fatalf("%v", err)
+	}
+	if got, want := strings.TrimSpace(stdout.String()), "Unmount successful or name not mounted."; got != want {
+		t.Errorf("got %q, want %q", got, want)
+	}
+	stdout.Reset()
+
+	// Test the 'resolvestep' command.
+	ctx.Infof("resovestep %s", naming.JoinAddressName(endpoint.String(), "name"))
+	if err := v23cmd.ParseAndRunForTest(cmdRoot, ctx, env, []string{"resolvestep", naming.JoinAddressName(endpoint.String(), "name")}); err != nil {
+		t.Fatalf("%v", err)
+	}
+	if got, wantRE := strings.TrimSpace(stdout.String()), regexp.MustCompile(`Servers: \[\{server1 [^}]+\}\] Suffix: "name" MT: false`); !wantRE.MatchString(got) {
+		t.Errorf("got %q, want regexp %q", got, wantRE)
+	}
+	stdout.Reset()
+}
diff --git a/cmd/namespace/doc.go b/cmd/namespace/doc.go
new file mode 100644
index 0000000..fb8f24d
--- /dev/null
+++ b/cmd/namespace/doc.go
@@ -0,0 +1,215 @@
+// 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
+
+/*
+Command namespace resolves and manages names in the Vanadium namespace.
+
+The namespace roots are set from the command line via --v23.namespace.root
+command line option or from environment variables that have a name starting with
+V23_NAMESPACE, e.g.  V23_NAMESPACE, V23_NAMESPACE_2, V23_NAMESPACE_GOOGLE, etc.
+The command line options override the environment.
+
+Usage:
+   namespace <command>
+
+The namespace commands are:
+   glob        Returns all matching entries from the namespace
+   mount       Adds a server to the namespace
+   unmount     Removes a server from the namespace
+   resolve     Translates a object name to its object address(es)
+   resolvetomt Finds the address of the mounttable that holds an object name
+   permissions Manipulates permissions on an entry in the namespace
+   delete      Deletes a name from the namespace
+   help        Display help for commands or topics
+
+The global flags are:
+ -v23.namespace.root=[/(dev.v.io/role/vprod/service/mounttabled)@ns.dev.v.io:8101]
+   local namespace root; can be repeated to provided multiple roots
+
+ -alsologtostderr=true
+   log to standard error as well as files
+ -log_backtrace_at=:0
+   when logging hits line file:N, emit a stack trace
+ -log_dir=
+   if non-empty, write log files to this directory
+ -logtostderr=false
+   log to standard error instead of files
+ -max_stack_buf_size=4292608
+   max size in bytes of the buffer to use for logging stack traces
+ -metadata=<just specify -metadata to activate>
+   Displays metadata for the program and exits.
+ -stderrthreshold=2
+   logs at or above this threshold go to stderr
+ -v=0
+   log level for V logs
+ -v23.credentials=
+   directory to use for storing security credentials
+ -v23.i18n-catalogue=
+   18n catalogue files to load, comma separated
+ -v23.proxy=
+   object name of proxy service to use to export services across network
+   boundaries
+ -v23.tcp.address=
+   address to listen on
+ -v23.tcp.protocol=wsh
+   protocol to listen with
+ -v23.vtrace.cache-size=1024
+   The number of vtrace traces to store in memory.
+ -v23.vtrace.collect-regexp=
+   Spans and annotations that match this regular expression will trigger trace
+   collection.
+ -v23.vtrace.dump-on-shutdown=true
+   If true, dump all stored traces on runtime shutdown.
+ -v23.vtrace.sample-rate=0
+   Rate (from 0.0 to 1.0) to sample vtrace traces.
+ -vmodule=
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
+
+Namespace glob - Returns all matching entries from the namespace
+
+Returns all matching entries from the namespace.
+
+Usage:
+   namespace glob [flags] <pattern>
+
+<pattern> is a glob pattern that is matched against all the names below the
+specified mount name.
+
+The namespace glob flags are:
+ -l=false
+   Long listing format.
+
+Namespace mount - Adds a server to the namespace
+
+Adds server <server> to the namespace with name <name>.
+
+Usage:
+   namespace mount <name> <server> <ttl>
+
+<name> is the name to add to the namespace. <server> is the object address of
+the server to add. <ttl> is the TTL of the new entry. It is a decimal number
+followed by a unit suffix (s, m, h). A value of 0s represents an infinite
+duration.
+
+Namespace unmount - Removes a server from the namespace
+
+Removes server <server> with name <name> from the namespace.
+
+Usage:
+   namespace unmount <name> <server>
+
+<name> is the name to remove from the namespace. <server> is the object address
+of the server to remove.
+
+Namespace resolve
+
+Translates a object name to its object address(es).
+
+Usage:
+   namespace resolve [flags] <name>
+
+<name> is the name to resolve.
+
+The namespace resolve flags are:
+ -insecure=false
+   Insecure mode: May return results from untrusted servers and invoke Resolve
+   on untrusted mounttables
+
+Namespace resolvetomt - Finds the address of the mounttable that holds an object name
+
+Finds the address of the mounttable that holds an object name.
+
+Usage:
+   namespace resolvetomt [flags] <name>
+
+<name> is the name to resolve.
+
+The namespace resolvetomt flags are:
+ -insecure=false
+   Insecure mode: May return results from untrusted servers and invoke Resolve
+   on untrusted mounttables
+
+Namespace permissions - Manipulates permissions on an entry in the namespace
+
+Commands to get and set the permissions on a name - controlling the blessing
+names required to resolve the name.
+
+The permissions are provided as an JSON-encoded version of the Permissions type
+defined in v.io/v23/security/access/types.vdl.
+
+Usage:
+   namespace permissions <command>
+
+The namespace permissions commands are:
+   get         Gets permissions on a mount name
+   set         Sets permissions on a mount name
+
+Namespace permissions get - Gets permissions on a mount name
+
+Get retrieves the permissions on the usage of a name.
+
+The output is a JSON-encoded Permissions object (defined in
+v.io/v23/security/access/types.vdl).
+
+Usage:
+   namespace permissions get <name>
+
+<name> is a name in the namespace.
+
+Namespace permissions set - Sets permissions on a mount name
+
+Set replaces the permissions controlling usage of a mount name.
+
+Usage:
+   namespace permissions set <name> <permissions>
+
+<name> is the name on which permissions are to be set.
+
+<permissions> is the path to a file containing a JSON-encoded Permissions object
+(defined in v.io/v23/security/access/types.vdl), or "-" for STDIN.
+
+Namespace delete - Deletes a name from the namespace
+
+Deletes a name from the namespace.
+
+Usage:
+   namespace delete [flags] <name>
+
+<name> is a name to delete.
+
+The namespace delete flags are:
+ -r=false
+   Delete all children of the name in addition to the name itself.
+
+Namespace help - Display help for commands or topics
+
+Help with no args displays the usage of the parent command.
+
+Help with args displays the usage of the specified sub-command or help topic.
+
+"help ..." recursively displays help for all commands and topics.
+
+Usage:
+   namespace help [flags] [command/topic ...]
+
+[command/topic ...] optionally identifies a specific sub-command or help topic.
+
+The namespace help flags are:
+ -style=compact
+   The formatting style for help output:
+      compact - Good for compact cmdline output.
+      full    - Good for cmdline output, shows all global flags.
+      godoc   - Good for godoc processing.
+   Override the default by setting the CMDLINE_STYLE environment variable.
+ -width=<terminal width>
+   Format output to this target width in runes, or unlimited if width < 0.
+   Defaults to the terminal width if available.  Override the default by setting
+   the CMDLINE_WIDTH environment variable.
+*/
+package main
diff --git a/cmd/namespace/impl.go b/cmd/namespace/impl.go
new file mode 100644
index 0000000..ca17f5c
--- /dev/null
+++ b/cmd/namespace/impl.go
@@ -0,0 +1,385 @@
+// 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.
+
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go .
+
+package main
+
+import (
+	"encoding/json"
+	"fmt"
+	"os"
+	"regexp"
+	"sort"
+	"time"
+
+	"v.io/x/lib/cmdline"
+	"v.io/x/lib/set"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/options"
+	"v.io/v23/security/access"
+	"v.io/v23/verror"
+
+	"v.io/x/ref/lib/v23cmd"
+	_ "v.io/x/ref/runtime/factories/generic"
+)
+
+func main() {
+	cmdline.HideGlobalFlagsExcept(regexp.MustCompile(`^v23\.namespace\.root$`))
+	cmdline.Main(cmdRoot)
+}
+
+var (
+	flagLongGlob            bool
+	flagInsecureResolve     bool
+	flagInsecureResolveToMT bool
+	flagDeleteSubtree       bool
+)
+
+func init() {
+	cmdGlob.Flags.BoolVar(&flagLongGlob, "l", false, "Long listing format.")
+	cmdResolve.Flags.BoolVar(&flagInsecureResolve, "insecure", false, "Insecure mode: May return results from untrusted servers and invoke Resolve on untrusted mounttables")
+	cmdResolveToMT.Flags.BoolVar(&flagInsecureResolveToMT, "insecure", false, "Insecure mode: May return results from untrusted servers and invoke Resolve on untrusted mounttables")
+	cmdDelete.Flags.BoolVar(&flagDeleteSubtree, "r", false, "Delete all children of the name in addition to the name itself.")
+}
+
+var cmdGlob = &cmdline.Command{
+	Runner:   v23cmd.RunnerFunc(runGlob),
+	Name:     "glob",
+	Short:    "Returns all matching entries from the namespace",
+	Long:     "Returns all matching entries from the namespace.",
+	ArgsName: "<pattern>",
+	ArgsLong: `
+<pattern> is a glob pattern that is matched against all the names below the
+specified mount name.
+`,
+}
+
+func runGlob(ctx *context.T, env *cmdline.Env, args []string) error {
+	if expected, got := 1, len(args); expected != got {
+		return env.UsageErrorf("glob: incorrect number of arguments, expected %d, got %d", expected, got)
+	}
+	pattern := args[0]
+
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
+	defer cancel()
+
+	ns := v23.GetNamespace(ctx)
+
+	c, err := ns.Glob(ctx, pattern)
+	if err != nil {
+		ctx.Infof("ns.Glob(%q) failed: %v", pattern, err)
+		return err
+	}
+	if flagLongGlob {
+		// Show all the information we received.
+		for res := range c {
+			switch v := res.(type) {
+			case *naming.GlobReplyEntry:
+				fmt.Fprint(env.Stdout, v.Value.Name)
+				for _, s := range v.Value.Servers {
+					delta := s.Deadline.Time.Sub(time.Now())
+					fmt.Fprintf(env.Stdout, " %s (Expires in %d sec)", s.Server, int(delta.Seconds()))
+				}
+				fmt.Fprintln(env.Stdout)
+			case *naming.GlobReplyError:
+				fmt.Fprintf(env.Stderr, "Error: %s: %v\n", v.Value.Name, v.Value.Error)
+			}
+		}
+		return nil
+	}
+	// Show a sorted list of unique names, and any errors.
+	resultSet := make(map[string]struct{})
+	errors := []*naming.GlobError{}
+	for res := range c {
+		switch v := res.(type) {
+		case *naming.GlobReplyEntry:
+			if v.Value.Name != "" {
+				resultSet[v.Value.Name] = struct{}{}
+			}
+		case *naming.GlobReplyError:
+			errors = append(errors, &v.Value)
+		}
+	}
+	results := set.String.ToSlice(resultSet)
+	sort.Strings(results)
+	for _, result := range results {
+		fmt.Fprintln(env.Stdout, result)
+	}
+	for _, err := range errors {
+		fmt.Fprintf(env.Stderr, "Error: %s: %v\n", err.Name, err.Error)
+	}
+	return nil
+}
+
+var cmdMount = &cmdline.Command{
+	Runner:   v23cmd.RunnerFunc(runMount),
+	Name:     "mount",
+	Short:    "Adds a server to the namespace",
+	Long:     "Adds server <server> to the namespace with name <name>.",
+	ArgsName: "<name> <server> <ttl>",
+	ArgsLong: `
+<name> is the name to add to the namespace.
+<server> is the object address of the server to add.
+<ttl> is the TTL of the new entry. It is a decimal number followed by a unit
+suffix (s, m, h). A value of 0s represents an infinite duration.
+`,
+}
+
+func runMount(ctx *context.T, env *cmdline.Env, args []string) error {
+	if expected, got := 3, len(args); expected != got {
+		return env.UsageErrorf("mount: incorrect number of arguments, expected %d, got %d", expected, got)
+	}
+	name := args[0]
+	server := args[1]
+	ttlArg := args[2]
+
+	ttl, err := time.ParseDuration(ttlArg)
+	if err != nil {
+		return fmt.Errorf("TTL parse error: %v", err)
+	}
+
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
+	defer cancel()
+
+	ns := v23.GetNamespace(ctx)
+	if err = ns.Mount(ctx, name, server, ttl); err != nil {
+		ctx.Infof("ns.Mount(%q, %q, %s) failed: %v", name, server, ttl, err)
+		return err
+	}
+	fmt.Fprintln(env.Stdout, "Server mounted successfully.")
+	return nil
+}
+
+var cmdUnmount = &cmdline.Command{
+	Runner:   v23cmd.RunnerFunc(runUnmount),
+	Name:     "unmount",
+	Short:    "Removes a server from the namespace",
+	Long:     "Removes server <server> with name <name> from the namespace.",
+	ArgsName: "<name> <server>",
+	ArgsLong: `
+<name> is the name to remove from the namespace.
+<server> is the object address of the server to remove.
+`,
+}
+
+func runUnmount(ctx *context.T, env *cmdline.Env, args []string) error {
+	if expected, got := 2, len(args); expected != got {
+		return env.UsageErrorf("unmount: incorrect number of arguments, expected %d, got %d", expected, got)
+	}
+	name := args[0]
+	server := args[1]
+
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
+	defer cancel()
+
+	ns := v23.GetNamespace(ctx)
+
+	if err := ns.Unmount(ctx, name, server); err != nil {
+		ctx.Infof("ns.Unmount(%q, %q) failed: %v", name, server, err)
+		return err
+	}
+	fmt.Fprintln(env.Stdout, "Server unmounted successfully.")
+	return nil
+}
+
+var cmdResolve = &cmdline.Command{
+	Runner:   v23cmd.RunnerFunc(runResolve),
+	Name:     "resolve",
+	Short:    "Translates a object name to its object address(es)",
+	Long:     "Translates a object name to its object address(es).",
+	ArgsName: "<name>",
+	ArgsLong: "<name> is the name to resolve.",
+}
+
+func runResolve(ctx *context.T, env *cmdline.Env, args []string) error {
+	if expected, got := 1, len(args); expected != got {
+		return env.UsageErrorf("resolve: incorrect number of arguments, expected %d, got %d", expected, got)
+	}
+	name := args[0]
+
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
+	defer cancel()
+
+	ns := v23.GetNamespace(ctx)
+
+	var opts []naming.NamespaceOpt
+	if flagInsecureResolve {
+		opts = append(opts, options.SkipServerEndpointAuthorization{})
+	}
+	me, err := ns.Resolve(ctx, name, opts...)
+	if err != nil {
+		ctx.Infof("ns.Resolve(%q) failed: %v", name, err)
+		return err
+	}
+	for _, n := range me.Names() {
+		fmt.Fprintln(env.Stdout, n)
+	}
+	return nil
+}
+
+var cmdResolveToMT = &cmdline.Command{
+	Runner:   v23cmd.RunnerFunc(runResolveToMT),
+	Name:     "resolvetomt",
+	Short:    "Finds the address of the mounttable that holds an object name",
+	Long:     "Finds the address of the mounttable that holds an object name.",
+	ArgsName: "<name>",
+	ArgsLong: "<name> is the name to resolve.",
+}
+
+func runResolveToMT(ctx *context.T, env *cmdline.Env, args []string) error {
+	if expected, got := 1, len(args); expected != got {
+		return env.UsageErrorf("resolvetomt: incorrect number of arguments, expected %d, got %d", expected, got)
+	}
+	name := args[0]
+
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
+	defer cancel()
+
+	ns := v23.GetNamespace(ctx)
+	var opts []naming.NamespaceOpt
+	if flagInsecureResolveToMT {
+		opts = append(opts, options.SkipServerEndpointAuthorization{})
+	}
+	e, err := ns.ResolveToMountTable(ctx, name, opts...)
+	if err != nil {
+		ctx.Infof("ns.ResolveToMountTable(%q) failed: %v", name, err)
+		return err
+	}
+	for _, s := range e.Servers {
+		fmt.Fprintln(env.Stdout, naming.JoinAddressName(s.Server, e.Name))
+	}
+	return nil
+}
+
+var cmdPermissions = &cmdline.Command{
+	Name:  "permissions",
+	Short: "Manipulates permissions on an entry in the namespace",
+	Long: `
+Commands to get and set the permissions on a name - controlling the blessing
+names required to resolve the name.
+
+The permissions are provided as an JSON-encoded version of the Permissions type
+defined in v.io/v23/security/access/types.vdl.
+`,
+	Children: []*cmdline.Command{cmdPermissionsGet, cmdPermissionsSet},
+}
+
+var cmdPermissionsSet = &cmdline.Command{
+	Runner: v23cmd.RunnerFunc(runPermissionsSet),
+	Name:   "set",
+	Short:  "Sets permissions on a mount name",
+	Long: `
+Set replaces the permissions controlling usage of a mount name.
+`,
+	ArgsName: "<name> <permissions>",
+	ArgsLong: `
+<name> is the name on which permissions are to be set.
+
+<permissions> is the path to a file containing a JSON-encoded Permissions
+object (defined in v.io/v23/security/access/types.vdl), or "-" for STDIN.
+`,
+}
+
+func runPermissionsSet(ctx *context.T, env *cmdline.Env, args []string) error {
+	if expected, got := 2, len(args); expected != got {
+		return env.UsageErrorf("set: incorrect number of arguments, expected %d, got %d", expected, got)
+	}
+	name := args[0]
+	var perms access.Permissions
+	file := os.Stdin
+	if args[1] != "-" {
+		var err error
+		if file, err = os.Open(args[1]); err != nil {
+			return err
+		}
+		defer file.Close()
+	}
+	if err := json.NewDecoder(file).Decode(&perms); err != nil {
+		return err
+	}
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
+	defer cancel()
+	ns := v23.GetNamespace(ctx)
+	for {
+		_, etag, err := ns.GetPermissions(ctx, name)
+		if err != nil && verror.ErrorID(err) != naming.ErrNoSuchName.ID {
+			return err
+		}
+		if err = ns.SetPermissions(ctx, name, perms, etag); verror.ErrorID(err) == verror.ErrBadVersion.ID {
+			ctx.Infof("SetPermissions(%q, %q) failed: %v, retrying...", name, etag, err)
+			continue
+		}
+		return err
+	}
+}
+
+var cmdPermissionsGet = &cmdline.Command{
+	Runner:   v23cmd.RunnerFunc(runPermissionsGet),
+	Name:     "get",
+	Short:    "Gets permissions on a mount name",
+	ArgsName: "<name>",
+	ArgsLong: `
+<name> is a name in the namespace.
+`,
+	Long: `
+Get retrieves the permissions on the usage of a name.
+
+The output is a JSON-encoded Permissions object (defined in
+v.io/v23/security/access/types.vdl).
+`,
+}
+
+func runPermissionsGet(ctx *context.T, env *cmdline.Env, args []string) error {
+	if expected, got := 1, len(args); expected != got {
+		return env.UsageErrorf("get: incorrect number of arguments, expected %d, got %d", expected, got)
+	}
+	name := args[0]
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
+	defer cancel()
+
+	perms, _, err := v23.GetNamespace(ctx).GetPermissions(ctx, name)
+	if err != nil {
+		return err
+	}
+	return json.NewEncoder(env.Stdout).Encode(perms)
+}
+
+var cmdDelete = &cmdline.Command{
+	Runner:   v23cmd.RunnerFunc(runDelete),
+	Name:     "delete",
+	Short:    "Deletes a name from the namespace",
+	ArgsName: "<name>",
+	ArgsLong: "<name> is a name to delete.",
+	Long:     "Deletes a name from the namespace.",
+}
+
+func runDelete(ctx *context.T, env *cmdline.Env, args []string) error {
+	if expected, got := 1, len(args); expected != got {
+		return env.UsageErrorf("delete: incorrect number of arguments, expected %d, got %d", expected, got)
+	}
+	name := args[0]
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
+	defer cancel()
+
+	return v23.GetNamespace(ctx).Delete(ctx, name, flagDeleteSubtree)
+}
+
+var cmdRoot = &cmdline.Command{
+	Name:  "namespace",
+	Short: "resolves and manages names in the Vanadium namespace",
+	Long: `
+Command namespace resolves and manages names in the Vanadium namespace.
+
+The namespace roots are set from the command line via --v23.namespace.root
+command line option or from environment variables that have a name starting
+with V23_NAMESPACE, e.g.  V23_NAMESPACE, V23_NAMESPACE_2, V23_NAMESPACE_GOOGLE,
+etc.  The command line options override the environment.
+`,
+	Children: []*cmdline.Command{cmdGlob, cmdMount, cmdUnmount, cmdResolve, cmdResolveToMT, cmdPermissions, cmdDelete},
+}
diff --git a/cmd/principal/bless.go b/cmd/principal/bless.go
new file mode 100644
index 0000000..2c24366
--- /dev/null
+++ b/cmd/principal/bless.go
@@ -0,0 +1,200 @@
+// 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 (
+	"crypto/rand"
+	"encoding/base64"
+	"fmt"
+	"html/template"
+	"net"
+	"net/http"
+	"net/url"
+	"os"
+	"os/exec"
+	"strings"
+	"time"
+
+	"v.io/v23/context"
+	"v.io/v23/options"
+	"v.io/v23/security"
+	"v.io/x/lib/vlog"
+	"v.io/x/ref/services/identity"
+)
+
+func exchangeMacaroonForBlessing(ctx *context.T, macaroonChan <-chan string) (security.Blessings, error) {
+	service, macaroon, serviceKey, err := prepareBlessArgs(ctx, macaroonChan)
+	if err != nil {
+		return security.Blessings{}, err
+	}
+
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
+	defer cancel()
+
+	// Authorize the server by its public key (obtained from macaroonChan).
+	// Must skip authorization during name resolution because the identity
+	// service is not a trusted root yet.
+	blessings, err := identity.MacaroonBlesserClient(service).Bless(ctx, macaroon, options.SkipServerEndpointAuthorization{}, options.ServerPublicKey{
+		PublicKey: serviceKey,
+	})
+	if err != nil {
+		return security.Blessings{}, fmt.Errorf("failed to get blessing from %q: %v", service, err)
+	}
+	return blessings, nil
+}
+
+func prepareBlessArgs(ctx *context.T, macaroonChan <-chan string) (service, macaroon string, root security.PublicKey, err error) {
+	macaroon = <-macaroonChan
+	service = <-macaroonChan
+
+	marshalKey, err := base64.URLEncoding.DecodeString(<-macaroonChan)
+	if err != nil {
+		return "", "", nil, fmt.Errorf("failed to decode root key: %v", err)
+	}
+	root, err = security.UnmarshalPublicKey(marshalKey)
+	if err != nil {
+		return "", "", nil, fmt.Errorf("failed to unmarshal root key: %v", err)
+	}
+
+	return service, macaroon, root, nil
+}
+
+func getMacaroonForBlessRPC(key security.PublicKey, blessServerURL string, blessedChan <-chan string, browser bool) (<-chan string, error) {
+	// Setup a HTTP server to recieve a blessing macaroon from the identity server.
+	// Steps:
+	// 1. Generate a state token to be included in the HTTP request
+	//    (though, arguably, the random port assigment for the HTTP server is enough
+	//    for XSRF protection)
+	// 2. Setup a HTTP server which will receive the final blessing macaroon from the id server.
+	// 3. Print out the link (to start the auth flow) for the user to click.
+	// 4. Return the macaroon and the rpc object name(where to make the MacaroonBlesser.Bless RPC call)
+	//    in the "result" channel.
+	var stateBuf [32]byte
+	if _, err := rand.Read(stateBuf[:]); err != nil {
+		return nil, fmt.Errorf("failed to generate state token for OAuth: %v", err)
+	}
+	state := base64.URLEncoding.EncodeToString(stateBuf[:])
+
+	ln, err := net.Listen("tcp", "127.0.0.1:0")
+	if err != nil {
+		return nil, fmt.Errorf("failed to setup authorization code interception server: %v", err)
+	}
+	result := make(chan string)
+
+	redirectURL := fmt.Sprintf("http://%s/macaroon", ln.Addr())
+	http.HandleFunc("/macaroon", func(w http.ResponseWriter, r *http.Request) {
+		w.Header().Set("Content-Type", "text/html")
+		tmplArgs := struct {
+			Blessings string
+			ErrShort  string
+			ErrLong   string
+			Browser   bool
+		}{
+			Browser: browser,
+		}
+		defer func() {
+			if len(tmplArgs.ErrShort) > 0 {
+				w.WriteHeader(http.StatusBadRequest)
+			}
+			if err := tmpl.Execute(w, tmplArgs); err != nil {
+				vlog.Info("Failed to render template:", err)
+			}
+		}()
+
+		defer close(result)
+		if r.FormValue("state") != state {
+			tmplArgs.ErrShort = "Unexpected request"
+			tmplArgs.ErrLong = "Mismatched state parameter. Possible cross-site-request-forgery?"
+			return
+		}
+		if errEnc := r.FormValue("error"); errEnc != "" {
+			tmplArgs.ErrShort = "Failed to get blessings"
+			errBytes, err := base64.URLEncoding.DecodeString(errEnc)
+			if err != nil {
+				tmplArgs.ErrLong = err.Error()
+			} else {
+				tmplArgs.ErrLong = string(errBytes)
+			}
+			return
+		}
+		result <- r.FormValue("macaroon")
+		result <- r.FormValue("object_name")
+		result <- r.FormValue("root_key")
+		blessed, ok := <-blessedChan
+		if !ok {
+			tmplArgs.ErrShort = "No blessings received"
+			tmplArgs.ErrLong = "Unable to obtain blessings from the Vanadium service"
+			return
+		}
+		tmplArgs.Blessings = blessed
+		ln.Close()
+	})
+	go http.Serve(ln, nil)
+
+	// Print the link to start the flow.
+	url, err := seekBlessingsURL(key, blessServerURL, redirectURL, state)
+	if err != nil {
+		return nil, fmt.Errorf("failed to create seekBlessingsURL: %s", err)
+	}
+	fmt.Fprintln(os.Stdout, "Please visit the following URL to seek blessings:")
+	fmt.Fprintln(os.Stdout, url)
+	// Make an attempt to start the browser as a convenience.
+	// If it fails, doesn't matter - the client can see the URL printed above.
+	// Use exec.Command().Start instead of exec.Command().Run since there is no
+	// need to wait for the command to return (and indeed on some window managers,
+	// the command will not exit until the browser is closed).
+	if len(openCommand) != 0 && browser {
+		exec.Command(openCommand, url).Start()
+	}
+	return result, nil
+}
+
+func seekBlessingsURL(key security.PublicKey, blessServerURL, redirectURL, state string) (string, error) {
+	baseURL, err := url.Parse(joinURL(blessServerURL, identity.SeekBlessingsRoute))
+	if err != nil {
+		return "", fmt.Errorf("failed to parse url: %v", err)
+	}
+	keyBytes, err := key.MarshalBinary()
+	if err != nil {
+		return "", fmt.Errorf("failed to marshal public key: %v", err)
+	}
+	params := url.Values{}
+	params.Add("redirect_url", redirectURL)
+	params.Add("state", state)
+	params.Add("public_key", base64.URLEncoding.EncodeToString(keyBytes))
+	baseURL.RawQuery = params.Encode()
+	return baseURL.String(), nil
+}
+
+func joinURL(baseURL, suffix string) string {
+	if !strings.HasSuffix(baseURL, "/") {
+		baseURL += "/"
+	}
+	return baseURL + suffix
+}
+
+var tmpl = template.Must(template.New("name").Parse(`<!doctype html>
+<html>
+<head>
+<meta charset="UTF-8">
+<!--Excluding any third-party hosted resources like scripts and stylesheets because otherwise we run the risk of leaking the macaroon out of this page (e.g., via the referrer header) -->
+<title>Vanadium Identity: Google</title>
+{{if and .Browser .Blessings}}
+<!--Attempt to close the window. Though this script does not work on many browser configurations-->
+<script type="text/javascript">window.close();</script>
+{{end}}
+</head>
+<body>
+<div>
+{{if .ErrShort}}
+<center><h1><span style="color:#FF6E40;">Error: </span>{{.ErrShort}}</h1></center>
+<center><h2>{{.ErrLong}}</h2></center>
+{{else}}
+<center><h1>Received blessings: <tt>{{.Blessings}}</tt></h1></center>
+<center><h2>You may close this tab now.</h2></center>
+{{end}}
+</div>
+</body>
+</html>`))
diff --git a/cmd/principal/caveat.go b/cmd/principal/caveat.go
new file mode 100644
index 0000000..30df4a7
--- /dev/null
+++ b/cmd/principal/caveat.go
@@ -0,0 +1,135 @@
+// 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 (
+	"fmt"
+	"strings"
+
+	"v.io/v23/security"
+	"v.io/v23/vdl"
+	"v.io/x/ref/lib/vdl/build"
+	"v.io/x/ref/lib/vdl/compile"
+	"v.io/x/ref/lib/vdl/parse"
+)
+
+// caveatsFlag defines a flag.Value for receiving multiple caveat definitions.
+type caveatsFlag struct {
+	caveatInfos []caveatInfo
+}
+
+type caveatInfo struct {
+	expr, params string
+}
+
+// Implements flag.Value.Get
+func (c caveatsFlag) Get() interface{} {
+	return c.caveatInfos
+}
+
+// Implements flag.Value.Set
+// Set expects s to be of the form:
+// caveatExpr=VDLExpressionOfParam
+func (c *caveatsFlag) Set(s string) error {
+	exprAndParam := strings.SplitN(s, "=", 2)
+	if len(exprAndParam) != 2 {
+		return fmt.Errorf("incorrect caveat format: %s", s)
+	}
+
+	c.caveatInfos = append(c.caveatInfos, caveatInfo{exprAndParam[0], exprAndParam[1]})
+	return nil
+}
+
+// Implements flag.Value.String
+func (c caveatsFlag) String() string {
+	return fmt.Sprint(c.caveatInfos)
+}
+
+func (c caveatsFlag) usage() string {
+	return `"package/path".CaveatName:VDLExpressionParam to attach to this blessing`
+}
+
+func (c caveatsFlag) Compile() ([]security.Caveat, error) {
+	if len(c.caveatInfos) == 0 {
+		return nil, nil
+	}
+	env := compile.NewEnv(-1)
+	if err := buildPackages(c.caveatInfos, env); err != nil {
+		return nil, err
+	}
+	var caveats []security.Caveat
+	for _, info := range c.caveatInfos {
+		caveat, err := newCaveat(info, env)
+		if err != nil {
+			return nil, err
+		}
+		caveats = append(caveats, caveat)
+	}
+	return caveats, nil
+}
+
+func buildPackages(caveatInfos []caveatInfo, env *compile.Env) error {
+	var (
+		pkgNames []string
+		exprs    []string
+	)
+	for _, info := range caveatInfos {
+		exprs = append(exprs, info.expr, info.params)
+	}
+	for _, pexpr := range parse.ParseExprs(strings.Join(exprs, ","), env.Errors) {
+		pkgNames = append(pkgNames, parse.ExtractExprPackagePaths(pexpr)...)
+	}
+	if !env.Errors.IsEmpty() {
+		return fmt.Errorf("can't build expressions %v:\n%v", exprs, env.Errors)
+	}
+	pkgs := build.TransitivePackages(pkgNames, build.UnknownPathIsError, build.Opts{}, env.Errors)
+	if !env.Errors.IsEmpty() {
+		return fmt.Errorf("failed to get transitive packages packages %v: %s", pkgNames, env.Errors)
+	}
+	for _, p := range pkgs {
+		if build.BuildPackage(p, env); !env.Errors.IsEmpty() {
+			return fmt.Errorf("failed to build package(%v): %v", p, env.Errors)
+		}
+	}
+	return nil
+}
+
+func newCaveat(info caveatInfo, env *compile.Env) (security.Caveat, error) {
+	caveatDesc, err := compileCaveatDesc(info.expr, env)
+	if err != nil {
+		return security.Caveat{}, err
+	}
+	param, err := compileParams(info.params, caveatDesc.ParamType, env)
+	if err != nil {
+		return security.Caveat{}, err
+	}
+	return security.NewCaveat(caveatDesc, param)
+}
+
+func compileCaveatDesc(expr string, env *compile.Env) (security.CaveatDescriptor, error) {
+	vdlValues := build.BuildExprs(expr, []*vdl.Type{vdl.TypeOf(security.CaveatDescriptor{})}, env)
+	if err := env.Errors.ToError(); err != nil {
+		return security.CaveatDescriptor{}, fmt.Errorf("can't build caveat desc %s:\n%v", expr, err)
+	}
+	if len(vdlValues) == 0 {
+		return security.CaveatDescriptor{}, fmt.Errorf("no caveat descriptors were built")
+	}
+	var desc security.CaveatDescriptor
+	if err := vdl.Convert(&desc, vdlValues[0]); err != nil {
+		return security.CaveatDescriptor{}, err
+	}
+	return desc, nil
+}
+
+func compileParams(paramData string, vdlType *vdl.Type, env *compile.Env) (interface{}, error) {
+	params := build.BuildExprs(paramData, []*vdl.Type{vdlType}, env)
+	if !env.Errors.IsEmpty() {
+		return nil, fmt.Errorf("can't build param data %s:\n%v", paramData, env.Errors)
+	}
+	if len(params) == 0 {
+		return security.CaveatDescriptor{}, fmt.Errorf("no caveat params were built")
+	}
+	return params[0], nil
+}
diff --git a/cmd/principal/doc.go b/cmd/principal/doc.go
new file mode 100644
index 0000000..06c7f18
--- /dev/null
+++ b/cmd/principal/doc.go
@@ -0,0 +1,540 @@
+// 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
+
+/*
+Command principal creates and manages Vanadium principals and blessings.
+
+All objects are printed using base64-VOM-encoding.
+
+Usage:
+   principal <command>
+
+The principal commands are:
+   create        Create a new principal and persist it into a directory
+   fork          Fork a new principal from the principal that this tool is
+                 running as and persist it into a directory
+   seekblessings Seek blessings from a web-based Vanadium blessing service
+   recvblessings Receive blessings sent by another principal and use them as the
+                 default
+   dump          Dump out information about the principal
+   dumpblessings Dump out information about the provided blessings
+   blessself     Generate a self-signed blessing
+   bless         Bless another principal
+   set           Mutate the principal's blessings.
+   get           Read the principal's blessings.
+   recognize     Add to the set of identity providers recognized by this
+                 principal
+   help          Display help for commands or topics
+
+The global flags are:
+ -alsologtostderr=true
+   log to standard error as well as files
+ -log_backtrace_at=:0
+   when logging hits line file:N, emit a stack trace
+ -log_dir=
+   if non-empty, write log files to this directory
+ -logtostderr=false
+   log to standard error instead of files
+ -max_stack_buf_size=4292608
+   max size in bytes of the buffer to use for logging stack traces
+ -metadata=<just specify -metadata to activate>
+   Displays metadata for the program and exits.
+ -stderrthreshold=2
+   logs at or above this threshold go to stderr
+ -v=0
+   log level for V logs
+ -v23.credentials=
+   directory to use for storing security credentials
+ -v23.i18n-catalogue=
+   18n catalogue files to load, comma separated
+ -v23.namespace.root=[/(dev.v.io/role/vprod/service/mounttabled)@ns.dev.v.io:8101]
+   local namespace root; can be repeated to provided multiple roots
+ -v23.permissions.file=map[]
+   specify a perms file as <name>:<permsfile>
+ -v23.permissions.literal=
+   explicitly specify the runtime perms as a JSON-encoded access.Permissions.
+   Overrides all --v23.permissions.file flags.
+ -v23.proxy=
+   object name of proxy service to use to export services across network
+   boundaries
+ -v23.tcp.address=
+   address to listen on
+ -v23.tcp.protocol=wsh
+   protocol to listen with
+ -v23.vtrace.cache-size=1024
+   The number of vtrace traces to store in memory.
+ -v23.vtrace.collect-regexp=
+   Spans and annotations that match this regular expression will trigger trace
+   collection.
+ -v23.vtrace.dump-on-shutdown=true
+   If true, dump all stored traces on runtime shutdown.
+ -v23.vtrace.sample-rate=0
+   Rate (from 0.0 to 1.0) to sample vtrace traces.
+ -vmodule=
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
+
+Principal create - Create a new principal and persist it into a directory
+
+Creates a new principal with a single self-blessed blessing and writes it out to
+the provided directory. The same directory can then be used to set the
+V23_CREDENTIALS environment variable for other vanadium applications.
+
+The operation fails if the directory already contains a principal. In this case
+the --overwrite flag can be provided to clear the directory and write out the
+new principal.
+
+Usage:
+   principal create [flags] <directory> <blessing>
+
+<directory> is the directory to which the new principal will be persisted.
+
+<blessing> is the self-blessed blessing that the principal will be setup to use
+by default.
+
+The principal create flags are:
+ -overwrite=false
+   If true, any existing principal data in the directory will be overwritten
+
+Principal fork - Fork a new principal from the principal that this tool is running as and persist it into a directory
+
+Creates a new principal with a blessing from the principal specified by the
+environment that this tool is running in, and writes it out to the provided
+directory. The blessing that will be extended is the default one from the
+blesser's store, or specified by the --with flag. Expiration on the blessing are
+controlled via the --for flag. Additional caveats on the blessing are controlled
+with the --caveat flag. The blessing is marked as default and shareable with all
+peers on the new principal's blessing store.
+
+The operation fails if the directory already contains a principal. In this case
+the --overwrite flag can be provided to clear the directory and write out the
+forked principal.
+
+Usage:
+   principal fork [flags] <directory> <extension>
+
+<directory> is the directory to which the forked principal will be persisted.
+
+<extension> is the extension under which the forked principal is blessed.
+
+The principal fork flags are:
+ -caveat=[]
+   "package/path".CaveatName:VDLExpressionParam to attach to this blessing
+ -for=0
+   Duration of blessing validity (zero implies no expiration caveat)
+ -overwrite=false
+   If true, any existing principal data in the directory will be overwritten
+ -require-caveats=true
+   If false, allow blessing without any caveats. This is typically not advised
+   as the principal wielding the blessing will be almost as powerful as its
+   blesser
+ -with=
+   Path to file containing blessing to extend
+
+Principal seekblessings - Seek blessings from a web-based Vanadium blessing service
+
+Seeks blessings from a web-based Vanadium blesser which requires the caller to
+first authenticate with Google using OAuth. Simply run the command to see what
+happens.
+
+The blessings are sought for the principal specified by the environment that
+this tool is running in.
+
+The blessings obtained are set as default, unless the --set-default flag is set
+to true, and are also set for sharing with all peers, unless a more specific
+peer pattern is provided using the --for-peer flag.
+
+Usage:
+   principal seekblessings [flags]
+
+The principal seekblessings flags are:
+ -add-to-roots=true
+   If true, the root certificate of the blessing will be added to the
+   principal's set of recognized root certificates
+ -browser=true
+   If false, the seekblessings command will not open the browser and only print
+   the url to visit.
+ -for-peer=...
+   If non-empty, the blessings obtained will be marked for peers matching this
+   pattern in the store
+ -from=https://dev.v.io/auth/google
+   URL to use to begin the seek blessings process
+ -set-default=true
+   If true, the blessings obtained will be set as the default blessing in the
+   store
+
+Principal recvblessings - Receive blessings sent by another principal and use them as the default
+
+Allow another principal (likely a remote process) to bless this one.
+
+This command sets up the invoker (this process) to wait for a blessing from
+another invocation of this tool (remote process) and prints out the command to
+be run as the remote principal.
+
+The received blessings are set as default, unless the --set-default flag is set
+to true, and are also set for sharing with all peers, unless a more specific
+peer pattern is provided using the --for-peer flag.
+
+TODO(ashankar,cnicolaou): Make this next paragraph possible! Requires the
+ability to obtain the proxied endpoint.
+
+Typically, this command should require no arguments. However, if the sender and
+receiver are on different network domains, it may make sense to use the
+--v23.proxy flag:
+    principal --v23.proxy=proxy recvblessings
+
+The command to be run at the sender is of the form:
+    principal bless --remote-key=KEY --remote-token=TOKEN ADDRESS EXTENSION
+
+The --remote-key flag is used to by the sender to "authenticate" the receiver,
+ensuring it blesses the intended recipient and not any attacker that may have
+taken over the address.
+
+The --remote-token flag is used by the sender to authenticate itself to the
+receiver. This helps ensure that the receiver rejects blessings from senders who
+just happened to guess the network address of the 'recvblessings' invocation.
+
+If the --remote-arg-file flag is provided to recvblessings, the remote key,
+remote token and object address of this principal will be written to the
+specified location. This file can be supplied to bless:
+		principal bless --remote-arg-file FILE EXTENSION
+
+Usage:
+   principal recvblessings [flags]
+
+The principal recvblessings flags are:
+ -for-peer=...
+   If non-empty, the blessings received will be marked for peers matching this
+   pattern in the store
+ -remote-arg-file=
+   If non-empty, the remote key, remote token, and principal will be written to
+   the specified file in a JSON object. This can be provided to 'principal bless
+   --remote-arg-file FILE EXTENSION'
+ -set-default=true
+   If true, the blessings received will be set as the default blessing in the
+   store
+
+Principal dump - Dump out information about the principal
+
+Prints out information about the principal specified by the environment that
+this tool is running in.
+
+Usage:
+   principal dump [flags]
+
+The principal dump flags are:
+ -s=false
+   If true, show only the default blessing names
+
+Principal dumpblessings - Dump out information about the provided blessings
+
+Prints out information about the blessings (typically obtained from this tool)
+encoded in the provided file.
+
+Usage:
+   principal dumpblessings <file>
+
+<file> is the path to a file containing blessings typically obtained from this
+tool. - is used for STDIN.
+
+Principal blessself - Generate a self-signed blessing
+
+Returns a blessing with name <name> and self-signed by the principal specified
+by the environment that this tool is running in. Optionally, the blessing can be
+restricted with an expiry caveat specified using the --for flag. Additional
+caveats can be added with the --caveat flag.
+
+Usage:
+   principal blessself [flags] [<name>]
+
+<name> is the name used to create the self-signed blessing. If not specified, a
+name will be generated based on the hostname of the machine and the name of the
+user running this command.
+
+The principal blessself flags are:
+ -caveat=[]
+   "package/path".CaveatName:VDLExpressionParam to attach to this blessing
+ -for=0
+   Duration of blessing validity (zero implies no expiration)
+
+Principal bless - Bless another principal
+
+Bless another principal.
+
+The blesser is obtained from the runtime this tool is using. The blessing that
+will be extended is the default one from the blesser's store, or specified by
+the --with flag. Expiration on the blessing are controlled via the --for flag.
+Additional caveats are controlled with the --caveat flag.
+
+For example, let's say a principal "alice" wants to bless another principal
+"bob" as "alice/friend", the invocation would be:
+    V23_CREDENTIALS=<path to alice> principal bless <path to bob> friend
+and this will dump the blessing to STDOUT.
+
+With the --remote-key and --remote-token flags, this command can be used to
+bless a principal on a remote machine as well. In this case, the blessing is not
+dumped to STDOUT but sent to the remote end. Use 'principal help recvblessings'
+for more details on that.
+
+When --remote-arg-file is specified, only the blessing extension is required, as
+all other arguments will be extracted from the specified file.
+
+Usage:
+   principal bless [flags] [<principal to bless>] [<extension>]
+
+<principal to bless> represents the principal to be blessed (i.e., whose public
+key will be provided with a name).  This can be either: (a) The directory
+containing credentials for that principal, OR (b) The filename (- for STDIN)
+containing any other blessings of that
+    principal,
+OR (c) The object name produced by the 'recvblessings' command of this tool
+    running on behalf of another principal (if the --remote-key and
+    --remote-token flags are specified).
+OR (d) None (if the --remote-arg-file flag is specified, only <extension> should
+be provided
+    to bless).
+
+<extension> is the string extension that will be applied to create the blessing.
+
+The principal bless flags are:
+ -caveat=[]
+   "package/path".CaveatName:VDLExpressionParam to attach to this blessing
+ -for=0
+   Duration of blessing validity (zero implies no expiration caveat)
+ -remote-arg-file=
+   File containing bless arguments written by 'principal recvblessings
+   -remote-arg-file FILE EXTENSION' command. This can be provided to bless in
+   place of --remote-key, --remote-token, and <principal>
+ -remote-key=
+   Public key of the remote principal to bless (obtained from the
+   'recvblessings' command run by the remote principal
+ -remote-token=
+   Token provided by principal running the 'recvblessings' command
+ -require-caveats=true
+   If false, allow blessing without any caveats. This is typically not advised
+   as the principal wielding the blessing will be almost as powerful as its
+   blesser
+ -with=
+   Path to file containing blessing to extend
+
+Principal set
+
+Commands to mutate the blessings of the principal.
+
+All input blessings are expected to be serialized using base64-VOM-encoding. See
+'principal get'.
+
+Usage:
+   principal set <command>
+
+The principal set commands are:
+   default     Set provided blessings as default
+   forpeer     Set provided blessings for peer
+
+Principal set default - Set provided blessings as default
+
+Sets the provided blessings as default in the BlessingStore specified by the
+environment that this tool is running in.
+
+It is an error to call 'set default' with blessings whose public key does not
+match the public key of the principal specified by the environment.
+
+Usage:
+   principal set default [flags] <file>
+
+<file> is the path to a file containing a blessing typically obtained from this
+tool. - is used for STDIN.
+
+The principal set default flags are:
+ -add-to-roots=true
+   If true, the root certificate of the blessing will be added to the
+   principal's set of recognized root certificates
+
+Principal set forpeer - Set provided blessings for peer
+
+Marks the provided blessings to be shared with the provided peers on the
+BlessingStore specified by the environment that this tool is running in.
+
+'set b pattern' marks the intention to reveal b to peers who present blessings
+of their own matching 'pattern'.
+
+'set nil pattern' can be used to remove the blessings previously associated with
+the pattern (by a prior 'set' command).
+
+It is an error to call 'set forpeer' with blessings whose public key does not
+match the public key of this principal specified by the environment.
+
+Usage:
+   principal set forpeer [flags] <file> <pattern>
+
+<file> is the path to a file containing a blessing typically obtained from this
+tool. - is used for STDIN.
+
+<pattern> is the BlessingPattern used to identify peers with whom this blessing
+can be shared with.
+
+The principal set forpeer flags are:
+ -add-to-roots=true
+   If true, the root certificate of the blessing will be added to the
+   principal's set of recognized root certificates
+
+Principal get
+
+Commands to inspect the blessings of the principal.
+
+All blessings are printed to stdout using base64-VOM-encoding.
+
+Usage:
+   principal get <command>
+
+The principal get commands are:
+   default         Return blessings marked as default
+   forpeer         Return blessings marked for the provided peer
+   publickey       Prints the public key of the principal.
+   recognizedroots Return recognized blessings, and their associated public key.
+   peermap         Shows the map from peer pattern to which blessing name to
+                   present.
+
+Principal get default - Return blessings marked as default
+
+Returns blessings that are marked as default in the BlessingStore specified by
+the environment that this tool is running in. Providing --names will print the
+default blessings' chain names. Providing --rootkey <chain_name> will print the
+root key of the certificate chain with chain_name. Providing --caveats
+<chain_name> will print the caveats on the certificate chain with chain_name.
+
+Usage:
+   principal get default [flags]
+
+The principal get default flags are:
+ -caveats=
+   Shows the caveats on the provided certificate chain name.
+ -names=false
+   If true, shows the value of the blessing name to be presented to the peer
+ -rootkey=
+   Shows the value of the root key of the provided certificate chain name.
+
+Principal get forpeer - Return blessings marked for the provided peer
+
+Returns blessings that are marked for the provided peer in the BlessingStore
+specified by the environment that this tool is running in. Providing --names
+will print the blessings' chain names. Providing --rootkey <chain_name> will
+print the root key of the certificate chain with chain_name. Providing --caveats
+<chain_name> will print the caveats on the certificate chain with chain_name.
+
+Usage:
+   principal get forpeer [flags] [<peer_1> ... <peer_k>]
+
+<peer_1> ... <peer_k> are the (human-readable string) blessings bound to the
+peer. The returned blessings are marked with a pattern that is matched by at
+least one of these. If no arguments are specified, store.forpeer returns the
+blessings that are marked for all peers (i.e., blessings set on the store with
+the "..." pattern).
+
+The principal get forpeer flags are:
+ -caveats=
+   Shows the caveats on the provided certificate chain name.
+ -names=false
+   If true, shows the value of the blessing name to be presented to the peer
+ -rootkey=
+   Shows the value of the root key of the provided certificate chain name.
+
+Principal get publickey
+
+Prints out the public key of the principal specified by the environment that
+this tool is running in.
+
+The key is printed as a base64 encoded bytes of the DER-format representation of
+the key (suitable to be provided as an argument to the 'recognize' command for
+example).
+
+With --pretty, a 16-byte fingerprint of the key instead. This format is easier
+for humans to read and is used in output of other commands in this program, but
+is not suitable as an argument to the 'recognize' command.
+
+Usage:
+   principal get publickey [flags]
+
+The principal get publickey flags are:
+ -pretty=false
+   If true, print the key out in a more human-readable but lossy representation.
+
+Principal get recognizedroots
+
+Shows list of blessing names that the principal recognizes, and their associated
+public key. If the principal is operating as a client, contacted servers must
+appear on this list. If the principal is operating as a server, clients must
+present blessings derived from this list.
+
+Usage:
+   principal get recognizedroots
+
+Principal get peermap
+
+Shows the map from peer pattern to which blessing name to present. If the
+principal operates as a server, it presents its default blessing to all peers.
+If the principal operates as a client, it presents the map value associated with
+the peer it contacts.
+
+Usage:
+   principal get peermap
+
+Principal recognize - Add to the set of identity providers recognized by this principal
+
+Adds an identity provider to the set of recognized roots public keys for this
+principal.
+
+It accepts either a single argument (which points to a file containing a
+blessing) or two arguments (a name and a base64-encoded DER-encoded public key).
+
+For example, to make the principal in credentials directory A recognize the root
+of the default blessing in credentials directory B:
+  principal -v23.credentials=B bless A some_extension |
+  principal -v23.credentials=A recognize -
+The extension 'some_extension' has no effect in the command above.
+
+Or to make the principal in credentials directory A recognize the base64-encoded
+public key KEY for blessing pattern P:
+  principal -v23.credentials=A recognize P KEY
+
+Usage:
+   principal recognize <key|blessing> [<blessing pattern>]
+
+<blessing> is the path to a file containing a blessing typically obtained from
+this tool. - is used for STDIN.
+
+<key> is a base64-encoded, DER-encoded public key.
+
+<blessing pattern> is the blessing pattern for which <key> should be recognized.
+
+Principal help - Display help for commands or topics
+
+Help with no args displays the usage of the parent command.
+
+Help with args displays the usage of the specified sub-command or help topic.
+
+"help ..." recursively displays help for all commands and topics.
+
+Usage:
+   principal help [flags] [command/topic ...]
+
+[command/topic ...] optionally identifies a specific sub-command or help topic.
+
+The principal help flags are:
+ -style=compact
+   The formatting style for help output:
+      compact - Good for compact cmdline output.
+      full    - Good for cmdline output, shows all global flags.
+      godoc   - Good for godoc processing.
+   Override the default by setting the CMDLINE_STYLE environment variable.
+ -width=<terminal width>
+   Format output to this target width in runes, or unlimited if width < 0.
+   Defaults to the terminal width if available.  Override the default by setting
+   the CMDLINE_WIDTH environment variable.
+*/
+package main
diff --git a/cmd/principal/main.go b/cmd/principal/main.go
new file mode 100644
index 0000000..e1eaadf
--- /dev/null
+++ b/cmd/principal/main.go
@@ -0,0 +1,1291 @@
+// 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.
+
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go .
+
+package main
+
+import (
+	"bytes"
+	"crypto/rand"
+	"crypto/subtle"
+	"encoding/base64"
+	"encoding/json"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"os/user"
+	"strings"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/options"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/vom"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref"
+	vsecurity "v.io/x/ref/lib/security"
+	"v.io/x/ref/lib/v23cmd"
+	"v.io/x/ref/lib/xrpc"
+	_ "v.io/x/ref/runtime/factories/static"
+)
+
+var (
+	// Flags for the "blessself" command
+	flagBlessSelfCaveats caveatsFlag
+	flagBlessSelfFor     time.Duration
+
+	// Flags for the "bless" command
+	flagBlessCaveats        caveatsFlag
+	flagBlessFor            time.Duration
+	flagBlessRequireCaveats bool
+	flagBlessWith           string
+	flagBlessRemoteKey      string
+	flagBlessRemoteToken    string
+
+	// Flags for the "dump" command
+	flagDumpShort bool
+
+	// Flags for the "fork" command
+	flagForkCaveats        caveatsFlag
+	flagForkFor            time.Duration
+	flagForkRequireCaveats bool
+	flagForkWith           string
+
+	// Flags for the "seekblessings" command
+	flagSeekBlessingsFrom       string
+	flagSeekBlessingsSetDefault bool
+	flagSeekBlessingsForPeer    string
+	flagSeekBlessingsBrowser    bool
+
+	// Flags common to many commands
+	flagAddToRoots      bool
+	flagCreateOverwrite bool
+	flagRemoteArgFile   string
+
+	// Flags for the "recvblessings" command
+	flagRecvBlessingsSetDefault bool
+	flagRecvBlessingsForPeer    string
+
+	// Flags for the commands that get blessings
+	flagBlessingsNames   bool
+	flagBlessingsRootKey string
+	flagBlessingsCaveats string
+
+	// Flags for the get publickey command.
+	flagGetPublicKeyPretty bool
+
+	errNoCaveats = fmt.Errorf("no caveats provided: it is generally dangerous to bless another principal without any caveats as that gives them almost unrestricted access to the blesser's credentials. If you really want to do this, set --require-caveats=false")
+
+	cmdDump = &cmdline.Command{
+		Name:  "dump",
+		Short: "Dump out information about the principal",
+		Long: `
+Prints out information about the principal specified by the environment
+that this tool is running in.
+`,
+		Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline.Env, args []string) error {
+			p := v23.GetPrincipal(ctx)
+			if flagDumpShort {
+				fmt.Printf("%s\n", printAnnotatedBlessingsNames(p.BlessingStore().Default()))
+				return nil
+			}
+			fmt.Printf("Public key : %v\n", p.PublicKey())
+			// NOTE(caprita): We print the default blessings name
+			// twice (it's also printed as part of the blessing
+			// store below) -- the reason we print it here is to
+			// expose whether the blessings are expired.  Ideally,
+			// the blessings store would print the expiry
+			// information about each blessing in the store, but
+			// that would require deeper changes beyond the
+			// principal tool.
+			fmt.Printf("Default Blessings : %s\n", printAnnotatedBlessingsNames(p.BlessingStore().Default()))
+			fmt.Println("---------------- BlessingStore ----------------")
+			fmt.Printf("%v", p.BlessingStore().DebugString())
+			fmt.Println("---------------- BlessingRoots ----------------")
+			fmt.Printf("%v", p.Roots().DebugString())
+			return nil
+		}),
+	}
+
+	cmdDumpBlessings = &cmdline.Command{
+		Name:  "dumpblessings",
+		Short: "Dump out information about the provided blessings",
+		Long: `
+Prints out information about the blessings (typically obtained from this tool)
+encoded in the provided file.
+`,
+		ArgsName: "<file>",
+		ArgsLong: `
+<file> is the path to a file containing blessings typically obtained from
+this tool. - is used for STDIN.
+`,
+		Runner: cmdline.RunnerFunc(func(env *cmdline.Env, args []string) error {
+			if len(args) != 1 {
+				return fmt.Errorf("requires exactly one argument, <file>, provided %d", len(args))
+			}
+			blessings, err := decodeBlessings(args[0])
+			if err != nil {
+				return fmt.Errorf("failed to decode provided blessings: %v", err)
+			}
+			wire, err := blessings2wire(blessings)
+			if err != nil {
+				return fmt.Errorf("failed to decode certificate chains: %v", err)
+			}
+			fmt.Printf("Blessings          : %s\n", printAnnotatedBlessingsNames(blessings))
+			fmt.Printf("PublicKey          : %v\n", blessings.PublicKey())
+			fmt.Printf("Certificate chains : %d\n", len(wire.CertificateChains))
+			for idx, chain := range wire.CertificateChains {
+				fmt.Printf("Chain #%d (%d certificates). Root certificate public key: %v\n", idx, len(chain), rootkey(chain))
+				for certidx, cert := range chain {
+					fmt.Printf("  Certificate #%d: %v with ", certidx, cert.Extension)
+					switch n := len(cert.Caveats); n {
+					case 1:
+						fmt.Printf("1 caveat")
+					default:
+						fmt.Printf("%d caveats", n)
+					}
+					fmt.Println("")
+					for cavidx, cav := range cert.Caveats {
+						fmt.Printf("    (%d) %v\n", cavidx, &cav)
+					}
+				}
+			}
+			return nil
+		}),
+	}
+
+	cmdBlessSelf = &cmdline.Command{
+		Name:  "blessself",
+		Short: "Generate a self-signed blessing",
+		Long: `
+Returns a blessing with name <name> and self-signed by the principal specified
+by the environment that this tool is running in. Optionally, the blessing can
+be restricted with an expiry caveat specified using the --for flag. Additional
+caveats can be added with the --caveat flag.
+`,
+		ArgsName: "[<name>]",
+		ArgsLong: `
+<name> is the name used to create the self-signed blessing. If not
+specified, a name will be generated based on the hostname of the
+machine and the name of the user running this command.
+`,
+		Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline.Env, args []string) error {
+			var name string
+			switch len(args) {
+			case 0:
+				name = defaultBlessingName()
+			case 1:
+				name = args[0]
+			default:
+				return fmt.Errorf("requires at most one argument, provided %d", len(args))
+			}
+			caveats, err := caveatsFromFlags(flagBlessSelfFor, &flagBlessSelfCaveats)
+			if err != nil {
+				return err
+			}
+			principal := v23.GetPrincipal(ctx)
+			blessing, err := principal.BlessSelf(name, caveats...)
+			if err != nil {
+				return fmt.Errorf("failed to create self-signed blessing for name %q: %v", name, err)
+			}
+
+			return dumpBlessings(blessing)
+		}),
+	}
+
+	cmdBless = &cmdline.Command{
+		Name:  "bless",
+		Short: "Bless another principal",
+		Long: `
+Bless another principal.
+
+The blesser is obtained from the runtime this tool is using. The blessing that
+will be extended is the default one from the blesser's store, or specified by
+the --with flag. Expiration on the blessing are controlled via the --for flag.
+Additional caveats are controlled with the --caveat flag.
+
+For example, let's say a principal "alice" wants to bless another principal "bob"
+as "alice/friend", the invocation would be:
+    V23_CREDENTIALS=<path to alice> principal bless <path to bob> friend
+and this will dump the blessing to STDOUT.
+
+With the --remote-key and --remote-token flags, this command can be used to
+bless a principal on a remote machine as well. In this case, the blessing is
+not dumped to STDOUT but sent to the remote end. Use 'principal help
+recvblessings' for more details on that.
+
+When --remote-arg-file is specified, only the blessing extension is required, as all other
+arguments will be extracted from the specified file.
+`,
+		ArgsName: "[<principal to bless>] [<extension>]",
+		ArgsLong: `
+<principal to bless> represents the principal to be blessed (i.e., whose public
+key will be provided with a name).  This can be either:
+(a) The directory containing credentials for that principal,
+OR
+(b) The filename (- for STDIN) containing any other blessings of that
+    principal,
+OR
+(c) The object name produced by the 'recvblessings' command of this tool
+    running on behalf of another principal (if the --remote-key and
+    --remote-token flags are specified).
+OR
+(d) None (if the --remote-arg-file flag is specified, only <extension> should be provided
+    to bless).
+
+<extension> is the string extension that will be applied to create the
+blessing.
+
+	`,
+		Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline.Env, args []string) error {
+			if len(flagRemoteArgFile) > 0 {
+				if len(args) > 1 {
+					return fmt.Errorf("when --remote-arg-file is provided, only <extension> is expected, provided %d", len(args))
+				}
+				if (len(flagBlessRemoteKey) + len(flagBlessRemoteToken)) > 0 {
+					return fmt.Errorf("--remote-key and --remote-token should not be specified when --remote-arg-file is")
+				}
+			} else if len(args) > 2 {
+				return fmt.Errorf("got %d arguments, require at most 2", len(args))
+			} else if (len(flagBlessRemoteKey) == 0) != (len(flagBlessRemoteToken) == 0) {
+				return fmt.Errorf("either both --remote-key and --remote-token should be set, or neither should")
+			}
+			p := v23.GetPrincipal(ctx)
+
+			var (
+				err  error
+				with security.Blessings
+			)
+			if len(flagBlessWith) > 0 {
+				if with, err = decodeBlessings(flagBlessWith); err != nil {
+					return fmt.Errorf("failed to read blessings from --with=%q: %v", flagBlessWith, err)
+				}
+			} else {
+				with = p.BlessingStore().Default()
+			}
+			caveats, err := caveatsFromFlags(flagBlessFor, &flagBlessCaveats)
+			if err != nil {
+				return err
+			}
+			if len(caveats) == 0 {
+				if flagBlessRequireCaveats {
+					if err := confirmNoCaveats(env); err != nil {
+						return err
+					}
+				}
+				caveats = []security.Caveat{security.UnconstrainedUse()}
+			}
+			if len(caveats) == 0 {
+				return errNoCaveats
+			}
+
+			tobless, extension, remoteKey, remoteToken, err := blessArgs(env, args)
+			if err != nil {
+				return err
+			}
+
+			// Send blessings to a "server" started by a "recvblessings" command, either
+			// with the --remote-arg-file flag, or with --remote-key and --remote-token flags.
+			if len(remoteKey) > 0 {
+				granter := &granter{with, extension, caveats, remoteKey}
+				return blessOverNetwork(ctx, tobless, granter, remoteToken)
+			}
+
+			// Blessing a principal whose key is available locally.
+			blessings, err := blessOverFileSystem(p, tobless, with, extension, caveats)
+			if err != nil {
+				return err
+			}
+			return dumpBlessings(blessings)
+		}),
+	}
+
+	cmdGetPublicKey = &cmdline.Command{
+		Name:  "publickey",
+		Short: "Prints the public key of the principal.",
+		Long: `
+Prints out the public key of the principal specified by the environment
+that this tool is running in.
+
+The key is printed as a base64 encoded bytes of the DER-format representation
+of the key (suitable to be provided as an argument to the 'recognize' command
+for example).
+
+With --pretty, a 16-byte fingerprint of the key instead. This format is easier
+for humans to read and is used in output of other commands in this program, but
+is not suitable as an argument to the 'recognize' command.
+`,
+		Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline.Env, args []string) error {
+			key := v23.GetPrincipal(ctx).PublicKey()
+			if flagGetPublicKeyPretty {
+				fmt.Println(key)
+				return nil
+			}
+			der, err := key.MarshalBinary()
+			if err != nil {
+				return fmt.Errorf("corrupted key: %v", err)
+			}
+			fmt.Println(base64.URLEncoding.EncodeToString(der))
+			return nil
+		}),
+	}
+
+	cmdGetTrustedRoots = &cmdline.Command{
+		Name:  "recognizedroots",
+		Short: "Return recognized blessings, and their associated public key.",
+		Long: `
+Shows list of blessing names that the principal recognizes, and their associated
+public key. If the principal is operating as a client, contacted servers must
+appear on this list. If the principal is operating as a server, clients must
+present blessings derived from this list.
+`,
+		Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline.Env, args []string) error {
+			fmt.Printf(v23.GetPrincipal(ctx).Roots().DebugString())
+			return nil
+		}),
+	}
+
+	cmdGetPeerMap = &cmdline.Command{
+		Name:  "peermap",
+		Short: "Shows the map from peer pattern to which blessing name to present.",
+		Long: `
+Shows the map from peer pattern to which blessing name to present.
+If the principal operates as a server, it presents its default blessing to all peers.
+If the principal operates as a client, it presents the map value associated with
+the peer it contacts.
+`,
+		Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline.Env, args []string) error {
+			fmt.Printf(v23.GetPrincipal(ctx).BlessingStore().DebugString())
+			return nil
+		}),
+	}
+
+	cmdGetForPeer = &cmdline.Command{
+		Name:  "forpeer",
+		Short: "Return blessings marked for the provided peer",
+		Long: `
+Returns blessings that are marked for the provided peer in the
+BlessingStore specified by the environment that this tool is
+running in.
+Providing --names will print the blessings' chain names.
+Providing --rootkey <chain_name> will print the root key of the certificate chain
+with chain_name.
+Providing --caveats <chain_name> will print the caveats on the certificate chain
+with chain_name.
+`,
+		ArgsName: "[<peer_1> ... <peer_k>]",
+		ArgsLong: `
+<peer_1> ... <peer_k> are the (human-readable string) blessings bound
+to the peer. The returned blessings are marked with a pattern that is
+matched by at least one of these. If no arguments are specified,
+store.forpeer returns the blessings that are marked for all peers (i.e.,
+blessings set on the store with the "..." pattern).
+`,
+		Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline.Env, args []string) error {
+			return printBlessingsInfo(v23.GetPrincipal(ctx).BlessingStore().ForPeer(args...))
+		}),
+	}
+
+	cmdGetDefault = &cmdline.Command{
+		Name:  "default",
+		Short: "Return blessings marked as default",
+		Long: `
+Returns blessings that are marked as default in the BlessingStore specified by
+the environment that this tool is running in.
+Providing --names will print the default blessings' chain names.
+Providing --rootkey <chain_name> will print the root key of the certificate chain
+with chain_name.
+Providing --caveats <chain_name> will print the caveats on the certificate chain
+with chain_name.
+`,
+		Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline.Env, args []string) error {
+			return printBlessingsInfo(v23.GetPrincipal(ctx).BlessingStore().Default())
+		}),
+	}
+
+	cmdSetForPeer = &cmdline.Command{
+		Name:  "forpeer",
+		Short: "Set provided blessings for peer",
+		Long: `
+Marks the provided blessings to be shared with the provided peers on the
+BlessingStore specified by the environment that this tool is running in.
+
+'set b pattern' marks the intention to reveal b to peers who
+present blessings of their own matching 'pattern'.
+
+'set nil pattern' can be used to remove the blessings previously
+associated with the pattern (by a prior 'set' command).
+
+It is an error to call 'set forpeer' with blessings whose public
+key does not match the public key of this principal specified
+by the environment.
+`,
+		ArgsName: "<file> <pattern>",
+		ArgsLong: `
+<file> is the path to a file containing a blessing typically obtained
+from this tool. - is used for STDIN.
+
+<pattern> is the BlessingPattern used to identify peers with whom this
+blessing can be shared with.
+`,
+		Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline.Env, args []string) error {
+			if len(args) != 2 {
+				return fmt.Errorf("requires exactly two arguments <file>, <pattern>, provided %d", len(args))
+			}
+			blessings, err := decodeBlessings(args[0])
+			if err != nil {
+				return fmt.Errorf("failed to decode provided blessings: %v", err)
+			}
+			pattern := security.BlessingPattern(args[1])
+
+			p := v23.GetPrincipal(ctx)
+			if _, err := p.BlessingStore().Set(blessings, pattern); err != nil {
+				return fmt.Errorf("failed to set blessings %v for peers %v: %v", blessings, pattern, err)
+			}
+			if flagAddToRoots {
+				if err := p.AddToRoots(blessings); err != nil {
+					return fmt.Errorf("AddToRoots failed: %v", err)
+				}
+			}
+			return nil
+		}),
+	}
+
+	cmdRecognize = &cmdline.Command{
+		Name:  "recognize",
+		Short: "Add to the set of identity providers recognized by this principal",
+		Long: `
+Adds an identity provider to the set of recognized roots public keys for this principal.
+
+It accepts either a single argument (which points to a file containing a blessing)
+or two arguments (a name and a base64-encoded DER-encoded public key).
+
+For example, to make the principal in credentials directory A recognize the
+root of the default blessing in credentials directory B:
+  principal -v23.credentials=B bless A some_extension |
+  principal -v23.credentials=A recognize -
+The extension 'some_extension' has no effect in the command above.
+
+Or to make the principal in credentials directory A recognize the base64-encoded
+public key KEY for blessing pattern P:
+  principal -v23.credentials=A recognize P KEY
+`,
+		ArgsName: "<key|blessing> [<blessing pattern>]",
+		ArgsLong: `
+<blessing> is the path to a file containing a blessing typically obtained from
+this tool. - is used for STDIN.
+
+<key> is a base64-encoded, DER-encoded public key.
+
+<blessing pattern> is the blessing pattern for which <key> should be recognized.
+`,
+		Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline.Env, args []string) error {
+			if len(args) != 1 && len(args) != 2 {
+				return fmt.Errorf("requires either one argument <file>, or two arguments <key> <blessing pattern>, provided %d", len(args))
+			}
+			p := v23.GetPrincipal(ctx)
+			if len(args) == 1 {
+				blessings, err := decodeBlessings(args[0])
+				if err != nil {
+					return fmt.Errorf("failed to decode provided blessings: %v", err)
+				}
+				if err := p.AddToRoots(blessings); err != nil {
+					return fmt.Errorf("AddToRoots failed: %v", err)
+				}
+				return nil
+			}
+			// len(args) == 2
+			der, err := base64.URLEncoding.DecodeString(args[1])
+			if err != nil {
+				return fmt.Errorf("invalid base64 encoding of public key: %v", err)
+			}
+			key, err := security.UnmarshalPublicKey(der)
+			if err != nil {
+				return fmt.Errorf("invalid DER encoding of public key: %v", err)
+			}
+			return p.Roots().Add(key, security.BlessingPattern(args[0]))
+		}),
+	}
+
+	cmdSetDefault = &cmdline.Command{
+		Name:  "default",
+		Short: "Set provided blessings as default",
+		Long: `
+Sets the provided blessings as default in the BlessingStore specified by the
+environment that this tool is running in.
+
+It is an error to call 'set default' with blessings whose public key does
+not match the public key of the principal specified by the environment.
+`,
+		ArgsName: "<file>",
+		ArgsLong: `
+<file> is the path to a file containing a blessing typically obtained from
+this tool. - is used for STDIN.
+`,
+		Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline.Env, args []string) error {
+			if len(args) != 1 {
+				return fmt.Errorf("requires exactly one argument, <file>, provided %d", len(args))
+			}
+			blessings, err := decodeBlessings(args[0])
+			if err != nil {
+				return fmt.Errorf("failed to decode provided blessings: %v", err)
+			}
+
+			p := v23.GetPrincipal(ctx)
+			if err := p.BlessingStore().SetDefault(blessings); err != nil {
+				return fmt.Errorf("failed to set blessings %v as default: %v", blessings, err)
+			}
+			if flagAddToRoots {
+				if err := p.AddToRoots(blessings); err != nil {
+					return fmt.Errorf("AddToRoots failed: %v", err)
+				}
+			}
+			return nil
+		}),
+	}
+
+	cmdCreate = &cmdline.Command{
+		Name:  "create",
+		Short: "Create a new principal and persist it into a directory",
+		Long: `
+Creates a new principal with a single self-blessed blessing and writes it out
+to the provided directory. The same directory can then be used to set the
+V23_CREDENTIALS environment variable for other vanadium applications.
+
+The operation fails if the directory already contains a principal. In this case
+the --overwrite flag can be provided to clear the directory and write out the
+new principal.
+`,
+		ArgsName: "<directory> <blessing>",
+		ArgsLong: `
+<directory> is the directory to which the new principal will be persisted.
+
+<blessing> is the self-blessed blessing that the principal will be setup to use by default.
+	`,
+		Runner: cmdline.RunnerFunc(func(env *cmdline.Env, args []string) error {
+			if len(args) != 2 {
+				return fmt.Errorf("requires exactly two arguments: <directory> and <blessing>, provided %d", len(args))
+			}
+			dir, name := args[0], args[1]
+			if flagCreateOverwrite {
+				if err := os.RemoveAll(dir); err != nil {
+					return err
+				}
+			}
+			p, err := vsecurity.CreatePersistentPrincipal(dir, nil)
+			if err != nil {
+				return err
+			}
+			blessings, err := p.BlessSelf(name)
+			if err != nil {
+				return fmt.Errorf("BlessSelf(%q) failed: %v", name, err)
+			}
+			if err := vsecurity.SetDefaultBlessings(p, blessings); err != nil {
+				return fmt.Errorf("could not set blessings %v as default: %v", blessings, err)
+			}
+			return nil
+		}),
+	}
+
+	cmdFork = &cmdline.Command{
+		Name:  "fork",
+		Short: "Fork a new principal from the principal that this tool is running as and persist it into a directory",
+		Long: `
+Creates a new principal with a blessing from the principal specified by the
+environment that this tool is running in, and writes it out to the provided
+directory. The blessing that will be extended is the default one from the
+blesser's store, or specified by the --with flag. Expiration on the blessing
+are controlled via the --for flag. Additional caveats on the blessing are
+controlled with the --caveat flag. The blessing is marked as default and
+shareable with all peers on the new principal's blessing store.
+
+The operation fails if the directory already contains a principal. In this case
+the --overwrite flag can be provided to clear the directory and write out the
+forked principal.
+`,
+		ArgsName: "<directory> <extension>",
+		ArgsLong: `
+<directory> is the directory to which the forked principal will be persisted.
+
+<extension> is the extension under which the forked principal is blessed.
+	`,
+		Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline.Env, args []string) error {
+			if len(args) != 2 {
+				return fmt.Errorf("requires exactly two arguments: <directory> and <extension>, provided %d", len(args))
+			}
+			dir, extension := args[0], args[1]
+			caveats, err := caveatsFromFlags(flagForkFor, &flagForkCaveats)
+			if err != nil {
+				return err
+			}
+			if !flagForkRequireCaveats && len(caveats) == 0 {
+				caveats = []security.Caveat{security.UnconstrainedUse()}
+			}
+			if len(caveats) == 0 {
+				return errNoCaveats
+			}
+			var with security.Blessings
+			if len(flagForkWith) > 0 {
+				if with, err = decodeBlessings(flagForkWith); err != nil {
+					return fmt.Errorf("failed to read blessings from --with=%q: %v", flagForkWith, err)
+				}
+			} else {
+				with = v23.GetPrincipal(ctx).BlessingStore().Default()
+			}
+
+			if flagCreateOverwrite {
+				if err := os.RemoveAll(dir); err != nil {
+					return err
+				}
+			}
+			p, err := vsecurity.CreatePersistentPrincipal(dir, nil)
+			if err != nil {
+				return err
+			}
+
+			key := p.PublicKey()
+			rp := v23.GetPrincipal(ctx)
+			blessings, err := rp.Bless(key, with, extension, caveats[0], caveats[1:]...)
+			if err != nil {
+				return fmt.Errorf("Bless(%v, %v, %q, ...) failed: %v", key, with, extension, err)
+			}
+			if err := vsecurity.SetDefaultBlessings(p, blessings); err != nil {
+				return fmt.Errorf("could not set blessings %v as default: %v", blessings, err)
+			}
+			return nil
+		}),
+	}
+
+	cmdSeekBlessings = &cmdline.Command{
+		Name:  "seekblessings",
+		Short: "Seek blessings from a web-based Vanadium blessing service",
+		Long: `
+Seeks blessings from a web-based Vanadium blesser which
+requires the caller to first authenticate with Google using OAuth. Simply
+run the command to see what happens.
+
+The blessings are sought for the principal specified by the environment that
+this tool is running in.
+
+The blessings obtained are set as default, unless the --set-default flag is
+set to true, and are also set for sharing with all peers, unless a more
+specific peer pattern is provided using the --for-peer flag.
+`,
+		Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline.Env, args []string) error {
+			p := v23.GetPrincipal(ctx)
+
+			blessedChan := make(chan string)
+			defer close(blessedChan)
+			macaroonChan, err := getMacaroonForBlessRPC(p.PublicKey(), flagSeekBlessingsFrom, blessedChan, flagSeekBlessingsBrowser)
+			if err != nil {
+				return fmt.Errorf("failed to get macaroon from Vanadium blesser: %v", err)
+			}
+
+			blessings, err := exchangeMacaroonForBlessing(ctx, macaroonChan)
+			if err != nil {
+				return err
+			}
+			blessedChan <- fmt.Sprint(blessings)
+			// Wait for getTokenForBlessRPC to clean up:
+			<-macaroonChan
+
+			if flagSeekBlessingsSetDefault {
+				if err := p.BlessingStore().SetDefault(blessings); err != nil {
+					return fmt.Errorf("failed to set blessings %v as default: %v", blessings, err)
+				}
+			}
+			if pattern := security.BlessingPattern(flagSeekBlessingsForPeer); len(pattern) > 0 {
+				if _, err := p.BlessingStore().Set(blessings, pattern); err != nil {
+					return fmt.Errorf("failed to set blessings %v for peers %v: %v", blessings, pattern, err)
+				}
+			}
+			if flagAddToRoots {
+				if err := p.AddToRoots(blessings); err != nil {
+					return fmt.Errorf("AddToRoots failed: %v", err)
+				}
+			}
+			fmt.Fprintf(env.Stdout, "Received blessings: %v\n", blessings)
+			return nil
+		}),
+	}
+
+	cmdRecvBlessings = &cmdline.Command{
+		Name:  "recvblessings",
+		Short: "Receive blessings sent by another principal and use them as the default",
+		Long: `
+Allow another principal (likely a remote process) to bless this one.
+
+This command sets up the invoker (this process) to wait for a blessing
+from another invocation of this tool (remote process) and prints out the
+command to be run as the remote principal.
+
+The received blessings are set as default, unless the --set-default flag is
+set to true, and are also set for sharing with all peers, unless a more
+specific peer pattern is provided using the --for-peer flag.
+
+TODO(ashankar,cnicolaou): Make this next paragraph possible! Requires
+the ability to obtain the proxied endpoint.
+
+Typically, this command should require no arguments.
+However, if the sender and receiver are on different network domains, it may
+make sense to use the --v23.proxy flag:
+    principal --v23.proxy=proxy recvblessings
+
+The command to be run at the sender is of the form:
+    principal bless --remote-key=KEY --remote-token=TOKEN ADDRESS EXTENSION
+
+The --remote-key flag is used to by the sender to "authenticate" the receiver,
+ensuring it blesses the intended recipient and not any attacker that may have
+taken over the address.
+
+The --remote-token flag is used by the sender to authenticate itself to the
+receiver. This helps ensure that the receiver rejects blessings from senders
+who just happened to guess the network address of the 'recvblessings'
+invocation.
+
+If the --remote-arg-file flag is provided to recvblessings, the remote key, remote token
+and object address of this principal will be written to the specified location.
+This file can be supplied to bless:
+		principal bless --remote-arg-file FILE EXTENSION
+
+`,
+		Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline.Env, args []string) error {
+			if len(args) != 0 {
+				return fmt.Errorf("command accepts no arguments")
+			}
+			var token [24]byte
+			if _, err := rand.Read(token[:]); err != nil {
+				return fmt.Errorf("unable to generate token: %v", err)
+			}
+			p := v23.GetPrincipal(ctx)
+			service := &recvBlessingsService{
+				principal: p,
+				token:     base64.URLEncoding.EncodeToString(token[:]),
+				notify:    make(chan error),
+			}
+			server, err := xrpc.NewServer(ctx, "", service, security.AllowEveryone())
+			if err != nil {
+				return fmt.Errorf("failed to create server to listen for blessings: %v", err)
+			}
+			name := server.Status().Endpoints[0].Name()
+			fmt.Println("Run the following command on behalf of the principal that will send blessings:")
+			fmt.Println("You may want to adjust flags affecting the caveats on this blessing, for example using")
+			fmt.Println("the --for flag")
+			fmt.Println()
+			if len(flagRemoteArgFile) > 0 {
+				if err := writeRecvBlessingsInfo(flagRemoteArgFile, p.PublicKey().String(), service.token, name); err != nil {
+					return fmt.Errorf("failed to write recvblessings info to %v: %v", flagRemoteArgFile, err)
+				}
+				fmt.Printf("make %q accessible to the blesser, possibly by copying the file over and then run:\n", flagRemoteArgFile)
+				fmt.Printf("principal bless --remote-arg-file=%v", flagRemoteArgFile)
+			} else {
+				fmt.Printf("principal bless --remote-key=%v --remote-token=%v %v\n", p.PublicKey(), service.token, name)
+			}
+			fmt.Println()
+			fmt.Println("...waiting for sender..")
+			return <-service.notify
+		}),
+	}
+)
+
+func printAnnotatedBlessingsNames(b security.Blessings) string {
+	// If the Blessings are expired, print a message saying so.
+	expiredMessage := ""
+	if exp := b.Expiry(); !exp.IsZero() && exp.Before(time.Now()) {
+		expiredMessage = " [EXPIRED]"
+	}
+	return fmt.Sprintf("%v%s", b, expiredMessage)
+}
+
+func blessArgs(env *cmdline.Env, args []string) (tobless, extension, remoteKey, remoteToken string, err error) {
+	extensionInArgs := false
+	if len(flagRemoteArgFile) == 0 {
+		tobless = args[0]
+		remoteKey = flagBlessRemoteKey
+		remoteToken = flagBlessRemoteToken
+		extensionInArgs = len(args) > 1
+	} else if len(flagRemoteArgFile) > 0 {
+		remoteKey, remoteToken, tobless, err = blessArgsFromFile(flagRemoteArgFile)
+		extensionInArgs = len(args) > 0
+	}
+	if extensionInArgs {
+		extension = args[len(args)-1]
+	} else {
+		extension, err = readFromStdin(env, "Extension to use for blessing:")
+	}
+	return
+}
+
+func confirmNoCaveats(env *cmdline.Env) error {
+	text, err := readFromStdin(env, `WARNING: No caveats provided
+It is generally dangerous to bless another principal without any caveats as
+that gives them unrestricted access to the blesser's credentials.
+
+Caveats can be specified with the --for or --caveat flags.
+
+Do you really wish to bless without caveats? (YES to confirm)`)
+	if err != nil || strings.ToUpper(text) != "YES" {
+		return errNoCaveats
+	}
+	return nil
+}
+
+func readFromStdin(env *cmdline.Env, prompt string) (string, error) {
+	fmt.Fprintf(env.Stdout, "%v ", prompt)
+	os.Stdout.Sync()
+	// Cannot use bufio because that may "lose" data beyond the line (the
+	// remainder in the buffer).
+	// Do the inefficient byte-by-byte scan for now - shouldn't be a problem
+	// given the common use case. If that becomes a problem, switch to bufio
+	// and share the bufio.Reader between multiple calls to readFromStdin.
+	buf := make([]byte, 0, 100)
+	r := make([]byte, 1)
+	for {
+		n, err := env.Stdin.Read(r)
+		if n == 1 && r[0] == '\n' {
+			break
+		}
+		if n == 1 {
+			buf = append(buf, r[0])
+			continue
+		}
+		if err != nil {
+			return "", err
+		}
+	}
+	return strings.TrimSpace(string(buf)), nil
+}
+
+func blessOverFileSystem(p security.Principal, tobless string, with security.Blessings, extension string, caveats []security.Caveat) (security.Blessings, error) {
+	var key security.PublicKey
+	if finfo, err := os.Stat(tobless); err == nil && finfo.IsDir() {
+		other, err := vsecurity.LoadPersistentPrincipal(tobless, nil)
+		if err != nil {
+			if other, err = vsecurity.CreatePersistentPrincipal(tobless, nil); err != nil {
+				return security.Blessings{}, fmt.Errorf("failed to read principal in directory %q: %v", tobless, err)
+			}
+		}
+		key = other.PublicKey()
+	} else if other, err := decodeBlessings(tobless); err != nil {
+		return security.Blessings{}, fmt.Errorf("failed to decode blessings in %q: %v", tobless, err)
+	} else {
+		key = other.PublicKey()
+	}
+	return p.Bless(key, with, extension, caveats[0], caveats[1:]...)
+}
+
+type recvBlessingsInfo struct {
+	RemoteKey   string `json:"remote_key"`
+	RemoteToken string `json:"remote_token"`
+	Name        string `json:"name"`
+}
+
+func writeRecvBlessingsInfo(fname string, remoteKey, remoteToken, name string) error {
+	f, err := os.OpenFile(fname, os.O_WRONLY|os.O_CREATE, 0666)
+	if err != nil {
+		return err
+	}
+	b, err := json.Marshal(recvBlessingsInfo{remoteKey, remoteToken, name})
+	if err != nil {
+		return err
+	}
+	if _, err := f.Write(b); err != nil {
+		return err
+	}
+	return nil
+}
+
+func blessArgsFromFile(fname string) (remoteKey, remoteToken, tobless string, err error) {
+	blessJSON, err := ioutil.ReadFile(fname)
+	if err != nil {
+		return "", "", "", err
+	}
+	var binfo recvBlessingsInfo
+	if err := json.Unmarshal(blessJSON, &binfo); err != nil {
+		return "", "", "", err
+	}
+	return binfo.RemoteKey, binfo.RemoteToken, binfo.Name, err
+}
+
+func main() {
+	cmdline.HideGlobalFlagsExcept()
+	cmdBlessSelf.Flags.Var(&flagBlessSelfCaveats, "caveat", flagBlessSelfCaveats.usage())
+	cmdBlessSelf.Flags.DurationVar(&flagBlessSelfFor, "for", 0, "Duration of blessing validity (zero implies no expiration)")
+
+	cmdDump.Flags.BoolVar(&flagDumpShort, "s", false, "If true, show only the default blessing names")
+
+	cmdFork.Flags.BoolVar(&flagCreateOverwrite, "overwrite", false, "If true, any existing principal data in the directory will be overwritten")
+	cmdFork.Flags.Var(&flagForkCaveats, "caveat", flagForkCaveats.usage())
+	cmdFork.Flags.DurationVar(&flagForkFor, "for", 0, "Duration of blessing validity (zero implies no expiration caveat)")
+	cmdFork.Flags.BoolVar(&flagForkRequireCaveats, "require-caveats", true, "If false, allow blessing without any caveats. This is typically not advised as the principal wielding the blessing will be almost as powerful as its blesser")
+	cmdFork.Flags.StringVar(&flagForkWith, "with", "", "Path to file containing blessing to extend")
+
+	cmdBless.Flags.Var(&flagBlessCaveats, "caveat", flagBlessCaveats.usage())
+	cmdBless.Flags.DurationVar(&flagBlessFor, "for", 0, "Duration of blessing validity (zero implies no expiration caveat)")
+	cmdBless.Flags.BoolVar(&flagBlessRequireCaveats, "require-caveats", true, "If false, allow blessing without any caveats. This is typically not advised as the principal wielding the blessing will be almost as powerful as its blesser")
+	cmdBless.Flags.StringVar(&flagBlessWith, "with", "", "Path to file containing blessing to extend")
+	cmdBless.Flags.StringVar(&flagBlessRemoteKey, "remote-key", "", "Public key of the remote principal to bless (obtained from the 'recvblessings' command run by the remote principal")
+	cmdBless.Flags.StringVar(&flagBlessRemoteToken, "remote-token", "", "Token provided by principal running the 'recvblessings' command")
+	cmdBless.Flags.StringVar(&flagRemoteArgFile, "remote-arg-file", "", "File containing bless arguments written by 'principal recvblessings -remote-arg-file FILE EXTENSION' command. This can be provided to bless in place of --remote-key, --remote-token, and <principal>")
+
+	defaultFrom := "https://dev.v.io/auth/google"
+	if e := os.Getenv(ref.EnvOAuthIdentityProvider); e != "" {
+		defaultFrom = e
+	}
+	cmdSeekBlessings.Flags.StringVar(&flagSeekBlessingsFrom, "from", defaultFrom, "URL to use to begin the seek blessings process")
+	cmdSeekBlessings.Flags.BoolVar(&flagSeekBlessingsSetDefault, "set-default", true, "If true, the blessings obtained will be set as the default blessing in the store")
+	cmdSeekBlessings.Flags.StringVar(&flagSeekBlessingsForPeer, "for-peer", string(security.AllPrincipals), "If non-empty, the blessings obtained will be marked for peers matching this pattern in the store")
+	cmdSeekBlessings.Flags.BoolVar(&flagSeekBlessingsBrowser, "browser", true, "If false, the seekblessings command will not open the browser and only print the url to visit.")
+	cmdSeekBlessings.Flags.BoolVar(&flagAddToRoots, "add-to-roots", true, "If true, the root certificate of the blessing will be added to the principal's set of recognized root certificates")
+
+	cmdSetForPeer.Flags.BoolVar(&flagAddToRoots, "add-to-roots", true, "If true, the root certificate of the blessing will be added to the principal's set of recognized root certificates")
+
+	cmdSetDefault.Flags.BoolVar(&flagAddToRoots, "add-to-roots", true, "If true, the root certificate of the blessing will be added to the principal's set of recognized root certificates")
+
+	cmdCreate.Flags.BoolVar(&flagCreateOverwrite, "overwrite", false, "If true, any existing principal data in the directory will be overwritten")
+
+	cmdRecvBlessings.Flags.BoolVar(&flagRecvBlessingsSetDefault, "set-default", true, "If true, the blessings received will be set as the default blessing in the store")
+	cmdRecvBlessings.Flags.StringVar(&flagRecvBlessingsForPeer, "for-peer", string(security.AllPrincipals), "If non-empty, the blessings received will be marked for peers matching this pattern in the store")
+	cmdRecvBlessings.Flags.StringVar(&flagRemoteArgFile, "remote-arg-file", "", "If non-empty, the remote key, remote token, and principal will be written to the specified file in a JSON object. This can be provided to 'principal bless --remote-arg-file FILE EXTENSION'")
+
+	cmdGetForPeer.Flags.BoolVar(&flagBlessingsNames, "names", false, "If true, shows the value of the blessing name to be presented to the peer")
+	cmdGetForPeer.Flags.StringVar(&flagBlessingsRootKey, "rootkey", "", "Shows the value of the root key of the provided certificate chain name.")
+	cmdGetForPeer.Flags.StringVar(&flagBlessingsCaveats, "caveats", "", "Shows the caveats on the provided certificate chain name.")
+
+	cmdGetDefault.Flags.BoolVar(&flagBlessingsNames, "names", false, "If true, shows the value of the blessing name to be presented to the peer")
+	cmdGetDefault.Flags.StringVar(&flagBlessingsRootKey, "rootkey", "", "Shows the value of the root key of the provided certificate chain name.")
+	cmdGetDefault.Flags.StringVar(&flagBlessingsCaveats, "caveats", "", "Shows the caveats on the provided certificate chain name.")
+
+	cmdGetPublicKey.Flags.BoolVar(&flagGetPublicKeyPretty, "pretty", false, "If true, print the key out in a more human-readable but lossy representation.")
+
+	cmdSet := &cmdline.Command{
+		Name:  "set",
+		Short: "Mutate the principal's blessings.",
+		Long: `
+Commands to mutate the blessings of the principal.
+
+All input blessings are expected to be serialized using base64-VOM-encoding.
+See 'principal get'.
+`,
+		Children: []*cmdline.Command{cmdSetDefault, cmdSetForPeer},
+	}
+
+	cmdGet := &cmdline.Command{
+		Name:  "get",
+		Short: "Read the principal's blessings.",
+		Long: `
+Commands to inspect the blessings of the principal.
+
+All blessings are printed to stdout using base64-VOM-encoding.
+`,
+		Children: []*cmdline.Command{cmdGetDefault, cmdGetForPeer, cmdGetPublicKey, cmdGetTrustedRoots, cmdGetPeerMap},
+	}
+
+	root := &cmdline.Command{
+		Name:  "principal",
+		Short: "creates and manages Vanadium principals and blessings",
+		Long: `
+Command principal creates and manages Vanadium principals and blessings.
+
+All objects are printed using base64-VOM-encoding.
+`,
+		Children: []*cmdline.Command{cmdCreate, cmdFork, cmdSeekBlessings, cmdRecvBlessings, cmdDump, cmdDumpBlessings, cmdBlessSelf, cmdBless, cmdSet, cmdGet, cmdRecognize},
+	}
+	cmdline.Main(root)
+}
+
+func decodeBlessings(fname string) (security.Blessings, error) {
+	var b security.Blessings
+	err := decode(fname, &b)
+	return b, err
+}
+
+func dumpBlessings(blessings security.Blessings) error {
+	if blessings.IsZero() {
+		return fmt.Errorf("no blessings found")
+	}
+	str, err := base64VomEncode(blessings)
+	if err != nil {
+		return fmt.Errorf("base64-VOM encoding failed: %v", err)
+	}
+	fmt.Println(str)
+	return nil
+}
+
+func printBlessingsInfo(blessings security.Blessings) error {
+	if blessings.IsZero() {
+		return fmt.Errorf("no blessings found")
+	}
+	if flagBlessingsNames {
+		fmt.Println(strings.Replace(fmt.Sprint(blessings), ",", "\n", -1))
+		return nil
+	} else if len(flagBlessingsRootKey) > 0 {
+		chain, err := getChainByName(blessings, flagBlessingsRootKey)
+		if err != nil {
+			return err
+		}
+		fmt.Println(rootkey(chain))
+		return nil
+	} else if len(flagBlessingsCaveats) > 0 {
+		chain, err := getChainByName(blessings, flagBlessingsCaveats)
+		if err != nil {
+			return err
+		}
+		cavs, err := prettyPrintCaveats(chain)
+		if err != nil {
+			return err
+		}
+		for _, c := range cavs {
+			fmt.Println(c)
+		}
+		return nil
+	}
+	return dumpBlessings(blessings)
+}
+
+func prettyPrintCaveats(chain []security.Certificate) ([]string, error) {
+	var cavs []security.Caveat
+	for _, cert := range chain {
+		cavs = append(cavs, cert.Caveats...)
+	}
+	var s []string
+	for _, cav := range cavs {
+		if cav.Id == security.PublicKeyThirdPartyCaveat.Id {
+			c := cav.ThirdPartyDetails()
+			s = append(s, fmt.Sprintf("ThirdPartyCaveat: Requires discharge from %v (ID=%q)", c.Location(), c.ID()))
+			continue
+		}
+		var param interface{}
+		if err := vom.Decode(cav.ParamVom, &param); err != nil {
+			return nil, err
+		}
+		switch cav.Id {
+		case security.ConstCaveat.Id:
+			// In the case a ConstCaveat is specified, we only want to print it
+			// if it never validates.
+			if !param.(bool) {
+				s = append(s, fmt.Sprintf("Never validates"))
+			}
+		case security.ExpiryCaveat.Id:
+			s = append(s, fmt.Sprintf("Expires at %v", param))
+		case security.MethodCaveat.Id:
+			s = append(s, fmt.Sprintf("Restricted to methods %v", param))
+		case security.PeerBlessingsCaveat.Id:
+			s = append(s, fmt.Sprintf("Restricted to peers with blessings %v", param))
+		default:
+			s = append(s, cav.String())
+		}
+	}
+	return s, nil
+}
+
+func getChainByName(b security.Blessings, name string) ([]security.Certificate, error) {
+	wire, err := blessings2wire(b)
+	if err != nil {
+		return nil, err
+	}
+	for _, chain := range wire.CertificateChains {
+		if chainName(chain) == name {
+			return chain, nil
+		}
+	}
+	return nil, fmt.Errorf("no chains of name %v in %v", name, b)
+}
+
+func read(fname string) (string, error) {
+	if len(fname) == 0 {
+		return "", nil
+	}
+	f := os.Stdin
+	if fname != "-" {
+		var err error
+		if f, err = os.Open(fname); err != nil {
+			return "", fmt.Errorf("failed to open %q: %v", fname, err)
+		}
+	}
+	defer f.Close()
+	var buf bytes.Buffer
+	if _, err := io.Copy(&buf, f); err != nil {
+		return "", fmt.Errorf("failed to read %q: %v", fname, err)
+	}
+	return buf.String(), nil
+}
+
+func decode(fname string, val interface{}) error {
+	str, err := read(fname)
+	if err != nil {
+		return err
+	}
+	if err := base64VomDecode(str, val); err != nil || val == nil {
+		return fmt.Errorf("failed to decode %q: %v", fname, err)
+	}
+	return nil
+}
+
+func defaultBlessingName() string {
+	var name string
+	if user, _ := user.Current(); user != nil && len(user.Username) > 0 {
+		name = user.Username
+	} else {
+		name = "anonymous"
+	}
+	if host, _ := os.Hostname(); len(host) > 0 {
+		name = name + "@" + host
+	}
+	return name
+}
+
+func rootkey(chain []security.Certificate) string {
+	if len(chain) == 0 {
+		return "<empty certificate chain>"
+	}
+	key, err := security.UnmarshalPublicKey(chain[0].PublicKey)
+	if err != nil {
+		return fmt.Sprintf("<invalid PublicKey: %v>", err)
+	}
+	return fmt.Sprintf("%v", key)
+}
+
+func chainName(chain []security.Certificate) string {
+	exts := make([]string, len(chain))
+	for i, cert := range chain {
+		exts[i] = cert.Extension
+	}
+	return strings.Join(exts, security.ChainSeparator)
+}
+
+func base64VomEncode(i interface{}) (string, error) {
+	buf := &bytes.Buffer{}
+	closer := base64.NewEncoder(base64.URLEncoding, buf)
+	enc := vom.NewEncoder(closer)
+	if err := enc.Encode(i); err != nil {
+		return "", err
+	}
+	// Must close the base64 encoder to flush out any partially written
+	// blocks.
+	if err := closer.Close(); err != nil {
+		return "", err
+	}
+	return buf.String(), nil
+}
+
+func base64VomDecode(s string, i interface{}) error {
+	b, err := base64.URLEncoding.DecodeString(s)
+	if err != nil {
+		return err
+	}
+	dec := vom.NewDecoder(bytes.NewBuffer(b))
+	return dec.Decode(i)
+}
+
+type recvBlessingsService struct {
+	principal security.Principal
+	notify    chan error
+	token     string
+}
+
+func (r *recvBlessingsService) Grant(_ *context.T, call rpc.ServerCall, token string) error {
+	b := call.GrantedBlessings()
+	if b.IsZero() {
+		return fmt.Errorf("no blessings granted by sender")
+	}
+	if len(token) != len(r.token) {
+		// A timing attack can be used to figure out the length
+		// of the token, but then again, so can looking at the
+		// source code. So, it's okay.
+		return fmt.Errorf("blessings received from unexpected sender")
+	}
+	if subtle.ConstantTimeCompare([]byte(token), []byte(r.token)) != 1 {
+		return fmt.Errorf("blessings received from unexpected sender")
+	}
+	if flagRecvBlessingsSetDefault {
+		if err := r.principal.BlessingStore().SetDefault(b); err != nil {
+			return fmt.Errorf("failed to set blessings %v as default: %v", b, err)
+		}
+	}
+	if pattern := security.BlessingPattern(flagRecvBlessingsForPeer); len(pattern) > 0 {
+		if _, err := r.principal.BlessingStore().Set(b, pattern); err != nil {
+			return fmt.Errorf("failed to set blessings %v for peers %v: %v", b, pattern, err)
+		}
+	}
+	if flagAddToRoots {
+		if err := r.principal.AddToRoots(b); err != nil {
+			return fmt.Errorf("failed to add blessings to recognized roots: %v", err)
+		}
+	}
+	fmt.Println("Received blessings:", b)
+	r.notify <- nil
+	return nil
+}
+
+type granter struct {
+	with      security.Blessings
+	extension string
+	caveats   []security.Caveat
+	serverKey string
+}
+
+func (g *granter) Grant(ctx *context.T, call security.Call) (security.Blessings, error) {
+	server := call.RemoteBlessings()
+	p := call.LocalPrincipal()
+	if got := fmt.Sprintf("%v", server.PublicKey()); got != g.serverKey {
+		// If the granter returns an error, the RPC framework should
+		// abort the RPC before sending the request to the server.
+		// Thus, there is no concern about leaking the token to an
+		// imposter server.
+		return security.Blessings{}, fmt.Errorf("key mismatch: Remote end has public key %v, want %v", got, g.serverKey)
+	}
+	return p.Bless(server.PublicKey(), g.with, g.extension, g.caveats[0], g.caveats[1:]...)
+}
+func (*granter) RPCCallOpt() {}
+
+func blessOverNetwork(ctx *context.T, object string, granter *granter, remoteToken string) error {
+	client := v23.GetClient(ctx)
+	// The receiver is being authorized based on the hash of its public key
+	// (see Grant), so it should be fine to ignore the blessing names in the endpoint
+	// (which are likely to not be recognized by the sender anyway).
+	//
+	// At worst, there is a privacy leak of the senders intent to send some
+	// blessings.  That could be addressed by making the full public key of
+	// the recipeint available to the sender and using
+	// options.ServerPublicKey instead of providing a "hash" of the
+	// recipients public key and verifying in the Granter implementation.
+	if err := client.Call(ctx, object, "Grant", []interface{}{remoteToken}, nil, granter, options.SkipServerEndpointAuthorization{}); err != nil {
+		return fmt.Errorf("failed to make RPC to %q: %v", object, err)
+	}
+	return nil
+}
+
+func caveatsFromFlags(expiry time.Duration, caveatsFlag *caveatsFlag) ([]security.Caveat, error) {
+	caveats, err := caveatsFlag.Compile()
+	if err != nil {
+		return nil, fmt.Errorf("failed to parse caveats: %v", err)
+	}
+	if expiry != 0 {
+		ecav, err := security.NewExpiryCaveat(time.Now().Add(expiry))
+		if err != nil {
+			return nil, fmt.Errorf("failed to create expiration caveat: %v", err)
+		}
+		caveats = append(caveats, ecav)
+	}
+	return caveats, nil
+}
+
+// Circuitous route to get to the certificate chains.
+// See comments on why security.MarshalBlessings is discouraged.
+// Though, a better alternative is worth looking into.
+func blessings2wire(b security.Blessings) (security.WireBlessings, error) {
+	var wire security.WireBlessings
+	data, err := vom.Encode(b)
+	if err != nil {
+		return wire, err
+	}
+	err = vom.Decode(data, &wire)
+	return wire, err
+}
diff --git a/cmd/principal/main_darwin.go b/cmd/principal/main_darwin.go
new file mode 100644
index 0000000..6b72080
--- /dev/null
+++ b/cmd/principal/main_darwin.go
@@ -0,0 +1,9 @@
+// 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 darwin
+
+package main
+
+const openCommand = "open"
diff --git a/cmd/principal/main_linux.go b/cmd/principal/main_linux.go
new file mode 100644
index 0000000..431b01a
--- /dev/null
+++ b/cmd/principal/main_linux.go
@@ -0,0 +1,9 @@
+// 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 linux
+
+package main
+
+const openCommand = "xdg-open"
diff --git a/cmd/principal/main_nacl.go b/cmd/principal/main_nacl.go
new file mode 100644
index 0000000..e22d317
--- /dev/null
+++ b/cmd/principal/main_nacl.go
@@ -0,0 +1,7 @@
+// 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
+
+const openCommand = ""
diff --git a/cmd/principal/principal_v23_test.go b/cmd/principal/principal_v23_test.go
new file mode 100644
index 0000000..6186649
--- /dev/null
+++ b/cmd/principal/principal_v23_test.go
@@ -0,0 +1,727 @@
+// 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_test
+
+import (
+	"bytes"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"regexp"
+	"strings"
+
+	"v.io/x/ref"
+	"v.io/x/ref/test/v23tests"
+)
+
+//go:generate v23 test generate
+
+// redirect redirects the stdout of the given invocation to the file at the
+// given path.
+func redirect(t *v23tests.T, inv *v23tests.Invocation, path string) {
+	if err := ioutil.WriteFile(path, []byte(inv.Output()), 0600); err != nil {
+		t.Fatalf("WriteFile(%q) failed: %v\n", path, err)
+	}
+}
+
+// removePublicKeys replaces public keys (16 hex bytes, :-separated) with
+// XX:....  This substitution enables comparison with golden output even when
+// keys are freshly minted by the "principal create" command.
+func removePublicKeys(input string) string {
+	return regexp.MustCompile("([0-9a-f]{2}:){15}[0-9a-f]{2}").ReplaceAllString(input, "XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX")
+}
+
+func removeCaveats(input string) string {
+	input = regexp.MustCompile(`0xa64c2d0119fba3348071feeb2f308000\(time\.Time=.*\)`).ReplaceAllString(input, "ExpiryCaveat")
+	input = regexp.MustCompile(`0x54a676398137187ecdb26d2d69ba0003\(\[]string=.*\)`).ReplaceAllString(input, "MethodCaveat")
+	input = regexp.MustCompile(`0x00000000000000000000000000000000\(bool=true\)`).ReplaceAllString(input, "Unconstrained")
+	return input
+}
+
+func V23TestBlessSelf(t *v23tests.T) {
+	var (
+		outputDir         = t.NewTempDir("")
+		aliceDir          = filepath.Join(outputDir, "alice")
+		aliceBlessingFile = filepath.Join(outputDir, "aliceself")
+	)
+
+	bin := t.BuildGoPkg("v.io/x/ref/cmd/principal")
+	bin.Run("create", aliceDir, "alice")
+
+	bin = bin.WithEnv(credEnv(aliceDir))
+	redirect(t, bin.Start("blessself", "alicereborn"), aliceBlessingFile)
+	got := removePublicKeys(bin.Start("dumpblessings", aliceBlessingFile).Output())
+	want := `Blessings          : alicereborn
+PublicKey          : XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
+Certificate chains : 1
+Chain #0 (1 certificates). Root certificate public key: XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
+  Certificate #0: alicereborn with 0 caveats
+`
+	if want != got {
+		t.Fatalf("unexpected output, wanted \n%s, got\n%s", want, got)
+	}
+}
+
+func V23TestStore(t *v23tests.T) {
+	var (
+		outputDir   = t.NewTempDir("")
+		bin         = t.BuildGoPkg("v.io/x/ref/cmd/principal")
+		aliceDir    = filepath.Join(outputDir, "alice")
+		aliceFriend = filepath.Join(outputDir, "alice.bless")
+		bobDir      = filepath.Join(outputDir, "bob")
+		bobForPeer  = filepath.Join(outputDir, "bob.get.forpeer")
+	)
+
+	// Create two principals: alice and bob.
+	bin.Start("create", aliceDir, "alice").WaitOrDie(os.Stdout, os.Stderr)
+	bin.Start("create", bobDir, "bob").WaitOrDie(os.Stdout, os.Stderr)
+
+	// Bless Bob with Alice's principal.
+	blessEnv := credEnv(aliceDir)
+	redirect(t, bin.WithEnv(blessEnv).Start("bless", "--for=1m", bobDir, "friend"), aliceFriend)
+
+	// Run store forpeer on bob.
+	bin.Start("--v23.credentials="+bobDir, "set", "forpeer", aliceFriend, "alice").WaitOrDie(os.Stdout, os.Stderr)
+	redirect(t, bin.WithEnv(blessEnv).Start("--v23.credentials="+bobDir, "get", "forpeer", "alice/server"), bobForPeer)
+
+	got := removeCaveats(removePublicKeys(bin.Start("dumpblessings", bobForPeer).Output()))
+	want := `Blessings          : bob,alice/friend
+PublicKey          : XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
+Certificate chains : 2
+Chain #0 (1 certificates). Root certificate public key: XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
+  Certificate #0: bob with 0 caveats
+Chain #1 (2 certificates). Root certificate public key: XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
+  Certificate #0: alice with 0 caveats
+  Certificate #1: friend with 1 caveat
+    (0) ExpiryCaveat
+`
+	if want != got {
+		t.Errorf("unexpected output, got\n%s, wanted\n%s", got, want)
+	}
+
+	// Test the names flag.
+	got = bin.WithEnv(blessEnv).Start("--v23.credentials="+bobDir, "get", "forpeer", "--names", "alice/server").Output()
+	want = `bob
+alice/friend
+`
+	if got != want {
+		t.Errorf("unexpected output, got %s, want %s", got, want)
+	}
+
+	// Test the rootkey flag. In particular alice/friend's rootkey should be equal to alice's publickey.
+	got = bin.WithEnv(blessEnv).Start("--v23.credentials="+bobDir, "get", "forpeer", "--rootkey", "alice/friend", "alice/server").Output()
+	want = bin.WithEnv(blessEnv).Start("get", "publickey", "--pretty").Output()
+	if got != want {
+		t.Errorf("unexpected output, got %s, want %s", got, want)
+	}
+
+	// Test the caveats flag.
+	got = bin.WithEnv(blessEnv).Start("--v23.credentials="+bobDir, "get", "forpeer", "--caveats", "alice/friend", "alice/server").Output()
+	want = "Expires at"
+	if !strings.HasPrefix(got, want) {
+		t.Errorf("unexpected output, got %s, want %s", got, want)
+	}
+}
+
+func V23TestDump(t *v23tests.T) {
+	var (
+		outputDir       = t.NewTempDir("")
+		bin             = t.BuildGoPkg("v.io/x/ref/cmd/principal")
+		aliceDir        = filepath.Join(outputDir, "alice")
+		aliceExpiredDir = filepath.Join(outputDir, "alice-expired")
+	)
+
+	bin.Start("create", aliceDir, "alice").WaitOrDie(os.Stdout, os.Stderr)
+
+	blessEnv := credEnv(aliceDir)
+	got := removePublicKeys(bin.WithEnv(blessEnv).Start("dump").Output())
+	want := `Public key : XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
+Default Blessings : alice
+---------------- BlessingStore ----------------
+Default Blessings                alice
+Peer pattern                     Blessings
+...                              alice
+---------------- BlessingRoots ----------------
+Public key                                        Pattern
+XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX   [alice]
+`
+	if want != got {
+		t.Fatalf("unexpected output, got\n%s, wanted\n%s", got, want)
+	}
+
+	got = bin.WithEnv(blessEnv).Start("dump", "-s").Output()
+	want = "alice\n"
+	if want != got {
+		t.Fatalf("unexpected output, got\n%s, wanted\n%s", got, want)
+	}
+
+	bin.Start("--v23.credentials="+aliceDir, "fork", "--for", "-1h", aliceExpiredDir, "expired").WaitOrDie(os.Stdout, os.Stderr)
+	blessEnv = credEnv(aliceExpiredDir)
+	got = removePublicKeys(bin.WithEnv(blessEnv).Start("dump").Output())
+	want = `Public key : XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
+Default Blessings : alice/expired [EXPIRED]
+---------------- BlessingStore ----------------
+Default Blessings                alice/expired
+Peer pattern                     Blessings
+...                              alice/expired
+---------------- BlessingRoots ----------------
+Public key                                        Pattern
+XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX   [alice]
+`
+	if want != got {
+		t.Fatalf("unexpected output, got\n%s, wanted\n%s", got, want)
+	}
+
+	got = bin.WithEnv(blessEnv).Start("dump", "-s").Output()
+	want = "alice/expired [EXPIRED]\n"
+	if want != got {
+		t.Fatalf("unexpected output, got\n%s, wanted\n%s", got, want)
+	}
+}
+
+func V23TestGetRecognizedRoots(t *v23tests.T) {
+	var (
+		outputDir = t.NewTempDir("")
+		bin       = t.BuildGoPkg("v.io/x/ref/cmd/principal")
+		aliceDir  = filepath.Join(outputDir, "alice")
+	)
+
+	bin.Start("create", aliceDir, "alice").WaitOrDie(os.Stdout, os.Stderr)
+
+	blessEnv := credEnv(aliceDir)
+	got := removePublicKeys(bin.WithEnv(blessEnv).Start("get", "recognizedroots").Output())
+	want := `Public key                                        Pattern
+XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX   [alice]
+`
+	if want != got {
+		t.Fatalf("unexpected output, got\n%s, wanted\n%s", got, want)
+	}
+}
+
+func V23TestGetPeermap(t *v23tests.T) {
+	var (
+		outputDir = t.NewTempDir("")
+		bin       = t.BuildGoPkg("v.io/x/ref/cmd/principal")
+		aliceDir  = filepath.Join(outputDir, "alice")
+	)
+
+	bin.Start("create", aliceDir, "alice").WaitOrDie(os.Stdout, os.Stderr)
+
+	blessEnv := credEnv(aliceDir)
+	got := bin.WithEnv(blessEnv).Start("get", "peermap").Output()
+	want := `Default Blessings                alice
+Peer pattern                     Blessings
+...                              alice
+`
+	if want != got {
+		t.Fatalf("unexpected output, got\n%s, wanted\n%s", got, want)
+	}
+}
+
+// Given an invocation of "principal recvblessings", this function returns the
+// arguments to provide to "principal bless" provided by the "recvblessings"
+// invocation.
+//
+// For example,
+// principal recvblessings
+// would typically print something like:
+//    principal bless --remote-key=<some_public_key> --remote-token=<some_token> extensionfoo
+// as an example of command line to use to send the blessings over.
+//
+// In that case, this method would return:
+// { "--remote-key=<some_public_key>", "--remote-token=<some_token>", "extensionfoo"}
+func blessArgsFromRecvBlessings(inv *v23tests.Invocation) []string {
+	cmd := inv.ExpectSetEventuallyRE("(^principal bless .*$)")[0][0]
+	return strings.Split(cmd, " ")[2:]
+}
+
+func V23TestRecvBlessings(t *v23tests.T) {
+	var (
+		outputDir    = t.NewTempDir("")
+		bin          = t.BuildGoPkg("v.io/x/ref/cmd/principal")
+		aliceDir     = filepath.Join(outputDir, "alice")
+		bobDir       = filepath.Join(outputDir, "bob")
+		carolDir     = filepath.Join(outputDir, "carol")
+		bobBlessFile = filepath.Join(outputDir, "bobBlessInfo")
+	)
+
+	// Generate principals
+	bin.Start("create", aliceDir, "alice").WaitOrDie(os.Stdout, os.Stderr)
+	bin.Start("create", bobDir, "bob").WaitOrDie(os.Stdout, os.Stderr)
+	bin.Start("create", carolDir, "carol").WaitOrDie(os.Stdout, os.Stderr)
+
+	// Run recvblessings on carol, and have alice send blessings over
+	// (blessings received must be set as default and shareable with all peers).
+	var args []string
+	{
+		inv := bin.Start("--v23.credentials="+carolDir, "--v23.tcp.address=127.0.0.1:0", "recvblessings")
+		args = append([]string{"bless", "--require-caveats=false"}, blessArgsFromRecvBlessings(inv)...)
+		// Use the "friend/carol" extension
+		args = append(args, "friend/carol")
+	}
+	bin.WithEnv(credEnv(aliceDir)).Start(args...).WaitOrDie(os.Stdout, os.Stderr)
+
+	// Run recvblessings on carol, and have alice send blessings over
+	// (blessings received must be set as shareable with peers matching 'alice/...'.)
+	{
+		inv := bin.Start("--v23.credentials="+carolDir, "--v23.tcp.address=127.0.0.1:0", "recvblessings", "--for-peer=alice", "--set-default=false")
+		// recvblessings suggests a random extension, find the extension and replace it with friend/carol/foralice.
+		args = append([]string{"bless", "--require-caveats=false"}, blessArgsFromRecvBlessings(inv)...)
+		args = append(args, "friend/carol/foralice")
+	}
+	bin.WithEnv(credEnv(aliceDir)).Start(args...).WaitOrDie(os.Stdout, os.Stderr)
+
+	// Run recvblessings on carol with the --remote-arg-file flag, and have bob send blessings over with the --remote-arg-file flag.
+	{
+		inv := bin.Start("--v23.credentials="+carolDir, "--v23.tcp.address=127.0.0.1:0", "recvblessings", "--for-peer=bob", "--set-default=false", "--remote-arg-file="+bobBlessFile)
+		// recvblessings suggests a random extension, use friend/carol/forbob instead.
+		args = append([]string{"bless", "--require-caveats=false"}, blessArgsFromRecvBlessings(inv)...)
+		args = append(args, "friend/carol/forbob")
+	}
+	bin.WithEnv(credEnv(bobDir)).Start(args...).WaitOrDie(os.Stdout, os.Stderr)
+
+	listenerInv := bin.Start("--v23.credentials="+carolDir, "--v23.tcp.address=127.0.0.1:0", "recvblessings", "--for-peer=alice/...", "--set-default=false", "--vmodule=*=2", "--logtostderr")
+
+	args = append([]string{"bless", "--require-caveats=false"}, blessArgsFromRecvBlessings(listenerInv)...)
+	args = append(args, "willfail")
+
+	{
+		// Mucking around with remote-key should fail.
+		cpy := strings.Split(regexp.MustCompile("remote-key=").ReplaceAllString(strings.Join(args, " "), "remote-key=BAD"), " ")
+		var buf bytes.Buffer
+		if bin.WithEnv(credEnv(aliceDir)).Start(cpy...).Wait(os.Stdout, &buf) == nil {
+			t.Fatalf("%v should have failed, but did not", cpy)
+		}
+
+		if want, got := "key mismatch", buf.String(); !strings.Contains(got, want) {
+			t.Fatalf("expected %q to be contained within\n%s\n, but was not", want, got)
+		}
+	}
+
+	{
+		var buf bytes.Buffer
+		// Mucking around with the token should fail.
+		cpy := strings.Split(regexp.MustCompile("remote-token=").ReplaceAllString(strings.Join(args, " "), "remote-token=BAD"), " ")
+		if bin.WithEnv(credEnv(aliceDir)).Start(cpy...).Wait(os.Stdout, &buf) == nil {
+			t.Fatalf("%v should have failed, but did not", cpy)
+		}
+
+		if want, got := "blessings received from unexpected sender", buf.String(); !strings.Contains(got, want) {
+			t.Fatalf("expected %q to be contained within\n%s\n, but was not", want, got)
+		}
+	}
+
+	// Dump carol out, the only blessing that survives should be from the
+	// first "bless" command. (alice/friend/carol).
+	got := removePublicKeys(bin.Start("--v23.credentials="+carolDir, "dump").Output())
+	want := `Public key : XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
+Default Blessings : alice/friend/carol
+---------------- BlessingStore ----------------
+Default Blessings                alice/friend/carol
+Peer pattern                     Blessings
+...                              alice/friend/carol
+alice                            alice/friend/carol/foralice
+bob                              bob/friend/carol/forbob
+---------------- BlessingRoots ----------------
+Public key                                        Pattern
+XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX   [alice]
+XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX   [bob]
+XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX   [carol]
+`
+	if want != got {
+		t.Fatalf("unexpected output, got\n%s, wanted\n%s", got, want)
+	}
+}
+
+func V23TestRecvBlessingsInteractive(t *v23tests.T) {
+	var (
+		outputDir = t.NewTempDir("")
+		bin       = t.BuildGoPkg("v.io/x/ref/cmd/principal")
+		aliceDir  = filepath.Join(outputDir, "alice")
+		bobDir    = filepath.Join(outputDir, "bob")
+		aliceBin  = bin.WithEnv(credEnv(aliceDir))
+	)
+
+	// Generate principals
+	bin.Start("create", aliceDir, "alice").WaitOrDie(os.Stdout, os.Stderr)
+	bin.Start("create", bobDir, "bob").WaitOrDie(os.Stdout, os.Stderr)
+
+	// Run recvblessings on bob
+	recv := bin.Start("--v23.credentials="+bobDir, "--v23.tcp.address=127.0.0.1:0", "recvblessings")
+	args := blessArgsFromRecvBlessings(recv)
+
+	// When running the exact command, must be prompted about caveats.
+	{
+		inv := aliceBin.Start(append([]string{"bless"}, args...)...)
+		inv.Expect("WARNING: No caveats provided")
+		// Saying something other than "yes" or "YES"
+		// should fail.
+		fmt.Fprintln(inv.Stdin(), "yeah")
+		if err := inv.Wait(os.Stdout, os.Stderr); err == nil {
+			t.Fatalf("Expected principal bless to fail because the wrong input was provided")
+		}
+	}
+	// When agreeing to have no caveats, must specify an extension
+	{
+		inv := aliceBin.Start(append([]string{"bless"}, args...)...)
+		inv.Expect("WARNING: No caveats provided")
+		fmt.Fprintln(inv.Stdin(), "yes")
+		inv.CloseStdin()
+		if err := inv.Wait(os.Stdout, os.Stderr); err == nil {
+			t.Fatalf("Expected principal bless to fail because no extension was provided")
+		}
+	}
+	// When providing both, the bless command should succeed.
+	{
+		inv := aliceBin.Start(append([]string{"bless"}, args...)...)
+		fmt.Fprintln(inv.Stdin(), "YES")
+		fmt.Fprintln(inv.Stdin(), "friend/bobby")
+		if err := inv.Wait(os.Stdout, os.Stderr); err != nil {
+			t.Fatal(err)
+		}
+	}
+	got := removePublicKeys(bin.Start("--v23.credentials="+bobDir, "dump").Output())
+	want := `Public key : XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
+Default Blessings : alice/friend/bobby
+---------------- BlessingStore ----------------
+Default Blessings                alice/friend/bobby
+Peer pattern                     Blessings
+...                              alice/friend/bobby
+---------------- BlessingRoots ----------------
+Public key                                        Pattern
+XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX   [alice]
+XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX   [bob]
+`
+	if want != got {
+		t.Fatalf("unexpected output, got\n%s, wanted\n%s", got, want)
+	}
+}
+
+func V23TestFork(t *v23tests.T) {
+	var (
+		outputDir             = t.NewTempDir("")
+		bin                   = t.BuildGoPkg("v.io/x/ref/cmd/principal")
+		aliceDir              = filepath.Join(outputDir, "alice")
+		alicePhoneDir         = filepath.Join(outputDir, "alice-phone")
+		alicePhoneCalendarDir = filepath.Join(outputDir, "alice-phone-calendar")
+		tmpfile               = filepath.Join(outputDir, "tmpfile")
+	)
+
+	// Generate principals for alice.
+	bin.Start("create", aliceDir, "alice").WaitOrDie(os.Stdout, os.Stderr)
+
+	// Run fork to setup up credentials for alice/phone that are
+	// blessed by alice under the extension "phone".
+	bin.Start("--v23.credentials="+aliceDir, "fork", "--for", "1h", alicePhoneDir, "phone").WaitOrDie(os.Stdout, os.Stderr)
+
+	// Dump alice-phone out, the only blessings it has must be from alice (alice/phone).
+	{
+		got := removePublicKeys(bin.Start("--v23.credentials="+alicePhoneDir, "dump").Output())
+		want := `Public key : XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
+Default Blessings : alice/phone
+---------------- BlessingStore ----------------
+Default Blessings                alice/phone
+Peer pattern                     Blessings
+...                              alice/phone
+---------------- BlessingRoots ----------------
+Public key                                        Pattern
+XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX   [alice]
+`
+		if want != got {
+			t.Fatalf("unexpected output, got\n%s, wanted\n%s", got, want)
+		}
+	}
+	// And it should have an expiry caveat
+	{
+		redirect(t, bin.Start("--v23.credentials", alicePhoneDir, "get", "default"), tmpfile)
+		got := removeCaveats(removePublicKeys(bin.Start("dumpblessings", tmpfile).Output()))
+		want := `Blessings          : alice/phone
+PublicKey          : XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
+Certificate chains : 1
+Chain #0 (2 certificates). Root certificate public key: XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
+  Certificate #0: alice with 0 caveats
+  Certificate #1: phone with 1 caveat
+    (0) ExpiryCaveat
+`
+		if want != got {
+			t.Fatalf("unexpected output, got\n%s, wanted\n%s", got, want)
+		}
+	}
+
+	// Run fork to setup up credentials for alice/phone/calendar that are
+	// blessed by alice/phone under the extension "calendar".
+	bin.Start("--v23.credentials="+alicePhoneDir, "fork", "--for", "1h", alicePhoneCalendarDir, "calendar").WaitOrDie(os.Stdout, os.Stderr)
+	{
+		got := removePublicKeys(bin.Start("--v23.credentials="+alicePhoneCalendarDir, "dump").Output())
+		want := `Public key : XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
+Default Blessings : alice/phone/calendar
+---------------- BlessingStore ----------------
+Default Blessings                alice/phone/calendar
+Peer pattern                     Blessings
+...                              alice/phone/calendar
+---------------- BlessingRoots ----------------
+Public key                                        Pattern
+XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX   [alice]
+`
+		if want != got {
+			t.Fatalf("unexpected output, got\n%s, wanted\n%s", got, want)
+		}
+	}
+	{
+		redirect(t, bin.Start("--v23.credentials", alicePhoneCalendarDir, "get", "default"), tmpfile)
+		got := removeCaveats(removePublicKeys(bin.Start("dumpblessings", tmpfile).Output()))
+		want := `Blessings          : alice/phone/calendar
+PublicKey          : XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
+Certificate chains : 1
+Chain #0 (3 certificates). Root certificate public key: XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
+  Certificate #0: alice with 0 caveats
+  Certificate #1: phone with 1 caveat
+    (0) ExpiryCaveat
+  Certificate #2: calendar with 1 caveat
+    (0) ExpiryCaveat
+`
+		if want != got {
+			t.Fatalf("unexpected output, got\n%s, wanted\n%s", got, want)
+		}
+	}
+}
+
+func V23TestCreate(t *v23tests.T) {
+	var (
+		outputDir = t.NewTempDir("")
+		bin       = t.BuildGoPkg("v.io/x/ref/cmd/principal")
+		aliceDir  = filepath.Join(outputDir, "alice")
+	)
+
+	// Creating a principal should succeed the first time.
+	bin.Start("create", aliceDir, "alice").WaitOrDie(os.Stdout, os.Stderr)
+
+	// The second time should fail (the create command won't override an existing principal).
+	if bin.Start("create", aliceDir, "alice").Wait(os.Stdout, os.Stderr) == nil {
+		t.Fatalf("principal creation should have failed, but did not")
+	}
+
+	// If we specify -overwrite, it will.
+	bin.Start("create", "--overwrite", aliceDir, "alice").WaitOrDie(os.Stdout, os.Stderr)
+}
+
+func V23TestCaveats(t *v23tests.T) {
+	var (
+		outputDir         = t.NewTempDir("")
+		aliceDir          = filepath.Join(outputDir, "alice")
+		aliceBlessingFile = filepath.Join(outputDir, "aliceself")
+	)
+
+	bin := t.BuildGoPkg("v.io/x/ref/cmd/principal")
+	bin.Start("create", aliceDir, "alice").WaitOrDie(os.Stdout, os.Stderr)
+
+	bin = bin.WithEnv(credEnv(aliceDir))
+	args := []string{
+		"blessself",
+		"--caveat=\"v.io/v23/security\".MethodCaveat={\"method\"}",
+		"--caveat={{0x54,0xa6,0x76,0x39,0x81,0x37,0x18,0x7e,0xcd,0xb2,0x6d,0x2d,0x69,0xba,0x0,0x3},typeobject([]string)}={\"method\"}",
+		"alicereborn",
+	}
+	redirect(t, bin.Start(args...), aliceBlessingFile)
+	got := removeCaveats(removePublicKeys(bin.Start("dumpblessings", aliceBlessingFile).Output()))
+	want := `Blessings          : alicereborn
+PublicKey          : XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
+Certificate chains : 1
+Chain #0 (1 certificates). Root certificate public key: XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
+  Certificate #0: alicereborn with 2 caveats
+    (0) MethodCaveat
+    (1) MethodCaveat
+`
+	if want != got {
+		t.Fatalf("unexpected output, wanted \n%s, got\n%s", want, got)
+	}
+}
+
+func V23TestForkWithoutVDLPATH(t *v23tests.T) {
+	var (
+		parent = t.NewTempDir("")
+		bin    = t.BuildGoPkg("v.io/x/ref/cmd/principal").WithEnv("V23_ROOT=''", "VDLPATH=''")
+	)
+	if err := bin.Start("create", parent, "parent").Wait(os.Stdout, os.Stderr); err != nil {
+		t.Fatalf("create %q failed: %v", parent, err)
+	}
+	if err := bin.Start("--v23.credentials="+parent, "fork", "--for=1s", t.NewTempDir(""), "child").Wait(os.Stdout, os.Stderr); err != nil {
+		t.Errorf("fork failed: %v", err)
+	}
+}
+
+func V23TestForkWithoutCaveats(t *v23tests.T) {
+	var (
+		parent = t.NewTempDir("")
+		child  = t.NewTempDir("")
+		bin    = t.BuildGoPkg("v.io/x/ref/cmd/principal")
+		buf    bytes.Buffer
+	)
+	if err := bin.Start("create", parent, "parent").Wait(os.Stdout, os.Stderr); err != nil {
+		t.Fatalf("create %q failed: %v", parent, err)
+	}
+	if err := bin.Start("--v23.credentials", parent, "fork", child, "child").Wait(os.Stdout, &buf); err == nil {
+		t.Errorf("fork should have failed without any caveats, but did not")
+	} else if got, want := buf.String(), "ERROR: no caveats provided"; !strings.Contains(got, want) {
+		t.Errorf("fork returned error: %q, expected error to contain %q", got, want)
+	}
+	if err := bin.Start("--v23.credentials", parent, "fork", "--for=0", child, "child").Wait(os.Stdout, &buf); err == nil {
+		t.Errorf("fork should have failed without any caveats, but did not")
+	} else if got, want := buf.String(), "ERROR: no caveats provided"; !strings.Contains(got, want) {
+		t.Errorf("fork returned error: %q, expected error to contain %q", got, want)
+	}
+	if err := bin.Start("--v23.credentials", parent, "fork", "--require-caveats=false", child, "child").Wait(os.Stdout, os.Stderr); err != nil {
+		t.Errorf("fork --require-caveats=false failed with: %v", err)
+	}
+}
+
+func V23TestBless(t *v23tests.T) {
+	var (
+		bin      = t.BuildGoPkg("v.io/x/ref/cmd/principal")
+		dir      = t.NewTempDir("")
+		aliceDir = filepath.Join(dir, "alice")
+		bobDir   = filepath.Join(dir, "bob")
+		tmpfile  = filepath.Join(dir, "tmpfile")
+	)
+	// Create two principals: alice and bob
+	bin.Start("create", aliceDir, "alice").WaitOrDie(os.Stdout, os.Stderr)
+	bin.Start("create", bobDir, "bob").WaitOrDie(os.Stdout, os.Stderr)
+
+	// All blessings will be done by "alice"
+	bin = bin.WithEnv(credEnv(aliceDir))
+
+	{
+		// "alice" should fail to bless "bob" without any caveats
+		var buf bytes.Buffer
+		if err := bin.Start("bless", bobDir, "friend").Wait(os.Stdout, &buf); err == nil {
+			t.Errorf("bless should have failed when no caveats are specified")
+		} else if got, want := buf.String(), "ERROR: no caveats provided"; !strings.Contains(got, want) {
+			t.Errorf("got error %q, expected to match %q", got, want)
+		}
+	}
+	{
+		// But succeed if --require-caveats=false is specified
+		redirect(t, bin.Start("bless", "--require-caveats=false", bobDir, "friend"), tmpfile)
+		got := removeCaveats(removePublicKeys(bin.Start("dumpblessings", tmpfile).Output()))
+		want := `Blessings          : alice/friend
+PublicKey          : XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
+Certificate chains : 1
+Chain #0 (2 certificates). Root certificate public key: XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
+  Certificate #0: alice with 0 caveats
+  Certificate #1: friend with 1 caveat
+    (0) Unconstrained
+`
+		if got != want {
+			t.Errorf("Got\n%vWant\n%v", got, want)
+		}
+	}
+	{
+		// And succeed if --for is specified
+		redirect(t, bin.Start("bless", "--for=1m", bobDir, "friend"), tmpfile)
+		got := removeCaveats(removePublicKeys(bin.Start("dumpblessings", tmpfile).Output()))
+		want := `Blessings          : alice/friend
+PublicKey          : XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
+Certificate chains : 1
+Chain #0 (2 certificates). Root certificate public key: XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
+  Certificate #0: alice with 0 caveats
+  Certificate #1: friend with 1 caveat
+    (0) ExpiryCaveat
+`
+		if got != want {
+			t.Errorf("Got\n%vWant\n%v", got, want)
+		}
+	}
+	{
+		// If the Blessings are expired, dumpBlessings should print so.
+		redirect(t, bin.Start("bless", "--for=-1s", bobDir, "friend"), tmpfile)
+		got := removeCaveats(removePublicKeys(bin.Start("dumpblessings", tmpfile).Output()))
+		want := `Blessings          : alice/friend [EXPIRED]
+PublicKey          : XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
+Certificate chains : 1
+Chain #0 (2 certificates). Root certificate public key: XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
+  Certificate #0: alice with 0 caveats
+  Certificate #1: friend with 1 caveat
+    (0) ExpiryCaveat
+`
+		if got != want {
+			t.Errorf("Got\n%vWant\n%v", got, want)
+		}
+	}
+	{
+		// But not if --for=0
+		var buf bytes.Buffer
+		if err := bin.Start("bless", "--for=0", bobDir, "friend").Wait(os.Stdout, &buf); err == nil {
+			t.Errorf("bless should have failed when no caveats are specified")
+		} else if got, want := buf.String(), "ERROR: no caveats provided"; !strings.Contains(got, want) {
+			t.Errorf("got error %q, expected to match %q", got, want)
+		}
+	}
+}
+
+func V23TestAddBlessingsToRoots(t *v23tests.T) {
+	var (
+		bin          = t.BuildGoPkg("v.io/x/ref/cmd/principal")
+		aliceDir     = t.NewTempDir("")
+		bobDir       = t.NewTempDir("")
+		blessingFile = filepath.Join(t.NewTempDir(""), "bobfile")
+
+		// Extract the public key from the first line of output from
+		// "principal dump", which is formatted as:
+		// Public key : <the public key>
+		publicKey = func(dir string) string {
+			output := bin.Start("--v23.credentials="+dir, "dump").Output()
+			line := strings.SplitN(output, "\n", 2)[0]
+			fields := strings.Split(line, " ")
+			return fields[len(fields)-1]
+		}
+	)
+	// Create two principals, "alice" and "bob"
+	bin.Start("create", aliceDir, "alice").WaitOrDie(os.Stdout, os.Stderr)
+	bin.Start("create", bobDir, "bob").WaitOrDie(os.Stdout, os.Stderr)
+	// Have bob create a "bob/friend" blessing and have alice recognize that.
+	redirect(t, bin.Start("--v23.credentials="+bobDir, "bless", "--require-caveats=false", aliceDir, "friend"), blessingFile)
+	bin.Start("--v23.credentials="+aliceDir, "recognize", blessingFile).WaitOrDie(os.Stdout, os.Stderr)
+
+	want := fmt.Sprintf(`Public key                                        Pattern
+%v   [alice]
+%v   [bob]
+`, publicKey(aliceDir), publicKey(bobDir))
+
+	// Finally view alice's recognized roots, it should have lines corresponding to aliceLine and bobLine.
+	got := bin.Start("--v23.credentials="+aliceDir, "get", "recognizedroots").Output()
+	if got != want {
+		t.Fatalf("Got:\n%v\n\nWant:\n%v", got, want)
+	}
+}
+
+func V23TestAddKeyToRoots(t *v23tests.T) {
+	var (
+		bin       = t.BuildGoPkg("v.io/x/ref/cmd/principal")
+		outputDir = t.NewTempDir("")
+		aliceDir  = filepath.Join(outputDir, "alice")
+		bobDir    = filepath.Join(outputDir, "bob")
+	)
+	bin.Start("create", aliceDir, "alice").WaitOrDie(os.Stdout, os.Stderr)
+	bin.Start("create", bobDir, "bob").WaitOrDie(os.Stdout, os.Stderr)
+	// Get bob's public key and add it to roots for alice
+	bobKey := strings.TrimSpace(bin.Start("--v23.credentials="+bobDir, "get", "publickey").Output())
+	bobPrettyKey := strings.TrimSpace(bin.Start("--v23.credentials="+bobDir, "get", "publickey", "--pretty").Output())
+	bin.Start("--v23.credentials="+aliceDir, "recognize", "bob", bobKey).WaitOrDie(os.Stdout, os.Stderr)
+
+	// Verify that it has been added
+	output := bin.Start("--v23.credentials="+aliceDir, "dump").Output()
+	want := fmt.Sprintf("%v   [bob]", bobPrettyKey)
+	for _, line := range strings.Split(output, "\n") {
+		if line == want {
+			return
+		}
+	}
+	t.Errorf("Could not find line:\n%v\nin output:\n%v\n", want, output)
+}
+
+func credEnv(dir string) string {
+	return fmt.Sprintf("%s=%s", ref.EnvCredentials, dir)
+}
diff --git a/cmd/principal/v23_test.go b/cmd/principal/v23_test.go
new file mode 100644
index 0000000..3aa5c29
--- /dev/null
+++ b/cmd/principal/v23_test.go
@@ -0,0 +1,86 @@
+// 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 main_test
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
+	"v.io/x/ref/test/v23tests"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	modules.DispatchAndExitIfChild()
+	cleanup := v23tests.UseSharedBinDir()
+	r := m.Run()
+	cleanup()
+	os.Exit(r)
+}
+
+func TestV23BlessSelf(t *testing.T) {
+	v23tests.RunTest(t, V23TestBlessSelf)
+}
+
+func TestV23Store(t *testing.T) {
+	v23tests.RunTest(t, V23TestStore)
+}
+
+func TestV23Dump(t *testing.T) {
+	v23tests.RunTest(t, V23TestDump)
+}
+
+func TestV23GetRecognizedRoots(t *testing.T) {
+	v23tests.RunTest(t, V23TestGetRecognizedRoots)
+}
+
+func TestV23GetPeermap(t *testing.T) {
+	v23tests.RunTest(t, V23TestGetPeermap)
+}
+
+func TestV23RecvBlessings(t *testing.T) {
+	v23tests.RunTest(t, V23TestRecvBlessings)
+}
+
+func TestV23RecvBlessingsInteractive(t *testing.T) {
+	v23tests.RunTest(t, V23TestRecvBlessingsInteractive)
+}
+
+func TestV23Fork(t *testing.T) {
+	v23tests.RunTest(t, V23TestFork)
+}
+
+func TestV23Create(t *testing.T) {
+	v23tests.RunTest(t, V23TestCreate)
+}
+
+func TestV23Caveats(t *testing.T) {
+	v23tests.RunTest(t, V23TestCaveats)
+}
+
+func TestV23ForkWithoutVDLPATH(t *testing.T) {
+	v23tests.RunTest(t, V23TestForkWithoutVDLPATH)
+}
+
+func TestV23ForkWithoutCaveats(t *testing.T) {
+	v23tests.RunTest(t, V23TestForkWithoutCaveats)
+}
+
+func TestV23Bless(t *testing.T) {
+	v23tests.RunTest(t, V23TestBless)
+}
+
+func TestV23AddBlessingsToRoots(t *testing.T) {
+	v23tests.RunTest(t, V23TestAddBlessingsToRoots)
+}
+
+func TestV23AddKeyToRoots(t *testing.T) {
+	v23tests.RunTest(t, V23TestAddKeyToRoots)
+}
diff --git a/cmd/servicerunner/doc.go b/cmd/servicerunner/doc.go
new file mode 100644
index 0000000..939f99d
--- /dev/null
+++ b/cmd/servicerunner/doc.go
@@ -0,0 +1,74 @@
+// 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
+
+// +build wspr
+
+/*
+Command servicerunner runs several Vanadium services, including the mounttable,
+proxy and wspr.  It prints a JSON map with their vars to stdout (as a single
+line), then waits forever.
+
+Usage:
+   servicerunner [flags]
+
+The servicerunner flags are:
+ -identd=
+   Name of wspr identd server.
+ -port=8124
+   Port for wspr to listen on.
+
+The global flags are:
+ -alsologtostderr=true
+   log to standard error as well as files
+ -external-http-addr=
+   External address on which the HTTP server listens on. If none is provided the
+   server will only listen on -http-addr.
+ -http-addr=localhost:0
+   Address on which the HTTP server listens on.
+ -log_backtrace_at=:0
+   when logging hits line file:N, emit a stack trace
+ -log_dir=
+   if non-empty, write log files to this directory
+ -logtostderr=false
+   log to standard error instead of files
+ -max_stack_buf_size=4292608
+   max size in bytes of the buffer to use for logging stack traces
+ -stderrthreshold=2
+   logs at or above this threshold go to stderr
+ -tls-config=
+   Comma-separated list of TLS certificate and private key files. This must be
+   provided.
+ -v=0
+   log level for V logs
+ -v23.credentials=
+   directory to use for storing security credentials
+ -v23.i18n-catalogue=
+   18n catalogue files to load, comma separated
+ -v23.metadata=<just specify -v23.metadata to activate>
+   Displays metadata for the program and exits.
+ -v23.namespace.root=[/(dev.v.io/role/vprod/service/mounttabled)@ns.dev.v.io:8101]
+   local namespace root; can be repeated to provided multiple roots
+ -v23.proxy=
+   object name of proxy service to use to export services across network
+   boundaries
+ -v23.tcp.address=
+   address to listen on
+ -v23.tcp.protocol=wsh
+   protocol to listen with
+ -v23.vtrace.cache-size=1024
+   The number of vtrace traces to store in memory.
+ -v23.vtrace.collect-regexp=
+   Spans and annotations that match this regular expression will trigger trace
+   collection.
+ -v23.vtrace.dump-on-shutdown=true
+   If true, dump all stored traces on runtime shutdown.
+ -v23.vtrace.sample-rate=0
+   Rate (from 0.0 to 1.0) to sample vtrace traces.
+ -vmodule=
+   comma-separated list of pattern=N settings for file-filtered logging
+*/
+package main
diff --git a/cmd/servicerunner/main.go b/cmd/servicerunner/main.go
new file mode 100644
index 0000000..76e12e8
--- /dev/null
+++ b/cmd/servicerunner/main.go
@@ -0,0 +1,195 @@
+// 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 wspr
+//
+// We restrict to a special build-tag since it's required by wsprlib.
+//
+// Manually run the following to generate the doc.go file.  This isn't a
+// go:generate comment, since generate also needs to be run with -tags=wspr,
+// which is troublesome for presubmit tests.
+//
+// cd $V23_ROOT/release/go/src && go run v.io/x/lib/cmdline/testdata/gendoc.go -tags=wspr v.io/x/ref/cmd/servicerunner -help
+
+package main
+
+import (
+	"encoding/json"
+	"fmt"
+	"os"
+	"strings"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/options"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/x/lib/cmdline"
+	"v.io/x/lib/set"
+	"v.io/x/ref"
+	"v.io/x/ref/lib/signals"
+	"v.io/x/ref/lib/xrpc"
+	"v.io/x/ref/runtime/factories/generic"
+	"v.io/x/ref/services/identity/identitylib"
+	"v.io/x/ref/services/mounttable/mounttablelib"
+	"v.io/x/ref/services/wspr/wsprlib"
+	"v.io/x/ref/test/expect"
+	"v.io/x/ref/test/modules"
+)
+
+var (
+	port   int
+	identd string
+)
+
+func init() {
+	wsprlib.OverrideCaveatValidation()
+	cmdServiceRunner.Flags.IntVar(&port, "port", 8124, "Port for wspr to listen on.")
+	cmdServiceRunner.Flags.StringVar(&identd, "identd", "", "Name of wspr identd server.")
+}
+
+func main() {
+	cmdline.HideGlobalFlagsExcept()
+	cmdline.Main(cmdServiceRunner)
+}
+
+var cmdServiceRunner = &cmdline.Command{
+	Runner: cmdline.RunnerFunc(run),
+	Name:   "servicerunner",
+	Short:  "Runs several services, including the mounttable, proxy and wspr.",
+	Long: `
+Command servicerunner runs several Vanadium services, including the mounttable,
+proxy and wspr.  It prints a JSON map with their vars to stdout (as a single
+line), then waits forever.
+`,
+}
+
+var rootMT = modules.Register(func(env *modules.Env, args ...string) error {
+	ctx, shutdown := v23.Init()
+	defer shutdown()
+
+	mt, err := mounttablelib.NewMountTableDispatcher(ctx, "", "", "mounttable")
+	if err != nil {
+		return fmt.Errorf("mounttablelib.NewMountTableDispatcher failed: %s", err)
+	}
+	server, err := xrpc.NewDispatchingServer(ctx, "", mt, options.ServesMountTable(true))
+	if err != nil {
+		return fmt.Errorf("root failed: %v", err)
+	}
+	fmt.Fprintf(env.Stdout, "PID=%d\n", os.Getpid())
+	for _, ep := range server.Status().Endpoints {
+		fmt.Fprintf(env.Stdout, "MT_NAME=%s\n", ep.Name())
+	}
+	modules.WaitForEOF(env.Stdin)
+	return nil
+}, "rootMT")
+
+// updateVars captures the vars from the given Handle's stdout and adds them to
+// the given vars map, overwriting existing entries.
+func updateVars(h modules.Handle, vars map[string]string, varNames ...string) error {
+	varsToAdd := set.StringBool.FromSlice(varNames)
+	numLeft := len(varsToAdd)
+
+	s := expect.NewSession(nil, h.Stdout(), 30*time.Second)
+	for {
+		l := s.ReadLine()
+		if err := s.OriginalError(); err != nil {
+			return err // EOF or otherwise
+		}
+		parts := strings.Split(l, "=")
+		if len(parts) != 2 {
+			return fmt.Errorf("Unexpected line: %s", l)
+		}
+		if varsToAdd[parts[0]] {
+			numLeft--
+			vars[parts[0]] = parts[1]
+			if numLeft == 0 {
+				break
+			}
+		}
+	}
+	return nil
+}
+
+func run(env *cmdline.Env, args []string) error {
+	// The dispatch to modules children must occur after the call to cmdline.Main
+	// (which calls cmdline.Parse), so that servicerunner flags are registered on
+	// the global flag.CommandLine.
+	if modules.IsChildProcess() {
+		return modules.Dispatch()
+	}
+
+	// We must wait until after we've dispatched to children before calling
+	// v23.Init, otherwise we'll end up initializing twice.
+	ctx, shutdown := v23.Init()
+	defer shutdown()
+
+	vars := map[string]string{}
+	sh, err := modules.NewShell(ctx, nil, false, nil)
+	if err != nil {
+		panic(fmt.Sprintf("modules.NewShell: %s", err))
+	}
+	defer sh.Cleanup(os.Stderr, os.Stderr)
+
+	h, err := sh.Start(nil, rootMT, "--v23.tcp.protocol=ws", "--v23.tcp.address=127.0.0.1:0")
+	if err != nil {
+		return err
+	}
+	if err := updateVars(h, vars, "MT_NAME"); err != nil {
+		return err
+	}
+
+	// Set ref.EnvNamespacePrefix env var, consumed downstream.
+	sh.SetVar(ref.EnvNamespacePrefix, vars["MT_NAME"])
+	v23.GetNamespace(ctx).SetRoots(vars["MT_NAME"])
+
+	lspec := v23.GetListenSpec(ctx)
+	lspec.Addrs = rpc.ListenAddrs{{"ws", "127.0.0.1:0"}}
+	proxyShutdown, proxyEndpoint, err := generic.NewProxy(ctx, lspec, security.AllowEveryone(), "test/proxy")
+	defer proxyShutdown()
+	vars["PROXY_NAME"] = proxyEndpoint.Name()
+
+	h, err = sh.Start(nil, wsprd, "--v23.tcp.protocol=ws", "--v23.tcp.address=127.0.0.1:0", "--v23.proxy=test/proxy", "--identd=test/identd")
+	if err != nil {
+		return err
+	}
+	if err := updateVars(h, vars, "WSPR_ADDR"); err != nil {
+		return err
+	}
+
+	h, err = sh.Start(nil, identitylib.TestIdentityd, "--v23.tcp.protocol=ws", "--v23.tcp.address=127.0.0.1:0", "--v23.proxy=test/proxy", "--http-addr=localhost:0")
+	if err != nil {
+		return err
+	}
+	if err := updateVars(h, vars, "TEST_IDENTITYD_NAME", "TEST_IDENTITYD_HTTP_ADDR"); err != nil {
+		return err
+	}
+
+	bytes, err := json.Marshal(vars)
+	if err != nil {
+		return err
+	}
+	fmt.Println(string(bytes))
+
+	<-signals.ShutdownOnSignals(ctx)
+	return nil
+}
+
+var wsprd = modules.Register(func(env *modules.Env, args ...string) error {
+	ctx, shutdown := v23.Init()
+	defer shutdown()
+
+	l := v23.GetListenSpec(ctx)
+	proxy := wsprlib.NewWSPR(ctx, port, &l, identd, nil)
+	defer proxy.Shutdown()
+
+	addr := proxy.Listen()
+	go func() {
+		proxy.Serve()
+	}()
+
+	fmt.Fprintf(env.Stdout, "WSPR_ADDR=%s\n", addr)
+	modules.WaitForEOF(env.Stdin)
+	return nil
+}, "wsprd")
diff --git a/cmd/servicerunner/servicerunner_test.go b/cmd/servicerunner/servicerunner_test.go
new file mode 100644
index 0000000..e99de0e
--- /dev/null
+++ b/cmd/servicerunner/servicerunner_test.go
@@ -0,0 +1,84 @@
+// 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 wspr
+//
+// We restrict to a special build-tag since it's required by wsprlib.
+
+// Runs the servicerunner binary and checks that it outputs a JSON line to
+// stdout with the expected variables.
+package main
+
+import (
+	"bufio"
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"path"
+	"testing"
+
+	"v.io/x/ref"
+	"v.io/x/ref/test"
+)
+
+// We provide our own TestMain, rather than allowing v23 test generate to create
+// one for us, to ensure all files require the "wspr" build tag.
+func TestMain(m *testing.M) {
+	test.Init()
+	os.Exit(m.Run())
+}
+
+func TestServiceRunner(t *testing.T) {
+	ref.EnvClearCredentials()
+	tmpdir, err := ioutil.TempDir("", "servicerunner_test")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(tmpdir)
+	os.Setenv("TMPDIR", tmpdir)
+
+	bin := path.Join(tmpdir, "servicerunner")
+	fmt.Println("Building", bin)
+	err = exec.Command("v23", "go", "build", "-o", bin, "-a", "-tags", "wspr", "v.io/x/ref/cmd/servicerunner").Run()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	cmd := exec.Command(bin)
+	stdout, err := cmd.StdoutPipe()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if err = cmd.Start(); err != nil {
+		t.Fatal(err)
+	}
+
+	line, err := bufio.NewReader(stdout).ReadBytes('\n')
+	if err != nil {
+		t.Fatal(err)
+	}
+	vars := map[string]string{}
+	if err = json.Unmarshal(line, &vars); err != nil {
+		t.Fatal(err)
+	}
+	fmt.Println(vars)
+	expectedVars := []string{
+		"MT_NAME",
+		"PROXY_NAME",
+		"WSPR_ADDR",
+		"TEST_IDENTITYD_NAME",
+		"TEST_IDENTITYD_HTTP_ADDR",
+	}
+	for _, name := range expectedVars {
+		if _, ok := vars[name]; !ok {
+			t.Error("Missing", name)
+		}
+	}
+
+	if err != cmd.Process.Kill() {
+		t.Fatal(err)
+	}
+}
diff --git a/cmd/uniqueid/doc.go b/cmd/uniqueid/doc.go
new file mode 100644
index 0000000..03b5c4e
--- /dev/null
+++ b/cmd/uniqueid/doc.go
@@ -0,0 +1,66 @@
+// 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
+
+/*
+Command uniqueid generates unique identifiers. It also has an option of
+automatically substituting unique ids with placeholders in files.
+
+Usage:
+   uniqueid <command>
+
+The uniqueid commands are:
+   generate    Generates UniqueIds
+   inject      Injects UniqueIds into existing files
+   help        Display help for commands or topics
+
+The global flags are:
+ -metadata=<just specify -metadata to activate>
+   Displays metadata for the program and exits.
+
+Uniqueid generate - Generates UniqueIds
+
+Generates unique ids and outputs them to standard out.
+
+Usage:
+   uniqueid generate
+
+Uniqueid inject - Injects UniqueIds into existing files
+
+Injects UniqueIds into existing files. Strings of the form "$UNIQUEID$" will be
+replaced with generated ids.
+
+Usage:
+   uniqueid inject <filenames>
+
+<filenames> List of files to inject unique ids into
+
+Uniqueid help - Display help for commands or topics
+
+Help with no args displays the usage of the parent command.
+
+Help with args displays the usage of the specified sub-command or help topic.
+
+"help ..." recursively displays help for all commands and topics.
+
+Usage:
+   uniqueid help [flags] [command/topic ...]
+
+[command/topic ...] optionally identifies a specific sub-command or help topic.
+
+The uniqueid help flags are:
+ -style=compact
+   The formatting style for help output:
+      compact - Good for compact cmdline output.
+      full    - Good for cmdline output, shows all global flags.
+      godoc   - Good for godoc processing.
+   Override the default by setting the CMDLINE_STYLE environment variable.
+ -width=<terminal width>
+   Format output to this target width in runes, or unlimited if width < 0.
+   Defaults to the terminal width if available.  Override the default by setting
+   the CMDLINE_WIDTH environment variable.
+*/
+package main
diff --git a/cmd/uniqueid/main.go b/cmd/uniqueid/main.go
new file mode 100644
index 0000000..bd98c32
--- /dev/null
+++ b/cmd/uniqueid/main.go
@@ -0,0 +1,113 @@
+// 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.
+
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go .
+
+package main
+
+import (
+	"bytes"
+	"fmt"
+	"io/ioutil"
+	"regexp"
+
+	"v.io/v23/uniqueid"
+	"v.io/x/lib/cmdline"
+)
+
+func main() {
+	cmdline.Main(cmdUniqueId)
+}
+
+var cmdUniqueId = &cmdline.Command{
+	Name:  "uniqueid",
+	Short: "generates unique identifiers",
+	Long: `
+Command uniqueid generates unique identifiers.
+It also has an option of automatically substituting unique ids with placeholders in files.
+`,
+	Children: []*cmdline.Command{cmdGenerate, cmdInject},
+	Topics:   []cmdline.Topic{},
+}
+
+var cmdGenerate = &cmdline.Command{
+	Runner: cmdline.RunnerFunc(runGenerate),
+	Name:   "generate",
+	Short:  "Generates UniqueIds",
+	Long: `
+Generates unique ids and outputs them to standard out.
+`,
+	ArgsName: "",
+	ArgsLong: "",
+}
+
+var cmdInject = &cmdline.Command{
+	Runner: cmdline.RunnerFunc(runInject),
+	Name:   "inject",
+	Short:  "Injects UniqueIds into existing files",
+	Long: `
+Injects UniqueIds into existing files.
+Strings of the form "$UNIQUEID$" will be replaced with generated ids.
+`,
+	ArgsName: "<filenames>",
+	ArgsLong: "<filenames> List of files to inject unique ids into",
+}
+
+// runGenerate implements the generate command which outputs generated ids to stdout.
+func runGenerate(env *cmdline.Env, args []string) error {
+	if len(args) > 0 {
+		return env.UsageErrorf("expected 0 args, got %d", len(args))
+	}
+	id, err := uniqueid.Random()
+	if err != nil {
+		return err
+	}
+	fmt.Printf("%#v\n", id)
+	return nil
+}
+
+// runInject implements the inject command which replaces $UNIQUEID$ strings with generated ids.
+func runInject(env *cmdline.Env, args []string) error {
+	if len(args) == 0 {
+		return env.UsageErrorf("expected at least one file arg, got 0")
+	}
+	for _, arg := range args {
+		if err := injectIntoFile(arg); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// injectIntoFile replaces $UNIQUEID$ strings when they exist in the specified file.
+func injectIntoFile(filename string) error {
+	inbytes, err := ioutil.ReadFile(filename)
+	if err != nil {
+		return err
+	}
+
+	// Replace $UNIQUEID$ with generated ids.
+	re, err := regexp.Compile("[$]UNIQUEID")
+	if err != nil {
+		return err
+	}
+	replaced := re.ReplaceAllFunc(inbytes, func(match []byte) []byte {
+		id, randErr := uniqueid.Random()
+		if randErr != nil {
+			err = randErr
+		}
+		return []byte(fmt.Sprintf("%#v", id))
+	})
+	if err != nil {
+		return err
+	}
+
+	// If the file with injections is different, write it to disk.
+	if !bytes.Equal(inbytes, replaced) {
+		fmt.Printf("Updated: %s\n", filename)
+		return ioutil.WriteFile(filename, replaced, 0)
+	}
+	return nil
+}
diff --git a/cmd/vdl/arith_test.go b/cmd/vdl/arith_test.go
new file mode 100644
index 0000000..8cb78b7
--- /dev/null
+++ b/cmd/vdl/arith_test.go
@@ -0,0 +1,472 @@
+// 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_test
+
+// This test assumes the vdl packages under v.io/x/ref/lib/vdl/testdata have been
+// compiled using the vdl binary, and runs end-to-end rpc tests against the
+// generated output.  It's meant as a final sanity check of the vdl compiler; by
+// using the compiled results we're behaving as an end-user would behave.
+
+import (
+	"errors"
+	"math"
+	"reflect"
+	"testing"
+
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/vdl"
+	"v.io/x/ref/lib/vdl/testdata/arith"
+	"v.io/x/ref/lib/vdl/testdata/base"
+	"v.io/x/ref/lib/xrpc"
+	"v.io/x/ref/test"
+
+	_ "v.io/x/ref/runtime/factories/generic"
+)
+
+var generatedError = errors.New("generated error")
+
+// serverArith implements the arith.Arith interface.
+type serverArith struct{}
+
+func (*serverArith) Add(_ *context.T, _ rpc.ServerCall, A, B int32) (int32, error) {
+	return A + B, nil
+}
+
+func (*serverArith) DivMod(_ *context.T, _ rpc.ServerCall, A, B int32) (int32, int32, error) {
+	return A / B, A % B, nil
+}
+
+func (*serverArith) Sub(_ *context.T, _ rpc.ServerCall, args base.Args) (int32, error) {
+	return args.A - args.B, nil
+}
+
+func (*serverArith) Mul(_ *context.T, _ rpc.ServerCall, nestedArgs base.NestedArgs) (int32, error) {
+	return nestedArgs.Args.A * nestedArgs.Args.B, nil
+}
+
+func (*serverArith) Count(_ *context.T, call arith.ArithCountServerCall, start int32) error {
+	const kNum = 1000
+	for i := int32(0); i < kNum; i++ {
+		if err := call.SendStream().Send(start + i); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func (*serverArith) StreamingAdd(_ *context.T, call arith.ArithStreamingAddServerCall) (int32, error) {
+	var total int32
+	for call.RecvStream().Advance() {
+		value := call.RecvStream().Value()
+		total += value
+		call.SendStream().Send(total)
+	}
+	return total, call.RecvStream().Err()
+}
+
+func (*serverArith) GenError(_ *context.T, _ rpc.ServerCall) error {
+	return generatedError
+}
+
+func (*serverArith) QuoteAny(_ *context.T, _ rpc.ServerCall, any *vdl.Value) (*vdl.Value, error) {
+	return vdl.StringValue(any.String()), nil
+}
+
+type serverCalculator struct {
+	serverArith
+}
+
+func (*serverCalculator) Sine(_ *context.T, _ rpc.ServerCall, angle float64) (float64, error) {
+	return math.Sin(angle), nil
+}
+
+func (*serverCalculator) Cosine(_ *context.T, _ rpc.ServerCall, angle float64) (float64, error) {
+	return math.Cos(angle), nil
+}
+
+func (*serverCalculator) Exp(_ *context.T, _ rpc.ServerCall, x float64) (float64, error) {
+	return math.Exp(x), nil
+}
+
+func (*serverCalculator) On(_ *context.T, _ rpc.ServerCall) error {
+	return nil
+}
+
+func (*serverCalculator) Off(_ *context.T, _ rpc.ServerCall) error {
+	return nil
+}
+
+func TestCalculator(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	server, err := xrpc.NewServer(ctx, "", arith.CalculatorServer(&serverCalculator{}), nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	root := server.Status().Endpoints[0].Name()
+	// Synchronous calls
+	calculator := arith.CalculatorClient(root)
+	sine, err := calculator.Sine(ctx, 0)
+	if err != nil {
+		t.Errorf("Sine: got %q but expected no error", err)
+	}
+	if sine != 0 {
+		t.Errorf("Sine: expected 0 got %f", sine)
+	}
+	cosine, err := calculator.Cosine(ctx, 0)
+	if err != nil {
+		t.Errorf("Cosine: got %q but expected no error", err)
+	}
+	if cosine != 1 {
+		t.Errorf("Cosine: expected 1 got %f", cosine)
+	}
+
+	ar := arith.ArithClient(root)
+	sum, err := ar.Add(ctx, 7, 8)
+	if err != nil {
+		t.Errorf("Add: got %q but expected no error", err)
+	}
+	if sum != 15 {
+		t.Errorf("Add: expected 15 got %d", sum)
+	}
+	ar = calculator
+	sum, err = ar.Add(ctx, 7, 8)
+	if err != nil {
+		t.Errorf("Add: got %q but expected no error", err)
+	}
+	if sum != 15 {
+		t.Errorf("Add: expected 15 got %d", sum)
+	}
+
+	trig := arith.TrigonometryClient(root)
+	cosine, err = trig.Cosine(ctx, 0)
+	if err != nil {
+		t.Errorf("Cosine: got %q but expected no error", err)
+	}
+	if cosine != 1 {
+		t.Errorf("Cosine: expected 1 got %f", cosine)
+	}
+
+	// Test auto-generated methods.
+	serverStub := arith.CalculatorServer(&serverCalculator{})
+	expectDesc(t, serverStub.Describe__(), []rpc.InterfaceDesc{
+		{
+			Name:    "Calculator",
+			PkgPath: "v.io/x/ref/lib/vdl/testdata/arith",
+			Embeds: []rpc.EmbedDesc{
+				{
+					Name:    "Arith",
+					PkgPath: "v.io/x/ref/lib/vdl/testdata/arith",
+				},
+				{
+					Name:    "AdvancedMath",
+					PkgPath: "v.io/x/ref/lib/vdl/testdata/arith",
+				},
+			},
+			Methods: []rpc.MethodDesc{
+				{Name: "On"},
+				{Name: "Off", Tags: []*vdl.Value{vdl.StringValue("offtag")}},
+			},
+		},
+		{
+			Name:    "Arith",
+			PkgPath: "v.io/x/ref/lib/vdl/testdata/arith",
+			Methods: []rpc.MethodDesc{
+				{
+					Name:    "Add",
+					InArgs:  []rpc.ArgDesc{{Name: "a"}, {Name: "b"}},
+					OutArgs: []rpc.ArgDesc{{}},
+				},
+				{
+					Name:    "DivMod",
+					InArgs:  []rpc.ArgDesc{{Name: "a"}, {Name: "b"}},
+					OutArgs: []rpc.ArgDesc{{Name: "quot"}, {Name: "rem"}},
+				},
+				{
+					Name:    "Sub",
+					InArgs:  []rpc.ArgDesc{{Name: "args"}},
+					OutArgs: []rpc.ArgDesc{{}},
+				},
+				{
+					Name:    "Mul",
+					InArgs:  []rpc.ArgDesc{{Name: "nested"}},
+					OutArgs: []rpc.ArgDesc{{}},
+				},
+				{
+					Name: "GenError",
+					Tags: []*vdl.Value{vdl.StringValue("foo"), vdl.StringValue("barz"), vdl.StringValue("hello"), vdl.Int32Value(129), vdl.Uint64Value(0x24)},
+				},
+				{
+					Name:   "Count",
+					InArgs: []rpc.ArgDesc{{Name: "start"}},
+				},
+				{
+					Name:    "StreamingAdd",
+					OutArgs: []rpc.ArgDesc{{Name: "total"}},
+				},
+				{
+					Name:    "QuoteAny",
+					InArgs:  []rpc.ArgDesc{{Name: "a"}},
+					OutArgs: []rpc.ArgDesc{{}},
+				},
+			},
+		},
+		{
+			Name:    "AdvancedMath",
+			PkgPath: "v.io/x/ref/lib/vdl/testdata/arith",
+			Embeds: []rpc.EmbedDesc{
+				{
+					Name:    "Trigonometry",
+					PkgPath: "v.io/x/ref/lib/vdl/testdata/arith",
+				},
+				{
+					Name:    "Exp",
+					PkgPath: "v.io/x/ref/lib/vdl/testdata/arith/exp",
+				}},
+		},
+		{
+			Name:    "Trigonometry",
+			PkgPath: "v.io/x/ref/lib/vdl/testdata/arith",
+			Doc:     "// Trigonometry is an interface that specifies a couple trigonometric functions.",
+			Methods: []rpc.MethodDesc{
+				{
+					Name: "Sine",
+					InArgs: []rpc.ArgDesc{
+						{"angle", ``}, // float64
+					},
+					OutArgs: []rpc.ArgDesc{
+						{"", ``}, // float64
+					},
+				},
+				{
+					Name: "Cosine",
+					InArgs: []rpc.ArgDesc{
+						{"angle", ``}, // float64
+					},
+					OutArgs: []rpc.ArgDesc{
+						{"", ``}, // float64
+					},
+				},
+			},
+		},
+		{
+			Name:    "Exp",
+			PkgPath: "v.io/x/ref/lib/vdl/testdata/arith/exp",
+			Methods: []rpc.MethodDesc{
+				{
+					Name: "Exp",
+					InArgs: []rpc.ArgDesc{
+						{"x", ``}, // float64
+					},
+					OutArgs: []rpc.ArgDesc{
+						{"", ``}, // float64
+					},
+				},
+			},
+		},
+	})
+}
+
+func TestArith(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	// TODO(bprosnitz) Split this test up -- it is quite long and hard to debug.
+
+	// We try a few types of dispatchers on the server side, to verify that
+	// anything dispatching to Arith or an interface embedding Arith (like
+	// Calculator) works for a client looking to talk to an Arith service.
+	objects := []interface{}{
+		arith.ArithServer(&serverArith{}),
+		arith.ArithServer(&serverCalculator{}),
+		arith.CalculatorServer(&serverCalculator{}),
+	}
+
+	for i, obj := range objects {
+		server, err := xrpc.NewServer(ctx, "", obj, nil)
+		if err != nil {
+			t.Fatalf("%d: %v", i, err)
+		}
+		root := server.Status().Endpoints[0].Name()
+
+		// Synchronous calls
+		ar := arith.ArithClient(root)
+		sum, err := ar.Add(ctx, 7, 8)
+		if err != nil {
+			t.Errorf("Add: got %q but expected no error", err)
+		}
+		if sum != 15 {
+			t.Errorf("Add: expected 15 got %d", sum)
+		}
+		q, r, err := ar.DivMod(ctx, 7, 3)
+		if err != nil {
+			t.Errorf("DivMod: got %q but expected no error", err)
+		}
+		if q != 2 || r != 1 {
+			t.Errorf("DivMod: expected (2,1) got (%d,%d)", q, r)
+		}
+		diff, err := ar.Sub(ctx, base.Args{A: 7, B: 8})
+		if err != nil {
+			t.Errorf("Sub: got %q but expected no error", err)
+		}
+		if diff != -1 {
+			t.Errorf("Sub: got %d, expected -1", diff)
+		}
+		prod, err := ar.Mul(ctx, base.NestedArgs{Args: base.Args{A: 7, B: 8}})
+		if err != nil {
+			t.Errorf("Mul: got %q, but expected no error", err)
+		}
+		if prod != 56 {
+			t.Errorf("Sub: got %d, expected 56", prod)
+		}
+		stream, err := ar.Count(ctx, 35)
+		if err != nil {
+			t.Fatalf("error while executing Count %v", err)
+		}
+
+		countIterator := stream.RecvStream()
+		for i := int32(0); i < 1000; i++ {
+			if !countIterator.Advance() {
+				t.Errorf("Error getting value %v", countIterator.Err())
+			}
+			val := countIterator.Value()
+			if val != 35+i {
+				t.Errorf("Expected value %d, got %d", 35+i, val)
+			}
+		}
+		if countIterator.Advance() || countIterator.Err() != nil {
+			t.Errorf("Reply stream should have been closed %v", countIterator.Err())
+		}
+
+		if err := stream.Finish(); err != nil {
+			t.Errorf("Count failed with %v", err)
+		}
+
+		addStream, err := ar.StreamingAdd(ctx)
+
+		go func() {
+			sender := addStream.SendStream()
+			for i := int32(0); i < 100; i++ {
+				if err := sender.Send(i); err != nil {
+					t.Errorf("Send error %v", err)
+				}
+			}
+			if err := sender.Close(); err != nil {
+				t.Errorf("Close error %v", err)
+			}
+		}()
+
+		var expectedSum int32
+		rStream := addStream.RecvStream()
+		for i := int32(0); i < 100; i++ {
+			expectedSum += i
+			if !rStream.Advance() {
+				t.Errorf("Error getting value %v", rStream.Err())
+			}
+			value := rStream.Value()
+			if value != expectedSum {
+				t.Errorf("Got %d but expected %d", value, expectedSum)
+			}
+		}
+
+		if rStream.Advance() || rStream.Err() != nil {
+			t.Errorf("Reply stream should have been closed %v", rStream.Err())
+		}
+
+		total, err := addStream.Finish()
+
+		if err != nil {
+			t.Errorf("Count failed with %v", err)
+		}
+
+		if total != expectedSum {
+			t.Errorf("Got %d but expexted %d", total, expectedSum)
+		}
+
+		if err := ar.GenError(ctx); err == nil {
+			t.Errorf("GenError: got %v but expected %v", err, generatedError)
+		}
+
+		// Server-side stubs
+
+		serverStub := arith.ArithServer(&serverArith{})
+		expectDesc(t, serverStub.Describe__(), []rpc.InterfaceDesc{
+			{
+				Name:    "Arith",
+				PkgPath: "v.io/x/ref/lib/vdl/testdata/arith",
+				Methods: []rpc.MethodDesc{
+					{
+						Name:    "Add",
+						InArgs:  []rpc.ArgDesc{{Name: "a"}, {Name: "b"}},
+						OutArgs: []rpc.ArgDesc{{}},
+					},
+					{
+						Name:    "DivMod",
+						InArgs:  []rpc.ArgDesc{{Name: "a"}, {Name: "b"}},
+						OutArgs: []rpc.ArgDesc{{Name: "quot"}, {Name: "rem"}},
+					},
+					{
+						Name:    "Sub",
+						InArgs:  []rpc.ArgDesc{{Name: "args"}},
+						OutArgs: []rpc.ArgDesc{{}},
+					},
+					{
+						Name:    "Mul",
+						InArgs:  []rpc.ArgDesc{{Name: "nested"}},
+						OutArgs: []rpc.ArgDesc{{}},
+					},
+					{
+						Name: "GenError",
+						Tags: []*vdl.Value{vdl.StringValue("foo"), vdl.StringValue("barz"), vdl.StringValue("hello"), vdl.Int32Value(129), vdl.Uint64Value(0x24)},
+					},
+					{
+						Name:   "Count",
+						InArgs: []rpc.ArgDesc{{Name: "start"}},
+					},
+					{
+						Name:    "StreamingAdd",
+						OutArgs: []rpc.ArgDesc{{Name: "total"}},
+					},
+					{
+						Name:    "QuoteAny",
+						InArgs:  []rpc.ArgDesc{{Name: "a"}},
+						OutArgs: []rpc.ArgDesc{{}},
+					},
+				},
+			},
+		})
+	}
+}
+
+func expectDesc(t *testing.T, got, want []rpc.InterfaceDesc) {
+	stripDesc(got)
+	stripDesc(want)
+	if !reflect.DeepEqual(got, want) {
+		t.Errorf("Describe__ got %#v, want %#v", got, want)
+	}
+}
+
+func stripDesc(desc []rpc.InterfaceDesc) {
+	// Don't bother testing the documentation, to avoid spurious changes.
+	for i := range desc {
+		desc[i].Doc = ""
+		for j := range desc[i].Embeds {
+			desc[i].Embeds[j].Doc = ""
+		}
+		for j := range desc[i].Methods {
+			desc[i].Methods[j].Doc = ""
+			for k := range desc[i].Methods[j].InArgs {
+				desc[i].Methods[j].InArgs[k].Doc = ""
+			}
+			for k := range desc[i].Methods[j].OutArgs {
+				desc[i].Methods[j].OutArgs[k].Doc = ""
+			}
+			desc[i].Methods[j].InStream.Doc = ""
+			desc[i].Methods[j].OutStream.Doc = ""
+		}
+	}
+}
diff --git a/cmd/vdl/doc.go b/cmd/vdl/doc.go
new file mode 100644
index 0000000..5cc386a
--- /dev/null
+++ b/cmd/vdl/doc.go
@@ -0,0 +1,290 @@
+// 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
+
+/*
+Command vdl manages Vanadium Definition Language source code.  It's similar to
+the go tool used for managing Go source code.
+
+Usage:
+   vdl [flags] <command>
+
+The vdl commands are:
+   generate    Compile packages and dependencies, and generate code
+   compile     Compile packages and dependencies, but don't generate code
+   audit       Check if any packages are stale and need generation
+   list        List package and dependency info in transitive order
+   help        Display help for commands or topics
+
+The vdl additional help topics are:
+   packages    Description of package lists
+   vdlpath     Description of VDLPATH environment variable
+   vdlroot     Description of VDLROOT environment variable
+   vdl.config  Description of vdl.config files
+
+The vdl flags are:
+ -exts=.vdl
+   Comma-separated list of valid VDL file name extensions.
+ -ignore_unknown=false
+   Ignore unknown packages provided on the command line.
+ -max-errors=-1
+   Stop processing after this many errors, or -1 for unlimited.
+ -v=false
+   Turn on verbose logging.
+ -vdl.config=vdl.config
+   Basename of the optional per-package config file.
+
+The global flags are:
+ -metadata=<just specify -metadata to activate>
+   Displays metadata for the program and exits.
+
+Vdl generate
+
+Generate compiles packages and their transitive dependencies, and generates code
+in the specified languages.
+
+Usage:
+   vdl generate [flags] <packages>
+
+<packages> are a list of packages to process, similar to the standard go tool.
+For more information, run "vdl help packages".
+
+The vdl generate flags are:
+ -go-out-dir=
+   Go output directory.  There are three modes:
+      ""                     : Generate output in-place in the source tree
+      "dir"                  : Generate output rooted at dir
+      "src->dst[,s2->d2...]" : Generate output using translation rules
+   Assume your source tree is organized as follows:
+      VDLPATH=/home/vdl
+         /home/vdl/test_base/base1.vdl
+         /home/vdl/test_base/base2.vdl
+   Here's example output under the different modes:
+      --go-out-dir=""
+         /home/vdl/test_base/base1.vdl.go
+         /home/vdl/test_base/base2.vdl.go
+      --go-out-dir="/tmp/foo"
+         /tmp/foo/test_base/base1.vdl.go
+         /tmp/foo/test_base/base2.vdl.go
+      --go-out-dir="vdl->foo/bar"
+         /home/foo/bar/test_base/base1.vdl.go
+         /home/foo/bar/test_base/base2.vdl.go
+   When the src->dst form is used, src must match the suffix of the path just
+   before the package path, and dst is the replacement for src.  Use commas to
+   separate multiple rules; the first rule matching src is used.  The special
+   dst SKIP indicates matching packages are skipped.
+ -java-out-dir=release/go/src->release/java/lib/generated-src/vdl,roadmap/go/src->release/java/lib/generated-src/vdl
+   Same semantics as --go-out-dir but applies to java code generation.
+ -java-out-pkg=v.io->io/v
+   Java output package translation rules.  Must be of the form:
+      "src->dst[,s2->d2...]"
+   If a VDL package has a prefix src, the prefix will be replaced with dst.  Use
+   commas to separate multiple rules; the first rule matching src is used, and
+   if there are no matching rules, the package remains unchanged.  The special
+   dst SKIP indicates matching packages are skipped.
+ -js-out-dir=release/go/src->release/javascript/core/src,roadmap/go/src->release/javascript/core/src,third_party/go/src->SKIP,tools/go/src->SKIP,release/go/src/v.io/v23/vdlroot->SKIP
+   Same semantics as --go-out-dir but applies to js code generation.
+ -js-relative-path-to-core=
+   If set, this is the relative path from js-out-dir to the root of the JS core
+ -lang=Go
+   Comma-separated list of languages to generate, currently supporting
+   Go,Java,Javascript
+ -status=true
+   Show package names as they are updated
+
+Vdl compile
+
+Compile compiles packages and their transitive dependencies, but does not
+generate code.  This is useful to sanity-check that your VDL files are valid.
+
+Usage:
+   vdl compile [flags] <packages>
+
+<packages> are a list of packages to process, similar to the standard go tool.
+For more information, run "vdl help packages".
+
+The vdl compile flags are:
+ -status=true
+   Show package names while we compile
+
+Vdl audit - Check if any packages are stale and need generation
+
+Audit runs the same logic as generate, but doesn't write out generated files.
+Returns a 0 exit code if all packages are up-to-date, otherwise returns a non-0
+exit code indicating some packages need generation.
+
+Usage:
+   vdl audit [flags] <packages>
+
+<packages> are a list of packages to process, similar to the standard go tool.
+For more information, run "vdl help packages".
+
+The vdl audit flags are:
+ -go-out-dir=
+   Go output directory.  There are three modes:
+      ""                     : Generate output in-place in the source tree
+      "dir"                  : Generate output rooted at dir
+      "src->dst[,s2->d2...]" : Generate output using translation rules
+   Assume your source tree is organized as follows:
+      VDLPATH=/home/vdl
+         /home/vdl/test_base/base1.vdl
+         /home/vdl/test_base/base2.vdl
+   Here's example output under the different modes:
+      --go-out-dir=""
+         /home/vdl/test_base/base1.vdl.go
+         /home/vdl/test_base/base2.vdl.go
+      --go-out-dir="/tmp/foo"
+         /tmp/foo/test_base/base1.vdl.go
+         /tmp/foo/test_base/base2.vdl.go
+      --go-out-dir="vdl->foo/bar"
+         /home/foo/bar/test_base/base1.vdl.go
+         /home/foo/bar/test_base/base2.vdl.go
+   When the src->dst form is used, src must match the suffix of the path just
+   before the package path, and dst is the replacement for src.  Use commas to
+   separate multiple rules; the first rule matching src is used.  The special
+   dst SKIP indicates matching packages are skipped.
+ -java-out-dir=release/go/src->release/java/lib/generated-src/vdl,roadmap/go/src->release/java/lib/generated-src/vdl
+   Same semantics as --go-out-dir but applies to java code generation.
+ -java-out-pkg=v.io->io/v
+   Java output package translation rules.  Must be of the form:
+      "src->dst[,s2->d2...]"
+   If a VDL package has a prefix src, the prefix will be replaced with dst.  Use
+   commas to separate multiple rules; the first rule matching src is used, and
+   if there are no matching rules, the package remains unchanged.  The special
+   dst SKIP indicates matching packages are skipped.
+ -js-out-dir=release/go/src->release/javascript/core/src,roadmap/go/src->release/javascript/core/src,third_party/go/src->SKIP,tools/go/src->SKIP,release/go/src/v.io/v23/vdlroot->SKIP
+   Same semantics as --go-out-dir but applies to js code generation.
+ -js-relative-path-to-core=
+   If set, this is the relative path from js-out-dir to the root of the JS core
+ -lang=Go
+   Comma-separated list of languages to generate, currently supporting
+   Go,Java,Javascript
+ -status=true
+   Show package names as they are updated
+
+Vdl list - List package and dependency info in transitive order
+
+List returns information about packages and their transitive dependencies, in
+transitive order.  This is the same order the generate and compile commands use
+for processing.  If "vdl list A" is run and A depends on B, which depends on C,
+the returned order will be C, B, A.  If multiple packages are specified the
+ordering is over all combined dependencies.
+
+Reminder: cyclic dependencies between packages are not allowed.  Cyclic
+dependencies between VDL files within the same package are also not allowed.
+This is more strict than regular Go; it makes it easier to generate code for
+other languages like C++.
+
+Usage:
+   vdl list <packages>
+
+<packages> are a list of packages to process, similar to the standard go tool.
+For more information, run "vdl help packages".
+
+Vdl help - Display help for commands or topics
+
+Help with no args displays the usage of the parent command.
+
+Help with args displays the usage of the specified sub-command or help topic.
+
+"help ..." recursively displays help for all commands and topics.
+
+Usage:
+   vdl help [flags] [command/topic ...]
+
+[command/topic ...] optionally identifies a specific sub-command or help topic.
+
+The vdl help flags are:
+ -style=compact
+   The formatting style for help output:
+      compact - Good for compact cmdline output.
+      full    - Good for cmdline output, shows all global flags.
+      godoc   - Good for godoc processing.
+   Override the default by setting the CMDLINE_STYLE environment variable.
+ -width=<terminal width>
+   Format output to this target width in runes, or unlimited if width < 0.
+   Defaults to the terminal width if available.  Override the default by setting
+   the CMDLINE_WIDTH environment variable.
+
+Vdl packages - Description of package lists
+
+Most vdl commands apply to a list of packages:
+
+   vdl command <packages>
+
+<packages> are a list of packages to process, similar to the standard go tool.
+In its simplest form each package is an import path; e.g.
+   "v.io/x/ref/lib/vdl"
+
+A package that is an absolute path or that begins with a . or .. element is
+interpreted as a file system path, and denotes the package in that directory.
+
+A package is a pattern if it includes one or more "..." wildcards, each of which
+can match any string, including the empty string and strings containing slashes.
+Such a pattern expands to all packages found in VDLPATH with names matching the
+pattern.  As a special-case, x/... matches x as well as x's subdirectories.
+
+The special-case "all" is a synonym for "...", and denotes all packages found in
+VDLPATH.
+
+Import path elements and file names are not allowed to begin with "." or "_";
+such paths are ignored in wildcard matches, and return errors if specified
+explicitly.
+
+Note that whereas GOPATH requires *.go source files and packages to appear under
+a "src" directory, VDLPATH requires *.vdl source files and packages to appear
+directly under the VDLPATH directories.
+
+ Run "vdl help vdlpath" to see docs on VDLPATH.
+ Run "go help packages" to see the standard go package docs.
+
+Vdl vdlpath - Description of VDLPATH environment variable
+
+The VDLPATH environment variable is used to resolve import statements. It must
+be set to compile and generate vdl packages.
+
+The format is a colon-separated list of directories containing vdl source code.
+These directories are searched recursively for VDL source files.  The path below
+the directory determines the import path.  If VDLPATH specifies multiple
+directories, imports are resolved by picking the first directory with a matching
+import name.
+
+An example:
+
+   VDPATH=/home/user/vdlA:/home/user/vdlB
+
+   /home/user/vdlA
+       foo/                 (import "foo" refers here)
+           foo1.vdl
+   /home/user/vdlB
+       foo/                 (this package is ignored)
+          foo2.vdl
+       bar/
+          baz/              (import "bar/baz" refers here)
+             baz.vdl
+
+Vdl vdlroot - Description of VDLROOT environment variable
+
+The VDLROOT environment variable is similar to VDLPATH, but instead of pointing
+to multiple user source directories, it points at a single source directory
+containing the standard vdl packages.
+
+Setting VDLROOT is optional.
+
+If VDLROOT is empty, we try to construct it out of the V23_ROOT environment
+variable.  It is an error if both VDLROOT and V23_ROOT are empty.
+
+Vdl vdl.config
+
+Each vdl source package P may contain an optional file "vdl.config" within the P
+directory.  This file specifies additional configuration for the vdl tool.
+
+The format of this file is described by the vdltool.Config type in the "vdltool"
+standard package, located at VDLROOT/vdltool/config.vdl.
+
+If the file does not exist, we use the zero value of vdl.Config.
+*/
+package main
diff --git a/cmd/vdl/main.go b/cmd/vdl/main.go
new file mode 100644
index 0000000..2810b55
--- /dev/null
+++ b/cmd/vdl/main.go
@@ -0,0 +1,650 @@
+// 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.
+
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go .
+
+package main
+
+import (
+	"bytes"
+	"fmt"
+	"io/ioutil"
+	"log"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"v.io/v23/vdlroot/vdltool"
+	"v.io/x/lib/cmdline"
+	"v.io/x/lib/textutil"
+	"v.io/x/ref/lib/vdl/build"
+	"v.io/x/ref/lib/vdl/codegen/golang"
+	"v.io/x/ref/lib/vdl/codegen/java"
+	"v.io/x/ref/lib/vdl/codegen/javascript"
+	"v.io/x/ref/lib/vdl/compile"
+	"v.io/x/ref/lib/vdl/vdlutil"
+)
+
+func init() {
+	log.SetFlags(log.Lshortfile | log.Ltime | log.Lmicroseconds)
+}
+
+func main() {
+	cmdline.Main(cmdVDL)
+}
+
+func checkErrors(errs *vdlutil.Errors) error {
+	if errs.IsEmpty() {
+		return nil
+	}
+	return fmt.Errorf(`
+%s   (run with "vdl -v" for verbose logging or "vdl help" for help)`, errs)
+}
+
+// runHelper returns a function that generates a sorted list of transitive
+// targets, and calls the supplied run function.
+func runHelper(run func(targets []*build.Package, env *compile.Env)) cmdline.Runner {
+	return cmdline.RunnerFunc(func(_ *cmdline.Env, args []string) error {
+		if flagVerbose {
+			vdlutil.SetVerbose()
+		}
+		if len(args) == 0 {
+			// If the user doesn't specify any targets, the cwd is implied.
+			args = append(args, ".")
+		}
+		env := compile.NewEnv(flagMaxErrors)
+		env.DisallowPathQualifiers()
+		mode := build.UnknownPathIsError
+		if flagIgnoreUnknown {
+			mode = build.UnknownPathIsIgnored
+		}
+		var opts build.Opts
+		opts.Extensions = strings.Split(flagExts, ",")
+		opts.VDLConfigName = flagVDLConfig
+		targets := build.TransitivePackages(args, mode, opts, env.Errors)
+		if err := checkErrors(env.Errors); err != nil {
+			return err
+		}
+		run(targets, env)
+		return checkErrors(env.Errors)
+	})
+}
+
+var topicPackages = cmdline.Topic{
+	Name:  "packages",
+	Short: "Description of package lists",
+	Long: `
+Most vdl commands apply to a list of packages:
+
+   vdl command <packages>
+
+<packages> are a list of packages to process, similar to the standard go tool.
+In its simplest form each package is an import path; e.g.
+   "v.io/x/ref/lib/vdl"
+
+A package that is an absolute path or that begins with a . or .. element is
+interpreted as a file system path, and denotes the package in that directory.
+
+A package is a pattern if it includes one or more "..." wildcards, each of which
+can match any string, including the empty string and strings containing
+slashes.  Such a pattern expands to all packages found in VDLPATH with names
+matching the pattern.  As a special-case, x/... matches x as well as x's
+subdirectories.
+
+The special-case "all" is a synonym for "...", and denotes all packages found
+in VDLPATH.
+
+Import path elements and file names are not allowed to begin with "." or "_";
+such paths are ignored in wildcard matches, and return errors if specified
+explicitly.
+
+Note that whereas GOPATH requires *.go source files and packages to appear
+under a "src" directory, VDLPATH requires *.vdl source files and packages to
+appear directly under the VDLPATH directories.
+
+ Run "vdl help vdlpath" to see docs on VDLPATH.
+ Run "go help packages" to see the standard go package docs.
+`,
+}
+
+var topicVdlPath = cmdline.Topic{
+	Name:  "vdlpath",
+	Short: "Description of VDLPATH environment variable",
+	Long: `
+The VDLPATH environment variable is used to resolve import statements.
+It must be set to compile and generate vdl packages.
+
+The format is a colon-separated list of directories containing vdl source code.
+These directories are searched recursively for VDL source files.  The path
+below the directory determines the import path.  If VDLPATH specifies multiple
+directories, imports are resolved by picking the first directory with a
+matching import name.
+
+An example:
+
+   VDPATH=/home/user/vdlA:/home/user/vdlB
+
+   /home/user/vdlA
+       foo/                 (import "foo" refers here)
+           foo1.vdl
+   /home/user/vdlB
+       foo/                 (this package is ignored)
+          foo2.vdl
+       bar/
+          baz/              (import "bar/baz" refers here)
+             baz.vdl
+`,
+}
+
+var topicVdlRoot = cmdline.Topic{
+	Name:  "vdlroot",
+	Short: "Description of VDLROOT environment variable",
+	Long: `
+The VDLROOT environment variable is similar to VDLPATH, but instead of pointing
+to multiple user source directories, it points at a single source directory
+containing the standard vdl packages.
+
+Setting VDLROOT is optional.
+
+If VDLROOT is empty, we try to construct it out of the V23_ROOT environment
+variable.  It is an error if both VDLROOT and V23_ROOT are empty.
+`,
+}
+
+var topicVdlConfig = cmdline.Topic{
+	Name:  "vdl.config",
+	Short: "Description of vdl.config files",
+	Long: `
+Each vdl source package P may contain an optional file "vdl.config" within the P
+directory.  This file specifies additional configuration for the vdl tool.
+
+The format of this file is described by the vdltool.Config type in the "vdltool"
+standard package, located at VDLROOT/vdltool/config.vdl.
+
+If the file does not exist, we use the zero value of vdl.Config.
+`,
+}
+
+const pkgArgName = "<packages>"
+const pkgArgLong = `
+<packages> are a list of packages to process, similar to the standard go tool.
+For more information, run "vdl help packages".
+`
+
+var cmdCompile = &cmdline.Command{
+	Runner: runHelper(runCompile),
+	Name:   "compile",
+	Short:  "Compile packages and dependencies, but don't generate code",
+	Long: `
+Compile compiles packages and their transitive dependencies, but does not
+generate code.  This is useful to sanity-check that your VDL files are valid.
+`,
+	ArgsName: pkgArgName,
+	ArgsLong: pkgArgLong,
+}
+
+var cmdGenerate = &cmdline.Command{
+	Runner: runHelper(runGenerate),
+	Name:   "generate",
+	Short:  "Compile packages and dependencies, and generate code",
+	Long: `
+Generate compiles packages and their transitive dependencies, and generates code
+in the specified languages.
+`,
+	ArgsName: pkgArgName,
+	ArgsLong: pkgArgLong,
+}
+
+var cmdAudit = &cmdline.Command{
+	Runner: runHelper(runAudit),
+	Name:   "audit",
+	Short:  "Check if any packages are stale and need generation",
+	Long: `
+Audit runs the same logic as generate, but doesn't write out generated files.
+Returns a 0 exit code if all packages are up-to-date, otherwise returns a
+non-0 exit code indicating some packages need generation.
+`,
+	ArgsName: pkgArgName,
+	ArgsLong: pkgArgLong,
+}
+
+var cmdList = &cmdline.Command{
+	Runner: runHelper(runList),
+	Name:   "list",
+	Short:  "List package and dependency info in transitive order",
+	Long: `
+List returns information about packages and their transitive dependencies, in
+transitive order.  This is the same order the generate and compile commands use
+for processing.  If "vdl list A" is run and A depends on B, which depends on C,
+the returned order will be C, B, A.  If multiple packages are specified the
+ordering is over all combined dependencies.
+
+Reminder: cyclic dependencies between packages are not allowed.  Cyclic
+dependencies between VDL files within the same package are also not allowed.
+This is more strict than regular Go; it makes it easier to generate code for
+other languages like C++.
+`,
+	ArgsName: pkgArgName,
+	ArgsLong: pkgArgLong,
+}
+
+var genLangAll = genLangs(vdltool.GenLanguageAll[:])
+
+type genLangs []vdltool.GenLanguage
+
+func (gls genLangs) String() string {
+	var ret string
+	for i, gl := range gls {
+		if i > 0 {
+			ret += ","
+		}
+		ret += gl.String()
+	}
+	return ret
+}
+
+func (gls *genLangs) Set(value string) error {
+	// If the flag is repeated on the cmdline it is overridden.  Duplicates within
+	// the comma separated list are ignored, and retain their original ordering.
+	*gls = genLangs{}
+	seen := make(map[vdltool.GenLanguage]bool)
+	for _, str := range strings.Split(value, ",") {
+		gl, err := vdltool.GenLanguageFromString(str)
+		if err != nil {
+			return err
+		}
+		if !seen[gl] {
+			seen[gl] = true
+			*gls = append(*gls, gl)
+		}
+	}
+	return nil
+}
+
+// genOutDir has three modes:
+//   1) If dir is non-empty, we use it as the out dir.
+//   2) If rules is non-empty, we translate using the xlate rules.
+//   3) If everything is empty, we generate in-place.
+type genOutDir struct {
+	dir   string
+	rules xlateRules
+}
+
+// xlateSrcDst specifies a translation rule, where src must match the suffix of
+// the path just before the package path, and dst is the replacement for src.
+// If dst is the special string "SKIP" we'll skip generation of packages
+// matching the src.
+type xlateSrcDst struct {
+	src, dst string
+}
+
+// xlateRules specifies a collection of translation rules.
+type xlateRules []xlateSrcDst
+
+func (x *xlateRules) String() (ret string) {
+	for _, srcdst := range *x {
+		if len(ret) > 0 {
+			ret += ","
+		}
+		ret += srcdst.src + "->" + srcdst.dst
+	}
+	return
+}
+
+func (x *xlateRules) Set(value string) error {
+	for _, rule := range strings.Split(value, ",") {
+		srcdst := strings.Split(rule, "->")
+		if len(srcdst) != 2 {
+			return fmt.Errorf("invalid out dir xlate rule %q (not src->dst format)", rule)
+		}
+		*x = append(*x, xlateSrcDst{srcdst[0], srcdst[1]})
+	}
+	return nil
+}
+
+func (x *genOutDir) String() string {
+	if x.dir != "" {
+		return x.dir
+	}
+	return x.rules.String()
+}
+
+func (x *genOutDir) Set(value string) error {
+	if strings.Contains(value, "->") {
+		x.dir = ""
+		return x.rules.Set(value)
+	}
+	x.dir = value
+	return nil
+}
+
+var (
+	// Common flags for the tool itself, applicable to all commands.
+	flagVerbose       bool
+	flagMaxErrors     int
+	flagExts          string
+	flagVDLConfig     string
+	flagIgnoreUnknown bool
+
+	// Options for each command.
+	optCompileStatus bool
+	optGenStatus     bool
+	optGenGoOutDir   = genOutDir{}
+	optGenJavaOutDir = genOutDir{
+		rules: xlateRules{
+			{"release/go/src", "release/java/lib/generated-src/vdl"},
+			{"roadmap/go/src", "release/java/lib/generated-src/vdl"},
+		},
+	}
+	optGenJavascriptOutDir = genOutDir{
+		rules: xlateRules{
+			{"release/go/src", "release/javascript/core/src"},
+			{"roadmap/go/src", "release/javascript/core/src"},
+			{"third_party/go/src", "SKIP"},
+			{"tools/go/src", "SKIP"},
+			// TODO(toddw): Skip vdlroot javascript generation for now.
+			{"release/go/src/v.io/v23/vdlroot", "SKIP"},
+		},
+	}
+	optGenJavaOutPkg = xlateRules{
+		{"v.io", "io/v"},
+	}
+	optPathToJSCore string
+	// Default to just running the go lang; other langs need special setup.
+	optGenLangs = genLangs{vdltool.GenLanguageGo}
+)
+
+// Root returns the root command for the VDL tool.
+var cmdVDL = &cmdline.Command{
+	Name:  "vdl",
+	Short: "manages Vanadium Definition Language source code",
+	Long: `
+Command vdl manages Vanadium Definition Language source code.  It's similar to
+the go tool used for managing Go source code.
+`,
+	Children: []*cmdline.Command{cmdGenerate, cmdCompile, cmdAudit, cmdList},
+	Topics:   []cmdline.Topic{topicPackages, topicVdlPath, topicVdlRoot, topicVdlConfig},
+}
+
+func init() {
+	// Common flags for the tool itself, applicable to all commands.
+	cmdVDL.Flags.BoolVar(&flagVerbose, "v", false, "Turn on verbose logging.")
+	cmdVDL.Flags.IntVar(&flagMaxErrors, "max-errors", -1, "Stop processing after this many errors, or -1 for unlimited.")
+	cmdVDL.Flags.StringVar(&flagExts, "exts", ".vdl", "Comma-separated list of valid VDL file name extensions.")
+	cmdVDL.Flags.StringVar(&flagVDLConfig, "vdl.config", "vdl.config", "Basename of the optional per-package config file.")
+	cmdVDL.Flags.BoolVar(&flagIgnoreUnknown, "ignore_unknown", false, "Ignore unknown packages provided on the command line.")
+
+	// Options for compile.
+	cmdCompile.Flags.BoolVar(&optCompileStatus, "status", true, "Show package names while we compile")
+
+	// Options for generate.
+	cmdGenerate.Flags.Var(&optGenLangs, "lang", "Comma-separated list of languages to generate, currently supporting "+genLangAll.String())
+	cmdGenerate.Flags.BoolVar(&optGenStatus, "status", true, "Show package names as they are updated")
+	// TODO(toddw): Move out_dir configuration into vdl.config, and provide a
+	// generic override mechanism for vdl.config.
+	cmdGenerate.Flags.Var(&optGenGoOutDir, "go-out-dir", `
+Go output directory.  There are three modes:
+   ""                     : Generate output in-place in the source tree
+   "dir"                  : Generate output rooted at dir
+   "src->dst[,s2->d2...]" : Generate output using translation rules
+Assume your source tree is organized as follows:
+   VDLPATH=/home/vdl
+      /home/vdl/test_base/base1.vdl
+      /home/vdl/test_base/base2.vdl
+Here's example output under the different modes:
+   --go-out-dir=""
+      /home/vdl/test_base/base1.vdl.go
+      /home/vdl/test_base/base2.vdl.go
+   --go-out-dir="/tmp/foo"
+      /tmp/foo/test_base/base1.vdl.go
+      /tmp/foo/test_base/base2.vdl.go
+   --go-out-dir="vdl->foo/bar"
+      /home/foo/bar/test_base/base1.vdl.go
+      /home/foo/bar/test_base/base2.vdl.go
+When the src->dst form is used, src must match the suffix of the path just
+before the package path, and dst is the replacement for src.  Use commas to
+separate multiple rules; the first rule matching src is used.  The special dst
+SKIP indicates matching packages are skipped.`)
+	cmdGenerate.Flags.Var(&optGenJavaOutDir, "java-out-dir",
+		"Same semantics as --go-out-dir but applies to java code generation.")
+	cmdGenerate.Flags.Var(&optGenJavaOutPkg, "java-out-pkg", `
+Java output package translation rules.  Must be of the form:
+   "src->dst[,s2->d2...]"
+If a VDL package has a prefix src, the prefix will be replaced with dst.  Use
+commas to separate multiple rules; the first rule matching src is used, and if
+there are no matching rules, the package remains unchanged.  The special dst
+SKIP indicates matching packages are skipped.`)
+	cmdGenerate.Flags.Var(&optGenJavascriptOutDir, "js-out-dir",
+		"Same semantics as --go-out-dir but applies to js code generation.")
+	cmdGenerate.Flags.StringVar(&optPathToJSCore, "js-relative-path-to-core", "",
+		"If set, this is the relative path from js-out-dir to the root of the JS core")
+
+	// Options for audit are identical to generate.
+	cmdAudit.Flags = cmdGenerate.Flags
+}
+
+func runCompile(targets []*build.Package, env *compile.Env) {
+	for _, target := range targets {
+		pkg := build.BuildPackage(target, env)
+		if pkg != nil && optCompileStatus {
+			fmt.Println(pkg.Path)
+		}
+	}
+}
+
+func runGenerate(targets []*build.Package, env *compile.Env) {
+	gen(false, targets, env)
+}
+
+func runAudit(targets []*build.Package, env *compile.Env) {
+	if gen(true, targets, env) && env.Errors.IsEmpty() {
+		// Some packages are stale, and there were no errors; return an arbitrary
+		// non-0 exit code.  Errors are handled in runHelper, as usual.
+		os.Exit(10)
+	}
+}
+
+func shouldGenerate(config vdltool.Config, lang vdltool.GenLanguage) bool {
+	// If config.GenLanguages is empty, all languages are allowed to be generated.
+	_, ok := config.GenLanguages[lang]
+	return len(config.GenLanguages) == 0 || ok
+}
+
+// gen generates the given targets with env.  If audit is true, only checks
+// whether any packages are stale; otherwise files will actually be written out.
+// Returns true if any packages are stale.
+func gen(audit bool, targets []*build.Package, env *compile.Env) bool {
+	anychanged := false
+	for _, target := range targets {
+		pkg := build.BuildPackage(target, env)
+		if pkg == nil {
+			// Stop at the first package that fails to compile.
+			if env.Errors.IsEmpty() {
+				env.Errors.Errorf("%s: internal error (compiled into nil package)", target.Path)
+			}
+			return true
+		}
+		// TODO(toddw): Skip code generation if the semantic contents of the
+		// generated file haven't changed.
+		pkgchanged := false
+		for _, gl := range optGenLangs {
+			switch gl {
+			case vdltool.GenLanguageGo:
+				if !shouldGenerate(pkg.Config, vdltool.GenLanguageGo) {
+					continue
+				}
+				dir, err := xlateOutDir(target.Dir, target.GenPath, optGenGoOutDir, pkg.GenPath)
+				if handleErrorOrSkip("--go-out-dir", err, env) {
+					continue
+				}
+				for _, file := range pkg.Files {
+					data := golang.Generate(file, env)
+					if writeFile(audit, data, dir, file.BaseName+".go", env) {
+						pkgchanged = true
+					}
+				}
+			case vdltool.GenLanguageJava:
+				if !shouldGenerate(pkg.Config, vdltool.GenLanguageJava) {
+					continue
+				}
+				pkgPath, err := xlatePkgPath(pkg.GenPath, optGenJavaOutPkg)
+				if handleErrorOrSkip("--java-out-pkg", err, env) {
+					continue
+				}
+				dir, err := xlateOutDir(target.Dir, target.GenPath, optGenJavaOutDir, pkgPath)
+				if handleErrorOrSkip("--java-out-dir", err, env) {
+					continue
+				}
+				java.SetPkgPathXlator(func(pkgPath string) string {
+					result, _ := xlatePkgPath(pkgPath, optGenJavaOutPkg)
+					return result
+				})
+				for _, file := range java.Generate(pkg, env) {
+					fileDir := filepath.Join(dir, file.Dir)
+					if writeFile(audit, file.Data, fileDir, file.Name, env) {
+						pkgchanged = true
+					}
+				}
+			case vdltool.GenLanguageJavascript:
+				if !shouldGenerate(pkg.Config, vdltool.GenLanguageJavascript) {
+					continue
+				}
+				dir, err := xlateOutDir(target.Dir, target.GenPath, optGenJavascriptOutDir, pkg.GenPath)
+				if handleErrorOrSkip("--js-out-dir", err, env) {
+					continue
+				}
+				path := func(importPath string) string {
+					prefix := filepath.Clean(target.Dir[0 : len(target.Dir)-len(target.GenPath)])
+					pkgDir := filepath.Join(prefix, filepath.FromSlash(importPath))
+					fullDir, err := xlateOutDir(pkgDir, importPath, optGenJavascriptOutDir, importPath)
+					if err != nil {
+						panic(err)
+					}
+					cleanPath, err := filepath.Rel(dir, fullDir)
+					if err != nil {
+						panic(err)
+					}
+					return cleanPath
+				}
+				data := javascript.Generate(pkg, env, path, optPathToJSCore)
+				if writeFile(audit, data, dir, "index.js", env) {
+					pkgchanged = true
+				}
+			default:
+				env.Errors.Errorf("Generating code for language %v isn't supported", gl)
+			}
+		}
+		if pkgchanged {
+			anychanged = true
+			if optGenStatus {
+				fmt.Println(pkg.Path)
+			}
+		}
+	}
+	return anychanged
+}
+
+// writeFile writes data into the standard location for file, using the given
+// suffix.  Errors are reported via env.  Returns true iff the file doesn't
+// already exist with the given data.
+func writeFile(audit bool, data []byte, dirName, baseName string, env *compile.Env) bool {
+	dstName := filepath.Join(dirName, baseName)
+	// Don't change anything if old and new are the same.
+	if oldData, err := ioutil.ReadFile(dstName); err == nil && bytes.Equal(oldData, data) {
+		return false
+	}
+	if !audit {
+		// Create containing directory, if it doesn't already exist.
+		if err := os.MkdirAll(dirName, os.FileMode(0777)); err != nil {
+			env.Errors.Errorf("Couldn't create directory %s: %v", dirName, err)
+			return true
+		}
+		if err := ioutil.WriteFile(dstName, data, os.FileMode(0666)); err != nil {
+			env.Errors.Errorf("Couldn't write file %s: %v", dstName, err)
+			return true
+		}
+	}
+	return true
+}
+
+func handleErrorOrSkip(prefix string, err error, env *compile.Env) bool {
+	if err != nil {
+		if err != errSkip {
+			env.Errors.Errorf("%s error: %v", prefix, err)
+		}
+		return true
+	}
+	return false
+}
+
+var errSkip = fmt.Errorf("SKIP")
+
+func xlateOutDir(dir, path string, outdir genOutDir, outPkgPath string) (string, error) {
+	path = filepath.FromSlash(path)
+	outPkgPath = filepath.FromSlash(outPkgPath)
+	// Strip package path from the directory.
+	if !strings.HasSuffix(dir, path) {
+		return "", fmt.Errorf("package dir %q doesn't end with package path %q", dir, path)
+	}
+	dir = filepath.Clean(dir[:len(dir)-len(path)])
+
+	switch {
+	case outdir.dir != "":
+		return filepath.Join(outdir.dir, outPkgPath), nil
+	case len(outdir.rules) == 0:
+		return filepath.Join(dir, outPkgPath), nil
+	}
+	// Try translation rules in order.
+	for _, xlate := range outdir.rules {
+		d := dir
+		if !strings.HasSuffix(d, xlate.src) {
+			continue
+		}
+		if xlate.dst == "SKIP" {
+			return "", errSkip
+		}
+		d = filepath.Clean(d[:len(d)-len(xlate.src)])
+		return filepath.Join(d, xlate.dst, outPkgPath), nil
+	}
+	return "", fmt.Errorf("package prefix %q doesn't match translation rules %q", dir, outdir)
+}
+
+func xlatePkgPath(pkgPath string, rules xlateRules) (string, error) {
+	for _, xlate := range rules {
+		if !strings.HasPrefix(pkgPath, xlate.src) {
+			continue
+		}
+		if xlate.dst == "SKIP" {
+			return pkgPath, errSkip
+		}
+		return xlate.dst + pkgPath[len(xlate.src):], nil
+	}
+	return pkgPath, nil
+}
+
+func runList(targets []*build.Package, env *compile.Env) {
+	for tx, target := range targets {
+		num := fmt.Sprintf("%d", tx)
+		fmt.Printf("%s %s\n", num, strings.Repeat("=", termWidth()-len(num)-1))
+		fmt.Printf("Name:    %v\n", target.Name)
+		fmt.Printf("Config:  %+v\n", target.Config)
+		fmt.Printf("Path:    %v\n", target.Path)
+		fmt.Printf("GenPath: %v\n", target.GenPath)
+		fmt.Printf("Dir:     %v\n", target.Dir)
+		if len(target.BaseFileNames) > 0 {
+			fmt.Print("Files:\n")
+			for _, file := range target.BaseFileNames {
+				fmt.Printf("   %v\n", file)
+			}
+		}
+	}
+}
+
+func termWidth() int {
+	if _, width, err := textutil.TerminalSize(); err == nil && width > 0 {
+		return width
+	}
+	return 80 // have a reasonable default
+}
diff --git a/cmd/vdl/v23_internal_test.go b/cmd/vdl/v23_internal_test.go
new file mode 100644
index 0000000..ae59080
--- /dev/null
+++ b/cmd/vdl/v23_internal_test.go
@@ -0,0 +1,20 @@
+// 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 main
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	os.Exit(m.Run())
+}
diff --git a/cmd/vdl/vdl_test.go b/cmd/vdl/vdl_test.go
new file mode 100644
index 0000000..da17bb9
--- /dev/null
+++ b/cmd/vdl/vdl_test.go
@@ -0,0 +1,68 @@
+// 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 (
+	"bytes"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strings"
+	"testing"
+
+	"v.io/x/lib/cmdline"
+)
+
+const (
+	testDir    = "../../lib/vdl/testdata/base"
+	outPkgPath = "v.io/x/ref/lib/vdl/testdata/base"
+)
+
+//go:generate v23 test generate
+
+// Compares generated VDL files against the copy in the repo.
+func TestVDLGenerator(t *testing.T) {
+	// Use vdl to generate Go code from input, into a temporary directory.
+	outDir, err := ioutil.TempDir("", "vdltest")
+	if err != nil {
+		t.Fatalf("TempDir() failed: %v", err)
+	}
+	defer os.RemoveAll(outDir)
+	// TODO(toddw): test the generated java and javascript files too.
+	outOpt := fmt.Sprintf("--go-out-dir=%s", outDir)
+	env := cmdline.EnvFromOS()
+	if err := cmdline.ParseAndRun(cmdVDL, env, []string{"generate", "--lang=go", outOpt, testDir}); err != nil {
+		t.Fatalf("Execute() failed: %v", err)
+	}
+	// Check that each *.vdl.go file in the testDir matches the generated output.
+	entries, err := ioutil.ReadDir(testDir)
+	if err != nil {
+		t.Fatalf("ReadDir(%v) failed: %v", testDir, err)
+	}
+	numEqual := 0
+	for _, entry := range entries {
+		if !strings.HasSuffix(entry.Name(), ".vdl.go") {
+			continue
+		}
+		testFile := filepath.Join(testDir, entry.Name())
+		testBytes, err := ioutil.ReadFile(testFile)
+		if err != nil {
+			t.Fatalf("ReadFile(%v) failed: %v", testFile, err)
+		}
+		outFile := filepath.Join(outDir, outPkgPath, entry.Name())
+		outBytes, err := ioutil.ReadFile(outFile)
+		if err != nil {
+			t.Fatalf("ReadFile(%v) failed: %v", outFile, err)
+		}
+		if !bytes.Equal(outBytes, testBytes) {
+			t.Fatalf("GOT:\n%v\n\nWANT:\n%v\n", string(outBytes), string(testBytes))
+		}
+		numEqual++
+	}
+	if numEqual == 0 {
+		t.Fatalf("testDir %s has no golden files *.vdl.go", testDir)
+	}
+}
diff --git a/cmd/vom/doc.go b/cmd/vom/doc.go
new file mode 100644
index 0000000..82a6a30
--- /dev/null
+++ b/cmd/vom/doc.go
@@ -0,0 +1,96 @@
+// 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
+
+/*
+Command vom helps debug the Vanadium Object Marshaling wire protocol.
+
+Usage:
+   vom <command>
+
+The vom commands are:
+   decode      Decode data encoded in the vom format
+   dump        Dump data encoded in the vom format into formatted output
+   help        Display help for commands or topics
+
+The global flags are:
+ -metadata=<just specify -metadata to activate>
+   Displays metadata for the program and exits.
+
+Vom decode - Decode data encoded in the vom format
+
+Decode decodes data encoded in the vom format.  If no arguments are provided,
+decode reads the data from stdin, otherwise the argument is the data.
+
+By default the data is assumed to be represented in hex, with all whitespace
+anywhere in the data ignored.  Use the -data flag to specify other data
+representations.
+
+Usage:
+   vom decode [flags] [data]
+
+[data] is the data to decode; if not specified, reads from stdin
+
+The vom decode flags are:
+ -data=Hex
+   Data representation, one of [Hex Binary]
+
+Vom dump - Dump data encoded in the vom format into formatted output
+
+Dump dumps data encoded in the vom format, generating formatted output
+describing each portion of the encoding.  If no arguments are provided, dump
+reads the data from stdin, otherwise the argument is the data.
+
+By default the data is assumed to be represented in hex, with all whitespace
+anywhere in the data ignored.  Use the -data flag to specify other data
+representations.
+
+Calling "vom dump" with no flags and no arguments combines the default stdin
+mode with the default hex mode.  This default mode is special; certain non-hex
+characters may be input to represent commands:
+  . (period)    Calls Dumper.Status to get the current decoding status.
+  ; (semicolon) Calls Dumper.Flush to flush output and start a new message.
+
+This lets you cut-and-paste hex strings into your terminal, and use the commands
+to trigger status or flush calls; i.e. a rudimentary debugging UI.
+
+See v.io/v23/vom.Dumper for details on the dump output.
+
+Usage:
+   vom dump [flags] [data]
+
+[data] is the data to dump; if not specified, reads from stdin
+
+The vom dump flags are:
+ -data=Hex
+   Data representation, one of [Hex Binary]
+
+Vom help - Display help for commands or topics
+
+Help with no args displays the usage of the parent command.
+
+Help with args displays the usage of the specified sub-command or help topic.
+
+"help ..." recursively displays help for all commands and topics.
+
+Usage:
+   vom help [flags] [command/topic ...]
+
+[command/topic ...] optionally identifies a specific sub-command or help topic.
+
+The vom help flags are:
+ -style=compact
+   The formatting style for help output:
+      compact - Good for compact cmdline output.
+      full    - Good for cmdline output, shows all global flags.
+      godoc   - Good for godoc processing.
+   Override the default by setting the CMDLINE_STYLE environment variable.
+ -width=<terminal width>
+   Format output to this target width in runes, or unlimited if width < 0.
+   Defaults to the terminal width if available.  Override the default by setting
+   the CMDLINE_WIDTH environment variable.
+*/
+package main
diff --git a/cmd/vom/types.vdl b/cmd/vom/types.vdl
new file mode 100644
index 0000000..fc1e191
--- /dev/null
+++ b/cmd/vom/types.vdl
@@ -0,0 +1,10 @@
+// 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
+
+type dataRep enum {
+	Hex
+	Binary
+}
diff --git a/cmd/vom/types.vdl.go b/cmd/vom/types.vdl.go
new file mode 100644
index 0000000..1111b2d
--- /dev/null
+++ b/cmd/vom/types.vdl.go
@@ -0,0 +1,65 @@
+// 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 by the vanadium vdl tool.
+// Source: types.vdl
+
+package main
+
+import (
+	// VDL system imports
+	"fmt"
+	"v.io/v23/vdl"
+)
+
+type dataRep int
+
+const (
+	dataRepHex dataRep = iota
+	dataRepBinary
+)
+
+// dataRepAll holds all labels for dataRep.
+var dataRepAll = [...]dataRep{dataRepHex, dataRepBinary}
+
+// dataRepFromString creates a dataRep from a string label.
+func dataRepFromString(label string) (x dataRep, err error) {
+	err = x.Set(label)
+	return
+}
+
+// Set assigns label to x.
+func (x *dataRep) Set(label string) error {
+	switch label {
+	case "Hex", "hex":
+		*x = dataRepHex
+		return nil
+	case "Binary", "binary":
+		*x = dataRepBinary
+		return nil
+	}
+	*x = -1
+	return fmt.Errorf("unknown label %q in main.dataRep", label)
+}
+
+// String returns the string label of x.
+func (x dataRep) String() string {
+	switch x {
+	case dataRepHex:
+		return "Hex"
+	case dataRepBinary:
+		return "Binary"
+	}
+	return ""
+}
+
+func (dataRep) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/cmd/vom.dataRep"`
+	Enum struct{ Hex, Binary string }
+}) {
+}
+
+func init() {
+	vdl.Register((*dataRep)(nil))
+}
diff --git a/cmd/vom/vom.go b/cmd/vom/vom.go
new file mode 100644
index 0000000..e14a0bd
--- /dev/null
+++ b/cmd/vom/vom.go
@@ -0,0 +1,251 @@
+// 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.
+
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go .
+
+package main
+
+import (
+	"bytes"
+	"encoding/hex"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"strings"
+	"unicode"
+
+	"v.io/v23/vdl"
+	"v.io/v23/vom"
+	"v.io/x/lib/cmdline"
+)
+
+func main() {
+	cmdline.Main(cmdVom)
+}
+
+var cmdVom = &cmdline.Command{
+	Name:  "vom",
+	Short: "helps debug the Vanadium Object Marshaling wire protocol",
+	Long: `
+Command vom helps debug the Vanadium Object Marshaling wire protocol.
+`,
+	Children: []*cmdline.Command{cmdDecode, cmdDump},
+}
+
+var cmdDecode = &cmdline.Command{
+	Runner: cmdline.RunnerFunc(runDecode),
+	Name:   "decode",
+	Short:  "Decode data encoded in the vom format",
+	Long: `
+Decode decodes data encoded in the vom format.  If no arguments are provided,
+decode reads the data from stdin, otherwise the argument is the data.
+
+By default the data is assumed to be represented in hex, with all whitespace
+anywhere in the data ignored.  Use the -data flag to specify other data
+representations.
+
+`,
+	ArgsName: "[data]",
+	ArgsLong: "[data] is the data to decode; if not specified, reads from stdin",
+}
+
+var cmdDump = &cmdline.Command{
+	Runner: cmdline.RunnerFunc(runDump),
+	Name:   "dump",
+	Short:  "Dump data encoded in the vom format into formatted output",
+	Long: `
+Dump dumps data encoded in the vom format, generating formatted output
+describing each portion of the encoding.  If no arguments are provided, dump
+reads the data from stdin, otherwise the argument is the data.
+
+By default the data is assumed to be represented in hex, with all whitespace
+anywhere in the data ignored.  Use the -data flag to specify other data
+representations.
+
+Calling "vom dump" with no flags and no arguments combines the default stdin
+mode with the default hex mode.  This default mode is special; certain non-hex
+characters may be input to represent commands:
+  . (period)    Calls Dumper.Status to get the current decoding status.
+  ; (semicolon) Calls Dumper.Flush to flush output and start a new message.
+
+This lets you cut-and-paste hex strings into your terminal, and use the commands
+to trigger status or flush calls; i.e. a rudimentary debugging UI.
+
+See v.io/v23/vom.Dumper for details on the dump output.
+`,
+	ArgsName: "[data]",
+	ArgsLong: "[data] is the data to dump; if not specified, reads from stdin",
+}
+
+var (
+	flagDataRep = dataRepHex
+)
+
+func init() {
+	cmdDecode.Flags.Var(&flagDataRep, "data",
+		"Data representation, one of "+fmt.Sprint(dataRepAll))
+	cmdDump.Flags.Var(&flagDataRep, "data",
+		"Data representation, one of "+fmt.Sprint(dataRepAll))
+}
+
+func runDecode(env *cmdline.Env, args []string) error {
+	// Convert all inputs into a reader over binary bytes.
+	var data string
+	switch {
+	case len(args) > 1:
+		return env.UsageErrorf("too many args")
+	case len(args) == 1:
+		data = args[0]
+	default:
+		bytes, err := ioutil.ReadAll(os.Stdin)
+		if err != nil {
+			return err
+		}
+		data = string(bytes)
+	}
+	binbytes, err := dataToBinaryBytes(data)
+	if err != nil {
+		return err
+	}
+	reader := bytes.NewBuffer(binbytes)
+	// Decode the binary bytes.
+	// TODO(toddw): Add a flag to set a specific type to decode into.
+	decoder := vom.NewDecoder(reader)
+	var result *vdl.Value
+	if err := decoder.Decode(&result); err != nil {
+		return err
+	}
+	fmt.Fprintln(env.Stdout, result)
+	if reader.Len() != 0 {
+		return fmt.Errorf("%d leftover bytes: % x", reader.Len(), reader.String())
+	}
+	return nil
+}
+
+func runDump(env *cmdline.Env, args []string) error {
+	// Handle non-streaming cases.
+	switch {
+	case len(args) > 1:
+		return env.UsageErrorf("too many args")
+	case len(args) == 1:
+		binbytes, err := dataToBinaryBytes(args[0])
+		if err != nil {
+			return err
+		}
+		fmt.Fprintln(env.Stdout, vom.Dump(binbytes))
+		return nil
+	}
+	// Handle streaming from stdin.
+	dumper := vom.NewDumper(vom.NewDumpWriter(env.Stdout))
+	defer dumper.Close()
+	// Handle simple non-hex cases.
+	switch flagDataRep {
+	case dataRepBinary:
+		_, err := io.Copy(dumper, os.Stdin)
+		return err
+	}
+	return runDumpHexStream(dumper)
+}
+
+// runDumpHexStream handles the hex stdin-streaming special-case, with commands
+// for status and flush.  This is tricky because we need to strip whitespace,
+// handle commands where they appear in the stream, and deal with the fact that
+// it takes two hex characters to encode a single byte.
+//
+// The strategy is to run a ReadLoop that reads into a reasonably-sized buffer.
+// Inside the ReadLoop we take the buffer, strip whitespace, and keep looping to
+// process all data up to each command, and then process the command.  If a
+// command appears in the middle of two hex characters representing a byte, we
+// send the command first, before sending the byte.
+//
+// Any leftover non-command single byte is stored in buf and bufStart is set, so
+// that the next iteration of ReadLoop can read after those bytes.
+func runDumpHexStream(dumper *vom.Dumper) error {
+	buf := make([]byte, 1024)
+	bufStart := 0
+ReadLoop:
+	for {
+		n, err := os.Stdin.Read(buf[bufStart:])
+		switch {
+		case n == 0 && err == io.EOF:
+			return nil
+		case n == 0 && err != nil:
+			return err
+		}
+		// We may have hex interspersed with spaces and commands.  The strategy is
+		// to strip all whitespace, and process each even-sized chunk of hex bytes
+		// up to a command or the end of the buffer.
+		//
+		// Data that appears before a command is written before the command, and
+		// data after the command is written after.  But if a command appears in the
+		// middle of two hex characters representing a byte, we send the command
+		// first, before sending the byte.
+		hexbytes := bytes.Map(dropWhitespace, buf[:bufStart+n])
+		for len(hexbytes) > 0 {
+			end := len(hexbytes)
+			cmdIndex := bytes.IndexAny(hexbytes, ".;")
+			if cmdIndex != -1 {
+				end = cmdIndex
+			} else if end == 1 {
+				// We have a single non-command byte left in hexbytes; copy it into buf
+				// and set bufStart.
+				copy(buf, hexbytes[0:1])
+				bufStart = 1
+				continue ReadLoop
+			}
+			if end%2 == 1 {
+				end -= 1 // Ensure the end is on an even boundary.
+			}
+			// Write this even-sized chunk of hex bytes to the dumper.
+			binbytes, err := hex.DecodeString(string(hexbytes[:end]))
+			if err != nil {
+				return err
+			}
+			if _, err := dumper.Write(binbytes); err != nil {
+				return err
+			}
+			// Handle commands.
+			if cmdIndex != -1 {
+				switch cmd := hexbytes[cmdIndex]; cmd {
+				case '.':
+					dumper.Status()
+				case ';':
+					dumper.Flush()
+				default:
+					return fmt.Errorf("unhandled command %q", cmd)
+				}
+				// Move data after the command forward.
+				copy(hexbytes[cmdIndex:], hexbytes[cmdIndex+1:])
+				hexbytes = hexbytes[:len(hexbytes)-1]
+			}
+			// Move data after the end forward.
+			copy(hexbytes, hexbytes[end:])
+			hexbytes = hexbytes[:len(hexbytes)-end]
+		}
+		bufStart = 0
+	}
+}
+
+func dataToBinaryBytes(data string) ([]byte, error) {
+	// Transform all data representations to binary.
+	switch flagDataRep {
+	case dataRepHex:
+		// Remove all whitespace out of the hex string.
+		binbytes, err := hex.DecodeString(strings.Map(dropWhitespace, data))
+		if err != nil {
+			return nil, err
+		}
+		return binbytes, nil
+	}
+	return []byte(data), nil
+}
+
+func dropWhitespace(r rune) rune {
+	if unicode.IsSpace(r) {
+		return -1
+	}
+	return r
+}
diff --git a/cmd/vomtestgen/doc.go b/cmd/vomtestgen/doc.go
new file mode 100644
index 0000000..7dd15f7
--- /dev/null
+++ b/cmd/vomtestgen/doc.go
@@ -0,0 +1,85 @@
+// 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
+
+/*
+Command vomtestgen generates test data for the vom wire protocol implementation.
+It takes as input a vdl config file, and outputs a vdl package with test cases.
+Test data is generated using this tool to make it easy to add many test cases,
+and verify them in implementations in different languages
+
+Usage:
+   vomtestgen [flags] [vomdata]
+
+[vomdata] is the path to the vomdata input file, specified in the vdl config
+file format.  It must be of the form "NAME.vdl.config", and the output vdl file
+will be generated at "NAME.vdl".
+
+The config file should export a const []any that contains all of the values that
+will be tested.  Here's an example:
+   config = []any{
+     bool(true), uint64(123), string("abc"),
+   }
+
+If not specified, we'll try to find the file at its canonical location:
+   v.io/v23/vom/testdata/vomdata.vdl.config
+
+The vomtestgen flags are:
+ -exts=.vdl
+   Comma-separated list of valid VDL file name extensions.
+ -max-errors=-1
+   Stop processing after this many errors, or -1 for unlimited.
+
+The global flags are:
+ -alsologtostderr=true
+   log to standard error as well as files
+ -log_backtrace_at=:0
+   when logging hits line file:N, emit a stack trace
+ -log_dir=
+   if non-empty, write log files to this directory
+ -logtostderr=false
+   log to standard error instead of files
+ -max_stack_buf_size=4292608
+   max size in bytes of the buffer to use for logging stack traces
+ -metadata=<just specify -metadata to activate>
+   Displays metadata for the program and exits.
+ -stderrthreshold=2
+   logs at or above this threshold go to stderr
+ -v=0
+   log level for V logs
+ -v23.credentials=
+   directory to use for storing security credentials
+ -v23.i18n-catalogue=
+   18n catalogue files to load, comma separated
+ -v23.namespace.root=[/(dev.v.io/role/vprod/service/mounttabled)@ns.dev.v.io:8101]
+   local namespace root; can be repeated to provided multiple roots
+ -v23.permissions.file=map[]
+   specify a perms file as <name>:<permsfile>
+ -v23.permissions.literal=
+   explicitly specify the runtime perms as a JSON-encoded access.Permissions.
+   Overrides all --v23.permissions.file flags.
+ -v23.proxy=
+   object name of proxy service to use to export services across network
+   boundaries
+ -v23.tcp.address=
+   address to listen on
+ -v23.tcp.protocol=wsh
+   protocol to listen with
+ -v23.vtrace.cache-size=1024
+   The number of vtrace traces to store in memory.
+ -v23.vtrace.collect-regexp=
+   Spans and annotations that match this regular expression will trigger trace
+   collection.
+ -v23.vtrace.dump-on-shutdown=true
+   If true, dump all stored traces on runtime shutdown.
+ -v23.vtrace.sample-rate=0
+   Rate (from 0.0 to 1.0) to sample vtrace traces.
+ -vmodule=
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
+*/
+package main
diff --git a/cmd/vomtestgen/generate.go b/cmd/vomtestgen/generate.go
new file mode 100644
index 0000000..19e4ac7
--- /dev/null
+++ b/cmd/vomtestgen/generate.go
@@ -0,0 +1,351 @@
+// 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.
+
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go . -help
+
+package main
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"v.io/v23/vdl"
+	"v.io/v23/vom"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/vdl/build"
+	"v.io/x/ref/lib/vdl/codegen"
+	"v.io/x/ref/lib/vdl/codegen/vdlgen"
+	"v.io/x/ref/lib/vdl/compile"
+	_ "v.io/x/ref/runtime/factories/static"
+)
+
+const (
+	testpkg          = "v.io/v23/vom/testdata"
+	vomdataCanonical = testpkg + "/" + vomdataConfig
+	vomdataConfig    = "vomdata.vdl.config"
+)
+
+var cmdGenerate = &cmdline.Command{
+	Runner: cmdline.RunnerFunc(runGenerate),
+	Name:   "vomtestgen",
+	Short:  "generates test data for the vom wire protocol implementation",
+	Long: `
+Command vomtestgen generates test data for the vom wire protocol implementation.
+It takes as input a vdl config file, and outputs a vdl package with test cases.
+Test data is generated using this tool to make it easy to add many test cases,
+and verify them in implementations in different languages
+`,
+	ArgsName: "[vomdata]",
+	ArgsLong: `
+[vomdata] is the path to the vomdata input file, specified in the vdl config
+file format.  It must be of the form "NAME.vdl.config", and the output vdl
+file will be generated at "NAME.vdl".
+
+The config file should export a const []any that contains all of the values
+that will be tested.  Here's an example:
+   config = []any{
+     bool(true), uint64(123), string("abc"),
+   }
+
+If not specified, we'll try to find the file at its canonical location:
+   ` + vomdataCanonical,
+}
+
+var (
+	optGenMaxErrors int
+	optGenExts      string
+)
+
+func init() {
+	cmdGenerate.Flags.IntVar(&optGenMaxErrors, "max-errors", -1, "Stop processing after this many errors, or -1 for unlimited.")
+	cmdGenerate.Flags.StringVar(&optGenExts, "exts", ".vdl", "Comma-separated list of valid VDL file name extensions.")
+}
+
+func main() {
+	cmdline.Main(cmdGenerate)
+}
+
+func runGenerate(env *cmdline.Env, args []string) error {
+	debug := new(bytes.Buffer)
+	defer dumpDebug(env.Stderr, debug)
+	compileEnv := compile.NewEnv(optGenMaxErrors)
+	// Get the input datafile path.
+	var path string
+	switch len(args) {
+	case 0:
+		srcDirs := build.SrcDirs(compileEnv.Errors)
+		if err := compileEnv.Errors.ToError(); err != nil {
+			return env.UsageErrorf("%v", err)
+		}
+		path = guessDataFilePath(debug, srcDirs)
+		if path == "" {
+			return env.UsageErrorf("couldn't find vomdata file in src dirs: %v", srcDirs)
+		}
+	case 1:
+		path = args[0]
+	default:
+		return env.UsageErrorf("too many args (expecting exactly 1 vomdata file)")
+	}
+	inName := filepath.Clean(path)
+	if !strings.HasSuffix(inName, ".vdl.config") {
+		return env.UsageErrorf(`vomdata file doesn't end in ".vdl.config": %s`, inName)
+	}
+	outName := inName[:len(inName)-len(".config")]
+	// Remove the generated file, so that it doesn't interfere with compiling the
+	// config.  Ignore errors since it might not exist yet.
+	if err := os.Remove(outName); err == nil {
+		fmt.Fprintf(debug, "Removed output file %v\n", outName)
+	}
+	config, err := compileConfig(debug, inName, compileEnv)
+	if err != nil {
+		return err
+	}
+	data, err := generate(config)
+	if err != nil {
+		return err
+	}
+	if err := writeFile(data, outName); err != nil {
+		return err
+	}
+	debug.Reset() // Don't dump debugging information on success
+	fmt.Fprintf(env.Stdout, "Wrote output file %v\n", outName)
+	return nil
+}
+
+func dumpDebug(stderr io.Writer, debug *bytes.Buffer) {
+	if d := debug.Bytes(); len(d) > 0 {
+		io.Copy(stderr, debug)
+	}
+}
+
+func guessDataFilePath(debug io.Writer, srcDirs []string) string {
+	// Try to guess the data file path by looking for the canonical vomdata input
+	// file in each of our source directories.
+	for _, dir := range srcDirs {
+		guess := filepath.Join(dir, vomdataCanonical)
+		if stat, err := os.Stat(guess); err == nil && !stat.IsDir() {
+			fmt.Fprintf(debug, "Found vomdata file %s\n", guess)
+			return guess
+		}
+	}
+	return ""
+}
+
+func compileConfig(debug io.Writer, inName string, env *compile.Env) (*vdl.Value, error) {
+	basename := filepath.Base(inName)
+	data, err := os.Open(inName)
+	if err != nil {
+		return nil, fmt.Errorf("couldn't open vomdata file %s: %v", inName, err)
+	}
+	defer func() { data.Close() }() // make defer use the latest value of data
+	if stat, err := data.Stat(); err != nil || stat.IsDir() {
+		return nil, fmt.Errorf("vomdata file %s is a directory", inName)
+	}
+	var opts build.Opts
+	opts.Extensions = strings.Split(optGenExts, ",")
+	// Compile package dependencies in transitive order.
+	deps := build.TransitivePackagesForConfig(basename, data, opts, env.Errors)
+	for _, dep := range deps {
+		if pkg := build.BuildPackage(dep, env); pkg != nil {
+			fmt.Fprintf(debug, "Built package %s\n", pkg.Path)
+		}
+	}
+	// Try to seek back to the beginning of the data file, or if that fails try to
+	// open the data file again.
+	if off, err := data.Seek(0, 0); off != 0 || err != nil {
+		if err := data.Close(); err != nil {
+			return nil, fmt.Errorf("couldn't close vomdata file %s: %v", inName, err)
+		}
+		data, err = os.Open(inName)
+		if err != nil {
+			return nil, fmt.Errorf("couldn't re-open vomdata file %s: %v", inName, err)
+		}
+	}
+	// Compile config into our test values.
+	config := build.BuildConfig(basename, data, nil, nil, env)
+	if err := env.Errors.ToError(); err != nil {
+		return nil, err
+	}
+	fmt.Fprintf(debug, "Compiled vomdata file %s\n", inName)
+	return config, err
+}
+
+func generate(config *vdl.Value) ([]byte, error) {
+	// This config needs to have a specific struct format. See @testdata/vomtype.vdl.
+	// TODO(alexfandrianto): Instead of this, we should have separate generator
+	// functions that switch off of the vomdata config filename. That way, we can
+	// have 1 config each for encode/decode, compatibility, and convertibility.
+	buf := new(bytes.Buffer)
+	fmt.Fprintf(buf, `// 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 "vomtest generate".
+// DO NOT UPDATE MANUALLY; read the comments in `+vomdataConfig+`.
+
+package testdata
+`)
+	imports := codegen.ImportsForValue(config, testpkg)
+	if len(imports) > 0 {
+		fmt.Fprintf(buf, "\n%s\n", vdlgen.Imports(imports))
+	}
+	fmt.Fprintf(buf, `
+// TestCase represents an individual testcase for vom encoding and decoding.
+type TestCase struct {
+	Name       string // Name of the testcase
+	Value      any    // Value to test
+	TypeString string // The string representation of the Type
+	Hex        string // Hex pattern representing vom encoding
+	HexVersion string // Hex pattern representing vom encoding of Version
+	HexType    string // Hex pattern representing vom encoding of Type
+	HexValue   string // Hex pattern representing vom encoding of Value
+}
+
+// Tests contains the testcases to use to test vom encoding and decoding.
+const Tests = []TestCase {`)
+	// The vom encode-decode test cases need to be of type []any.
+	encodeDecodeTests := config.StructField(0)
+	if got, want := encodeDecodeTests.Type(), vdl.ListType(vdl.AnyType); got != want {
+		return nil, fmt.Errorf("got encodeDecodeTests type %v, want %v", got, want)
+	}
+
+	for ix := 0; ix < encodeDecodeTests.Len(); ix++ {
+		value := encodeDecodeTests.Index(ix)
+		if !value.IsNil() {
+			// The encodeDecodeTests has type []any, and there's no need for our values to
+			// include the "any" type explicitly, so we descend into the elem value.
+			value = value.Elem()
+		}
+		valstr := vdlgen.TypedConst(value, testpkg, imports)
+		hexversion, hextype, hexvalue, vomdump, err := toVomHex(value)
+		if err != nil {
+			return nil, err
+		}
+		fmt.Fprintf(buf, `
+%[7]s
+	{
+		%#[1]q,
+		%[1]s,
+		%[2]q,
+		%[3]q,
+		%[4]q, %[5]q, %[6]q,
+	},`, valstr, value.Type().String(), hexversion+hextype+hexvalue, hexversion, hextype, hexvalue, vomdump)
+	}
+	fmt.Fprintf(buf, `
+}
+`)
+
+	// The vom compatibility tests need to be of type map[string][]typeobject
+	// Each of the []typeobject are a slice of inter-compatible typeobjects.
+	// However, the typeobjects are not compatible with any other []typeobject.
+	// Note: any and optional should be tested separately.
+	compatTests := config.StructField(1)
+	if got, want := compatTests.Type(), vdl.MapType(vdl.StringType, vdl.ListType(vdl.TypeObjectType)); got != want {
+		return nil, fmt.Errorf("got compatTests type %v, want %v", got, want)
+	}
+	fmt.Fprintf(buf, `
+// CompatTests contains the testcases to use to test vom type compatibility.
+// CompatTests maps TestName (string) to CompatibleTypeSet ([]typeobject)
+// Each CompatibleTypeSet contains types compatible with each other. However,
+// types from different CompatibleTypeSets are incompatible.
+const CompatTests = map[string][]typeobject{`)
+
+	for _, testName := range vdl.SortValuesAsString(compatTests.Keys()) {
+		compatibleTypeSet := compatTests.MapIndex(testName)
+		valstr := vdlgen.TypedConst(compatibleTypeSet, testpkg, imports)
+		fmt.Fprintf(buf, `
+	%[1]q: %[2]s,`, testName.RawString(), valstr)
+	}
+	fmt.Fprintf(buf, `
+}
+`)
+
+	// The vom conversion tests need to be a map[string][]ConvertGroup
+	// See vom/testdata/vomtype.vdl
+	convertTests := config.StructField(2)
+	fmt.Fprintf(buf, `
+// ConvertTests contains the testcases to check vom value convertibility.
+// ConvertTests maps TestName (string) to ConvertGroups ([]ConvertGroup)
+// Each ConvertGroup is a struct with 'Name', 'PrimaryType', and 'Values'.
+// The values within a ConvertGroup can convert between themselves w/o error.
+// However, values in higher-indexed ConvertGroups will error when converting up
+// to the primary type of the lower-indexed ConvertGroups.
+const ConvertTests = map[string][]ConvertGroup{`)
+	for _, testName := range vdl.SortValuesAsString(convertTests.Keys()) {
+		fmt.Fprintf(buf, `
+	%[1]q: {`, testName.RawString())
+
+		convertTest := convertTests.MapIndex(testName)
+		for ix := 0; ix < convertTest.Len(); ix++ {
+			convertGroup := convertTest.Index(ix)
+
+			fmt.Fprintf(buf, `
+		{
+			%[1]q,
+			%[2]s,
+			{ `, convertGroup.StructField(0).RawString(), vdlgen.TypedConst(convertGroup.StructField(1), testpkg, imports))
+
+			values := convertGroup.StructField(2)
+			for iy := 0; iy < values.Len(); iy++ {
+				value := values.Index(iy)
+				if !value.IsNil() {
+					// The value is any, and there's no need for our values to include
+					// the "any" type explicitly, so we descend into the elem value.
+					value = value.Elem()
+				}
+				valstr := vdlgen.TypedConst(value, testpkg, imports)
+				fmt.Fprintf(buf, `%[1]s, `, valstr)
+			}
+
+			fmt.Fprintf(buf, `},
+		},`)
+		}
+
+		fmt.Fprintf(buf, `
+	},`)
+	}
+	fmt.Fprintf(buf, `
+}
+`)
+
+	return buf.Bytes(), nil
+}
+
+func toVomHex(value *vdl.Value) (string, string, string, string, error) {
+	var buf, typebuf bytes.Buffer
+	encoder := vom.NewEncoderWithTypeEncoder(&buf, vom.NewTypeEncoder(&typebuf))
+	if err := encoder.Encode(value); err != nil {
+		return "", "", "", "", fmt.Errorf("vom.Encode(%v) failed: %v", value, err)
+	}
+	version, _ := buf.ReadByte() // Read the version byte.
+	if typebuf.Len() > 0 {
+		typebuf.ReadByte() // Remove the version byte.
+	}
+	vombytes := append(append([]byte{version}, typebuf.Bytes()...), buf.Bytes()...)
+	const pre = "\t// "
+	vomdump := pre + strings.Replace(vom.Dump(vombytes), "\n", "\n"+pre, -1)
+	if strings.HasSuffix(vomdump, "\n"+pre) {
+		vomdump = vomdump[:len(vomdump)-len("\n"+pre)]
+	}
+	// TODO(toddw): Add hex pattern bracketing for map and set.
+	return fmt.Sprintf("%x", version), fmt.Sprintf("%x", typebuf.Bytes()), fmt.Sprintf("%x", buf.Bytes()), vomdump, nil
+}
+
+func writeFile(data []byte, outName string) error {
+	// Create containing directory and write the file.
+	dirName := filepath.Dir(outName)
+	if err := os.MkdirAll(dirName, os.FileMode(0777)); err != nil {
+		return fmt.Errorf("couldn't create directory %s: %v", dirName, err)
+	}
+	if err := ioutil.WriteFile(outName, data, os.FileMode(0666)); err != nil {
+		return fmt.Errorf("couldn't write file %s: %v", outName, err)
+	}
+	return nil
+}
diff --git a/cmd/vrpc/doc.go b/cmd/vrpc/doc.go
new file mode 100644
index 0000000..ff84dc9
--- /dev/null
+++ b/cmd/vrpc/doc.go
@@ -0,0 +1,161 @@
+// 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
+
+/*
+Command vrpc sends and receives Vanadium remote procedure calls.  It is used as
+a generic client to interact with any Vanadium server.
+
+Usage:
+   vrpc <command>
+
+The vrpc commands are:
+   signature   Describe the interfaces of a Vanadium server
+   call        Call a method of a Vanadium server
+   identify    Reveal blessings presented by a Vanadium server
+   help        Display help for commands or topics
+
+The global flags are:
+ -v23.namespace.root=[/(dev.v.io/role/vprod/service/mounttabled)@ns.dev.v.io:8101]
+   local namespace root; can be repeated to provided multiple roots
+
+ -alsologtostderr=true
+   log to standard error as well as files
+ -log_backtrace_at=:0
+   when logging hits line file:N, emit a stack trace
+ -log_dir=
+   if non-empty, write log files to this directory
+ -logtostderr=false
+   log to standard error instead of files
+ -max_stack_buf_size=4292608
+   max size in bytes of the buffer to use for logging stack traces
+ -metadata=<just specify -metadata to activate>
+   Displays metadata for the program and exits.
+ -stderrthreshold=2
+   logs at or above this threshold go to stderr
+ -v=0
+   log level for V logs
+ -v23.credentials=
+   directory to use for storing security credentials
+ -v23.i18n-catalogue=
+   18n catalogue files to load, comma separated
+ -v23.proxy=
+   object name of proxy service to use to export services across network
+   boundaries
+ -v23.tcp.address=
+   address to listen on
+ -v23.tcp.protocol=wsh
+   protocol to listen with
+ -v23.vtrace.cache-size=1024
+   The number of vtrace traces to store in memory.
+ -v23.vtrace.collect-regexp=
+   Spans and annotations that match this regular expression will trigger trace
+   collection.
+ -v23.vtrace.dump-on-shutdown=true
+   If true, dump all stored traces on runtime shutdown.
+ -v23.vtrace.sample-rate=0
+   Rate (from 0.0 to 1.0) to sample vtrace traces.
+ -vmodule=
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
+
+Vrpc signature - Describe the interfaces of a Vanadium server
+
+Signature connects to the Vanadium server identified by <server>.
+
+If no [method] is provided, returns all interfaces implemented by the server.
+
+If a [method] is provided, returns the signature of just that method.
+
+Usage:
+   vrpc signature [flags] <server> [method]
+
+<server> identifies a Vanadium server.  It can either be the object address of
+the server, or an object name that will be resolved to an end-point.
+
+[method] is the optional server method name.
+
+The vrpc signature flags are:
+ -insecure=false
+   If true, skip server authentication. This means that the client will reveal
+   its blessings to servers that it may not recognize.
+ -show-reserved=false
+   if true, also show the signatures of reserved methods
+
+Vrpc call - Call a method of a Vanadium server
+
+Call connects to the Vanadium server identified by <server> and calls the
+<method> with the given positional [args...], returning results on stdout.
+
+TODO(toddw): stdin is read for streaming arguments sent to the server.  An EOF
+on stdin (e.g. via ^D) causes the send stream to be closed.
+
+Regardless of whether the call is streaming, the main goroutine blocks for
+streaming and positional results received from the server.
+
+All input arguments (both positional and streaming) are specified as VDL
+expressions, with commas separating multiple expressions.  Positional arguments
+may also be specified as separate command-line arguments.  Streaming arguments
+may also be specified as separate newline-terminated expressions.
+
+The method signature is always retrieved from the server as a first step.  This
+makes it easier to input complex typed arguments, since the top-level type for
+each argument is implicit and doesn't need to be specified.
+
+Usage:
+   vrpc call <server> <method> [args...]
+
+<server> identifies a Vanadium server.  It can either be the object address of
+the server, or an object name that will be resolved to an end-point.
+
+<method> is the server method to call.
+
+[args...] are the positional input arguments, specified as VDL expressions.
+
+Vrpc identify - Reveal blessings presented by a Vanadium server
+
+Identify connects to the Vanadium server identified by <server> and dumps out
+the blessings presented by that server (and the subset of those that are
+considered valid by the principal running this tool) to standard output.
+
+Usage:
+   vrpc identify [flags] <server>
+
+<server> identifies a Vanadium server.  It can either be the object address of
+the server, or an object name that will be resolved to an end-point.
+
+The vrpc identify flags are:
+ -insecure=false
+   If true, skip server authentication. This means that the client will reveal
+   its blessings to servers that it may not recognize.
+
+Vrpc help - Display help for commands or topics
+
+Help with no args displays the usage of the parent command.
+
+Help with args displays the usage of the specified sub-command or help topic.
+
+"help ..." recursively displays help for all commands and topics.
+
+Usage:
+   vrpc help [flags] [command/topic ...]
+
+[command/topic ...] optionally identifies a specific sub-command or help topic.
+
+The vrpc help flags are:
+ -style=compact
+   The formatting style for help output:
+      compact - Good for compact cmdline output.
+      full    - Good for cmdline output, shows all global flags.
+      godoc   - Good for godoc processing.
+   Override the default by setting the CMDLINE_STYLE environment variable.
+ -width=<terminal width>
+   Format output to this target width in runes, or unlimited if width < 0.
+   Defaults to the terminal width if available.  Override the default by setting
+   the CMDLINE_WIDTH environment variable.
+*/
+package main
diff --git a/cmd/vrpc/internal/test_base.vdl b/cmd/vrpc/internal/test_base.vdl
new file mode 100644
index 0000000..c26042d
--- /dev/null
+++ b/cmd/vrpc/internal/test_base.vdl
@@ -0,0 +1,40 @@
+// 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
+
+type Struct struct {
+  X,Y int32
+}
+
+type Array2Int [2]int32
+
+// TypeTester methods are listed in alphabetical order, to make it easier to
+// test Signature output, which sorts methods alphabetically.
+type TypeTester interface {
+	// Methods to test support for primitive types.
+	EchoBool(I1 bool) (O1 bool | error)
+	EchoFloat32(I1 float32) (O1 float32 | error)
+	EchoFloat64(I1 float64) (O1 float64 | error)
+	EchoInt32(I1 int32) (O1 int32 | error)
+	EchoInt64(I1 int64) (O1 int64 | error)
+	EchoString(I1 string) (O1 string | error)
+	EchoByte(I1 byte) (O1 byte | error)
+	EchoUint32(I1 uint32) (O1 uint32 | error)
+	EchoUint64(I1 uint64) (O1 uint64 | error)
+
+	// Methods to test support for composite types.
+	XEchoArray(I1 Array2Int) (O1 Array2Int | error)
+	XEchoMap(I1 map[int32]string) (O1 map[int32]string | error)
+	XEchoSet(I1 set[int32]) (O1 set[int32] | error)
+	XEchoSlice(I1 []int32) (O1 []int32 | error)
+	XEchoStruct(I1 Struct) (O1 Struct | error)
+
+	// Methods to test support for different number of arguments.
+	YMultiArg(I1, I2 int32) (O1, O2 int32 | error)
+	YNoArgs() error
+
+	// Methods to test support for streaming.
+	ZStream(NumStreamItems int32, StreamItem bool) stream<_, bool> error
+}
diff --git a/cmd/vrpc/internal/test_base.vdl.go b/cmd/vrpc/internal/test_base.vdl.go
new file mode 100644
index 0000000..c1dc0dc
--- /dev/null
+++ b/cmd/vrpc/internal/test_base.vdl.go
@@ -0,0 +1,608 @@
+// 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 by the vanadium vdl tool.
+// Source: test_base.vdl
+
+package internal
+
+import (
+	// VDL system imports
+	"io"
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/vdl"
+)
+
+type Struct struct {
+	X int32
+	Y int32
+}
+
+func (Struct) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/cmd/vrpc/internal.Struct"`
+}) {
+}
+
+type Array2Int [2]int32
+
+func (Array2Int) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/cmd/vrpc/internal.Array2Int"`
+}) {
+}
+
+func init() {
+	vdl.Register((*Struct)(nil))
+	vdl.Register((*Array2Int)(nil))
+}
+
+// TypeTesterClientMethods is the client interface
+// containing TypeTester methods.
+//
+// TypeTester methods are listed in alphabetical order, to make it easier to
+// test Signature output, which sorts methods alphabetically.
+type TypeTesterClientMethods interface {
+	// Methods to test support for primitive types.
+	EchoBool(ctx *context.T, I1 bool, opts ...rpc.CallOpt) (O1 bool, err error)
+	EchoFloat32(ctx *context.T, I1 float32, opts ...rpc.CallOpt) (O1 float32, err error)
+	EchoFloat64(ctx *context.T, I1 float64, opts ...rpc.CallOpt) (O1 float64, err error)
+	EchoInt32(ctx *context.T, I1 int32, opts ...rpc.CallOpt) (O1 int32, err error)
+	EchoInt64(ctx *context.T, I1 int64, opts ...rpc.CallOpt) (O1 int64, err error)
+	EchoString(ctx *context.T, I1 string, opts ...rpc.CallOpt) (O1 string, err error)
+	EchoByte(ctx *context.T, I1 byte, opts ...rpc.CallOpt) (O1 byte, err error)
+	EchoUint32(ctx *context.T, I1 uint32, opts ...rpc.CallOpt) (O1 uint32, err error)
+	EchoUint64(ctx *context.T, I1 uint64, opts ...rpc.CallOpt) (O1 uint64, err error)
+	// Methods to test support for composite types.
+	XEchoArray(ctx *context.T, I1 Array2Int, opts ...rpc.CallOpt) (O1 Array2Int, err error)
+	XEchoMap(ctx *context.T, I1 map[int32]string, opts ...rpc.CallOpt) (O1 map[int32]string, err error)
+	XEchoSet(ctx *context.T, I1 map[int32]struct{}, opts ...rpc.CallOpt) (O1 map[int32]struct{}, err error)
+	XEchoSlice(ctx *context.T, I1 []int32, opts ...rpc.CallOpt) (O1 []int32, err error)
+	XEchoStruct(ctx *context.T, I1 Struct, opts ...rpc.CallOpt) (O1 Struct, err error)
+	// Methods to test support for different number of arguments.
+	YMultiArg(ctx *context.T, I1 int32, I2 int32, opts ...rpc.CallOpt) (O1 int32, O2 int32, err error)
+	YNoArgs(*context.T, ...rpc.CallOpt) error
+	// Methods to test support for streaming.
+	ZStream(ctx *context.T, NumStreamItems int32, StreamItem bool, opts ...rpc.CallOpt) (TypeTesterZStreamClientCall, error)
+}
+
+// TypeTesterClientStub adds universal methods to TypeTesterClientMethods.
+type TypeTesterClientStub interface {
+	TypeTesterClientMethods
+	rpc.UniversalServiceMethods
+}
+
+// TypeTesterClient returns a client stub for TypeTester.
+func TypeTesterClient(name string) TypeTesterClientStub {
+	return implTypeTesterClientStub{name}
+}
+
+type implTypeTesterClientStub struct {
+	name string
+}
+
+func (c implTypeTesterClientStub) EchoBool(ctx *context.T, i0 bool, opts ...rpc.CallOpt) (o0 bool, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "EchoBool", []interface{}{i0}, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implTypeTesterClientStub) EchoFloat32(ctx *context.T, i0 float32, opts ...rpc.CallOpt) (o0 float32, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "EchoFloat32", []interface{}{i0}, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implTypeTesterClientStub) EchoFloat64(ctx *context.T, i0 float64, opts ...rpc.CallOpt) (o0 float64, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "EchoFloat64", []interface{}{i0}, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implTypeTesterClientStub) EchoInt32(ctx *context.T, i0 int32, opts ...rpc.CallOpt) (o0 int32, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "EchoInt32", []interface{}{i0}, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implTypeTesterClientStub) EchoInt64(ctx *context.T, i0 int64, opts ...rpc.CallOpt) (o0 int64, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "EchoInt64", []interface{}{i0}, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implTypeTesterClientStub) EchoString(ctx *context.T, i0 string, opts ...rpc.CallOpt) (o0 string, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "EchoString", []interface{}{i0}, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implTypeTesterClientStub) EchoByte(ctx *context.T, i0 byte, opts ...rpc.CallOpt) (o0 byte, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "EchoByte", []interface{}{i0}, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implTypeTesterClientStub) EchoUint32(ctx *context.T, i0 uint32, opts ...rpc.CallOpt) (o0 uint32, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "EchoUint32", []interface{}{i0}, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implTypeTesterClientStub) EchoUint64(ctx *context.T, i0 uint64, opts ...rpc.CallOpt) (o0 uint64, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "EchoUint64", []interface{}{i0}, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implTypeTesterClientStub) XEchoArray(ctx *context.T, i0 Array2Int, opts ...rpc.CallOpt) (o0 Array2Int, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "XEchoArray", []interface{}{i0}, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implTypeTesterClientStub) XEchoMap(ctx *context.T, i0 map[int32]string, opts ...rpc.CallOpt) (o0 map[int32]string, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "XEchoMap", []interface{}{i0}, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implTypeTesterClientStub) XEchoSet(ctx *context.T, i0 map[int32]struct{}, opts ...rpc.CallOpt) (o0 map[int32]struct{}, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "XEchoSet", []interface{}{i0}, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implTypeTesterClientStub) XEchoSlice(ctx *context.T, i0 []int32, opts ...rpc.CallOpt) (o0 []int32, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "XEchoSlice", []interface{}{i0}, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implTypeTesterClientStub) XEchoStruct(ctx *context.T, i0 Struct, opts ...rpc.CallOpt) (o0 Struct, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "XEchoStruct", []interface{}{i0}, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implTypeTesterClientStub) YMultiArg(ctx *context.T, i0 int32, i1 int32, opts ...rpc.CallOpt) (o0 int32, o1 int32, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "YMultiArg", []interface{}{i0, i1}, []interface{}{&o0, &o1}, opts...)
+	return
+}
+
+func (c implTypeTesterClientStub) YNoArgs(ctx *context.T, opts ...rpc.CallOpt) (err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "YNoArgs", nil, nil, opts...)
+	return
+}
+
+func (c implTypeTesterClientStub) ZStream(ctx *context.T, i0 int32, i1 bool, opts ...rpc.CallOpt) (ocall TypeTesterZStreamClientCall, err error) {
+	var call rpc.ClientCall
+	if call, err = v23.GetClient(ctx).StartCall(ctx, c.name, "ZStream", []interface{}{i0, i1}, opts...); err != nil {
+		return
+	}
+	ocall = &implTypeTesterZStreamClientCall{ClientCall: call}
+	return
+}
+
+// TypeTesterZStreamClientStream is the client stream for TypeTester.ZStream.
+type TypeTesterZStreamClientStream interface {
+	// RecvStream returns the receiver side of the TypeTester.ZStream client stream.
+	RecvStream() interface {
+		// Advance stages an item so that it may be retrieved via Value.  Returns
+		// true iff there is an item to retrieve.  Advance must be called before
+		// Value is called.  May block if an item is not available.
+		Advance() bool
+		// Value returns the item that was staged by Advance.  May panic if Advance
+		// returned false or was not called.  Never blocks.
+		Value() bool
+		// Err returns any error encountered by Advance.  Never blocks.
+		Err() error
+	}
+}
+
+// TypeTesterZStreamClientCall represents the call returned from TypeTester.ZStream.
+type TypeTesterZStreamClientCall interface {
+	TypeTesterZStreamClientStream
+	// Finish blocks until the server is done, and returns the positional return
+	// values for call.
+	//
+	// Finish returns immediately if the call has been canceled; depending on the
+	// timing the output could either be an error signaling cancelation, or the
+	// valid positional return values from the server.
+	//
+	// Calling Finish is mandatory for releasing stream resources, unless the call
+	// has been canceled or any of the other methods return an error.  Finish should
+	// be called at most once.
+	Finish() error
+}
+
+type implTypeTesterZStreamClientCall struct {
+	rpc.ClientCall
+	valRecv bool
+	errRecv error
+}
+
+func (c *implTypeTesterZStreamClientCall) RecvStream() interface {
+	Advance() bool
+	Value() bool
+	Err() error
+} {
+	return implTypeTesterZStreamClientCallRecv{c}
+}
+
+type implTypeTesterZStreamClientCallRecv struct {
+	c *implTypeTesterZStreamClientCall
+}
+
+func (c implTypeTesterZStreamClientCallRecv) Advance() bool {
+	c.c.errRecv = c.c.Recv(&c.c.valRecv)
+	return c.c.errRecv == nil
+}
+func (c implTypeTesterZStreamClientCallRecv) Value() bool {
+	return c.c.valRecv
+}
+func (c implTypeTesterZStreamClientCallRecv) Err() error {
+	if c.c.errRecv == io.EOF {
+		return nil
+	}
+	return c.c.errRecv
+}
+func (c *implTypeTesterZStreamClientCall) Finish() (err error) {
+	err = c.ClientCall.Finish()
+	return
+}
+
+// TypeTesterServerMethods is the interface a server writer
+// implements for TypeTester.
+//
+// TypeTester methods are listed in alphabetical order, to make it easier to
+// test Signature output, which sorts methods alphabetically.
+type TypeTesterServerMethods interface {
+	// Methods to test support for primitive types.
+	EchoBool(ctx *context.T, call rpc.ServerCall, I1 bool) (O1 bool, err error)
+	EchoFloat32(ctx *context.T, call rpc.ServerCall, I1 float32) (O1 float32, err error)
+	EchoFloat64(ctx *context.T, call rpc.ServerCall, I1 float64) (O1 float64, err error)
+	EchoInt32(ctx *context.T, call rpc.ServerCall, I1 int32) (O1 int32, err error)
+	EchoInt64(ctx *context.T, call rpc.ServerCall, I1 int64) (O1 int64, err error)
+	EchoString(ctx *context.T, call rpc.ServerCall, I1 string) (O1 string, err error)
+	EchoByte(ctx *context.T, call rpc.ServerCall, I1 byte) (O1 byte, err error)
+	EchoUint32(ctx *context.T, call rpc.ServerCall, I1 uint32) (O1 uint32, err error)
+	EchoUint64(ctx *context.T, call rpc.ServerCall, I1 uint64) (O1 uint64, err error)
+	// Methods to test support for composite types.
+	XEchoArray(ctx *context.T, call rpc.ServerCall, I1 Array2Int) (O1 Array2Int, err error)
+	XEchoMap(ctx *context.T, call rpc.ServerCall, I1 map[int32]string) (O1 map[int32]string, err error)
+	XEchoSet(ctx *context.T, call rpc.ServerCall, I1 map[int32]struct{}) (O1 map[int32]struct{}, err error)
+	XEchoSlice(ctx *context.T, call rpc.ServerCall, I1 []int32) (O1 []int32, err error)
+	XEchoStruct(ctx *context.T, call rpc.ServerCall, I1 Struct) (O1 Struct, err error)
+	// Methods to test support for different number of arguments.
+	YMultiArg(ctx *context.T, call rpc.ServerCall, I1 int32, I2 int32) (O1 int32, O2 int32, err error)
+	YNoArgs(*context.T, rpc.ServerCall) error
+	// Methods to test support for streaming.
+	ZStream(ctx *context.T, call TypeTesterZStreamServerCall, NumStreamItems int32, StreamItem bool) error
+}
+
+// TypeTesterServerStubMethods is the server interface containing
+// TypeTester methods, as expected by rpc.Server.
+// The only difference between this interface and TypeTesterServerMethods
+// is the streaming methods.
+type TypeTesterServerStubMethods interface {
+	// Methods to test support for primitive types.
+	EchoBool(ctx *context.T, call rpc.ServerCall, I1 bool) (O1 bool, err error)
+	EchoFloat32(ctx *context.T, call rpc.ServerCall, I1 float32) (O1 float32, err error)
+	EchoFloat64(ctx *context.T, call rpc.ServerCall, I1 float64) (O1 float64, err error)
+	EchoInt32(ctx *context.T, call rpc.ServerCall, I1 int32) (O1 int32, err error)
+	EchoInt64(ctx *context.T, call rpc.ServerCall, I1 int64) (O1 int64, err error)
+	EchoString(ctx *context.T, call rpc.ServerCall, I1 string) (O1 string, err error)
+	EchoByte(ctx *context.T, call rpc.ServerCall, I1 byte) (O1 byte, err error)
+	EchoUint32(ctx *context.T, call rpc.ServerCall, I1 uint32) (O1 uint32, err error)
+	EchoUint64(ctx *context.T, call rpc.ServerCall, I1 uint64) (O1 uint64, err error)
+	// Methods to test support for composite types.
+	XEchoArray(ctx *context.T, call rpc.ServerCall, I1 Array2Int) (O1 Array2Int, err error)
+	XEchoMap(ctx *context.T, call rpc.ServerCall, I1 map[int32]string) (O1 map[int32]string, err error)
+	XEchoSet(ctx *context.T, call rpc.ServerCall, I1 map[int32]struct{}) (O1 map[int32]struct{}, err error)
+	XEchoSlice(ctx *context.T, call rpc.ServerCall, I1 []int32) (O1 []int32, err error)
+	XEchoStruct(ctx *context.T, call rpc.ServerCall, I1 Struct) (O1 Struct, err error)
+	// Methods to test support for different number of arguments.
+	YMultiArg(ctx *context.T, call rpc.ServerCall, I1 int32, I2 int32) (O1 int32, O2 int32, err error)
+	YNoArgs(*context.T, rpc.ServerCall) error
+	// Methods to test support for streaming.
+	ZStream(ctx *context.T, call *TypeTesterZStreamServerCallStub, NumStreamItems int32, StreamItem bool) error
+}
+
+// TypeTesterServerStub adds universal methods to TypeTesterServerStubMethods.
+type TypeTesterServerStub interface {
+	TypeTesterServerStubMethods
+	// Describe the TypeTester interfaces.
+	Describe__() []rpc.InterfaceDesc
+}
+
+// TypeTesterServer returns a server stub for TypeTester.
+// It converts an implementation of TypeTesterServerMethods into
+// an object that may be used by rpc.Server.
+func TypeTesterServer(impl TypeTesterServerMethods) TypeTesterServerStub {
+	stub := implTypeTesterServerStub{
+		impl: impl,
+	}
+	// Initialize GlobState; always check the stub itself first, to handle the
+	// case where the user has the Glob method defined in their VDL source.
+	if gs := rpc.NewGlobState(stub); gs != nil {
+		stub.gs = gs
+	} else if gs := rpc.NewGlobState(impl); gs != nil {
+		stub.gs = gs
+	}
+	return stub
+}
+
+type implTypeTesterServerStub struct {
+	impl TypeTesterServerMethods
+	gs   *rpc.GlobState
+}
+
+func (s implTypeTesterServerStub) EchoBool(ctx *context.T, call rpc.ServerCall, i0 bool) (bool, error) {
+	return s.impl.EchoBool(ctx, call, i0)
+}
+
+func (s implTypeTesterServerStub) EchoFloat32(ctx *context.T, call rpc.ServerCall, i0 float32) (float32, error) {
+	return s.impl.EchoFloat32(ctx, call, i0)
+}
+
+func (s implTypeTesterServerStub) EchoFloat64(ctx *context.T, call rpc.ServerCall, i0 float64) (float64, error) {
+	return s.impl.EchoFloat64(ctx, call, i0)
+}
+
+func (s implTypeTesterServerStub) EchoInt32(ctx *context.T, call rpc.ServerCall, i0 int32) (int32, error) {
+	return s.impl.EchoInt32(ctx, call, i0)
+}
+
+func (s implTypeTesterServerStub) EchoInt64(ctx *context.T, call rpc.ServerCall, i0 int64) (int64, error) {
+	return s.impl.EchoInt64(ctx, call, i0)
+}
+
+func (s implTypeTesterServerStub) EchoString(ctx *context.T, call rpc.ServerCall, i0 string) (string, error) {
+	return s.impl.EchoString(ctx, call, i0)
+}
+
+func (s implTypeTesterServerStub) EchoByte(ctx *context.T, call rpc.ServerCall, i0 byte) (byte, error) {
+	return s.impl.EchoByte(ctx, call, i0)
+}
+
+func (s implTypeTesterServerStub) EchoUint32(ctx *context.T, call rpc.ServerCall, i0 uint32) (uint32, error) {
+	return s.impl.EchoUint32(ctx, call, i0)
+}
+
+func (s implTypeTesterServerStub) EchoUint64(ctx *context.T, call rpc.ServerCall, i0 uint64) (uint64, error) {
+	return s.impl.EchoUint64(ctx, call, i0)
+}
+
+func (s implTypeTesterServerStub) XEchoArray(ctx *context.T, call rpc.ServerCall, i0 Array2Int) (Array2Int, error) {
+	return s.impl.XEchoArray(ctx, call, i0)
+}
+
+func (s implTypeTesterServerStub) XEchoMap(ctx *context.T, call rpc.ServerCall, i0 map[int32]string) (map[int32]string, error) {
+	return s.impl.XEchoMap(ctx, call, i0)
+}
+
+func (s implTypeTesterServerStub) XEchoSet(ctx *context.T, call rpc.ServerCall, i0 map[int32]struct{}) (map[int32]struct{}, error) {
+	return s.impl.XEchoSet(ctx, call, i0)
+}
+
+func (s implTypeTesterServerStub) XEchoSlice(ctx *context.T, call rpc.ServerCall, i0 []int32) ([]int32, error) {
+	return s.impl.XEchoSlice(ctx, call, i0)
+}
+
+func (s implTypeTesterServerStub) XEchoStruct(ctx *context.T, call rpc.ServerCall, i0 Struct) (Struct, error) {
+	return s.impl.XEchoStruct(ctx, call, i0)
+}
+
+func (s implTypeTesterServerStub) YMultiArg(ctx *context.T, call rpc.ServerCall, i0 int32, i1 int32) (int32, int32, error) {
+	return s.impl.YMultiArg(ctx, call, i0, i1)
+}
+
+func (s implTypeTesterServerStub) YNoArgs(ctx *context.T, call rpc.ServerCall) error {
+	return s.impl.YNoArgs(ctx, call)
+}
+
+func (s implTypeTesterServerStub) ZStream(ctx *context.T, call *TypeTesterZStreamServerCallStub, i0 int32, i1 bool) error {
+	return s.impl.ZStream(ctx, call, i0, i1)
+}
+
+func (s implTypeTesterServerStub) Globber() *rpc.GlobState {
+	return s.gs
+}
+
+func (s implTypeTesterServerStub) Describe__() []rpc.InterfaceDesc {
+	return []rpc.InterfaceDesc{TypeTesterDesc}
+}
+
+// TypeTesterDesc describes the TypeTester interface.
+var TypeTesterDesc rpc.InterfaceDesc = descTypeTester
+
+// descTypeTester hides the desc to keep godoc clean.
+var descTypeTester = rpc.InterfaceDesc{
+	Name:    "TypeTester",
+	PkgPath: "v.io/x/ref/cmd/vrpc/internal",
+	Doc:     "// TypeTester methods are listed in alphabetical order, to make it easier to\n// test Signature output, which sorts methods alphabetically.",
+	Methods: []rpc.MethodDesc{
+		{
+			Name: "EchoBool",
+			Doc:  "// Methods to test support for primitive types.",
+			InArgs: []rpc.ArgDesc{
+				{"I1", ``}, // bool
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"O1", ``}, // bool
+			},
+		},
+		{
+			Name: "EchoFloat32",
+			InArgs: []rpc.ArgDesc{
+				{"I1", ``}, // float32
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"O1", ``}, // float32
+			},
+		},
+		{
+			Name: "EchoFloat64",
+			InArgs: []rpc.ArgDesc{
+				{"I1", ``}, // float64
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"O1", ``}, // float64
+			},
+		},
+		{
+			Name: "EchoInt32",
+			InArgs: []rpc.ArgDesc{
+				{"I1", ``}, // int32
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"O1", ``}, // int32
+			},
+		},
+		{
+			Name: "EchoInt64",
+			InArgs: []rpc.ArgDesc{
+				{"I1", ``}, // int64
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"O1", ``}, // int64
+			},
+		},
+		{
+			Name: "EchoString",
+			InArgs: []rpc.ArgDesc{
+				{"I1", ``}, // string
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"O1", ``}, // string
+			},
+		},
+		{
+			Name: "EchoByte",
+			InArgs: []rpc.ArgDesc{
+				{"I1", ``}, // byte
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"O1", ``}, // byte
+			},
+		},
+		{
+			Name: "EchoUint32",
+			InArgs: []rpc.ArgDesc{
+				{"I1", ``}, // uint32
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"O1", ``}, // uint32
+			},
+		},
+		{
+			Name: "EchoUint64",
+			InArgs: []rpc.ArgDesc{
+				{"I1", ``}, // uint64
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"O1", ``}, // uint64
+			},
+		},
+		{
+			Name: "XEchoArray",
+			Doc:  "// Methods to test support for composite types.",
+			InArgs: []rpc.ArgDesc{
+				{"I1", ``}, // Array2Int
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"O1", ``}, // Array2Int
+			},
+		},
+		{
+			Name: "XEchoMap",
+			InArgs: []rpc.ArgDesc{
+				{"I1", ``}, // map[int32]string
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"O1", ``}, // map[int32]string
+			},
+		},
+		{
+			Name: "XEchoSet",
+			InArgs: []rpc.ArgDesc{
+				{"I1", ``}, // map[int32]struct{}
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"O1", ``}, // map[int32]struct{}
+			},
+		},
+		{
+			Name: "XEchoSlice",
+			InArgs: []rpc.ArgDesc{
+				{"I1", ``}, // []int32
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"O1", ``}, // []int32
+			},
+		},
+		{
+			Name: "XEchoStruct",
+			InArgs: []rpc.ArgDesc{
+				{"I1", ``}, // Struct
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"O1", ``}, // Struct
+			},
+		},
+		{
+			Name: "YMultiArg",
+			Doc:  "// Methods to test support for different number of arguments.",
+			InArgs: []rpc.ArgDesc{
+				{"I1", ``}, // int32
+				{"I2", ``}, // int32
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"O1", ``}, // int32
+				{"O2", ``}, // int32
+			},
+		},
+		{
+			Name: "YNoArgs",
+		},
+		{
+			Name: "ZStream",
+			Doc:  "// Methods to test support for streaming.",
+			InArgs: []rpc.ArgDesc{
+				{"NumStreamItems", ``}, // int32
+				{"StreamItem", ``},     // bool
+			},
+		},
+	},
+}
+
+// TypeTesterZStreamServerStream is the server stream for TypeTester.ZStream.
+type TypeTesterZStreamServerStream interface {
+	// SendStream returns the send side of the TypeTester.ZStream server stream.
+	SendStream() interface {
+		// Send places the item onto the output stream.  Returns errors encountered
+		// while sending.  Blocks if there is no buffer space; will unblock when
+		// buffer space is available.
+		Send(item bool) error
+	}
+}
+
+// TypeTesterZStreamServerCall represents the context passed to TypeTester.ZStream.
+type TypeTesterZStreamServerCall interface {
+	rpc.ServerCall
+	TypeTesterZStreamServerStream
+}
+
+// TypeTesterZStreamServerCallStub is a wrapper that converts rpc.StreamServerCall into
+// a typesafe stub that implements TypeTesterZStreamServerCall.
+type TypeTesterZStreamServerCallStub struct {
+	rpc.StreamServerCall
+}
+
+// Init initializes TypeTesterZStreamServerCallStub from rpc.StreamServerCall.
+func (s *TypeTesterZStreamServerCallStub) Init(call rpc.StreamServerCall) {
+	s.StreamServerCall = call
+}
+
+// SendStream returns the send side of the TypeTester.ZStream server stream.
+func (s *TypeTesterZStreamServerCallStub) SendStream() interface {
+	Send(item bool) error
+} {
+	return implTypeTesterZStreamServerCallSend{s}
+}
+
+type implTypeTesterZStreamServerCallSend struct {
+	s *TypeTesterZStreamServerCallStub
+}
+
+func (s implTypeTesterZStreamServerCallSend) Send(item bool) error {
+	return s.s.Send(item)
+}
diff --git a/cmd/vrpc/v23_internal_test.go b/cmd/vrpc/v23_internal_test.go
new file mode 100644
index 0000000..ae59080
--- /dev/null
+++ b/cmd/vrpc/v23_internal_test.go
@@ -0,0 +1,20 @@
+// 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 main
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	os.Exit(m.Run())
+}
diff --git a/cmd/vrpc/vrpc.go b/cmd/vrpc/vrpc.go
new file mode 100644
index 0000000..e0f35e0
--- /dev/null
+++ b/cmd/vrpc/vrpc.go
@@ -0,0 +1,295 @@
+// 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.
+
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go .
+
+package main
+
+import (
+	"fmt"
+	"io"
+	"regexp"
+	"strings"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/options"
+	"v.io/v23/rpc"
+	"v.io/v23/rpc/reserved"
+	"v.io/v23/vdl"
+	"v.io/v23/vdlroot/signature"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/v23cmd"
+	"v.io/x/ref/lib/vdl/build"
+	"v.io/x/ref/lib/vdl/codegen/vdlgen"
+	"v.io/x/ref/lib/vdl/compile"
+
+	_ "v.io/x/ref/runtime/factories/generic"
+)
+
+var (
+	flagInsecure     bool
+	flagShowReserved bool
+)
+
+func main() {
+	cmdline.HideGlobalFlagsExcept(regexp.MustCompile(`^v23\.namespace\.root$`))
+	cmdline.Main(cmdVRPC)
+}
+
+func init() {
+	const (
+		insecureVal  = false
+		insecureName = "insecure"
+		insecureDesc = "If true, skip server authentication. This means that the client will reveal its blessings to servers that it may not recognize."
+	)
+	cmdSignature.Flags.BoolVar(&flagInsecure, insecureName, insecureVal, insecureDesc)
+	cmdIdentify.Flags.BoolVar(&flagInsecure, insecureName, insecureVal, insecureDesc)
+
+	cmdSignature.Flags.BoolVar(&flagShowReserved, "show-reserved", false, "if true, also show the signatures of reserved methods")
+}
+
+var cmdVRPC = &cmdline.Command{
+	Name:  "vrpc",
+	Short: "sends and receives Vanadium remote procedure calls",
+	Long: `
+Command vrpc sends and receives Vanadium remote procedure calls.  It is used as
+a generic client to interact with any Vanadium server.
+`,
+	// TODO(toddw): Add cmdServe, which will take an interface as input, and set
+	// up a server capable of handling the given methods.  When a request is
+	// received, it'll allow the user to respond via stdin.
+	Children: []*cmdline.Command{cmdSignature, cmdCall, cmdIdentify},
+}
+
+const serverDesc = `
+<server> identifies a Vanadium server.  It can either be the object address of
+the server, or an object name that will be resolved to an end-point.
+`
+
+var cmdSignature = &cmdline.Command{
+	Runner: v23cmd.RunnerFunc(runSignature),
+	Name:   "signature",
+	Short:  "Describe the interfaces of a Vanadium server",
+	Long: `
+Signature connects to the Vanadium server identified by <server>.
+
+If no [method] is provided, returns all interfaces implemented by the server.
+
+If a [method] is provided, returns the signature of just that method.
+`,
+	ArgsName: "<server> [method]",
+	ArgsLong: serverDesc + `
+[method] is the optional server method name.
+`,
+}
+
+var cmdCall = &cmdline.Command{
+	Runner: v23cmd.RunnerFunc(runCall),
+	Name:   "call",
+	Short:  "Call a method of a Vanadium server",
+	Long: `
+Call connects to the Vanadium server identified by <server> and calls the
+<method> with the given positional [args...], returning results on stdout.
+
+TODO(toddw): stdin is read for streaming arguments sent to the server.  An EOF
+on stdin (e.g. via ^D) causes the send stream to be closed.
+
+Regardless of whether the call is streaming, the main goroutine blocks for
+streaming and positional results received from the server.
+
+All input arguments (both positional and streaming) are specified as VDL
+expressions, with commas separating multiple expressions.  Positional arguments
+may also be specified as separate command-line arguments.  Streaming arguments
+may also be specified as separate newline-terminated expressions.
+
+The method signature is always retrieved from the server as a first step.  This
+makes it easier to input complex typed arguments, since the top-level type for
+each argument is implicit and doesn't need to be specified.
+`,
+	ArgsName: "<server> <method> [args...]",
+	ArgsLong: serverDesc + `
+<method> is the server method to call.
+
+[args...] are the positional input arguments, specified as VDL expressions.
+`,
+}
+
+var cmdIdentify = &cmdline.Command{
+	Runner: v23cmd.RunnerFunc(runIdentify),
+	Name:   "identify",
+	Short:  "Reveal blessings presented by a Vanadium server",
+	Long: `
+Identify connects to the Vanadium server identified by <server> and dumps out
+the blessings presented by that server (and the subset of those that are
+considered valid by the principal running this tool) to standard output.
+`,
+	ArgsName: "<server>",
+	ArgsLong: serverDesc,
+}
+
+func runSignature(ctx *context.T, env *cmdline.Env, args []string) error {
+	// Error-check args.
+	var server, method string
+	switch len(args) {
+	case 1:
+		server = args[0]
+	case 2:
+		server, method = args[0], args[1]
+	default:
+		return env.UsageErrorf("wrong number of arguments")
+	}
+	// Get the interface or method signature, and pretty-print.  We print the
+	// named types after the signatures, to aid in readability.
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
+	defer cancel()
+	var types vdlgen.NamedTypes
+	var opts []rpc.CallOpt
+	if flagInsecure {
+		opts = append(opts, options.SkipServerEndpointAuthorization{})
+	}
+	if method != "" {
+		methodSig, err := reserved.MethodSignature(ctx, server, method, opts...)
+		if err != nil {
+			return fmt.Errorf("MethodSignature failed: %v", err)
+		}
+		vdlgen.PrintMethod(env.Stdout, methodSig, &types)
+		fmt.Fprintln(env.Stdout)
+		types.Print(env.Stdout)
+		return nil
+	}
+	ifacesSig, err := reserved.Signature(ctx, server, opts...)
+	if err != nil {
+		return fmt.Errorf("Signature failed: %v", err)
+	}
+	for i, iface := range ifacesSig {
+		if !flagShowReserved && naming.IsReserved(iface.Name) {
+			continue
+		}
+		if i > 0 {
+			fmt.Fprintln(env.Stdout)
+		}
+		vdlgen.PrintInterface(env.Stdout, iface, &types)
+		fmt.Fprintln(env.Stdout)
+	}
+	types.Print(env.Stdout)
+	return nil
+}
+
+func runCall(ctx *context.T, env *cmdline.Env, args []string) error {
+	// Error-check args, and set up argsdata with a comma-separated list of
+	// arguments, allowing each individual arg to already be comma-separated.
+	//
+	// TODO(toddw): Should we just space-separate the args instead?
+	if len(args) < 2 {
+		return env.UsageErrorf("must specify <server> and <method>")
+	}
+	server, method := args[0], args[1]
+	var argsdata string
+	for _, arg := range args[2:] {
+		arg := strings.TrimSpace(arg)
+		if argsdata == "" || strings.HasSuffix(argsdata, ",") || strings.HasPrefix(arg, ",") {
+			argsdata += arg
+		} else {
+			argsdata += "," + arg
+		}
+	}
+	// Get the method signature and parse args.
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
+	defer cancel()
+	methodSig, err := reserved.MethodSignature(ctx, server, method)
+	if err != nil {
+		return fmt.Errorf("MethodSignature failed: %v", err)
+	}
+	inargs, err := parseInArgs(argsdata, methodSig)
+	if err != nil {
+		// TODO: Print signature and example.
+		return err
+	}
+	// Start the method call.
+	call, err := v23.GetClient(ctx).StartCall(ctx, server, method, inargs)
+	if err != nil {
+		return fmt.Errorf("StartCall failed: %v", err)
+	}
+	// TODO(toddw): Fire off a goroutine to handle streaming inputs.
+	// Handle streaming results.
+StreamingResultsLoop:
+	for {
+		var item *vdl.Value
+		switch err := call.Recv(&item); {
+		case err == io.EOF:
+			break StreamingResultsLoop
+		case err != nil:
+			return fmt.Errorf("call.Recv failed: %v", err)
+		}
+		fmt.Fprintf(env.Stdout, "<< %v\n", vdlgen.TypedConst(item, "", nil))
+	}
+	// Finish the method call.
+	outargs := make([]*vdl.Value, len(methodSig.OutArgs))
+	outptrs := make([]interface{}, len(outargs))
+	for i := range outargs {
+		outptrs[i] = &outargs[i]
+	}
+	if err := call.Finish(outptrs...); err != nil {
+		return fmt.Errorf("call.Finish failed: %v", err)
+	}
+	// Pretty-print results.
+	for i, arg := range outargs {
+		if i > 0 {
+			fmt.Fprint(env.Stdout, " ")
+		}
+		fmt.Fprint(env.Stdout, vdlgen.TypedConst(arg, "", nil))
+	}
+	fmt.Fprintln(env.Stdout)
+	return nil
+}
+
+func parseInArgs(argsdata string, methodSig signature.Method) ([]interface{}, error) {
+	if len(methodSig.InArgs) == 0 {
+		return nil, nil
+	}
+	var intypes []*vdl.Type
+	for _, inarg := range methodSig.InArgs {
+		intypes = append(intypes, inarg.Type)
+	}
+	env := compile.NewEnv(-1)
+	inargs := build.BuildExprs(argsdata, intypes, env)
+	if err := env.Errors.ToError(); err != nil {
+		return nil, fmt.Errorf("can't parse in-args:\n%v", err)
+	}
+	if got, want := len(inargs), len(methodSig.InArgs); got != want {
+		return nil, fmt.Errorf("got %d args, want %d", got, want)
+	}
+	// Translate []*vdl.Value to []interface, with each item still *vdl.Value.
+	var ret []interface{}
+	for _, arg := range inargs {
+		ret = append(ret, arg)
+	}
+	return ret, nil
+}
+
+func runIdentify(ctx *context.T, env *cmdline.Env, args []string) error {
+	if len(args) != 1 {
+		return env.UsageErrorf("wrong number of arguments")
+	}
+	server := args[0]
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
+	defer cancel()
+	var opts []rpc.CallOpt
+	if flagInsecure {
+		opts = append(opts, options.SkipServerEndpointAuthorization{})
+	}
+	// The method name does not matter - only interested in authentication,
+	// not in actually making an RPC.
+	call, err := v23.GetClient(ctx).StartCall(ctx, server, "", nil, opts...)
+	if err != nil {
+		return fmt.Errorf(`client.StartCall(%q, "", nil) failed with %v`, server, err)
+	}
+	valid, presented := call.RemoteBlessings()
+	fmt.Fprintf(env.Stdout, "PRESENTED: %v\nVALID:     %v\nPUBLICKEY: %v\n", presented, valid, presented.PublicKey())
+	return nil
+}
diff --git a/cmd/vrpc/vrpc_test.go b/cmd/vrpc/vrpc_test.go
new file mode 100644
index 0000000..d4ffcac
--- /dev/null
+++ b/cmd/vrpc/vrpc_test.go
@@ -0,0 +1,340 @@
+// 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 (
+	"bytes"
+	"fmt"
+	"strings"
+	"testing"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/x/lib/cmdline"
+	"v.io/x/lib/vlog"
+	"v.io/x/ref/cmd/vrpc/internal"
+	"v.io/x/ref/lib/v23cmd"
+	"v.io/x/ref/lib/xrpc"
+	_ "v.io/x/ref/runtime/factories/generic"
+	"v.io/x/ref/test"
+)
+
+type server struct{}
+
+//go:generate v23 test generate
+
+// TypeTester interface implementation
+
+func (*server) EchoBool(_ *context.T, _ rpc.ServerCall, i1 bool) (bool, error) {
+	vlog.VI(2).Info("EchoBool(%v) was called.", i1)
+	return i1, nil
+}
+
+func (*server) EchoFloat32(_ *context.T, _ rpc.ServerCall, i1 float32) (float32, error) {
+	vlog.VI(2).Info("EchoFloat32(%v) was called.", i1)
+	return i1, nil
+}
+
+func (*server) EchoFloat64(_ *context.T, _ rpc.ServerCall, i1 float64) (float64, error) {
+	vlog.VI(2).Info("EchoFloat64(%v) was called.", i1)
+	return i1, nil
+}
+
+func (*server) EchoInt32(_ *context.T, _ rpc.ServerCall, i1 int32) (int32, error) {
+	vlog.VI(2).Info("EchoInt32(%v) was called.", i1)
+	return i1, nil
+}
+
+func (*server) EchoInt64(_ *context.T, _ rpc.ServerCall, i1 int64) (int64, error) {
+	vlog.VI(2).Info("EchoInt64(%v) was called.", i1)
+	return i1, nil
+}
+
+func (*server) EchoString(_ *context.T, _ rpc.ServerCall, i1 string) (string, error) {
+	vlog.VI(2).Info("EchoString(%v) was called.", i1)
+	return i1, nil
+}
+
+func (*server) EchoByte(_ *context.T, _ rpc.ServerCall, i1 byte) (byte, error) {
+	vlog.VI(2).Info("EchoByte(%v) was called.", i1)
+	return i1, nil
+}
+
+func (*server) EchoUint32(_ *context.T, _ rpc.ServerCall, i1 uint32) (uint32, error) {
+	vlog.VI(2).Info("EchoUint32(%v) was called.", i1)
+	return i1, nil
+}
+
+func (*server) EchoUint64(_ *context.T, _ rpc.ServerCall, i1 uint64) (uint64, error) {
+	vlog.VI(2).Info("EchoUint64(%v) was called.", i1)
+	return i1, nil
+}
+
+func (*server) XEchoArray(_ *context.T, _ rpc.ServerCall, i1 internal.Array2Int) (internal.Array2Int, error) {
+	vlog.VI(2).Info("XEchoArray(%v) was called.", i1)
+	return i1, nil
+}
+
+func (*server) XEchoMap(_ *context.T, _ rpc.ServerCall, i1 map[int32]string) (map[int32]string, error) {
+	vlog.VI(2).Info("XEchoMap(%v) was called.", i1)
+	return i1, nil
+}
+
+func (*server) XEchoSet(_ *context.T, _ rpc.ServerCall, i1 map[int32]struct{}) (map[int32]struct{}, error) {
+	vlog.VI(2).Info("XEchoSet(%v) was called.", i1)
+	return i1, nil
+}
+
+func (*server) XEchoSlice(_ *context.T, _ rpc.ServerCall, i1 []int32) ([]int32, error) {
+	vlog.VI(2).Info("XEchoSlice(%v) was called.", i1)
+	return i1, nil
+}
+
+func (*server) XEchoStruct(_ *context.T, _ rpc.ServerCall, i1 internal.Struct) (internal.Struct, error) {
+	vlog.VI(2).Info("XEchoStruct(%v) was called.", i1)
+	return i1, nil
+}
+
+func (*server) YMultiArg(_ *context.T, _ rpc.ServerCall, i1, i2 int32) (int32, int32, error) {
+	vlog.VI(2).Info("YMultiArg(%v,%v) was called.", i1, i2)
+	return i1, i2, nil
+}
+
+func (*server) YNoArgs(_ *context.T, _ rpc.ServerCall) error {
+	vlog.VI(2).Info("YNoArgs() was called.")
+	return nil
+}
+
+func (*server) ZStream(_ *context.T, call internal.TypeTesterZStreamServerCall, nStream int32, item bool) error {
+	vlog.VI(2).Info("ZStream(%v,%v) was called.", nStream, item)
+	sender := call.SendStream()
+	for i := int32(0); i < nStream; i++ {
+		sender.Send(item)
+	}
+	return nil
+}
+
+func initTest(t *testing.T) (ctx *context.T, name string, shutdown v23.Shutdown) {
+	ctx, shutdown = test.V23Init()
+	obj := internal.TypeTesterServer(&server{})
+	server, err := xrpc.NewServer(ctx, "", obj, nil)
+	if err != nil {
+		t.Fatalf("NewServer failed: %v", err)
+		return
+	}
+	name = server.Status().Endpoints[0].Name()
+	return
+}
+
+func testSignature(t *testing.T, showReserved bool, wantSig string) {
+	ctx, name, shutdown := initTest(t)
+	defer shutdown()
+	var stdout, stderr bytes.Buffer
+	env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
+	args := []string{"signature", fmt.Sprintf("-show-reserved=%v", showReserved), name}
+	if err := v23cmd.ParseAndRunForTest(cmdVRPC, ctx, env, args); err != nil {
+		t.Fatalf("%s: %v", args, err)
+	}
+
+	if got, want := stdout.String(), wantSig; got != want {
+		t.Errorf("%s: got stdout %s, want %s", args, got, want)
+	}
+	if got, want := stderr.String(), ""; got != want {
+		t.Errorf("%s: got stderr %s, want %s", args, got, want)
+	}
+}
+
+func TestSignatureWithReserved(t *testing.T) {
+	wantSig := `// TypeTester methods are listed in alphabetical order, to make it easier to
+// test Signature output, which sorts methods alphabetically.
+type "v.io/x/ref/cmd/vrpc/internal".TypeTester interface {
+	// Methods to test support for primitive types.
+	EchoBool(I1 bool) (O1 bool | error)
+	EchoByte(I1 byte) (O1 byte | error)
+	EchoFloat32(I1 float32) (O1 float32 | error)
+	EchoFloat64(I1 float64) (O1 float64 | error)
+	EchoInt32(I1 int32) (O1 int32 | error)
+	EchoInt64(I1 int64) (O1 int64 | error)
+	EchoString(I1 string) (O1 string | error)
+	EchoUint32(I1 uint32) (O1 uint32 | error)
+	EchoUint64(I1 uint64) (O1 uint64 | error)
+	// Methods to test support for composite types.
+	XEchoArray(I1 "v.io/x/ref/cmd/vrpc/internal".Array2Int) (O1 "v.io/x/ref/cmd/vrpc/internal".Array2Int | error)
+	XEchoMap(I1 map[int32]string) (O1 map[int32]string | error)
+	XEchoSet(I1 set[int32]) (O1 set[int32] | error)
+	XEchoSlice(I1 []int32) (O1 []int32 | error)
+	XEchoStruct(I1 "v.io/x/ref/cmd/vrpc/internal".Struct) (O1 "v.io/x/ref/cmd/vrpc/internal".Struct | error)
+	// Methods to test support for different number of arguments.
+	YMultiArg(I1 int32, I2 int32) (O1 int32, O2 int32 | error)
+	YNoArgs() error
+	// Methods to test support for streaming.
+	ZStream(NumStreamItems int32, StreamItem bool) stream<_, bool> error
+}
+
+// Reserved methods implemented by the RPC framework.  Each method name is prefixed with a double underscore "__".
+type __Reserved interface {
+	// Glob returns all entries matching the pattern.
+	__Glob(pattern string) stream<any, any> error
+	// MethodSignature returns the signature for the given method.
+	__MethodSignature(method string) ("signature".Method | error)
+	// Signature returns all interface signatures implemented by the object.
+	__Signature() ([]"signature".Interface | error)
+}
+
+type "signature".Arg struct {
+	Name string
+	Doc string
+	Type typeobject
+}
+
+type "signature".Embed struct {
+	Name string
+	PkgPath string
+	Doc string
+}
+
+type "signature".Interface struct {
+	Name string
+	PkgPath string
+	Doc string
+	Embeds []"signature".Embed
+	Methods []"signature".Method
+}
+
+type "signature".Method struct {
+	Name string
+	Doc string
+	InArgs []"signature".Arg
+	OutArgs []"signature".Arg
+	InStream ?"signature".Arg
+	OutStream ?"signature".Arg
+	Tags []any
+}
+
+type "v.io/x/ref/cmd/vrpc/internal".Array2Int [2]int32
+
+type "v.io/x/ref/cmd/vrpc/internal".Struct struct {
+	X int32
+	Y int32
+}
+`
+	testSignature(t, true, wantSig)
+}
+
+func TestSignatureNoReserved(t *testing.T) {
+	wantSig := `// TypeTester methods are listed in alphabetical order, to make it easier to
+// test Signature output, which sorts methods alphabetically.
+type "v.io/x/ref/cmd/vrpc/internal".TypeTester interface {
+	// Methods to test support for primitive types.
+	EchoBool(I1 bool) (O1 bool | error)
+	EchoByte(I1 byte) (O1 byte | error)
+	EchoFloat32(I1 float32) (O1 float32 | error)
+	EchoFloat64(I1 float64) (O1 float64 | error)
+	EchoInt32(I1 int32) (O1 int32 | error)
+	EchoInt64(I1 int64) (O1 int64 | error)
+	EchoString(I1 string) (O1 string | error)
+	EchoUint32(I1 uint32) (O1 uint32 | error)
+	EchoUint64(I1 uint64) (O1 uint64 | error)
+	// Methods to test support for composite types.
+	XEchoArray(I1 "v.io/x/ref/cmd/vrpc/internal".Array2Int) (O1 "v.io/x/ref/cmd/vrpc/internal".Array2Int | error)
+	XEchoMap(I1 map[int32]string) (O1 map[int32]string | error)
+	XEchoSet(I1 set[int32]) (O1 set[int32] | error)
+	XEchoSlice(I1 []int32) (O1 []int32 | error)
+	XEchoStruct(I1 "v.io/x/ref/cmd/vrpc/internal".Struct) (O1 "v.io/x/ref/cmd/vrpc/internal".Struct | error)
+	// Methods to test support for different number of arguments.
+	YMultiArg(I1 int32, I2 int32) (O1 int32, O2 int32 | error)
+	YNoArgs() error
+	// Methods to test support for streaming.
+	ZStream(NumStreamItems int32, StreamItem bool) stream<_, bool> error
+}
+
+type "v.io/x/ref/cmd/vrpc/internal".Array2Int [2]int32
+
+type "v.io/x/ref/cmd/vrpc/internal".Struct struct {
+	X int32
+	Y int32
+}
+`
+	testSignature(t, false, wantSig)
+}
+
+func TestMethodSignature(t *testing.T) {
+	ctx, name, shutdown := initTest(t)
+	defer shutdown()
+
+	tests := []struct {
+		Method, Want string
+	}{
+		// Spot-check some individual methods.
+		{"EchoByte", `EchoByte(I1 byte) (O1 byte | error)`},
+		{"EchoFloat32", `EchoFloat32(I1 float32) (O1 float32 | error)`},
+		{"XEchoStruct", `
+XEchoStruct(I1 "v.io/x/ref/cmd/vrpc/internal".Struct) (O1 "v.io/x/ref/cmd/vrpc/internal".Struct | error)
+
+type "v.io/x/ref/cmd/vrpc/internal".Struct struct {
+	X int32
+	Y int32
+}
+`},
+	}
+	for _, test := range tests {
+		var stdout, stderr bytes.Buffer
+		env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
+		if err := v23cmd.ParseAndRunForTest(cmdVRPC, ctx, env, []string{"signature", name, test.Method}); err != nil {
+			t.Errorf("%q failed: %v", test.Method, err)
+			continue
+		}
+		if got, want := strings.TrimSpace(stdout.String()), strings.TrimSpace(test.Want); got != want {
+			t.Errorf("got stdout %q, want %q", got, want)
+		}
+		if got, want := stderr.String(), ""; got != want {
+			t.Errorf("got stderr %q, want %q", got, want)
+		}
+	}
+}
+
+func TestCall(t *testing.T) {
+	ctx, name, shutdown := initTest(t)
+	defer shutdown()
+
+	tests := []struct {
+		Method, InArgs, Want string
+	}{
+		{"EchoBool", `true`, `true`},
+		{"EchoBool", `false`, `false`},
+		{"EchoFloat32", `1.2`, `float32(1.2)`},
+		{"EchoFloat64", `-3.4`, `float64(-3.4)`},
+		{"EchoInt32", `11`, `int32(11)`},
+		{"EchoInt64", `-22`, `int64(-22)`},
+		{"EchoString", `"abc"`, `"abc"`},
+		{"EchoByte", `33`, `byte(33)`},
+		{"EchoUint32", `44`, `uint32(44)`},
+		{"EchoUint64", `55`, `uint64(55)`},
+		{"XEchoArray", `{1,2}`, `"v.io/x/ref/cmd/vrpc/internal".Array2Int{1, 2}`},
+		{"XEchoMap", `{1:"a"}`, `map[int32]string{1: "a"}`},
+		{"XEchoSet", `{1}`, `set[int32]{1}`},
+		{"XEchoSlice", `{1,2}`, `[]int32{1, 2}`},
+		{"XEchoStruct", `{1,2}`, `"v.io/x/ref/cmd/vrpc/internal".Struct{X: 1, Y: 2}`},
+		{"YMultiArg", `1,2`, `int32(1) int32(2)`},
+		{"YNoArgs", ``, ``},
+		{"ZStream", `2,true`, `<< true
+<< true`},
+	}
+	for _, test := range tests {
+		var stdout, stderr bytes.Buffer
+		env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
+		if err := v23cmd.ParseAndRunForTest(cmdVRPC, ctx, env, []string{"call", name, test.Method, test.InArgs}); err != nil {
+			t.Errorf("%q(%s) failed: %v", test.Method, test.InArgs, err)
+			continue
+		}
+		if got, want := strings.TrimSpace(stdout.String()), strings.TrimSpace(test.Want); got != want {
+			t.Errorf("got stdout %q, want %q", got, want)
+		}
+		if got, want := stderr.String(), ""; got != want {
+			t.Errorf("got stderr %q, want %q", got, want)
+		}
+	}
+}
diff --git a/cmd/vrun/doc.go b/cmd/vrun/doc.go
new file mode 100644
index 0000000..b0ef6a5
--- /dev/null
+++ b/cmd/vrun/doc.go
@@ -0,0 +1,68 @@
+// 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
+
+/*
+Command vrun executes commands with a derived Vanadium principal.
+
+Usage:
+   vrun [flags] <command> [command args...]
+
+The vrun flags are:
+ -duration=1h0m0s
+   Duration for the blessing.
+ -name=
+   Name to use for the blessing. Uses the command name if unset.
+ -role=
+   Role object from which to request the blessing. If set, the blessings from
+   this role server are used and --name is ignored. If not set, the default
+   blessings of the calling principal are extended with --name.
+
+The global flags are:
+ -alsologtostderr=true
+   log to standard error as well as files
+ -log_backtrace_at=:0
+   when logging hits line file:N, emit a stack trace
+ -log_dir=
+   if non-empty, write log files to this directory
+ -logtostderr=false
+   log to standard error instead of files
+ -max_stack_buf_size=4292608
+   max size in bytes of the buffer to use for logging stack traces
+ -metadata=<just specify -metadata to activate>
+   Displays metadata for the program and exits.
+ -stderrthreshold=2
+   logs at or above this threshold go to stderr
+ -v=0
+   log level for V logs
+ -v23.credentials=
+   directory to use for storing security credentials
+ -v23.i18n-catalogue=
+   18n catalogue files to load, comma separated
+ -v23.namespace.root=[/(dev.v.io/role/vprod/service/mounttabled)@ns.dev.v.io:8101]
+   local namespace root; can be repeated to provided multiple roots
+ -v23.proxy=
+   object name of proxy service to use to export services across network
+   boundaries
+ -v23.tcp.address=
+   address to listen on
+ -v23.tcp.protocol=wsh
+   protocol to listen with
+ -v23.vtrace.cache-size=1024
+   The number of vtrace traces to store in memory.
+ -v23.vtrace.collect-regexp=
+   Spans and annotations that match this regular expression will trigger trace
+   collection.
+ -v23.vtrace.dump-on-shutdown=true
+   If true, dump all stored traces on runtime shutdown.
+ -v23.vtrace.sample-rate=0
+   Rate (from 0.0 to 1.0) to sample vtrace traces.
+ -vmodule=
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
+*/
+package main
diff --git a/cmd/vrun/v23_test.go b/cmd/vrun/v23_test.go
new file mode 100644
index 0000000..ece399a
--- /dev/null
+++ b/cmd/vrun/v23_test.go
@@ -0,0 +1,30 @@
+// 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 main_test
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
+	"v.io/x/ref/test/v23tests"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	modules.DispatchAndExitIfChild()
+	cleanup := v23tests.UseSharedBinDir()
+	r := m.Run()
+	cleanup()
+	os.Exit(r)
+}
+
+func TestV23Agentd(t *testing.T) {
+	v23tests.RunTest(t, V23TestAgentd)
+}
diff --git a/cmd/vrun/vrun.go b/cmd/vrun/vrun.go
new file mode 100644
index 0000000..8d06d39
--- /dev/null
+++ b/cmd/vrun/vrun.go
@@ -0,0 +1,188 @@
+// 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.
+
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go . -help
+
+package main
+
+import (
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"syscall"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/security"
+	"v.io/x/lib/cmdline"
+	"v.io/x/lib/vlog"
+	"v.io/x/ref"
+	"v.io/x/ref/lib/v23cmd"
+	"v.io/x/ref/services/agent"
+	"v.io/x/ref/services/agent/agentlib"
+	"v.io/x/ref/services/agent/keymgr"
+	"v.io/x/ref/services/role"
+
+	_ "v.io/x/ref/runtime/factories/generic"
+)
+
+var (
+	durationFlag time.Duration
+	nameFlag     string
+	roleFlag     string
+)
+
+var cmdVrun = &cmdline.Command{
+	Runner:   v23cmd.RunnerFunc(vrun),
+	Name:     "vrun",
+	Short:    "executes commands with a derived Vanadium principal",
+	Long:     "Command vrun executes commands with a derived Vanadium principal.",
+	ArgsName: "<command> [command args...]",
+}
+
+func main() {
+	cmdline.HideGlobalFlagsExcept()
+	syscall.CloseOnExec(3)
+	syscall.CloseOnExec(4)
+
+	cmdVrun.Flags.DurationVar(&durationFlag, "duration", 1*time.Hour, "Duration for the blessing.")
+	cmdVrun.Flags.StringVar(&nameFlag, "name", "", "Name to use for the blessing. Uses the command name if unset.")
+	cmdVrun.Flags.StringVar(&roleFlag, "role", "", "Role object from which to request the blessing. If set, the blessings from this role server are used and --name is ignored. If not set, the default blessings of the calling principal are extended with --name.")
+
+	cmdline.Main(cmdVrun)
+}
+
+func vrun(ctx *context.T, env *cmdline.Env, args []string) error {
+	if len(args) == 0 {
+		args = []string{"bash", "--norc"}
+	}
+	m, err := connectToKeyManager()
+	if err != nil {
+		return err
+	}
+
+	path, err := newPrincipal(m)
+	if err != nil {
+		return err
+	}
+
+	// Connect to the Principal
+	principal, err := agentlib.NewAgentPrincipalX(path)
+	if err != nil {
+		vlog.Errorf("Couldn't connect to principal")
+	}
+
+	if len(roleFlag) == 0 {
+		if len(nameFlag) == 0 {
+			nameFlag = filepath.Base(args[0])
+		}
+		if err := bless(ctx, principal, nameFlag); err != nil {
+			return err
+		}
+	} else {
+		// The role server expects the client's blessing name to end
+		// with RoleSuffix. This is to avoid accidentally granting role
+		// access to anything else that might have been blessed by the
+		// same principal.
+		if err := bless(ctx, principal, role.RoleSuffix); err != nil {
+			return err
+		}
+		rCtx, err := v23.WithPrincipal(ctx, principal)
+		if err != nil {
+			return err
+		}
+		if err := setupRoleBlessings(rCtx, roleFlag); err != nil {
+			return err
+		}
+	}
+
+	return doExec(args, path)
+}
+
+func bless(ctx *context.T, p security.Principal, name string) error {
+	caveat, err := security.NewExpiryCaveat(time.Now().Add(durationFlag))
+	if err != nil {
+		vlog.Errorf("Couldn't create caveat")
+		return err
+	}
+
+	rp := v23.GetPrincipal(ctx)
+	blessing, err := rp.Bless(p.PublicKey(), rp.BlessingStore().Default(), name, caveat)
+	if err != nil {
+		vlog.Errorf("Couldn't bless")
+		return err
+	}
+
+	if err = p.BlessingStore().SetDefault(blessing); err != nil {
+		vlog.Errorf("Couldn't set default blessing")
+		return err
+	}
+	if _, err = p.BlessingStore().Set(blessing, security.AllPrincipals); err != nil {
+		vlog.Errorf("Couldn't set default client blessing")
+		return err
+	}
+	if err = p.AddToRoots(blessing); err != nil {
+		vlog.Errorf("Couldn't set trusted roots")
+		return err
+	}
+	return nil
+}
+
+func doExec(cmd []string, agentPath string) error {
+	ref.EnvClearCredentials()
+	if err := os.Setenv(ref.EnvAgentPath, agentPath); err != nil {
+		return err
+	}
+	p, err := exec.LookPath(cmd[0])
+	if err != nil {
+		vlog.Errorf("Couldn't find %q", cmd[0])
+		return err
+	}
+	err = syscall.Exec(p, cmd, os.Environ())
+	vlog.Errorf("Couldn't exec %s.", cmd[0])
+	return err
+}
+
+func connectToKeyManager() (agent.KeyManager, error) {
+	path := os.Getenv(ref.EnvAgentPath)
+	return keymgr.NewKeyManager(path)
+}
+
+func newPrincipal(m agent.KeyManager) (path string, err error) {
+	var dir string
+	if dir, err = ioutil.TempDir("", "vrun"); err != nil {
+		return
+	}
+	var id [64]byte
+	if id, err = m.NewPrincipal(true); err != nil {
+		vlog.Errorf("Couldn't create principal")
+		return
+	}
+	// Note: because we exec the child, there's no way to cleanup
+	// this principal and socket after the child is gone.
+	path = filepath.Join(dir, "sock")
+	err = m.ServePrincipal(id, path)
+	return
+}
+
+func setupRoleBlessings(ctx *context.T, roleStr string) error {
+	b, err := role.RoleClient(roleStr).SeekBlessings(ctx)
+	if err != nil {
+		return err
+	}
+	p := v23.GetPrincipal(ctx)
+	// TODO(rthellend,ashankar): Revisit this configuration.
+	// SetDefault: Should we expect users to want to act as a server on behalf of the role (by default?)
+	// AllPrincipals: Do we not want to be discriminating about which services we use the role blessing at.
+	if err := p.BlessingStore().SetDefault(b); err != nil {
+		return err
+	}
+	if _, err := p.BlessingStore().Set(b, security.AllPrincipals); err != nil {
+		return err
+	}
+	return nil
+}
diff --git a/cmd/vrun/vrun_v23_test.go b/cmd/vrun/vrun_v23_test.go
new file mode 100644
index 0000000..87ca66c
--- /dev/null
+++ b/cmd/vrun/vrun_v23_test.go
@@ -0,0 +1,105 @@
+// 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_test
+
+//go:generate v23 test generate .
+
+import (
+	"fmt"
+	"os"
+
+	"v.io/v23/security"
+	"v.io/x/ref"
+	vsecurity "v.io/x/ref/lib/security"
+	"v.io/x/ref/test/v23tests"
+)
+
+func V23TestAgentd(t *v23tests.T) {
+	var (
+		clientAgent, serverAgent = createClientAndServerAgents(t)
+		tmpdir                   = t.NewTempDir("")
+		vrun                     = t.BuildGoPkg("v.io/x/ref/cmd/vrun").Path()
+		pingpong                 = t.BuildGoPkg("v.io/x/ref/services/agent/internal/pingpong").Path()
+		serverName               = serverAgent.Start(pingpong).ExpectVar("NAME")
+
+		tests = []struct {
+			Command []string
+			Client  string
+		}{
+			{
+				[]string{pingpong, serverName}, // No vrun
+				"pingpongd/client",
+			},
+			{
+				[]string{vrun, pingpong, serverName},
+				"pingpongd/client/pingpong",
+			},
+			{
+				[]string{vrun, "--name=bugsy", pingpong, serverName},
+				"pingpongd/client/bugsy",
+			},
+		}
+	)
+	for _, test := range tests {
+		args := append([]string{"--additional-principals=" + tmpdir}, test.Command...)
+		client := clientAgent.Start(args...)
+		client.Expect("Pinging...")
+		client.Expect(fmt.Sprintf("pong (client:[%v] server:[pingpongd])", test.Client))
+		client.WaitOrDie(os.Stdout, os.Stderr)
+		if err := client.Error(); err != nil {
+			t.Errorf("Test: %+v: %v", test, err)
+		}
+	}
+}
+
+// createClientAndServerAgents creates two principals, sets up their
+// blessings and returns the agent binaries that will use the created credentials.
+//
+// The server will have a single blessing "pingpongd".
+// The client will have a single blessing "pingpongd/client", blessed by the server.
+func createClientAndServerAgents(i *v23tests.T) (client, server *v23tests.Binary) {
+	var (
+		agentd    = i.BuildGoPkg("v.io/x/ref/services/agent/agentd")
+		clientDir = i.NewTempDir("")
+		serverDir = i.NewTempDir("")
+	)
+	pserver, err := vsecurity.CreatePersistentPrincipal(serverDir, nil)
+	if err != nil {
+		i.Fatal(err)
+	}
+	pclient, err := vsecurity.CreatePersistentPrincipal(clientDir, nil)
+	if err != nil {
+		i.Fatal(err)
+	}
+	// Server will only serve, not make any client requests, so only needs a default blessing.
+	bserver, err := pserver.BlessSelf("pingpongd")
+	if err != nil {
+		i.Fatal(err)
+	}
+	if err := pserver.BlessingStore().SetDefault(bserver); err != nil {
+		i.Fatal(err)
+	}
+	// Clients need not have a default blessing as they will only make a request to the server.
+	bclient, err := pserver.Bless(pclient.PublicKey(), bserver, "client", security.UnconstrainedUse())
+	if err != nil {
+		i.Fatal(err)
+	}
+	if _, err := pclient.BlessingStore().Set(bclient, "pingpongd"); err != nil {
+		i.Fatal(err)
+	}
+	// The client and server must both recognize bserver and its delegates.
+	if err := pserver.AddToRoots(bserver); err != nil {
+		i.Fatal(err)
+	}
+	if err := pclient.AddToRoots(bserver); err != nil {
+		i.Fatal(err)
+	}
+	// We need to add set a default blessings to this pclient for this principal to
+	// work with vrun.
+	if err := pclient.BlessingStore().SetDefault(bclient); err != nil {
+		i.Fatal(err)
+	}
+	return agentd.WithEnv(ref.EnvCredentials + "=" + clientDir), agentd.WithEnv(ref.EnvCredentials + "=" + serverDir)
+}
diff --git a/envvar.go b/envvar.go
new file mode 100644
index 0000000..0c153db
--- /dev/null
+++ b/envvar.go
@@ -0,0 +1,91 @@
+// 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 ref defines constants used through the Vanadium reference
+// implementation, which is implemented in its subdirectories.
+//
+// For more details about the Vanadium project, please visit https://v.io.
+package ref
+
+import (
+	"os"
+	"strings"
+)
+
+const (
+	// EnvCredentials is the name of the environment variable pointing to a
+	// directory containing all the credentials of a principal (the blessing
+	// store, the blessing roots, possibly the private key etc.).
+	//
+	// Typically only one of EnvCredentials or EnvAgentEndpoint will be set in a
+	// process. If both are set, then EnvCredentials takes preference.
+	//
+	// See v.io/x/ref/lib/security.CreatePersistentPrincipal.
+	EnvCredentials = "V23_CREDENTIALS"
+
+	// EnvAgentPath is the name of the environment variable pointing to a socket
+	// of the agentd process containing all the credentials for a principal (the
+	// blessing store, the blessing roots, possibly the private key etc.).
+	//
+	// Typically only one of EnvCredentials or EnvAgentPath will be set in a
+	// process. If both are set, then EnvCredentials takes preference.
+	EnvAgentPath = "V23_AGENT_PATH"
+
+	// EnvAgentEndpoint is the name of the environment variable pointing to an
+	// agentd process containing all the credentials a principal (the blessing
+	// store, the blessing roots, possibly the private key etc.).
+	//
+	// EnvAgentEndpoint is deprecated. New agentd processes should use EnvAgentPath.
+	// If both are set, EnvAgentPath takes preference.
+	EnvAgentEndpoint = "V23_AGENT_ENDPOINT"
+
+	// EnvNamespacePrefix is the prefix of all environment variables that define a
+	// namespace root.
+	EnvNamespacePrefix = "V23_NAMESPACE"
+
+	// EnvI18nCatalogueFiles is the name of the environment variable pointing to a
+	// comma-separated list of i18n catalogue files to be loaded at startup.
+	EnvI18nCatalogueFiles = "V23_I18N_CATALOGUE"
+
+	// EnvOAuthIdentityProvider is the name of the environment variable pointing
+	// to the url of the OAuth identity provider used by the principal
+	// seekblessings command.
+	EnvOAuthIdentityProvider = "V23_OAUTH_IDENTITY_PROVIDER"
+)
+
+// EnvNamespaceRoots returns the set of namespace roots to be used by the
+// process, as specified by environment variables.
+//
+// It returns both a map of environment variable name to value and the list of
+// values.
+func EnvNamespaceRoots() (map[string]string, []string) {
+	m := make(map[string]string)
+	var l []string
+	for _, ev := range os.Environ() {
+		p := strings.SplitN(ev, "=", 2)
+		if len(p) != 2 {
+			continue
+		}
+		k, v := p[0], p[1]
+		if strings.HasPrefix(k, EnvNamespacePrefix) && len(v) > 0 {
+			l = append(l, v)
+			m[k] = v
+		}
+	}
+	return m, l
+}
+
+// EnvClearCredentials unsets all environment variables that are used by the
+// Runtime to intialize the principal.
+func EnvClearCredentials() error {
+	for _, v := range []string{
+		EnvCredentials,
+		EnvAgentEndpoint,
+	} {
+		if err := os.Unsetenv(v); err != nil {
+			return err
+		}
+	}
+	return nil
+}
diff --git a/envvar_test.go b/envvar_test.go
new file mode 100644
index 0000000..8d1d8f1
--- /dev/null
+++ b/envvar_test.go
@@ -0,0 +1,57 @@
+// 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 ref
+
+import (
+	"os"
+	"reflect"
+	"testing"
+)
+
+// Set an environment variable and return a function to undo it.
+// Typical usage:
+//   defer setenv(t, "VAR", "VALUE")()
+func setenv(t *testing.T, name, value string) func() {
+	oldval := os.Getenv(name)
+	if err := os.Setenv(name, value); err != nil {
+		t.Fatalf("Failed to set %q to %q: %v", name, value, err)
+		return func() {}
+	}
+	return func() {
+		if err := os.Setenv(name, oldval); err != nil {
+			t.Fatalf("Failed to restore %q to %q: %v", name, oldval, err)
+		}
+	}
+}
+
+func TestEnvNamespaceRoots(t *testing.T) {
+	defer setenv(t, EnvNamespacePrefix, "NS1")()
+	defer setenv(t, EnvNamespacePrefix+"_BLAH", "NS_BLAH")()
+
+	wantm := map[string]string{
+		"V23_NAMESPACE":      "NS1",
+		"V23_NAMESPACE_BLAH": "NS_BLAH",
+	}
+	wantl := []string{"NS1", "NS_BLAH"}
+
+	gotm, gotl := EnvNamespaceRoots()
+	if !reflect.DeepEqual(wantm, gotm) {
+		t.Errorf("Got %v want %v", gotm, wantm)
+	}
+	if !reflect.DeepEqual(wantl, gotl) {
+		t.Errorf("Got %v want %v", gotl, wantl)
+	}
+}
+
+func TestEnvClearCredentials(t *testing.T) {
+	defer setenv(t, EnvCredentials, "FOO")()
+	if got, want := os.Getenv(EnvCredentials), "FOO"; got != want {
+		t.Errorf("Got %q, want %q", got, want)
+	}
+	EnvClearCredentials()
+	if got := os.Getenv(EnvCredentials); got != "" {
+		t.Errorf("Got %q, wanted empty string", got)
+	}
+}
diff --git a/examples/fortune/fortune.vdl b/examples/fortune/fortune.vdl
new file mode 100644
index 0000000..877b1e7
--- /dev/null
+++ b/examples/fortune/fortune.vdl
@@ -0,0 +1,16 @@
+// 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 fortune defines the Fortune example interface.
+package fortune
+
+// Fortune is the interface to a fortune-telling service.
+type Fortune interface {
+  // Returns a random fortune.
+  Get() (fortune string | error)
+  // Adds a fortune to the set used by Get().
+  Add(fortune string) error
+  // Returns whether or not a fortune exists.
+  Has(fortune string) (bool | error)
+}
diff --git a/examples/fortune/fortune.vdl.go b/examples/fortune/fortune.vdl.go
new file mode 100644
index 0000000..d8a6d44
--- /dev/null
+++ b/examples/fortune/fortune.vdl.go
@@ -0,0 +1,163 @@
+// 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 by the vanadium vdl tool.
+// Source: fortune.vdl
+
+// Package fortune defines the Fortune example interface.
+package fortune
+
+import (
+	// VDL system imports
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+)
+
+// FortuneClientMethods is the client interface
+// containing Fortune methods.
+//
+// Fortune is the interface to a fortune-telling service.
+type FortuneClientMethods interface {
+	// Returns a random fortune.
+	Get(*context.T, ...rpc.CallOpt) (fortune string, err error)
+	// Adds a fortune to the set used by Get().
+	Add(ctx *context.T, fortune string, opts ...rpc.CallOpt) error
+	// Returns whether or not a fortune exists.
+	Has(ctx *context.T, fortune string, opts ...rpc.CallOpt) (bool, error)
+}
+
+// FortuneClientStub adds universal methods to FortuneClientMethods.
+type FortuneClientStub interface {
+	FortuneClientMethods
+	rpc.UniversalServiceMethods
+}
+
+// FortuneClient returns a client stub for Fortune.
+func FortuneClient(name string) FortuneClientStub {
+	return implFortuneClientStub{name}
+}
+
+type implFortuneClientStub struct {
+	name string
+}
+
+func (c implFortuneClientStub) Get(ctx *context.T, opts ...rpc.CallOpt) (o0 string, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "Get", nil, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implFortuneClientStub) Add(ctx *context.T, i0 string, opts ...rpc.CallOpt) (err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "Add", []interface{}{i0}, nil, opts...)
+	return
+}
+
+func (c implFortuneClientStub) Has(ctx *context.T, i0 string, opts ...rpc.CallOpt) (o0 bool, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "Has", []interface{}{i0}, []interface{}{&o0}, opts...)
+	return
+}
+
+// FortuneServerMethods is the interface a server writer
+// implements for Fortune.
+//
+// Fortune is the interface to a fortune-telling service.
+type FortuneServerMethods interface {
+	// Returns a random fortune.
+	Get(*context.T, rpc.ServerCall) (fortune string, err error)
+	// Adds a fortune to the set used by Get().
+	Add(ctx *context.T, call rpc.ServerCall, fortune string) error
+	// Returns whether or not a fortune exists.
+	Has(ctx *context.T, call rpc.ServerCall, fortune string) (bool, error)
+}
+
+// FortuneServerStubMethods is the server interface containing
+// Fortune methods, as expected by rpc.Server.
+// There is no difference between this interface and FortuneServerMethods
+// since there are no streaming methods.
+type FortuneServerStubMethods FortuneServerMethods
+
+// FortuneServerStub adds universal methods to FortuneServerStubMethods.
+type FortuneServerStub interface {
+	FortuneServerStubMethods
+	// Describe the Fortune interfaces.
+	Describe__() []rpc.InterfaceDesc
+}
+
+// FortuneServer returns a server stub for Fortune.
+// It converts an implementation of FortuneServerMethods into
+// an object that may be used by rpc.Server.
+func FortuneServer(impl FortuneServerMethods) FortuneServerStub {
+	stub := implFortuneServerStub{
+		impl: impl,
+	}
+	// Initialize GlobState; always check the stub itself first, to handle the
+	// case where the user has the Glob method defined in their VDL source.
+	if gs := rpc.NewGlobState(stub); gs != nil {
+		stub.gs = gs
+	} else if gs := rpc.NewGlobState(impl); gs != nil {
+		stub.gs = gs
+	}
+	return stub
+}
+
+type implFortuneServerStub struct {
+	impl FortuneServerMethods
+	gs   *rpc.GlobState
+}
+
+func (s implFortuneServerStub) Get(ctx *context.T, call rpc.ServerCall) (string, error) {
+	return s.impl.Get(ctx, call)
+}
+
+func (s implFortuneServerStub) Add(ctx *context.T, call rpc.ServerCall, i0 string) error {
+	return s.impl.Add(ctx, call, i0)
+}
+
+func (s implFortuneServerStub) Has(ctx *context.T, call rpc.ServerCall, i0 string) (bool, error) {
+	return s.impl.Has(ctx, call, i0)
+}
+
+func (s implFortuneServerStub) Globber() *rpc.GlobState {
+	return s.gs
+}
+
+func (s implFortuneServerStub) Describe__() []rpc.InterfaceDesc {
+	return []rpc.InterfaceDesc{FortuneDesc}
+}
+
+// FortuneDesc describes the Fortune interface.
+var FortuneDesc rpc.InterfaceDesc = descFortune
+
+// descFortune hides the desc to keep godoc clean.
+var descFortune = rpc.InterfaceDesc{
+	Name:    "Fortune",
+	PkgPath: "v.io/x/ref/examples/fortune",
+	Doc:     "// Fortune is the interface to a fortune-telling service.",
+	Methods: []rpc.MethodDesc{
+		{
+			Name: "Get",
+			Doc:  "// Returns a random fortune.",
+			OutArgs: []rpc.ArgDesc{
+				{"fortune", ``}, // string
+			},
+		},
+		{
+			Name: "Add",
+			Doc:  "// Adds a fortune to the set used by Get().",
+			InArgs: []rpc.ArgDesc{
+				{"fortune", ``}, // string
+			},
+		},
+		{
+			Name: "Has",
+			Doc:  "// Returns whether or not a fortune exists.",
+			InArgs: []rpc.ArgDesc{
+				{"fortune", ``}, // string
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"", ``}, // bool
+			},
+		},
+	},
+}
diff --git a/examples/fortune/fortune/main.go b/examples/fortune/fortune/main.go
new file mode 100644
index 0000000..6ffe0f4
--- /dev/null
+++ b/examples/fortune/fortune/main.go
@@ -0,0 +1,63 @@
+// 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.
+
+// Command fortune is a client to the Fortune interface.
+package main
+
+import (
+	"flag"
+	"log"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/x/ref/examples/fortune"
+	_ "v.io/x/ref/runtime/factories/generic"
+)
+
+var (
+	name = flag.String("name", "", "Name of the server to connect to")
+	add  = flag.String("add", "", "Fortune string to add")
+	has  = flag.String("has", "", "Fortune string to check")
+)
+
+func main() {
+	ctx, shutdown := v23.Init()
+	defer shutdown()
+
+	client := fortune.FortuneClient(*name)
+
+	// Create a child context that will timeout in 60s.
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
+	defer cancel()
+
+	switch {
+	case *add != "":
+		err := client.Add(ctx, *add)
+		if err != nil {
+			log.Panic("Error adding fortune: ", err)
+		}
+
+		log.Println("Fortune added")
+	case *has != "":
+		exists, err := client.Has(ctx, *has)
+		if err != nil {
+			log.Panic("Error checking fortune: ", err)
+		}
+
+		if exists {
+			log.Printf("\"%v\" exists\n", *has)
+		} else {
+			log.Printf("\"%v\" does not exist\n", *has)
+		}
+	default:
+		// Default for no args is to get a fortune.
+		fortune, err := client.Get(ctx)
+		if err != nil {
+			log.Panic("Error getting fortune: ", err)
+		}
+
+		log.Println(fortune)
+	}
+}
diff --git a/examples/fortune/fortuned/impl.go b/examples/fortune/fortuned/impl.go
new file mode 100644
index 0000000..d6fad2d
--- /dev/null
+++ b/examples/fortune/fortuned/impl.go
@@ -0,0 +1,74 @@
+// 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 (
+	"math/rand"
+	"sync"
+
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/x/ref/examples/fortune"
+)
+
+// Fortune implements fortune.FortuneServerMethods.
+type impl struct {
+	fortunes []string
+	random   *rand.Rand
+	mutex    sync.RWMutex
+}
+
+// newImpl returns a Fortune implementation that can be used to create a service.
+func newImpl() fortune.FortuneServerMethods {
+	return &impl{
+		fortunes: []string{
+			"You will reach the heights of success.",
+			"Conquer your fears or they will conquer you.",
+			"Today is your lucky day!",
+		},
+		random: rand.New(rand.NewSource(99)),
+	}
+}
+
+// Get retrieves a random fortune from the fortunes array.
+func (f *impl) Get(_ *context.T, _ rpc.ServerCall) (fortune string, err error) {
+	f.mutex.RLock()
+	defer f.mutex.RUnlock()
+
+	length := len(f.fortunes)
+	index := f.random.Intn(length)
+
+	return f.fortunes[index], nil
+}
+
+// Add inserts a new fortune into the fortunes array.
+func (f *impl) Add(_ *context.T, _ rpc.ServerCall, fortune string) error {
+	f.mutex.Lock()
+	defer f.mutex.Unlock()
+
+	f.fortunes = append(f.fortunes, fortune)
+
+	return nil
+}
+
+// Has returns a boolean that states wether a fortune exists in the fortunes array.
+func (f *impl) Has(_ *context.T, _ rpc.ServerCall, fortune string) (bool, error) {
+	f.mutex.RLock()
+	defer f.mutex.RUnlock()
+
+	exists := contains(f.fortunes, fortune)
+
+	return exists, nil
+}
+
+func contains(fortunes []string, fortune string) bool {
+	for _, item := range fortunes {
+		if item == fortune {
+			return true
+		}
+	}
+
+	return false
+}
diff --git a/examples/fortune/fortuned/impl_test.go b/examples/fortune/fortuned/impl_test.go
new file mode 100644
index 0000000..248bb03
--- /dev/null
+++ b/examples/fortune/fortuned/impl_test.go
@@ -0,0 +1,68 @@
+// 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 (
+	"testing"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/security"
+	"v.io/x/ref/examples/fortune"
+	"v.io/x/ref/lib/xrpc"
+)
+
+func TestGet(t *testing.T) {
+	ctx, client, shutdown := setup(t)
+	defer shutdown()
+
+	value, err := client.Get(ctx)
+	if err != nil {
+		t.Errorf("Should not error")
+	}
+
+	if value == "" {
+		t.Errorf("Expected non-empty fortune")
+	}
+}
+
+func TestAdd(t *testing.T) {
+	ctx, client, shutdown := setup(t)
+	defer shutdown()
+
+	value := "Lucky numbers: 12 2 35 46 56 4"
+	err := client.Add(ctx, value)
+	if err != nil {
+		t.Errorf("Should not error")
+	}
+
+	added, err := client.Has(ctx, value)
+	if err != nil {
+		t.Errorf("Should not error")
+	}
+
+	if !added {
+		t.Errorf("Expected service to add fortune")
+	}
+}
+
+func setup(t *testing.T) (*context.T, fortune.FortuneClientStub, v23.Shutdown) {
+	ctx, shutdown := v23.Init()
+
+	authorizer := security.DefaultAuthorizer()
+	impl := newImpl()
+	service := fortune.FortuneServer(impl)
+	name := ""
+
+	server, err := xrpc.NewServer(ctx, name, service, authorizer)
+	if err != nil {
+		t.Errorf("Failure creating server: %v", err)
+	}
+
+	endpoint := server.Status().Endpoints[0].Name()
+	client := fortune.FortuneClient(endpoint)
+
+	return ctx, client, shutdown
+}
diff --git a/examples/fortune/fortuned/main.go b/examples/fortune/fortuned/main.go
new file mode 100644
index 0000000..deb214e
--- /dev/null
+++ b/examples/fortune/fortuned/main.go
@@ -0,0 +1,40 @@
+// 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.
+
+// Command fortuned runs a daemon that implements the Fortune interface.
+package main
+
+import (
+	"flag"
+	"log"
+
+	"v.io/v23"
+	"v.io/v23/security"
+	"v.io/x/ref/examples/fortune"
+	"v.io/x/ref/lib/signals"
+	"v.io/x/ref/lib/xrpc"
+	// The v23.Init call below will use the generic runtime factory.
+	_ "v.io/x/ref/runtime/factories/generic"
+)
+
+var (
+	name = flag.String("name", "", "Name for fortuned in default mount table")
+)
+
+func main() {
+	ctx, shutdown := v23.Init()
+	defer shutdown()
+
+	authorizer := security.DefaultAuthorizer()
+	impl := newImpl()
+	service := fortune.FortuneServer(impl)
+
+	server, err := xrpc.NewServer(ctx, *name, service, authorizer)
+	if err != nil {
+		log.Panic("Failure creating server: ", err)
+	}
+	log.Printf("Listening at: %v\n", server.Status().Endpoints[0])
+
+	<-signals.ShutdownOnSignals(ctx)
+}
diff --git a/examples/rps/internal/auth.go b/examples/rps/internal/auth.go
new file mode 100644
index 0000000..a01b128
--- /dev/null
+++ b/examples/rps/internal/auth.go
@@ -0,0 +1,18 @@
+// 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
+
+import (
+	"v.io/v23/security"
+	"v.io/v23/security/access"
+)
+
+func NewAuthorizer(fname string) security.Authorizer {
+	a, err := access.PermissionsAuthorizerFromFile(fname, access.TypicalTagType())
+	if err != nil {
+		panic(err)
+	}
+	return a
+}
diff --git a/examples/rps/internal/common.go b/examples/rps/internal/common.go
new file mode 100644
index 0000000..2d15a8e
--- /dev/null
+++ b/examples/rps/internal/common.go
@@ -0,0 +1,120 @@
+// 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 defines common functions used by both rock paper scissors
+// clients and servers.
+package internal
+
+import (
+	"bytes"
+	"errors"
+	"fmt"
+	"math/rand"
+	"os"
+	"os/user"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/naming"
+
+	"v.io/x/ref/examples/rps"
+	"v.io/x/ref/internal/logger"
+)
+
+// CreateName creates a name using the username and hostname.
+func CreateName() string {
+	hostname, err := os.Hostname()
+	if err != nil {
+		logger.Global().Fatalf("os.Hostname failed: %v", err)
+	}
+	u, err := user.Current()
+	if err != nil {
+		logger.Global().Fatalf("user.Current failed: %v", err)
+	}
+	return u.Username + "@" + hostname
+}
+
+// FindJudge returns a random rock-paper-scissors judge from the mount table.
+func FindJudge(ctx *context.T) (string, error) {
+	judges, err := findAll(ctx, "judge")
+	if err != nil {
+		return "", err
+	}
+	if len(judges) > 0 {
+		return judges[rand.Intn(len(judges))], nil
+	}
+	return "", errors.New("no judges")
+}
+
+// FindPlayer returns a random rock-paper-scissors player from the mount table.
+func FindPlayer(ctx *context.T) (string, error) {
+	players, err := findAll(ctx, "player")
+	if err != nil {
+		return "", err
+	}
+	if len(players) > 0 {
+		return players[rand.Intn(len(players))], nil
+	}
+	return "", errors.New("no players")
+}
+
+// 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")
+	if err != nil {
+		return nil, err
+	}
+	return sKeepers, nil
+}
+
+func findAll(ctx *context.T, t string) ([]string, error) {
+	start := time.Now()
+	ns := v23.GetNamespace(ctx)
+	c, err := ns.Glob(ctx, "rps/"+t+"/*")
+	if err != nil {
+		ctx.Infof("mt.Glob failed: %v", err)
+		return nil, err
+	}
+	var servers []string
+	for e := range c {
+		switch v := e.(type) {
+		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)
+		}
+	}
+	ctx.VI(1).Infof("findAll(%q) elapsed: %s", t, time.Now().Sub(start))
+	return servers, nil
+}
+
+// FormatScoreCard returns a string representation of a score card.
+func FormatScoreCard(score rps.ScoreCard) string {
+	buf := bytes.NewBufferString("")
+	var gameType string
+	switch score.Opts.GameType {
+	case rps.Classic:
+		gameType = "Classic"
+	case rps.LizardSpock:
+		gameType = "LizardSpock"
+	default:
+		gameType = "Unknown"
+	}
+	fmt.Fprintf(buf, "Game Type: %s\n", gameType)
+	fmt.Fprintf(buf, "Number of rounds: %d\n", score.Opts.NumRounds)
+	fmt.Fprintf(buf, "Judge: %s\n", score.Judge)
+	fmt.Fprintf(buf, "Player 1: %s\n", score.Players[0])
+	fmt.Fprintf(buf, "Player 2: %s\n", score.Players[1])
+	for i, r := range score.Rounds {
+		roundOffset := r.StartTime.Sub(score.StartTime)
+		roundTime := r.EndTime.Sub(r.StartTime)
+		fmt.Fprintf(buf, "Round %2d: Player 1 played %-10q. Player 2 played %-10q. Winner: %d %-28s [%-10s/%-10s]\n",
+			i+1, r.Moves[0], r.Moves[1], r.Winner, r.Comment, roundOffset, roundTime)
+	}
+	fmt.Fprintf(buf, "Winner: %d\n", score.Winner)
+	fmt.Fprintf(buf, "Time: %s\n", score.EndTime.Sub(score.StartTime))
+	return buf.String()
+}
diff --git a/examples/rps/rpsbot/doc.go b/examples/rps/rpsbot/doc.go
new file mode 100644
index 0000000..1f94e3a
--- /dev/null
+++ b/examples/rps/rpsbot/doc.go
@@ -0,0 +1,73 @@
+// 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
+
+/*
+Command rpsbot repeatedly runs automated games, implementing all three roles. It
+publishes itself as player, judge, and scorekeeper. Then, it initiates games
+with other players, in a loop. As soon as one game is over, it starts a new one.
+
+Usage:
+   rpsbot [flags]
+
+The rpsbot flags are:
+ -acl-file=
+   File containing JSON-encoded Permissions.
+ -name=
+   Identifier to publish as (defaults to user@hostname).
+ -num-games=-1
+   Number of games to play (-1 means unlimited).
+
+The global flags are:
+ -alsologtostderr=true
+   log to standard error as well as files
+ -log_backtrace_at=:0
+   when logging hits line file:N, emit a stack trace
+ -log_dir=
+   if non-empty, write log files to this directory
+ -logtostderr=false
+   log to standard error instead of files
+ -max_stack_buf_size=4292608
+   max size in bytes of the buffer to use for logging stack traces
+ -metadata=<just specify -metadata to activate>
+   Displays metadata for the program and exits.
+ -stderrthreshold=2
+   logs at or above this threshold go to stderr
+ -v=0
+   log level for V logs
+ -v23.credentials=
+   directory to use for storing security credentials
+ -v23.i18n-catalogue=
+   18n catalogue files to load, comma separated
+ -v23.namespace.root=[/(dev.v.io/role/vprod/service/mounttabled)@ns.dev.v.io:8101]
+   local namespace root; can be repeated to provided multiple roots
+ -v23.permissions.file=map[]
+   specify a perms file as <name>:<permsfile>
+ -v23.permissions.literal=
+   explicitly specify the runtime perms as a JSON-encoded access.Permissions.
+   Overrides all --v23.permissions.file flags.
+ -v23.proxy=
+   object name of proxy service to use to export services across network
+   boundaries
+ -v23.tcp.address=
+   address to listen on
+ -v23.tcp.protocol=wsh
+   protocol to listen with
+ -v23.vtrace.cache-size=1024
+   The number of vtrace traces to store in memory.
+ -v23.vtrace.collect-regexp=
+   Spans and annotations that match this regular expression will trigger trace
+   collection.
+ -v23.vtrace.dump-on-shutdown=true
+   If true, dump all stored traces on runtime shutdown.
+ -v23.vtrace.sample-rate=0
+   Rate (from 0.0 to 1.0) to sample vtrace traces.
+ -vmodule=
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
+*/
+package main
diff --git a/examples/rps/rpsbot/impl.go b/examples/rps/rpsbot/impl.go
new file mode 100644
index 0000000..3223ac8
--- /dev/null
+++ b/examples/rps/rpsbot/impl.go
@@ -0,0 +1,73 @@
+// 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 (
+	"errors"
+
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/vtrace"
+	"v.io/x/ref/examples/rps"
+)
+
+// RPS implements rps.RockPaperScissorsServerMethods
+type RPS struct {
+	player      *Player
+	judge       *Judge
+	scoreKeeper *ScoreKeeper
+	ctx         *context.T
+}
+
+func NewRPS(ctx *context.T) *RPS {
+	return &RPS{player: NewPlayer(), judge: NewJudge(), scoreKeeper: NewScoreKeeper(), ctx: ctx}
+}
+
+func (r *RPS) Judge() *Judge {
+	return r.judge
+}
+
+func (r *RPS) Player() *Player {
+	return r.player
+}
+
+func (r *RPS) ScoreKeeper() *ScoreKeeper {
+	return r.scoreKeeper
+}
+
+func (r *RPS) CreateGame(ctx *context.T, call rpc.ServerCall, opts rps.GameOptions) (rps.GameId, error) {
+	if ctx.V(1) {
+		b, _ := security.RemoteBlessingNames(ctx, call.Security())
+		ctx.Infof("CreateGame %+v from %v", opts, b)
+	}
+	names := security.LocalBlessingNames(ctx, call.Security())
+	if len(names) == 0 {
+		return rps.GameId{}, errors.New("no names provided for context")
+	}
+	return r.judge.createGame(names[0], opts)
+}
+
+func (r *RPS) Play(ctx *context.T, call rps.JudgePlayServerCall, id rps.GameId) (rps.PlayResult, error) {
+	names, _ := security.RemoteBlessingNames(ctx, call.Security())
+	ctx.VI(1).Infof("Play %+v from %v", id, names)
+	if len(names) == 0 {
+		return rps.PlayResult{}, errors.New("no names provided for context")
+	}
+	return r.judge.play(ctx, call, names[0], id)
+}
+
+func (r *RPS) Challenge(ctx *context.T, call rpc.ServerCall, address string, id rps.GameId, opts rps.GameOptions) error {
+	b, _ := security.RemoteBlessingNames(ctx, call.Security())
+	ctx.VI(1).Infof("Challenge (%q, %+v, %+v) from %v", address, id, opts, b)
+	newctx, _ := vtrace.WithNewTrace(r.ctx)
+	return r.player.challenge(newctx, address, id, opts)
+}
+
+func (r *RPS) Record(ctx *context.T, call rpc.ServerCall, score rps.ScoreCard) error {
+	b, _ := security.RemoteBlessingNames(ctx, call.Security())
+	ctx.VI(1).Infof("Record (%+v) from %v", score, b)
+	return r.scoreKeeper.Record(ctx, call, score)
+}
diff --git a/examples/rps/rpsbot/impl_test.go b/examples/rps/rpsbot/impl_test.go
new file mode 100644
index 0000000..2e93780
--- /dev/null
+++ b/examples/rps/rpsbot/impl_test.go
@@ -0,0 +1,183 @@
+// 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 (
+	"bufio"
+	"bytes"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"regexp"
+	"runtime"
+	"runtime/pprof"
+	"strconv"
+	"testing"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/options"
+	"v.io/x/ref/examples/rps"
+	"v.io/x/ref/lib/xrpc"
+	"v.io/x/ref/services/mounttable/mounttablelib"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
+)
+
+//go:generate v23 test generate
+
+var rootMT = modules.Register(func(env *modules.Env, args ...string) error {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	mt, err := mounttablelib.NewMountTableDispatcher(ctx, "", "", "mounttable")
+	if err != nil {
+		return fmt.Errorf("mounttablelib.NewMountTableDispatcher failed: %s", err)
+	}
+	server, err := xrpc.NewDispatchingServer(ctx, "", mt, options.ServesMountTable(true))
+	if err != nil {
+		return fmt.Errorf("root failed: %v", err)
+	}
+	fmt.Fprintf(env.Stdout, "PID=%d\n", os.Getpid())
+	for _, ep := range server.Status().Endpoints {
+		fmt.Fprintf(env.Stdout, "MT_NAME=%s\n", ep.Name())
+	}
+	modules.WaitForEOF(env.Stdin)
+	return nil
+}, "rootMT")
+
+func startRockPaperScissors(t *testing.T, ctx *context.T, mtAddress string) (*RPS, func()) {
+	ns := v23.GetNamespace(ctx)
+	ns.SetRoots(mtAddress)
+	rpsService := NewRPS(ctx)
+	names := []string{"rps/judge/test", "rps/player/test", "rps/scorekeeper/test"}
+	server, err := xrpc.NewServer(ctx, names[0], rps.RockPaperScissorsServer(rpsService), nil)
+	if err != nil {
+		t.Fatalf("NewServer failed: %v", err)
+	}
+	for _, n := range names[1:] {
+		server.AddName(n)
+	}
+	return rpsService, func() {
+		if err := server.Stop(); err != nil {
+			t.Fatalf("Stop() failed: %v", err)
+		}
+	}
+}
+
+// TestRockPaperScissorsImpl runs one rock-paper-scissors game and verifies
+// that all the counters are consistent.
+func TestRockPaperScissorsImpl(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	sh, err := modules.NewShell(ctx, nil, testing.Verbose(), t)
+	if err != nil {
+		t.Fatalf("Could not create shell: %v", err)
+	}
+	defer sh.Cleanup(os.Stdout, os.Stderr)
+	h, err := sh.Start(nil, rootMT, "--v23.tcp.address=127.0.0.1:0")
+	if err != nil {
+		if h != nil {
+			h.Shutdown(nil, os.Stderr)
+		}
+		t.Fatalf("unexpected error for root mt: %s", err)
+	}
+	h.ExpectVar("PID")
+	mtAddress := h.ExpectVar("MT_NAME")
+
+	rpsService, rpsStop := startRockPaperScissors(t, ctx, mtAddress)
+	defer rpsStop()
+
+	const numGames = 100
+	for x := 0; x < numGames; x++ {
+		if err := rpsService.Player().InitiateGame(ctx); err != nil {
+			t.Errorf("Failed to initiate game: %v", err)
+		}
+	}
+	rpsService.Player().WaitUntilIdle()
+
+	// For each game, the player plays twice. So, we expect the player to
+	// show that it played 2×numGames, and won numGames.
+	played, won := rpsService.Player().Stats()
+	if want, got := int64(2*numGames), played; want != got {
+		t.Errorf("Unexpected number of played games. Got %d, want %d", got, want)
+	}
+	if want, got := int64(numGames), won; want != got {
+		t.Errorf("Unexpected number of won games. Got %d, want %d", got, want)
+	}
+
+	// The Judge ran every game.
+	if want, got := int64(numGames), rpsService.Judge().Stats(); want != got {
+		t.Errorf("Unexpected number of games run. Got %d, want %d", got, want)
+	}
+
+	// The Score Keeper received one score card per game.
+	if want, got := int64(numGames), rpsService.ScoreKeeper().Stats(); want != got {
+		t.Errorf("Unexpected number of score cards. Got %d, want %d", got, want)
+	}
+
+	// Check for leaked goroutines.
+	if n := runtime.NumGoroutine(); n > 100 {
+		t.Logf("Num goroutines: %d", n)
+		if leak := reportLeakedGoroutines(t, n/3); len(leak) != 0 {
+			t.Errorf("Potentially leaked goroutines:\n%s", leak)
+		}
+	}
+}
+
+// reportLeakedGoroutines reads the goroutine pprof profile and returns the
+// goroutines that have more than 'threshold' instances.
+func reportLeakedGoroutines(t *testing.T, threshold int) string {
+	f, err := ioutil.TempFile("", "test-profile-")
+	if err != nil {
+		t.Fatalf("Failed to create temp file: %v", err)
+	}
+	defer f.Close()
+	defer os.Remove(f.Name())
+	p := pprof.Lookup("goroutine")
+	if p == nil {
+		t.Fatalf("Failed to lookup the goroutine profile")
+	}
+	if err := p.WriteTo(f, 1); err != nil {
+		t.Fatalf("Failed to write profile: %v", err)
+	}
+
+	re, err := regexp.Compile(`^([0-9]+) @`)
+	if err != nil {
+		t.Fatalf("Failed to compile regexp")
+	}
+	f.Seek(0, 0)
+	r := bufio.NewReader(f)
+
+	var buf bytes.Buffer
+	var printing bool
+	for {
+		line, err := r.ReadBytes('\n')
+		if err == io.EOF {
+			break
+		}
+		if err != nil {
+			t.Fatalf("Failed to read bytes: %v", err)
+		}
+		if len(line) <= 1 {
+			printing = false
+			continue
+		}
+		if !printing {
+			if m := re.FindSubmatch(line); len(m) == 2 {
+				count, _ := strconv.Atoi(string(m[1]))
+				if count > threshold {
+					printing = true
+				}
+			}
+		}
+		if printing {
+			buf.Write(line)
+		}
+	}
+	return buf.String()
+}
diff --git a/examples/rps/rpsbot/judge.go b/examples/rps/rpsbot/judge.go
new file mode 100644
index 0000000..f63e15f
--- /dev/null
+++ b/examples/rps/rpsbot/judge.go
@@ -0,0 +1,366 @@
+// 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 (
+	"errors"
+	"fmt"
+	"strconv"
+	"sync"
+	"time"
+
+	"v.io/v23/context"
+
+	"v.io/x/ref/examples/rps"
+	"v.io/x/ref/examples/rps/internal"
+	"v.io/x/ref/internal/logger"
+	"v.io/x/ref/lib/stats"
+	"v.io/x/ref/lib/stats/counter"
+)
+
+var (
+	errBadGameID      = errors.New("requested gameID doesn't exist")
+	errTooManyPlayers = errors.New("all players are already seated")
+)
+
+type Judge struct {
+	lock     sync.Mutex
+	games    map[rps.GameId]*gameInfo
+	gamesRun *counter.Counter
+}
+
+type sendStream interface {
+	Send(item rps.JudgeAction) error
+}
+
+type gameInfo struct {
+	id        rps.GameId
+	startTime time.Time
+	score     rps.ScoreCard
+	streams   []sendStream
+	playerIn  chan playerInput
+	playerOut []chan rps.JudgeAction
+	scoreChan chan scoreData
+}
+
+func (g *gameInfo) moveOptions() []string {
+	switch g.score.Opts.GameType {
+	case rps.LizardSpock:
+		return []string{"rock", "paper", "scissors", "lizard", "spock"}
+	default:
+		return []string{"rock", "paper", "scissors"}
+	}
+}
+
+func (g *gameInfo) validMove(m string) bool {
+	for _, x := range g.moveOptions() {
+		if x == m {
+			return true
+		}
+	}
+	return false
+}
+
+type playerInput struct {
+	player int
+	action rps.PlayerAction
+}
+
+type scoreData struct {
+	err   error
+	score rps.ScoreCard
+}
+
+func NewJudge() *Judge {
+	return &Judge{
+		games:    make(map[rps.GameId]*gameInfo),
+		gamesRun: stats.NewCounter("judge/games-run"),
+	}
+}
+
+func (j *Judge) Stats() int64 {
+	return j.gamesRun.Value()
+}
+
+// createGame handles a request to create a new game.
+func (j *Judge) createGame(ownName string, opts rps.GameOptions) (rps.GameId, error) {
+	logger.Global().VI(1).Infof("createGame called")
+	score := rps.ScoreCard{Opts: opts, Judge: ownName}
+	now := time.Now()
+	id := rps.GameId{Id: strconv.FormatInt(now.UnixNano(), 16)}
+
+	j.lock.Lock()
+	defer j.lock.Unlock()
+	for k, v := range j.games {
+		if now.Sub(v.startTime) > 1*time.Hour {
+			logger.Global().Infof("Removing stale game ID %v", k)
+			delete(j.games, k)
+		}
+	}
+	j.games[id] = &gameInfo{
+		id:        id,
+		startTime: now,
+		score:     score,
+		playerIn:  make(chan playerInput),
+		playerOut: []chan rps.JudgeAction{
+			make(chan rps.JudgeAction),
+			make(chan rps.JudgeAction),
+		},
+		scoreChan: make(chan scoreData),
+	}
+	return id, nil
+}
+
+// play interacts with a player for the duration of a game.
+func (j *Judge) play(ctx *context.T, call rps.JudgePlayServerCall, name string, id rps.GameId) (rps.PlayResult, error) {
+	ctx.VI(1).Infof("play from %q for %v", name, id)
+	nilResult := rps.PlayResult{}
+
+	pIn, pOut, s, err := j.gameChannels(id)
+	if err != nil {
+		return nilResult, err
+	}
+	playerNum, err := j.addPlayer(name, id, call)
+	if err != nil {
+		return nilResult, err
+	}
+	ctx.VI(1).Infof("This is player %d", playerNum)
+
+	// Send all user input to the player input channel.
+	stopRead := make(chan struct{})
+	defer close(stopRead)
+	go func() {
+		rStream := call.RecvStream()
+		for rStream.Advance() {
+			action := rStream.Value()
+			select {
+			case pIn <- playerInput{player: playerNum, action: action}:
+			case <-stopRead:
+				return
+			}
+		}
+		select {
+		case pIn <- playerInput{player: playerNum, action: rps.PlayerActionQuit{}}:
+		case <-stopRead:
+		}
+	}()
+	// Send all the output to the user.
+	writeDone := make(chan struct{})
+	go func() {
+		defer close(writeDone)
+		for packet := range pOut[playerNum-1] {
+			if err := call.SendStream().Send(packet); err != nil {
+				ctx.Infof("error sending to player stream: %v", err)
+			}
+		}
+	}()
+	defer func() {
+		close(pOut[playerNum-1])
+		<-writeDone
+	}()
+
+	pOut[playerNum-1] <- rps.JudgeActionPlayerNum{int32(playerNum)}
+
+	// When the second player connects, we start the game.
+	if playerNum == 2 {
+		go j.manageGame(ctx, id)
+	}
+	// Wait for the ScoreCard.
+	scoreData := <-s
+	if scoreData.err != nil {
+		return rps.PlayResult{}, scoreData.err
+	}
+	return rps.PlayResult{YouWon: scoreData.score.Winner == rps.WinnerTag(playerNum)}, nil
+}
+
+func (j *Judge) manageGame(ctx *context.T, id rps.GameId) {
+	j.gamesRun.Incr(1)
+	j.lock.Lock()
+	info, exists := j.games[id]
+	if !exists {
+		e := scoreData{err: errBadGameID}
+		info.scoreChan <- e
+		info.scoreChan <- e
+		return
+	}
+	delete(j.games, id)
+	j.lock.Unlock()
+
+	// Inform each player of their opponent's name.
+	for p := 0; p < 2; p++ {
+		opp := 1 - p
+		info.playerOut[p] <- rps.JudgeActionOpponentName{info.score.Players[opp]}
+	}
+
+	win1, win2 := 0, 0
+	goal := int(info.score.Opts.NumRounds)
+	// Play until one player has won 'goal' times.
+	for win1 < goal && win2 < goal {
+		round, err := j.playOneRound(info)
+		if err != nil {
+			err := scoreData{err: err}
+			info.scoreChan <- err
+			info.scoreChan <- err
+			return
+		}
+		if round.Winner == rps.Player1 {
+			win1++
+		} else if round.Winner == rps.Player2 {
+			win2++
+		}
+		info.score.Rounds = append(info.score.Rounds, round)
+	}
+	if win1 > win2 {
+		info.score.Winner = rps.Player1
+	} else {
+		info.score.Winner = rps.Player2
+	}
+
+	info.score.StartTime = info.startTime
+	info.score.EndTime = time.Now()
+
+	// Send the score card to the players.
+	action := rps.JudgeActionScore{info.score}
+	for _, p := range info.playerOut {
+		p <- action
+	}
+
+	// Send the score card to the score keepers.
+	scoreCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
+	defer cancel()
+	keepers, err := internal.FindScoreKeepers(scoreCtx)
+	if err != nil || len(keepers) == 0 {
+		ctx.Infof("No score keepers: %v", err)
+		return
+	}
+	var wg sync.WaitGroup
+	wg.Add(len(keepers))
+	for _, k := range keepers {
+		go j.sendScore(scoreCtx, k, info.score, &wg)
+	}
+	wg.Wait()
+
+	info.scoreChan <- scoreData{score: info.score}
+	info.scoreChan <- scoreData{score: info.score}
+}
+
+func (j *Judge) playOneRound(info *gameInfo) (rps.Round, error) {
+	round := rps.Round{StartTime: time.Now()}
+	var action rps.JudgeAction
+	action = rps.JudgeActionMoveOptions{info.moveOptions()}
+	for _, p := range info.playerOut {
+		p <- action
+	}
+	for x := 0; x < 2; x++ {
+		in := <-info.playerIn
+		switch v := in.action.(type) {
+		case rps.PlayerActionQuit:
+			return round, fmt.Errorf("player %d quit the game", in.player)
+		case rps.PlayerActionMove:
+			move := v.Value
+			if !info.validMove(move) {
+				return round, fmt.Errorf("player %d made an invalid move: %s", in.player, move)
+			}
+			if len(round.Moves[in.player-1]) > 0 {
+				return round, fmt.Errorf("player %d played twice in the same round!", in.player)
+			}
+			round.Moves[in.player-1] = move
+		default:
+			logger.Global().Infof("unexpected message type: %T", in.action)
+		}
+	}
+	round.Winner, round.Comment = j.compareMoves(round.Moves[0], round.Moves[1])
+	logger.Global().VI(1).Infof("Player 1 played %q. Player 2 played %q. Winner: %d %s", round.Moves[0], round.Moves[1], round.Winner, round.Comment)
+
+	action = rps.JudgeActionRoundResult{round}
+	for _, p := range info.playerOut {
+		p <- action
+	}
+	round.EndTime = time.Now()
+	return round, nil
+}
+
+func (j *Judge) addPlayer(name string, id rps.GameId, stream rps.JudgePlayServerStream) (int, error) {
+	j.lock.Lock()
+	defer j.lock.Unlock()
+
+	info, exists := j.games[id]
+	if !exists {
+		return 0, errBadGameID
+	}
+	if len(info.streams) == 2 {
+		return 0, errTooManyPlayers
+	}
+	info.score.Players = append(info.score.Players, name)
+	info.streams = append(info.streams, stream.SendStream())
+	return len(info.streams), nil
+}
+
+func (j *Judge) gameChannels(id rps.GameId) (chan playerInput, []chan rps.JudgeAction, chan scoreData, error) {
+	j.lock.Lock()
+	defer j.lock.Unlock()
+	info, exists := j.games[id]
+	if !exists {
+		return nil, nil, nil, errBadGameID
+	}
+	return info.playerIn, info.playerOut, info.scoreChan, nil
+}
+
+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 {
+		logger.Global().Infof("Record: %v", err)
+		return err
+	}
+	return nil
+}
+
+var moveComments = map[string]string{
+	"lizard-paper":    "lizard eats paper",
+	"lizard-rock":     "rock crushes lizard",
+	"lizard-scissors": "scissors decapitates lizard",
+	"lizard-spock":    "lizard poisons spock",
+	"paper-rock":      "paper covers rock",
+	"paper-scissors":  "scissors cuts paper",
+	"paper-spock":     "paper disproves spock",
+	"rock-scissors":   "rock crushes scissors",
+	"rock-spock":      "spock vaporizes rock",
+	"scissors-spock":  "spock smashes scissors",
+}
+
+func (j *Judge) compareMoves(m1, m2 string) (winner rps.WinnerTag, comment string) {
+	if m1 < m2 {
+		comment = moveComments[m1+"-"+m2]
+	} else {
+		comment = moveComments[m2+"-"+m1]
+	}
+	if m1 == m2 {
+		winner = rps.Draw
+		return
+	}
+	if m1 == "rock" && (m2 == "scissors" || m2 == "lizard") {
+		winner = rps.Player1
+		return
+	}
+	if m1 == "paper" && (m2 == "rock" || m2 == "spock") {
+		winner = rps.Player1
+		return
+	}
+	if m1 == "scissors" && (m2 == "paper" || m2 == "lizard") {
+		winner = rps.Player1
+		return
+	}
+	if m1 == "lizard" && (m2 == "paper" || m2 == "spock") {
+		winner = rps.Player1
+		return
+	}
+	if m1 == "spock" && (m2 == "scissors" || m2 == "rock") {
+		winner = rps.Player1
+		return
+	}
+	winner = rps.Player2
+	return
+}
diff --git a/examples/rps/rpsbot/main.go b/examples/rps/rpsbot/main.go
new file mode 100644
index 0000000..86bcef5
--- /dev/null
+++ b/examples/rps/rpsbot/main.go
@@ -0,0 +1,87 @@
+// 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.
+
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go . -help
+
+package main
+
+import (
+	"fmt"
+	"math/rand"
+	"time"
+
+	"v.io/x/lib/cmdline"
+
+	"v.io/v23/context"
+
+	"v.io/x/ref/examples/rps"
+	"v.io/x/ref/examples/rps/internal"
+	"v.io/x/ref/lib/signals"
+	"v.io/x/ref/lib/v23cmd"
+	"v.io/x/ref/lib/xrpc"
+
+	_ "v.io/x/ref/runtime/factories/roaming"
+)
+
+var (
+	name, aclFile string
+	numGames      int
+)
+
+func main() {
+	cmdRoot.Flags.StringVar(&name, "name", "", "Identifier to publish as (defaults to user@hostname).")
+	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).")
+	cmdline.HideGlobalFlagsExcept()
+	cmdline.Main(cmdRoot)
+}
+
+var cmdRoot = &cmdline.Command{
+	Runner: v23cmd.RunnerFunc(runBot),
+	Name:   "rpsbot",
+	Short:  "repeatedly runs automated games",
+	Long: `
+Command rpsbot repeatedly runs automated games, implementing all three roles.
+It publishes itself as player, judge, and scorekeeper. Then, it initiates games
+with other players, in a loop. As soon as one game is over, it starts a new one.
+`,
+}
+
+func runBot(ctx *context.T, env *cmdline.Env, args []string) error {
+	auth := internal.NewAuthorizer(aclFile)
+	rand.Seed(time.Now().UnixNano())
+	rpsService := NewRPS(ctx)
+	if name == "" {
+		name = internal.CreateName()
+	}
+	names := []string{
+		fmt.Sprintf("rps/judge/%s", name),
+		fmt.Sprintf("rps/player/%s", name),
+		fmt.Sprintf("rps/scorekeeper/%s", name),
+	}
+	server, err := xrpc.NewServer(ctx, names[0], rps.RockPaperScissorsServer(rpsService), auth)
+	if err != nil {
+		return fmt.Errorf("NewServer failed: %v", err)
+	}
+	for _, n := range names[1:] {
+		if err := server.AddName(n); err != nil {
+			return fmt.Errorf("(%v) failed: %v", n, err)
+		}
+	}
+	ctx.Infof("Listening on endpoint %s (published as %v)", server.Status().Endpoints[0], names)
+
+	go initiateGames(ctx, rpsService)
+	<-signals.ShutdownOnSignals(ctx)
+	return nil
+}
+
+func initiateGames(ctx *context.T, rpsService *RPS) {
+	for i := 0; i < numGames || numGames == -1; i++ {
+		if err := rpsService.Player().InitiateGame(ctx); err != nil {
+			ctx.Infof("Failed to initiate game: %v", err)
+		}
+		time.Sleep(5 * time.Second)
+	}
+}
diff --git a/examples/rps/rpsbot/player.go b/examples/rps/rpsbot/player.go
new file mode 100644
index 0000000..592ffcb
--- /dev/null
+++ b/examples/rps/rpsbot/player.go
@@ -0,0 +1,159 @@
+// 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 (
+	"math/rand"
+	"time"
+
+	"v.io/v23/context"
+
+	"v.io/x/ref/examples/rps"
+	"v.io/x/ref/examples/rps/internal"
+	"v.io/x/ref/lib/stats"
+	"v.io/x/ref/lib/stats/counter"
+)
+
+type Player struct {
+	gamesPlayed     *counter.Counter
+	gamesWon        *counter.Counter
+	gamesInProgress *stats.Integer
+}
+
+func NewPlayer() *Player {
+	return &Player{
+		gamesPlayed:     stats.NewCounter("player/games-played"),
+		gamesWon:        stats.NewCounter("player/games-won"),
+		gamesInProgress: stats.NewInteger("player/games-in-progress"),
+	}
+}
+
+func (p *Player) Stats() (played, won int64) {
+	played = p.gamesPlayed.Value()
+	won = p.gamesWon.Value()
+	return
+}
+
+// only used by tests.
+func (p *Player) WaitUntilIdle() {
+	for p.gamesInProgress.Value() != int64(0) {
+		time.Sleep(10 * time.Millisecond)
+	}
+}
+
+func (p *Player) InitiateGame(ctx *context.T) error {
+	judge, err := internal.FindJudge(ctx)
+	if err != nil {
+		ctx.Infof("FindJudge: %v", err)
+		return err
+	}
+	gameID, gameOpts, err := p.createGame(ctx, judge)
+	if err != nil {
+		ctx.Infof("createGame: %v", err)
+		return err
+	}
+	ctx.VI(1).Infof("Created gameID %q on %q", gameID, judge)
+
+	for {
+		opponent, err := internal.FindPlayer(ctx)
+		if err != nil {
+			ctx.Infof("FindPlayer: %v", err)
+			return err
+		}
+		ctx.VI(1).Infof("chosen opponent is %q", opponent)
+		if err = p.sendChallenge(ctx, opponent, judge, gameID, gameOpts); err == nil {
+			break
+		}
+		ctx.Infof("sendChallenge: %v", err)
+	}
+	result, err := p.playGame(ctx, judge, gameID)
+	if err != nil {
+		ctx.Infof("playGame: %v", err)
+		return err
+	}
+	if result.YouWon {
+		ctx.VI(1).Info("Game result: I won! :)")
+	} else {
+		ctx.VI(1).Info("Game result: I lost :(")
+	}
+	return nil
+}
+
+func (p *Player) createGame(ctx *context.T, server string) (rps.GameId, rps.GameOptions, error) {
+	j := rps.RockPaperScissorsClient(server)
+	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)
+	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)
+}
+
+// challenge receives an incoming challenge and starts to play a new game.
+// Note that the new game will occur in a new context.
+func (p *Player) challenge(ctx *context.T, judge string, gameID rps.GameId, _ rps.GameOptions) error {
+	ctx.VI(1).Infof("challenge received: %s %v", judge, gameID)
+	go p.playGame(ctx, judge, gameID)
+	return nil
+}
+
+// playGame plays an entire game, which really only consists of reading
+// commands from the server, and picking a random "move" when asked to.
+func (p *Player) playGame(outer *context.T, judge string, gameID rps.GameId) (rps.PlayResult, error) {
+	ctx, cancel := context.WithTimeout(outer, 10*time.Minute)
+	defer cancel()
+	p.gamesInProgress.Incr(1)
+	defer p.gamesInProgress.Incr(-1)
+	j := rps.RockPaperScissorsClient(judge)
+	game, err := j.Play(ctx, gameID)
+	if err != nil {
+		return rps.PlayResult{}, err
+	}
+	rStream := game.RecvStream()
+	sender := game.SendStream()
+	for rStream.Advance() {
+		in := rStream.Value()
+		switch v := in.(type) {
+		case rps.JudgeActionPlayerNum:
+			outer.VI(1).Infof("I'm player %d", v.Value)
+		case rps.JudgeActionOpponentName:
+			outer.VI(1).Infof("My opponent is %q", v.Value)
+		case rps.JudgeActionMoveOptions:
+			opts := v.Value
+			n := rand.Intn(len(opts))
+			outer.VI(1).Infof("My turn to play. Picked %q from %v", opts[n], opts)
+			if err := sender.Send(rps.PlayerActionMove{Value: opts[n]}); err != nil {
+				return rps.PlayResult{}, err
+			}
+		case rps.JudgeActionRoundResult:
+			rr := v.Value
+			outer.VI(1).Infof("Player 1 played %q. Player 2 played %q. Winner: %v %s",
+				rr.Moves[0], rr.Moves[1], rr.Winner, rr.Comment)
+		case rps.JudgeActionScore:
+			outer.VI(1).Infof("Score card: %s", internal.FormatScoreCard(v.Value))
+		default:
+			outer.Infof("unexpected message type: %T", in)
+		}
+	}
+
+	if err := rStream.Err(); err != nil {
+		outer.Infof("stream error: %v", err)
+	} else {
+		outer.VI(1).Infof("Game Ended")
+	}
+	result, err := game.Finish()
+	p.gamesPlayed.Incr(1)
+	if err == nil && result.YouWon {
+		p.gamesWon.Incr(1)
+	}
+	return result, err
+}
diff --git a/examples/rps/rpsbot/scorekeeper.go b/examples/rps/rpsbot/scorekeeper.go
new file mode 100644
index 0000000..900ec24
--- /dev/null
+++ b/examples/rps/rpsbot/scorekeeper.go
@@ -0,0 +1,37 @@
+// 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 (
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/x/ref/examples/rps"
+	"v.io/x/ref/examples/rps/internal"
+	"v.io/x/ref/lib/stats"
+	"v.io/x/ref/lib/stats/counter"
+)
+
+type ScoreKeeper struct {
+	numRecords *counter.Counter
+}
+
+func NewScoreKeeper() *ScoreKeeper {
+	return &ScoreKeeper{
+		numRecords: stats.NewCounter("scorekeeper/num-records"),
+	}
+}
+
+func (k *ScoreKeeper) Stats() int64 {
+	return k.numRecords.Value()
+}
+
+func (k *ScoreKeeper) Record(ctx *context.T, call rpc.ServerCall, score rps.ScoreCard) error {
+	b, _ := security.RemoteBlessingNames(ctx, call.Security())
+	ctx.VI(1).Infof("Received ScoreCard from %v:", b)
+	ctx.VI(1).Info(internal.FormatScoreCard(score))
+	k.numRecords.Incr(1)
+	return nil
+}
diff --git a/examples/rps/rpsbot/v23_internal_test.go b/examples/rps/rpsbot/v23_internal_test.go
new file mode 100644
index 0000000..a80e0ec
--- /dev/null
+++ b/examples/rps/rpsbot/v23_internal_test.go
@@ -0,0 +1,22 @@
+// 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 main
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	modules.DispatchAndExitIfChild()
+	os.Exit(m.Run())
+}
diff --git a/examples/rps/rpsplayer/doc.go b/examples/rps/rpsplayer/doc.go
new file mode 100644
index 0000000..bfa7fea
--- /dev/null
+++ b/examples/rps/rpsplayer/doc.go
@@ -0,0 +1,70 @@
+// 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
+
+/*
+Command rpsplayer implements the Player interface, which enables a human to play
+the game.
+
+Usage:
+   rpsplayer [flags]
+
+The rpsplayer flags are:
+ -acl-file=
+   File containing JSON-encoded Permissions.
+ -name=
+   Identifier to publish as (defaults to user@hostname).
+
+The global flags are:
+ -alsologtostderr=true
+   log to standard error as well as files
+ -log_backtrace_at=:0
+   when logging hits line file:N, emit a stack trace
+ -log_dir=
+   if non-empty, write log files to this directory
+ -logtostderr=false
+   log to standard error instead of files
+ -max_stack_buf_size=4292608
+   max size in bytes of the buffer to use for logging stack traces
+ -metadata=<just specify -metadata to activate>
+   Displays metadata for the program and exits.
+ -stderrthreshold=2
+   logs at or above this threshold go to stderr
+ -v=0
+   log level for V logs
+ -v23.credentials=
+   directory to use for storing security credentials
+ -v23.i18n-catalogue=
+   18n catalogue files to load, comma separated
+ -v23.namespace.root=[/(dev.v.io/role/vprod/service/mounttabled)@ns.dev.v.io:8101]
+   local namespace root; can be repeated to provided multiple roots
+ -v23.permissions.file=map[]
+   specify a perms file as <name>:<permsfile>
+ -v23.permissions.literal=
+   explicitly specify the runtime perms as a JSON-encoded access.Permissions.
+   Overrides all --v23.permissions.file flags.
+ -v23.proxy=
+   object name of proxy service to use to export services across network
+   boundaries
+ -v23.tcp.address=
+   address to listen on
+ -v23.tcp.protocol=wsh
+   protocol to listen with
+ -v23.vtrace.cache-size=1024
+   The number of vtrace traces to store in memory.
+ -v23.vtrace.collect-regexp=
+   Spans and annotations that match this regular expression will trigger trace
+   collection.
+ -v23.vtrace.dump-on-shutdown=true
+   If true, dump all stored traces on runtime shutdown.
+ -v23.vtrace.sample-rate=0
+   Rate (from 0.0 to 1.0) to sample vtrace traces.
+ -vmodule=
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
+*/
+package main
diff --git a/examples/rps/rpsplayer/main.go b/examples/rps/rpsplayer/main.go
new file mode 100644
index 0000000..ad57b4f
--- /dev/null
+++ b/examples/rps/rpsplayer/main.go
@@ -0,0 +1,323 @@
+// 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.
+
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go . -help
+
+package main
+
+import (
+	"errors"
+	"fmt"
+	"sort"
+	"strings"
+	"sync"
+	"time"
+
+	"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/vtrace"
+
+	"v.io/x/ref/examples/rps"
+	"v.io/x/ref/examples/rps/internal"
+	"v.io/x/ref/internal/logger"
+	"v.io/x/ref/lib/v23cmd"
+	"v.io/x/ref/lib/xrpc"
+
+	_ "v.io/x/ref/runtime/factories/roaming"
+)
+
+var name, aclFile string
+
+func main() {
+	cmdRoot.Flags.StringVar(&name, "name", "", "Identifier to publish as (defaults to user@hostname).")
+	cmdRoot.Flags.StringVar(&aclFile, "acl-file", "", "File containing JSON-encoded Permissions.")
+	cmdline.HideGlobalFlagsExcept()
+	cmdline.Main(cmdRoot)
+}
+
+var cmdRoot = &cmdline.Command{
+	Runner: v23cmd.RunnerFunc(runPlayer),
+	Name:   "rpsplayer",
+	Short:  "Implements the Player interface",
+	Long: `
+Command rpsplayer implements the Player interface, which enables a human to play
+the game.
+`,
+}
+
+func runPlayer(rootctx *context.T, env *cmdline.Env, args []string) error {
+	for {
+		ctx, _ := vtrace.WithNewTrace(rootctx)
+		if selectOne([]string{"Initiate Game", "Wait For Challenge"}) == 0 {
+			initiateGame(ctx)
+		} else {
+			fmt.Println("Waiting to receive a challenge...")
+			game := recvChallenge(ctx)
+			playGame(ctx, game.address, game.id)
+		}
+		if selectOne([]string{"Play Again", "Quit"}) == 1 {
+			break
+		}
+	}
+	return nil
+}
+
+type gameChallenge struct {
+	address string
+	id      rps.GameId
+	opts    rps.GameOptions
+}
+
+// impl is a PlayerServerMethods implementation that prompts the user to accept
+// or decline challenges. While waiting for a reply from the user, any incoming
+// challenges are auto-declined.
+type impl struct {
+	ch      chan gameChallenge
+	decline bool
+	lock    sync.Mutex
+}
+
+func (i *impl) setDecline(v bool) bool {
+	i.lock.Lock()
+	defer i.lock.Unlock()
+	prev := i.decline
+	i.decline = v
+	return prev
+}
+
+func (i *impl) Challenge(ctx *context.T, call rpc.ServerCall, address string, id rps.GameId, opts rps.GameOptions) error {
+	remote, _ := security.RemoteBlessingNames(ctx, call.Security())
+	ctx.VI(1).Infof("Challenge (%q, %+v) from %v", address, id, remote)
+	// When setDecline(true) returns, future challenges will be declined.
+	// Whether the current challenge should be considered depends on the
+	// previous state. If 'decline' was already true, we need to decline
+	// this challenge. It 'decline' was false, this is the first challenge
+	// that we should process.
+	if i.setDecline(true) {
+		return errors.New("player is busy")
+	}
+	fmt.Println()
+	fmt.Printf("Challenge received from %v for a %d-round ", remote, opts.NumRounds)
+	switch opts.GameType {
+	case rps.Classic:
+		fmt.Print("Classic ")
+	case rps.LizardSpock:
+		fmt.Print("Lizard-Spock ")
+	default:
+	}
+	fmt.Println("Game.")
+	if selectOne([]string{"Accept", "Decline"}) == 0 {
+		i.ch <- gameChallenge{address, id, opts}
+		return nil
+	}
+	// Start considering challenges again.
+	i.setDecline(false)
+	return errors.New("player declined challenge")
+}
+
+// recvChallenge runs a server until a game challenge is accepted by the user.
+// The server is stopped afterwards.
+func recvChallenge(ctx *context.T) gameChallenge {
+	ch := make(chan gameChallenge)
+	if name == "" {
+		name = internal.CreateName()
+	}
+	fullname := fmt.Sprintf("rps/player/%s", name)
+	service := rps.PlayerServer(&impl{ch: ch})
+	auth := internal.NewAuthorizer(aclFile)
+	server, err := xrpc.NewServer(ctx, fullname, service, auth)
+	if err != nil {
+		ctx.Fatalf("NewServer failed: %v", err)
+	}
+	ctx.Infof("Listening on endpoint /%s", server.Status().Endpoints[0])
+	result := <-ch
+	server.Stop()
+	return result
+}
+
+// initiateGame initiates a new game by getting a list of judges and players,
+// and asking the user to select one of each, to select the game options, what
+// to play, etc.
+func initiateGame(ctx *context.T) error {
+	jChan := make(chan []string)
+	oChan := make(chan []string)
+	go findAll(ctx, "judge", jChan)
+	go findAll(ctx, "player", oChan)
+
+	fmt.Println("Looking for available participants...")
+	judges := <-jChan
+	opponents := <-oChan
+	fmt.Println()
+	if len(judges) == 0 || len(opponents) == 0 {
+		return errors.New("no one to play with")
+	}
+
+	fmt.Println("Choose a judge:")
+	j := selectOne(judges)
+	fmt.Println("Choose an opponent:")
+	o := selectOne(opponents)
+	fmt.Println("Choose the type of rock-paper-scissors game would you like to play:")
+	gameType := selectOne([]string{"Classic", "LizardSpock"})
+	fmt.Println("Choose the number of rounds required to win:")
+	numRounds := selectOne([]string{"1", "2", "3", "4", "5", "6"}) + 1
+	gameOpts := rps.GameOptions{NumRounds: int32(numRounds), GameType: rps.GameTypeTag(gameType)}
+
+	gameID, err := createGame(ctx, judges[j], gameOpts)
+	if err != nil {
+		ctx.Infof("createGame: %v", err)
+		return err
+	}
+	for {
+		err := sendChallenge(ctx, opponents[o], judges[j], gameID, gameOpts)
+		if err == nil {
+			break
+		}
+		fmt.Printf("Challenge was declined by %s (%v)\n", opponents[o], err)
+		fmt.Println("Choose another opponent:")
+		o = selectOne(opponents)
+	}
+	fmt.Println("Joining the game...")
+
+	if _, err = playGame(ctx, judges[j], gameID); err != nil {
+		ctx.Infof("playGame: %v", err)
+		return err
+	}
+	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 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)
+}
+
+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)
+	if err != nil {
+		return rps.PlayResult{}, err
+	}
+	var playerNum int32
+	rStream := game.RecvStream()
+	for rStream.Advance() {
+		in := rStream.Value()
+		switch v := in.(type) {
+		case rps.JudgeActionPlayerNum:
+			playerNum = v.Value
+			fmt.Printf("You are player %d\n", playerNum)
+		case rps.JudgeActionOpponentName:
+			fmt.Printf("Your opponent is %q\n", v.Value)
+		case rps.JudgeActionRoundResult:
+			rr := v.Value
+			if playerNum != 1 && playerNum != 2 {
+				ctx.Fatalf("invalid playerNum: %d", playerNum)
+			}
+			fmt.Printf("You played %q\n", rr.Moves[playerNum-1])
+			fmt.Printf("Your opponent played %q\n", rr.Moves[2-playerNum])
+			if len(rr.Comment) > 0 {
+				fmt.Printf(">>> %s <<<\n", strings.ToUpper(rr.Comment))
+			}
+			if rr.Winner == 0 {
+				fmt.Println("----- It's a draw -----")
+			} else if rps.WinnerTag(playerNum) == rr.Winner {
+				fmt.Println("***** You WIN *****")
+			} else {
+				fmt.Println("##### You LOSE #####")
+			}
+		case rps.JudgeActionMoveOptions:
+			opts := v.Value
+			fmt.Println()
+			fmt.Println("Choose your weapon:")
+			m := selectOne(opts)
+			if err := game.SendStream().Send(rps.PlayerActionMove{Value: opts[m]}); err != nil {
+				return rps.PlayResult{}, err
+			}
+		case rps.JudgeActionScore:
+			score := v.Value
+			fmt.Println()
+			fmt.Println("==== GAME SUMMARY ====")
+			fmt.Print(internal.FormatScoreCard(score))
+			fmt.Println("======================")
+			if rps.WinnerTag(playerNum) == score.Winner {
+				fmt.Println("You won! :)")
+			} else {
+				fmt.Println("You lost! :(")
+			}
+		default:
+			outer.Infof("unexpected message type: %T", in)
+		}
+	}
+	if err := rStream.Err(); err == nil {
+		fmt.Println("Game Ended")
+	} else {
+		outer.Infof("stream error: %v", err)
+	}
+
+	return game.Finish()
+}
+
+func selectOne(choices []string) (choice int) {
+	if len(choices) == 0 {
+		logger.Global().Fatal("No options to choose from!")
+	}
+	fmt.Println()
+	for i, x := range choices {
+		fmt.Printf("  %d. %s\n", i+1, x)
+	}
+	fmt.Println()
+	for {
+		if len(choices) == 1 {
+			fmt.Print("Select one [1] --> ")
+		} else {
+			fmt.Printf("Select one [1-%d] --> ", len(choices))
+		}
+		fmt.Scanf("%d", &choice)
+		if choice >= 1 && choice <= len(choices) {
+			choice -= 1
+			break
+		}
+	}
+	fmt.Println()
+	return
+}
+
+func findAll(ctx *context.T, t string, out chan []string) {
+	ns := v23.GetNamespace(ctx)
+	var result []string
+	c, err := ns.Glob(ctx, "rps/"+t+"/*")
+	if err != nil {
+		ctx.Infof("ns.Glob failed: %v", err)
+		out <- result
+		return
+	}
+	for e := range c {
+		switch v := e.(type) {
+		case *naming.GlobReplyError:
+			fmt.Print("E")
+		case *naming.GlobReplyEntry:
+			fmt.Print(".")
+			result = append(result, v.Value.Name)
+		}
+	}
+	if len(result) == 0 {
+		ctx.Infof("found no %ss", t)
+		out <- result
+		return
+	}
+	sort.Strings(result)
+	out <- result
+}
diff --git a/examples/rps/rpsscorekeeper/doc.go b/examples/rps/rpsscorekeeper/doc.go
new file mode 100644
index 0000000..93e07f7
--- /dev/null
+++ b/examples/rps/rpsscorekeeper/doc.go
@@ -0,0 +1,69 @@
+// 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
+
+/*
+Command rpsscorekeeper implements the ScoreKeeper interface.  It publishes
+itself as a score keeper for the rock-paper-scissors game and prints out all the
+score cards it receives to stdout.
+
+Usage:
+   rpsscorekeeper [flags]
+
+The rpsscorekeeper flags are:
+ -acl-file=
+   File containing JSON-encoded Permissions.
+
+The global flags are:
+ -alsologtostderr=true
+   log to standard error as well as files
+ -log_backtrace_at=:0
+   when logging hits line file:N, emit a stack trace
+ -log_dir=
+   if non-empty, write log files to this directory
+ -logtostderr=false
+   log to standard error instead of files
+ -max_stack_buf_size=4292608
+   max size in bytes of the buffer to use for logging stack traces
+ -metadata=<just specify -metadata to activate>
+   Displays metadata for the program and exits.
+ -stderrthreshold=2
+   logs at or above this threshold go to stderr
+ -v=0
+   log level for V logs
+ -v23.credentials=
+   directory to use for storing security credentials
+ -v23.i18n-catalogue=
+   18n catalogue files to load, comma separated
+ -v23.namespace.root=[/(dev.v.io/role/vprod/service/mounttabled)@ns.dev.v.io:8101]
+   local namespace root; can be repeated to provided multiple roots
+ -v23.permissions.file=map[]
+   specify a perms file as <name>:<permsfile>
+ -v23.permissions.literal=
+   explicitly specify the runtime perms as a JSON-encoded access.Permissions.
+   Overrides all --v23.permissions.file flags.
+ -v23.proxy=
+   object name of proxy service to use to export services across network
+   boundaries
+ -v23.tcp.address=
+   address to listen on
+ -v23.tcp.protocol=wsh
+   protocol to listen with
+ -v23.vtrace.cache-size=1024
+   The number of vtrace traces to store in memory.
+ -v23.vtrace.collect-regexp=
+   Spans and annotations that match this regular expression will trigger trace
+   collection.
+ -v23.vtrace.dump-on-shutdown=true
+   If true, dump all stored traces on runtime shutdown.
+ -v23.vtrace.sample-rate=0
+   Rate (from 0.0 to 1.0) to sample vtrace traces.
+ -vmodule=
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
+*/
+package main
diff --git a/examples/rps/rpsscorekeeper/main.go b/examples/rps/rpsscorekeeper/main.go
new file mode 100644
index 0000000..6c8fd39
--- /dev/null
+++ b/examples/rps/rpsscorekeeper/main.go
@@ -0,0 +1,79 @@
+// 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.
+
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go . -help
+
+package main
+
+import (
+	"fmt"
+	"os"
+
+	"v.io/x/lib/cmdline"
+
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+
+	"v.io/x/ref/examples/rps"
+	"v.io/x/ref/examples/rps/internal"
+	"v.io/x/ref/lib/v23cmd"
+	"v.io/x/ref/lib/xrpc"
+
+	_ "v.io/x/ref/runtime/factories/roaming"
+)
+
+var aclFile string
+
+func main() {
+	cmdRoot.Flags.StringVar(&aclFile, "acl-file", "", "File containing JSON-encoded Permissions.")
+	cmdline.HideGlobalFlagsExcept()
+	cmdline.Main(cmdRoot)
+}
+
+var cmdRoot = &cmdline.Command{
+	Runner: v23cmd.RunnerFunc(runScoreKeeper),
+	Name:   "rpsscorekeeper",
+	Short:  "Implements the ScoreKeeper interface",
+	Long: `
+Command rpsscorekeeper implements the ScoreKeeper interface.  It publishes
+itself as a score keeper for the rock-paper-scissors game and prints out all the
+score cards it receives to stdout.
+`,
+}
+
+type impl struct {
+	ch chan rps.ScoreCard
+}
+
+func (i *impl) Record(ctx *context.T, call rpc.ServerCall, score rps.ScoreCard) error {
+	b, _ := security.RemoteBlessingNames(ctx, call.Security())
+	ctx.VI(1).Infof("Record (%+v) from %v", score, b)
+	i.ch <- score
+	return nil
+}
+
+func runScoreKeeper(ctx *context.T, env *cmdline.Env, args []string) error {
+	ch := make(chan rps.ScoreCard)
+	rpsService := &impl{ch}
+	hostname, err := os.Hostname()
+	if err != nil {
+		return fmt.Errorf("os.Hostname failed: %v", err)
+	}
+	name := fmt.Sprintf("rps/scorekeeper/%s", hostname)
+	service := rps.ScoreKeeperServer(rpsService)
+	authorizer := internal.NewAuthorizer(aclFile)
+	server, err := xrpc.NewServer(ctx, name, service, authorizer)
+	if err != nil {
+		return fmt.Errorf("NewServer failed: %v", err)
+	}
+
+	ctx.Infof("Listening on endpoint /%s", server.Status().Endpoints[0])
+
+	for score := range ch {
+		fmt.Print("======================\n", internal.FormatScoreCard(score))
+	}
+	return nil
+}
diff --git a/examples/rps/service.vdl b/examples/rps/service.vdl
new file mode 100644
index 0000000..b5f9c7c
--- /dev/null
+++ b/examples/rps/service.vdl
@@ -0,0 +1,119 @@
+// 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 rps defines interfaces for playing the game Rock-Paper-Scissors.  It
+// is an example of a simple Vanadium service.
+//
+// http://en.wikipedia.org/wiki/Rock-paper-scissors
+//
+// There are three different roles in the game:
+//
+// 1. Judge: A judge enforces the rules of the game and decides who the winner
+// is. At the end of the game, the judge reports the final score to all the
+// score keepers.
+//
+// 2. Player: A player can ask a judge to start a new game, it can challenge
+// another player, and it can play a game.
+//
+// 3. ScoreKeeper: A score keeper receives the final score for a game after it
+// ended.
+package rps
+
+import (
+	"time"
+	"v.io/v23/security/access"
+)
+
+type RockPaperScissors interface {
+  Judge
+  Player
+  ScoreKeeper
+}
+
+type Judge interface {
+  // CreateGame creates a new game with the given game options and returns a game
+  // identifier that can be used by the players to join the game.
+  CreateGame(Opts GameOptions) (GameId | error) {access.Write}
+  // Play lets a player join an existing game and play.
+  Play(Id GameId) stream<PlayerAction,JudgeAction> (PlayResult | error) {access.Write}
+}
+
+// A GameId is used to uniquely identify a game within one Judge.
+type GameId struct {
+  Id string
+}
+
+// GameOptions specifies the parameters of a game.
+type GameOptions struct {
+  NumRounds int32        // The number of rounds that a player must win to win the game.
+  GameType  GameTypeTag  // The type of game to play: Classic or LizardSpock.
+}
+
+type GameTypeTag byte
+const (
+  Classic     = GameTypeTag(0)  // Rock-Paper-Scissors
+  LizardSpock = GameTypeTag(1)  // Rock-Paper-Scissors-Lizard-Spock
+)
+
+type PlayerAction union {
+  Move string  // The move that the player wants to make.
+  Quit unused  // Indicates that the player is quitting the game.
+}
+
+type unused struct{}
+
+type JudgeAction union {
+  PlayerNum    int32      // The player's number.
+  OpponentName string     // The name of the opponent.
+  MoveOptions  []string   // A list of allowed moves that the player must choose from.
+  RoundResult  Round      // The result of the previous round.
+  Score        ScoreCard  // The result of the game.
+}
+
+type PlayersMoves [2]string
+
+// Round represents the state of a round.
+type Round struct {
+  Moves       PlayersMoves  // Each player's move.
+  Comment     string        // A text comment from judge about the round.
+  Winner      WinnerTag     // Who won the round.
+  StartTime   time.Time     // The time at which the round started.
+  EndTime     time.Time     // The time at which the round ended.
+}
+
+// WinnerTag is a type used to indicate whether a round or a game was a draw,
+// was won by player 1 or was won by player 2.
+type WinnerTag byte
+const (
+  Draw    = WinnerTag(0)
+  Player1 = WinnerTag(1)
+  Player2 = WinnerTag(2)
+)
+
+// PlayResult is the value returned by the Play method. It indicates the outcome of the game.
+type PlayResult struct {
+  YouWon bool  // True if the player receiving the result won the game.
+}
+
+// Player can receive challenges from other players.
+type Player interface {
+  // Challenge is used by other players to challenge this player to a game. If
+  // the challenge is accepted, the method returns nil.
+  Challenge(Address string, Id GameId, Opts GameOptions) error {access.Write}
+}
+
+// ScoreKeeper receives the outcome of games from Judges.
+type ScoreKeeper interface {
+  Record(Score ScoreCard) error {access.Write}
+}
+
+type ScoreCard struct {
+  Opts        GameOptions // The game options.
+  Judge       string      // The name of the judge.
+  Players     []string    // The name of the players.
+  Rounds      []Round     // The outcome of each round.
+  StartTime   time.Time   // The time at which the game started.
+  EndTime     time.Time   // The time at which the game ended.
+  Winner      WinnerTag   // Who won the game.
+}
diff --git a/examples/rps/service.vdl.go b/examples/rps/service.vdl.go
new file mode 100644
index 0000000..3f86372
--- /dev/null
+++ b/examples/rps/service.vdl.go
@@ -0,0 +1,895 @@
+// 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 by the vanadium vdl tool.
+// Source: service.vdl
+
+// Package rps defines interfaces for playing the game Rock-Paper-Scissors.  It
+// is an example of a simple Vanadium service.
+//
+// http://en.wikipedia.org/wiki/Rock-paper-scissors
+//
+// There are three different roles in the game:
+//
+// 1. Judge: A judge enforces the rules of the game and decides who the winner
+// is. At the end of the game, the judge reports the final score to all the
+// score keepers.
+//
+// 2. Player: A player can ask a judge to start a new game, it can challenge
+// another player, and it can play a game.
+//
+// 3. ScoreKeeper: A score keeper receives the final score for a game after it
+// ended.
+package rps
+
+import (
+	// VDL system imports
+	"io"
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/vdl"
+
+	// VDL user imports
+	"time"
+	"v.io/v23/security/access"
+	_ "v.io/v23/vdlroot/time"
+)
+
+// A GameId is used to uniquely identify a game within one Judge.
+type GameId struct {
+	Id string
+}
+
+func (GameId) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/examples/rps.GameId"`
+}) {
+}
+
+// GameOptions specifies the parameters of a game.
+type GameOptions struct {
+	NumRounds int32       // The number of rounds that a player must win to win the game.
+	GameType  GameTypeTag // The type of game to play: Classic or LizardSpock.
+}
+
+func (GameOptions) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/examples/rps.GameOptions"`
+}) {
+}
+
+type GameTypeTag byte
+
+func (GameTypeTag) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/examples/rps.GameTypeTag"`
+}) {
+}
+
+type (
+	// PlayerAction represents any single field of the PlayerAction union type.
+	PlayerAction interface {
+		// Index returns the field index.
+		Index() int
+		// Interface returns the field value as an interface.
+		Interface() interface{}
+		// Name returns the field name.
+		Name() string
+		// __VDLReflect describes the PlayerAction union type.
+		__VDLReflect(__PlayerActionReflect)
+	}
+	// PlayerActionMove represents field Move of the PlayerAction union type.
+	PlayerActionMove struct{ Value string } // The move that the player wants to make.
+	// PlayerActionQuit represents field Quit of the PlayerAction union type.
+	PlayerActionQuit struct{ Value unused } // Indicates that the player is quitting the game.
+	// __PlayerActionReflect describes the PlayerAction union type.
+	__PlayerActionReflect struct {
+		Name  string `vdl:"v.io/x/ref/examples/rps.PlayerAction"`
+		Type  PlayerAction
+		Union struct {
+			Move PlayerActionMove
+			Quit PlayerActionQuit
+		}
+	}
+)
+
+func (x PlayerActionMove) Index() int                         { return 0 }
+func (x PlayerActionMove) Interface() interface{}             { return x.Value }
+func (x PlayerActionMove) Name() string                       { return "Move" }
+func (x PlayerActionMove) __VDLReflect(__PlayerActionReflect) {}
+
+func (x PlayerActionQuit) Index() int                         { return 1 }
+func (x PlayerActionQuit) Interface() interface{}             { return x.Value }
+func (x PlayerActionQuit) Name() string                       { return "Quit" }
+func (x PlayerActionQuit) __VDLReflect(__PlayerActionReflect) {}
+
+type unused struct {
+}
+
+func (unused) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/examples/rps.unused"`
+}) {
+}
+
+type (
+	// JudgeAction represents any single field of the JudgeAction union type.
+	JudgeAction interface {
+		// Index returns the field index.
+		Index() int
+		// Interface returns the field value as an interface.
+		Interface() interface{}
+		// Name returns the field name.
+		Name() string
+		// __VDLReflect describes the JudgeAction union type.
+		__VDLReflect(__JudgeActionReflect)
+	}
+	// JudgeActionPlayerNum represents field PlayerNum of the JudgeAction union type.
+	JudgeActionPlayerNum struct{ Value int32 } // The player's number.
+	// JudgeActionOpponentName represents field OpponentName of the JudgeAction union type.
+	JudgeActionOpponentName struct{ Value string } // The name of the opponent.
+	// JudgeActionMoveOptions represents field MoveOptions of the JudgeAction union type.
+	JudgeActionMoveOptions struct{ Value []string } // A list of allowed moves that the player must choose from.
+	// JudgeActionRoundResult represents field RoundResult of the JudgeAction union type.
+	JudgeActionRoundResult struct{ Value Round } // The result of the previous round.
+	// JudgeActionScore represents field Score of the JudgeAction union type.
+	JudgeActionScore struct{ Value ScoreCard } // The result of the game.
+	// __JudgeActionReflect describes the JudgeAction union type.
+	__JudgeActionReflect struct {
+		Name  string `vdl:"v.io/x/ref/examples/rps.JudgeAction"`
+		Type  JudgeAction
+		Union struct {
+			PlayerNum    JudgeActionPlayerNum
+			OpponentName JudgeActionOpponentName
+			MoveOptions  JudgeActionMoveOptions
+			RoundResult  JudgeActionRoundResult
+			Score        JudgeActionScore
+		}
+	}
+)
+
+func (x JudgeActionPlayerNum) Index() int                        { return 0 }
+func (x JudgeActionPlayerNum) Interface() interface{}            { return x.Value }
+func (x JudgeActionPlayerNum) Name() string                      { return "PlayerNum" }
+func (x JudgeActionPlayerNum) __VDLReflect(__JudgeActionReflect) {}
+
+func (x JudgeActionOpponentName) Index() int                        { return 1 }
+func (x JudgeActionOpponentName) Interface() interface{}            { return x.Value }
+func (x JudgeActionOpponentName) Name() string                      { return "OpponentName" }
+func (x JudgeActionOpponentName) __VDLReflect(__JudgeActionReflect) {}
+
+func (x JudgeActionMoveOptions) Index() int                        { return 2 }
+func (x JudgeActionMoveOptions) Interface() interface{}            { return x.Value }
+func (x JudgeActionMoveOptions) Name() string                      { return "MoveOptions" }
+func (x JudgeActionMoveOptions) __VDLReflect(__JudgeActionReflect) {}
+
+func (x JudgeActionRoundResult) Index() int                        { return 3 }
+func (x JudgeActionRoundResult) Interface() interface{}            { return x.Value }
+func (x JudgeActionRoundResult) Name() string                      { return "RoundResult" }
+func (x JudgeActionRoundResult) __VDLReflect(__JudgeActionReflect) {}
+
+func (x JudgeActionScore) Index() int                        { return 4 }
+func (x JudgeActionScore) Interface() interface{}            { return x.Value }
+func (x JudgeActionScore) Name() string                      { return "Score" }
+func (x JudgeActionScore) __VDLReflect(__JudgeActionReflect) {}
+
+type PlayersMoves [2]string
+
+func (PlayersMoves) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/examples/rps.PlayersMoves"`
+}) {
+}
+
+// Round represents the state of a round.
+type Round struct {
+	Moves     PlayersMoves // Each player's move.
+	Comment   string       // A text comment from judge about the round.
+	Winner    WinnerTag    // Who won the round.
+	StartTime time.Time    // The time at which the round started.
+	EndTime   time.Time    // The time at which the round ended.
+}
+
+func (Round) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/examples/rps.Round"`
+}) {
+}
+
+// WinnerTag is a type used to indicate whether a round or a game was a draw,
+// was won by player 1 or was won by player 2.
+type WinnerTag byte
+
+func (WinnerTag) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/examples/rps.WinnerTag"`
+}) {
+}
+
+// PlayResult is the value returned by the Play method. It indicates the outcome of the game.
+type PlayResult struct {
+	YouWon bool // True if the player receiving the result won the game.
+}
+
+func (PlayResult) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/examples/rps.PlayResult"`
+}) {
+}
+
+type ScoreCard struct {
+	Opts      GameOptions // The game options.
+	Judge     string      // The name of the judge.
+	Players   []string    // The name of the players.
+	Rounds    []Round     // The outcome of each round.
+	StartTime time.Time   // The time at which the game started.
+	EndTime   time.Time   // The time at which the game ended.
+	Winner    WinnerTag   // Who won the game.
+}
+
+func (ScoreCard) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/examples/rps.ScoreCard"`
+}) {
+}
+
+func init() {
+	vdl.Register((*GameId)(nil))
+	vdl.Register((*GameOptions)(nil))
+	vdl.Register((*GameTypeTag)(nil))
+	vdl.Register((*PlayerAction)(nil))
+	vdl.Register((*unused)(nil))
+	vdl.Register((*JudgeAction)(nil))
+	vdl.Register((*PlayersMoves)(nil))
+	vdl.Register((*Round)(nil))
+	vdl.Register((*WinnerTag)(nil))
+	vdl.Register((*PlayResult)(nil))
+	vdl.Register((*ScoreCard)(nil))
+}
+
+const Classic = GameTypeTag(0) // Rock-Paper-Scissors
+
+const LizardSpock = GameTypeTag(1) // Rock-Paper-Scissors-Lizard-Spock
+
+const Draw = WinnerTag(0)
+
+const Player1 = WinnerTag(1)
+
+const Player2 = WinnerTag(2)
+
+// JudgeClientMethods is the client interface
+// containing Judge methods.
+type JudgeClientMethods interface {
+	// CreateGame creates a new game with the given game options and returns a game
+	// identifier that can be used by the players to join the game.
+	CreateGame(ctx *context.T, Opts GameOptions, opts ...rpc.CallOpt) (GameId, error)
+	// Play lets a player join an existing game and play.
+	Play(ctx *context.T, Id GameId, opts ...rpc.CallOpt) (JudgePlayClientCall, error)
+}
+
+// JudgeClientStub adds universal methods to JudgeClientMethods.
+type JudgeClientStub interface {
+	JudgeClientMethods
+	rpc.UniversalServiceMethods
+}
+
+// JudgeClient returns a client stub for Judge.
+func JudgeClient(name string) JudgeClientStub {
+	return implJudgeClientStub{name}
+}
+
+type implJudgeClientStub struct {
+	name string
+}
+
+func (c implJudgeClientStub) CreateGame(ctx *context.T, i0 GameOptions, opts ...rpc.CallOpt) (o0 GameId, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "CreateGame", []interface{}{i0}, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implJudgeClientStub) Play(ctx *context.T, i0 GameId, opts ...rpc.CallOpt) (ocall JudgePlayClientCall, err error) {
+	var call rpc.ClientCall
+	if call, err = v23.GetClient(ctx).StartCall(ctx, c.name, "Play", []interface{}{i0}, opts...); err != nil {
+		return
+	}
+	ocall = &implJudgePlayClientCall{ClientCall: call}
+	return
+}
+
+// JudgePlayClientStream is the client stream for Judge.Play.
+type JudgePlayClientStream interface {
+	// RecvStream returns the receiver side of the Judge.Play client stream.
+	RecvStream() interface {
+		// Advance stages an item so that it may be retrieved via Value.  Returns
+		// true iff there is an item to retrieve.  Advance must be called before
+		// Value is called.  May block if an item is not available.
+		Advance() bool
+		// Value returns the item that was staged by Advance.  May panic if Advance
+		// returned false or was not called.  Never blocks.
+		Value() JudgeAction
+		// Err returns any error encountered by Advance.  Never blocks.
+		Err() error
+	}
+	// SendStream returns the send side of the Judge.Play client stream.
+	SendStream() interface {
+		// Send places the item onto the output stream.  Returns errors
+		// encountered while sending, or if Send is called after Close or
+		// the stream has been canceled.  Blocks if there is no buffer
+		// space; will unblock when buffer space is available or after
+		// the stream has been canceled.
+		Send(item PlayerAction) error
+		// Close indicates to the server that no more items will be sent;
+		// server Recv calls will receive io.EOF after all sent items.
+		// This is an optional call - e.g. a client might call Close if it
+		// needs to continue receiving items from the server after it's
+		// done sending.  Returns errors encountered while closing, or if
+		// Close is called after the stream has been canceled.  Like Send,
+		// blocks if there is no buffer space available.
+		Close() error
+	}
+}
+
+// JudgePlayClientCall represents the call returned from Judge.Play.
+type JudgePlayClientCall interface {
+	JudgePlayClientStream
+	// Finish performs the equivalent of SendStream().Close, then blocks until
+	// the server is done, and returns the positional return values for the call.
+	//
+	// Finish returns immediately if the call has been canceled; depending on the
+	// timing the output could either be an error signaling cancelation, or the
+	// valid positional return values from the server.
+	//
+	// Calling Finish is mandatory for releasing stream resources, unless the call
+	// has been canceled or any of the other methods return an error.  Finish should
+	// be called at most once.
+	Finish() (PlayResult, error)
+}
+
+type implJudgePlayClientCall struct {
+	rpc.ClientCall
+	valRecv JudgeAction
+	errRecv error
+}
+
+func (c *implJudgePlayClientCall) RecvStream() interface {
+	Advance() bool
+	Value() JudgeAction
+	Err() error
+} {
+	return implJudgePlayClientCallRecv{c}
+}
+
+type implJudgePlayClientCallRecv struct {
+	c *implJudgePlayClientCall
+}
+
+func (c implJudgePlayClientCallRecv) Advance() bool {
+	c.c.errRecv = c.c.Recv(&c.c.valRecv)
+	return c.c.errRecv == nil
+}
+func (c implJudgePlayClientCallRecv) Value() JudgeAction {
+	return c.c.valRecv
+}
+func (c implJudgePlayClientCallRecv) Err() error {
+	if c.c.errRecv == io.EOF {
+		return nil
+	}
+	return c.c.errRecv
+}
+func (c *implJudgePlayClientCall) SendStream() interface {
+	Send(item PlayerAction) error
+	Close() error
+} {
+	return implJudgePlayClientCallSend{c}
+}
+
+type implJudgePlayClientCallSend struct {
+	c *implJudgePlayClientCall
+}
+
+func (c implJudgePlayClientCallSend) Send(item PlayerAction) error {
+	return c.c.Send(item)
+}
+func (c implJudgePlayClientCallSend) Close() error {
+	return c.c.CloseSend()
+}
+func (c *implJudgePlayClientCall) Finish() (o0 PlayResult, err error) {
+	err = c.ClientCall.Finish(&o0)
+	return
+}
+
+// JudgeServerMethods is the interface a server writer
+// implements for Judge.
+type JudgeServerMethods interface {
+	// CreateGame creates a new game with the given game options and returns a game
+	// identifier that can be used by the players to join the game.
+	CreateGame(ctx *context.T, call rpc.ServerCall, Opts GameOptions) (GameId, error)
+	// Play lets a player join an existing game and play.
+	Play(ctx *context.T, call JudgePlayServerCall, Id GameId) (PlayResult, error)
+}
+
+// JudgeServerStubMethods is the server interface containing
+// Judge methods, as expected by rpc.Server.
+// The only difference between this interface and JudgeServerMethods
+// is the streaming methods.
+type JudgeServerStubMethods interface {
+	// CreateGame creates a new game with the given game options and returns a game
+	// identifier that can be used by the players to join the game.
+	CreateGame(ctx *context.T, call rpc.ServerCall, Opts GameOptions) (GameId, error)
+	// Play lets a player join an existing game and play.
+	Play(ctx *context.T, call *JudgePlayServerCallStub, Id GameId) (PlayResult, error)
+}
+
+// JudgeServerStub adds universal methods to JudgeServerStubMethods.
+type JudgeServerStub interface {
+	JudgeServerStubMethods
+	// Describe the Judge interfaces.
+	Describe__() []rpc.InterfaceDesc
+}
+
+// JudgeServer returns a server stub for Judge.
+// It converts an implementation of JudgeServerMethods into
+// an object that may be used by rpc.Server.
+func JudgeServer(impl JudgeServerMethods) JudgeServerStub {
+	stub := implJudgeServerStub{
+		impl: impl,
+	}
+	// Initialize GlobState; always check the stub itself first, to handle the
+	// case where the user has the Glob method defined in their VDL source.
+	if gs := rpc.NewGlobState(stub); gs != nil {
+		stub.gs = gs
+	} else if gs := rpc.NewGlobState(impl); gs != nil {
+		stub.gs = gs
+	}
+	return stub
+}
+
+type implJudgeServerStub struct {
+	impl JudgeServerMethods
+	gs   *rpc.GlobState
+}
+
+func (s implJudgeServerStub) CreateGame(ctx *context.T, call rpc.ServerCall, i0 GameOptions) (GameId, error) {
+	return s.impl.CreateGame(ctx, call, i0)
+}
+
+func (s implJudgeServerStub) Play(ctx *context.T, call *JudgePlayServerCallStub, i0 GameId) (PlayResult, error) {
+	return s.impl.Play(ctx, call, i0)
+}
+
+func (s implJudgeServerStub) Globber() *rpc.GlobState {
+	return s.gs
+}
+
+func (s implJudgeServerStub) Describe__() []rpc.InterfaceDesc {
+	return []rpc.InterfaceDesc{JudgeDesc}
+}
+
+// JudgeDesc describes the Judge interface.
+var JudgeDesc rpc.InterfaceDesc = descJudge
+
+// descJudge hides the desc to keep godoc clean.
+var descJudge = rpc.InterfaceDesc{
+	Name:    "Judge",
+	PkgPath: "v.io/x/ref/examples/rps",
+	Methods: []rpc.MethodDesc{
+		{
+			Name: "CreateGame",
+			Doc:  "// CreateGame creates a new game with the given game options and returns a game\n// identifier that can be used by the players to join the game.",
+			InArgs: []rpc.ArgDesc{
+				{"Opts", ``}, // GameOptions
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"", ``}, // GameId
+			},
+			Tags: []*vdl.Value{vdl.ValueOf(access.Tag("Write"))},
+		},
+		{
+			Name: "Play",
+			Doc:  "// Play lets a player join an existing game and play.",
+			InArgs: []rpc.ArgDesc{
+				{"Id", ``}, // GameId
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"", ``}, // PlayResult
+			},
+			Tags: []*vdl.Value{vdl.ValueOf(access.Tag("Write"))},
+		},
+	},
+}
+
+// JudgePlayServerStream is the server stream for Judge.Play.
+type JudgePlayServerStream interface {
+	// RecvStream returns the receiver side of the Judge.Play server stream.
+	RecvStream() interface {
+		// Advance stages an item so that it may be retrieved via Value.  Returns
+		// true iff there is an item to retrieve.  Advance must be called before
+		// Value is called.  May block if an item is not available.
+		Advance() bool
+		// Value returns the item that was staged by Advance.  May panic if Advance
+		// returned false or was not called.  Never blocks.
+		Value() PlayerAction
+		// Err returns any error encountered by Advance.  Never blocks.
+		Err() error
+	}
+	// SendStream returns the send side of the Judge.Play server stream.
+	SendStream() interface {
+		// Send places the item onto the output stream.  Returns errors encountered
+		// while sending.  Blocks if there is no buffer space; will unblock when
+		// buffer space is available.
+		Send(item JudgeAction) error
+	}
+}
+
+// JudgePlayServerCall represents the context passed to Judge.Play.
+type JudgePlayServerCall interface {
+	rpc.ServerCall
+	JudgePlayServerStream
+}
+
+// JudgePlayServerCallStub is a wrapper that converts rpc.StreamServerCall into
+// a typesafe stub that implements JudgePlayServerCall.
+type JudgePlayServerCallStub struct {
+	rpc.StreamServerCall
+	valRecv PlayerAction
+	errRecv error
+}
+
+// Init initializes JudgePlayServerCallStub from rpc.StreamServerCall.
+func (s *JudgePlayServerCallStub) Init(call rpc.StreamServerCall) {
+	s.StreamServerCall = call
+}
+
+// RecvStream returns the receiver side of the Judge.Play server stream.
+func (s *JudgePlayServerCallStub) RecvStream() interface {
+	Advance() bool
+	Value() PlayerAction
+	Err() error
+} {
+	return implJudgePlayServerCallRecv{s}
+}
+
+type implJudgePlayServerCallRecv struct {
+	s *JudgePlayServerCallStub
+}
+
+func (s implJudgePlayServerCallRecv) Advance() bool {
+	s.s.errRecv = s.s.Recv(&s.s.valRecv)
+	return s.s.errRecv == nil
+}
+func (s implJudgePlayServerCallRecv) Value() PlayerAction {
+	return s.s.valRecv
+}
+func (s implJudgePlayServerCallRecv) Err() error {
+	if s.s.errRecv == io.EOF {
+		return nil
+	}
+	return s.s.errRecv
+}
+
+// SendStream returns the send side of the Judge.Play server stream.
+func (s *JudgePlayServerCallStub) SendStream() interface {
+	Send(item JudgeAction) error
+} {
+	return implJudgePlayServerCallSend{s}
+}
+
+type implJudgePlayServerCallSend struct {
+	s *JudgePlayServerCallStub
+}
+
+func (s implJudgePlayServerCallSend) Send(item JudgeAction) error {
+	return s.s.Send(item)
+}
+
+// PlayerClientMethods is the client interface
+// containing Player methods.
+//
+// Player can receive challenges from other players.
+type PlayerClientMethods interface {
+	// Challenge is used by other players to challenge this player to a game. If
+	// the challenge is accepted, the method returns nil.
+	Challenge(ctx *context.T, Address string, Id GameId, Opts GameOptions, opts ...rpc.CallOpt) error
+}
+
+// PlayerClientStub adds universal methods to PlayerClientMethods.
+type PlayerClientStub interface {
+	PlayerClientMethods
+	rpc.UniversalServiceMethods
+}
+
+// PlayerClient returns a client stub for Player.
+func PlayerClient(name string) PlayerClientStub {
+	return implPlayerClientStub{name}
+}
+
+type implPlayerClientStub struct {
+	name string
+}
+
+func (c implPlayerClientStub) Challenge(ctx *context.T, i0 string, i1 GameId, i2 GameOptions, opts ...rpc.CallOpt) (err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "Challenge", []interface{}{i0, i1, i2}, nil, opts...)
+	return
+}
+
+// PlayerServerMethods is the interface a server writer
+// implements for Player.
+//
+// Player can receive challenges from other players.
+type PlayerServerMethods interface {
+	// Challenge is used by other players to challenge this player to a game. If
+	// the challenge is accepted, the method returns nil.
+	Challenge(ctx *context.T, call rpc.ServerCall, Address string, Id GameId, Opts GameOptions) error
+}
+
+// PlayerServerStubMethods is the server interface containing
+// Player methods, as expected by rpc.Server.
+// There is no difference between this interface and PlayerServerMethods
+// since there are no streaming methods.
+type PlayerServerStubMethods PlayerServerMethods
+
+// PlayerServerStub adds universal methods to PlayerServerStubMethods.
+type PlayerServerStub interface {
+	PlayerServerStubMethods
+	// Describe the Player interfaces.
+	Describe__() []rpc.InterfaceDesc
+}
+
+// PlayerServer returns a server stub for Player.
+// It converts an implementation of PlayerServerMethods into
+// an object that may be used by rpc.Server.
+func PlayerServer(impl PlayerServerMethods) PlayerServerStub {
+	stub := implPlayerServerStub{
+		impl: impl,
+	}
+	// Initialize GlobState; always check the stub itself first, to handle the
+	// case where the user has the Glob method defined in their VDL source.
+	if gs := rpc.NewGlobState(stub); gs != nil {
+		stub.gs = gs
+	} else if gs := rpc.NewGlobState(impl); gs != nil {
+		stub.gs = gs
+	}
+	return stub
+}
+
+type implPlayerServerStub struct {
+	impl PlayerServerMethods
+	gs   *rpc.GlobState
+}
+
+func (s implPlayerServerStub) Challenge(ctx *context.T, call rpc.ServerCall, i0 string, i1 GameId, i2 GameOptions) error {
+	return s.impl.Challenge(ctx, call, i0, i1, i2)
+}
+
+func (s implPlayerServerStub) Globber() *rpc.GlobState {
+	return s.gs
+}
+
+func (s implPlayerServerStub) Describe__() []rpc.InterfaceDesc {
+	return []rpc.InterfaceDesc{PlayerDesc}
+}
+
+// PlayerDesc describes the Player interface.
+var PlayerDesc rpc.InterfaceDesc = descPlayer
+
+// descPlayer hides the desc to keep godoc clean.
+var descPlayer = rpc.InterfaceDesc{
+	Name:    "Player",
+	PkgPath: "v.io/x/ref/examples/rps",
+	Doc:     "// Player can receive challenges from other players.",
+	Methods: []rpc.MethodDesc{
+		{
+			Name: "Challenge",
+			Doc:  "// Challenge is used by other players to challenge this player to a game. If\n// the challenge is accepted, the method returns nil.",
+			InArgs: []rpc.ArgDesc{
+				{"Address", ``}, // string
+				{"Id", ``},      // GameId
+				{"Opts", ``},    // GameOptions
+			},
+			Tags: []*vdl.Value{vdl.ValueOf(access.Tag("Write"))},
+		},
+	},
+}
+
+// ScoreKeeperClientMethods is the client interface
+// containing ScoreKeeper methods.
+//
+// ScoreKeeper receives the outcome of games from Judges.
+type ScoreKeeperClientMethods interface {
+	Record(ctx *context.T, Score ScoreCard, opts ...rpc.CallOpt) error
+}
+
+// ScoreKeeperClientStub adds universal methods to ScoreKeeperClientMethods.
+type ScoreKeeperClientStub interface {
+	ScoreKeeperClientMethods
+	rpc.UniversalServiceMethods
+}
+
+// ScoreKeeperClient returns a client stub for ScoreKeeper.
+func ScoreKeeperClient(name string) ScoreKeeperClientStub {
+	return implScoreKeeperClientStub{name}
+}
+
+type implScoreKeeperClientStub struct {
+	name string
+}
+
+func (c implScoreKeeperClientStub) Record(ctx *context.T, i0 ScoreCard, opts ...rpc.CallOpt) (err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "Record", []interface{}{i0}, nil, opts...)
+	return
+}
+
+// ScoreKeeperServerMethods is the interface a server writer
+// implements for ScoreKeeper.
+//
+// ScoreKeeper receives the outcome of games from Judges.
+type ScoreKeeperServerMethods interface {
+	Record(ctx *context.T, call rpc.ServerCall, Score ScoreCard) error
+}
+
+// ScoreKeeperServerStubMethods is the server interface containing
+// ScoreKeeper methods, as expected by rpc.Server.
+// There is no difference between this interface and ScoreKeeperServerMethods
+// since there are no streaming methods.
+type ScoreKeeperServerStubMethods ScoreKeeperServerMethods
+
+// ScoreKeeperServerStub adds universal methods to ScoreKeeperServerStubMethods.
+type ScoreKeeperServerStub interface {
+	ScoreKeeperServerStubMethods
+	// Describe the ScoreKeeper interfaces.
+	Describe__() []rpc.InterfaceDesc
+}
+
+// ScoreKeeperServer returns a server stub for ScoreKeeper.
+// It converts an implementation of ScoreKeeperServerMethods into
+// an object that may be used by rpc.Server.
+func ScoreKeeperServer(impl ScoreKeeperServerMethods) ScoreKeeperServerStub {
+	stub := implScoreKeeperServerStub{
+		impl: impl,
+	}
+	// Initialize GlobState; always check the stub itself first, to handle the
+	// case where the user has the Glob method defined in their VDL source.
+	if gs := rpc.NewGlobState(stub); gs != nil {
+		stub.gs = gs
+	} else if gs := rpc.NewGlobState(impl); gs != nil {
+		stub.gs = gs
+	}
+	return stub
+}
+
+type implScoreKeeperServerStub struct {
+	impl ScoreKeeperServerMethods
+	gs   *rpc.GlobState
+}
+
+func (s implScoreKeeperServerStub) Record(ctx *context.T, call rpc.ServerCall, i0 ScoreCard) error {
+	return s.impl.Record(ctx, call, i0)
+}
+
+func (s implScoreKeeperServerStub) Globber() *rpc.GlobState {
+	return s.gs
+}
+
+func (s implScoreKeeperServerStub) Describe__() []rpc.InterfaceDesc {
+	return []rpc.InterfaceDesc{ScoreKeeperDesc}
+}
+
+// ScoreKeeperDesc describes the ScoreKeeper interface.
+var ScoreKeeperDesc rpc.InterfaceDesc = descScoreKeeper
+
+// descScoreKeeper hides the desc to keep godoc clean.
+var descScoreKeeper = rpc.InterfaceDesc{
+	Name:    "ScoreKeeper",
+	PkgPath: "v.io/x/ref/examples/rps",
+	Doc:     "// ScoreKeeper receives the outcome of games from Judges.",
+	Methods: []rpc.MethodDesc{
+		{
+			Name: "Record",
+			InArgs: []rpc.ArgDesc{
+				{"Score", ``}, // ScoreCard
+			},
+			Tags: []*vdl.Value{vdl.ValueOf(access.Tag("Write"))},
+		},
+	},
+}
+
+// RockPaperScissorsClientMethods is the client interface
+// containing RockPaperScissors methods.
+type RockPaperScissorsClientMethods interface {
+	JudgeClientMethods
+	// Player can receive challenges from other players.
+	PlayerClientMethods
+	// ScoreKeeper receives the outcome of games from Judges.
+	ScoreKeeperClientMethods
+}
+
+// RockPaperScissorsClientStub adds universal methods to RockPaperScissorsClientMethods.
+type RockPaperScissorsClientStub interface {
+	RockPaperScissorsClientMethods
+	rpc.UniversalServiceMethods
+}
+
+// RockPaperScissorsClient returns a client stub for RockPaperScissors.
+func RockPaperScissorsClient(name string) RockPaperScissorsClientStub {
+	return implRockPaperScissorsClientStub{name, JudgeClient(name), PlayerClient(name), ScoreKeeperClient(name)}
+}
+
+type implRockPaperScissorsClientStub struct {
+	name string
+
+	JudgeClientStub
+	PlayerClientStub
+	ScoreKeeperClientStub
+}
+
+// RockPaperScissorsServerMethods is the interface a server writer
+// implements for RockPaperScissors.
+type RockPaperScissorsServerMethods interface {
+	JudgeServerMethods
+	// Player can receive challenges from other players.
+	PlayerServerMethods
+	// ScoreKeeper receives the outcome of games from Judges.
+	ScoreKeeperServerMethods
+}
+
+// RockPaperScissorsServerStubMethods is the server interface containing
+// RockPaperScissors methods, as expected by rpc.Server.
+// The only difference between this interface and RockPaperScissorsServerMethods
+// is the streaming methods.
+type RockPaperScissorsServerStubMethods interface {
+	JudgeServerStubMethods
+	// Player can receive challenges from other players.
+	PlayerServerStubMethods
+	// ScoreKeeper receives the outcome of games from Judges.
+	ScoreKeeperServerStubMethods
+}
+
+// RockPaperScissorsServerStub adds universal methods to RockPaperScissorsServerStubMethods.
+type RockPaperScissorsServerStub interface {
+	RockPaperScissorsServerStubMethods
+	// Describe the RockPaperScissors interfaces.
+	Describe__() []rpc.InterfaceDesc
+}
+
+// RockPaperScissorsServer returns a server stub for RockPaperScissors.
+// It converts an implementation of RockPaperScissorsServerMethods into
+// an object that may be used by rpc.Server.
+func RockPaperScissorsServer(impl RockPaperScissorsServerMethods) RockPaperScissorsServerStub {
+	stub := implRockPaperScissorsServerStub{
+		impl:                  impl,
+		JudgeServerStub:       JudgeServer(impl),
+		PlayerServerStub:      PlayerServer(impl),
+		ScoreKeeperServerStub: ScoreKeeperServer(impl),
+	}
+	// Initialize GlobState; always check the stub itself first, to handle the
+	// case where the user has the Glob method defined in their VDL source.
+	if gs := rpc.NewGlobState(stub); gs != nil {
+		stub.gs = gs
+	} else if gs := rpc.NewGlobState(impl); gs != nil {
+		stub.gs = gs
+	}
+	return stub
+}
+
+type implRockPaperScissorsServerStub struct {
+	impl RockPaperScissorsServerMethods
+	JudgeServerStub
+	PlayerServerStub
+	ScoreKeeperServerStub
+	gs *rpc.GlobState
+}
+
+func (s implRockPaperScissorsServerStub) Globber() *rpc.GlobState {
+	return s.gs
+}
+
+func (s implRockPaperScissorsServerStub) Describe__() []rpc.InterfaceDesc {
+	return []rpc.InterfaceDesc{RockPaperScissorsDesc, JudgeDesc, PlayerDesc, ScoreKeeperDesc}
+}
+
+// RockPaperScissorsDesc describes the RockPaperScissors interface.
+var RockPaperScissorsDesc rpc.InterfaceDesc = descRockPaperScissors
+
+// descRockPaperScissors hides the desc to keep godoc clean.
+var descRockPaperScissors = rpc.InterfaceDesc{
+	Name:    "RockPaperScissors",
+	PkgPath: "v.io/x/ref/examples/rps",
+	Embeds: []rpc.EmbedDesc{
+		{"Judge", "v.io/x/ref/examples/rps", ``},
+		{"Player", "v.io/x/ref/examples/rps", "// Player can receive challenges from other players."},
+		{"ScoreKeeper", "v.io/x/ref/examples/rps", "// ScoreKeeper receives the outcome of games from Judges."},
+	},
+}
diff --git a/examples/tunnel/internal/forward.go b/examples/tunnel/internal/forward.go
new file mode 100644
index 0000000..d87886a
--- /dev/null
+++ b/examples/tunnel/internal/forward.go
@@ -0,0 +1,68 @@
+// 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
+
+import (
+	"fmt"
+	"io"
+	"net"
+)
+
+type sender interface {
+	Send([]uint8) error
+}
+type receiver interface {
+	Advance() bool
+
+	Value() []uint8
+
+	Err() error
+}
+
+// Forward forwards data read from net.Conn to a TunnelForwardClientStream or a
+// TunnelForwardServerStream.
+func Forward(conn net.Conn, s sender, r receiver) error {
+	defer conn.Close()
+	// Both conn2stream and stream2conn will write to the channel exactly
+	// once.
+	// Forward reads from the channel exactly once.
+	// A buffered channel is used to prevent the other write to the channel
+	// from blocking.
+	done := make(chan error, 1)
+	go conn2stream(conn, s, done)
+	go stream2conn(r, conn, done)
+	return <-done
+}
+
+func conn2stream(r io.Reader, s sender, done chan error) {
+	var buf [2048]byte
+	for {
+		n, err := r.Read(buf[:])
+		if err == io.EOF {
+			done <- nil
+			return
+		}
+		if err != nil {
+			done <- err
+			return
+		}
+		if err := s.Send(buf[:n]); err != nil {
+			done <- err
+			return
+		}
+	}
+}
+
+func stream2conn(r receiver, w io.Writer, done chan error) {
+	for r.Advance() {
+		buf := r.Value()
+
+		if n, err := w.Write(buf); n != len(buf) || err != nil {
+			done <- fmt.Errorf("conn.Write returned (%d, %v) want (%d, nil)", n, err, len(buf))
+			return
+		}
+	}
+	done <- r.Err()
+}
diff --git a/examples/tunnel/internal/terminal.go b/examples/tunnel/internal/terminal.go
new file mode 100644
index 0000000..d8ca950
--- /dev/null
+++ b/examples/tunnel/internal/terminal.go
@@ -0,0 +1,104 @@
+// 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 defines common types and functions used by both tunnel
+// clients and servers.
+package internal
+
+import (
+	"errors"
+	"os/exec"
+	"strings"
+	"syscall"
+	"unsafe"
+
+	"v.io/x/ref/internal/logger"
+)
+
+// Winsize defines the window size used by ioctl TIOCGWINSZ and TIOCSWINSZ.
+type Winsize struct {
+	Row    uint16
+	Col    uint16
+	Xpixel uint16
+}
+
+// SetWindowSize sets the terminal's window size.
+func SetWindowSize(fd uintptr, ws Winsize) error {
+	logger.Global().Infof("Setting window size: %v", ws)
+	ret, _, _ := syscall.Syscall(
+		syscall.SYS_IOCTL,
+		fd,
+		uintptr(syscall.TIOCSWINSZ),
+		uintptr(unsafe.Pointer(&ws)))
+	if int(ret) == -1 {
+		return errors.New("ioctl(TIOCSWINSZ) failed")
+	}
+	return nil
+}
+
+// GetWindowSize gets the terminal's window size.
+func GetWindowSize() (*Winsize, error) {
+	ws := &Winsize{}
+	ret, _, _ := syscall.Syscall(
+		syscall.SYS_IOCTL,
+		uintptr(syscall.Stdin),
+		uintptr(syscall.TIOCGWINSZ),
+		uintptr(unsafe.Pointer(ws)))
+	if int(ret) == -1 {
+		return nil, errors.New("ioctl(TIOCGWINSZ) failed")
+	}
+	return ws, nil
+}
+
+// EnterRawTerminalMode uses stty to enter the terminal into raw mode; stdin is
+// unbuffered, local echo of input characters is disabled, and special signal
+// characters are disabled.  Returns a string which may be passed to
+// RestoreTerminalSettings to restore to the original terminal settings.
+func EnterRawTerminalMode() string {
+	var savedBytes []byte
+	var err error
+	if savedBytes, err = exec.Command("stty", "-F", "/dev/tty", "-g").Output(); err != nil {
+		logger.Global().Infof("Failed to save terminal settings: %q (%v)", savedBytes, err)
+	}
+	saved := strings.TrimSpace(string(savedBytes))
+
+	args := []string{
+		"-F", "/dev/tty",
+		// Don't buffer stdin. Read characters as they are typed.
+		"-icanon", "min", "1", "time", "0",
+		// Turn off local echo of input characters.
+		"-echo", "-echoe", "-echok", "-echonl",
+		// Disable interrupt, quit, and suspend special characters.
+		"-isig",
+		// Ignore characters with parity errors.
+		"ignpar",
+		// Disable translate newline to carriage return.
+		"-inlcr",
+		// Disable ignore carriage return.
+		"-igncr",
+		// Disable translate carriage return to newline.
+		"-icrnl",
+		// Disable flow control.
+		"-ixon", "-ixany", "-ixoff",
+		// Disable non-POSIX special characters.
+		"-iexten",
+	}
+	if out, err := exec.Command("stty", args...).CombinedOutput(); err != nil {
+		logger.Global().Infof("stty failed (%v) (%q)", err, out)
+	}
+
+	return string(saved)
+}
+
+// RestoreTerminalSettings uses stty to restore the terminal to the original
+// settings, taking the saved settings returned by EnterRawTerminalMode.
+func RestoreTerminalSettings(saved string) {
+	args := []string{
+		"-F", "/dev/tty",
+		saved,
+	}
+	if out, err := exec.Command("stty", args...).CombinedOutput(); err != nil {
+		logger.Global().Infof("stty failed (%v) (%q)", err, out)
+	}
+}
diff --git a/examples/tunnel/tunnel.vdl b/examples/tunnel/tunnel.vdl
new file mode 100644
index 0000000..f1bafcb
--- /dev/null
+++ b/examples/tunnel/tunnel.vdl
@@ -0,0 +1,53 @@
+// 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 tunnel defines an interface for creating a network tunnel from client
+// to server.
+package tunnel
+
+import "v.io/v23/security/access"
+
+type Tunnel interface {
+  // The Forward method is used for network forwarding. All the data sent over
+  // the byte stream is forwarded to the requested network address and all the
+  // data received from that network connection is sent back in the reply
+  // stream.
+  Forward(network, address string) stream<[]byte, []byte> error {access.Admin}
+
+  // The Shell method is used to either run shell commands remotely, or to open
+  // an interactive shell. The data received over the byte stream is sent to the
+  // shell's stdin, and the data received from the shell's stdout and stderr is
+  // sent back in the reply stream. It returns the exit status of the shell
+  // command.
+  Shell(command string, shellOpts ShellOpts) stream<ClientShellPacket, ServerShellPacket> (int32 | error) {access.Admin}
+}
+
+type ShellOpts struct {
+  UsePty      bool       // Whether to open a pseudo-terminal.
+  Environment []string   // Environment variables to pass to the remote shell.
+  WinSize     WindowSize // The size of the window.
+}
+
+type WindowSize struct {
+  Rows, Cols uint16
+}
+
+type ClientShellPacket union {
+  // Bytes going to the shell's stdin.
+  Stdin      []byte
+  // Indicates that stdin should be closed. The presence of this field indicates
+  // EOF. Its actual value is ignored.
+  EndOfFile  unused
+  // A dynamic update of the window size.
+  WinSize    WindowSize
+}
+
+type unused struct {}
+
+type ServerShellPacket union {
+  // Bytes coming from the shell's stdout.
+  Stdout []byte
+  // Bytes coming from the shell's stderr.
+  Stderr []byte
+}
diff --git a/examples/tunnel/tunnel.vdl.go b/examples/tunnel/tunnel.vdl.go
new file mode 100644
index 0000000..51973c2
--- /dev/null
+++ b/examples/tunnel/tunnel.vdl.go
@@ -0,0 +1,684 @@
+// 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 by the vanadium vdl tool.
+// Source: tunnel.vdl
+
+// Package tunnel defines an interface for creating a network tunnel from client
+// to server.
+package tunnel
+
+import (
+	// VDL system imports
+	"io"
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/vdl"
+
+	// VDL user imports
+	"v.io/v23/security/access"
+)
+
+type ShellOpts struct {
+	UsePty      bool       // Whether to open a pseudo-terminal.
+	Environment []string   // Environment variables to pass to the remote shell.
+	WinSize     WindowSize // The size of the window.
+}
+
+func (ShellOpts) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/examples/tunnel.ShellOpts"`
+}) {
+}
+
+type WindowSize struct {
+	Rows uint16
+	Cols uint16
+}
+
+func (WindowSize) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/examples/tunnel.WindowSize"`
+}) {
+}
+
+type (
+	// ClientShellPacket represents any single field of the ClientShellPacket union type.
+	ClientShellPacket interface {
+		// Index returns the field index.
+		Index() int
+		// Interface returns the field value as an interface.
+		Interface() interface{}
+		// Name returns the field name.
+		Name() string
+		// __VDLReflect describes the ClientShellPacket union type.
+		__VDLReflect(__ClientShellPacketReflect)
+	}
+	// ClientShellPacketStdin represents field Stdin of the ClientShellPacket union type.
+	//
+	// Bytes going to the shell's stdin.
+	ClientShellPacketStdin struct{ Value []byte }
+	// ClientShellPacketEndOfFile represents field EndOfFile of the ClientShellPacket union type.
+	//
+	// Indicates that stdin should be closed. The presence of this field indicates
+	// EOF. Its actual value is ignored.
+	ClientShellPacketEndOfFile struct{ Value unused }
+	// ClientShellPacketWinSize represents field WinSize of the ClientShellPacket union type.
+	//
+	// A dynamic update of the window size.
+	ClientShellPacketWinSize struct{ Value WindowSize }
+	// __ClientShellPacketReflect describes the ClientShellPacket union type.
+	__ClientShellPacketReflect struct {
+		Name  string `vdl:"v.io/x/ref/examples/tunnel.ClientShellPacket"`
+		Type  ClientShellPacket
+		Union struct {
+			Stdin     ClientShellPacketStdin
+			EndOfFile ClientShellPacketEndOfFile
+			WinSize   ClientShellPacketWinSize
+		}
+	}
+)
+
+func (x ClientShellPacketStdin) Index() int                              { return 0 }
+func (x ClientShellPacketStdin) Interface() interface{}                  { return x.Value }
+func (x ClientShellPacketStdin) Name() string                            { return "Stdin" }
+func (x ClientShellPacketStdin) __VDLReflect(__ClientShellPacketReflect) {}
+
+func (x ClientShellPacketEndOfFile) Index() int                              { return 1 }
+func (x ClientShellPacketEndOfFile) Interface() interface{}                  { return x.Value }
+func (x ClientShellPacketEndOfFile) Name() string                            { return "EndOfFile" }
+func (x ClientShellPacketEndOfFile) __VDLReflect(__ClientShellPacketReflect) {}
+
+func (x ClientShellPacketWinSize) Index() int                              { return 2 }
+func (x ClientShellPacketWinSize) Interface() interface{}                  { return x.Value }
+func (x ClientShellPacketWinSize) Name() string                            { return "WinSize" }
+func (x ClientShellPacketWinSize) __VDLReflect(__ClientShellPacketReflect) {}
+
+type unused struct {
+}
+
+func (unused) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/examples/tunnel.unused"`
+}) {
+}
+
+type (
+	// ServerShellPacket represents any single field of the ServerShellPacket union type.
+	ServerShellPacket interface {
+		// Index returns the field index.
+		Index() int
+		// Interface returns the field value as an interface.
+		Interface() interface{}
+		// Name returns the field name.
+		Name() string
+		// __VDLReflect describes the ServerShellPacket union type.
+		__VDLReflect(__ServerShellPacketReflect)
+	}
+	// ServerShellPacketStdout represents field Stdout of the ServerShellPacket union type.
+	//
+	// Bytes coming from the shell's stdout.
+	ServerShellPacketStdout struct{ Value []byte }
+	// ServerShellPacketStderr represents field Stderr of the ServerShellPacket union type.
+	//
+	// Bytes coming from the shell's stderr.
+	ServerShellPacketStderr struct{ Value []byte }
+	// __ServerShellPacketReflect describes the ServerShellPacket union type.
+	__ServerShellPacketReflect struct {
+		Name  string `vdl:"v.io/x/ref/examples/tunnel.ServerShellPacket"`
+		Type  ServerShellPacket
+		Union struct {
+			Stdout ServerShellPacketStdout
+			Stderr ServerShellPacketStderr
+		}
+	}
+)
+
+func (x ServerShellPacketStdout) Index() int                              { return 0 }
+func (x ServerShellPacketStdout) Interface() interface{}                  { return x.Value }
+func (x ServerShellPacketStdout) Name() string                            { return "Stdout" }
+func (x ServerShellPacketStdout) __VDLReflect(__ServerShellPacketReflect) {}
+
+func (x ServerShellPacketStderr) Index() int                              { return 1 }
+func (x ServerShellPacketStderr) Interface() interface{}                  { return x.Value }
+func (x ServerShellPacketStderr) Name() string                            { return "Stderr" }
+func (x ServerShellPacketStderr) __VDLReflect(__ServerShellPacketReflect) {}
+
+func init() {
+	vdl.Register((*ShellOpts)(nil))
+	vdl.Register((*WindowSize)(nil))
+	vdl.Register((*ClientShellPacket)(nil))
+	vdl.Register((*unused)(nil))
+	vdl.Register((*ServerShellPacket)(nil))
+}
+
+// TunnelClientMethods is the client interface
+// containing Tunnel methods.
+type TunnelClientMethods interface {
+	// The Forward method is used for network forwarding. All the data sent over
+	// the byte stream is forwarded to the requested network address and all the
+	// data received from that network connection is sent back in the reply
+	// stream.
+	Forward(ctx *context.T, network string, address string, opts ...rpc.CallOpt) (TunnelForwardClientCall, error)
+	// The Shell method is used to either run shell commands remotely, or to open
+	// an interactive shell. The data received over the byte stream is sent to the
+	// shell's stdin, and the data received from the shell's stdout and stderr is
+	// sent back in the reply stream. It returns the exit status of the shell
+	// command.
+	Shell(ctx *context.T, command string, shellOpts ShellOpts, opts ...rpc.CallOpt) (TunnelShellClientCall, error)
+}
+
+// TunnelClientStub adds universal methods to TunnelClientMethods.
+type TunnelClientStub interface {
+	TunnelClientMethods
+	rpc.UniversalServiceMethods
+}
+
+// TunnelClient returns a client stub for Tunnel.
+func TunnelClient(name string) TunnelClientStub {
+	return implTunnelClientStub{name}
+}
+
+type implTunnelClientStub struct {
+	name string
+}
+
+func (c implTunnelClientStub) Forward(ctx *context.T, i0 string, i1 string, opts ...rpc.CallOpt) (ocall TunnelForwardClientCall, err error) {
+	var call rpc.ClientCall
+	if call, err = v23.GetClient(ctx).StartCall(ctx, c.name, "Forward", []interface{}{i0, i1}, opts...); err != nil {
+		return
+	}
+	ocall = &implTunnelForwardClientCall{ClientCall: call}
+	return
+}
+
+func (c implTunnelClientStub) Shell(ctx *context.T, i0 string, i1 ShellOpts, opts ...rpc.CallOpt) (ocall TunnelShellClientCall, err error) {
+	var call rpc.ClientCall
+	if call, err = v23.GetClient(ctx).StartCall(ctx, c.name, "Shell", []interface{}{i0, i1}, opts...); err != nil {
+		return
+	}
+	ocall = &implTunnelShellClientCall{ClientCall: call}
+	return
+}
+
+// TunnelForwardClientStream is the client stream for Tunnel.Forward.
+type TunnelForwardClientStream interface {
+	// RecvStream returns the receiver side of the Tunnel.Forward client stream.
+	RecvStream() interface {
+		// Advance stages an item so that it may be retrieved via Value.  Returns
+		// true iff there is an item to retrieve.  Advance must be called before
+		// Value is called.  May block if an item is not available.
+		Advance() bool
+		// Value returns the item that was staged by Advance.  May panic if Advance
+		// returned false or was not called.  Never blocks.
+		Value() []byte
+		// Err returns any error encountered by Advance.  Never blocks.
+		Err() error
+	}
+	// SendStream returns the send side of the Tunnel.Forward client stream.
+	SendStream() interface {
+		// Send places the item onto the output stream.  Returns errors
+		// encountered while sending, or if Send is called after Close or
+		// the stream has been canceled.  Blocks if there is no buffer
+		// space; will unblock when buffer space is available or after
+		// the stream has been canceled.
+		Send(item []byte) error
+		// Close indicates to the server that no more items will be sent;
+		// server Recv calls will receive io.EOF after all sent items.
+		// This is an optional call - e.g. a client might call Close if it
+		// needs to continue receiving items from the server after it's
+		// done sending.  Returns errors encountered while closing, or if
+		// Close is called after the stream has been canceled.  Like Send,
+		// blocks if there is no buffer space available.
+		Close() error
+	}
+}
+
+// TunnelForwardClientCall represents the call returned from Tunnel.Forward.
+type TunnelForwardClientCall interface {
+	TunnelForwardClientStream
+	// Finish performs the equivalent of SendStream().Close, then blocks until
+	// the server is done, and returns the positional return values for the call.
+	//
+	// Finish returns immediately if the call has been canceled; depending on the
+	// timing the output could either be an error signaling cancelation, or the
+	// valid positional return values from the server.
+	//
+	// Calling Finish is mandatory for releasing stream resources, unless the call
+	// has been canceled or any of the other methods return an error.  Finish should
+	// be called at most once.
+	Finish() error
+}
+
+type implTunnelForwardClientCall struct {
+	rpc.ClientCall
+	valRecv []byte
+	errRecv error
+}
+
+func (c *implTunnelForwardClientCall) RecvStream() interface {
+	Advance() bool
+	Value() []byte
+	Err() error
+} {
+	return implTunnelForwardClientCallRecv{c}
+}
+
+type implTunnelForwardClientCallRecv struct {
+	c *implTunnelForwardClientCall
+}
+
+func (c implTunnelForwardClientCallRecv) Advance() bool {
+	c.c.errRecv = c.c.Recv(&c.c.valRecv)
+	return c.c.errRecv == nil
+}
+func (c implTunnelForwardClientCallRecv) Value() []byte {
+	return c.c.valRecv
+}
+func (c implTunnelForwardClientCallRecv) Err() error {
+	if c.c.errRecv == io.EOF {
+		return nil
+	}
+	return c.c.errRecv
+}
+func (c *implTunnelForwardClientCall) SendStream() interface {
+	Send(item []byte) error
+	Close() error
+} {
+	return implTunnelForwardClientCallSend{c}
+}
+
+type implTunnelForwardClientCallSend struct {
+	c *implTunnelForwardClientCall
+}
+
+func (c implTunnelForwardClientCallSend) Send(item []byte) error {
+	return c.c.Send(item)
+}
+func (c implTunnelForwardClientCallSend) Close() error {
+	return c.c.CloseSend()
+}
+func (c *implTunnelForwardClientCall) Finish() (err error) {
+	err = c.ClientCall.Finish()
+	return
+}
+
+// TunnelShellClientStream is the client stream for Tunnel.Shell.
+type TunnelShellClientStream interface {
+	// RecvStream returns the receiver side of the Tunnel.Shell client stream.
+	RecvStream() interface {
+		// Advance stages an item so that it may be retrieved via Value.  Returns
+		// true iff there is an item to retrieve.  Advance must be called before
+		// Value is called.  May block if an item is not available.
+		Advance() bool
+		// Value returns the item that was staged by Advance.  May panic if Advance
+		// returned false or was not called.  Never blocks.
+		Value() ServerShellPacket
+		// Err returns any error encountered by Advance.  Never blocks.
+		Err() error
+	}
+	// SendStream returns the send side of the Tunnel.Shell client stream.
+	SendStream() interface {
+		// Send places the item onto the output stream.  Returns errors
+		// encountered while sending, or if Send is called after Close or
+		// the stream has been canceled.  Blocks if there is no buffer
+		// space; will unblock when buffer space is available or after
+		// the stream has been canceled.
+		Send(item ClientShellPacket) error
+		// Close indicates to the server that no more items will be sent;
+		// server Recv calls will receive io.EOF after all sent items.
+		// This is an optional call - e.g. a client might call Close if it
+		// needs to continue receiving items from the server after it's
+		// done sending.  Returns errors encountered while closing, or if
+		// Close is called after the stream has been canceled.  Like Send,
+		// blocks if there is no buffer space available.
+		Close() error
+	}
+}
+
+// TunnelShellClientCall represents the call returned from Tunnel.Shell.
+type TunnelShellClientCall interface {
+	TunnelShellClientStream
+	// Finish performs the equivalent of SendStream().Close, then blocks until
+	// the server is done, and returns the positional return values for the call.
+	//
+	// Finish returns immediately if the call has been canceled; depending on the
+	// timing the output could either be an error signaling cancelation, or the
+	// valid positional return values from the server.
+	//
+	// Calling Finish is mandatory for releasing stream resources, unless the call
+	// has been canceled or any of the other methods return an error.  Finish should
+	// be called at most once.
+	Finish() (int32, error)
+}
+
+type implTunnelShellClientCall struct {
+	rpc.ClientCall
+	valRecv ServerShellPacket
+	errRecv error
+}
+
+func (c *implTunnelShellClientCall) RecvStream() interface {
+	Advance() bool
+	Value() ServerShellPacket
+	Err() error
+} {
+	return implTunnelShellClientCallRecv{c}
+}
+
+type implTunnelShellClientCallRecv struct {
+	c *implTunnelShellClientCall
+}
+
+func (c implTunnelShellClientCallRecv) Advance() bool {
+	c.c.errRecv = c.c.Recv(&c.c.valRecv)
+	return c.c.errRecv == nil
+}
+func (c implTunnelShellClientCallRecv) Value() ServerShellPacket {
+	return c.c.valRecv
+}
+func (c implTunnelShellClientCallRecv) Err() error {
+	if c.c.errRecv == io.EOF {
+		return nil
+	}
+	return c.c.errRecv
+}
+func (c *implTunnelShellClientCall) SendStream() interface {
+	Send(item ClientShellPacket) error
+	Close() error
+} {
+	return implTunnelShellClientCallSend{c}
+}
+
+type implTunnelShellClientCallSend struct {
+	c *implTunnelShellClientCall
+}
+
+func (c implTunnelShellClientCallSend) Send(item ClientShellPacket) error {
+	return c.c.Send(item)
+}
+func (c implTunnelShellClientCallSend) Close() error {
+	return c.c.CloseSend()
+}
+func (c *implTunnelShellClientCall) Finish() (o0 int32, err error) {
+	err = c.ClientCall.Finish(&o0)
+	return
+}
+
+// TunnelServerMethods is the interface a server writer
+// implements for Tunnel.
+type TunnelServerMethods interface {
+	// The Forward method is used for network forwarding. All the data sent over
+	// the byte stream is forwarded to the requested network address and all the
+	// data received from that network connection is sent back in the reply
+	// stream.
+	Forward(ctx *context.T, call TunnelForwardServerCall, network string, address string) error
+	// The Shell method is used to either run shell commands remotely, or to open
+	// an interactive shell. The data received over the byte stream is sent to the
+	// shell's stdin, and the data received from the shell's stdout and stderr is
+	// sent back in the reply stream. It returns the exit status of the shell
+	// command.
+	Shell(ctx *context.T, call TunnelShellServerCall, command string, shellOpts ShellOpts) (int32, error)
+}
+
+// TunnelServerStubMethods is the server interface containing
+// Tunnel methods, as expected by rpc.Server.
+// The only difference between this interface and TunnelServerMethods
+// is the streaming methods.
+type TunnelServerStubMethods interface {
+	// The Forward method is used for network forwarding. All the data sent over
+	// the byte stream is forwarded to the requested network address and all the
+	// data received from that network connection is sent back in the reply
+	// stream.
+	Forward(ctx *context.T, call *TunnelForwardServerCallStub, network string, address string) error
+	// The Shell method is used to either run shell commands remotely, or to open
+	// an interactive shell. The data received over the byte stream is sent to the
+	// shell's stdin, and the data received from the shell's stdout and stderr is
+	// sent back in the reply stream. It returns the exit status of the shell
+	// command.
+	Shell(ctx *context.T, call *TunnelShellServerCallStub, command string, shellOpts ShellOpts) (int32, error)
+}
+
+// TunnelServerStub adds universal methods to TunnelServerStubMethods.
+type TunnelServerStub interface {
+	TunnelServerStubMethods
+	// Describe the Tunnel interfaces.
+	Describe__() []rpc.InterfaceDesc
+}
+
+// TunnelServer returns a server stub for Tunnel.
+// It converts an implementation of TunnelServerMethods into
+// an object that may be used by rpc.Server.
+func TunnelServer(impl TunnelServerMethods) TunnelServerStub {
+	stub := implTunnelServerStub{
+		impl: impl,
+	}
+	// Initialize GlobState; always check the stub itself first, to handle the
+	// case where the user has the Glob method defined in their VDL source.
+	if gs := rpc.NewGlobState(stub); gs != nil {
+		stub.gs = gs
+	} else if gs := rpc.NewGlobState(impl); gs != nil {
+		stub.gs = gs
+	}
+	return stub
+}
+
+type implTunnelServerStub struct {
+	impl TunnelServerMethods
+	gs   *rpc.GlobState
+}
+
+func (s implTunnelServerStub) Forward(ctx *context.T, call *TunnelForwardServerCallStub, i0 string, i1 string) error {
+	return s.impl.Forward(ctx, call, i0, i1)
+}
+
+func (s implTunnelServerStub) Shell(ctx *context.T, call *TunnelShellServerCallStub, i0 string, i1 ShellOpts) (int32, error) {
+	return s.impl.Shell(ctx, call, i0, i1)
+}
+
+func (s implTunnelServerStub) Globber() *rpc.GlobState {
+	return s.gs
+}
+
+func (s implTunnelServerStub) Describe__() []rpc.InterfaceDesc {
+	return []rpc.InterfaceDesc{TunnelDesc}
+}
+
+// TunnelDesc describes the Tunnel interface.
+var TunnelDesc rpc.InterfaceDesc = descTunnel
+
+// descTunnel hides the desc to keep godoc clean.
+var descTunnel = rpc.InterfaceDesc{
+	Name:    "Tunnel",
+	PkgPath: "v.io/x/ref/examples/tunnel",
+	Methods: []rpc.MethodDesc{
+		{
+			Name: "Forward",
+			Doc:  "// The Forward method is used for network forwarding. All the data sent over\n// the byte stream is forwarded to the requested network address and all the\n// data received from that network connection is sent back in the reply\n// stream.",
+			InArgs: []rpc.ArgDesc{
+				{"network", ``}, // string
+				{"address", ``}, // string
+			},
+			Tags: []*vdl.Value{vdl.ValueOf(access.Tag("Admin"))},
+		},
+		{
+			Name: "Shell",
+			Doc:  "// The Shell method is used to either run shell commands remotely, or to open\n// an interactive shell. The data received over the byte stream is sent to the\n// shell's stdin, and the data received from the shell's stdout and stderr is\n// sent back in the reply stream. It returns the exit status of the shell\n// command.",
+			InArgs: []rpc.ArgDesc{
+				{"command", ``},   // string
+				{"shellOpts", ``}, // ShellOpts
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"", ``}, // int32
+			},
+			Tags: []*vdl.Value{vdl.ValueOf(access.Tag("Admin"))},
+		},
+	},
+}
+
+// TunnelForwardServerStream is the server stream for Tunnel.Forward.
+type TunnelForwardServerStream interface {
+	// RecvStream returns the receiver side of the Tunnel.Forward server stream.
+	RecvStream() interface {
+		// Advance stages an item so that it may be retrieved via Value.  Returns
+		// true iff there is an item to retrieve.  Advance must be called before
+		// Value is called.  May block if an item is not available.
+		Advance() bool
+		// Value returns the item that was staged by Advance.  May panic if Advance
+		// returned false or was not called.  Never blocks.
+		Value() []byte
+		// Err returns any error encountered by Advance.  Never blocks.
+		Err() error
+	}
+	// SendStream returns the send side of the Tunnel.Forward server stream.
+	SendStream() interface {
+		// Send places the item onto the output stream.  Returns errors encountered
+		// while sending.  Blocks if there is no buffer space; will unblock when
+		// buffer space is available.
+		Send(item []byte) error
+	}
+}
+
+// TunnelForwardServerCall represents the context passed to Tunnel.Forward.
+type TunnelForwardServerCall interface {
+	rpc.ServerCall
+	TunnelForwardServerStream
+}
+
+// TunnelForwardServerCallStub is a wrapper that converts rpc.StreamServerCall into
+// a typesafe stub that implements TunnelForwardServerCall.
+type TunnelForwardServerCallStub struct {
+	rpc.StreamServerCall
+	valRecv []byte
+	errRecv error
+}
+
+// Init initializes TunnelForwardServerCallStub from rpc.StreamServerCall.
+func (s *TunnelForwardServerCallStub) Init(call rpc.StreamServerCall) {
+	s.StreamServerCall = call
+}
+
+// RecvStream returns the receiver side of the Tunnel.Forward server stream.
+func (s *TunnelForwardServerCallStub) RecvStream() interface {
+	Advance() bool
+	Value() []byte
+	Err() error
+} {
+	return implTunnelForwardServerCallRecv{s}
+}
+
+type implTunnelForwardServerCallRecv struct {
+	s *TunnelForwardServerCallStub
+}
+
+func (s implTunnelForwardServerCallRecv) Advance() bool {
+	s.s.errRecv = s.s.Recv(&s.s.valRecv)
+	return s.s.errRecv == nil
+}
+func (s implTunnelForwardServerCallRecv) Value() []byte {
+	return s.s.valRecv
+}
+func (s implTunnelForwardServerCallRecv) Err() error {
+	if s.s.errRecv == io.EOF {
+		return nil
+	}
+	return s.s.errRecv
+}
+
+// SendStream returns the send side of the Tunnel.Forward server stream.
+func (s *TunnelForwardServerCallStub) SendStream() interface {
+	Send(item []byte) error
+} {
+	return implTunnelForwardServerCallSend{s}
+}
+
+type implTunnelForwardServerCallSend struct {
+	s *TunnelForwardServerCallStub
+}
+
+func (s implTunnelForwardServerCallSend) Send(item []byte) error {
+	return s.s.Send(item)
+}
+
+// TunnelShellServerStream is the server stream for Tunnel.Shell.
+type TunnelShellServerStream interface {
+	// RecvStream returns the receiver side of the Tunnel.Shell server stream.
+	RecvStream() interface {
+		// Advance stages an item so that it may be retrieved via Value.  Returns
+		// true iff there is an item to retrieve.  Advance must be called before
+		// Value is called.  May block if an item is not available.
+		Advance() bool
+		// Value returns the item that was staged by Advance.  May panic if Advance
+		// returned false or was not called.  Never blocks.
+		Value() ClientShellPacket
+		// Err returns any error encountered by Advance.  Never blocks.
+		Err() error
+	}
+	// SendStream returns the send side of the Tunnel.Shell server stream.
+	SendStream() interface {
+		// Send places the item onto the output stream.  Returns errors encountered
+		// while sending.  Blocks if there is no buffer space; will unblock when
+		// buffer space is available.
+		Send(item ServerShellPacket) error
+	}
+}
+
+// TunnelShellServerCall represents the context passed to Tunnel.Shell.
+type TunnelShellServerCall interface {
+	rpc.ServerCall
+	TunnelShellServerStream
+}
+
+// TunnelShellServerCallStub is a wrapper that converts rpc.StreamServerCall into
+// a typesafe stub that implements TunnelShellServerCall.
+type TunnelShellServerCallStub struct {
+	rpc.StreamServerCall
+	valRecv ClientShellPacket
+	errRecv error
+}
+
+// Init initializes TunnelShellServerCallStub from rpc.StreamServerCall.
+func (s *TunnelShellServerCallStub) Init(call rpc.StreamServerCall) {
+	s.StreamServerCall = call
+}
+
+// RecvStream returns the receiver side of the Tunnel.Shell server stream.
+func (s *TunnelShellServerCallStub) RecvStream() interface {
+	Advance() bool
+	Value() ClientShellPacket
+	Err() error
+} {
+	return implTunnelShellServerCallRecv{s}
+}
+
+type implTunnelShellServerCallRecv struct {
+	s *TunnelShellServerCallStub
+}
+
+func (s implTunnelShellServerCallRecv) Advance() bool {
+	s.s.errRecv = s.s.Recv(&s.s.valRecv)
+	return s.s.errRecv == nil
+}
+func (s implTunnelShellServerCallRecv) Value() ClientShellPacket {
+	return s.s.valRecv
+}
+func (s implTunnelShellServerCallRecv) Err() error {
+	if s.s.errRecv == io.EOF {
+		return nil
+	}
+	return s.s.errRecv
+}
+
+// SendStream returns the send side of the Tunnel.Shell server stream.
+func (s *TunnelShellServerCallStub) SendStream() interface {
+	Send(item ServerShellPacket) error
+} {
+	return implTunnelShellServerCallSend{s}
+}
+
+type implTunnelShellServerCallSend struct {
+	s *TunnelShellServerCallStub
+}
+
+func (s implTunnelShellServerCallSend) Send(item ServerShellPacket) error {
+	return s.s.Send(item)
+}
diff --git a/examples/tunnel/tunneld/doc.go b/examples/tunnel/tunneld/doc.go
new file mode 100644
index 0000000..653eab7
--- /dev/null
+++ b/examples/tunnel/tunneld/doc.go
@@ -0,0 +1,67 @@
+// 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
+
+/*
+Command tunneld runs the tunneld daemon, which implements the Tunnel interface.
+
+Usage:
+   tunneld [flags]
+
+The tunneld flags are:
+ -name=
+   Name to publish the server as.
+
+The global flags are:
+ -alsologtostderr=true
+   log to standard error as well as files
+ -log_backtrace_at=:0
+   when logging hits line file:N, emit a stack trace
+ -log_dir=
+   if non-empty, write log files to this directory
+ -logtostderr=false
+   log to standard error instead of files
+ -max_stack_buf_size=4292608
+   max size in bytes of the buffer to use for logging stack traces
+ -metadata=<just specify -metadata to activate>
+   Displays metadata for the program and exits.
+ -stderrthreshold=2
+   logs at or above this threshold go to stderr
+ -v=0
+   log level for V logs
+ -v23.credentials=
+   directory to use for storing security credentials
+ -v23.i18n-catalogue=
+   18n catalogue files to load, comma separated
+ -v23.namespace.root=[/(dev.v.io/role/vprod/service/mounttabled)@ns.dev.v.io:8101]
+   local namespace root; can be repeated to provided multiple roots
+ -v23.permissions.file=map[]
+   specify a perms file as <name>:<permsfile>
+ -v23.permissions.literal=
+   explicitly specify the runtime perms as a JSON-encoded access.Permissions.
+   Overrides all --v23.permissions.file flags.
+ -v23.proxy=
+   object name of proxy service to use to export services across network
+   boundaries
+ -v23.tcp.address=
+   address to listen on
+ -v23.tcp.protocol=wsh
+   protocol to listen with
+ -v23.vtrace.cache-size=1024
+   The number of vtrace traces to store in memory.
+ -v23.vtrace.collect-regexp=
+   Spans and annotations that match this regular expression will trigger trace
+   collection.
+ -v23.vtrace.dump-on-shutdown=true
+   If true, dump all stored traces on runtime shutdown.
+ -v23.vtrace.sample-rate=0
+   Rate (from 0.0 to 1.0) to sample vtrace traces.
+ -vmodule=
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
+*/
+package main
diff --git a/examples/tunnel/tunneld/impl.go b/examples/tunnel/tunneld/impl.go
new file mode 100644
index 0000000..64f68a7
--- /dev/null
+++ b/examples/tunnel/tunneld/impl.go
@@ -0,0 +1,175 @@
+// 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 (
+	"github.com/kr/pty"
+
+	"errors"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"net"
+	"os"
+	"os/exec"
+	"syscall"
+
+	"v.io/v23/context"
+	"v.io/v23/logging"
+	"v.io/v23/security"
+	"v.io/x/ref/examples/tunnel"
+	"v.io/x/ref/examples/tunnel/internal"
+)
+
+// T implements tunnel.TunnelServerMethods
+type T struct {
+}
+
+const nonShellErrorCode = 255
+
+func (t *T) Forward(ctx *context.T, call tunnel.TunnelForwardServerCall, network, address string) error {
+	conn, err := net.Dial(network, address)
+	if err != nil {
+		return err
+	}
+	b, _ := security.RemoteBlessingNames(ctx, call.Security())
+	name := fmt.Sprintf("RemoteBlessings:%v LocalAddr:%v RemoteAddr:%v", b, conn.LocalAddr(), conn.RemoteAddr())
+	ctx.Infof("TUNNEL START: %v", name)
+	err = internal.Forward(conn, call.SendStream(), call.RecvStream())
+	ctx.Infof("TUNNEL END  : %v (%v)", name, err)
+	return err
+}
+
+func (t *T) Shell(ctx *context.T, call tunnel.TunnelShellServerCall, command string, shellOpts tunnel.ShellOpts) (int32, error) {
+	b, _ := security.RemoteBlessingNames(ctx, call.Security())
+	ctx.Infof("SHELL START for %v: %q", b, command)
+	shell, err := findShell()
+	if err != nil {
+		return nonShellErrorCode, err
+	}
+	var c *exec.Cmd
+	// An empty command means that we need an interactive shell.
+	if len(command) == 0 {
+		c = exec.Command(shell, "-i")
+		sendMotd(ctx, call)
+	} else {
+		c = exec.Command(shell, "-c", command)
+	}
+
+	c.Env = []string{
+		fmt.Sprintf("HOME=%s", os.Getenv("HOME")),
+		fmt.Sprintf("PATH=%s", os.Getenv("PATH")),
+	}
+	c.Env = append(c.Env, shellOpts.Environment...)
+	ctx.Infof("Shell environment: %v", c.Env)
+
+	c.Dir = os.Getenv("HOME")
+	ctx.Infof("Shell CWD: %v", c.Dir)
+
+	var (
+		stdin          io.WriteCloser // We write to stdin.
+		stdout, stderr io.ReadCloser  // We read from stdout and stderr.
+		ptyFd          uintptr        // File descriptor for pty.
+	)
+
+	if shellOpts.UsePty {
+		f, err := pty.Start(c)
+		if err != nil {
+			return nonShellErrorCode, err
+		}
+		stdin = f
+		stdout = f
+		stderr = nil
+		ptyFd = f.Fd()
+
+		defer f.Close()
+
+		setWindowSize(ctx, ptyFd, shellOpts.WinSize.Rows, shellOpts.WinSize.Cols)
+	} else {
+		var err error
+		if stdin, err = c.StdinPipe(); err != nil {
+			return nonShellErrorCode, err
+		}
+		defer stdin.Close()
+
+		if stdout, err = c.StdoutPipe(); err != nil {
+			return nonShellErrorCode, err
+		}
+		defer stdout.Close()
+
+		if stderr, err = c.StderrPipe(); err != nil {
+			return nonShellErrorCode, err
+		}
+		defer stderr.Close()
+
+		if err = c.Start(); err != nil {
+			ctx.Infof("Cmd.Start failed: %v", err)
+			return nonShellErrorCode, err
+		}
+	}
+
+	defer c.Process.Kill()
+
+	select {
+	case runErr := <-runIOManager(stdin, stdout, stderr, ptyFd, call):
+		b, _ := security.RemoteBlessingNames(ctx, call.Security())
+		ctx.Infof("SHELL END for %v: %q (%v)", b, command, runErr)
+		return harvestExitcode(c.Process, runErr)
+	case <-ctx.Done():
+		return nonShellErrorCode, fmt.Errorf("remote end exited")
+	}
+}
+
+// harvestExitcode returns the (exitcode, error) pair to be returned for the Shell RPC
+// based on the status of the process and the error returned from runIOManager
+func harvestExitcode(process *os.Process, ioerr error) (int32, error) {
+	// Check the exit status.
+	var status syscall.WaitStatus
+	if _, err := syscall.Wait4(process.Pid, &status, syscall.WNOHANG, nil); err != nil {
+		return nonShellErrorCode, err
+	}
+	if status.Signaled() {
+		return int32(status), fmt.Errorf("process killed by signal %d (%v)", status.Signal(), status.Signal())
+	}
+	if status.Exited() {
+		if status.ExitStatus() == 0 {
+			return 0, nil
+		}
+		return int32(status.ExitStatus()), fmt.Errorf("process exited with exit status %d", status.ExitStatus())
+	}
+	// The process has not exited. Use the error from ForwardStdIO.
+	return nonShellErrorCode, ioerr
+}
+
+// findShell returns the path to the first usable shell binary.
+func findShell() (string, error) {
+	shells := []string{"/bin/bash", "/bin/sh"}
+	for _, s := range shells {
+		if _, err := os.Stat(s); err == nil {
+			return s, nil
+		}
+	}
+	return "", errors.New("could not find any shell binary")
+}
+
+// sendMotd sends the content of the MOTD file to the stream, if it exists.
+func sendMotd(logger logging.Logger, s tunnel.TunnelShellServerStream) {
+	data, err := ioutil.ReadFile("/etc/motd")
+	if err != nil {
+		// No MOTD. That's OK.
+		return
+	}
+	packet := tunnel.ServerShellPacketStdout{[]byte(data)}
+	if err = s.SendStream().Send(packet); err != nil {
+		logger.Infof("Send failed: %v", err)
+	}
+}
+
+func setWindowSize(logger logging.Logger, fd uintptr, row, col uint16) {
+	ws := internal.Winsize{Row: row, Col: col}
+	if err := internal.SetWindowSize(fd, ws); err != nil {
+		logger.Infof("Failed to set window size: %v", err)
+	}
+}
diff --git a/examples/tunnel/tunneld/iomanager.go b/examples/tunnel/tunneld/iomanager.go
new file mode 100644
index 0000000..509e2dc
--- /dev/null
+++ b/examples/tunnel/tunneld/iomanager.go
@@ -0,0 +1,191 @@
+// 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 (
+	"fmt"
+	"io"
+	"sync"
+
+	"v.io/x/ref/examples/tunnel"
+	"v.io/x/ref/internal/logger"
+)
+
+func runIOManager(stdin io.WriteCloser, stdout, stderr io.Reader, ptyFd uintptr, stream tunnel.TunnelShellServerStream) <-chan error {
+	m := ioManager{stdin: stdin, stdout: stdout, stderr: stderr, ptyFd: ptyFd, stream: stream}
+	c := make(chan error, 1) // buffered channel so that the goroutine spawned below is not leaked if the channel is not read from.
+	go func() { c <- m.run() }()
+	return c
+}
+
+// ioManager manages the forwarding of all the data between the shell and the
+// stream.
+type ioManager struct {
+	stdin          io.WriteCloser
+	stdout, stderr io.Reader
+	ptyFd          uintptr
+	stream         tunnel.TunnelShellServerStream
+
+	// streamError receives errors coming from stream operations.
+	streamError chan error
+	// stdioError receives errors coming from stdio operations.
+	stdioError chan error
+}
+
+func (m *ioManager) run() error {
+	m.streamError = make(chan error, 1)
+	m.stdioError = make(chan error, 1)
+
+	var pendingShellOutput sync.WaitGroup
+	pendingShellOutput.Add(1)
+	var pendingStreamInput sync.WaitGroup
+	pendingStreamInput.Add(1)
+
+	// Forward data between the shell's stdio and the stream.
+	go func() {
+		defer pendingShellOutput.Done()
+		// outchan is used to serialize the output to the stream.
+		// chan2stream() receives data sent by stdout2outchan() and
+		// stderr2outchan() and sends it to the stream.
+		outchan := make(chan tunnel.ServerShellPacket)
+		var wgStream sync.WaitGroup
+		wgStream.Add(1)
+		go m.chan2stream(outchan, &wgStream)
+		var wgStdio sync.WaitGroup
+		wgStdio.Add(1)
+		go m.stdout2outchan(outchan, &wgStdio)
+		if m.stderr != nil {
+			wgStdio.Add(1)
+			go m.stderr2outchan(outchan, &wgStdio)
+		}
+		// When both stdout2outchan and stderr2outchan are done, close
+		// outchan to signal chan2stream to exit.
+		wgStdio.Wait()
+		close(outchan)
+		wgStream.Wait()
+	}()
+	go m.stream2stdin(&pendingStreamInput)
+
+	// Block until something reports an error.
+	//
+	// If there is any stream error, we assume that both ends of the stream
+	// have an error, e.g. if stream.Reader.Advance fails then
+	// stream.Sender.Send will fail. We process any remaining input from
+	// the stream and then return.
+	//
+	// If there is any stdio error, we assume all 3 io channels will fail
+	// (if stdout.Read fails then stdin.Write and stderr.Read will also
+	// fail). We process is remaining output from the shell and then
+	// return.
+	select {
+	case err := <-m.streamError:
+		// Process remaining input from the stream before exiting.
+		logger.Global().VI(2).Infof("run stream error: %v", err)
+		pendingStreamInput.Wait()
+		return err
+	case err := <-m.stdioError:
+		// Process remaining output from the shell before exiting.
+		logger.Global().VI(2).Infof("run stdio error: %v", err)
+		pendingShellOutput.Wait()
+		return err
+	}
+}
+
+func (m *ioManager) sendStreamError(err error) {
+	select {
+	case m.streamError <- err:
+	default:
+	}
+}
+
+func (m *ioManager) sendStdioError(err error) {
+	select {
+	case m.stdioError <- err:
+	default:
+	}
+}
+
+// chan2stream receives ServerShellPacket from outchan and sends it to stream.
+func (m *ioManager) chan2stream(outchan <-chan tunnel.ServerShellPacket, wg *sync.WaitGroup) {
+	defer wg.Done()
+	sender := m.stream.SendStream()
+	for packet := range outchan {
+		logger.Global().VI(3).Infof("chan2stream packet: %+v", packet)
+		if err := sender.Send(packet); err != nil {
+			logger.Global().VI(2).Infof("chan2stream: %v", err)
+			m.sendStreamError(err)
+		}
+	}
+}
+
+// stdout2stream reads data from the shell's stdout and sends it to the outchan.
+func (m *ioManager) stdout2outchan(outchan chan<- tunnel.ServerShellPacket, wg *sync.WaitGroup) {
+	defer wg.Done()
+	for {
+		buf := make([]byte, 2048)
+		n, err := m.stdout.Read(buf[:])
+		if err != nil {
+			logger.Global().VI(2).Infof("stdout2outchan: %v", err)
+			m.sendStdioError(err)
+			return
+		}
+		outchan <- tunnel.ServerShellPacketStdout{buf[:n]}
+	}
+}
+
+// stderr2stream reads data from the shell's stderr and sends it to the outchan.
+func (m *ioManager) stderr2outchan(outchan chan<- tunnel.ServerShellPacket, wg *sync.WaitGroup) {
+	defer wg.Done()
+	for {
+		buf := make([]byte, 2048)
+		n, err := m.stderr.Read(buf[:])
+		if err != nil {
+			logger.Global().VI(2).Infof("stderr2outchan: %v", err)
+			m.sendStdioError(err)
+			return
+		}
+		outchan <- tunnel.ServerShellPacketStderr{buf[:n]}
+	}
+}
+
+// stream2stdin reads data from the stream and sends it to the shell's stdin.
+func (m *ioManager) stream2stdin(wg *sync.WaitGroup) {
+	defer wg.Done()
+	rStream := m.stream.RecvStream()
+	for rStream.Advance() {
+		packet := rStream.Value()
+		logger.Global().VI(3).Infof("stream2stdin packet: %+v", packet)
+		switch v := packet.(type) {
+		case tunnel.ClientShellPacketStdin:
+			if n, err := m.stdin.Write(v.Value); n != len(v.Value) || err != nil {
+				m.sendStdioError(fmt.Errorf("stdin.Write returned (%d, %v) want (%d, nil)", n, err, len(v.Value)))
+				return
+			}
+		case tunnel.ClientShellPacketEndOfFile:
+			if err := m.stdin.Close(); err != nil {
+				m.sendStdioError(fmt.Errorf("stdin.Close: %v", err))
+				return
+			}
+		case tunnel.ClientShellPacketWinSize:
+			size := v.Value
+			if size.Rows > 0 && size.Cols > 0 && m.ptyFd != 0 {
+				setWindowSize(logger.Global(), m.ptyFd, size.Rows, size.Cols)
+			}
+		default:
+			logger.Global().Infof("unexpected message type: %T", packet)
+		}
+	}
+
+	err := rStream.Err()
+	if err == nil {
+		err = io.EOF
+	}
+
+	logger.Global().VI(2).Infof("stream2stdin: %v", err)
+	m.sendStreamError(err)
+	if err := m.stdin.Close(); err != nil {
+		m.sendStdioError(fmt.Errorf("stdin.Close: %v", err))
+	}
+}
diff --git a/examples/tunnel/tunneld/main.go b/examples/tunnel/tunneld/main.go
new file mode 100644
index 0000000..5291e67
--- /dev/null
+++ b/examples/tunnel/tunneld/main.go
@@ -0,0 +1,58 @@
+// 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.
+
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go . -help
+
+package main
+
+import (
+	"fmt"
+
+	"v.io/x/lib/cmdline"
+
+	"v.io/v23/context"
+
+	"v.io/x/ref/examples/tunnel"
+	"v.io/x/ref/lib/security/securityflag"
+	"v.io/x/ref/lib/signals"
+	"v.io/x/ref/lib/v23cmd"
+	"v.io/x/ref/lib/xrpc"
+
+	_ "v.io/x/ref/runtime/factories/roaming"
+)
+
+var name string
+
+func main() {
+	cmdRoot.Flags.StringVar(&name, "name", "", "Name to publish the server as.")
+	cmdline.HideGlobalFlagsExcept()
+	cmdline.Main(cmdRoot)
+}
+
+var cmdRoot = &cmdline.Command{
+	Runner: v23cmd.RunnerFunc(runTunnelD),
+	Name:   "tunneld",
+	Short:  "Runs the tunneld daemon",
+	Long: `
+Command tunneld runs the tunneld daemon, which implements the Tunnel interface.
+`,
+}
+
+func runTunnelD(ctx *context.T, env *cmdline.Env, args []string) error {
+	auth := securityflag.NewAuthorizerOrDie()
+	server, err := xrpc.NewServer(ctx, name, tunnel.TunnelServer(&T{}), auth)
+	if err != nil {
+		return fmt.Errorf("NewServer failed: %v", err)
+	}
+	status := server.Status()
+	ctx.Infof("Listening on: %v", status.Endpoints)
+	if len(status.Endpoints) > 0 {
+		fmt.Printf("NAME=%s\n", status.Endpoints[0].Name())
+	}
+	ctx.Infof("Published as %q", name)
+
+	<-signals.ShutdownOnSignals(ctx)
+	return nil
+}
diff --git a/examples/tunnel/tunneld/tunneld_v23_test.go b/examples/tunnel/tunneld/tunneld_v23_test.go
new file mode 100644
index 0000000..195a871
--- /dev/null
+++ b/examples/tunnel/tunneld/tunneld_v23_test.go
@@ -0,0 +1,70 @@
+// 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_test
+
+//go:generate v23 test generate .
+
+import (
+	"bytes"
+	"io/ioutil"
+	"path/filepath"
+
+	"v.io/x/ref"
+	"v.io/x/ref/test/v23tests"
+)
+
+func V23TestTunneld(t *v23tests.T) {
+	v23tests.RunRootMT(t, "--v23.tcp.address=127.0.0.1:0")
+
+	tunneldBin := t.BuildV23Pkg("v.io/x/ref/examples/tunnel/tunneld")
+	vsh := t.BuildV23Pkg("v.io/x/ref/examples/tunnel/vsh")
+	mounttableBin := t.BuildV23Pkg("v.io/x/ref/cmd/mounttable")
+
+	// Start tunneld with a known endpoint.
+	tunnelEndpoint := tunneldBin.Start("--v23.tcp.address=127.0.0.1:0", "--name=tunnel/test").ExpectVar("NAME")
+
+	// Run remote command with the endpoint.
+	if want, got := "HELLO ENDPOINT\n", vsh.Start(tunnelEndpoint, "echo", "HELLO", "ENDPOINT").Output(); want != got {
+		t.Fatalf("unexpected output, got %s, want %s", got, want)
+	}
+
+	if want, got := "HELLO NAME\n", vsh.Start("tunnel/test", "echo", "HELLO", "NAME").Output(); want != got {
+		t.Fatalf("unexpected output, got %s, want %s", got, want)
+	}
+
+	// Send input to remote command.
+	want := "HELLO SERVER"
+	if got := vsh.WithStdin(bytes.NewBufferString(want)).Start(tunnelEndpoint, "cat").Output(); want != got {
+		t.Fatalf("unexpected output, got %s, want %s", got, want)
+	}
+
+	// And again with a file redirection this time.
+	outDir := t.NewTempDir("")
+	outPath := filepath.Join(outDir, "hello.txt")
+
+	// TODO(sjr): instead of using Output() here, we'd really rather do
+	// WaitOrDie(os.Stdout, os.Stderr). There is currently a race caused by
+	// WithStdin that makes this flaky.
+	vsh.WithStdin(bytes.NewBufferString(want)).Start(tunnelEndpoint, "cat > "+outPath).Output()
+	if got, err := ioutil.ReadFile(outPath); err != nil || string(got) != want {
+		if err != nil {
+			t.Fatalf("ReadFile(%v) failed: %v", outPath, err)
+		} else {
+			t.Fatalf("unexpected output, got %s, want %s", got, want)
+		}
+	}
+
+	// Verify that all published names are there.
+	root, _ := t.GetVar(ref.EnvNamespacePrefix)
+	inv := mounttableBin.Start("glob", root, "tunnel/test")
+
+	// Expect one entry: the tunnel name.
+	matches := inv.ExpectSetEventuallyRE("tunnel/test" + " (.*) \\(Deadline .*\\)")
+
+	// The full endpoint should be the one we saw originally.
+	if got, want := matches[0][1], tunnelEndpoint; "/"+got != want {
+		t.Fatalf("expected tunnel endpoint %s to be %s, but it was not", got, want)
+	}
+}
diff --git a/examples/tunnel/tunneld/v23_test.go b/examples/tunnel/tunneld/v23_test.go
new file mode 100644
index 0000000..99c37d9
--- /dev/null
+++ b/examples/tunnel/tunneld/v23_test.go
@@ -0,0 +1,30 @@
+// 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 main_test
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
+	"v.io/x/ref/test/v23tests"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	modules.DispatchAndExitIfChild()
+	cleanup := v23tests.UseSharedBinDir()
+	r := m.Run()
+	cleanup()
+	os.Exit(r)
+}
+
+func TestV23Tunneld(t *testing.T) {
+	v23tests.RunTest(t, V23TestTunneld)
+}
diff --git a/examples/tunnel/vsh/doc.go b/examples/tunnel/vsh/doc.go
new file mode 100644
index 0000000..3094470
--- /dev/null
+++ b/examples/tunnel/vsh/doc.go
@@ -0,0 +1,97 @@
+// 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
+
+/*
+Command vsh runs the Vanadium shell, a Tunnel client that can be used to run
+shell commands or start an interactive shell on a remote tunneld server.
+
+To open an interactive shell, use:
+  vsh <object name>
+
+To run a shell command, use:
+  vsh <object name> <command to run>
+
+The -L flag will forward connections from a local port to a remote address
+through the tunneld service. The flag value is localaddr,remoteaddr. E.g.
+  -L :14141,www.google.com:80
+
+vsh can't be used directly with tools like rsync because vanadium object names
+don't look like traditional hostnames, which rsync doesn't understand. For
+compatibility with such tools, vsh has a special feature that allows passing the
+vanadium object name via the VSH_NAME environment variable.
+
+  $ VSH_NAME=<object name> rsync -avh -e vsh /foo/* v23:/foo/
+
+In this example, the "v23" host will be substituted with $VSH_NAME by vsh and
+rsync will work as expected.
+
+Usage:
+   vsh [flags] <object name> [command]
+
+<object name> is the Vanadium object name to connect to.
+
+[command] is the shell command and args to run, for non-interactive vsh.
+
+The vsh flags are:
+ -L=
+   Forward local to remote, format is "localaddr,remoteaddr".
+ -N=false
+   Do not execute a shell.  Only do port forwarding.
+ -T=false
+   Disable pseudo-terminal allocation.
+ -local_protocol=tcp
+   Local network protocol for port forwarding.
+ -remote_protocol=tcp
+   Remote network protocol for port forwarding.
+ -t=false
+   Force allocation of pseudo-terminal.
+
+The global flags are:
+ -alsologtostderr=true
+   log to standard error as well as files
+ -log_backtrace_at=:0
+   when logging hits line file:N, emit a stack trace
+ -log_dir=
+   if non-empty, write log files to this directory
+ -logtostderr=false
+   log to standard error instead of files
+ -max_stack_buf_size=4292608
+   max size in bytes of the buffer to use for logging stack traces
+ -metadata=<just specify -metadata to activate>
+   Displays metadata for the program and exits.
+ -stderrthreshold=2
+   logs at or above this threshold go to stderr
+ -v=0
+   log level for V logs
+ -v23.credentials=
+   directory to use for storing security credentials
+ -v23.i18n-catalogue=
+   18n catalogue files to load, comma separated
+ -v23.namespace.root=[/(dev.v.io/role/vprod/service/mounttabled)@ns.dev.v.io:8101]
+   local namespace root; can be repeated to provided multiple roots
+ -v23.proxy=
+   object name of proxy service to use to export services across network
+   boundaries
+ -v23.tcp.address=
+   address to listen on
+ -v23.tcp.protocol=wsh
+   protocol to listen with
+ -v23.vtrace.cache-size=1024
+   The number of vtrace traces to store in memory.
+ -v23.vtrace.collect-regexp=
+   Spans and annotations that match this regular expression will trigger trace
+   collection.
+ -v23.vtrace.dump-on-shutdown=true
+   If true, dump all stored traces on runtime shutdown.
+ -v23.vtrace.sample-rate=0
+   Rate (from 0.0 to 1.0) to sample vtrace traces.
+ -vmodule=
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
+*/
+package main
diff --git a/examples/tunnel/vsh/iomanager.go b/examples/tunnel/vsh/iomanager.go
new file mode 100644
index 0000000..4682d94
--- /dev/null
+++ b/examples/tunnel/vsh/iomanager.go
@@ -0,0 +1,189 @@
+// 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 (
+	"fmt"
+	"io"
+	"os"
+	"os/signal"
+	"sync"
+	"syscall"
+
+	"v.io/x/ref/examples/tunnel"
+	"v.io/x/ref/examples/tunnel/internal"
+	"v.io/x/ref/internal/logger"
+)
+
+func runIOManager(stdin io.Reader, stdout, stderr io.Writer, stream tunnel.TunnelShellClientCall) error {
+	m := ioManager{stdin: stdin, stdout: stdout, stderr: stderr, stream: stream}
+	return m.run()
+}
+
+// ioManager manages the forwarding of all the data between the shell and the
+// stream.
+type ioManager struct {
+	stdin          io.Reader
+	stdout, stderr io.Writer
+	stream         tunnel.TunnelShellClientCall
+
+	// streamError receives errors coming from stream operations.
+	streamError chan error
+	// stdioError receives errors coming from stdio operations.
+	stdioError chan error
+}
+
+func (m *ioManager) run() error {
+	m.streamError = make(chan error, 1)
+	m.stdioError = make(chan error, 1)
+
+	var pendingUserInput sync.WaitGroup
+	pendingUserInput.Add(1)
+	var pendingStreamOutput sync.WaitGroup
+	pendingStreamOutput.Add(1)
+
+	// Forward data between the user and the remote shell.
+	go func() {
+		defer pendingUserInput.Done()
+		// outchan is used to serialize the output to the stream.
+		// chan2stream() receives data sent by handleWindowResize() and
+		// user2outchan() and sends it to the stream.
+		outchan := make(chan tunnel.ClientShellPacket)
+		var wgStream sync.WaitGroup
+		wgStream.Add(1)
+		go m.chan2stream(outchan, &wgStream)
+
+		// When the terminal window is resized, we receive a SIGWINCH. Then we
+		// send the new window size to the server.
+		winch := make(chan os.Signal, 1)
+		signal.Notify(winch, syscall.SIGWINCH)
+
+		var wgUser sync.WaitGroup
+		wgUser.Add(2)
+		go func() {
+			m.user2outchan(outchan, &wgUser)
+			signal.Stop(winch)
+			close(winch)
+		}()
+		go m.handleWindowResize(winch, outchan, &wgUser)
+		// When both user2outchan and handleWindowResize are done,
+		// close outchan to signal chan2stream to exit.
+		wgUser.Wait()
+		close(outchan)
+		wgStream.Wait()
+	}()
+	go m.stream2user(&pendingStreamOutput)
+	// Block until something reports an error.
+	select {
+	case err := <-m.streamError:
+		// When we receive an error from the stream, wait for any
+		// remaining stream output to be sent to the user before
+		// exiting.
+		logger.Global().VI(2).Infof("run stream error: %v", err)
+		pendingStreamOutput.Wait()
+		return err
+	case err := <-m.stdioError:
+		// When we receive an error from the user, wait for any
+		// remaining input from the user to be sent to the stream
+		// before exiting.
+		logger.Global().VI(2).Infof("run stdio error: %v", err)
+		pendingUserInput.Wait()
+		return err
+	}
+}
+
+func (m *ioManager) sendStreamError(err error) {
+	select {
+	case m.streamError <- err:
+	default:
+	}
+}
+
+func (m *ioManager) sendStdioError(err error) {
+	select {
+	case m.stdioError <- err:
+	default:
+	}
+}
+
+// chan2stream receives ClientShellPacket from outchan and sends it to stream.
+func (m *ioManager) chan2stream(outchan <-chan tunnel.ClientShellPacket, wg *sync.WaitGroup) {
+	defer wg.Done()
+	sender := m.stream.SendStream()
+	for packet := range outchan {
+		logger.Global().VI(3).Infof("chan2stream packet: %+v", packet)
+		if err := sender.Send(packet); err != nil {
+			logger.Global().VI(2).Infof("chan2stream: %v", err)
+			m.sendStreamError(err)
+		}
+	}
+	m.sendStreamError(io.EOF)
+}
+
+func (m *ioManager) handleWindowResize(winch <-chan os.Signal, outchan chan<- tunnel.ClientShellPacket, wg *sync.WaitGroup) {
+	defer wg.Done()
+	for _ = range winch {
+		ws, err := internal.GetWindowSize()
+		if err != nil {
+			logger.Global().Infof("GetWindowSize failed: %v", err)
+			continue
+		}
+		outchan <- tunnel.ClientShellPacketWinSize{tunnel.WindowSize{
+			Rows: ws.Row,
+			Cols: ws.Col,
+		}}
+	}
+}
+
+// user2stream reads input from stdin and sends it to the outchan.
+func (m *ioManager) user2outchan(outchan chan<- tunnel.ClientShellPacket, wg *sync.WaitGroup) {
+	defer wg.Done()
+	for {
+		buf := make([]byte, 2048)
+		n, err := m.stdin.Read(buf[:])
+		if err == io.EOF {
+			logger.Global().VI(2).Infof("user2outchan: EOF, closing stdin")
+			outchan <- tunnel.ClientShellPacketEndOfFile{}
+			return
+		}
+		if err != nil {
+			logger.Global().VI(2).Infof("user2outchan: %v", err)
+			m.sendStdioError(err)
+			return
+		}
+		outchan <- tunnel.ClientShellPacketStdin{buf[:n]}
+	}
+}
+
+// stream2user reads data from the stream and sends it to either stdout or stderr.
+func (m *ioManager) stream2user(wg *sync.WaitGroup) {
+	defer wg.Done()
+	rStream := m.stream.RecvStream()
+	for rStream.Advance() {
+		packet := rStream.Value()
+		logger.Global().VI(3).Infof("stream2user packet: %+v", packet)
+
+		switch v := packet.(type) {
+		case tunnel.ServerShellPacketStdout:
+			if n, err := m.stdout.Write(v.Value); n != len(v.Value) || err != nil {
+				m.sendStdioError(fmt.Errorf("stdout.Write returned (%d, %v) want (%d, nil)", n, err, len(v.Value)))
+				return
+			}
+		case tunnel.ServerShellPacketStderr:
+			if n, err := m.stderr.Write(v.Value); n != len(v.Value) || err != nil {
+				m.sendStdioError(fmt.Errorf("stderr.Write returned (%d, %v) want (%d, nil)", n, err, len(v.Value)))
+				return
+			}
+		default:
+			logger.Global().Infof("unexpected message type: %T", packet)
+		}
+	}
+	err := rStream.Err()
+	if err == nil {
+		err = io.EOF
+	}
+	logger.Global().VI(2).Infof("stream2user: %v", err)
+	m.sendStreamError(err)
+}
diff --git a/examples/tunnel/vsh/main.go b/examples/tunnel/vsh/main.go
new file mode 100644
index 0000000..55a0c44
--- /dev/null
+++ b/examples/tunnel/vsh/main.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.
+
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go . -help
+
+package main
+
+import (
+	"errors"
+	"fmt"
+	"net"
+	"strings"
+
+	"v.io/x/lib/cmdline"
+
+	"v.io/v23/context"
+
+	"v.io/x/ref/examples/tunnel"
+	"v.io/x/ref/examples/tunnel/internal"
+	"v.io/x/ref/internal/logger"
+	"v.io/x/ref/lib/signals"
+	"v.io/x/ref/lib/v23cmd"
+	_ "v.io/x/ref/runtime/factories/generic"
+)
+
+var (
+	disablePty, forcePty, noshell     bool
+	portforward, lprotocol, rprotocol string
+)
+
+func main() {
+	cmdVsh.Flags.BoolVar(&disablePty, "T", false, "Disable pseudo-terminal allocation.")
+	cmdVsh.Flags.BoolVar(&forcePty, "t", false, "Force allocation of pseudo-terminal.")
+	cmdVsh.Flags.BoolVar(&noshell, "N", false, "Do not execute a shell.  Only do port forwarding.")
+	cmdVsh.Flags.StringVar(&portforward, "L", "", `Forward local to remote, format is "localaddr,remoteaddr".`)
+	cmdVsh.Flags.StringVar(&lprotocol, "local_protocol", "tcp", "Local network protocol for port forwarding.")
+	cmdVsh.Flags.StringVar(&rprotocol, "remote_protocol", "tcp", "Remote network protocol for port forwarding.")
+	cmdline.HideGlobalFlagsExcept()
+	cmdline.Main(cmdVsh)
+}
+
+var cmdVsh = &cmdline.Command{
+	Runner: v23cmd.RunnerFunc(runVsh),
+	Name:   "vsh",
+	Short:  "Vanadium shell",
+	Long: `
+Command vsh runs the Vanadium shell, a Tunnel client that can be used to run
+shell commands or start an interactive shell on a remote tunneld server.
+
+To open an interactive shell, use:
+  vsh <object name>
+
+To run a shell command, use:
+  vsh <object name> <command to run>
+
+The -L flag will forward connections from a local port to a remote address
+through the tunneld service. The flag value is localaddr,remoteaddr. E.g.
+  -L :14141,www.google.com:80
+
+vsh can't be used directly with tools like rsync because vanadium object names
+don't look like traditional hostnames, which rsync doesn't understand. For
+compatibility with such tools, vsh has a special feature that allows passing the
+vanadium object name via the VSH_NAME environment variable.
+
+  $ VSH_NAME=<object name> rsync -avh -e vsh /foo/* v23:/foo/
+
+In this example, the "v23" host will be substituted with $VSH_NAME by vsh and
+rsync will work as expected.
+`,
+	ArgsName: "<object name> [command]",
+	ArgsLong: `
+<object name> is the Vanadium object name to connect to.
+
+[command] is the shell command and args to run, for non-interactive vsh.
+`,
+}
+
+func runVsh(ctx *context.T, env *cmdline.Env, args []string) error {
+	oname, cmd, err := objectNameAndCommandLine(env, args)
+	if err != nil {
+		return env.UsageErrorf("%v", err)
+	}
+
+	t := tunnel.TunnelClient(oname)
+
+	if len(portforward) > 0 {
+		go runPortForwarding(ctx, t, oname)
+	}
+
+	if noshell {
+		<-signals.ShutdownOnSignals(ctx)
+		return nil
+	}
+
+	opts := shellOptions(env, cmd)
+
+	stream, err := t.Shell(ctx, cmd, opts)
+	if err != nil {
+		return err
+	}
+	if opts.UsePty {
+		saved := internal.EnterRawTerminalMode()
+		defer internal.RestoreTerminalSettings(saved)
+	}
+	runIOManager(env.Stdin, env.Stdout, env.Stderr, stream)
+
+	exitMsg := fmt.Sprintf("Connection to %s closed.", oname)
+	exitStatus, err := stream.Finish()
+	if err != nil {
+		exitMsg += fmt.Sprintf(" (%v)", err)
+	}
+	ctx.VI(1).Info(exitMsg)
+	// Only show the exit message on stdout for interactive shells.
+	// Otherwise, the exit message might get confused with the output
+	// of the command that was run.
+	if err != nil {
+		fmt.Fprintln(env.Stderr, exitMsg)
+	} else if len(cmd) == 0 {
+		fmt.Println(exitMsg)
+	}
+	return cmdline.ErrExitCode(exitStatus)
+}
+
+func shellOptions(env *cmdline.Env, cmd string) (opts tunnel.ShellOpts) {
+	opts.UsePty = (len(cmd) == 0 || forcePty) && !disablePty
+	opts.Environment = environment(env.Vars)
+	ws, err := internal.GetWindowSize()
+	if err != nil {
+		logger.Global().VI(1).Infof("GetWindowSize failed: %v", err)
+	} else {
+		opts.WinSize.Rows = ws.Row
+		opts.WinSize.Cols = ws.Col
+	}
+	return
+}
+
+func environment(vars map[string]string) []string {
+	env := []string{}
+	for _, name := range []string{"TERM", "COLORTERM"} {
+		if value := vars[name]; value != "" {
+			env = append(env, name+"="+value)
+		}
+	}
+	return env
+}
+
+// objectNameAndCommandLine extracts the object name and the remote command to
+// send to the server. The object name is the first non-flag argument.
+// The command line is the concatenation of all non-flag arguments excluding
+// the object name.
+func objectNameAndCommandLine(env *cmdline.Env, args []string) (string, string, error) {
+	if len(args) < 1 {
+		return "", "", errors.New("object name missing")
+	}
+	name := args[0]
+	args = args[1:]
+	// For compatibility with tools like rsync. Because object names
+	// don't look like traditional hostnames, tools that work with rsh and
+	// ssh can't work directly with vsh. This trick makes the following
+	// possible:
+	//   $ VSH_NAME=<object name> rsync -avh -e vsh /foo/* v23:/foo/
+	// The "v23" host will be substituted with <object name>.
+	if envName := env.Vars["VSH_NAME"]; envName != "" && name == "v23" {
+		name = envName
+	}
+	cmd := strings.Join(args, " ")
+	return name, cmd, nil
+}
+
+func runPortForwarding(ctx *context.T, t tunnel.TunnelClientMethods, oname string) {
+	// portforward is localaddr,remoteaddr
+	parts := strings.Split(portforward, ",")
+	var laddr, raddr string
+	if len(parts) != 2 {
+		ctx.Fatalf("-L flag expects 2 values separated by a comma")
+	}
+	laddr = parts[0]
+	raddr = parts[1]
+
+	ln, err := net.Listen(lprotocol, laddr)
+	if err != nil {
+		ctx.Fatalf("net.Listen(%q, %q) failed: %v", lprotocol, laddr, err)
+	}
+	defer ln.Close()
+	ctx.VI(1).Infof("Listening on %q", ln.Addr())
+	for {
+		conn, err := ln.Accept()
+		if err != nil {
+			ctx.Infof("Accept failed: %v", err)
+			continue
+		}
+		stream, err := t.Forward(ctx, rprotocol, raddr)
+		if err != nil {
+			ctx.Infof("Tunnel(%q, %q) failed: %v", rprotocol, raddr, err)
+			conn.Close()
+			continue
+		}
+		name := fmt.Sprintf("%v-->%v-->(%v)-->%v", conn.RemoteAddr(), conn.LocalAddr(), oname, raddr)
+		go func() {
+			ctx.VI(1).Infof("TUNNEL START: %v", name)
+			errf := internal.Forward(conn, stream.SendStream(), stream.RecvStream())
+			err := stream.Finish()
+			ctx.VI(1).Infof("TUNNEL END  : %v (%v, %v)", name, errf, err)
+		}()
+	}
+}
diff --git a/internal/logger/logger.go b/internal/logger/logger.go
new file mode 100644
index 0000000..c8a84af
--- /dev/null
+++ b/internal/logger/logger.go
@@ -0,0 +1,80 @@
+// 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 logger provides access to an implementation of v23/logger.Logging
+// for use within a runtime implementation. There is pre-created "global" logger
+// and the ability to create new loggers.
+package logger
+
+import (
+	"v.io/x/lib/vlog"
+
+	"v.io/v23/context"
+	"v.io/v23/logging"
+)
+
+// Global returns the global logger.
+func Global() logging.Logger {
+	return vlog.Log
+}
+
+// NewLogger creates a new logger with the supplied name.
+func NewLogger(name string) logging.Logger {
+	return vlog.NewLogger(name)
+}
+
+// ManageLog defines the methods for managing and configuring a logger.
+type ManageLog interface {
+
+	// LogDir returns the directory where the log files are written.
+	LogDir() string
+
+	// Stats returns stats on how many lines/bytes haven been written to
+	// this set of logs.
+	Stats() (Info, Error struct{ Lines, Bytes int64 })
+
+	// ConfigureLoggerFromFlags will configure the supplied logger using
+	// command line flags.
+	ConfigureFromFlags() error
+
+	// ExplicitlySetFlags returns a map of the logging command line flags and their
+	// values formatted as strings.  Only the flags that were explicitly set are
+	// returned. This is intended for use when an application needs to know what
+	// value the flags were set to, for example when creating subprocesses.
+	ExplicitlySetFlags() map[string]string
+}
+
+type dummy struct{}
+
+func (*dummy) LogDir() string { return "" }
+func (*dummy) Stats() (Info, Error struct{ Lines, Bytes int64 }) {
+	return struct{ Lines, Bytes int64 }{0, 0}, struct{ Lines, Bytes int64 }{0, 0}
+}
+func (*dummy) ConfigureFromFlags() error             { return nil }
+func (*dummy) ExplicitlySetFlags() map[string]string { return nil }
+
+// Manager determines if the supplied logging.Logger implements ManageLog and if so
+// returns an instance of it. If it doesn't implement ManageLog then Manager
+// will return a dummy implementation that is essentially a no-op. It is
+// always safe to use it as: logger.Manager(logger.Global()).LogDir() for example.
+func Manager(logger logging.Logger) ManageLog {
+	if vl, ok := logger.(ManageLog); ok {
+		return vl
+	}
+	// If the logger is a context.T then ask it for its implementation
+	// of logging.Logger and look for ManageLog being implemented by it,
+	// since context.T can never implement ManageLog itself.
+	if ctx, ok := logger.(*context.T); ok {
+		if l, ok := ctx.LoggerImplementation().(logging.Logger); ok {
+			return Manager(l)
+		}
+	}
+	return &dummy{}
+}
+
+// IsAlreadyConfiguredError returns true if the err parameter indicates
+// the the logger has already been configured.
+func IsAlreadyConfiguredError(err error) bool {
+	return err == vlog.ErrConfigured
+}
diff --git a/internal/logger/logger_test.go b/internal/logger/logger_test.go
new file mode 100644
index 0000000..617df60
--- /dev/null
+++ b/internal/logger/logger_test.go
@@ -0,0 +1,45 @@
+// 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 logger_test
+
+import (
+	"testing"
+
+	"v.io/x/lib/vlog"
+
+	"v.io/v23/context"
+	"v.io/v23/logging"
+
+	"v.io/x/ref/internal/logger"
+)
+
+func TestManager(t *testing.T) {
+	global := logger.Global()
+	if _, ok := global.(*vlog.Logger); !ok {
+		t.Fatalf("global logger is not a vlog.Logger")
+	}
+
+	manager := logger.Manager(logger.Global())
+	if _, ok := manager.(*vlog.Logger); !ok {
+		t.Fatalf("logger.Manager does not return a vlog.Logger")
+	}
+
+	// Make sure vlog.Log satisfies the logging interfaces
+	var _ logger.ManageLog = vlog.Log
+	var _ logging.Logger = vlog.Log
+
+	// Make sure context.T implements logging.T
+	ctx, _ := context.RootContext()
+	var _ logging.Logger = ctx
+
+	// Make sure that logger.Manager can extract the appropriate management
+	// interface from a context.
+	nl := vlog.NewLogger("test")
+	ctx = context.WithLogger(ctx, nl)
+	manager = logger.Manager(ctx)
+	if _, ok := manager.(*vlog.Logger); !ok {
+		t.Errorf("failed to extract correct manager type")
+	}
+}
diff --git a/internal/reflectutil/all_test.go b/internal/reflectutil/all_test.go
new file mode 100644
index 0000000..ad4c949
--- /dev/null
+++ b/internal/reflectutil/all_test.go
@@ -0,0 +1,367 @@
+// 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 reflectutil
+
+import (
+	"reflect"
+	"testing"
+	"unsafe"
+)
+
+type (
+	Struct struct {
+		A uint
+		B string
+	}
+
+	Recurse struct {
+		U uint
+		R *Recurse
+	}
+
+	RecurseA struct {
+		Ua uint
+		B  *RecurseB
+	}
+	RecurseB struct {
+		Ub uint
+		A  *RecurseA
+	}
+
+	abIntPtr struct {
+		A, B *int
+	}
+)
+
+var (
+	recurseCycle   *Recurse  = &Recurse{}
+	recurseABCycle *RecurseA = &RecurseA{}
+
+	intPtr1a *int = new(int)
+	intPtr1b *int = new(int)
+
+	iface interface{}
+)
+
+func init() {
+	recurseCycle.U = 5
+	recurseCycle.R = recurseCycle
+
+	recurseABCycle.Ua = 5
+	recurseABCycle.B = &RecurseB{6, recurseABCycle}
+
+	*intPtr1a = 1
+	*intPtr1b = 1
+}
+
+func TestDeepEqual(t *testing.T) {
+	tests := []struct {
+		a, b   interface{}
+		expect bool
+	}{
+		{true, true, true},
+		{1, 1, true},
+		{-1, -1, true},
+		{1.1, 1.1, true},
+		{"abc", "abc", true},
+		{1 + 1i, 1 + 1i, true},
+		{[2]uint{1, 1}, [2]uint{1, 1}, true},
+		{[]uint{1, 1}, []uint{1, 1}, true},
+		{map[uint]string{1: "1", 2: "2"}, map[uint]string{1: "1", 2: "2"}, true},
+		{Struct{1, "a"}, Struct{1, "a"}, true},
+		{recurseCycle, recurseCycle, true},
+		{recurseABCycle, recurseABCycle, true},
+		{abIntPtr{intPtr1a, intPtr1a}, abIntPtr{intPtr1a, intPtr1a}, true},
+		{abIntPtr{intPtr1a, intPtr1b}, abIntPtr{intPtr1a, intPtr1b}, true},
+
+		{true, false, false},
+		{1, 2, false},
+		{-1, -2, false},
+		{1.1, 2.2, false},
+		{"abc", "def", false},
+		{1 + 1i, 2 + 2i, false},
+		{[2]uint{1, 1}, [2]uint{2, 2}, false},
+		{[]uint{1, 1}, []uint{2, 2}, false},
+		{map[uint]string{1: "1", 2: "2"}, map[uint]string{3: "3", 4: "4"}, false},
+		{Struct{1, "a"}, Struct{1, "b"}, false},
+		{recurseCycle, &Recurse{5, &Recurse{5, nil}}, false},
+		{recurseABCycle, &RecurseA{5, &RecurseB{6, nil}}, false},
+		{abIntPtr{intPtr1a, intPtr1a}, abIntPtr{intPtr1a, intPtr1b}, false},
+		{abIntPtr{intPtr1a, intPtr1b}, abIntPtr{intPtr1a, intPtr1a}, false},
+	}
+	for _, test := range tests {
+		actual := DeepEqual(test.a, test.b, &DeepEqualOpts{})
+		if actual != test.expect {
+			t.Errorf("DeepEqual(%#v, %#v) != %v", test.a, test.b, test.expect)
+		}
+	}
+}
+
+func TestAreComparable(t *testing.T) {
+	tests := []struct {
+		a, b   interface{}
+		expect bool
+	}{
+		{true, true, true},
+		{"", "", true},
+		{uint(0), uint(0), true},
+		{uint8(0), uint8(0), true},
+		{uint16(0), uint16(0), true},
+		{uint32(0), uint32(0), true},
+		{uint64(0), uint64(0), true},
+		{uintptr(0), uintptr(0), true},
+		{int(0), int(0), true},
+		{int8(0), int8(0), true},
+		{int16(0), int16(0), true},
+		{int32(0), int32(0), true},
+		{int64(0), int64(0), true},
+		{float32(0), float32(0), true},
+		{float64(0), float64(0), true},
+		{complex64(0), complex64(0), true},
+		{complex128(0), complex128(0), true},
+		{[2]uint{1, 1}, [2]uint{1, 1}, true},
+		{[]uint{1, 1}, []uint{1, 1}, true},
+		{Struct{1, "a"}, Struct{1, "a"}, true},
+		{(*int)(nil), (*int)(nil), true},
+		{recurseCycle, recurseCycle, true},
+		{recurseABCycle, recurseABCycle, true},
+		{abIntPtr{intPtr1a, intPtr1a}, abIntPtr{intPtr1a, intPtr1a}, true},
+		{abIntPtr{intPtr1a, intPtr1b}, abIntPtr{intPtr1a, intPtr1b}, true},
+
+		{map[uint]string{1: "1"}, map[uint]string{1: "1"}, false},
+		{&iface, &iface, false},
+		{make(chan int), make(chan int), false},
+		{TestAreComparable, TestAreComparable, false},
+		{unsafe.Pointer(nil), unsafe.Pointer(nil), false},
+	}
+	for _, test := range tests {
+		actual := AreComparable(test.a, test.b)
+		if actual != test.expect {
+			t.Errorf("AreComparable(%#v, %#v) != %v", test.a, test.b, test.expect)
+		}
+	}
+}
+
+func TestLess(t *testing.T) {
+	for _, test := range compareTests {
+		actual := Less(test.a, test.b)
+		expect := false
+		if test.expect == -1 {
+			expect = true // For eq and gt we expect Less to return false.
+		}
+		if actual != expect {
+			t.Errorf("Less(%#v, %#v) != %v", test.a, test.b, expect)
+		}
+	}
+}
+
+func TestCompare(t *testing.T) {
+	for _, test := range compareTests {
+		actual := Compare(test.a, test.b)
+		if actual != test.expect {
+			t.Errorf("Compare(%#v, %#v) != %v", test.a, test.b, test.expect)
+		}
+	}
+}
+
+var compareTests = []struct {
+	a, b   interface{}
+	expect int
+}{
+	{false, true, -1},
+	{false, false, 0},
+	{true, false, +1},
+	{true, true, 0},
+
+	{"", "aa", -1},
+	{"a", "aa", -1},
+	{"aa", "ab", -1},
+	{"aa", "b", -1},
+	{"", "", 0},
+	{"aa", "", +1},
+	{"aa", "a", +1},
+	{"ab", "aa", +1},
+	{"b", "aa", +1},
+
+	{uint(0), uint(1), -1},
+	{uint(0), uint(0), 0},
+	{uint(1), uint(0), +1},
+	{uint(1), uint(1), 0},
+
+	{int(-1), int(+1), -1},
+	{int(-1), int(-1), 0},
+	{int(+1), int(-1), +1},
+	{int(+1), int(+1), 0},
+
+	{float32(-1.1), float32(+1.1), -1},
+	{float32(-1.1), float32(-1.1), 0},
+	{float32(+1.1), float32(-1.1), +1},
+	{float32(+1.1), float32(+1.1), 0},
+
+	{complex64(1 + 1i), complex64(1 + 2i), -1},
+	{complex64(1 + 2i), complex64(2 + 1i), -1},
+	{complex64(1 + 2i), complex64(2 + 2i), -1},
+	{complex64(1 + 2i), complex64(2 + 3i), -1},
+	{complex64(1 + 1i), complex64(1 + 1i), 0},
+	{complex64(1 + 2i), complex64(1 + 1i), +1},
+	{complex64(2 + 1i), complex64(1 + 2i), +1},
+	{complex64(2 + 2i), complex64(1 + 2i), +1},
+	{complex64(2 + 3i), complex64(1 + 2i), +1},
+
+	{[2]int{1, 1}, [2]int{1, 2}, -1},
+	{[2]int{1, 2}, [2]int{2, 1}, -1},
+	{[2]int{1, 2}, [2]int{2, 2}, -1},
+	{[2]int{1, 2}, [2]int{2, 3}, -1},
+	{[2]int{1, 1}, [2]int{1, 1}, 0},
+	{[2]int{1, 2}, [2]int{1, 1}, +1},
+	{[2]int{2, 1}, [2]int{1, 2}, +1},
+	{[2]int{2, 2}, [2]int{1, 2}, +1},
+	{[2]int{2, 3}, [2]int{1, 2}, +1},
+
+	{[]int{}, []int{1, 1}, -1},
+	{[]int{1}, []int{1, 1}, -1},
+	{[]int{1, 1}, []int{}, +1},
+	{[]int{1, 1}, []int{1}, +1},
+	{[]int{1, 1}, []int{1, 2}, -1},
+	{[]int{1, 2}, []int{2, 1}, -1},
+	{[]int{1, 2}, []int{2, 2}, -1},
+	{[]int{1, 2}, []int{2, 3}, -1},
+	{[]int{1, 1}, []int{1, 1}, 0},
+	{[]int{1, 2}, []int{1, 1}, +1},
+	{[]int{2, 1}, []int{1, 2}, +1},
+	{[]int{2, 2}, []int{1, 2}, +1},
+	{[]int{2, 3}, []int{1, 2}, +1},
+
+	{Struct{1, "a"}, Struct{1, "b"}, -1},
+	{Struct{1, "b"}, Struct{2, "a"}, -1},
+	{Struct{1, "b"}, Struct{2, "b"}, -1},
+	{Struct{1, "b"}, Struct{2, "c"}, -1},
+	{Struct{1, "a"}, Struct{1, "a"}, 0},
+	{Struct{1, "b"}, Struct{1, "a"}, +1},
+	{Struct{2, "a"}, Struct{1, "b"}, +1},
+	{Struct{2, "b"}, Struct{1, "b"}, +1},
+	{Struct{2, "c"}, Struct{1, "b"}, +1},
+
+	{(*Struct)(nil), &Struct{1, "a"}, -1},
+	{&Struct{1, "a"}, &Struct{1, "b"}, -1},
+	{&Struct{1, "b"}, &Struct{2, "a"}, -1},
+	{&Struct{1, "b"}, &Struct{2, "b"}, -1},
+	{&Struct{1, "b"}, &Struct{2, "c"}, -1},
+	{(*Struct)(nil), (*Struct)(nil), 0},
+	{&Struct{1, "a"}, (*Struct)(nil), +1},
+	{&Struct{1, "a"}, &Struct{1, "a"}, 0},
+	{&Struct{1, "b"}, &Struct{1, "a"}, +1},
+	{&Struct{2, "a"}, &Struct{1, "b"}, +1},
+	{&Struct{2, "b"}, &Struct{1, "b"}, +1},
+	{&Struct{2, "c"}, &Struct{1, "b"}, +1},
+}
+
+type v []interface{}
+
+func toRVS(values v) (rvs []reflect.Value) {
+	for _, val := range values {
+		rvs = append(rvs, reflect.ValueOf(val))
+	}
+	return
+}
+
+func fromRVS(rvs []reflect.Value) (values v) {
+	for _, rv := range rvs {
+		values = append(values, rv.Interface())
+	}
+	return
+}
+
+func TestTrySortValues(t *testing.T) {
+	tests := []struct {
+		values v
+		expect v
+	}{
+		{
+			v{true, false},
+			v{false, true},
+		},
+		{
+			v{"c", "b", "a"},
+			v{"a", "b", "c"},
+		},
+		{
+			v{3, 1, 2},
+			v{1, 2, 3},
+		},
+		{
+			v{3.3, 1.1, 2.2},
+			v{1.1, 2.2, 3.3},
+		},
+		{
+			v{3 + 3i, 1 + 1i, 2 + 2i},
+			v{1 + 1i, 2 + 2i, 3 + 3i},
+		},
+		{
+			v{[1]int{3}, [1]int{1}, [1]int{2}},
+			v{[1]int{1}, [1]int{2}, [1]int{3}},
+		},
+		{
+			v{[]int{3}, []int{}, []int{2, 2}},
+			v{[]int{}, []int{2, 2}, []int{3}},
+		},
+		{
+			v{Struct{3, "c"}, Struct{1, "a"}, Struct{2, "b"}},
+			v{Struct{1, "a"}, Struct{2, "b"}, Struct{3, "c"}},
+		},
+		{
+			v{&Struct{3, "c"}, (*Struct)(nil), &Struct{2, "b"}},
+			v{(*Struct)(nil), &Struct{2, "b"}, &Struct{3, "c"}},
+		},
+	}
+	for _, test := range tests {
+		actual := fromRVS(TrySortValues(toRVS(test.values)))
+		if !reflect.DeepEqual(actual, test.expect) {
+			t.Errorf("TrySortValues(%v) got %v, want %v", test.values, actual, test.expect)
+		}
+	}
+}
+
+func TestOptionSliceEqNilEmpty(t *testing.T) {
+	tests := []struct {
+		first               interface{}
+		second              interface{}
+		resultWithoutOption bool
+		resultWithOption    bool
+	}{
+		{
+			[]int{}, []int{}, true, true,
+		},
+		{
+			[]int(nil), []int(nil), true, true,
+		},
+		{
+			[]int{}, []int(nil), false, true,
+		},
+		{
+			[]([]int){([]int)(nil)}, []([]int){[]int{}}, false, true,
+		},
+	}
+
+	for _, nilEqOpt := range []bool{true, false} {
+		for _, test := range tests {
+			options := &DeepEqualOpts{
+				SliceEqNilEmpty: nilEqOpt,
+			}
+
+			result := DeepEqual(test.first, test.second, options)
+
+			if nilEqOpt {
+				if result != test.resultWithOption {
+					t.Errorf("Unexpected result with SliceEqNilEmpty option: inputs %#v and %#v. Got %v, expected: %v", test.first, test.second, result, test.resultWithOption)
+				}
+			} else {
+				if result != test.resultWithoutOption {
+					t.Errorf("Unexpected result without SliceEqNilEmpty option: inputs %#v and %#v. Got %v, expected: %v", test.first, test.second, result, test.resultWithoutOption)
+				}
+			}
+		}
+	}
+}
diff --git a/internal/reflectutil/deepequal.go b/internal/reflectutil/deepequal.go
new file mode 100644
index 0000000..6ff5d30
--- /dev/null
+++ b/internal/reflectutil/deepequal.go
@@ -0,0 +1,156 @@
+// 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 reflectutil
+
+import (
+	"fmt"
+	"reflect"
+)
+
+// Equal is similar to reflect.DeepEqual, except that it also
+// considers the sharing structure for pointers.  When reflect.DeepEqual
+// encounters pointers it just compares the dereferenced values; we also keep
+// track of the pointers themselves and require that if a pointer appears
+// multiple places in a, it appears in the same places in b.
+func DeepEqual(a, b interface{}, options *DeepEqualOpts) bool {
+	return deepEqual(reflect.ValueOf(a), reflect.ValueOf(b), &orderInfo{}, options)
+}
+
+// TODO(bprosnitz) Implement the debuggable deep equal option.
+// TODO(bprosnitz) Add an option to turn pointer sharing on/off
+
+// DeepEqualOpts represents the options configuration for DeepEqual.
+type DeepEqualOpts struct {
+	SliceEqNilEmpty bool
+}
+
+// orderInfo tracks pointer ordering information.  As we encounter new pointers
+// in our a and b values we maintain their ordering information in the slices.
+// We use slices rather than maps for efficiency; we typically have a small
+// number of pointers and sequential lookup is fast.
+type orderInfo struct {
+	orderA, orderB []uintptr
+}
+
+func lookupPtr(items []uintptr, target uintptr) (int, bool) { // (index, seen)
+	for index, item := range items {
+		if item == target {
+			return index, true
+		}
+	}
+	return -1, false
+}
+
+// sharingEqual returns equal=true iff the sharing structure between a and b is
+// the same, and returns seen=true iff we've seen either a or b before.
+func (info *orderInfo) sharingEqual(a, b uintptr) (bool, bool) { // (equal, seen)
+	indexA, seenA := lookupPtr(info.orderA, a)
+	indexB, seenB := lookupPtr(info.orderB, b)
+	if seenA || seenB {
+		return seenA == seenB && indexA == indexB, seenA || seenB
+	}
+	// Neither type has been seen - add to our order slices and return.
+	info.orderA = append(info.orderA, a)
+	info.orderB = append(info.orderB, b)
+	return true, false
+}
+
+func deepEqual(a, b reflect.Value, info *orderInfo, options *DeepEqualOpts) bool {
+	// We only consider sharing via explicit pointers, and ignore sharing via
+	// slices, maps or pointers to internal data.
+	if !a.IsValid() || !b.IsValid() {
+		return a.IsValid() == b.IsValid()
+	}
+	if a.Type() != b.Type() {
+		return false
+	}
+	switch a.Kind() {
+	case reflect.Ptr:
+		if a.IsNil() || b.IsNil() {
+			return a.IsNil() == b.IsNil()
+		}
+		equal, seen := info.sharingEqual(a.Pointer(), b.Pointer())
+		if !equal {
+			return false // a and b are not equal if their sharing isn't equal.
+		}
+		if seen {
+			// Skip the deepEqual call if we've already seen the pointers and they're
+			// equal, otherwise we'll have an infinite loop for cyclic values.
+			return true
+		}
+		return deepEqual(a.Elem(), b.Elem(), info, options)
+	case reflect.Array:
+		if a.Len() != b.Len() {
+			return false
+		}
+		for ix := 0; ix < a.Len(); ix++ {
+			if !deepEqual(a.Index(ix), b.Index(ix), info, options) {
+				return false
+			}
+		}
+		return true
+	case reflect.Slice:
+		if !options.SliceEqNilEmpty {
+			if a.IsNil() || b.IsNil() {
+				return a.IsNil() == b.IsNil()
+			}
+		}
+		if a.Len() != b.Len() {
+			return false
+		}
+		for ix := 0; ix < a.Len(); ix++ {
+			if !deepEqual(a.Index(ix), b.Index(ix), info, options) {
+				return false
+			}
+		}
+		return true
+	case reflect.Map:
+		if a.IsNil() || b.IsNil() {
+			return a.IsNil() == b.IsNil()
+		}
+		if a.Len() != b.Len() {
+			return false
+		}
+		for _, key := range a.MapKeys() {
+			if !deepEqual(a.MapIndex(key), b.MapIndex(key), info, options) {
+				return false
+			}
+		}
+		return true
+	case reflect.Struct:
+		for fx := 0; fx < a.NumField(); fx++ {
+			if !deepEqual(a.Field(fx), b.Field(fx), info, options) {
+				return false
+			}
+		}
+		return true
+	case reflect.Interface:
+		if a.IsNil() || b.IsNil() {
+			return a.IsNil() == b.IsNil()
+		}
+		return deepEqual(a.Elem(), b.Elem(), info, options)
+
+		// Ideally we would add a default clause here that would just return
+		// a.Interface() == b.Interface(), but that panics if we're dealing with
+		// unexported fields.  Instead we check each primitive type.
+
+	case reflect.Bool:
+		return a.Bool() == b.Bool()
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		return a.Int() == b.Int()
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+		return a.Uint() == b.Uint()
+	case reflect.Float32, reflect.Float64:
+		return a.Float() == b.Float()
+	case reflect.Complex64, reflect.Complex128:
+		return a.Complex() == b.Complex()
+	case reflect.String:
+		return a.String() == b.String()
+	case reflect.UnsafePointer:
+		return a.Pointer() == b.Pointer()
+	default:
+		panic(fmt.Errorf("SharingDeepEqual unhandled kind %v type %q", a.Kind(), a.Type()))
+	}
+}
diff --git a/internal/reflectutil/doc.go b/internal/reflectutil/doc.go
new file mode 100644
index 0000000..bdd0e3d
--- /dev/null
+++ b/internal/reflectutil/doc.go
@@ -0,0 +1,6 @@
+// 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 reflectutil provides reflection-based utilities.
+package reflectutil
diff --git a/internal/reflectutil/sort.go b/internal/reflectutil/sort.go
new file mode 100644
index 0000000..3517bb2
--- /dev/null
+++ b/internal/reflectutil/sort.go
@@ -0,0 +1,315 @@
+// 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 reflectutil
+
+import (
+	"reflect"
+	"sort"
+)
+
+// AreComparable is a helper to call AreComparableTypes.
+func AreComparable(a, b interface{}) bool {
+	return AreComparableTypes(reflect.TypeOf(a), reflect.TypeOf(b))
+}
+
+// AreComparableTypes returns true iff a and b are comparable types: bools,
+// strings and numbers, and composites using arrays, slices, structs or
+// pointers.
+func AreComparableTypes(a, b reflect.Type) bool {
+	return areComparable(a, b, make(map[reflect.Type]bool))
+}
+
+func areComparable(a, b reflect.Type, seen map[reflect.Type]bool) bool {
+	if a.Kind() != b.Kind() {
+		if isUint(a) && isUint(b) || isInt(a) && isInt(b) || isFloat(a) && isFloat(b) || isComplex(a) && isComplex(b) {
+			return true // Special-case for comparable numbers.
+		}
+		return false // Different kinds are incomparable.
+	}
+
+	// Deal with cyclic types.
+	if seen[a] {
+		return true
+	}
+	seen[a] = true
+
+	switch a.Kind() {
+	case reflect.Bool, reflect.String,
+		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
+		reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
+		reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128:
+		return true
+	case reflect.Array, reflect.Slice, reflect.Ptr:
+		return areComparable(a.Elem(), b.Elem(), seen)
+	case reflect.Struct:
+		if a.NumField() != b.NumField() {
+			return false
+		}
+		for fx := 0; fx < a.NumField(); fx++ {
+			af := a.Field(fx)
+			bf := b.Field(fx)
+			if af.Name != bf.Name || af.PkgPath != bf.PkgPath {
+				return false
+			}
+			if !areComparable(af.Type, bf.Type, seen) {
+				return false
+			}
+		}
+		return true
+	default:
+		// Unhandled: Map, Interface, Chan, Func, UnsafePointer
+		return false
+	}
+}
+
+func isUint(rt reflect.Type) bool {
+	switch rt.Kind() {
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+		return true
+	}
+	return false
+}
+
+func isInt(rt reflect.Type) bool {
+	switch rt.Kind() {
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		return true
+	}
+	return false
+}
+
+func isFloat(rt reflect.Type) bool {
+	switch rt.Kind() {
+	case reflect.Float32, reflect.Float64:
+		return true
+	}
+	return false
+}
+
+func isComplex(rt reflect.Type) bool {
+	switch rt.Kind() {
+	case reflect.Complex64, reflect.Complex128:
+		return true
+	}
+	return false
+}
+
+// Less is a helper to call LessValues.
+func Less(a, b interface{}) bool {
+	return LessValues(reflect.ValueOf(a), reflect.ValueOf(b))
+}
+
+// LessValues returns true iff a and b are comparable and a < b.  If a and b are
+// incomparable an arbitrary value is returned.  Cyclic values are not handled;
+// if a and b are cyclic and equal, this will infinite loop.  Arrays, slices and
+// structs use lexicographic ordering, and complex numbers compare real before
+// imaginary.
+func LessValues(a, b reflect.Value) bool {
+	if a.Kind() != b.Kind() {
+		return false // Different kinds are incomparable.
+	}
+	switch a.Kind() {
+	case reflect.Bool:
+		return lessBool(a.Bool(), b.Bool())
+	case reflect.String:
+		return a.String() < b.String()
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+		return a.Uint() < b.Uint()
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		return a.Int() < b.Int()
+	case reflect.Float32, reflect.Float64:
+		return a.Float() < b.Float()
+	case reflect.Complex64, reflect.Complex128:
+		return lessComplex(a.Complex(), b.Complex())
+	case reflect.Array:
+		return compareArray(a, b) == -1
+	case reflect.Slice:
+		return compareSlice(a, b) == -1
+	case reflect.Struct:
+		return compareStruct(a, b) == -1
+	case reflect.Ptr:
+		if a.IsNil() || b.IsNil() {
+			return a.IsNil() && !b.IsNil() // nil is less than non-nil.
+		}
+		return LessValues(a.Elem(), b.Elem())
+	default:
+		return false
+	}
+}
+
+func lessBool(a, b bool) bool {
+	return !a && b // false < true
+}
+
+func lessComplex(a, b complex128) bool {
+	// Compare lexicographically, real part before imaginary part.
+	if real(a) == real(b) {
+		return imag(a) < imag(b)
+	}
+	return real(a) < real(b)
+}
+
+// Compare is a helper to call CompareValues.
+func Compare(a, b interface{}) int {
+	return CompareValues(reflect.ValueOf(a), reflect.ValueOf(b))
+}
+
+// CompareValues returns an integer comparing two values.  If a and b are
+// comparable, the result is 0 if a == b, -1 if a < b and +1 if a > b.  If a and
+// b are incomparable an arbitrary value is returned.  Arrays, slices and
+// structs use lexicographic ordering, and complex numbers compare real before
+// imaginary.
+func CompareValues(a, b reflect.Value) int {
+	if a.Kind() != b.Kind() {
+		return 0 // Different kinds are incomparable.
+	}
+	switch a.Kind() {
+	case reflect.Array:
+		return compareArray(a, b)
+	case reflect.Slice:
+		return compareSlice(a, b)
+	case reflect.Struct:
+		return compareStruct(a, b)
+	case reflect.Ptr:
+		if a.IsNil() || b.IsNil() {
+			if a.IsNil() && !b.IsNil() {
+				return -1
+			}
+			if !a.IsNil() && b.IsNil() {
+				return +1
+			}
+			return 0
+		}
+		return CompareValues(a.Elem(), b.Elem())
+	}
+	if LessValues(a, b) {
+		return -1 // a < b
+	}
+	if LessValues(b, a) {
+		return +1 // a > b
+	}
+	return 0 // a == b, or incomparable.
+}
+
+func compareArray(a, b reflect.Value) int {
+	// Return lexicographic ordering of the array elements.
+	for ix := 0; ix < a.Len(); ix++ {
+		if c := CompareValues(a.Index(ix), b.Index(ix)); c != 0 {
+			return c
+		}
+	}
+	return 0
+}
+
+func compareSlice(a, b reflect.Value) int {
+	// Return lexicographic ordering of the slice elements.
+	for ix := 0; ix < a.Len() && ix < b.Len(); ix++ {
+		if c := CompareValues(a.Index(ix), b.Index(ix)); c != 0 {
+			return c
+		}
+	}
+	// Equal prefixes, shorter comes before longer.
+	if a.Len() < b.Len() {
+		return -1
+	}
+	if a.Len() > b.Len() {
+		return +1
+	}
+	return 0
+}
+
+func compareStruct(a, b reflect.Value) int {
+	// Return lexicographic ordering of the struct fields.
+	for ix := 0; ix < a.NumField(); ix++ {
+		if c := CompareValues(a.Field(ix), b.Field(ix)); c != 0 {
+			return c
+		}
+	}
+	return 0
+}
+
+// TrySortValues sorts a slice of reflect.Value if the value kind is supported.
+// Supported kinds are bools, strings and numbers, and composites using arrays,
+// slices, structs or pointers.  Arrays, slices and structs use lexicographic
+// ordering, and complex numbers compare real before imaginary.  If the values
+// in the slice aren't comparable or supported, the resulting ordering is
+// arbitrary.
+func TrySortValues(v []reflect.Value) []reflect.Value {
+	if len(v) <= 1 {
+		return v
+	}
+	switch v[0].Kind() {
+	case reflect.Bool:
+		sort.Sort(rvBools{v})
+	case reflect.String:
+		sort.Sort(rvStrings{v})
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+		sort.Sort(rvUints{v})
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		sort.Sort(rvInts{v})
+	case reflect.Float32, reflect.Float64:
+		sort.Sort(rvFloats{v})
+	case reflect.Complex64, reflect.Complex128:
+		sort.Sort(rvComplexes{v})
+	case reflect.Array:
+		sort.Sort(rvArrays{v})
+	case reflect.Slice:
+		sort.Sort(rvSlices{v})
+	case reflect.Struct:
+		sort.Sort(rvStructs{v})
+	case reflect.Ptr:
+		sort.Sort(rvPtrs{v})
+	}
+	return v
+}
+
+// Sorting helpers, heavily inspired by similar code in text/template.
+
+type rvs []reflect.Value
+
+func (x rvs) Len() int      { return len(x) }
+func (x rvs) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
+
+type rvBools struct{ rvs }
+type rvStrings struct{ rvs }
+type rvUints struct{ rvs }
+type rvInts struct{ rvs }
+type rvFloats struct{ rvs }
+type rvComplexes struct{ rvs }
+type rvArrays struct{ rvs }
+type rvSlices struct{ rvs }
+type rvStructs struct{ rvs }
+type rvPtrs struct{ rvs }
+
+func (x rvBools) Less(i, j int) bool {
+	return lessBool(x.rvs[i].Bool(), x.rvs[j].Bool())
+}
+func (x rvStrings) Less(i, j int) bool {
+	return x.rvs[i].String() < x.rvs[j].String()
+}
+func (x rvUints) Less(i, j int) bool {
+	return x.rvs[i].Uint() < x.rvs[j].Uint()
+}
+func (x rvInts) Less(i, j int) bool {
+	return x.rvs[i].Int() < x.rvs[j].Int()
+}
+func (x rvFloats) Less(i, j int) bool {
+	return x.rvs[i].Float() < x.rvs[j].Float()
+}
+func (x rvComplexes) Less(i, j int) bool {
+	return lessComplex(x.rvs[i].Complex(), x.rvs[j].Complex())
+}
+func (x rvArrays) Less(i, j int) bool {
+	return compareArray(x.rvs[i], x.rvs[j]) == -1
+}
+func (x rvSlices) Less(i, j int) bool {
+	return compareSlice(x.rvs[i], x.rvs[j]) == -1
+}
+func (x rvStructs) Less(i, j int) bool {
+	return compareStruct(x.rvs[i], x.rvs[j]) == -1
+}
+func (x rvPtrs) Less(i, j int) bool {
+	return LessValues(x.rvs[i], x.rvs[j])
+}
diff --git a/lib/apilog/apilog.go b/lib/apilog/apilog.go
new file mode 100644
index 0000000..a5d3d91
--- /dev/null
+++ b/lib/apilog/apilog.go
@@ -0,0 +1,178 @@
+// 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 apilog provides functions to be used in conjunction with logcop.
+// In particular, logcop will inject calls to these functions as the first
+// statement in methods that implement the v23 API. The output can
+// be controlled by vlog verbosity or vtrace.
+// --vmodule=apilog=<level> can be used to globally control logging,
+// and --vmodule=module=<level> can also be used to control logging
+// on a per-file basis.
+package apilog
+
+import (
+	"fmt"
+	"path"
+	"reflect"
+	"runtime"
+	"sync/atomic"
+
+	"v.io/v23/context"
+	"v.io/v23/logging"
+
+	"v.io/x/ref/internal/logger"
+)
+
+// logCallLogLevel is the log level beyond which calls are logged.
+const logCallLogLevel = 1
+
+func callerLocation() string {
+	var funcName string
+	const stackSkip = 1
+	pc, _, _, ok := runtime.Caller(stackSkip + 1)
+	if ok {
+		function := runtime.FuncForPC(pc)
+		if function != nil {
+			funcName = path.Base(function.Name())
+		}
+	}
+	return funcName
+}
+
+func getLogger(ctx *context.T) logging.Logger {
+	if ctx == nil {
+		return logger.Global()
+	}
+	return ctx
+}
+
+// LogCall logs that its caller has been called given the arguments
+// passed to it. It returns a function that is supposed to be called
+// when the caller returns, logging the caller’s return along with the
+// arguments it is provided with.
+// File name and line number of the call site and a randomly generated
+// invocation identifier is logged automatically.  The path through which
+// the caller function returns will be logged automatically too.
+//
+// The canonical way to use LogCall is along the lines of the following:
+//
+//     func Function(ctx *context.T, a Type1, b Type2) ReturnType {
+//         defer apilog.LogCall(ctx, a, b)(ctx)
+//         // ... function body ...
+//         return retVal
+//     }
+//
+// To log the return value as the function returns, the following
+// pattern should be used. Note that pointers to the output
+// variables should be passed to the returning function, not the
+// variables themselves. Also note that nil can be used when a context.T
+// is not available:
+//
+//     func Function(a Type1, b Type2) (r ReturnType) {
+//         defer apilog.LogCall(nil, a, b)(nil, &r)
+//         // ... function body ...
+//         return computeReturnValue()
+//     }
+//
+// Note that when using this pattern, you do not need to actually
+// assign anything to the named return variable explicitly.  A regular
+// return statement would automatically do the proper return variable
+// assignments.
+//
+// The log injector tool will automatically insert a LogCall invocation
+// into all implementations of the public API it runs, unless a Valid
+// Log Construct is found.  A Valid Log Construct is defined as one of
+// the following at the beginning of the function body (i.e. should not
+// be preceded by any non-whitespace or non-comment tokens):
+//     1. defer apilog.LogCall(optional arguments)(optional pointers to return values)
+//     2. defer apilog.LogCallf(argsFormat, optional arguments)(returnValuesFormat, optional pointers to return values)
+//     3. // nologcall
+//
+// The comment "// nologcall" serves as a hint to log injection and
+// checking tools to exclude the function from their consideration.
+// It is used as follows:
+//
+//     func FunctionWithoutLogging(args ...interface{}) {
+//         // nologcall
+//         // ... function body ...
+//     }
+//
+func LogCall(ctx *context.T, v ...interface{}) func(*context.T, ...interface{}) {
+	l := getLogger(ctx)
+	if !l.V(logCallLogLevel) { // TODO(mattr): add call to vtrace.
+		return func(*context.T, ...interface{}) {}
+	}
+	callerLocation := callerLocation()
+	invocationId := newInvocationIdentifier()
+	var output string
+	if len(v) > 0 {
+		output = fmt.Sprintf("call[%s %s]: args:%v", callerLocation, invocationId, v)
+	} else {
+		output = fmt.Sprintf("call[%s %s]", callerLocation, invocationId)
+	}
+	l.InfoDepth(1, output)
+
+	// TODO(mattr): annotate vtrace span.
+	return func(ctx *context.T, v ...interface{}) {
+		var output string
+		if len(v) > 0 {
+			output = fmt.Sprintf("return[%s %s]: %v", callerLocation, invocationId, derefSlice(v))
+		} else {
+			output = fmt.Sprintf("return[%s %s]", callerLocation, invocationId)
+		}
+		getLogger(ctx).InfoDepth(1, output)
+		// TODO(mattr): annotate vtrace span.
+	}
+}
+
+// LogCallf behaves identically to LogCall, except it lets the caller to
+// customize the log messages via format specifiers, like the following:
+//
+//     func Function(a Type1, b Type2) (r, t ReturnType) {
+//         defer apilog.LogCallf(nil, "a: %v, b: %v", a, b)(nil, "(r,t)=(%v,%v)", &r, &t)
+//         // ... function body ...
+//         return finalR, finalT
+//     }
+//
+func LogCallf(ctx *context.T, format string, v ...interface{}) func(*context.T, string, ...interface{}) {
+	l := getLogger(ctx)
+	if !l.V(logCallLogLevel) { // TODO(mattr): add call to vtrace.
+		return func(*context.T, string, ...interface{}) {}
+	}
+	callerLocation := callerLocation()
+	invocationId := newInvocationIdentifier()
+	output := fmt.Sprintf("call[%s %s]: %s", callerLocation, invocationId, fmt.Sprintf(format, v...))
+	l.InfoDepth(1, output)
+	// TODO(mattr): annotate vtrace span.
+	return func(ctx *context.T, format string, v ...interface{}) {
+		output := fmt.Sprintf("return[%s %s]: %v", callerLocation, invocationId, fmt.Sprintf(format, derefSlice(v)...))
+		getLogger(ctx).InfoDepth(1, output)
+		// TODO(mattr): annotate vtrace span.
+	}
+}
+
+func derefSlice(slice []interface{}) []interface{} {
+	o := make([]interface{}, 0, len(slice))
+	for _, x := range slice {
+		o = append(o, reflect.Indirect(reflect.ValueOf(x)).Interface())
+	}
+	return o
+}
+
+var invocationCounter uint64 = 0
+
+// newInvocationIdentifier generates a unique identifier for a method invocation
+// to make it easier to match up log lines for the entry and exit of a function
+// when looking at a log transcript.
+func newInvocationIdentifier() string {
+	const (
+		charSet    = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz"
+		charSetLen = uint64(len(charSet))
+	)
+	r := []byte{'@'}
+	for n := atomic.AddUint64(&invocationCounter, 1); n > 0; n /= charSetLen {
+		r = append(r, charSet[n%charSetLen])
+	}
+	return string(r)
+}
diff --git a/lib/apilog/apilog_test.go b/lib/apilog/apilog_test.go
new file mode 100644
index 0000000..e440cb1
--- /dev/null
+++ b/lib/apilog/apilog_test.go
@@ -0,0 +1,110 @@
+// 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 apilog_test
+
+import (
+	"bufio"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"regexp"
+	"testing"
+
+	"v.io/x/lib/vlog"
+
+	"v.io/v23/context"
+
+	"v.io/x/ref/lib/apilog"
+)
+
+func readLogFiles(dir string) ([]string, error) {
+	files, err := ioutil.ReadDir(dir)
+	if err != nil {
+		return nil, err
+	}
+	var contents []string
+	for _, fi := range files {
+		// Skip symlinks to avoid double-counting log lines.
+		if !fi.Mode().IsRegular() {
+			continue
+		}
+		file, err := os.Open(filepath.Join(dir, fi.Name()))
+		if err != nil {
+			return nil, err
+		}
+		scanner := bufio.NewScanner(file)
+		for scanner.Scan() {
+			if line := scanner.Text(); len(line) > 0 && line[0] == 'I' {
+				contents = append(contents, line)
+			}
+		}
+	}
+	return contents, nil
+}
+
+func myLoggedFunc(ctx *context.T) {
+	f := apilog.LogCall(ctx, "entry")
+	f(ctx, "exit")
+}
+
+func TestLogCall(t *testing.T) {
+	dir, err := ioutil.TempDir("", "logtest")
+	defer os.RemoveAll(dir)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	l := vlog.NewLogger("testHeader")
+	l.Configure(vlog.LogDir(dir), vlog.Level(2))
+	ctx, _ := context.RootContext()
+	ctx = context.WithLogger(ctx, l)
+	myLoggedFunc(ctx)
+	ctx.FlushLog()
+	testLogOutput(t, dir)
+}
+
+func TestLogCallNoContext(t *testing.T) {
+	dir, err := ioutil.TempDir("", "logtest")
+	defer os.RemoveAll(dir)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	l := vlog.NewLogger("testHeader")
+	l.Configure(vlog.LogDir(dir), vlog.Level(2))
+	saved := vlog.Log
+	vlog.Log = l
+	defer func() { vlog.Log = saved }()
+	myLoggedFunc(nil)
+	vlog.FlushLog()
+	testLogOutput(t, dir)
+}
+
+func testLogOutput(t *testing.T, dir string) {
+	contents, err := readLogFiles(dir)
+	if err != nil {
+		t.Fatalf("unexpected error: %q %s", dir, err)
+	}
+	if want, got := 2, len(contents); want != got {
+		t.Errorf("Expected %d info lines, got %d instead", want, got)
+	}
+	logCallLineRE := regexp.MustCompile(`\S+ \S+\s+\S+ ([^:]*):.*(call|return)\[(\S*)`)
+	for _, line := range contents {
+		match := logCallLineRE.FindStringSubmatch(line)
+		if len(match) != 4 {
+			t.Errorf("failed to match %s", line)
+			continue
+		}
+		fileName, callType, funcName := match[1], match[2], match[3]
+		if fileName != "apilog_test.go" {
+			t.Errorf("unexpected file name: %s", fileName)
+			continue
+		}
+		if callType != "call" && callType != "return" {
+			t.Errorf("unexpected call type: %s", callType)
+		}
+		if funcName != "apilog_test.myLoggedFunc" {
+			t.Errorf("unexpected func name: %s", funcName)
+		}
+	}
+}
diff --git a/lib/exec/child.go b/lib/exec/child.go
new file mode 100644
index 0000000..4cb59a0
--- /dev/null
+++ b/lib/exec/child.go
@@ -0,0 +1,169 @@
+// 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 exec
+
+import (
+	"encoding/binary"
+	"io"
+	"os"
+	"strconv"
+	"sync"
+	"unicode/utf8"
+
+	"v.io/v23/verror"
+)
+
+var (
+	ErrNoVersion          = verror.Register(pkgPath+".ErrNoVersion", verror.NoRetry, "{1:}{2:} "+ExecVersionVariable+" environment variable missing{:_}")
+	ErrUnsupportedVersion = verror.Register(pkgPath+".ErrUnsupportedVersion", verror.NoRetry, "{1:}{2:} Unsupported version of v.io/x/ref/lib/exec request by "+ExecVersionVariable+" environment variable{:_}")
+
+	errDifferentStatusSent = verror.Register(pkgPath+".errDifferentStatusSent", verror.NoRetry, "{1:}{2:} A different status: {3} has already been sent{:_}")
+	errPartialRead         = verror.Register(pkgPath+".PartialRead", verror.NoRetry, "{1:}{2:} partial read{:_}")
+)
+
+type ChildHandle struct {
+	// Config is passed down from the parent.
+	Config Config
+	// Secret is a secret passed to the child by its parent via a
+	// trusted channel.
+	Secret string
+	// statusPipe is a pipe that is used to notify the parent that the child
+	// process has started successfully. It is meant to be invoked by the
+	// vanadium framework to notify the parent that the child process has
+	// successfully started.
+	statusPipe *os.File
+
+	// statusMu protexts sentStatus and statusErr and prevents us from trying to
+	// send multiple status updates to the parent.
+	statusMu sync.Mutex
+	// sentStatus records the status that was already sent to the parent.
+	sentStatus string
+	// statusErr records the error we received, if any, when sentStatus
+	// was sent.
+	statusErr error
+}
+
+var (
+	childHandle    *ChildHandle
+	childHandleErr error
+	once           sync.Once
+)
+
+// FileOffset accounts for the file descriptors that are always passed
+// to the child by the parent: stderr, stdin, stdout, data read, and
+// status write. Any extra files added by the client will follow
+// fileOffset.
+const FileOffset = 5
+
+// GetChildHandle returns a ChildHandle that can be used to signal
+// that the child is 'ready' (by calling SetReady) to its parent or to
+// retrieve data securely passed to this process by its parent. For
+// instance, a secret intended to create a secure communication
+// channels and or authentication.
+//
+// If the child is relying on exec.Cmd.ExtraFiles then its first file
+// descriptor will not be 3, but will be offset by extra files added
+// by the framework. The developer should use the NewExtraFile method
+// to robustly get their extra files with the correct offset applied.
+func GetChildHandle() (*ChildHandle, error) {
+	once.Do(func() {
+		childHandle, childHandleErr = createChildHandle()
+	})
+	return childHandle, childHandleErr
+}
+
+func (c *ChildHandle) writeStatus(status, detail string) error {
+	c.statusMu.Lock()
+	defer c.statusMu.Unlock()
+
+	if c.sentStatus == "" {
+		c.sentStatus = status
+		status = status + detail
+		toWrite := make([]byte, 0, len(status))
+		var buf [utf8.UTFMax]byte
+		// This replaces any invalid utf-8 bytes in the status string with the
+		// Unicode replacement character.  This ensures that we only send valid
+		// utf-8 (followed by the eofChar).
+		for _, r := range status {
+			n := utf8.EncodeRune(buf[:], r)
+			toWrite = append(toWrite, buf[:n]...)
+		}
+		toWrite = append(toWrite, eofChar)
+		_, c.statusErr = c.statusPipe.Write(toWrite)
+		c.statusPipe.Close()
+	} else if c.sentStatus != status {
+		return verror.New(errDifferentStatusSent, nil, c.sentStatus)
+	}
+	return c.statusErr
+}
+
+// SetReady writes a 'ready' status to its parent.
+// Only one of SetReady or SetFailed can be called, attempting to send
+// both will fail.  In addition the status is only sent once to the parent
+// subsequent calls will return immediately with the same error that was
+// returned on the first call (possibly nil).
+func (c *ChildHandle) SetReady() error {
+	return c.writeStatus(readyStatus, strconv.Itoa(os.Getpid()))
+}
+
+// SetFailed writes a 'failed' status to its parent.
+func (c *ChildHandle) SetFailed(oerr error) error {
+	return c.writeStatus(failedStatus, oerr.Error())
+}
+
+// NewExtraFile creates a new file handle for the i-th file descriptor after
+// discounting stdout, stderr, stdin and the files reserved by the framework for
+// its own purposes.
+func (c *ChildHandle) NewExtraFile(i uintptr, name string) *os.File {
+	return os.NewFile(i+FileOffset, name)
+}
+
+func createChildHandle() (*ChildHandle, error) {
+	// TODO(cnicolaou): need to use major.minor.build format for
+	// version #s.
+	switch os.Getenv(ExecVersionVariable) {
+	case "":
+		return nil, verror.New(ErrNoVersion, nil)
+	case version1:
+		os.Setenv(ExecVersionVariable, "")
+	default:
+		return nil, verror.New(ErrUnsupportedVersion, nil)
+	}
+	dataPipe := os.NewFile(3, "data_rd")
+	serializedConfig, err := decodeString(dataPipe)
+	if err != nil {
+		return nil, err
+	}
+	cfg := NewConfig()
+	if err := cfg.MergeFrom(serializedConfig); err != nil {
+		return nil, err
+	}
+	secret, err := decodeString(dataPipe)
+	if err != nil {
+		return nil, err
+	}
+	childHandle = &ChildHandle{
+		Config:     cfg,
+		Secret:     secret,
+		statusPipe: os.NewFile(4, "status_wr"),
+	}
+	return childHandle, nil
+}
+
+func decodeString(r io.Reader) (string, error) {
+	var l int64 = 0
+	if err := binary.Read(r, binary.BigEndian, &l); err != nil {
+		return "", err
+	}
+	var data []byte = make([]byte, l)
+	if n, err := r.Read(data); err != nil || int64(n) != l {
+		if err != nil {
+			return "", err
+		} else {
+			return "", verror.New(errPartialRead, nil)
+		}
+	}
+	return string(data), nil
+}
diff --git a/lib/exec/config.go b/lib/exec/config.go
new file mode 100644
index 0000000..601ad35
--- /dev/null
+++ b/lib/exec/config.go
@@ -0,0 +1,110 @@
+// 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 exec
+
+import (
+	"sync"
+
+	"v.io/v23/verror"
+	"v.io/v23/vom"
+)
+
+// Config defines a simple key-value configuration.  Keys and values are
+// strings, and a key can have exactly one value.  The client is responsible for
+// encoding structured values, or multiple values, in the provided string.
+//
+// Config data can come from several sources:
+// - passed from parent process to child process through pipe;
+// - using environment variables or flags;
+// - via the neighborhood-based config service;
+// - by RPCs using the Config idl;
+// - manually, by calling the Set method.
+//
+// This interface makes no assumptions about the source of the configuration,
+// but provides a unified API for accessing it.
+type Config interface {
+	// Set sets the value for the key.  If the key already exists in the
+	// config, its value is overwritten.
+	Set(key, value string)
+	// Get returns the value for the key. If the key doesn't exist
+	// in the config, Get returns an error.
+	Get(key string) (string, error)
+	// Clear removes the specified key from the config.
+	Clear(key string)
+	// Serialize serializes the config to a string.
+	Serialize() (string, error)
+	// MergeFrom deserializes config information from a string created using
+	// Serialize(), and merges this information into the config, updating
+	// values for keys that already exist and creating new key-value pairs
+	// for keys that don't.
+	MergeFrom(string) error
+	// Dump returns the config information as a map from ket to value.
+	Dump() map[string]string
+}
+
+type cfg struct {
+	sync.RWMutex
+	m map[string]string
+}
+
+// New creates a new empty config.
+func NewConfig() Config {
+	return &cfg{m: make(map[string]string)}
+}
+
+func (c *cfg) Set(key, value string) {
+	c.Lock()
+	defer c.Unlock()
+	c.m[key] = value
+}
+
+func (c *cfg) Get(key string) (string, error) {
+	c.RLock()
+	defer c.RUnlock()
+	v, ok := c.m[key]
+	if !ok {
+		return "", verror.New(verror.ErrNoExist, nil, "config.Get", key)
+	}
+	return v, nil
+}
+
+func (c *cfg) Dump() (res map[string]string) {
+	res = make(map[string]string)
+	c.RLock()
+	defer c.RUnlock()
+	for k, v := range c.m {
+		res[k] = v
+	}
+	return
+}
+
+func (c *cfg) Clear(key string) {
+	c.Lock()
+	defer c.Unlock()
+	delete(c.m, key)
+}
+
+func (c *cfg) Serialize() (string, error) {
+	c.RLock()
+	data, err := vom.Encode(c.m)
+	c.RUnlock()
+	if err != nil {
+		return "", err
+	}
+	return string(data), nil
+}
+
+func (c *cfg) MergeFrom(serialized string) error {
+	var newM map[string]string
+	if err := vom.Decode([]byte(serialized), &newM); err != nil {
+		return err
+	}
+	c.Lock()
+	for k, v := range newM {
+		c.m[k] = v
+	}
+	c.Unlock()
+	return nil
+}
diff --git a/lib/exec/config_test.go b/lib/exec/config_test.go
new file mode 100644
index 0000000..6bad180
--- /dev/null
+++ b/lib/exec/config_test.go
@@ -0,0 +1,80 @@
+// 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 exec
+
+import (
+	"reflect"
+	"testing"
+
+	"v.io/v23/verror"
+)
+
+func checkPresent(t *testing.T, c Config, k, wantV string) {
+	if v, err := c.Get(k); err != nil {
+		t.Errorf("Expected value %q for key %q, got error %v instead", wantV, k, err)
+	} else if v != wantV {
+		t.Errorf("Expected value %q for key %q, got %q instead", wantV, k, v)
+	}
+}
+
+func checkAbsent(t *testing.T, c Config, k string) {
+	if v, err := c.Get(k); verror.ErrorID(err) != verror.ErrNoExist.ID {
+		t.Errorf("Expected (\"\", %v) for key %q, got (%q, %v) instead", verror.ErrNoExist, k, v, err)
+	}
+}
+
+// TestConfig checks that Set and Get work as expected.
+func TestConfig(t *testing.T) {
+	c := NewConfig()
+	c.Set("foo", "bar")
+	checkPresent(t, c, "foo", "bar")
+	checkAbsent(t, c, "food")
+	c.Set("foo", "baz")
+	checkPresent(t, c, "foo", "baz")
+	c.Clear("foo")
+	checkAbsent(t, c, "foo")
+	if want, got := map[string]string{}, c.Dump(); !reflect.DeepEqual(want, got) {
+		t.Errorf("Expected %v for Dump, got %v instead", want, got)
+	}
+}
+
+// TestSerialize checks that serializing the config and merging from a
+// serialized config work as expected.
+func TestSerialize(t *testing.T) {
+	c := NewConfig()
+	c.Set("k1", "v1")
+	c.Set("k2", "v2")
+	s, err := c.Serialize()
+	if err != nil {
+		t.Fatalf("Failed to serialize: %v", err)
+	}
+	readC := NewConfig()
+	if err := readC.MergeFrom(s); err != nil {
+		t.Fatalf("Failed to deserialize: %v", err)
+	}
+	checkPresent(t, readC, "k1", "v1")
+	checkPresent(t, readC, "k2", "v2")
+
+	readC.Set("k2", "newv2") // This should be overwritten by the next merge.
+	checkPresent(t, readC, "k2", "newv2")
+	readC.Set("k3", "v3") // This should survive the next merge.
+
+	c.Set("k1", "newv1") // This should overwrite v1 in the next merge.
+	c.Set("k4", "v4")    // This should be added following the next merge.
+	s, err = c.Serialize()
+	if err != nil {
+		t.Fatalf("Failed to serialize: %v", err)
+	}
+	if err := readC.MergeFrom(s); err != nil {
+		t.Fatalf("Failed to deserialize: %v", err)
+	}
+	checkPresent(t, readC, "k1", "newv1")
+	checkPresent(t, readC, "k2", "v2")
+	checkPresent(t, readC, "k3", "v3")
+	checkPresent(t, readC, "k4", "v4")
+	if want, got := map[string]string{"k1": "newv1", "k2": "v2", "k3": "v3", "k4": "v4"}, readC.Dump(); !reflect.DeepEqual(want, got) {
+		t.Errorf("Expected %v for Dump, got %v instead", want, got)
+	}
+}
diff --git a/lib/exec/consts.go b/lib/exec/consts.go
new file mode 100644
index 0000000..54bb5ae
--- /dev/null
+++ b/lib/exec/consts.go
@@ -0,0 +1,32 @@
+// 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 exec
+
+// ExecVersionVariable is the name of the environment variable used by the exec
+// package to communicate the protocol version between the parent and child.  It
+// takes care to clear this variable from the child process' environment as soon
+// as it can, however, there may still be some situations where an application
+// may need to test for its presence or ensure that it doesn't appear in a set
+// of environment variables; exposing the name of this variable is intended to
+// support such situations.
+const ExecVersionVariable = "V23_EXEC_VERSION"
+
+const (
+	version1     = "1.0.0"
+	readyStatus  = "ready::"
+	failedStatus = "failed::"
+	initStatus   = "init"
+
+	// eofChar is written onto the status pipe to signal end-of-file.  It
+	// should be the last byte written onto the pipe, before closing it.
+	// This signals to the reader that no more input is coming.  This is
+	// needed since we cannot use the closing of the write end of the pipe
+	// to send io.EOF to the reader: since there are two write ends (one in
+	// the parent and one in the child), closing any one of the two is not
+	// going to send io.EOF to the reader.
+	// Since the data coming from the child should be valid utf-8, we pick
+	// one of the invalid utf-8 bytes for this.
+	eofChar = 0xFF
+)
diff --git a/lib/exec/doc.go b/lib/exec/doc.go
new file mode 100644
index 0000000..b1b464f
--- /dev/null
+++ b/lib/exec/doc.go
@@ -0,0 +1,18 @@
+// 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 exec implements configuration and secret-sharing between parent and
+// child processes via anoymous pipes.  Anonymous pipes are used since they are
+// the most secure communication channel available.
+//
+// Once a parent starts a child process it can use WaitForReady to wait for the
+// child to reach its 'Ready' state.  Operations are provided to wait for the
+// child to terminate, and to terminate the child, cleaning up any state
+// associated with it.
+//
+// A child process uses the GetChildHandle function to complete the initial
+// authentication handshake.  The child must call SetReady to indicate that it
+// is fully initialized and ready for whatever purpose it is intended to
+// fulfill.  This handshake is referred as the 'exec protocol'.
+package exec
diff --git a/lib/exec/example_test.go b/lib/exec/example_test.go
new file mode 100644
index 0000000..b8157e8
--- /dev/null
+++ b/lib/exec/example_test.go
@@ -0,0 +1,42 @@
+// 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 exec
+
+import (
+	"log"
+	"os/exec"
+	"time"
+)
+
+func ExampleChildHandle() {
+	ch, _ := GetChildHandle()
+	// Initalize the app/service, access the secret shared with the
+	// child by its parent
+	_ = ch.Secret
+	ch.SetReady()
+	// Do work
+}
+
+func ExampleParentHandle() {
+	cmd := exec.Command("/bin/hostname")
+	ph := NewParentHandle(cmd, SecretOpt("secret"))
+
+	// Start the child process.
+	if err := ph.Start(); err != nil {
+		log.Printf("failed to start child: %s\n", err)
+		return
+	}
+
+	// Wait for the child to become ready.
+	if err := ph.WaitForReady(time.Second); err != nil {
+		log.Printf("failed to start child: %s\n", err)
+		return
+	}
+
+	// Wait for the child to exit giving it an hour to do it's work.
+	if err := ph.Wait(time.Hour); err != nil {
+		log.Printf("wait or child failed %s\n", err)
+	}
+}
diff --git a/lib/exec/exec_test.go b/lib/exec/exec_test.go
new file mode 100644
index 0000000..00e58ec
--- /dev/null
+++ b/lib/exec/exec_test.go
@@ -0,0 +1,657 @@
+// 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 exec_test
+
+import (
+	"fmt"
+	"io"
+	"log"
+	"os"
+	"os/exec"
+	"runtime"
+	"strings"
+	"sync"
+	"syscall"
+	"testing"
+	"time"
+	"unicode/utf8"
+
+	"v.io/v23/verror"
+	vexec "v.io/x/ref/lib/exec"
+	// Use mock timekeeper to avoid actually sleeping during the test.
+	"v.io/x/ref/test/timekeeper"
+)
+
+// We always expect there to be exactly three open file descriptors
+// when the test starts out: STDIN, STDOUT, and STDERR. This
+// assumption is tested in init below, and in the rare cases where it
+// is wrong, we try to close all additional file descriptors, and bail
+// out if that fails.
+const baselineOpenFiles = 3
+
+func init() {
+	if os.Getenv("GOMAXPROCS") == "" {
+		// Set the number of logical processors to 1 if GOMAXPROCS is
+		// not set in the environment.
+		//
+		// TODO(caprita): the default in Go 1.5 is num cpus, which
+		// causes flakiness.  Figure out why.
+		runtime.GOMAXPROCS(1)
+	}
+	if os.Getenv("GO_WANT_HELPER_PROCESS_EXEC") == "1" {
+		return
+	}
+	want, got := baselineOpenFiles, openFiles()
+	if want == got {
+		return
+	}
+	for i := want; i < got; i++ {
+		syscall.Close(i)
+	}
+	if want, got = baselineOpenFiles, openFiles(); want != got {
+		message := `Test expected to start with %d open files, found %d instead.
+This can happen if parent process has any open file descriptors,
+e.g. pipes, that are being inherited.`
+		panic(fmt.Errorf(message, want, got))
+	}
+}
+
+// These tests need to run a subprocess and we reuse this same test
+// binary to do so. A fake test 'TestHelperProcess' contains the code
+// we need to run in the child and we simply run this same binary with
+// a test.run= arg that runs just that test. This idea was taken from
+// the tests for os/exec.
+func helperCommand(s ...string) *exec.Cmd {
+	cs := []string{"-test.run=TestHelperProcess", "--"}
+	cs = append(cs, s...)
+	cmd := exec.Command(os.Args[0], cs...)
+	cmd.Env = append([]string{"GO_WANT_HELPER_PROCESS_EXEC=1"}, os.Environ()...)
+	return cmd
+}
+
+func openFiles() int {
+	f, err := os.Open("/dev/null")
+	if err != nil {
+		panic("Failed to open /dev/null\n")
+	}
+	n := f.Fd()
+	f.Close()
+	return int(n)
+}
+
+func clean(t *testing.T, ph ...*vexec.ParentHandle) {
+	for _, p := range ph {
+		alreadyClean := !p.Exists()
+		p.Clean()
+		if !alreadyClean && p.Exists() {
+			t.Errorf("child process left behind even after calling Clean")
+		}
+	}
+	if want, got := baselineOpenFiles, openFiles(); want != got {
+		t.Errorf("Leaking file descriptors: expect %d, got %d", want, got)
+	}
+}
+
+func read(ch chan bool, r io.Reader, m string) {
+	buf := make([]byte, 4096*4)
+	n, err := r.Read(buf)
+	if err != nil {
+		log.Printf("failed to read message: error %s, expecting '%s'\n",
+			err, m)
+		ch <- false
+		return
+	}
+	g := string(buf[:n])
+	b := g == m
+	if !b {
+		log.Printf("read '%s', not '%s'\n", g, m)
+	}
+	ch <- b
+}
+
+func expectMessage(r io.Reader, m string) bool {
+	ch := make(chan bool, 1)
+	go read(ch, r, m)
+	select {
+	case b := <-ch:
+		return b
+	case <-time.After(5 * time.Second):
+		log.Printf("expectMessage: timeout\n")
+		return false
+	}
+}
+
+func TestConfigExchange(t *testing.T) {
+	cmd := helperCommand("testConfig")
+	stderr, _ := cmd.StderrPipe()
+	cfg := vexec.NewConfig()
+	cfg.Set("foo", "bar")
+	ph := vexec.NewParentHandle(cmd, vexec.ConfigOpt{Config: cfg})
+	err := ph.Start()
+	if err != nil {
+		t.Fatalf("testConfig: start: %v", err)
+	}
+	serialized, err := cfg.Serialize()
+	if err != nil {
+		t.Fatalf("testConfig: failed to serialize config: %v", err)
+	}
+	if !expectMessage(stderr, serialized) {
+		t.Errorf("unexpected output from child")
+	} else {
+		if err = cmd.Wait(); err != nil {
+			t.Errorf("testConfig: wait: %v", err)
+		}
+	}
+	clean(t, ph)
+}
+
+func TestSecretExchange(t *testing.T) {
+	cmd := helperCommand("testSecret")
+	stderr, _ := cmd.StderrPipe()
+	ph := vexec.NewParentHandle(cmd, vexec.SecretOpt("dummy_secret"))
+	err := ph.Start()
+	if err != nil {
+		t.Fatalf("testSecretTest: start: %v", err)
+	}
+	if !expectMessage(stderr, "dummy_secret") {
+		t.Errorf("unexpected output from child")
+	} else {
+		if err = cmd.Wait(); err != nil {
+			t.Errorf("testSecretTest: wait: %v", err)
+		}
+	}
+	clean(t, ph)
+}
+
+func TestNoVersion(t *testing.T) {
+	// Make sure that Init correctly tests for the presence of VEXEC_VERSION
+	_, err := vexec.GetChildHandle()
+	if verror.ErrorID(err) != vexec.ErrNoVersion.ID {
+		t.Errorf("Should be missing Version")
+	}
+}
+
+func waitForReady(t *testing.T, cmd *exec.Cmd, name string, delay int, ph *vexec.ParentHandle) error {
+	err := ph.Start()
+	if err != nil {
+		t.Fatalf("%s: start: %v", name, err)
+		return err
+	}
+	return ph.WaitForReady(time.Duration(delay) * time.Second)
+}
+
+func readyHelperCmd(t *testing.T, cmd *exec.Cmd, name, result string) *vexec.ParentHandle {
+	stderr, err := cmd.StderrPipe()
+	if err != nil {
+		t.Fatalf("%s: failed to get stderr pipe\n", name)
+	}
+	ph := vexec.NewParentHandle(cmd)
+	if err := waitForReady(t, cmd, name, 4, ph); err != nil {
+		t.Errorf("%s: WaitForReady: %v (%v)", name, err, ph)
+		return nil
+	}
+	if !expectMessage(stderr, result) {
+		t.Errorf("%s: failed to read '%s' from child\n", name, result)
+	}
+	return ph
+}
+
+func readyHelper(t *testing.T, name, test, result string) *vexec.ParentHandle {
+	cmd := helperCommand(test)
+	return readyHelperCmd(t, cmd, name, result)
+}
+
+func testMany(t *testing.T, name, test, result string) []*vexec.ParentHandle {
+	nprocs := 10
+	ph := make([]*vexec.ParentHandle, nprocs)
+	cmd := make([]*exec.Cmd, nprocs)
+	stderr := make([]io.ReadCloser, nprocs)
+	controlReaders := make([]io.ReadCloser, nprocs)
+	var done sync.WaitGroup
+	for i := 0; i < nprocs; i++ {
+		cmd[i] = helperCommand(test)
+		// The control pipe is used to signal the child when to wake up.
+		controlRead, controlWrite, err := os.Pipe()
+		if err != nil {
+			t.Errorf("Failed to create control pipe: %v", err)
+			return nil
+		}
+		controlReaders[i] = controlRead
+		cmd[i].ExtraFiles = append(cmd[i].ExtraFiles, controlRead)
+		stderr[i], _ = cmd[i].StderrPipe()
+		tk := timekeeper.NewManualTime()
+		ph[i] = vexec.NewParentHandle(cmd[i], vexec.TimeKeeperOpt{TimeKeeper: tk})
+		done.Add(1)
+		go func() {
+			// For simulated slow children, wait until the parent
+			// starts waiting, and then wake up the child.
+			if test == "testReadySlow" {
+				<-tk.Requests()
+				tk.AdvanceTime(3 * time.Second)
+				if _, err = controlWrite.Write([]byte("wake")); err != nil {
+					t.Errorf("Failed to write to control pipe: %v", err)
+				}
+			}
+			controlWrite.Close()
+			done.Done()
+		}()
+		if err := ph[i].Start(); err != nil {
+			t.Errorf("%s: Failed to start child %d: %s\n", name, i, err)
+		}
+	}
+	for i := 0; i < nprocs; i++ {
+		if err := ph[i].WaitForReady(5 * time.Second); err != nil {
+			t.Errorf("%s: Failed to wait for child %d: %s\n", name, i, err)
+		}
+		controlReaders[i].Close()
+	}
+	for i := 0; i < nprocs; i++ {
+		if !expectMessage(stderr[i], result) {
+			t.Errorf("%s: Failed to read message from child %d\n", name, i)
+		}
+	}
+	done.Wait()
+	return ph
+}
+
+func TestToReadyMany(t *testing.T) {
+	clean(t, testMany(t, "TestToReadyMany", "testReady", ".")...)
+}
+
+func TestToReadySlowMany(t *testing.T) {
+	clean(t, testMany(t, "TestToReadySlowMany", "testReadySlow", "..")...)
+}
+
+func TestToReady(t *testing.T) {
+	ph := readyHelper(t, "TestToReady", "testReady", ".")
+	clean(t, ph)
+}
+
+func TestToFail(t *testing.T) {
+	name := "testFail"
+	cmd := helperCommand(name, "failed", "to", "start")
+	ph := vexec.NewParentHandle(cmd)
+	err := waitForReady(t, cmd, name, 4, ph)
+	if err == nil || !strings.Contains(err.Error(), "failed to start") {
+		t.Errorf("unexpected error: %v", err)
+	}
+}
+
+func TestToFailInvalidUTF8(t *testing.T) {
+	name := "testFail"
+	cmd := helperCommand(name, "invalid", "utf8", string([]byte{0xFF}), "in", string([]byte{0xFC}), "error", "message")
+	ph := vexec.NewParentHandle(cmd)
+	err := waitForReady(t, cmd, name, 4, ph)
+	if err == nil || !strings.Contains(err.Error(), "invalid utf8 "+string(utf8.RuneError)+" in "+string(utf8.RuneError)+" error message") {
+		t.Errorf("unexpected error: %v", err)
+	}
+}
+
+func TestNeverReady(t *testing.T) {
+	name := "testNeverReady"
+	result := "never ready"
+	cmd := helperCommand(name)
+	stderr, _ := cmd.StderrPipe()
+	ph := vexec.NewParentHandle(cmd)
+	err := waitForReady(t, cmd, name, 1, ph)
+	if verror.ErrorID(err) != vexec.ErrTimeout.ID {
+		t.Errorf("Failed to get timeout: got %v\n", err)
+	} else {
+		// block waiting for error from child
+		if !expectMessage(stderr, result) {
+			t.Errorf("%s: failed to read '%s' from child\n", name, result)
+		}
+	}
+	clean(t, ph)
+}
+
+func TestTooSlowToReady(t *testing.T) {
+	name := "testTooSlowToReady"
+	result := "write status_wr: broken pipe"
+	cmd := helperCommand(name)
+	// The control pipe is used to signal the child when to wake up.
+	controlRead, controlWrite, err := os.Pipe()
+	if err != nil {
+		t.Errorf("Failed to create control pipe: %v", err)
+		return
+	}
+	cmd.ExtraFiles = append(cmd.ExtraFiles, controlRead)
+	stderr, _ := cmd.StderrPipe()
+	tk := timekeeper.NewManualTime()
+	ph := vexec.NewParentHandle(cmd, vexec.TimeKeeperOpt{TimeKeeper: tk})
+	defer clean(t, ph)
+	defer controlWrite.Close()
+	defer controlRead.Close()
+	// Wait for the parent to start waiting, then simulate a timeout.
+	go func() {
+		toWait := <-tk.Requests()
+		tk.AdvanceTime(toWait)
+	}()
+	err = waitForReady(t, cmd, name, 1, ph)
+	if verror.ErrorID(err) != vexec.ErrTimeout.ID {
+		t.Errorf("Failed to get timeout: got %v\n", err)
+	} else {
+		// After the parent timed out, wake up the child and let it
+		// proceed.
+		if _, err = controlWrite.Write([]byte("wake")); err != nil {
+			t.Errorf("Failed to write to control pipe: %v", err)
+		} else {
+			// block waiting for error from child
+			if !expectMessage(stderr, result) {
+				t.Errorf("%s: failed to read '%s' from child\n", name, result)
+			}
+		}
+	}
+}
+
+func TestToReadySlow(t *testing.T) {
+	name := "TestToReadySlow"
+	cmd := helperCommand("testReadySlow")
+	// The control pipe is used to signal the child when to wake up.
+	controlRead, controlWrite, err := os.Pipe()
+	if err != nil {
+		t.Errorf("Failed to create control pipe: %v", err)
+		return
+	}
+	cmd.ExtraFiles = append(cmd.ExtraFiles, controlRead)
+	stderr, err := cmd.StderrPipe()
+	if err != nil {
+		t.Fatalf("%s: failed to get stderr pipe", name)
+	}
+	tk := timekeeper.NewManualTime()
+	ph := vexec.NewParentHandle(cmd, vexec.TimeKeeperOpt{TimeKeeper: tk})
+	defer clean(t, ph)
+	defer controlWrite.Close()
+	defer controlRead.Close()
+	// Wait for the parent to start waiting, simulate a short wait (but not
+	// enough to timeout the parent), then wake up the child.
+	go func() {
+		<-tk.Requests()
+		tk.AdvanceTime(2 * time.Second)
+		if _, err = controlWrite.Write([]byte("wake")); err != nil {
+			t.Errorf("Failed to write to control pipe: %v", err)
+		}
+	}()
+	if err := waitForReady(t, cmd, name, 4, ph); err != nil {
+		t.Errorf("%s: WaitForReady: %v (%v)", name, err, ph)
+		return
+	}
+	// After the child has replied, simulate a timeout on the server by
+	// advacing the time more; at this point, however, the timeout should no
+	// longer occur since the child already replied.
+	tk.AdvanceTime(2 * time.Second)
+	if result := ".."; !expectMessage(stderr, result) {
+		t.Errorf("%s: failed to read '%s' from child\n", name, result)
+	}
+}
+
+func TestToCompletion(t *testing.T) {
+	ph := readyHelper(t, "TestToCompletion", "testSuccess", "...ok")
+	e := ph.Wait(time.Second)
+	if e != nil {
+		t.Errorf("Wait failed: err %s\n", e)
+	}
+	clean(t, ph)
+}
+
+func TestToCompletionError(t *testing.T) {
+	ph := readyHelper(t, "TestToCompletionError", "testError", "...err")
+	e := ph.Wait(time.Second)
+	if e == nil {
+		t.Errorf("Wait failed: err %s\n", e)
+	}
+	clean(t, ph)
+}
+
+func TestExtraFiles(t *testing.T) {
+	cmd := helperCommand("testExtraFiles")
+	rd, wr, err := os.Pipe()
+	if err != nil {
+		t.Fatalf("Failed to create pipe: %s\n", err)
+	}
+	cmd.ExtraFiles = append(cmd.ExtraFiles, rd)
+	msg := "hello there..."
+	fmt.Fprintf(wr, msg)
+	ph := readyHelperCmd(t, cmd, "TestExtraFiles", "child: "+msg)
+	if ph == nil {
+		t.Fatalf("Failed to get vexec.ParentHandle\n")
+	}
+	e := ph.Wait(1 * time.Second)
+	if e != nil {
+		t.Errorf("Wait failed: err %s\n", e)
+	}
+	rd.Close()
+	wr.Close()
+	clean(t, ph)
+}
+
+func TestExitEarly(t *testing.T) {
+	name := "exitEarly"
+	cmd := helperCommand(name)
+	tk := timekeeper.NewManualTime()
+	ph := vexec.NewParentHandle(cmd, vexec.TimeKeeperOpt{TimeKeeper: tk})
+	err := ph.Start()
+	if err != nil {
+		t.Fatalf("%s: start: %v", name, err)
+	}
+	e := ph.Wait(time.Second)
+	if e == nil || e.Error() != "exit status 1" {
+		t.Errorf("Unexpected value for error: %v\n", e)
+	}
+	clean(t, ph)
+}
+
+func TestWaitAndCleanRace(t *testing.T) {
+	name := "testReadyAndHang"
+	cmd := helperCommand(name)
+	tk := timekeeper.NewManualTime()
+	ph := vexec.NewParentHandle(cmd, vexec.TimeKeeperOpt{TimeKeeper: tk})
+	if err := waitForReady(t, cmd, name, 1, ph); err != nil {
+		t.Errorf("%s: WaitForReady: %v (%v)", name, err, ph)
+	}
+	go func() {
+		// Wait for the ph.Wait below to block, then advance the time
+		// s.t. the Wait times out.
+		<-tk.Requests()
+		tk.AdvanceTime(2 * time.Second)
+	}()
+	if got, want := ph.Wait(time.Second), vexec.ErrTimeout.ID; got == nil || verror.ErrorID(got) != want {
+		t.Errorf("Wait returned %v, wanted %v instead", got, want)
+	}
+	if got, want := ph.Clean(), "signal: killed"; got == nil || got.Error() != want {
+		t.Errorf("Wait returned %v, wanted %v instead", got, want)
+	}
+}
+
+func verifyNoExecVariable() {
+	version := os.Getenv(vexec.ExecVersionVariable)
+	if len(version) != 0 {
+		log.Fatalf("Version variable %q has a value: %s", vexec.ExecVersionVariable, version)
+	}
+}
+
+// TestHelperProcess isn't a real test; it's used as a helper process
+// for the other tests.
+func TestHelperProcess(*testing.T) {
+	// Return immediately if this is not run as the child helper
+	// for the other tests.
+	if os.Getenv("GO_WANT_HELPER_PROCESS_EXEC") != "1" {
+		return
+	}
+	defer os.Exit(0)
+
+	version := os.Getenv(vexec.ExecVersionVariable)
+	if len(version) == 0 {
+		log.Fatalf("Version variable %q has no value", vexec.ExecVersionVariable)
+	}
+
+	// Write errors to stderr or using log. since the parent
+	// process is reading stderr.
+	args := os.Args
+	for len(args) > 0 {
+		if args[0] == "--" {
+			args = args[1:]
+			break
+		}
+		args = args[1:]
+	}
+
+	if len(args) == 0 {
+		log.Fatal("No command")
+	}
+
+	cmd, args := args[0], args[1:]
+
+	switch cmd {
+	case "exitEarly":
+		_, err := vexec.GetChildHandle()
+		if err != nil {
+			log.Fatal(err)
+		}
+		os.Exit(1)
+	case "testNeverReady":
+		_, err := vexec.GetChildHandle()
+		if err != nil {
+			log.Fatal(err)
+		}
+		verifyNoExecVariable()
+		fmt.Fprintf(os.Stderr, "never ready")
+	case "testTooSlowToReady":
+		ch, err := vexec.GetChildHandle()
+		if err != nil {
+			log.Fatal(err)
+		}
+		verifyNoExecVariable()
+		// Wait for the parent to tell us when it's ok to proceed.
+		controlPipe := ch.NewExtraFile(0, "control_rd")
+		for {
+			buf := make([]byte, 100)
+			n, err := controlPipe.Read(buf)
+			if err != nil {
+				log.Fatal(err)
+			}
+			if n > 0 {
+				break
+			}
+		}
+		// SetReady should return an error since the parent has
+		// timed out on us and we'd be writing to a closed pipe.
+		if err := ch.SetReady(); err != nil {
+			fmt.Fprintf(os.Stderr, "%s", err)
+		} else {
+			fmt.Fprintf(os.Stderr, "didn't get the expected error")
+		}
+	case "testFail":
+		ch, err := vexec.GetChildHandle()
+		if err != nil {
+			log.Fatal(err)
+		}
+		verifyNoExecVariable()
+		err = ch.SetFailed(fmt.Errorf("%s", strings.Join(args, " ")))
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "%s\n", err)
+		}
+		// It's fine to call SetFailed multiple times.
+		if err2 := ch.SetFailed(fmt.Errorf("dummy")); err != err2 {
+			fmt.Fprintf(os.Stderr, "Received new error got: %v, want %v\n", err2, err)
+		}
+	case "testReady":
+		ch, err := vexec.GetChildHandle()
+		if err != nil {
+			log.Fatal(err)
+		}
+		verifyNoExecVariable()
+		err = ch.SetReady()
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "%s\n", err)
+		}
+		// It's fine to call SetReady multiple times.
+		if err2 := ch.SetReady(); err != err2 {
+			fmt.Fprintf(os.Stderr, "Received new error got: %v, want %v\n", err2, err)
+		}
+		fmt.Fprintf(os.Stderr, ".")
+	case "testReadySlow":
+		ch, err := vexec.GetChildHandle()
+		if err != nil {
+			log.Fatal(err)
+		}
+		verifyNoExecVariable()
+		// Wait for the parent to tell us when it's ok to proceed.
+		controlPipe := ch.NewExtraFile(0, "control_rd")
+		for {
+			buf := make([]byte, 100)
+			n, err := controlPipe.Read(buf)
+			if err != nil {
+				log.Fatal(err)
+			}
+			if n > 0 {
+				break
+			}
+		}
+		ch.SetReady()
+		fmt.Fprintf(os.Stderr, "..")
+	case "testReadyAndHang":
+		ch, err := vexec.GetChildHandle()
+		if err != nil {
+			log.Fatal(err)
+		}
+		verifyNoExecVariable()
+		ch.SetReady()
+		<-time.After(time.Minute)
+	case "testSuccess", "testError":
+		ch, err := vexec.GetChildHandle()
+		if err != nil {
+			log.Fatal(err)
+		}
+		verifyNoExecVariable()
+		ch.SetReady()
+		rc := make(chan int)
+		go func() {
+			if cmd == "testError" {
+				fmt.Fprintf(os.Stderr, "...err")
+				rc <- 1
+			} else {
+				fmt.Fprintf(os.Stderr, "...ok")
+				rc <- 0
+			}
+		}()
+		r := <-rc
+		os.Exit(r)
+	case "testConfig":
+		ch, err := vexec.GetChildHandle()
+		if err != nil {
+			log.Fatalf("%v", err)
+		} else {
+			verifyNoExecVariable()
+			serialized, err := ch.Config.Serialize()
+			if err != nil {
+				log.Fatalf("%v", err)
+			}
+			fmt.Fprintf(os.Stderr, "%s", serialized)
+		}
+	case "testSecret":
+		ch, err := vexec.GetChildHandle()
+		if err != nil {
+			log.Fatalf("%s", err)
+		} else {
+			verifyNoExecVariable()
+			fmt.Fprintf(os.Stderr, "%s", ch.Secret)
+		}
+	case "testExtraFiles":
+		ch, err := vexec.GetChildHandle()
+		if err != nil {
+			log.Fatalf("error.... %s\n", err)
+		}
+		verifyNoExecVariable()
+		err = ch.SetReady()
+		rd := ch.NewExtraFile(0, "read")
+		buf := make([]byte, 1024)
+		if n, err := rd.Read(buf); err != nil {
+			fmt.Fprintf(os.Stderr, "child: error %s\n", err)
+		} else {
+			fmt.Fprintf(os.Stderr, "child: %s", string(buf[:n]))
+		}
+	}
+}
diff --git a/lib/exec/noprotocol_test.go b/lib/exec/noprotocol_test.go
new file mode 100644
index 0000000..ba0444b
--- /dev/null
+++ b/lib/exec/noprotocol_test.go
@@ -0,0 +1,36 @@
+// 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 exec_test
+
+import (
+	"bufio"
+	"fmt"
+	"os/exec"
+	"regexp"
+	"testing"
+	"time"
+
+	"v.io/v23/verror"
+	vexec "v.io/x/ref/lib/exec"
+)
+
+func TestNoExecProtocol(t *testing.T) {
+	cmd := exec.Command("bash", "-c", "printenv")
+	stdout, _ := cmd.StdoutPipe()
+	ph := vexec.NewParentHandle(cmd, vexec.UseExecProtocolOpt(false))
+	if err := ph.Start(); err != nil {
+		t.Fatal(err)
+	}
+	if got, want := ph.WaitForReady(time.Minute), vexec.ErrNotUsingProtocol.ID; verror.ErrorID(got) != want {
+		t.Fatalf("got %v, want %v", got, want)
+	}
+	re := regexp.MustCompile(fmt.Sprintf(".*%s=.*", vexec.ExecVersionVariable))
+	scanner := bufio.NewScanner(stdout)
+	for scanner.Scan() {
+		if re.MatchString(scanner.Text()) {
+			t.Fatalf("%s passed to child", vexec.ExecVersionVariable)
+		}
+	}
+}
diff --git a/lib/exec/parent.go b/lib/exec/parent.go
new file mode 100644
index 0000000..230e5d0
--- /dev/null
+++ b/lib/exec/parent.go
@@ -0,0 +1,386 @@
+// 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 exec
+
+import (
+	"bytes"
+	"encoding/binary"
+	"fmt"
+	"io"
+	"os"
+	"os/exec"
+	"strconv"
+	"strings"
+	"sync"
+	"syscall"
+	"time"
+
+	"v.io/v23/verror"
+	"v.io/x/lib/envvar"
+	"v.io/x/lib/vlog"
+	"v.io/x/ref/lib/timekeeper"
+)
+
+const pkgPath = "v.io/x/ref/lib/exec"
+
+var (
+	ErrAuthTimeout      = verror.Register(pkgPath+".ErrAuthTimeout", verror.NoRetry, "{1:}{2:} timeout in auth handshake{:_}")
+	ErrTimeout          = verror.Register(pkgPath+".ErrTimeout", verror.NoRetry, "{1:}{2:} timeout waiting for child{:_}")
+	ErrSecretTooLarge   = verror.Register(pkgPath+".ErrSecretTooLarge", verror.NoRetry, "{1:}{2:} secret is too large{:_}")
+	ErrNotUsingProtocol = verror.Register(pkgPath+".ErrNotUsingProtocol", verror.NoRetry, "{1:}{2:} not using parent/child exec protocol{:_}")
+
+	errFailedStatus       = verror.Register(pkgPath+".errFailedStatus", verror.NoRetry, "{1:}{2:} {_}")
+	errUnrecognizedStatus = verror.Register(pkgPath+".errUnrecognizedStatus", verror.NoRetry, "{1:}{2:} unrecognised status from subprocess{:_}")
+	errUnexpectedType     = verror.Register(pkgPath+".errUnexpectedType", verror.NoRetry, "{1:}{2:} unexpected type {3}{:_}")
+	errNoSuchProcess      = verror.Register(pkgPath+".errNoSuchProcess", verror.NoRetry, "{1:}{2:} no such process{:_}")
+	errPartialWrite       = verror.Register(pkgPath+".errPartialWrite", verror.NoRetry, "{1:}{2:} partial write{:_}")
+)
+
+// A ParentHandle is the Parent process' means of managing a single child.
+type ParentHandle struct {
+	c           *exec.Cmd
+	config      Config
+	protocol    bool // true if we're using the parent/child protocol.
+	secret      string
+	statusRead  *os.File
+	statusWrite *os.File
+	tk          timekeeper.TimeKeeper
+	waitDone    bool
+	waitErr     error
+	waitLock    sync.Mutex
+	callbackPid int
+}
+
+// ParentHandleOpt is an option for NewParentHandle.
+type ParentHandleOpt interface {
+	// ExecParentHandleOpt is a signature 'dummy' method for the
+	// interface.
+	ExecParentHandleOpt()
+}
+
+// ConfigOpt can be used to seed the parent handle with a
+// config to be passed to the child.
+type ConfigOpt struct {
+	Config
+}
+
+// ExecParentHandleOpt makes ConfigOpt an instance of
+// ParentHandleOpt.
+func (ConfigOpt) ExecParentHandleOpt() {}
+
+// SecretOpt can be used to seed the parent handle with a custom secret.
+type SecretOpt string
+
+// ExecParentHandleOpt makes SecretOpt an instance of ParentHandleOpt.
+func (SecretOpt) ExecParentHandleOpt() {}
+
+// TimeKeeperOpt can be used to seed the parent handle with a custom timekeeper.
+type TimeKeeperOpt struct {
+	timekeeper.TimeKeeper
+}
+
+// ExecParentHandleOpt makes TimeKeeperOpt an instance of ParentHandleOpt.
+func (TimeKeeperOpt) ExecParentHandleOpt() {}
+
+// UseExecProtocolOpt can be used to control whether parent/child handshake
+// protocol is used. WaitForReady will return immediately with an error if
+// this option is set to false. The defaults behaviour is to assume that
+// the exec protocol is used. If it is not used, then the Start method
+// will not create shared file descriptors to use for the exec protocol.
+type UseExecProtocolOpt bool
+
+func (UseExecProtocolOpt) ExecParentHandleOpt() {}
+
+// NewParentHandle creates a ParentHandle for the child process represented by
+// an instance of exec.Cmd.
+func NewParentHandle(c *exec.Cmd, opts ...ParentHandleOpt) *ParentHandle {
+	cfg, secret := NewConfig(), ""
+	tk := timekeeper.RealTime()
+	protocol := true
+	for _, opt := range opts {
+		switch v := opt.(type) {
+		case ConfigOpt:
+			cfg = v
+		case SecretOpt:
+			secret = string(v)
+		case TimeKeeperOpt:
+			tk = v
+		case UseExecProtocolOpt:
+			protocol = bool(v)
+		default:
+			vlog.Errorf("Unrecognized parent option: %v", v)
+		}
+	}
+	return &ParentHandle{
+		c:        c,
+		protocol: protocol,
+		config:   cfg,
+		secret:   secret,
+		tk:       tk,
+	}
+}
+
+// Start starts the child process, sharing a secret with it and
+// setting up a communication channel over which to read its status.
+func (p *ParentHandle) Start() error {
+	env := envvar.SliceToMap(p.c.Env)
+	if !p.protocol {
+		// Ensure ExecVersionVariable is not set if we're not using the protocol.
+		delete(env, ExecVersionVariable)
+		p.c.Env = envvar.MapToSlice(env)
+		return p.c.Start()
+	}
+	// Ensure ExecVersionVariable is always set if we are using the protocol.
+	env[ExecVersionVariable] = version1
+	p.c.Env = envvar.MapToSlice(env)
+
+	// Create anonymous pipe for communicating data between the child
+	// and the parent.
+	// TODO(caprita): As per ribrdb@, Go's exec does not prune the set
+	// of file descriptors passed down to the child process, and hence
+	// a child may get access to the files meant for another child.
+	// Do we need to ensure only one thread is allowed to create these
+	// pipes at any time?
+	dataRead, dataWrite, err := os.Pipe()
+	if err != nil {
+		return err
+	}
+	defer dataRead.Close()
+	defer dataWrite.Close()
+	statusRead, statusWrite, err := os.Pipe()
+	if err != nil {
+		return err
+	}
+	p.statusRead = statusRead
+	p.statusWrite = statusWrite
+	// Add the parent-child pipes to cmd.ExtraFiles, offsetting all
+	// existing file descriptors accordingly.
+	extraFiles := make([]*os.File, len(p.c.ExtraFiles)+2)
+	extraFiles[0] = dataRead
+	extraFiles[1] = statusWrite
+	for i, _ := range p.c.ExtraFiles {
+		extraFiles[i+2] = p.c.ExtraFiles[i]
+	}
+	p.c.ExtraFiles = extraFiles
+	// Start the child process.
+	if err := p.c.Start(); err != nil {
+		p.statusWrite.Close()
+		p.statusRead.Close()
+		return err
+	}
+	// Pass data to the child using a pipe.
+	serializedConfig, err := p.config.Serialize()
+	if err != nil {
+		return err
+	}
+	if err := encodeString(dataWrite, serializedConfig); err != nil {
+		p.statusWrite.Close()
+		p.statusRead.Close()
+		return err
+	}
+	if err := encodeString(dataWrite, p.secret); err != nil {
+		p.statusWrite.Close()
+		p.statusRead.Close()
+		return err
+	}
+	return nil
+}
+
+// copy is like io.Copy, but it also treats the receipt of the special eofChar
+// byte to mean io.EOF.
+func copy(w io.Writer, r io.Reader) (err error) {
+	buf := make([]byte, 1024)
+	for {
+		nRead, errRead := r.Read(buf)
+		if nRead > 0 {
+			if eofCharIndex := bytes.IndexByte(buf[:nRead], eofChar); eofCharIndex != -1 {
+				nRead = eofCharIndex
+				errRead = io.EOF
+			}
+			nWrite, errWrite := w.Write(buf[:nRead])
+			if errWrite != nil {
+				err = errWrite
+				break
+			}
+			if nRead != nWrite {
+				err = io.ErrShortWrite
+				break
+			}
+		}
+		if errRead == io.EOF {
+			break
+		}
+		if errRead != nil {
+			err = errRead
+			break
+		}
+	}
+	return
+}
+
+func waitForStatus(c chan interface{}, r *os.File) {
+	var readBytes bytes.Buffer
+	err := copy(&readBytes, r)
+	r.Close()
+	if err != nil {
+		c <- err
+	} else {
+		c <- readBytes.String()
+	}
+	close(c)
+}
+
+// WaitForReady will wait for the child process to become ready.
+func (p *ParentHandle) WaitForReady(timeout time.Duration) error {
+	if !p.protocol {
+		return verror.New(ErrNotUsingProtocol, nil)
+	}
+	// An invariant of WaitForReady is that both statusWrite and statusRead
+	// get closed before WaitForStatus returns (statusRead gets closed by
+	// waitForStatus).
+	defer p.statusWrite.Close()
+	c := make(chan interface{}, 1)
+	go waitForStatus(c, p.statusRead)
+	// TODO(caprita): This can be simplified further by doing the reading
+	// from the status pipe here, and instead moving the timeout listener to
+	// a separate goroutine.
+	select {
+	case msg := <-c:
+		switch m := msg.(type) {
+		case error:
+			return m
+		case string:
+			if strings.HasPrefix(m, readyStatus) {
+				pid, err := strconv.Atoi(m[len(readyStatus):])
+				if err != nil {
+					return err
+				}
+				p.callbackPid = pid
+				return nil
+			}
+			if strings.HasPrefix(m, failedStatus) {
+				return verror.New(errFailedStatus, nil, strings.TrimPrefix(m, failedStatus))
+			}
+			return verror.New(errUnrecognizedStatus, nil, m)
+		default:
+			return verror.New(errUnexpectedType, nil, fmt.Sprintf("%T", m))
+		}
+	case <-p.tk.After(timeout):
+		vlog.Errorf("Timed out waiting for child status")
+		// By writing the special eofChar byte to the pipe, we ensure
+		// that waitForStatus returns: the copy function treats eofChar
+		// to indicate end of read input.  Note, copy could have
+		// finished for other reasons already (receipt of eofChar from
+		// the child process).  Note, closing the pipe from the child
+		// (explicitly or due to crash) would NOT cause copy to read
+		// io.EOF, since we keep the statusWrite open in the parent.
+		// Hence, a child crash will eventually trigger this timeout.
+		p.statusWrite.Write([]byte{eofChar})
+		// Before returning, waitForStatus will close r, and then close
+		// c.  Waiting on c ensures that r.Close() in waitForStatus
+		// already executed.
+		<-c
+		return verror.New(ErrTimeout, nil)
+	}
+}
+
+// wait performs the Wait on the underlying command under lock, and only once
+// (subsequent wait calls block until the Wait is finished).  It's ok to call
+// wait multiple times, and in parallel.  The error from the initial Wait is
+// cached and returned for all subsequent calls.
+func (p *ParentHandle) wait() error {
+	p.waitLock.Lock()
+	defer p.waitLock.Unlock()
+	if p.waitDone {
+		return p.waitErr
+	}
+	p.waitErr = p.c.Wait()
+	p.waitDone = true
+	return p.waitErr
+}
+
+// Wait will wait for the child process to terminate of its own accord.
+// It returns nil if the process exited cleanly with an exit status of 0,
+// any other exit code or error will result in an appropriate error return
+func (p *ParentHandle) Wait(timeout time.Duration) error {
+	c := make(chan error, 1)
+	go func() {
+		c <- p.wait()
+		close(c)
+	}()
+	// If timeout is zero time.After will panic; we handle zero specially
+	// to mean infinite timeout.
+	if timeout > 0 {
+		select {
+		case <-p.tk.After(timeout):
+			return verror.New(ErrTimeout, nil)
+		case err := <-c:
+			return err
+		}
+	} else {
+		return <-c
+	}
+}
+
+// Pid returns the pid of the child, 0 if the child process doesn't exist
+func (p *ParentHandle) Pid() int {
+	if p.c.Process != nil {
+		return p.c.Process.Pid
+	}
+	return 0
+}
+
+// ChildPid returns the pid of a child process as reported by its status
+// callback.
+func (p *ParentHandle) ChildPid() int {
+	return p.callbackPid
+}
+
+// Exists returns true if the child process exists and can be signal'ed
+func (p *ParentHandle) Exists() bool {
+	if p.c.Process != nil {
+		return syscall.Kill(p.c.Process.Pid, 0) == nil
+	}
+	return false
+}
+
+// Kill kills the child process.
+func (p *ParentHandle) Kill() error {
+	if p.c.Process == nil {
+		return verror.New(errNoSuchProcess, nil)
+	}
+	return p.c.Process.Kill()
+}
+
+// Signal sends the given signal to the child process.
+func (p *ParentHandle) Signal(sig syscall.Signal) error {
+	if p.c.Process == nil {
+		return verror.New(errNoSuchProcess, nil)
+	}
+	return syscall.Kill(p.c.Process.Pid, sig)
+}
+
+// Clean will clean up state, including killing the child process.
+func (p *ParentHandle) Clean() error {
+	if err := p.Kill(); err != nil {
+		return err
+	}
+	return p.wait()
+}
+
+func encodeString(w io.Writer, data string) error {
+	l := len(data)
+	if err := binary.Write(w, binary.BigEndian, int64(l)); err != nil {
+		return err
+	}
+	if n, err := w.Write([]byte(data)); err != nil || n != l {
+		if err != nil {
+			return err
+		} else {
+			return verror.New(errPartialWrite, nil)
+		}
+	}
+	return nil
+}
diff --git a/lib/flags/doc.go b/lib/flags/doc.go
new file mode 100644
index 0000000..6d7807a
--- /dev/null
+++ b/lib/flags/doc.go
@@ -0,0 +1,22 @@
+// 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 flags implements utilities to augment the standard Go flag package.
+// It defines commonly used Vanadium flags, and implementations of the
+// flag.Value interface for those flags to ensure that only valid values of
+// those flags are supplied.  Some of these flags may also be specified using
+// environment variables directly and are documented accordingly; in these cases
+// the command line value takes precedence over the environment variable.
+//
+// Flags are defined as 'groups' of related flags so that the caller may choose
+// which ones to use without having to be burdened with the full set.  The
+// groups may be used directly or via the Flags type that aggregates multiple
+// groups.  In all cases, the flags are registered with a supplied flag.FlagSet
+// and hence are not forced onto the command line unless the caller passes in
+// flag.CommandLine as the flag.FlagSet to use.
+//
+// In general, this package will be used by vanadium profiles and the runtime
+// implementations, but can also be used by any application that wants access to
+// the flags and environment variables it supports.
+package flags
diff --git a/lib/flags/flags.go b/lib/flags/flags.go
new file mode 100644
index 0000000..68211e5
--- /dev/null
+++ b/lib/flags/flags.go
@@ -0,0 +1,480 @@
+// 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 flags
+
+import (
+	"flag"
+	"fmt"
+	"os"
+	"strings"
+	"sync"
+
+	"v.io/v23/verror"
+	"v.io/x/ref"
+)
+
+const pkgPath = "v.io/x/ref/lib/flags"
+
+var (
+	errNotNameColonFile = verror.Register(pkgPath+".errNotNameColonFile", verror.NoRetry, "{1:}{2:} {3} is not in 'name:file' format{:_}")
+)
+
+// FlagGroup is the type for identifying groups of related flags.
+type FlagGroup int
+
+const (
+	// Runtime identifies the flags and associated environment variables
+	// used by the Vanadium process runtime. Namely:
+	// --v23.namespace.root (which may be repeated to supply multiple values)
+	// --v23.credentials
+	// --v23.vtrace.sample-rate
+	// --v23.vtrace.dump-on-shutdown
+	// --v23.vtrace.cache-size
+	// --v23.vtrace.collect-regexp
+	Runtime FlagGroup = iota
+	// Listen identifies the flags typically required to configure
+	// rpc.ListenSpec. Namely:
+	// --v23.tcp.protocol
+	// --v23.tcp.address
+	// --v23.proxy
+	// --v23.i18n-catalogue
+	Listen
+	// --v23.permissions.file (which may be repeated to supply multiple values)
+	// Permissions files are named - i.e. --v23.permissions.file=<name>:<file>
+	// with the name "runtime" reserved for use by the runtime. "file" is
+	// a JSON-encoded representation of the Permissions type defined in the
+	// VDL package v.io/v23/security/access
+	// -v23.permissions.literal
+	Permissions
+)
+
+var (
+	defaultNamespaceRoot = "/(dev.v.io/role/vprod/service/mounttabled)@ns.dev.v.io:8101" // GUARDED_BY namespaceMu
+	namespaceMu          sync.Mutex
+
+	defaultProtocol = "wsh" // GUARDED_BY listenMu
+	defaultHostPort = ":0"  // GUARDED_BY listenMu
+	listenMu        sync.RWMutex
+)
+
+// Flags represents the set of flag groups created by a call to
+// CreateAndRegister.
+type Flags struct {
+	FlagSet *flag.FlagSet
+	groups  map[FlagGroup]interface{}
+}
+
+type namespaceRootFlagVar struct {
+	isSet bool // is true when a flag has been explicitly set.
+	// isDefault true when a flag has the default value and is needed in
+	// addition to isSet to distinguish between using a default value
+	// as opposed to one from an environment variable.
+	isDefault bool
+	roots     []string
+}
+
+func (nsr *namespaceRootFlagVar) String() string {
+	return fmt.Sprintf("%v", nsr.roots)
+}
+
+func (nsr *namespaceRootFlagVar) Set(v string) error {
+	nsr.isDefault = false
+	if !nsr.isSet {
+		// override the default value
+		nsr.isSet = true
+		nsr.roots = []string{}
+	}
+	for _, t := range nsr.roots {
+		if v == t {
+			return nil
+		}
+	}
+	nsr.roots = append(nsr.roots, v)
+	return nil
+}
+
+type permsFlagVar struct {
+	isSet bool
+	files map[string]string
+}
+
+func (permsf *permsFlagVar) String() string {
+	return fmt.Sprintf("%v", permsf.files)
+}
+
+func (permsf *permsFlagVar) Set(v string) error {
+	if !permsf.isSet {
+		// override the default value
+		permsf.isSet = true
+		permsf.files = make(map[string]string)
+	}
+	parts := strings.SplitN(v, ":", 2)
+	if len(parts) != 2 {
+		return verror.New(errNotNameColonFile, nil, v)
+	}
+	name, file := parts[0], parts[1]
+	permsf.files[name] = file
+	return nil
+}
+
+// RuntimeFlags contains the values of the Runtime flag group.
+type RuntimeFlags struct {
+	// NamespaceRoots may be initialized by ref.EnvNamespacePrefix* enivornment
+	// variables as well as --v23.namespace.root. The command line
+	// will override the environment.
+	NamespaceRoots []string
+
+	// Credentials may be initialized by the ref.EnvCredentials
+	// environment variable. The command line will override the environment.
+	Credentials string // TODO(cnicolaou): provide flag.Value impl
+
+	// I18nCatalogue may be initialized by the ref.EnvI18nCatalogueFiles
+	// environment variable.  The command line will override the
+	// environment.
+	I18nCatalogue string
+
+	// Vtrace flags control various aspects of Vtrace.
+	Vtrace VtraceFlags
+
+	namespaceRootsFlag namespaceRootFlagVar
+}
+
+type VtraceFlags struct {
+	// VtraceSampleRate is the rate (from 0.0 - 1.0) at which
+	// vtrace traces started by this process are sampled for collection.
+	SampleRate float64
+
+	// VtraceDumpOnShutdown tells the runtime to dump all stored traces
+	// to Stderr at shutdown if true.
+	DumpOnShutdown bool
+
+	// VtraceCacheSize is the number of traces to cache in memory.
+	// TODO(mattr): Traces can be of widely varying size, we should have
+	// some better measurement then just number of traces.
+	CacheSize int
+
+	// SpanRegexp matches a regular expression against span names and
+	// annotations and forces any trace matching trace to be collected.
+	CollectRegexp string
+}
+
+// PermissionsFlags contains the values of the PermissionsFlags flag group.
+type PermissionsFlags struct {
+	// List of named Permissions files.
+	fileFlag permsFlagVar
+
+	// Single json string, overrides everything.
+	literal string
+}
+
+// PermissionsFile returns the file which is presumed to contain Permissions
+// information associated with the supplied name parameter.
+func (af PermissionsFlags) PermissionsFile(name string) string {
+	return af.fileFlag.files[name]
+}
+
+func (af PermissionsFlags) PermissionsLiteral() string {
+	return af.literal
+}
+
+// ListenAddrs is the set of listen addresses captured from the command line.
+// ListenAddrs mirrors rpc.ListenAddrs.
+type ListenAddrs []struct {
+	Protocol, Address string
+}
+
+// ListenFlags contains the values of the Listen flag group.
+type ListenFlags struct {
+	Addrs       ListenAddrs
+	ListenProxy string
+	protocol    tcpProtocolFlagVar
+	addresses   ipHostPortFlagVar
+}
+
+type tcpProtocolFlagVar struct {
+	isSet     bool
+	validator TCPProtocolFlag
+}
+
+// Implements flag.Value.Get
+func (proto tcpProtocolFlagVar) Get() interface{} {
+	return proto.validator.String()
+}
+
+func (proto tcpProtocolFlagVar) String() string {
+	return proto.validator.String()
+}
+
+// Implements flag.Value.Set
+func (proto *tcpProtocolFlagVar) Set(s string) error {
+	if err := proto.validator.Set(s); err != nil {
+		return err
+	}
+	proto.isSet = true
+	return nil
+}
+
+type ipHostPortFlagVar struct {
+	isSet     bool
+	validator IPHostPortFlag
+	flags     *ListenFlags
+}
+
+// Implements flag.Value.Get
+func (ip ipHostPortFlagVar) Get() interface{} {
+	return ip.String()
+}
+
+// Implements flag.Value.Set
+func (ip *ipHostPortFlagVar) Set(s string) error {
+	if err := ip.validator.Set(s); err != nil {
+		return err
+	}
+	a := struct {
+		Protocol, Address string
+	}{
+		ip.flags.protocol.validator.String(),
+		ip.validator.String(),
+	}
+	for _, t := range ip.flags.Addrs {
+		if t.Protocol == a.Protocol && t.Address == a.Address {
+			return nil
+		}
+	}
+	ip.flags.Addrs = append(ip.flags.Addrs, a)
+	ip.isSet = true
+	return nil
+}
+
+// Implements flag.Value.String
+func (ip ipHostPortFlagVar) String() string {
+	s := ""
+	for _, a := range ip.flags.Addrs {
+		s += fmt.Sprintf("(%s %s)", a.Protocol, a.Address)
+	}
+	return s
+}
+
+// createAndRegisterRuntimeFlags creates and registers the RuntimeFlags
+// group with the supplied flag.FlagSet.
+func createAndRegisterRuntimeFlags(fs *flag.FlagSet) *RuntimeFlags {
+	var (
+		f             = &RuntimeFlags{}
+		_, roots      = ref.EnvNamespaceRoots()
+		creds         = os.Getenv(ref.EnvCredentials)
+		i18nCatalogue = os.Getenv(ref.EnvI18nCatalogueFiles)
+	)
+	if len(roots) == 0 {
+		f.namespaceRootsFlag.roots = []string{defaultNamespaceRoot}
+		f.namespaceRootsFlag.isDefault = true
+	} else {
+		f.namespaceRootsFlag.roots = roots
+	}
+
+	fs.Var(&f.namespaceRootsFlag, "v23.namespace.root", "local namespace root; can be repeated to provided multiple roots")
+	fs.StringVar(&f.Credentials, "v23.credentials", creds, "directory to use for storing security credentials")
+	fs.StringVar(&f.I18nCatalogue, "v23.i18n-catalogue", i18nCatalogue, "18n catalogue files to load, comma separated")
+
+	fs.Float64Var(&f.Vtrace.SampleRate, "v23.vtrace.sample-rate", 0.0, "Rate (from 0.0 to 1.0) to sample vtrace traces.")
+	fs.BoolVar(&f.Vtrace.DumpOnShutdown, "v23.vtrace.dump-on-shutdown", true, "If true, dump all stored traces on runtime shutdown.")
+	fs.IntVar(&f.Vtrace.CacheSize, "v23.vtrace.cache-size", 1024, "The number of vtrace traces to store in memory.")
+	fs.StringVar(&f.Vtrace.CollectRegexp, "v23.vtrace.collect-regexp", "", "Spans and annotations that match this regular expression will trigger trace collection.")
+
+	return f
+}
+
+func createAndRegisterPermissionsFlags(fs *flag.FlagSet) *PermissionsFlags {
+	f := &PermissionsFlags{}
+	fs.Var(&f.fileFlag, "v23.permissions.file", "specify a perms file as <name>:<permsfile>")
+	fs.StringVar(&f.literal, "v23.permissions.literal", "", "explicitly specify the runtime perms as a JSON-encoded access.Permissions. Overrides all --v23.permissions.file flags.")
+	return f
+}
+
+// SetDefaultProtocol sets the default protocol used when --v23.tcp.protocol is
+// not provided. It must be called before flags are parsed for it to take effect.
+func SetDefaultProtocol(protocol string) {
+	listenMu.Lock()
+	defaultProtocol = protocol
+	listenMu.Unlock()
+}
+
+// SetDefaultHostPort sets the default host and port used when --v23.tcp.address
+// is not provided. It must be called before flags are parsed for it to take effect.
+func SetDefaultHostPort(s string) {
+	listenMu.Lock()
+	defaultHostPort = s
+	listenMu.Unlock()
+}
+
+// SetDefaultNamespaceRoot sets the default value for --v23.namespace.root
+func SetDefaultNamespaceRoot(root string) {
+	namespaceMu.Lock()
+	defaultNamespaceRoot = root
+	namespaceMu.Unlock()
+}
+
+// DefaultNamespaceRoot gets the default value of --v23.namespace.root
+func DefaultNamespaceRoot() string {
+	namespaceMu.Lock()
+	defer namespaceMu.Unlock()
+	return defaultNamespaceRoot
+}
+
+// createAndRegisterListenFlags creates and registers the ListenFlags
+// group with the supplied flag.FlagSet.
+func createAndRegisterListenFlags(fs *flag.FlagSet) *ListenFlags {
+	listenMu.RLock()
+	defer listenMu.RUnlock()
+	var ipHostPortFlag IPHostPortFlag
+	if err := ipHostPortFlag.Set(defaultHostPort); err != nil {
+		panic(err)
+	}
+	var protocolFlag TCPProtocolFlag
+	if err := protocolFlag.Set(defaultProtocol); err != nil {
+		panic(err)
+	}
+	f := &ListenFlags{
+		protocol:  tcpProtocolFlagVar{validator: protocolFlag},
+		addresses: ipHostPortFlagVar{validator: ipHostPortFlag},
+	}
+	f.addresses.flags = f
+
+	fs.Var(&f.protocol, "v23.tcp.protocol", "protocol to listen with")
+	fs.Var(&f.addresses, "v23.tcp.address", "address to listen on")
+	fs.StringVar(&f.ListenProxy, "v23.proxy", "", "object name of proxy service to use to export services across network boundaries")
+
+	return f
+}
+
+// CreateAndRegister creates a new set of flag groups as specified by the
+// supplied flag group parameters and registers them with the supplied
+// flag.FlagSet.
+func CreateAndRegister(fs *flag.FlagSet, groups ...FlagGroup) *Flags {
+	if len(groups) == 0 {
+		return nil
+	}
+	f := &Flags{FlagSet: fs, groups: make(map[FlagGroup]interface{})}
+	for _, g := range groups {
+		switch g {
+		case Runtime:
+			f.groups[Runtime] = createAndRegisterRuntimeFlags(fs)
+		case Listen:
+			f.groups[Listen] = createAndRegisterListenFlags(fs)
+		case Permissions:
+			f.groups[Permissions] = createAndRegisterPermissionsFlags(fs)
+		}
+	}
+	return f
+}
+
+func refreshDefaults(f *Flags) {
+	for _, g := range f.groups {
+		switch v := g.(type) {
+		case *RuntimeFlags:
+			if v.namespaceRootsFlag.isDefault {
+				v.namespaceRootsFlag.roots = []string{defaultNamespaceRoot}
+				v.NamespaceRoots = v.namespaceRootsFlag.roots
+			}
+		case *ListenFlags:
+			if !v.protocol.isSet {
+				v.protocol.validator.Set(defaultProtocol)
+			}
+			if !v.addresses.isSet {
+				v.addresses.validator.Set(defaultHostPort)
+			}
+		}
+	}
+}
+
+// RuntimeFlags returns the Runtime flag subset stored in its Flags
+// instance.
+func (f *Flags) RuntimeFlags() RuntimeFlags {
+	if p := f.groups[Runtime]; p == nil {
+		return RuntimeFlags{}
+	}
+	from := f.groups[Runtime].(*RuntimeFlags)
+	to := *from
+	to.NamespaceRoots = make([]string, len(from.NamespaceRoots))
+	copy(to.NamespaceRoots, from.NamespaceRoots)
+	return to
+}
+
+// ListenFlags returns a copy of the Listen flag group stored in Flags.
+// This copy will contain default values if the Listen flag group
+// was not specified when CreateAndRegister was called. The HasGroup
+// method can be used for testing to see if any given group was configured.
+func (f *Flags) ListenFlags() ListenFlags {
+	if p := f.groups[Listen]; p != nil {
+		lf := p.(*ListenFlags)
+		n := *lf
+		if len(lf.Addrs) == 0 {
+			n.Addrs = ListenAddrs{{n.protocol.String(),
+				n.addresses.validator.String()}}
+			return n
+		}
+		n.Addrs = make(ListenAddrs, len(lf.Addrs))
+		copy(n.Addrs, lf.Addrs)
+		return n
+	}
+	return ListenFlags{}
+}
+
+// PermissionsFlags returns a copy of the Permissions flag group stored in
+// Flags. This copy will contain default values if the Permissions flag group
+// was not specified when CreateAndRegister was called. The HasGroup method can
+// be used for testing to see if any given group was configured.
+func (f *Flags) PermissionsFlags() PermissionsFlags {
+	if p := f.groups[Permissions]; p != nil {
+		return *(p.(*PermissionsFlags))
+	}
+	return PermissionsFlags{}
+}
+
+// HasGroup returns group if the supplied FlagGroup has been created
+// for these Flags.
+func (f *Flags) HasGroup(group FlagGroup) bool {
+	_, present := f.groups[group]
+	return present
+}
+
+// Args returns the unparsed args, as per flag.Args.
+func (f *Flags) Args() []string {
+	return f.FlagSet.Args()
+}
+
+// Parse parses the supplied args, as per flag.Parse.  The config can optionally
+// specify flag overrides.
+func (f *Flags) Parse(args []string, cfg map[string]string) error {
+	// Refresh any defaults that may have changed.
+	refreshDefaults(f)
+
+	// TODO(cnicolaou): implement a single env var 'VANADIUM_OPTS'
+	// that can be used to specify any command line.
+	if err := f.FlagSet.Parse(args); err != nil {
+		return err
+	}
+	for k, v := range cfg {
+		if f.FlagSet.Lookup(k) != nil {
+			f.FlagSet.Set(k, v)
+		}
+	}
+
+	hasrt := f.groups[Runtime] != nil
+	if hasrt {
+		runtime := f.groups[Runtime].(*RuntimeFlags)
+		if runtime.namespaceRootsFlag.isSet {
+			// command line overrides the environment.
+			runtime.NamespaceRoots = runtime.namespaceRootsFlag.roots
+		} else {
+			// we have a default value for the command line, which
+			// is only used if the environment variables have not been
+			// supplied.
+			if len(runtime.NamespaceRoots) == 0 {
+				runtime.NamespaceRoots = runtime.namespaceRootsFlag.roots
+			}
+		}
+	}
+	return nil
+}
diff --git a/lib/flags/flags_test.go b/lib/flags/flags_test.go
new file mode 100644
index 0000000..fd3ac19
--- /dev/null
+++ b/lib/flags/flags_test.go
@@ -0,0 +1,408 @@
+// 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 flags_test
+
+import (
+	"flag"
+	"io/ioutil"
+	"os"
+	"reflect"
+	"testing"
+
+	"v.io/x/ref"
+	"v.io/x/ref/lib/flags"
+)
+
+func TestFlags(t *testing.T) {
+	fs := flag.NewFlagSet("test", flag.ContinueOnError)
+	if flags.CreateAndRegister(fs) != nil {
+		t.Fatalf("should have returned a nil value")
+	}
+	fl := flags.CreateAndRegister(fs, flags.Runtime)
+	if fl == nil {
+		t.Errorf("should have succeeded")
+	}
+	creds := "creddir"
+	roots := []string{"ab:cd:ef"}
+	args := []string{"--v23.credentials=" + creds, "--v23.namespace.root=" + roots[0]}
+	fl.Parse(args, nil)
+	rtf := fl.RuntimeFlags()
+	if got, want := rtf.NamespaceRoots, roots; !reflect.DeepEqual(got, want) {
+		t.Errorf("got %v, want %v", got, want)
+	}
+	if got, want := rtf.Credentials, creds; !reflect.DeepEqual(got, want) {
+		t.Errorf("got %v, want %v", got, want)
+	}
+	if got, want := fl.HasGroup(flags.Listen), false; got != want {
+		t.Errorf("got %t, want %t", got, want)
+	}
+	// Make sure we have a deep copy.
+	rtf.NamespaceRoots[0] = "oooh"
+	rtf = fl.RuntimeFlags()
+	if got, want := rtf.NamespaceRoots, roots; !reflect.DeepEqual(got, want) {
+		t.Errorf("got %v, want %v", got, want)
+	}
+}
+
+func TestPermissionsFlags(t *testing.T) {
+	fs := flag.NewFlagSet("test", flag.ContinueOnError)
+	fl := flags.CreateAndRegister(fs, flags.Runtime, flags.Permissions)
+	args := []string{"--v23.permissions.file=runtime:foo.json", "--v23.permissions.file=bar:bar.json", "--v23.permissions.file=baz:bar:baz.json"}
+	fl.Parse(args, nil)
+	permsf := fl.PermissionsFlags()
+
+	if got, want := permsf.PermissionsFile("runtime"), "foo.json"; got != want {
+		t.Errorf("got %q, want %q", got, want)
+	}
+	if got, want := permsf.PermissionsFile("bar"), "bar.json"; got != want {
+		t.Errorf("got %q, want %q", got, want)
+	}
+	if got, want := permsf.PermissionsFile("wombat"), ""; got != want {
+		t.Errorf("got %q, want %q", got, want)
+	}
+	if got, want := permsf.PermissionsFile("baz"), "bar:baz.json"; got != want {
+		t.Errorf("got %q, want %q", got, want)
+	}
+}
+
+func TestPermissionsLiteralFlags(t *testing.T) {
+	fs := flag.NewFlagSet("test", flag.ContinueOnError)
+	fl := flags.CreateAndRegister(fs, flags.Runtime, flags.Permissions)
+	args := []string{"--v23.permissions.literal=hedgehog"}
+	fl.Parse(args, nil)
+	permsf := fl.PermissionsFlags()
+
+	if got, want := permsf.PermissionsFile("runtime"), ""; got != want {
+		t.Errorf("got %q, want %q", got, want)
+	}
+	if got, want := permsf.PermissionsLiteral(), "hedgehog"; got != want {
+		t.Errorf("got %q, want %q", got, want)
+	}
+}
+
+func TestPermissionsLiteralBoth(t *testing.T) {
+	fs := flag.NewFlagSet("test", flag.ContinueOnError)
+	fl := flags.CreateAndRegister(fs, flags.Runtime, flags.Permissions)
+	args := []string{"--v23.permissions.file=runtime:foo.json", "--v23.permissions.literal=hedgehog"}
+	fl.Parse(args, nil)
+	permsf := fl.PermissionsFlags()
+
+	if got, want := permsf.PermissionsFile("runtime"), "foo.json"; got != want {
+		t.Errorf("got %q, want %q", got, want)
+	}
+	if got, want := permsf.PermissionsLiteral(), "hedgehog"; got != want {
+		t.Errorf("got %q, want %q", got, want)
+	}
+}
+
+func TestFlagError(t *testing.T) {
+	fs := flag.NewFlagSet("test", flag.ContinueOnError)
+	fs.SetOutput(ioutil.Discard)
+	fl := flags.CreateAndRegister(fs, flags.Runtime)
+	addr := "192.168.10.1:0"
+	args := []string{"--xxxv23.tcp.address=" + addr, "not an arg"}
+	err := fl.Parse(args, nil)
+	if err == nil {
+		t.Fatalf("expected this to fail!")
+	}
+	if got, want := len(fl.Args()), 1; got != want {
+		t.Errorf("got %d, want %d [args: %v]", got, want, fl.Args())
+	}
+
+	fs = flag.NewFlagSet("test", flag.ContinueOnError)
+	fl = flags.CreateAndRegister(fs, flags.Permissions)
+	args = []string{"--v23.permissions.file=noname"}
+	err = fl.Parse(args, nil)
+	if err == nil {
+		t.Fatalf("expected this to fail!")
+	}
+}
+
+func TestFlagsGroups(t *testing.T) {
+	fl := flags.CreateAndRegister(flag.NewFlagSet("test", flag.ContinueOnError), flags.Runtime, flags.Listen)
+	if got, want := fl.HasGroup(flags.Listen), true; got != want {
+		t.Errorf("got %t, want %t", got, want)
+	}
+	addr := "192.168.10.1:0"
+	roots := []string{"ab:cd:ef"}
+	args := []string{"--v23.tcp.address=" + addr, "--v23.namespace.root=" + roots[0]}
+	fl.Parse(args, nil)
+	lf := fl.ListenFlags()
+	if got, want := fl.RuntimeFlags().NamespaceRoots, roots; !reflect.DeepEqual(got, want) {
+		t.Errorf("got %v, want %v", got, want)
+	}
+	if got, want := lf.Addrs[0].Address, addr; got != want {
+		t.Errorf("got %q, want %q", got, want)
+	}
+}
+
+const (
+	rootEnvVar  = ref.EnvNamespacePrefix
+	rootEnvVar0 = ref.EnvNamespacePrefix + "0"
+)
+
+func TestEnvVars(t *testing.T) {
+	oldcreds := os.Getenv(ref.EnvCredentials)
+	defer os.Setenv(ref.EnvCredentials, oldcreds)
+
+	oldroot := os.Getenv(rootEnvVar)
+	oldroot0 := os.Getenv(rootEnvVar0)
+	defer os.Setenv(rootEnvVar, oldroot)
+	defer os.Setenv(rootEnvVar0, oldroot0)
+
+	os.Setenv(ref.EnvCredentials, "bar")
+	fl := flags.CreateAndRegister(flag.NewFlagSet("test", flag.ContinueOnError), flags.Runtime)
+	if err := fl.Parse([]string{}, nil); err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	rtf := fl.RuntimeFlags()
+	if got, want := rtf.Credentials, "bar"; got != want {
+		t.Errorf("got %q, want %q", got, want)
+	}
+
+	if err := fl.Parse([]string{"--v23.credentials=baz"}, nil); err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	rtf = fl.RuntimeFlags()
+	if got, want := rtf.Credentials, "baz"; got != want {
+		t.Errorf("got %q, want %q", got, want)
+	}
+
+	os.Setenv(rootEnvVar, "a:1")
+	os.Setenv(rootEnvVar0, "a:2")
+	fl = flags.CreateAndRegister(flag.NewFlagSet("test", flag.ContinueOnError), flags.Runtime)
+	if err := fl.Parse([]string{}, nil); err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	rtf = fl.RuntimeFlags()
+	if got, want := rtf.NamespaceRoots, []string{"a:1", "a:2"}; !reflect.DeepEqual(got, want) {
+		t.Errorf("got %q, want %q", got, want)
+	}
+	if err := fl.Parse([]string{"--v23.namespace.root=b:1", "--v23.namespace.root=b:2", "--v23.namespace.root=b:3", "--v23.credentials=b:4"}, nil); err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	rtf = fl.RuntimeFlags()
+	if got, want := rtf.NamespaceRoots, []string{"b:1", "b:2", "b:3"}; !reflect.DeepEqual(got, want) {
+		t.Errorf("got %q, want %q", got, want)
+	}
+	if got, want := rtf.Credentials, "b:4"; got != want {
+		t.Errorf("got %q, want %q", got, want)
+	}
+}
+
+func TestDefaults(t *testing.T) {
+	oldroot := os.Getenv(rootEnvVar)
+	oldroot0 := os.Getenv(rootEnvVar0)
+	defer os.Setenv(rootEnvVar, oldroot)
+	defer os.Setenv(rootEnvVar0, oldroot0)
+
+	os.Setenv(rootEnvVar, "")
+	os.Setenv(rootEnvVar0, "")
+
+	fl := flags.CreateAndRegister(flag.NewFlagSet("test", flag.ContinueOnError), flags.Runtime, flags.Permissions)
+	if err := fl.Parse([]string{}, nil); err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	rtf := fl.RuntimeFlags()
+	if got, want := rtf.NamespaceRoots, []string{"/(dev.v.io/role/vprod/service/mounttabled)@ns.dev.v.io:8101"}; !reflect.DeepEqual(got, want) {
+		t.Errorf("got %q, want %q", got, want)
+	}
+	permsf := fl.PermissionsFlags()
+	if got, want := permsf.PermissionsFile(""), ""; got != want {
+		t.Errorf("got %q, want %q", got, want)
+	}
+}
+
+func TestListenFlags(t *testing.T) {
+	fl := flags.CreateAndRegister(flag.NewFlagSet("test", flag.ContinueOnError), flags.Listen)
+	if err := fl.Parse([]string{}, nil); err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	lf := fl.ListenFlags()
+	if got, want := len(lf.Addrs), 1; got != want {
+		t.Errorf("got %d, want %d", got, want)
+	}
+
+	// Test the default protocol and address is "wsh" and ":0".
+	def := struct{ Protocol, Address string }{"wsh", ":0"}
+	if got, want := lf.Addrs[0], def; !reflect.DeepEqual(got, want) {
+		t.Errorf("got %v, want %v", got, want)
+	}
+
+	fl = flags.CreateAndRegister(flag.NewFlagSet("test", flag.ContinueOnError), flags.Listen)
+	if err := fl.Parse([]string{
+		"--v23.tcp.address=172.0.0.1:10", // Will default to protocol "wsh".
+		"--v23.tcp.protocol=tcp", "--v23.tcp.address=127.0.0.10:34",
+		"--v23.tcp.protocol=ws4", "--v23.tcp.address=127.0.0.10:44",
+		"--v23.tcp.protocol=tcp6", "--v23.tcp.address=172.0.0.100:100"}, nil); err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	lf = fl.ListenFlags()
+	if got, want := len(lf.Addrs), 4; got != want {
+		t.Fatalf("got %d, want %d", got, want)
+	}
+	for i, p := range []string{"wsh", "tcp", "ws4", "tcp6"} {
+		if got, want := lf.Addrs[i].Protocol, p; got != want {
+			t.Errorf("got %q, want %q", got, want)
+		}
+	}
+	for i, p := range []string{"172.0.0.1:10", "127.0.0.10:34", "127.0.0.10:44", "172.0.0.100:100"} {
+		if got, want := lf.Addrs[i].Address, p; got != want {
+			t.Errorf("got %q, want %q", got, want)
+		}
+	}
+}
+
+func TestDuplicateFlags(t *testing.T) {
+	fl := flags.CreateAndRegister(flag.NewFlagSet("test", flag.ContinueOnError), flags.Listen)
+	if err := fl.Parse([]string{
+		"--v23.tcp.address=172.0.0.1:10", "--v23.tcp.address=172.0.0.1:10", "--v23.tcp.address=172.0.0.1:34",
+		"--v23.tcp.protocol=tcp", "--v23.tcp.address=172.0.0.1:10", "--v23.tcp.address=172.0.0.1:10", "--v23.tcp.address=172.0.0.1:34",
+		"--v23.tcp.protocol=ws", "--v23.tcp.address=172.0.0.1:10", "--v23.tcp.address=172.0.0.1:34", "--v23.tcp.address=172.0.0.1:34"}, nil); err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	lf := fl.ListenFlags()
+	if got, want := len(lf.Addrs), 6; got != want {
+		t.Errorf("got %d, want %d", got, want)
+	}
+	expected := flags.ListenAddrs{
+		{"wsh", "172.0.0.1:10"},
+		{"wsh", "172.0.0.1:34"},
+		{"tcp", "172.0.0.1:10"},
+		{"tcp", "172.0.0.1:34"},
+		{"ws", "172.0.0.1:10"},
+		{"ws", "172.0.0.1:34"},
+	}
+	if got, want := lf.Addrs, expected; !reflect.DeepEqual(got, want) {
+		t.Fatalf("got %#v, want %#v", got, want)
+	}
+	if err := fl.Parse([]string{
+		"--v23.tcp.address=172.0.0.1:10", "--v23.tcp.address=172.0.0.1:10", "--v23.tcp.address=172.0.0.1:34",
+		"--v23.tcp.protocol=tcp", "--v23.tcp.address=172.0.0.1:10", "--v23.tcp.address=127.0.0.1:34", "--v23.tcp.address=127.0.0.1:34",
+		"--v23.tcp.protocol=ws", "--v23.tcp.address=172.0.0.1:10", "--v23.tcp.address=127.0.0.1:34", "--v23.tcp.address=127.0.0.1:34"}, nil); err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	if got, want := len(lf.Addrs), 6; got != want {
+		t.Errorf("got %d, want %d", got, want)
+	}
+	if got, want := lf.Addrs, expected; !reflect.DeepEqual(got, want) {
+		t.Fatalf("got %#v, want %#v", got, want)
+	}
+
+	fl = flags.CreateAndRegister(flag.NewFlagSet("test", flag.ContinueOnError), flags.Runtime)
+
+	if err := fl.Parse([]string{"--v23.namespace.root=ab", "--v23.namespace.root=xy", "--v23.namespace.root=ab"}, nil); err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+
+	rf := fl.RuntimeFlags()
+	if got, want := len(rf.NamespaceRoots), 2; got != want {
+		t.Errorf("got %d, want %d", got, want)
+	}
+	if got, want := rf.NamespaceRoots, []string{"ab", "xy"}; !reflect.DeepEqual(got, want) {
+		t.Fatalf("got %#v, want %#v", got, want)
+	}
+}
+
+func TestConfig(t *testing.T) {
+	fs := flag.NewFlagSet("test", flag.ContinueOnError)
+	var testFlag1, testFlag2 string
+	fs.StringVar(&testFlag1, "test_flag1", "default1", "")
+	fs.StringVar(&testFlag2, "test_flag2", "default2", "")
+	fl := flags.CreateAndRegister(fs, flags.Runtime)
+	args := []string{
+		"--v23.namespace.root=argRoot1",
+		"--v23.namespace.root=argRoot2",
+		"--v23.vtrace.cache-size=1234",
+	}
+	config := map[string]string{
+		"v23.namespace.root":       "configRoot",
+		"v23.credentials":          "configCreds",
+		"v23.vtrace.cache-size":    "4321",
+		"test_flag1":               "test value",
+		"flag.that.does.not.exist": "some value",
+	}
+	if err := fl.Parse(args, config); err != nil {
+		t.Errorf("Parse(%v, %v) failed: %v", args, config, err)
+	}
+	rtf := fl.RuntimeFlags()
+	if got, want := rtf.NamespaceRoots, []string{"argRoot1", "argRoot2", "configRoot"}; !reflect.DeepEqual(got, want) {
+		t.Errorf("Namespace roots: got %v, want %v", got, want)
+	}
+	if got, want := rtf.Credentials, "configCreds"; got != want {
+		t.Errorf("Credentials: got %v, want %v", got, want)
+	}
+	if got, want := testFlag1, "test value"; got != want {
+		t.Errorf("Test flag 1: got %v, want %v", got, want)
+	}
+	if got, want := testFlag2, "default2"; got != want {
+		t.Errorf("Test flag 2: got %v, want %v", got, want)
+	}
+	if got, want := rtf.Vtrace.CacheSize, 4321; got != want {
+		t.Errorf("Test flag 2: got %v, want %v", got, want)
+	}
+}
+
+func TestRefreshDefaults(t *testing.T) {
+	orig := flags.DefaultNamespaceRoot()
+	defer flags.SetDefaultNamespaceRoot(orig)
+	defer flags.SetDefaultHostPort(":0")
+	defer flags.SetDefaultProtocol("wsh")
+
+	nsRoot := "/127.0.0.1:8101"
+	hostPort := "128.0.0.1:11"
+	fs := flag.NewFlagSet("test", flag.ContinueOnError)
+	fl := flags.CreateAndRegister(fs, flags.Runtime, flags.Listen)
+	// It's possible to set defaults after CreateAndRegister, but before Parse.
+	flags.SetDefaultNamespaceRoot(nsRoot)
+	flags.SetDefaultHostPort(hostPort)
+	flags.SetDefaultProtocol("tcp6")
+	fl.Parse([]string{}, nil)
+	rtf := fl.RuntimeFlags()
+	if got, want := rtf.NamespaceRoots, []string{nsRoot}; !reflect.DeepEqual(got, want) {
+		t.Errorf("got %v, want %v", got, want)
+	}
+	lf := fl.ListenFlags()
+	want := flags.ListenAddrs{struct{ Protocol, Address string }{"tcp6", hostPort}}
+	if got := lf.Addrs; !reflect.DeepEqual(got, want) {
+		t.Errorf("got %v, want %v", got, want)
+	}
+	changed := "/128.1.1.1:1"
+	flags.SetDefaultNamespaceRoot(changed)
+	fl.Parse([]string{}, nil)
+	rtf = fl.RuntimeFlags()
+	if got, want := rtf.NamespaceRoots, []string{changed}; !reflect.DeepEqual(got, want) {
+		t.Errorf("got %v, want %v", got, want)
+	}
+}
+
+func TestRefreshAlreadySetDefaults(t *testing.T) {
+	orig := flags.DefaultNamespaceRoot()
+	defer flags.SetDefaultNamespaceRoot(orig)
+	defer flags.SetDefaultHostPort(":0")
+	defer flags.SetDefaultProtocol("wsh")
+
+	fs := flag.NewFlagSet("test", flag.ContinueOnError)
+	fl := flags.CreateAndRegister(fs, flags.Runtime, flags.Listen)
+	nsRoot := "/127.0.1.1:10"
+	hostPort := "127.0.0.1:10"
+	fl.Parse([]string{"--v23.namespace.root", nsRoot, "--v23.tcp.address", hostPort}, nil)
+	rtf := fl.RuntimeFlags()
+	if got, want := rtf.NamespaceRoots, []string{nsRoot}; !reflect.DeepEqual(got, want) {
+		t.Errorf("got %v, want %v", got, want)
+	}
+	flags.SetDefaultNamespaceRoot("/128.1.1.1:2")
+	flags.SetDefaultHostPort("128.0.0.1:11")
+	fl.Parse([]string{}, nil)
+	rtf = fl.RuntimeFlags()
+	if got, want := rtf.NamespaceRoots, []string{nsRoot}; !reflect.DeepEqual(got, want) {
+		t.Errorf("got %v, want %v", got, want)
+	}
+	lf := fl.ListenFlags()
+	want := flags.ListenAddrs{struct{ Protocol, Address string }{"wsh", hostPort}}
+	if got := lf.Addrs; !reflect.DeepEqual(got, want) {
+		t.Errorf("got %v, want %v", got, want)
+	}
+}
diff --git a/lib/flags/listen.go b/lib/flags/listen.go
new file mode 100644
index 0000000..da8bb90
--- /dev/null
+++ b/lib/flags/listen.go
@@ -0,0 +1,141 @@
+// 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 flags
+
+import (
+	"net"
+	"strconv"
+
+	"v.io/v23/verror"
+)
+
+var (
+	errNotTCP           = verror.Register(pkgPath+".errNotTCP", verror.NoRetry, "{1:}{2:} {3} is not a tcp protocol{:_}")
+	errCantParsePort    = verror.Register(pkgPath+".errCantParsePort", verror.NoRetry, "{1:}{2:} failed to parse port number from {3}{:_}")
+	errNeedIPOrHostName = verror.Register(pkgPath+".errNeedIPOrHostName", verror.NoRetry, "{1:}{2:} {3} is neither an IP address nor a host name{:_}")
+	errBadIP            = verror.Register(pkgPath+".errBadIP", verror.NoRetry, "{1:}{2:} failed to parse {3} as an IP address{:_}")
+)
+
+// TCPProtocolFlag implements flag.Value to provide validation of the command
+// line values passed to it: tcp, tcp4, tcp6, ws, ws4, ws6, wsh, wsh4, and wsh6
+// being the only allowed values.
+type TCPProtocolFlag struct {
+	Protocol string
+}
+
+// Implements flag.Value.Get
+func (t TCPProtocolFlag) Get() interface{} {
+	return t.Protocol
+}
+
+// Implements flag.Value.Set
+func (t *TCPProtocolFlag) Set(s string) error {
+	switch s {
+	case "tcp", "tcp4", "tcp6", "ws", "ws4", "ws6", "wsh", "wsh4", "wsh6":
+		t.Protocol = s
+		return nil
+	default:
+		return verror.New(errNotTCP, nil, s)
+	}
+
+}
+
+// Implements flag.Value.String
+func (t TCPProtocolFlag) String() string {
+	return t.Protocol
+}
+
+// IPHostPortFlag implements flag.Value to provide validation of the
+// command line value it is set to. The allowed format is <host>:<port> in
+// ip4 and ip6 formats. The host may be specified as a hostname or as an IP
+// address (v4 or v6). If a hostname is used and it resolves to multiple IP
+// addresses then all of those addresses are stored in IPHostPort.
+type IPHostPortFlag struct {
+	Address string
+	Host    string
+	IP      []*net.IPAddr
+	Port    string
+}
+
+// Implements flag.Value.Get
+func (ip IPHostPortFlag) Get() interface{} {
+	return ip.String()
+}
+
+// Implements flag.Value.Set
+func (ip *IPHostPortFlag) Set(s string) error {
+	if len(s) == 0 {
+		ip.Address, ip.Port, ip.Host = "", "", ""
+		return nil
+	}
+	ip.Address = s
+	host, port, err := net.SplitHostPort(s)
+	if err != nil {
+		// no port number in s.
+		host = s
+		ip.Port = "0"
+	} else {
+		// have a port in s.
+		if _, err := strconv.ParseUint(port, 10, 16); err != nil {
+			return verror.New(errCantParsePort, nil, s)
+		}
+		ip.Port = port
+	}
+	// if len(host) == 0 then we have no host, just a port.
+	if len(host) > 0 {
+		if addr := net.ParseIP(host); addr == nil {
+			// Could be a hostname.
+			addrs, err := net.LookupIP(host)
+			if err != nil {
+				return verror.New(errNeedIPOrHostName, nil, host, err)
+			}
+			for _, a := range addrs {
+				ip.IP = append(ip.IP, &net.IPAddr{IP: a})
+			}
+			ip.Host = host
+		} else {
+			ip.IP = []*net.IPAddr{{IP: addr}}
+		}
+		return nil
+	}
+	return nil
+}
+
+// Implements flag.Value.String
+func (ip IPHostPortFlag) String() string {
+	if len(ip.Address) == 0 && len(ip.Port) == 0 {
+		return ""
+	}
+	host := ip.Host
+	if len(ip.Host) == 0 && ip.IP != nil && len(ip.IP) > 0 {
+		// We don't have a hostname, so there should be at most one IP address.
+		host = ip.IP[0].String()
+	}
+	return net.JoinHostPort(host, ip.Port)
+}
+
+// IPFlag implements flag.Value in order to provide validation of
+// IP addresses in the flag package.
+type IPFlag struct{ net.IP }
+
+// Implements flag.Value.Get
+func (ip IPFlag) Get() interface{} {
+	return ip.IP
+}
+
+// Implements flag.Value.Set
+func (ip *IPFlag) Set(s string) error {
+	t := net.ParseIP(s)
+	if t == nil {
+		return verror.New(errBadIP, nil, s)
+	}
+	ip.IP = t
+	return nil
+}
+
+// Implements flag.Value.String
+func (ip IPFlag) String() string {
+	return ip.IP.String()
+}
diff --git a/lib/flags/listen_test.go b/lib/flags/listen_test.go
new file mode 100644
index 0000000..e5c23c8
--- /dev/null
+++ b/lib/flags/listen_test.go
@@ -0,0 +1,95 @@
+// 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 flags_test
+
+import (
+	"net"
+	"reflect"
+	"strings"
+	"testing"
+
+	"v.io/x/ref/lib/flags"
+)
+
+func TestIPFlag(t *testing.T) {
+	ip := &flags.IPFlag{}
+	if err := ip.Set("172.16.1.22"); err != nil {
+		t.Errorf("unexpected error %s", err)
+	}
+	if got, want := ip.IP, net.ParseIP("172.16.1.22"); !got.Equal(want) {
+		t.Errorf("got %s, expected %s", got, want)
+	}
+	if err := ip.Set("172.16"); err == nil || !strings.Contains(err.Error(), "failed to parse 172.16 as an IP address") {
+		t.Errorf("expected error %v", err)
+	}
+}
+
+func TestTCPFlag(t *testing.T) {
+	tcp := &flags.TCPProtocolFlag{}
+	if err := tcp.Set("tcp6"); err != nil {
+		t.Errorf("unexpected error %s", err)
+	}
+	if got, want := tcp.Protocol, "tcp6"; got != want {
+		t.Errorf("got %s, expected %s", got, want)
+	}
+	if err := tcp.Set("foo"); err == nil || !strings.Contains(err.Error(), "not a tcp protocol") {
+		t.Errorf("expected error %v", err)
+	}
+}
+
+func TestIPHostPortFlag(t *testing.T) {
+	lh := []*net.IPAddr{{IP: net.ParseIP("127.0.0.1")}}
+	ip6 := []*net.IPAddr{{IP: net.ParseIP("FE80:0000:0000:0000:0202:B3FF:FE1E:8329")}}
+	cases := []struct {
+		input string
+		want  flags.IPHostPortFlag
+		str   string
+	}{
+		{"", flags.IPHostPortFlag{Port: ""}, ""},
+		{":0", flags.IPHostPortFlag{Port: "0"}, ":0"},
+		{":22", flags.IPHostPortFlag{Port: "22"}, ":22"},
+		{"127.0.0.1", flags.IPHostPortFlag{IP: lh, Port: "0"}, "127.0.0.1:0"},
+		{"127.0.0.1:10", flags.IPHostPortFlag{IP: lh, Port: "10"}, "127.0.0.1:10"},
+		{"[]:0", flags.IPHostPortFlag{Port: "0"}, ":0"},
+		{"[FE80:0000:0000:0000:0202:B3FF:FE1E:8329]:100", flags.IPHostPortFlag{IP: ip6, Port: "100"}, "[fe80::202:b3ff:fe1e:8329]:100"},
+	}
+	for _, c := range cases {
+		got, want := &flags.IPHostPortFlag{}, &c.want
+		c.want.Address = c.input
+		if err := got.Set(c.input); err != nil || !reflect.DeepEqual(got, want) {
+			if err != nil {
+				t.Errorf("%q: unexpected error %s", c.input, err)
+			} else {
+				t.Errorf("%q: got %#v, want %#v", c.input, got, want)
+			}
+		}
+		if got.String() != c.str {
+			t.Errorf("%q: got %#v, want %#v", c.input, got.String(), c.str)
+		}
+	}
+
+	host := &flags.IPHostPortFlag{}
+	if err := host.Set("localhost:122"); err != nil {
+		t.Errorf("unexpected error: %s", err)
+	}
+	if len(host.IP) == 0 {
+		t.Errorf("localhost should have resolved to at least one address")
+	}
+	if got, want := host.Port, "122"; got != want {
+		t.Errorf("got %q, want %q", got, want)
+	}
+	if got, want := host.String(), "localhost:122"; got != want {
+		t.Errorf("got %q, want %q", got, want)
+	}
+
+	for _, s := range []string{
+		":", ":59999999", "nohost.invalid", "nohost.invalid:"} {
+		f := &flags.IPHostPortFlag{}
+		if err := f.Set(s); err == nil {
+			t.Errorf("expected an error for %q", s)
+		}
+	}
+
+}
diff --git a/lib/flags/main.go b/lib/flags/main.go
new file mode 100644
index 0000000..f2a7fc6
--- /dev/null
+++ b/lib/flags/main.go
@@ -0,0 +1,34 @@
+// 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 ignore
+
+package main
+
+import (
+	"flag"
+	"fmt"
+	"os"
+
+	"v.io/x/ref/lib/flags"
+)
+
+func main() {
+	fl := flags.CreateAndRegister(flag.CommandLine, flags.Runtime, flags.AccessList, flags.Listen)
+	flag.PrintDefaults()
+	fmt.Printf("Args: %v\n", os.Args)
+	if err := fl.Parse(os.Args[1:], nil); err != nil {
+		fmt.Println("ERROR: %s", err)
+		return
+	}
+	rtf := fl.RuntimeFlags()
+	fmt.Printf("Runtime: Credentials: %s\n", rtf.Credentials)
+	fmt.Printf("Runtime: Namespace Roots: %s\n", rtf.NamespaceRoots)
+	lf := fl.ListenFlags()
+	for _, a := range lf.Addrs {
+		fmt.Printf("Listen: Protocol %q, Address %q\n", a.Protocol, a.Address)
+	}
+	fmt.Printf("Listen: Proxy %q\n", lf.ListenProxy)
+	fmt.Printf("AccessList: %v\n", fl.AccessListFlags())
+}
diff --git a/lib/glob/glob.go b/lib/glob/glob.go
new file mode 100644
index 0000000..6bbb111
--- /dev/null
+++ b/lib/glob/glob.go
@@ -0,0 +1,119 @@
+// 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 glob defines a globbing syntax and implements matching routines.
+//
+// Globs match a slash separated series of glob expressions.
+//
+//   // Patterns:
+//   term ['/' term]*
+//   term:
+//   '*'         matches any sequence of non-Separator characters
+//   '?'         matches any single non-Separator character
+//   '[' [ '^' ] { character-range } ']'
+//   // Character classes (must be non-empty):
+//   c           matches character c (c != '*', '?', '\\', '[', '/')
+//   '\\' c      matches character c
+//   // Character-ranges:
+//   c           matches character c (c != '\\', '-', ']')
+//   '\\' c      matches character c
+//   lo '-' hi   matches character c for lo <= c <= hi
+//
+// This package is DEPRECATED. Use v.io/v23/glob instead.
+package glob
+
+import (
+	"v.io/v23/glob"
+)
+
+// Glob represents a slash separated path glob pattern.
+// This type is DEPRECATED. Use v.io/v23/glob.Glob instead.
+type Glob struct {
+	*glob.Glob
+}
+
+// Parse returns a new Glob.
+// This function is DEPRECATED.
+func Parse(pattern string) (*Glob, error) {
+	g, err := glob.Parse(pattern)
+	return &Glob{g}, err
+}
+
+// Tail returns the suffix of g starting at the second element.
+// This method is DEPRECATED.
+func (g *Glob) Tail() *Glob {
+	return &Glob{g.Glob.Tail()}
+}
+
+// Finished returns true if the pattern cannot match anything.
+// This method is DEPRECATED.
+func (g *Glob) Finished() bool {
+	return g.Empty()
+}
+
+// Split returns the suffix of g starting at the path element corresponding to
+// start.
+// This method is DEPRECATED.
+func (g *Glob) Split(start int) *Glob {
+	suffix := g
+	for i := start - 1; i >= 0; i-- {
+		suffix = suffix.Tail()
+	}
+	return suffix
+}
+
+// MatchInitialSegment tries to match segment against the initial element of g.
+// Returns:
+// matched, a boolean indicating whether the match was successful;
+// exact, a boolean indicating whether segment matched a fixed string pattern;
+// remainder, a Glob representing the unmatched remainder of g.
+// This method is DEPRECATED.
+func (g *Glob) MatchInitialSegment(segment string) (matched bool, exact bool, remainder *Glob) {
+	m := g.Head()
+	matched = m.Match(segment)
+	_, exact = m.FixedPrefix()
+	remainder = g.Tail()
+	return
+}
+
+// PartialMatch tries matching elems against part of a glob pattern.
+// Returns:
+// matched, a boolean indicating whether each element e_i of elems matches the
+// (start + i)th element of the glob pattern;
+// exact, a boolean indicating whether elems matched a fixed string pattern.
+// <path> is considered an exact match for pattern <path>/...;
+// remainder, a Glob representing the unmatched remainder of g. remainder will
+// be empty if the pattern is completely matched.
+//
+// Note that if the glob is recursive elems can have more elements then
+// the glob pattern and still get a true result.
+// This method is DEPRECATED.
+func (g *Glob) PartialMatch(start int, elems []string) (matched bool, exact bool, remainder *Glob) {
+	g = g.Split(start)
+	allExact := true
+	for i := 0; i < len(elems); i++ {
+		var matched, exact bool
+		if matched, exact, g = g.MatchInitialSegment(elems[i]); !matched {
+			return false, false, nil
+		} else if !exact {
+			allExact = false
+		}
+	}
+	return true, allExact, g
+}
+
+// SplitFixedElements returns the part of the glob pattern that contains only
+// fixed elements, and the glob that follows it.
+// This method is DEPRECATED.
+func (g *Glob) SplitFixedElements() ([]string, *Glob) {
+	prefix, left := g.Glob.SplitFixedElements()
+	return prefix, &Glob{left}
+}
+
+// SplitFixedPrefix returns the part of the glob pattern that contains only
+// fixed elements, and the glob that follows it.
+// This method is DEPRECATED.
+func (g *Glob) SplitFixedPrefix() ([]string, *Glob) {
+	return g.SplitFixedElements()
+}
diff --git a/lib/glob/glob_test.go b/lib/glob/glob_test.go
new file mode 100644
index 0000000..e75be4f
--- /dev/null
+++ b/lib/glob/glob_test.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 glob
+
+import (
+	"testing"
+)
+
+func same(a, b []string) bool {
+	if len(a) != len(b) {
+		return false
+	}
+	for i := range a {
+		if a[i] != b[i] {
+			return false
+		}
+	}
+	return true
+}
+
+func TestStripFixedElements(t *testing.T) {
+	tests := []struct {
+		pattern string
+		fixed   []string
+		rest    string
+	}{
+		{"*", nil, "*"},
+		{"a/b/c/*", []string{"a", "b", "c"}, "*"},
+		{"a/b/*/...", []string{"a", "b"}, "*/..."},
+		{"a/b/c/...", []string{"a", "b", "c"}, "..."},
+		{"a/the\\?rain.in\\*spain", []string{"a", "the?rain.in*spain"}, ""},
+	}
+	for _, test := range tests {
+		g, err := Parse(test.pattern)
+		if err != nil {
+			t.Fatalf("parsing %q: %q", test.pattern, err.Error())
+		}
+		if f, ng := g.SplitFixedElements(); !same(f, test.fixed) || test.rest != ng.String() {
+			t.Fatalf("SplitFixedElements(%q) got %q,%q, expected %q,%q", test.pattern, f, ng.String(), test.fixed, test.rest)
+		}
+	}
+}
+
+func TestMatch(t *testing.T) {
+	tests := []struct {
+		pattern string
+		name    string
+		matched bool
+	}{
+		{"...", "", true},
+		{"***", "", true},
+		{"...", "a", true},
+		{"***", "a", true},
+		{"a", "", false},
+		{"a", "a", true},
+		{"a", "b", false},
+		{"a*", "a", true},
+		{"a*", "b", false},
+		{"a*b", "ab", true},
+		{"a*b", "afoob", true},
+		{"a\\*", "a", false},
+		{"a\\*", "a*", true},
+		{"\\\\", "\\", true},
+		{"?", "?", true},
+		{"?", "a", true},
+		{"?", "", false},
+		{"*?", "", false},
+		{"*?", "a", true},
+		{"*?", "ab", true},
+		{"*?", "abv", true},
+		{"[abc]", "a", true},
+		{"[abc]", "b", true},
+		{"[abc]", "c", true},
+		{"[abc]", "d", false},
+		{"[a-c]", "a", true},
+		{"[a-c]", "b", true},
+		{"[a-c]", "c", true},
+		{"[a-c]", "d", false},
+		{"\\[abc]", "a", false},
+		{"\\[abc]", "[abc]", true},
+		{"a/*", "a", true},
+		{"a/*", "b", false},
+		{"a/...", "a", true},
+		{"a/...", "b", false},
+	}
+	for i, test := range tests {
+		g, err := Parse(test.pattern)
+		if err != nil {
+			t.Errorf("unexpected parsing error for %q (#%d): %v", test.pattern, i, err)
+			continue
+		}
+		if matched := g.Head().Match(test.name); matched != test.matched {
+			t.Errorf("unexpected result for %q.Match(%q) (#%d). Got %v, expected %v", test.pattern, test.name, i, matched, test.matched)
+		}
+	}
+}
+
+func TestFixedPrefix(t *testing.T) {
+	tests := []struct {
+		pattern string
+		prefix  string
+		full    bool
+	}{
+		{"", "", true},
+		{"...", "", false},
+		{"***", "", false},
+		{"...", "", false},
+		{"***", "", false},
+		{"a", "a", true},
+		{"*a", "", false},
+		{"a*", "a", false},
+		{"a*b", "a", false},
+		{"a\\*", "a*", true},
+		{"\\\\", "\\", true},
+		{"?", "", false},
+		{"\\?", "?", true},
+		{"*?", "", false},
+		{"[abc]", "", false},
+		{"\\[abc]", "[abc]", true},
+		{"\\[abc]*", "[abc]", false},
+	}
+	for i, test := range tests {
+		g, err := Parse(test.pattern)
+		if err != nil {
+			t.Errorf("unexpected parsing error for %q (#%d): %v", test.pattern, i, err)
+			continue
+		}
+		if prefix, full := g.Head().FixedPrefix(); prefix != test.prefix || full != test.full {
+			t.Errorf("unexpected result for %q.FixedPrefix() (#%d). Got (%q,%v), expected (%q,%v)", test.pattern, i, prefix, full, test.prefix, test.full)
+		}
+	}
+}
+
+func TestBadPattern(t *testing.T) {
+	tests := []string{"[", "[foo", "[^foo", "\\", "a\\", "abc[foo", "a//b"}
+	for _, test := range tests {
+		if _, err := Parse(test); err == nil {
+			t.Errorf("Unexpected success for %q", test)
+		}
+	}
+}
+
+func TestExactMatch(t *testing.T) {
+	tests := []struct {
+		pattern string
+		elems   []string
+		matched bool
+		exact   bool
+	}{
+		// Test recursive.
+		{"...", []string{}, true, true},
+		{"...", []string{"a"}, true, false},
+		{"a/...", []string{"a"}, true, true},
+		{"a/...", []string{"a", "b"}, true, false},
+		// Test one element, fixed.
+		{"a", []string{"a"}, true, true},
+		{"a", []string{"b"}, false, false},
+		{"\\\\", []string{"\\"}, true, true},
+		// Test one element, containing *.
+		{"*", []string{"*"}, true, false},
+		{"*", []string{"abc"}, true, false},
+		{"\\*", []string{"*"}, true, true},
+		{"\\*", []string{"abc"}, false, false},
+		{"\\\\*", []string{"\\"}, true, false},
+		{"\\\\*", []string{"\\*"}, true, false},
+		{"\\\\*", []string{"\\abc"}, true, false},
+		// Test one element, containing ?.
+		{"?", []string{"?"}, true, false},
+		{"?", []string{"a"}, true, false},
+		{"\\?", []string{"?"}, true, true},
+		{"\\?", []string{"a"}, false, false},
+		{"\\\\?", []string{"\\"}, false, false},
+		{"\\\\?", []string{"\\?"}, true, false},
+		{"\\\\?", []string{"\\a"}, true, false},
+		// Test one element, containing [].
+		{"[abc]", []string{"c"}, true, false},
+		{"\\[abc\\]", []string{"[abc]"}, true, true},
+		{"\\[abc\\]", []string{"a"}, false, false},
+		{"\\\\[abc]", []string{"\\a"}, true, false},
+		{"\\\\[abc]", []string{"a"}, false, false},
+		{"[\\\\]", []string{"\\"}, true, false},
+		{"[\\\\]", []string{"a"}, false, false},
+		// Test multiple elements.
+		{"a/b", []string{"a", "b"}, true, true},
+		{"a/\\*", []string{"a", "*"}, true, true},
+		{"a/\\?", []string{"a", "?"}, true, true},
+		{"a/\\[b\\]", []string{"a", "[b]"}, true, true},
+		{"a/*", []string{"a", "bc"}, true, false},
+		{"a/?", []string{"a", "b"}, true, false},
+		{"a/[bc]", []string{"a", "b"}, true, false},
+		{"a/*/c", []string{"a", "b", "c"}, true, false},
+		{"a/?/c", []string{"a", "b", "c"}, true, false},
+		{"a/[bc]/d", []string{"a", "b", "d"}, true, false},
+	}
+	for _, test := range tests {
+		g, err := Parse(test.pattern)
+		if err != nil {
+			t.Fatalf("parsing %q: %q", test.pattern, err.Error())
+		}
+		matched, exact, _ := g.PartialMatch(0, test.elems)
+		if matched != test.matched || exact != test.exact {
+			t.Fatalf("'%v'.PartialMatch(0, '%v') got (%v, %v), expected (%v, %v)",
+				test.pattern, test.elems, matched, exact, test.matched, test.exact)
+		}
+	}
+}
diff --git a/lib/mgmt/model.go b/lib/mgmt/model.go
new file mode 100644
index 0000000..95a667f
--- /dev/null
+++ b/lib/mgmt/model.go
@@ -0,0 +1,20 @@
+// 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 mgmt defines constants used by the management tools and daemons.
+package mgmt
+
+const (
+	ParentNameConfigKey            = "MGMT_PARENT_PROCESS_NAME"
+	ChildNameConfigKey             = "MGMT_CHILD_PROCESS_NAME"
+	AppCycleManagerConfigKey       = "MGMT_APP_CYCLE_MANAGER_NAME"
+	AddressConfigKey               = "MGMT_CHILD_PROCESS_ADDRESS"
+	ProtocolConfigKey              = "MGMT_CHILD_PROCESS_PROTOCOL"
+	ParentBlessingConfigKey        = "MGMT_PARENT_BLESSING_PEER_PATTERN"
+	SecurityAgentEndpointConfigKey = "MGMT_SECURITY_AGENT_EP"
+	SecurityAgentPathConfigKey     = "MGMT_SECURITY_AGENT_PATH"
+	AppOriginConfigKey             = "MGMT_APP_ORIGIN"
+	PublisherBlessingPrefixesKey   = "MGMT_PUBLISHER_BLESSING_PREFIXES"
+	InstanceNameKey                = "MGMT_INSTANCE_NAME"
+)
diff --git a/lib/pubsub/config_test.go b/lib/pubsub/config_test.go
new file mode 100644
index 0000000..b354437
--- /dev/null
+++ b/lib/pubsub/config_test.go
@@ -0,0 +1,328 @@
+// 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 pubsub_test
+
+import (
+	"fmt"
+	"math/rand"
+	"reflect"
+	"sync"
+	"testing"
+	"time"
+
+	"v.io/x/ref/lib/pubsub"
+
+	"v.io/v23/verror"
+)
+
+func ExamplePublisher() {
+	in := make(chan pubsub.Setting)
+	pub := pubsub.NewPublisher()
+	pub.CreateStream("net", "network settings", in)
+
+	// A simple producer of IP address settings.
+	producer := func() {
+		in <- pubsub.NewString("ip", "address", "1.2.3.5")
+	}
+
+	var waiter sync.WaitGroup
+	waiter.Add(2)
+
+	// A simple consumer of IP address Settings.
+	consumer := func(ch chan pubsub.Setting) {
+		fmt.Println(<-ch)
+		waiter.Done()
+	}
+
+	// Publish an initial Setting to the Stream.
+	in <- pubsub.NewString("ip", "address", "1.2.3.4")
+
+	// Fork the stream twice, and read the latest value.
+	ch1 := make(chan pubsub.Setting)
+	st, _ := pub.ForkStream("net", ch1)
+	fmt.Println(st.Latest["ip"])
+	ch2 := make(chan pubsub.Setting)
+	st, _ = pub.ForkStream("net", ch2)
+	fmt.Println(st.Latest["ip"])
+
+	// Now we can read new Settings as they are generated.
+	go producer()
+	go consumer(ch1)
+	go consumer(ch2)
+
+	waiter.Wait()
+
+	// Output:
+	// ip: address: (string: 1.2.3.4)
+	// ip: address: (string: 1.2.3.4)
+	// ip: address: (string: 1.2.3.5)
+	// ip: address: (string: 1.2.3.5)
+}
+
+func ExampleShutdown() {
+	in := make(chan pubsub.Setting)
+	pub := pubsub.NewPublisher()
+	stop, _ := pub.CreateStream("net", "network settings", in)
+
+	var producerReady sync.WaitGroup
+	producerReady.Add(1)
+	var consumersReady sync.WaitGroup
+	consumersReady.Add(2)
+
+	// A producer to write 100 Settings before signalling that it's
+	// ready to be shutdown. This is purely to demonstrate how to use
+	// Shutdown.
+	producer := func() {
+		for i := 0; ; i++ {
+			select {
+			case <-stop:
+				close(in)
+				return
+			default:
+				in <- pubsub.NewString("ip", "address", "1.2.3.4")
+				if i == 100 {
+					producerReady.Done()
+				}
+			}
+		}
+	}
+
+	var waiter sync.WaitGroup
+	waiter.Add(2)
+
+	consumer := func() {
+		ch := make(chan pubsub.Setting, 10)
+		pub.ForkStream("net", ch)
+		consumersReady.Done()
+		i := 0
+		for {
+			if _, ok := <-ch; !ok {
+				// The channel has been closed when the publisher
+				// is asked to shut down.
+				break
+			}
+			i++
+		}
+		if i >= 100 {
+			// We've received at least 100 Settings as per the producer above.
+			fmt.Println("done")
+		}
+		waiter.Done()
+	}
+
+	go consumer()
+	go consumer()
+	consumersReady.Wait()
+	go producer()
+	producerReady.Wait()
+	pub.Shutdown()
+	waiter.Wait()
+	// Output:
+	// done
+	// done
+}
+
+func TestSimple(t *testing.T) {
+	ch := make(chan pubsub.Setting, 2)
+	pub := pubsub.NewPublisher()
+	if _, err := pub.ForkStream("stream", nil); err == nil || verror.ErrorID(err) != "v.io/x/ref/lib/pubsub.errStreamDoesntExist" {
+		t.Errorf("missing or wrong error: %v", err)
+	}
+	if _, err := pub.CreateStream("stream", "example", nil); err == nil || verror.ErrorID(err) != "v.io/x/ref/lib/pubsub.errNeedNonNilChannel" {
+		t.Fatalf("missing or wrong error: %v", err)
+	}
+	if _, err := pub.CreateStream("stream", "example", ch); err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	if _, err := pub.CreateStream("stream", "example", ch); err == nil || verror.ErrorID(err) != "v.io/x/ref/lib/pubsub.errStreamExists" {
+		t.Fatalf("missing or wrong error: %v", err)
+	}
+	if got, want := pub.String(), "(stream: example)"; got != want {
+		t.Errorf("got %q, want %q", got, want)
+	}
+	stop, err := pub.CreateStream("s2", "eg2", ch)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	if got, want := pub.String(), "(stream: example) (s2: eg2)"; got != want {
+		wantAlternate := "(s2: eg2) (stream: example)"
+		if got != wantAlternate {
+			t.Errorf("got %q, want %q or %q", got, want, wantAlternate)
+		}
+	}
+
+	got, want := pub.Latest("s2"), &pubsub.Stream{
+		Name:        "s2",
+		Description: "eg2",
+		Latest:      nil,
+	}
+	if !reflect.DeepEqual(got, want) {
+		t.Errorf("got %q, want %q", got, want)
+	}
+	pub.Shutdown()
+	if _, running := <-stop; running {
+		t.Errorf("expected to be shutting down")
+	}
+	if _, err := pub.ForkStream("stream", nil); err == nil || verror.ErrorID(err) != "v.io/x/ref/lib/pubsub.errStreamShutDown" {
+		t.Errorf("missing or wrong error: %v", err)
+	}
+	if got, want := pub.String(), "shutdown"; got != want {
+		t.Errorf("got %s, want %s", got, want)
+	}
+
+}
+
+func producer(pub *pubsub.Publisher, in chan<- pubsub.Setting, stop <-chan struct{}, limit int, ch chan int, waiter *sync.WaitGroup) {
+	for i := 0; ; i++ {
+		select {
+		case <-stop:
+			ch <- i
+			waiter.Done()
+			// must close this channel, otherwise the Publisher will leak a goroutine for this stream.
+			close(in)
+			return
+		default:
+			// signal progress on ch, at limit/2, limit and when we're done (above)
+			switch {
+			case i == limit/2:
+				ch <- i
+			case i == limit:
+				ch <- i
+			}
+			if i%2 == 0 {
+				in <- pubsub.NewInt("i", "int", i)
+			} else {
+				in <- pubsub.NewFloat64("f", "float", float64(i))
+			}
+		}
+	}
+}
+
+func consumer(t *testing.T, pub *pubsub.Publisher, limit, bufsize int, errch chan error, starter, waiter *sync.WaitGroup) {
+	defer close(errch)
+	ch := make(chan pubsub.Setting, bufsize)
+	st, err := pub.ForkStream("net", ch)
+	if err != nil {
+		errch <- err
+		return
+	}
+	starter.Done()
+	i, i2 := 0, 0
+	if st.Latest["i"] != nil {
+		i = int(st.Latest["i"].Value().(int))
+	}
+	if st.Latest["f"] != nil {
+		i2 = int(st.Latest["f"].Value().(float64))
+	}
+	if i2 > i {
+		i = i2
+	}
+	i++
+	for s := range ch {
+		switch v := s.Value().(type) {
+		case int:
+			if i%2 != 0 {
+				errch <- fmt.Errorf("expected a float, got an int")
+				return
+			}
+			if v != i {
+				errch <- fmt.Errorf("got %d, want %d", v, i)
+				return
+			}
+		case float64:
+			if i%2 != 1 {
+				errch <- fmt.Errorf("expected an int, got a float")
+				return
+			}
+			if v != float64(i) {
+				errch <- fmt.Errorf("got %f, want %f", v, float64(i))
+				return
+			}
+		}
+		i++
+	}
+	if i < limit {
+		errch <- fmt.Errorf("didn't read enough settings: got %d, want >= %d", i, limit)
+		return
+	}
+	waiter.Done()
+}
+
+func testStream(t *testing.T, consumerBufSize int) {
+	in := make(chan pubsub.Setting)
+	pub := pubsub.NewPublisher()
+	stop, err := pub.CreateStream("net", "network settings", in)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	rand.Seed(time.Now().UnixNano())
+	limit := rand.Intn(5000)
+	if limit < 100 {
+		limit = 100
+	}
+	t.Logf("limit: %d", limit)
+
+	var waiter sync.WaitGroup
+	waiter.Add(3)
+
+	progress := make(chan int)
+	go producer(pub, in, stop, limit, progress, &waiter)
+
+	i := <-progress
+	t.Logf("limit/2 = %d", i)
+
+	err1 := make(chan error, 1)
+	err2 := make(chan error, 1)
+	var starter sync.WaitGroup
+	starter.Add(2)
+	go consumer(t, pub, limit, consumerBufSize, err1, &starter, &waiter)
+	go consumer(t, pub, limit, consumerBufSize, err2, &starter, &waiter)
+
+	reached := <-progress
+	// Give the consumers a chance to get going before shutting down
+	// the producer.
+	starter.Wait()
+	time.Sleep(100 * time.Millisecond)
+	pub.Shutdown()
+	shutdown := <-progress
+	t.Logf("reached %d, shut down at %d", reached, shutdown)
+
+	// This is a little annoying, we check for the presence of errors on the error
+	// channels once everything has run its course  since it's not allowed to call
+	// t.Fatal/Errorf from a separate goroutine. We wait until here so that we
+	// don't block waiting for errors that don't occur when the tests all work.
+	err = <-err1
+	if err != nil {
+		t.Fatal(err)
+	}
+	err = <-err2
+	if err != nil {
+		t.Fatal(err)
+	}
+	// Wait for all goroutines to finish.
+	waiter.Wait()
+}
+
+func TestStream(t *testing.T) {
+	testStream(t, 500)
+}
+
+func TestStreamSmallBuffers(t *testing.T) {
+	testStream(t, 1)
+}
+
+func TestDurationFlag(t *testing.T) {
+	d := &pubsub.DurationFlag{}
+	if err := d.Set("1s"); err != nil {
+		t.Errorf("unexpected error %s", err)
+	}
+	if got, want := d.Duration, time.Duration(time.Second); got != want {
+		t.Errorf("got %s, expected %s", got, want)
+	}
+	if err := d.Set("1t"); err == nil || err.Error() != "time: unknown unit t in duration 1t" {
+		t.Errorf("expected error %v", err)
+	}
+}
diff --git a/lib/pubsub/model.go b/lib/pubsub/model.go
new file mode 100644
index 0000000..f2bc2cd
--- /dev/null
+++ b/lib/pubsub/model.go
@@ -0,0 +1,35 @@
+// 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 pubsub defines interfaces for accessing dynamically changing
+// process configuration information.
+//
+// Settings represent configuration parameters and their value. Settings
+// are published to named Streams. Streams are forked to add additional
+// consumers, i.e. readers of the Settings published to the Stream.
+//
+// Settings are represented by an interface type that wraps the data and
+// provides a name and description for each Settings. Streams are similarly
+// named and also have a description. When streams are 'forked' the latest
+// value of all Settings that have been sent over the Stream are made
+// available to the caller. This allows for a rendezvous between the single
+// producer of Settings and multiple consumers of those Settings that
+// may be added at arbitrary points in time.
+//
+// Streams are hosted by a Publisher type, which in addition to the methods
+// required for managing Streams provides a means to shut down all of the
+// Streams it hosts.
+package pubsub
+
+// Setting must be implemented by all data types to sent over Publisher
+// streams.
+type Setting interface {
+	String() string
+	// Name returns the name of the Setting
+	Name() string
+	// Description returns the description of the Setting
+	Description() string
+	// Value returns the value of the Setting.
+	Value() interface{}
+}
diff --git a/lib/pubsub/publisher.go b/lib/pubsub/publisher.go
new file mode 100644
index 0000000..ec10fb4
--- /dev/null
+++ b/lib/pubsub/publisher.go
@@ -0,0 +1,229 @@
+// 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 pubsub
+
+import (
+	"fmt"
+	"strings"
+	"sync"
+
+	"v.io/v23/verror"
+)
+
+const pkgPath = "v.io/x/ref/lib/pubsub"
+
+var (
+	errNeedNonNilChannel = verror.Register(pkgPath+".errNeedNonNilChannel", verror.NoRetry, "must provide a non-nil channel")
+	errStreamExists      = verror.Register(pkgPath+".errStreamExists", verror.NoRetry, "stream {3} already exists")
+	errStreamShutDown    = verror.Register(pkgPath+".errStreamShutDown", verror.NoRetry, "stream {3} has been shut down")
+	errStreamDoesntExist = verror.Register(pkgPath+".errStreamDoesntExist", verror.NoRetry, "stream {3} doesn't exist")
+)
+
+// A Publisher provides a mechanism for communicating Settings from a set
+// of producers to multiple consumers. Each such producer and associated
+// consumers are called a Stream. Operations are provided for creating
+// streams (CreateStream) and adding new consumers (ForkStream). Communication
+// is implemented using channels, with the producer and consumers providing
+// channels to send and receive Settings over. A Stream remembers the last value
+// of all Settings that were sent over it; these can be retrieved via ForkStream
+// or the Latest method.
+//
+// The Publisher may be shut down by calling its Shutdown method and
+// the producers will be notified via the channel returned by CreateStream,
+// at which point they should close the channel they use for publishing Settings.
+// If producers fail to close this channel then the Publisher will leak
+// goroutines (one per stream) when it is shutdown.
+type Publisher struct {
+	mu       sync.RWMutex
+	stop     chan struct{}
+	shutdown bool
+	streams  map[string]*fork
+}
+
+// Stream is returned by Latest and includes the name and description
+// for the stream and the most recent values of the Setting that flowed
+// through it.
+type Stream struct {
+	Name, Description string
+	// Latest is a map of Setting names to the Setting itself.
+	Latest map[string]Setting
+}
+
+// NewPublisher creates a Publisher.
+func NewPublisher() *Publisher {
+	return &Publisher{
+		streams: make(map[string]*fork),
+		stop:    make(chan struct{}),
+	}
+}
+
+type fork struct {
+	sync.RWMutex
+	desc string
+	vals map[string]Setting
+	in   <-chan Setting
+	outs []chan<- Setting
+}
+
+// CreateStream creates a Stream with the provided name and description
+// (note, Settings have their own names and description, these are for the
+// stream). In general, no buffering is required for this channel since
+// the Publisher itself will read from it, however, if the consumers are slow
+// then the publisher may be slow in draining the channel. The publisher
+// should provide additional buffering if this is a concern.
+// Consequently this mechanism should be used for rarely changing Settings,
+// such as network address changes forced by DHCP and hence no buffering
+// will be required. The channel returned by CreateStream is closed when the
+// publisher is shut down and hence the caller should wait for this to occur
+// and then close the channel it has passed to CreateStream.
+func (p *Publisher) CreateStream(name, description string, ch <-chan Setting) (<-chan struct{}, error) {
+	if ch == nil {
+		return nil, verror.New(errNeedNonNilChannel, nil)
+	}
+	p.mu.Lock()
+	defer p.mu.Unlock()
+	if p.streams[name] != nil {
+		return nil, verror.New(errStreamExists, nil, name)
+	}
+	f := &fork{desc: description, in: ch, vals: make(map[string]Setting)}
+	p.streams[name] = f
+	go f.flow(p.stop)
+	return p.stop, nil
+}
+
+// String returns a string representation of the publisher, including
+// the names and descriptions of all the streams it currently supports.
+func (p *Publisher) String() string {
+	r := ""
+	p.mu.RLock()
+	defer p.mu.RUnlock()
+	if p.shutdown {
+		return "shutdown"
+	}
+	for k, s := range p.streams {
+		r += fmt.Sprintf("(%s: %s) ", k, s.desc)
+	}
+	return strings.TrimRight(r, " ")
+}
+
+// Latest returns information on the requested stream, including the
+// last instance of all Settings, if any, that flowed over it.
+func (p *Publisher) Latest(name string) *Stream {
+	p.mu.Lock()
+	defer p.mu.Unlock()
+	f := p.streams[name]
+	if f == nil {
+		return nil
+	}
+	var r map[string]Setting
+	f.RLock()
+	defer f.RUnlock()
+	for k, v := range f.vals {
+		r[k] = v
+	}
+	return &Stream{Name: name, Description: f.desc, Latest: r}
+}
+
+// ForkStream 'forks' the named stream to add a new consumer. The channel
+// provided is to be used to read Settings sent down the stream. This
+// channel will be closed by the Publisher when it is asked to shut down.
+// The reader on this channel must be able to keep up with the flow of Settings
+// through the Stream in order to avoid blocking all other readers and hence
+// should set an appropriate amount of buffering for the channel it passes in.
+// ForkStream returns the most recent values of all Settings previously
+// sent over the stream, thus allowing its caller to synchronise with the
+// stream.
+func (p *Publisher) ForkStream(name string, ch chan<- Setting) (*Stream, error) {
+	p.mu.Lock()
+	defer p.mu.Unlock()
+	if p.shutdown {
+		return nil, verror.New(errStreamShutDown, nil, name)
+	}
+	f := p.streams[name]
+	if f == nil {
+		return nil, verror.New(errStreamDoesntExist, nil, name)
+	}
+	f.Lock()
+	defer f.Unlock()
+	r := make(map[string]Setting)
+	for k, v := range f.vals {
+		r[k] = v
+	}
+	f.outs = append(f.outs, ch)
+	return &Stream{Name: name, Description: f.desc, Latest: r}, nil
+}
+
+// CloseFork removes the specified channel from the named stream.
+// The caller must drain the channel before closing it.
+// TODO(cnicolaou): add tests for this.
+func (p *Publisher) CloseFork(name string, ch chan<- Setting) error {
+	p.mu.Lock()
+	defer p.mu.Unlock()
+	if p.shutdown {
+		return verror.New(errStreamShutDown, nil, name)
+	}
+	f := p.streams[name]
+	if f == nil {
+		return verror.New(errStreamDoesntExist, nil, name)
+	}
+	f.Lock()
+	defer f.Unlock()
+	for i, v := range f.outs {
+		if v == ch {
+			f.outs = append(f.outs[0:i], f.outs[i+1:]...)
+			break
+		}
+	}
+	return nil
+}
+
+// Shutdown initiates the process of stopping the operation of the Publisher.
+// All of the channels passed to CreateStream must be closed by their owner
+// to ensure that all goroutines are garbage collected.
+func (p *Publisher) Shutdown() {
+	p.mu.Lock()
+	defer p.mu.Unlock()
+	if p.shutdown {
+		return
+	}
+	p.shutdown = true
+	close(p.stop)
+}
+
+func (f *fork) closeChans() {
+	f.Lock()
+	for _, o := range f.outs {
+		close(o)
+	}
+	f.outs = nil
+	f.Unlock()
+}
+
+func (f *fork) flow(stop chan struct{}) {
+	closed := false
+	for {
+		select {
+		case <-stop:
+			if !closed {
+				f.closeChans()
+				closed = true
+			}
+		case val, ok := <-f.in:
+			if !ok {
+				f.closeChans()
+				return
+			}
+			f.Lock()
+			f.vals[val.Name()] = val
+			cpy := make([]chan<- Setting, len(f.outs))
+			copy(cpy, f.outs)
+			f.Unlock()
+			for _, o := range cpy {
+				// We may well block here.
+				o <- val
+			}
+		}
+	}
+}
diff --git a/lib/pubsub/types.go b/lib/pubsub/types.go
new file mode 100644
index 0000000..1ee8d66
--- /dev/null
+++ b/lib/pubsub/types.go
@@ -0,0 +1,91 @@
+// 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 pubsub
+
+import (
+	"fmt"
+
+	"time"
+)
+
+// Format formats a Setting in a consistent manner, it is intended to be
+// used when implementing the Setting interface.
+func Format(s Setting) string {
+	return fmt.Sprintf("%s: %s: (%T: %s)", s.Name(), s.Description(), s.Value(), s.Value())
+}
+
+// Type Any can be used to represent or implement a Setting of any type.
+type Any struct {
+	name, description string
+	value             interface{}
+}
+
+func (s *Any) String() string {
+	return Format(s)
+}
+
+func (s *Any) Name() string {
+	return s.name
+}
+
+func (s *Any) Description() string {
+	return s.description
+}
+
+func (s *Any) Value() interface{} {
+	return s.value
+}
+
+func NewAny(name, description string, value interface{}) Setting {
+	return &Any{name, description, value}
+}
+
+func NewInt(name, description string, value int) Setting {
+	return &Any{name, description, value}
+}
+
+func NewInt64(name, description string, value int64) Setting {
+	return &Any{name, description, value}
+}
+
+func NewBool(name, description string, value bool) Setting {
+	return &Any{name, description, value}
+}
+
+func NewFloat64(name, description string, value float64) Setting {
+	return &Any{name, description, value}
+}
+
+func NewString(name, description string, value string) Setting {
+	return &Any{name, description, value}
+}
+
+func NewDuration(name, description string, value time.Duration) Setting {
+	return &Any{name, description, value}
+}
+
+// DurationFlag implements flag.Value in order to provide validation of
+// duration values in the flag package.
+type DurationFlag struct{ time.Duration }
+
+// Implements flag.Value.Get
+func (d DurationFlag) Get() interface{} {
+	return d.Duration
+}
+
+// Implements flag.Value.Set
+func (d *DurationFlag) Set(s string) error {
+	duration, err := time.ParseDuration(s)
+	if err != nil {
+		return err
+	}
+	d.Duration = duration
+	return nil
+}
+
+// Implements flag.Value.String
+func (d DurationFlag) String() string {
+	return d.Duration.String()
+}
diff --git a/lib/security/audit/auditor.go b/lib/security/audit/auditor.go
new file mode 100644
index 0000000..52eddaa
--- /dev/null
+++ b/lib/security/audit/auditor.go
@@ -0,0 +1,57 @@
+// 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 audit implements a mechanism for writing auditable events to an audit
+// log.
+//
+// Typical use would be for tracking sensitive operations like private key usage
+// (NewPrincipal), or sensitive RPC method invocations.
+package audit
+
+import (
+	"fmt"
+	"strings"
+	"time"
+
+	"v.io/v23/context"
+)
+
+// Auditor is the interface for writing auditable events.
+type Auditor interface {
+	Audit(ctx *context.T, entry Entry) error
+}
+
+// Entry is the information logged on each auditable event.
+type Entry struct {
+	// Method being invoked.
+	Method string
+	// Arguments to the method.
+	// Any sensitive data in the arguments should not be included,
+	// even if the argument was provided to the real method invocation.
+	Arguments []interface{}
+	// Result of the method invocation.
+	// A common use case is to audit only successful method invocations.
+	Results []interface{}
+
+	// Timestamp of method invocation.
+	Timestamp time.Time
+}
+
+func (e Entry) String() string {
+	return fmt.Sprintf("%v: %s(%s)%s", e.Timestamp.Format(time.RFC3339), e.Method, join(e.Arguments, "", ""), join(e.Results, " = (", ")"))
+}
+
+func join(elems []interface{}, prefix, suffix string) string {
+	switch len(elems) {
+	case 0:
+		return ""
+	case 1:
+		return fmt.Sprintf("%s%v%s", prefix, elems[0], suffix)
+	}
+	strs := make([]string, len(elems))
+	for i, e := range elems {
+		strs[i] = fmt.Sprintf("%v", e)
+	}
+	return prefix + strings.Join(strs, ", ") + suffix
+}
diff --git a/lib/security/audit/auditor_test.go b/lib/security/audit/auditor_test.go
new file mode 100644
index 0000000..a6cdc35
--- /dev/null
+++ b/lib/security/audit/auditor_test.go
@@ -0,0 +1,49 @@
+// 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 audit_test
+
+import (
+	"testing"
+	"time"
+
+	"v.io/x/ref/lib/security/audit"
+)
+
+func TestEntryString(t *testing.T) {
+	timestamp, err := time.Parse(time.RFC3339, "2014-08-08T14:39:25-07:00") // "2014-08-08 12:56:40.698493437 -0700 PDT")
+	if err != nil {
+		t.Fatal(err)
+	}
+	var (
+		onearg      = []interface{}{"Arg"}
+		manyargs    = []interface{}{"Arg1", "Arg2"}
+		oneresult   = []interface{}{"Result"}
+		manyresults = []interface{}{"Res1", "Res2"}
+		tests       = []struct {
+			Entry  audit.Entry
+			String string
+		}{
+			// No results, 0, 1 or multiple arguments
+			{audit.Entry{Method: "M"}, "2014-08-08T14:39:25-07:00: M()"},
+			{audit.Entry{Method: "M", Arguments: onearg}, "2014-08-08T14:39:25-07:00: M(Arg)"},
+			{audit.Entry{Method: "M", Arguments: manyargs}, "2014-08-08T14:39:25-07:00: M(Arg1, Arg2)"},
+			// 1 result, 0, 1 or multiple arguments
+			{audit.Entry{Method: "M", Results: oneresult}, "2014-08-08T14:39:25-07:00: M() = (Result)"},
+			{audit.Entry{Method: "M", Arguments: onearg, Results: oneresult}, "2014-08-08T14:39:25-07:00: M(Arg) = (Result)"},
+			{audit.Entry{Method: "M", Arguments: manyargs, Results: oneresult}, "2014-08-08T14:39:25-07:00: M(Arg1, Arg2) = (Result)"},
+			// Multiple results, 0, 1 or multiple arguments
+			{audit.Entry{Method: "M", Results: manyresults}, "2014-08-08T14:39:25-07:00: M() = (Res1, Res2)"},
+			{audit.Entry{Method: "M", Arguments: onearg, Results: manyresults}, "2014-08-08T14:39:25-07:00: M(Arg) = (Res1, Res2)"},
+			{audit.Entry{Method: "M", Arguments: manyargs, Results: manyresults}, "2014-08-08T14:39:25-07:00: M(Arg1, Arg2) = (Res1, Res2)"},
+		}
+	)
+
+	for _, test := range tests {
+		test.Entry.Timestamp = timestamp
+		if got, want := test.Entry.String(), test.String; got != want {
+			t.Errorf("Got %q want %q for [%#v]", got, want, test.Entry)
+		}
+	}
+}
diff --git a/lib/security/audit/principal.go b/lib/security/audit/principal.go
new file mode 100644
index 0000000..60d4a78
--- /dev/null
+++ b/lib/security/audit/principal.go
@@ -0,0 +1,109 @@
+// 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 audit
+
+import (
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/security"
+	"v.io/v23/verror"
+)
+
+const pkgPath = "v.io/x/ref/lib/security/audit"
+
+var (
+	errCantAuditCall = verror.Register(pkgPath+".errCantAuditCall", verror.NoRetry, "{1:}{2:} failed to audit call to {3}{:_}")
+)
+
+// NewPrincipal returns a security.Principal implementation that logs
+// all private key operations of 'wrapped' to 'auditor' (i.e., all calls to
+// BlessSelf, Bless, MintDischarge and Sign).
+func NewPrincipal(ctx *context.T, auditor Auditor) security.Principal {
+	wrapped := v23.GetPrincipal(ctx)
+	return &auditingPrincipal{principal: wrapped, auditor: auditor, ctx: ctx}
+}
+
+type auditingPrincipal struct {
+	principal security.Principal
+	auditor   Auditor
+	ctx       *context.T
+}
+
+type args []interface{}
+
+var noBlessings security.Blessings
+
+func (p *auditingPrincipal) Bless(key security.PublicKey, with security.Blessings, extension string, caveat security.Caveat, additionalCaveats ...security.Caveat) (security.Blessings, error) {
+	blessings, err := p.principal.Bless(key, with, extension, caveat, additionalCaveats...)
+	if err = p.audit(err, "Bless", addCaveats(args{key, with, extension, caveat}, additionalCaveats...), blessings); err != nil {
+		return noBlessings, err
+	}
+	return blessings, nil
+}
+
+func (p *auditingPrincipal) BlessSelf(name string, caveats ...security.Caveat) (security.Blessings, error) {
+	blessings, err := p.principal.BlessSelf(name, caveats...)
+	if err = p.audit(err, "BlessSelf", addCaveats(args{name}, caveats...), blessings); err != nil {
+		return noBlessings, err
+	}
+	return blessings, nil
+}
+
+func (p *auditingPrincipal) Sign(message []byte) (security.Signature, error) {
+	// Do not save the signature itself.
+	sig, err := p.principal.Sign(message)
+	if err = p.audit(err, "Sign", args{message}, nil); err != nil {
+		return security.Signature{}, err
+	}
+	return sig, nil
+}
+
+func (p *auditingPrincipal) MintDischarge(forCaveat, caveatOnDischarge security.Caveat, additionalCaveatsOnDischarge ...security.Caveat) (security.Discharge, error) {
+	d, err := p.principal.MintDischarge(forCaveat, caveatOnDischarge, additionalCaveatsOnDischarge...)
+	// No need to log the discharge
+	if err = p.audit(err, "MintDischarge", addCaveats(args{forCaveat, caveatOnDischarge}, additionalCaveatsOnDischarge...), nil); err != nil {
+		return security.Discharge{}, err
+	}
+	return d, nil
+}
+
+func (p *auditingPrincipal) BlessingsByName(name security.BlessingPattern) []security.Blessings {
+	return p.principal.BlessingsByName(name)
+}
+
+func (p *auditingPrincipal) BlessingsInfo(b security.Blessings) map[string][]security.Caveat {
+	return p.principal.BlessingsInfo(b)
+}
+
+func (p *auditingPrincipal) PublicKey() security.PublicKey         { return p.principal.PublicKey() }
+func (p *auditingPrincipal) Roots() security.BlessingRoots         { return p.principal.Roots() }
+func (p *auditingPrincipal) BlessingStore() security.BlessingStore { return p.principal.BlessingStore() }
+func (p *auditingPrincipal) AddToRoots(b security.Blessings) error { return p.principal.AddToRoots(b) }
+
+func (p *auditingPrincipal) audit(err error, method string, args args, result interface{}) error {
+	if err != nil {
+		return err
+	}
+	entry := Entry{Method: method, Timestamp: time.Now()}
+	if len(args) > 0 {
+		entry.Arguments = []interface{}(args)
+	}
+	if result != nil {
+		entry.Results = []interface{}{result}
+	}
+	if err := p.auditor.Audit(p.ctx, entry); err != nil {
+		return verror.New(errCantAuditCall, nil, method, err)
+	}
+	return nil
+}
+
+func addCaveats(args args, caveats ...security.Caveat) args {
+	for _, c := range caveats {
+		args = append(args, c)
+	}
+	return args
+}
diff --git a/lib/security/audit/principal_test.go b/lib/security/audit/principal_test.go
new file mode 100644
index 0000000..751fe23
--- /dev/null
+++ b/lib/security/audit/principal_test.go
@@ -0,0 +1,305 @@
+// 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 audit_test
+
+import (
+	"crypto/ecdsa"
+	"crypto/elliptic"
+	"crypto/rand"
+	"errors"
+	"fmt"
+	"reflect"
+	"strings"
+	"testing"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/security"
+	"v.io/v23/verror"
+	"v.io/x/ref/lib/security/audit"
+	_ "v.io/x/ref/runtime/factories/fake"
+	"v.io/x/ref/test"
+)
+
+func TestAuditingPrincipal(t *testing.T) {
+	ctx, shutdown := test.V23InitAnon()
+	defer shutdown()
+	var (
+		thirdPartyCaveat, discharge = newThirdPartyCaveatAndDischarge(t)
+		wantErr                     = errors.New("call failed") // The error returned by call calls to mockID operations
+
+		mockP   = new(mockPrincipal)
+		auditor = new(mockAuditor)
+	)
+	ctx, _ = v23.WithPrincipal(ctx, mockP)
+	p := audit.NewPrincipal(ctx, auditor)
+	tests := []struct {
+		Method      string
+		Args        V
+		Result      interface{} // Result returned by the Method call.
+		AuditResult bool        // If true, Result should appear in the audit log. If false, it should not.
+	}{
+		{"BlessSelf", V{"self"}, newBlessing(t, "blessing"), true},
+		{"Bless", V{newPrincipal(t).PublicKey(), newBlessing(t, "root"), "extension", security.UnconstrainedUse()}, newBlessing(t, "root/extension"), true},
+		{"MintDischarge", V{thirdPartyCaveat, security.UnconstrainedUse()}, discharge, false},
+		{"Sign", V{make([]byte, 10)}, security.Signature{R: []byte{1}, S: []byte{1}}, false},
+	}
+	for _, test := range tests {
+		// Test1: If the underlying operation fails, the error should be returned and nothing should be audited.
+		mockP.NextError = wantErr
+		results, err := call(p, test.Method, test.Args)
+		if err != nil {
+			t.Errorf("failed to invoke p.%v(%#v): %v", test.Method, test.Args, err)
+			continue
+		}
+		if got, ok := results[len(results)-1].(error); !ok || got != wantErr {
+			t.Errorf("p.%v(%#v) returned (..., %v), want (..., %v)", test.Method, test.Args, got, wantErr)
+		}
+		if audited := auditor.Release(); !reflect.DeepEqual(audited, audit.Entry{}) {
+			t.Errorf("p.%v(%#v) resulted in [%+v] being written to the audit log, nothing should have been", test.Method, test.Args, audited)
+		}
+
+		// Test2: If the auditor fails, then the operation should fail too.
+		auditor.NextError = errors.New("auditor failed")
+		results, err = call(p, test.Method, test.Args)
+		if err != nil {
+			t.Errorf("failed to invoke p.%v(%#v): %v", test.Method, test.Args, err)
+			continue
+		}
+		if got, ok := results[len(results)-1].(error); !ok || !strings.HasSuffix(got.Error(), "auditor failed") {
+			t.Errorf("p.%v(%#v) returned %v when auditor failed, wanted (..., %v)", test.Method, test.Args, results, "... auditor failed")
+		}
+
+		// Test3: If the underlying operation succeeds, should return the same value and write to the audit log.
+		now := time.Now()
+		mockP.NextResult = test.Result
+		results, err = call(p, test.Method, test.Args)
+		audited := auditor.Release()
+		if err != nil {
+			t.Errorf("failed to invoke p.%v(%#v): %v", test.Method, test.Args, err)
+			continue
+		}
+		if got := results[len(results)-1]; got != nil {
+			t.Errorf("p.%v(%#v) returned an error: %v", test.Method, test.Args, got)
+		}
+		if got := results[0]; !reflect.DeepEqual(got, test.Result) {
+			t.Errorf("p.%v(%#v) returned %v(%T) want %v(%T)", test.Method, test.Args, got, got, test.Result, test.Result)
+		}
+		if audited.Timestamp.Before(now) || audited.Timestamp.IsZero() {
+			t.Errorf("p.%v(%#v) audited the time as %v, should have been a time after %v", test.Method, test.Args, audited.Timestamp, now)
+		}
+		if want := (audit.Entry{
+			Method:    test.Method,
+			Arguments: []interface{}(test.Args),
+			Results:   sliceOrNil(test.AuditResult, test.Result),
+			Timestamp: audited.Timestamp, // Hard to come up with the expected timestamp, relying on sanity check above.
+		}); !reflect.DeepEqual(audited, want) {
+			t.Errorf("p.%v(%#v) resulted in [%#v] being audited, wanted [%#v]", test.Method, test.Args, audited, want)
+		}
+	}
+}
+
+// equalResults returns nil iff the arrays got[] and want[] are equivalent.
+// Equivalent arrays have equal length, and either are equal according to
+// reflect.DeepEqual, or the elements of each are errors with identical verror
+// error codes.
+func equalResults(got, want []interface{}) error {
+	if len(got) != len(want) {
+		return fmt.Errorf("got %d results, want %d (%v vs. %v)", len(got), len(want), got, want)
+	}
+	// Special case comparisons on verror.E
+	for i := range want {
+		if werr, wiserr := want[i].(verror.E); wiserr {
+			// Compare verror ids
+			gerr, giserr := got[i].(verror.E)
+			if !giserr {
+				return fmt.Errorf("result #%d: Got %T, want %T", i, got, want)
+			}
+			if verror.ErrorID(gerr) != verror.ErrorID(werr) {
+				return fmt.Errorf("result #%d: Got error %v, want %v", i, gerr, werr)
+			}
+		} else if !reflect.DeepEqual(got, want) {
+			return fmt.Errorf("result #%d: Got  %v, want %v", i, got, want)
+		}
+	}
+	return nil
+}
+
+func TestUnauditedMethodsOnPrincipal(t *testing.T) {
+	ctx, shutdown := test.V23InitAnon()
+	defer shutdown()
+	var (
+		auditor = new(mockAuditor)
+		p       = newPrincipal(t)
+	)
+	ctx, _ = v23.WithPrincipal(ctx, p)
+	auditedP := audit.NewPrincipal(ctx, auditor)
+	blessing, err := p.BlessSelf("self")
+	if err != nil {
+		t.Fatal(err)
+	}
+	tests := []struct {
+		Method string
+		Args   V
+	}{
+		{"PublicKey", V{}},
+		{"Roots", V{}},
+		{"AddToRoots", V{blessing}},
+		{"BlessingStore", V{}},
+	}
+
+	for i, test := range tests {
+		want, err := call(p, test.Method, test.Args)
+		if err != nil {
+			t.Fatalf("%v: %v", test.Method, err)
+		}
+		got, err := call(auditedP, test.Method, test.Args)
+		if err != nil {
+			t.Fatalf("%v: %v", test.Method, err)
+		}
+		if err := equalResults(got, want); err != nil {
+			t.Errorf("testcase %d: %v", i, err)
+		}
+		if gotEntry := auditor.Release(); !reflect.DeepEqual(gotEntry, audit.Entry{}) {
+			t.Errorf("Unexpected entry in audit log: %v", gotEntry)
+		}
+	}
+}
+
+type mockPrincipal struct {
+	NextResult interface{}
+	NextError  error
+}
+
+func (p *mockPrincipal) reset() {
+	p.NextError = nil
+	p.NextResult = nil
+}
+
+func (p *mockPrincipal) Bless(security.PublicKey, security.Blessings, string, security.Caveat, ...security.Caveat) (security.Blessings, error) {
+	defer p.reset()
+	b, _ := p.NextResult.(security.Blessings)
+	return b, p.NextError
+}
+
+func (p *mockPrincipal) BlessSelf(string, ...security.Caveat) (security.Blessings, error) {
+	defer p.reset()
+	b, _ := p.NextResult.(security.Blessings)
+	return b, p.NextError
+}
+
+func (p *mockPrincipal) Sign([]byte) (sig security.Signature, err error) {
+	defer p.reset()
+	sig, _ = p.NextResult.(security.Signature)
+	err = p.NextError
+	return
+}
+
+func (p *mockPrincipal) MintDischarge(security.Caveat, security.Caveat, ...security.Caveat) (security.Discharge, error) {
+	defer p.reset()
+	d, _ := p.NextResult.(security.Discharge)
+	return d, p.NextError
+}
+
+func (p *mockPrincipal) BlessingsByName(name security.BlessingPattern) []security.Blessings {
+	return nil
+}
+
+func (p *mockPrincipal) BlessingsInfo(b security.Blessings) map[string][]security.Caveat {
+	return nil
+}
+
+func (p *mockPrincipal) PublicKey() security.PublicKey         { return p.NextResult.(security.PublicKey) }
+func (p *mockPrincipal) Roots() security.BlessingRoots         { return nil }
+func (p *mockPrincipal) BlessingStore() security.BlessingStore { return nil }
+func (p *mockPrincipal) AddToRoots(b security.Blessings) error { return nil }
+
+type mockAuditor struct {
+	LastEntry audit.Entry
+	NextError error
+}
+
+func (a *mockAuditor) Audit(ctx *context.T, entry audit.Entry) error {
+	if a.NextError != nil {
+		err := a.NextError
+		a.NextError = nil
+		return err
+	}
+	a.LastEntry = entry
+	return nil
+}
+
+func (a *mockAuditor) Release() audit.Entry {
+	entry := a.LastEntry
+	a.LastEntry = audit.Entry{}
+	return entry
+}
+
+type V []interface{}
+
+func call(receiver interface{}, method string, args V) (results []interface{}, err interface{}) {
+	defer func() {
+		err = recover()
+	}()
+	callargs := make([]reflect.Value, len(args))
+	for idx, arg := range args {
+		callargs[idx] = reflect.ValueOf(arg)
+	}
+	callresults := reflect.ValueOf(receiver).MethodByName(method).Call(callargs)
+	results = make([]interface{}, len(callresults))
+	for idx, res := range callresults {
+		results[idx] = res.Interface()
+	}
+	return
+}
+
+func sliceOrNil(include bool, item interface{}) []interface{} {
+	if item != nil && include {
+		return []interface{}{item}
+	}
+	return nil
+}
+
+func newPrincipal(t *testing.T) security.Principal {
+	key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+	if err != nil {
+		t.Fatal(err)
+	}
+	signer := security.NewInMemoryECDSASigner(key)
+	p, err := security.CreatePrincipal(signer, nil, nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	return p
+}
+
+func newCaveat(c security.Caveat, err error) security.Caveat {
+	if err != nil {
+		panic(err)
+	}
+	return c
+}
+
+func newBlessing(t *testing.T, name string) security.Blessings {
+	b, err := newPrincipal(t).BlessSelf(name)
+	if err != nil {
+		t.Fatal(err)
+	}
+	return b
+}
+
+func newThirdPartyCaveatAndDischarge(t *testing.T) (security.Caveat, security.Discharge) {
+	p := newPrincipal(t)
+	c, err := security.NewPublicKeyCaveat(p.PublicKey(), "location", security.ThirdPartyRequirements{}, newCaveat(security.NewMethodCaveat("method")))
+	if err != nil {
+		t.Fatal(err)
+	}
+	d, err := p.MintDischarge(c, security.UnconstrainedUse())
+	if err != nil {
+		t.Fatal(err)
+	}
+	return c, d
+}
diff --git a/lib/security/blessingroots.go b/lib/security/blessingroots.go
new file mode 100644
index 0000000..5c0a7b2
--- /dev/null
+++ b/lib/security/blessingroots.go
@@ -0,0 +1,176 @@
+// 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 security
+
+import (
+	"bytes"
+	"fmt"
+	"sort"
+	"sync"
+
+	"v.io/v23/security"
+	"v.io/v23/verror"
+	"v.io/x/lib/vlog"
+	"v.io/x/ref/lib/security/serialization"
+)
+
+var errRootsAddPattern = verror.Register(pkgPath+".errRootsAddPattern", verror.NoRetry, "{1:}{2:} a root cannot be recognized for all blessing names (i.e., the pattern '...')")
+
+// blessingRoots implements security.BlessingRoots.
+type blessingRoots struct {
+	persistedData SerializerReaderWriter
+	signer        serialization.Signer
+	mu            sync.RWMutex
+	state         blessingRootsState // GUARDED_BY(mu)
+}
+
+func stateMapKey(root security.PublicKey) (string, error) {
+	rootBytes, err := root.MarshalBinary()
+	if err != nil {
+		return "", err
+	}
+	return string(rootBytes), nil
+}
+
+func (br *blessingRoots) Add(root security.PublicKey, pattern security.BlessingPattern) error {
+	if pattern == security.AllPrincipals {
+		return verror.New(errRootsAddPattern, nil)
+	}
+	key, err := stateMapKey(root)
+	if err != nil {
+		return err
+	}
+
+	br.mu.Lock()
+	defer br.mu.Unlock()
+	patterns := br.state[key]
+	for _, p := range patterns {
+		if p == pattern {
+			return nil
+		}
+	}
+	br.state[key] = append(patterns, pattern)
+
+	if err := br.save(); err != nil {
+		br.state[key] = patterns[:len(patterns)-1]
+		return err
+	}
+
+	return nil
+}
+
+func (br *blessingRoots) Recognized(root security.PublicKey, blessing string) error {
+	key, err := stateMapKey(root)
+	if err != nil {
+		return err
+	}
+
+	br.mu.RLock()
+	defer br.mu.RUnlock()
+	for _, p := range br.state[key] {
+		if p.MatchedBy(blessing) {
+			return nil
+		}
+	}
+	return security.NewErrUnrecognizedRoot(nil, root.String(), nil)
+}
+
+func (br *blessingRoots) Dump() map[security.BlessingPattern][]security.PublicKey {
+	dump := make(map[security.BlessingPattern][]security.PublicKey)
+	br.mu.RLock()
+	defer br.mu.RUnlock()
+	for keyStr, patterns := range br.state {
+		key, err := security.UnmarshalPublicKey([]byte(keyStr))
+		if err != nil {
+			vlog.Errorf("security.UnmarshalPublicKey(%v) returned error: %v", []byte(keyStr), err)
+			return nil
+		}
+		for _, p := range patterns {
+			dump[p] = append(dump[p], key)
+		}
+	}
+	return dump
+}
+
+// DebugString return a human-readable string encoding of the roots
+// DebugString encodes all roots into a string in the following
+// format
+//
+// Public key     Pattern
+// <public key>   <patterns>
+// ...
+// <public key>   <patterns>
+func (br *blessingRoots) DebugString() string {
+	const format = "%-47s   %s\n"
+	b := bytes.NewBufferString(fmt.Sprintf(format, "Public key", "Pattern"))
+	var s rootSorter
+	for keyBytes, patterns := range br.state {
+		key, err := security.UnmarshalPublicKey([]byte(keyBytes))
+		if err != nil {
+			return fmt.Sprintf("failed to decode public key: %v", err)
+		}
+		s = append(s, &root{key, fmt.Sprintf("%v", patterns)})
+	}
+	sort.Sort(s)
+	for _, r := range s {
+		b.WriteString(fmt.Sprintf(format, r.key, r.patterns))
+	}
+	return b.String()
+}
+
+type root struct {
+	key      security.PublicKey
+	patterns string
+}
+
+type rootSorter []*root
+
+func (s rootSorter) Len() int           { return len(s) }
+func (s rootSorter) Less(i, j int) bool { return s[i].patterns < s[j].patterns }
+func (s rootSorter) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
+
+func (br *blessingRoots) save() error {
+	if (br.signer == nil) && (br.persistedData == nil) {
+		return nil
+	}
+	data, signature, err := br.persistedData.Writers()
+	if err != nil {
+		return err
+	}
+	return encodeAndStore(br.state, data, signature, br.signer)
+}
+
+// newInMemoryBlessingRoots returns an in-memory security.BlessingRoots.
+//
+// The returned BlessingRoots is initialized with an empty set of keys.
+func newInMemoryBlessingRoots() security.BlessingRoots {
+	return &blessingRoots{
+		state: make(blessingRootsState),
+	}
+}
+
+// newPersistingBlessingRoots returns a security.BlessingRoots for a principal
+// that is initialized with the persisted data. The returned security.BlessingRoots
+// also persists any updates to its state.
+func newPersistingBlessingRoots(persistedData SerializerReaderWriter, signer serialization.Signer) (security.BlessingRoots, error) {
+	if persistedData == nil || signer == nil {
+		return nil, verror.New(errDataOrSignerUnspecified, nil)
+	}
+	br := &blessingRoots{
+		state:         make(blessingRootsState),
+		persistedData: persistedData,
+		signer:        signer,
+	}
+	data, signature, err := br.persistedData.Readers()
+	if err != nil {
+		return nil, err
+	}
+	if (data != nil) && (signature != nil) {
+		if err := decodeFromStorage(&br.state, data, signature, br.signer.PublicKey()); err != nil {
+			return nil, err
+		}
+	}
+	return br, nil
+}
diff --git a/lib/security/blessingroots_test.go b/lib/security/blessingroots_test.go
new file mode 100644
index 0000000..ad6afd5
--- /dev/null
+++ b/lib/security/blessingroots_test.go
@@ -0,0 +1,164 @@
+// 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 security
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"reflect"
+	"sort"
+	"testing"
+
+	"v.io/v23/security"
+	"v.io/v23/verror"
+)
+
+type rootsTester [4]security.PublicKey
+
+func newRootsTester() *rootsTester {
+	var tester rootsTester
+	var err error
+	for idx := range tester {
+		if tester[idx], _, err = NewPrincipalKey(); err != nil {
+			panic(err)
+		}
+	}
+	return &tester
+}
+
+func (t *rootsTester) add(br security.BlessingRoots) error {
+	if err := br.Add(t[0], security.AllPrincipals); err == nil {
+		return fmt.Errorf("Add( , %v) succeeded, expected it to fail", security.AllPrincipals)
+	}
+	testdata := []struct {
+		root    security.PublicKey
+		pattern security.BlessingPattern
+	}{
+		{t[0], "vanadium"},
+		{t[1], "google/foo"},
+		{t[2], "google/foo"},
+		{t[0], "google/$"},
+	}
+	for _, d := range testdata {
+		if err := br.Add(d.root, d.pattern); err != nil {
+			return fmt.Errorf("Add(%v, %q) failed: %s", d.root, d.pattern, err)
+		}
+	}
+	return nil
+}
+
+func (t *rootsTester) testRecognized(br security.BlessingRoots) error {
+	testdata := []struct {
+		root          security.PublicKey
+		recognized    []string
+		notRecognized []string
+	}{
+		{
+			root:          t[0],
+			recognized:    []string{"vanadium", "vanadium/foo", "vanadium/foo/bar", "google"},
+			notRecognized: []string{"google/foo", "foo", "foo/bar"},
+		},
+		{
+			root:          t[1],
+			recognized:    []string{"google/foo", "google/foo/bar"},
+			notRecognized: []string{"google", "google/bar", "vanadium", "vanadium/foo", "foo", "foo/bar"},
+		},
+		{
+			root:          t[2],
+			recognized:    []string{"google/foo", "google/foo/bar"},
+			notRecognized: []string{"google", "google/bar", "vanadium", "vanadium/foo", "foo", "foo/bar"},
+		},
+		{
+			root:          t[3],
+			recognized:    []string{},
+			notRecognized: []string{"vanadium", "vanadium/foo", "vanadium/bar", "google", "google/foo", "google/bar", "foo", "foo/bar"},
+		},
+	}
+	for _, d := range testdata {
+		for _, b := range d.recognized {
+			if err := br.Recognized(d.root, b); err != nil {
+				return fmt.Errorf("Recognized(%v, %q): got: %v, want nil", d.root, b, err)
+			}
+		}
+		for _, b := range d.notRecognized {
+			if err, want := br.Recognized(d.root, b), security.ErrUnrecognizedRoot.ID; verror.ErrorID(err) != want {
+				return fmt.Errorf("Recognized(%v, %q): got %v(errorid=%v), want errorid=%v", d.root, b, err, verror.ErrorID(err), want)
+			}
+		}
+	}
+	return nil
+}
+
+type pubKeySorter []security.PublicKey
+
+func (s pubKeySorter) Len() int           { return len(s) }
+func (s pubKeySorter) Less(i, j int) bool { return s[i].String() < s[j].String() }
+func (s pubKeySorter) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
+
+func (t *rootsTester) testDump(br security.BlessingRoots) error {
+	want := map[security.BlessingPattern][]security.PublicKey{
+		"google/foo": []security.PublicKey{t[1], t[2]},
+		"google/$":   []security.PublicKey{t[0]},
+		"vanadium":   []security.PublicKey{t[0]},
+	}
+	got := br.Dump()
+	sort.Sort(pubKeySorter(want["google/foo"]))
+	sort.Sort(pubKeySorter(got["google/foo"]))
+	if !reflect.DeepEqual(got, want) {
+		return fmt.Errorf("Dump(): got %v, want %v", got, want)
+	}
+	return nil
+}
+
+func TestBlessingRoots(t *testing.T) {
+	p, err := NewPrincipal()
+	if err != nil {
+		t.Fatal(err)
+	}
+	tester := newRootsTester()
+	if err := tester.add(p.Roots()); err != nil {
+		t.Fatal(err)
+	}
+	if err := tester.testRecognized(p.Roots()); err != nil {
+		t.Fatal(err)
+	}
+	if err := tester.testDump(p.Roots()); err != nil {
+		t.Fatal(err)
+	}
+}
+
+func TestBlessingRootsPersistence(t *testing.T) {
+	dir, err := ioutil.TempDir("", "TestBlessingRootsPersistence")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(dir)
+	tester := newRootsTester()
+	p, err := CreatePersistentPrincipal(dir, nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if err := tester.add(p.Roots()); err != nil {
+		t.Error(err)
+	}
+	if err := tester.testRecognized(p.Roots()); err != nil {
+		t.Error(err)
+	}
+	if err := tester.testDump(p.Roots()); err != nil {
+		t.Error(err)
+	}
+	// Recreate the principal (and thus BlessingRoots)
+	p2, err := LoadPersistentPrincipal(dir, nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if err := tester.testRecognized(p2.Roots()); err != nil {
+		t.Error(err)
+	}
+	if err := tester.testDump(p2.Roots()); err != nil {
+		t.Error(err)
+	}
+}
diff --git a/lib/security/blessingstore.go b/lib/security/blessingstore.go
new file mode 100644
index 0000000..0b09ee8
--- /dev/null
+++ b/lib/security/blessingstore.go
@@ -0,0 +1,324 @@
+// 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 security
+
+// TODO(ashankar,ataly): This file is a bit of a mess!! Define a serialization
+// format for the blessing store and rewrite this file before release!
+
+import (
+	"bytes"
+	"crypto/sha256"
+	"fmt"
+	"reflect"
+	"sort"
+	"strings"
+	"sync"
+	"time"
+
+	"v.io/v23/security"
+	"v.io/v23/verror"
+	"v.io/x/lib/vlog"
+	"v.io/x/ref/lib/security/serialization"
+)
+
+var (
+	errStoreAddMismatch        = verror.Register(pkgPath+".errStoreAddMismatch", verror.NoRetry, "{1:}{2:} blessing's public key does not match store's public key{:_}")
+	errBadBlessingPattern      = verror.Register(pkgPath+".errBadBlessingPattern", verror.NoRetry, "{1:}{2:} {3} is an invalid BlessingPattern{:_}")
+	errBlessingsNotForKey      = verror.Register(pkgPath+".errBlessingsNotForKey", verror.NoRetry, "{1:}{2:} read Blessings: {3} that are not for provided PublicKey{:_}")
+	errDataOrSignerUnspecified = verror.Register(pkgPath+".errDataOrSignerUnspecified", verror.NoRetry, "{1:}{2:} persisted data or signer is not specified{:_}")
+)
+
+const cacheKeyFormat = uint32(1)
+
+// blessingStore implements security.BlessingStore.
+type blessingStore struct {
+	publicKey  security.PublicKey
+	serializer SerializerReaderWriter
+	signer     serialization.Signer
+	mu         sync.RWMutex
+	state      blessingStoreState // GUARDED_BY(mu)
+}
+
+func (bs *blessingStore) Set(blessings security.Blessings, forPeers security.BlessingPattern) (security.Blessings, error) {
+	if !forPeers.IsValid() {
+		return security.Blessings{}, verror.New(errBadBlessingPattern, nil, forPeers)
+	}
+	if !blessings.IsZero() && !reflect.DeepEqual(blessings.PublicKey(), bs.publicKey) {
+		return security.Blessings{}, verror.New(errStoreAddMismatch, nil)
+	}
+	bs.mu.Lock()
+	defer bs.mu.Unlock()
+	old, hadold := bs.state.PeerBlessings[forPeers]
+	if !blessings.IsZero() {
+		bs.state.PeerBlessings[forPeers] = blessings
+	} else {
+		delete(bs.state.PeerBlessings, forPeers)
+	}
+	if err := bs.save(); err != nil {
+		if hadold {
+			bs.state.PeerBlessings[forPeers] = old
+		} else {
+			delete(bs.state.PeerBlessings, forPeers)
+		}
+		return security.Blessings{}, err
+	}
+	return old, nil
+}
+
+func (bs *blessingStore) ForPeer(peerBlessings ...string) security.Blessings {
+	bs.mu.RLock()
+	defer bs.mu.RUnlock()
+
+	var ret security.Blessings
+	for pattern, b := range bs.state.PeerBlessings {
+		if pattern.MatchedBy(peerBlessings...) {
+			if union, err := security.UnionOfBlessings(ret, b); err != nil {
+				vlog.Errorf("UnionOfBlessings(%v, %v) failed: %v, dropping the latter from BlessingStore.ForPeers(%v)", ret, b, err, peerBlessings)
+			} else {
+				ret = union
+			}
+		}
+	}
+	return ret
+}
+
+func (bs *blessingStore) Default() security.Blessings {
+	bs.mu.RLock()
+	defer bs.mu.RUnlock()
+	return bs.state.DefaultBlessings
+}
+
+func (bs *blessingStore) SetDefault(blessings security.Blessings) error {
+	bs.mu.Lock()
+	defer bs.mu.Unlock()
+	if !blessings.IsZero() && !reflect.DeepEqual(blessings.PublicKey(), bs.publicKey) {
+		return verror.New(errStoreAddMismatch, nil)
+	}
+	oldDefault := bs.state.DefaultBlessings
+	bs.state.DefaultBlessings = blessings
+	if err := bs.save(); err != nil {
+		bs.state.DefaultBlessings = oldDefault
+		return err
+	}
+	return nil
+}
+
+func (bs *blessingStore) PublicKey() security.PublicKey {
+	return bs.publicKey
+}
+
+func (bs *blessingStore) String() string {
+	return fmt.Sprintf("{state: %v, publicKey: %v}", bs.state, bs.publicKey)
+}
+
+func (bs *blessingStore) PeerBlessings() map[security.BlessingPattern]security.Blessings {
+	m := make(map[security.BlessingPattern]security.Blessings)
+	for pattern, b := range bs.state.PeerBlessings {
+		m[pattern] = b
+	}
+	return m
+}
+
+func (bs *blessingStore) CacheDischarge(discharge security.Discharge, caveat security.Caveat, impetus security.DischargeImpetus) {
+	id := discharge.ID()
+	tp := caveat.ThirdPartyDetails()
+	// Only add to the cache if the caveat did not require arguments.
+	if id == "" || tp == nil || tp.Requirements().ReportArguments {
+		return
+	}
+	key := dcacheKey(tp, impetus)
+	bs.mu.Lock()
+	defer bs.mu.Unlock()
+	old, hadold := bs.state.DischargeCache[key]
+	bs.state.DischargeCache[key] = discharge
+	if err := bs.save(); err != nil {
+		if hadold {
+			bs.state.DischargeCache[key] = old
+		} else {
+			delete(bs.state.DischargeCache, key)
+		}
+	}
+	return
+}
+
+func (bs *blessingStore) ClearDischarges(discharges ...security.Discharge) {
+	bs.mu.Lock()
+	bs.clearDischargesLocked(discharges...)
+	bs.mu.Unlock()
+	return
+}
+
+func (bs *blessingStore) clearDischargesLocked(discharges ...security.Discharge) {
+	for _, d := range discharges {
+		for k, cached := range bs.state.DischargeCache {
+			if cached.Equivalent(d) {
+				delete(bs.state.DischargeCache, k)
+			}
+		}
+	}
+}
+
+func (bs *blessingStore) Discharge(caveat security.Caveat, impetus security.DischargeImpetus) (out security.Discharge) {
+	defer bs.mu.Unlock()
+	bs.mu.Lock()
+	tp := caveat.ThirdPartyDetails()
+	if tp == nil || tp.Requirements().ReportArguments {
+		return
+	}
+	key := dcacheKey(tp, impetus)
+	if cached, exists := bs.state.DischargeCache[key]; exists {
+		out = cached
+		// If the discharge has expired, purge it from the cache.
+		if hasDischargeExpired(out) {
+			out = security.Discharge{}
+			bs.clearDischargesLocked(cached)
+		}
+	}
+	return
+}
+
+func hasDischargeExpired(dis security.Discharge) bool {
+	expiry := dis.Expiry()
+	if expiry.IsZero() {
+		return false
+	}
+	return expiry.Before(time.Now())
+}
+
+func dcacheKey(tp security.ThirdPartyCaveat, impetus security.DischargeImpetus) dischargeCacheKey {
+	// If the algorithm for computing dcacheKey changes, cacheKeyFormat must be changed as well.
+	id := tp.ID()
+	r := tp.Requirements()
+	var method, servers string
+	// We currently do not cache on impetus.Arguments because there it seems there is no
+	// general way to generate a key from them.
+	if r.ReportMethod {
+		method = impetus.Method
+	}
+	if r.ReportServer && len(impetus.Server) > 0 {
+		// Sort the server blessing patterns to increase cache usage.
+		var bps []string
+		for _, bp := range impetus.Server {
+			bps = append(bps, string(bp))
+		}
+		sort.Strings(bps)
+		servers = strings.Join(bps, ",")
+	}
+	h := sha256.New()
+	h.Write(hashString(id))
+	h.Write(hashString(method))
+	h.Write(hashString(servers))
+	var key [sha256.Size]byte
+	copy(key[:], h.Sum(nil))
+	return key
+}
+
+func hashString(d string) []byte {
+	h := sha256.Sum256([]byte(d))
+	return h[:]
+}
+
+// DebugString return a human-readable string encoding of the store
+// in the following format
+// Default Blessings <blessings>
+// Peer pattern   Blessings
+// <pattern>      <blessings>
+// ...
+// <pattern>      <blessings>
+func (bs *blessingStore) DebugString() string {
+	const format = "%-30s   %s\n"
+	buff := bytes.NewBufferString(fmt.Sprintf(format, "Default Blessings", bs.state.DefaultBlessings))
+
+	buff.WriteString(fmt.Sprintf(format, "Peer pattern", "Blessings"))
+
+	sorted := make([]string, 0, len(bs.state.PeerBlessings))
+	for k, _ := range bs.state.PeerBlessings {
+		sorted = append(sorted, string(k))
+	}
+	sort.Strings(sorted)
+	for _, pattern := range sorted {
+		buff.WriteString(fmt.Sprintf(format, pattern, bs.state.PeerBlessings[security.BlessingPattern(pattern)]))
+	}
+	return buff.String()
+}
+
+func (bs *blessingStore) save() error {
+	if (bs.signer == nil) && (bs.serializer == nil) {
+		return nil
+	}
+	data, signature, err := bs.serializer.Writers()
+	if err != nil {
+		return err
+	}
+	return encodeAndStore(bs.state, data, signature, bs.signer)
+}
+
+// newInMemoryBlessingStore returns an in-memory security.BlessingStore for a
+// principal with the provided PublicKey.
+//
+// The returned BlessingStore is initialized with an empty set of blessings.
+func newInMemoryBlessingStore(publicKey security.PublicKey) security.BlessingStore {
+	return &blessingStore{
+		publicKey: publicKey,
+		state: blessingStoreState{
+			PeerBlessings:  make(map[security.BlessingPattern]security.Blessings),
+			DischargeCache: make(map[dischargeCacheKey]security.Discharge),
+		},
+	}
+}
+
+func (bs *blessingStore) verifyState() error {
+	for _, b := range bs.state.PeerBlessings {
+		if !reflect.DeepEqual(b.PublicKey(), bs.publicKey) {
+			return verror.New(errBlessingsNotForKey, nil, b, bs.publicKey)
+		}
+	}
+	if !bs.state.DefaultBlessings.IsZero() && !reflect.DeepEqual(bs.state.DefaultBlessings.PublicKey(), bs.publicKey) {
+		return verror.New(errBlessingsNotForKey, nil, bs.state.DefaultBlessings, bs.publicKey)
+	}
+	return nil
+}
+
+func (bs *blessingStore) deserialize() error {
+	data, signature, err := bs.serializer.Readers()
+	if err != nil {
+		return err
+	}
+	if data == nil && signature == nil {
+		return nil
+	}
+	if err := decodeFromStorage(&bs.state, data, signature, bs.signer.PublicKey()); err != nil {
+		return err
+	}
+	if bs.state.CacheKeyFormat != cacheKeyFormat {
+		bs.state.CacheKeyFormat = cacheKeyFormat
+		bs.state.DischargeCache = make(map[dischargeCacheKey]security.Discharge)
+	}
+	return bs.verifyState()
+}
+
+// newPersistingBlessingStore returns a security.BlessingStore for a principal
+// that is initialized with the persisted data. The returned security.BlessingStore
+// also persists any updates to its state.
+func newPersistingBlessingStore(serializer SerializerReaderWriter, signer serialization.Signer) (security.BlessingStore, error) {
+	if serializer == nil || signer == nil {
+		return nil, verror.New(errDataOrSignerUnspecified, nil)
+	}
+	bs := &blessingStore{
+		publicKey:  signer.PublicKey(),
+		serializer: serializer,
+		signer:     signer,
+	}
+	if err := bs.deserialize(); err != nil {
+		return nil, err
+	}
+	if bs.state.PeerBlessings == nil {
+		bs.state.PeerBlessings = make(map[security.BlessingPattern]security.Blessings)
+	}
+	if bs.state.DischargeCache == nil {
+		bs.state.DischargeCache = make(map[dischargeCacheKey]security.Discharge)
+	}
+	return bs, nil
+}
diff --git a/lib/security/blessingstore_test.go b/lib/security/blessingstore_test.go
new file mode 100644
index 0000000..01cc3df
--- /dev/null
+++ b/lib/security/blessingstore_test.go
@@ -0,0 +1,268 @@
+// 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 security
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"reflect"
+	"testing"
+
+	"v.io/v23/security"
+)
+
+type storeTester struct {
+	forAll, forFoo, forBar, def security.Blessings
+	other                       security.Blessings // Blessings bound to a different principal.
+}
+
+func (t *storeTester) testSet(s security.BlessingStore) error {
+	testdata := []struct {
+		blessings security.Blessings
+		pattern   security.BlessingPattern
+		wantErr   string
+	}{
+		{t.forAll, security.AllPrincipals, ""},
+		{t.forAll, "$", ""},
+		{t.forFoo, "foo", ""},
+		{t.forBar, "bar/$", ""},
+		{t.other, security.AllPrincipals, "public key does not match"},
+		{t.forAll, "", "invalid BlessingPattern"},
+		{t.forAll, "foo/$/bar", "invalid BlessingPattern"},
+	}
+	added := make(map[security.BlessingPattern]security.Blessings)
+	for _, d := range testdata {
+		_, err := s.Set(d.blessings, d.pattern)
+		if merr := matchesError(err, d.wantErr); merr != nil {
+			return fmt.Errorf("Set(%v, %q): %v", d.blessings, d.pattern, merr)
+		}
+		if err == nil {
+			added[d.pattern] = d.blessings
+		}
+	}
+	m := s.PeerBlessings()
+	if !reflect.DeepEqual(added, m) {
+		return fmt.Errorf("PeerBlessings(%v) != added(%v)", m, added)
+	}
+	return nil
+}
+
+func (t *storeTester) testSetDefault(s security.BlessingStore) error {
+	if err := s.SetDefault(security.Blessings{}); err != nil {
+		return fmt.Errorf("SetDefault({}): %v", err)
+	}
+	if got := s.Default(); !got.IsZero() {
+		return fmt.Errorf("Default returned %v, wanted empty", got)
+	}
+	if err := s.SetDefault(t.def); err != nil {
+		return fmt.Errorf("SetDefault(%v): %v", t.def, err)
+	}
+	if got, want := s.Default(), t.def; !reflect.DeepEqual(got, want) {
+		return fmt.Errorf("Default returned %v, want %v", got, want)
+	}
+	// Changing default to an invalid blessing should not affect the existing default.
+	if err := matchesError(s.SetDefault(t.other), "public key does not match"); err != nil {
+		return err
+	}
+	if got, want := s.Default(), t.def; !reflect.DeepEqual(got, want) {
+		return fmt.Errorf("Default returned %v, want %v", got, want)
+	}
+	return nil
+}
+
+func (t *storeTester) testForPeer(s security.BlessingStore) error {
+	testdata := []struct {
+		peers     []string
+		blessings security.Blessings
+	}{
+		{nil, t.forAll},
+		{[]string{"baz"}, t.forAll},
+		{[]string{"foo"}, unionOfBlessings(t.forAll, t.forFoo)},
+		{[]string{"bar"}, unionOfBlessings(t.forAll, t.forBar)},
+		{[]string{"foo/foo"}, unionOfBlessings(t.forAll, t.forFoo)},
+		{[]string{"bar/baz"}, t.forAll},
+		{[]string{"foo/foo/bar"}, unionOfBlessings(t.forAll, t.forFoo)},
+		{[]string{"bar/foo", "foo"}, unionOfBlessings(t.forAll, t.forFoo)},
+		{[]string{"bar", "foo"}, unionOfBlessings(t.forAll, t.forFoo, t.forBar)},
+	}
+	for _, d := range testdata {
+		if got, want := s.ForPeer(d.peers...), d.blessings; !reflect.DeepEqual(got, want) {
+			return fmt.Errorf("ForPeer(%v): got: %v, want: %v", d.peers, got, want)
+		}
+	}
+	return nil
+}
+
+func newStoreTester(blessed security.Principal) *storeTester {
+	var (
+		blessing = func(root, extension string) security.Blessings {
+			blesser, err := NewPrincipal()
+			if err != nil {
+				panic(err)
+			}
+			blessing, err := blesser.Bless(blessed.PublicKey(), blessSelf(blesser, root), extension, security.UnconstrainedUse())
+			if err != nil {
+				panic(err)
+			}
+			return blessing
+		}
+	)
+	pother, err := NewPrincipal()
+	if err != nil {
+		panic(err)
+	}
+
+	s := &storeTester{}
+	s.forAll = blessing("bar", "alice")
+	s.forFoo = blessing("foo", "alice")
+	s.forBar = unionOfBlessings(s.forAll, s.forFoo)
+	s.def = blessing("default", "alice")
+	s.other = blessSelf(pother, "other")
+	return s
+}
+
+func TestBlessingStore(t *testing.T) {
+	p, err := NewPrincipal()
+	if err != nil {
+		t.Fatal(err)
+	}
+	tester := newStoreTester(p)
+	s := p.BlessingStore()
+	if err := tester.testSet(s); err != nil {
+		t.Error(err)
+	}
+	if err := tester.testForPeer(s); err != nil {
+		t.Error(err)
+	}
+	if err := tester.testSetDefault(s); err != nil {
+		t.Error(err)
+	}
+	testDischargeCache(t, s)
+}
+
+func TestBlessingStorePersistence(t *testing.T) {
+	dir, err := ioutil.TempDir("", "TestPersistingBlessingStore")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(dir)
+	p, err := CreatePersistentPrincipal(dir, nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	tester := newStoreTester(p)
+	s := p.BlessingStore()
+
+	if err := tester.testSet(s); err != nil {
+		t.Error(err)
+	}
+	if err := tester.testForPeer(s); err != nil {
+		t.Error(err)
+	}
+	if err := tester.testSetDefault(s); err != nil {
+		t.Error(err)
+	}
+	testDischargeCache(t, s)
+
+	// Recreate the BlessingStore from the directory.
+	p2, err := LoadPersistentPrincipal(dir, nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	s = p2.BlessingStore()
+	if err := tester.testForPeer(s); err != nil {
+		t.Error(err)
+	}
+	if got, want := s.Default(), tester.def; !reflect.DeepEqual(got, want) {
+		t.Fatalf("Default(): got: %v, want: %v", got, want)
+	}
+}
+
+func TestBlessingStoreSetOverridesOldSetting(t *testing.T) {
+	p, err := NewPrincipal()
+	if err != nil {
+		t.Fatal(err)
+	}
+	var (
+		alice = blessSelf(p, "alice")
+		bob   = blessSelf(p, "bob")
+		s     = p.BlessingStore()
+		empty security.Blessings
+	)
+	// {alice, bob} is shared with "alice", whilst {bob} is shared with "alice/tv"
+	if _, err := s.Set(alice, "alice/$"); err != nil {
+		t.Fatal(err)
+	}
+	if _, err := s.Set(bob, "alice"); err != nil {
+		t.Fatal(err)
+	}
+	if got, want := s.ForPeer("alice"), unionOfBlessings(alice, bob); !reflect.DeepEqual(got, want) {
+		t.Errorf("Got %v, want %v", got, want)
+	}
+	if got, want := s.ForPeer("alice/friend"), bob; !reflect.DeepEqual(got, want) {
+		t.Errorf("Got %v, want %v", got, want)
+	}
+
+	// Clear out the blessing associated with "alice".
+	// Now, bob should be shared with both alice and alice/friend.
+	if _, err := s.Set(empty, "alice/$"); err != nil {
+		t.Fatal(err)
+	}
+	if got, want := s.ForPeer("alice"), bob; !reflect.DeepEqual(got, want) {
+		t.Errorf("Got %v, want %v", got, want)
+	}
+	if got, want := s.ForPeer("alice/friend"), bob; !reflect.DeepEqual(got, want) {
+		t.Errorf("Got %v, want %v", got, want)
+	}
+
+	// Clearing out an association that doesn't exist should have no effect.
+	if _, err := s.Set(empty, "alice/enemy/$"); err != nil {
+		t.Fatal(err)
+	}
+	if got, want := s.ForPeer("alice"), bob; !reflect.DeepEqual(got, want) {
+		t.Errorf("Got %v, want %v", got, want)
+	}
+	if got, want := s.ForPeer("alice/friend"), bob; !reflect.DeepEqual(got, want) {
+		t.Errorf("Got %v, want %v", got, want)
+	}
+
+	// Clear everything
+	if _, err := s.Set(empty, "alice"); err != nil {
+		t.Fatal(err)
+	}
+	if got := s.ForPeer("alice"); !got.IsZero() {
+		t.Errorf("Got %v, want empty", got)
+	}
+	if got := s.ForPeer("alice/friend"); !got.IsZero() {
+		t.Errorf("Got %v, want empty", got)
+	}
+}
+
+func TestBlessingStoreSetReturnsOldValue(t *testing.T) {
+	p, err := NewPrincipal()
+	if err != nil {
+		t.Fatal(err)
+	}
+	var (
+		alice = blessSelf(p, "alice")
+		bob   = blessSelf(p, "bob")
+		s     = p.BlessingStore()
+		empty security.Blessings
+	)
+
+	if old, err := s.Set(alice, security.AllPrincipals); !reflect.DeepEqual(old, empty) || err != nil {
+		t.Errorf("Got (%v, %v), want (%v, nil)", old, err, empty)
+	}
+	if old, err := s.Set(alice, security.AllPrincipals); !reflect.DeepEqual(old, alice) || err != nil {
+		t.Errorf("Got (%v, %v) want (%v, nil)", old, err, alice)
+	}
+	if old, err := s.Set(bob, security.AllPrincipals); !reflect.DeepEqual(old, alice) || err != nil {
+		t.Errorf("Got (%v, %v) want (%v, nil)", old, err, alice)
+	}
+	if old, err := s.Set(empty, security.AllPrincipals); !reflect.DeepEqual(old, bob) || err != nil {
+		t.Errorf("Got (%v, %v) want (%v, nil)", old, err, bob)
+	}
+}
diff --git a/lib/security/discharge_cache_test.go b/lib/security/discharge_cache_test.go
new file mode 100644
index 0000000..f974d1a
--- /dev/null
+++ b/lib/security/discharge_cache_test.go
@@ -0,0 +1,105 @@
+// 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 security
+
+import (
+	"testing"
+	"time"
+
+	"v.io/v23/security"
+	"v.io/v23/vdl"
+)
+
+func testDischargeCache(t *testing.T, s security.BlessingStore) {
+	var (
+		discharger = mkPrincipal()
+		expiredCav = mkCaveat(security.NewPublicKeyCaveat(discharger.PublicKey(), "moline", security.ThirdPartyRequirements{}, security.UnconstrainedUse()))
+		argsCav    = mkCaveat(security.NewPublicKeyCaveat(discharger.PublicKey(), "peoria", security.ThirdPartyRequirements{ReportArguments: true}, security.UnconstrainedUse()))
+		methodCav  = mkCaveat(security.NewPublicKeyCaveat(discharger.PublicKey(), "moline", security.ThirdPartyRequirements{ReportMethod: true}, security.UnconstrainedUse()))
+		serverCav  = mkCaveat(security.NewPublicKeyCaveat(discharger.PublicKey(), "peoria", security.ThirdPartyRequirements{ReportServer: true}, security.UnconstrainedUse()))
+
+		dEmpty   = security.Discharge{}
+		dExpired = mkDischarge(discharger.MintDischarge(expiredCav, mkCaveat(security.NewExpiryCaveat(time.Now().Add(-1*time.Minute)))))
+		dArgs    = mkDischarge(discharger.MintDischarge(argsCav, security.UnconstrainedUse()))
+		dMethod  = mkDischarge(discharger.MintDischarge(methodCav, security.UnconstrainedUse()))
+		dServer  = mkDischarge(discharger.MintDischarge(serverCav, security.UnconstrainedUse()))
+
+		emptyImp       = security.DischargeImpetus{}
+		argsImp        = security.DischargeImpetus{Arguments: []*vdl.Value{&vdl.Value{}}}
+		methodImp      = security.DischargeImpetus{Method: "foo"}
+		otherMethodImp = security.DischargeImpetus{Method: "bar"}
+		serverImp      = security.DischargeImpetus{Server: []security.BlessingPattern{security.BlessingPattern("fooserver")}}
+		otherServerImp = security.DischargeImpetus{Server: []security.BlessingPattern{security.BlessingPattern("barserver")}}
+	)
+
+	// Discharges for different cavs should not be cached.
+	d := mkDischarge(discharger.MintDischarge(argsCav, security.UnconstrainedUse()))
+	s.CacheDischarge(d, argsCav, emptyImp)
+	if d := s.Discharge(methodCav, emptyImp); d.ID() != "" {
+		t.Errorf("Discharge for different caveat should not have been in cache")
+	}
+	s.ClearDischarges(d)
+
+	// Add some discharges into the cache.
+	s.CacheDischarge(dArgs, argsCav, argsImp)
+	s.CacheDischarge(dMethod, methodCav, methodImp)
+	s.CacheDischarge(dServer, serverCav, serverImp)
+	s.CacheDischarge(dExpired, expiredCav, emptyImp)
+
+	testCases := []struct {
+		caveat          security.Caveat           // caveat that we are fetching discharges for.
+		queryImpetus    security.DischargeImpetus // Impetus used to  query the cache.
+		cachedDischarge security.Discharge        // Discharge that we expect to be returned from the cache, nil if the discharge should not be cached.
+	}{
+		// Expired discharges should not be returned by the cache.
+		{expiredCav, emptyImp, dEmpty},
+
+		// Discharges with Impetuses that have Arguments should not be cached.
+		{argsCav, argsImp, dEmpty},
+
+		{methodCav, methodImp, dMethod},
+		{methodCav, otherMethodImp, dEmpty},
+		{methodCav, emptyImp, dEmpty},
+
+		{serverCav, serverImp, dServer},
+		{serverCav, otherServerImp, dEmpty},
+		{serverCav, emptyImp, dEmpty},
+	}
+
+	for i, test := range testCases {
+		out := s.Discharge(test.caveat, test.queryImpetus)
+		if got := out.ID(); got != test.cachedDischarge.ID() {
+			t.Errorf("#%d: got discharge %v, want %v, queried with %v", i, got, test.cachedDischarge.ID(), test.queryImpetus)
+		}
+	}
+	if t.Failed() {
+		t.Logf("dArgs.ID():    %v", dArgs.ID())
+		t.Logf("dMethod.ID():  %v", dMethod.ID())
+		t.Logf("dServer.ID():  %v", dServer.ID())
+		t.Logf("dExpired.ID(): %v", dExpired.ID())
+	}
+}
+
+func mkPrincipal() security.Principal {
+	p, err := NewPrincipal()
+	if err != nil {
+		panic(err)
+	}
+	return p
+}
+
+func mkDischarge(d security.Discharge, err error) security.Discharge {
+	if err != nil {
+		panic(err)
+	}
+	return d
+}
+
+func mkCaveat(c security.Caveat, err error) security.Caveat {
+	if err != nil {
+		panic(err)
+	}
+	return c
+}
diff --git a/lib/security/doc.go b/lib/security/doc.go
new file mode 100644
index 0000000..11c7325
--- /dev/null
+++ b/lib/security/doc.go
@@ -0,0 +1,7 @@
+// 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 security implements utilities for creating and using Vanadium
+// security primitives.
+package security
diff --git a/lib/security/prepare_discharges.go b/lib/security/prepare_discharges.go
new file mode 100644
index 0000000..8cdca3e
--- /dev/null
+++ b/lib/security/prepare_discharges.go
@@ -0,0 +1,171 @@
+// 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 security
+
+import (
+	"sync"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/security"
+	"v.io/v23/vdl"
+	"v.io/v23/vtrace"
+)
+
+// If this is attached to the context, we will not fetch discharges.
+// We use this to prevent ourselves from fetching discharges in the
+// process of fetching discharges, thus creating an infinite loop.
+type skipDischargesKey struct{}
+
+// PrepareDischarges retrieves the caveat discharges required for using blessings
+// at server. The discharges are either found in the dischargeCache, in the call
+// options, or requested from the discharge issuer indicated on the caveat.
+// Note that requesting a discharge is an rpc call, so one copy of this
+// function must be able to successfully terminate while another is blocked.
+func PrepareDischarges(
+	ctx *context.T,
+	blessings security.Blessings,
+	impetus security.DischargeImpetus,
+	expiryBuffer time.Duration) map[string]security.Discharge {
+	tpCavs := blessings.ThirdPartyCaveats()
+	if skip, _ := ctx.Value(skipDischargesKey{}).(bool); skip || len(tpCavs) == 0 {
+		return nil
+	}
+	ctx = context.WithValue(ctx, skipDischargesKey{}, true)
+
+	// Make a copy since this copy will be mutated.
+	var caveats []security.Caveat
+	var filteredImpetuses []security.DischargeImpetus
+	for _, cav := range tpCavs {
+		// It shouldn't happen, but in case there are non-third-party
+		// caveats, drop them.
+		if tp := cav.ThirdPartyDetails(); tp != nil {
+			caveats = append(caveats, cav)
+			filteredImpetuses = append(filteredImpetuses, filteredImpetus(tp.Requirements(), impetus))
+		}
+	}
+	bstore := v23.GetPrincipal(ctx).BlessingStore()
+	// Gather discharges from cache.
+	discharges, rem := discharges(bstore, caveats, impetus)
+	if rem > 0 {
+		// Fetch discharges for caveats for which no discharges were
+		// found in the cache.
+		if ctx != nil {
+			var span vtrace.Span
+			ctx, span = vtrace.WithNewSpan(ctx, "Fetching Discharges")
+			defer span.Finish()
+		}
+		fetchDischarges(ctx, caveats, filteredImpetuses, discharges, expiryBuffer)
+	}
+	ret := make(map[string]security.Discharge, len(discharges))
+	for _, d := range discharges {
+		if d.ID() != "" {
+			ret[d.ID()] = d
+		}
+	}
+	return ret
+}
+
+func discharges(bs security.BlessingStore, caveats []security.Caveat, imp security.DischargeImpetus) (out []security.Discharge, rem int) {
+	out = make([]security.Discharge, len(caveats))
+	for i := range caveats {
+		out[i] = bs.Discharge(caveats[i], imp)
+		if out[i].ID() == "" {
+			rem++
+		}
+	}
+	return
+}
+
+// fetchDischarges fills out by fetching discharges for caveats from the
+// appropriate discharge service. Since there may be dependencies in the
+// caveats, fetchDischarges keeps retrying until either all discharges can be
+// fetched or no new discharges are fetched.
+// REQUIRES: len(caveats) == len(out)
+// REQUIRES: caveats[i].ThirdPartyDetails() != nil for 0 <= i < len(caveats)
+func fetchDischarges(
+	ctx *context.T,
+	caveats []security.Caveat,
+	impetuses []security.DischargeImpetus,
+	out []security.Discharge,
+	expiryBuffer time.Duration) {
+	bstore := v23.GetPrincipal(ctx).BlessingStore()
+	var wg sync.WaitGroup
+	for {
+		type fetched struct {
+			idx       int
+			discharge security.Discharge
+			caveat    security.Caveat
+			impetus   security.DischargeImpetus
+		}
+		discharges := make(chan fetched, len(caveats))
+		want := 0
+		for i := range caveats {
+			if !shouldFetchDischarge(out[i], expiryBuffer) {
+				continue
+			}
+			want++
+			wg.Add(1)
+			go func(i int, ctx *context.T, cav security.Caveat) {
+				defer wg.Done()
+				tp := cav.ThirdPartyDetails()
+				var dis security.Discharge
+				ctx.VI(3).Infof("Fetching discharge for %v", tp)
+				if err := v23.GetClient(ctx).Call(ctx, tp.Location(), "Discharge",
+					[]interface{}{cav, impetuses[i]}, []interface{}{&dis}); err != nil {
+					ctx.VI(3).Infof("Discharge fetch for %v failed: %v", tp, err)
+					return
+				}
+				discharges <- fetched{i, dis, caveats[i], impetuses[i]}
+			}(i, ctx, caveats[i])
+		}
+		wg.Wait()
+		close(discharges)
+		var got int
+		for fetched := range discharges {
+			bstore.CacheDischarge(fetched.discharge, fetched.caveat, fetched.impetus)
+			out[fetched.idx] = fetched.discharge
+			got++
+		}
+		if want > 0 {
+			ctx.VI(3).Infof("fetchDischarges: got %d of %d discharge(s) (total %d caveats)", got, want, len(caveats))
+		}
+		if got == 0 || got == want {
+			return
+		}
+	}
+}
+
+// filteredImpetus returns a copy of 'before' after removing any values that are not required as per 'r'.
+func filteredImpetus(r security.ThirdPartyRequirements, before security.DischargeImpetus) (after security.DischargeImpetus) {
+	if r.ReportServer && len(before.Server) > 0 {
+		after.Server = make([]security.BlessingPattern, len(before.Server))
+		for i := range before.Server {
+			after.Server[i] = before.Server[i]
+		}
+	}
+	if r.ReportMethod {
+		after.Method = before.Method
+	}
+	if r.ReportArguments && len(before.Arguments) > 0 {
+		after.Arguments = make([]*vdl.Value, len(before.Arguments))
+		for i := range before.Arguments {
+			after.Arguments[i] = vdl.CopyValue(before.Arguments[i])
+		}
+	}
+	return
+}
+
+func shouldFetchDischarge(dis security.Discharge, expiryBuffer time.Duration) bool {
+	if dis.ID() == "" {
+		return true
+	}
+	expiry := dis.Expiry()
+	if expiry.IsZero() {
+		return false
+	}
+	return expiry.Before(time.Now().Add(expiryBuffer))
+}
diff --git a/lib/security/prepare_discharges_test.go b/lib/security/prepare_discharges_test.go
new file mode 100644
index 0000000..292c58e
--- /dev/null
+++ b/lib/security/prepare_discharges_test.go
@@ -0,0 +1,137 @@
+// 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 security_test
+
+import (
+	"fmt"
+	"testing"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	securitylib "v.io/x/ref/lib/security"
+	"v.io/x/ref/lib/xrpc"
+	_ "v.io/x/ref/runtime/factories/generic"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/testutil"
+)
+
+func init() {
+	test.Init()
+}
+
+type expiryDischarger struct {
+	called bool
+}
+
+func (ed *expiryDischarger) Discharge(ctx *context.T, call rpc.StreamServerCall, cav security.Caveat, _ security.DischargeImpetus) (security.Discharge, error) {
+	tp := cav.ThirdPartyDetails()
+	if tp == nil {
+		return security.Discharge{}, fmt.Errorf("discharger: %v does not represent a third-party caveat", cav)
+	}
+	if err := tp.Dischargeable(ctx, call.Security()); err != nil {
+		return security.Discharge{}, fmt.Errorf("third-party caveat %v cannot be discharged for this context: %v", cav, err)
+	}
+	expDur := 10 * time.Millisecond
+	if ed.called {
+		expDur = time.Second
+	}
+	expiry, err := security.NewExpiryCaveat(time.Now().Add(expDur))
+	if err != nil {
+		return security.Discharge{}, fmt.Errorf("failed to create an expiration on the discharge: %v", err)
+	}
+	d, err := call.Security().LocalPrincipal().MintDischarge(cav, expiry)
+	if err != nil {
+		return security.Discharge{}, err
+	}
+	ctx.Infof("got discharge on sever %#v", d)
+	ed.called = true
+	return d, nil
+}
+
+func TestPrepareDischarges(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	pclient := testutil.NewPrincipal("client")
+	cctx, err := v23.WithPrincipal(ctx, pclient)
+	if err != nil {
+		t.Fatal(err)
+	}
+	pdischarger := testutil.NewPrincipal("discharger")
+	dctx, err := v23.WithPrincipal(ctx, pdischarger)
+	if err != nil {
+		t.Fatal(err)
+	}
+	pclient.AddToRoots(pdischarger.BlessingStore().Default())
+	pclient.AddToRoots(v23.GetPrincipal(ctx).BlessingStore().Default())
+	pdischarger.AddToRoots(pclient.BlessingStore().Default())
+	pdischarger.AddToRoots(v23.GetPrincipal(ctx).BlessingStore().Default())
+
+	expcav, err := security.NewExpiryCaveat(time.Now().Add(time.Hour))
+	if err != nil {
+		t.Fatal(err)
+	}
+	tpcav, err := security.NewPublicKeyCaveat(
+		pdischarger.PublicKey(),
+		"discharger",
+		security.ThirdPartyRequirements{},
+		expcav)
+	if err != nil {
+		t.Fatal(err)
+	}
+	cbless, err := pclient.BlessSelf("clientcaveats", tpcav)
+	if err != nil {
+		t.Fatal(err)
+	}
+	tpid := tpcav.ThirdPartyDetails().ID()
+
+	v23.GetPrincipal(dctx)
+	_, err = xrpc.NewServer(dctx,
+		"discharger",
+		&expiryDischarger{},
+		security.AllowEveryone())
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// Fetch discharges for tpcav.
+	buffer := 100 * time.Millisecond
+	discharges := securitylib.PrepareDischarges(cctx, cbless,
+		security.DischargeImpetus{}, buffer)
+	if len(discharges) != 1 {
+		t.Errorf("Got %d discharges, expected 1.", len(discharges))
+	}
+	dis, has := discharges[tpid]
+	if !has {
+		t.Errorf("Got %#v, Expected discharge for %s", discharges, tpid)
+	}
+	// Check that the discharges is not yet expired, but is expired after 100 milliseconds.
+	expiry := dis.Expiry()
+	// The discharge should expire.
+	select {
+	case <-time.After(time.Now().Sub(expiry)):
+		break
+	case <-time.After(time.Second):
+		t.Fatalf("discharge didn't expire within a second")
+	}
+
+	// Preparing Discharges again to get fresh discharges.
+	discharges = securitylib.PrepareDischarges(cctx, cbless,
+		security.DischargeImpetus{}, buffer)
+	if len(discharges) != 1 {
+		t.Errorf("Got %d discharges, expected 1.", len(discharges))
+	}
+	dis, has = discharges[tpid]
+	if !has {
+		t.Errorf("Got %#v, Expected discharge for %s", discharges, tpid)
+	}
+	now := time.Now()
+	if expiry = dis.Expiry(); expiry.Before(now) {
+		t.Fatalf("discharge has expired %v, but should be fresh", dis)
+	}
+}
diff --git a/lib/security/principal.go b/lib/security/principal.go
new file mode 100644
index 0000000..003f23a
--- /dev/null
+++ b/lib/security/principal.go
@@ -0,0 +1,202 @@
+// 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 security
+
+import (
+	"crypto/ecdsa"
+	"os"
+	"path"
+
+	"v.io/v23/security"
+	"v.io/v23/verror"
+)
+
+const pkgPath = "v.io/x/ref/lib/security"
+
+var (
+	errCantCreateSigner      = verror.Register(pkgPath+".errCantCreateSigner", verror.NoRetry, "{1:}{2:} failed to create serialization.Signer{:_}")
+	errCantLoadBlessingRoots = verror.Register(pkgPath+".errCantLoadBlessingRoots", verror.NoRetry, "{1:}{2:} failed to load BlessingRoots{:_}")
+	errCantLoadBlessingStore = verror.Register(pkgPath+".errCantLoadBlessingStore", verror.NoRetry, "{1:}{2:} failed to load BlessingStore{:_}")
+	errCantInitPrivateKey    = verror.Register(pkgPath+".errCantInitPrivateKey", verror.NoRetry, "{1:}{2:} failed to initialize private key{:_}")
+	errNotADirectory         = verror.Register(pkgPath+".errNotADirectory", verror.NoRetry, "{1:}{2:} {3} is not a directory{:_}")
+	errCantCreate            = verror.Register(pkgPath+".errCantCreate", verror.NoRetry, "{1:}{2:} failed to create {3}{:_}")
+	errCantOpenForWriting    = verror.Register(pkgPath+".errCantOpenForWriting", verror.NoRetry, "{1:}{2:} failed to open {3} for writing{:_}")
+	errCantGenerateKey       = verror.Register(pkgPath+".errCantGenerateKey", verror.NoRetry, "{1:}{2:} failed to generate private key{:_}")
+	errCantSaveKey           = verror.Register(pkgPath+".errCantSaveKey", verror.NoRetry, "{1:}{2:} failed to save private key to {3}{:_}")
+)
+
+const (
+	blessingStoreDataFile = "blessingstore.data"
+	blessingStoreSigFile  = "blessingstore.sig"
+
+	blessingRootsDataFile = "blessingroots.data"
+	blessingRootsSigFile  = "blessingroots.sig"
+
+	privateKeyFile = "privatekey.pem"
+)
+
+// NewPrincipal mints a new private key and generates a principal based on
+// this key, storing its BlessingRoots and BlessingStore in memory.
+func NewPrincipal() (security.Principal, error) {
+	pub, priv, err := NewPrincipalKey()
+	if err != nil {
+		return nil, err
+	}
+	return security.CreatePrincipal(security.NewInMemoryECDSASigner(priv), newInMemoryBlessingStore(pub), newInMemoryBlessingRoots())
+}
+
+// PrincipalStateSerializer is used to persist BlessingRoots/BlessingStore state for
+// a principal with the provided SerializerReaderWriters.
+type PrincipalStateSerializer struct {
+	BlessingRoots SerializerReaderWriter
+	BlessingStore SerializerReaderWriter
+}
+
+// NewPrincipalStateSerializer is a convenience function that returns a serializer
+// for BlessingStore and BlessingRoots given a directory location. We create the
+// directory if it does not already exist.
+func NewPrincipalStateSerializer(dir string) (*PrincipalStateSerializer, error) {
+	if err := mkDir(dir); err != nil {
+		return nil, err
+	}
+	return &PrincipalStateSerializer{
+		BlessingRoots: NewFileSerializer(path.Join(dir, blessingRootsDataFile), path.Join(dir, blessingRootsSigFile)),
+		BlessingStore: NewFileSerializer(path.Join(dir, blessingStoreDataFile), path.Join(dir, blessingStoreSigFile)),
+	}, nil
+}
+
+// NewPrincipalFromSigner creates a new principal using the provided Signer. If previously
+// persisted state is available, we use the serializers to populate BlessingRoots/BlessingStore
+// for the Principal. If provided, changes to the state are persisted and committed with the
+// same serializers. Otherwise, the state (ie: BlessingStore, BlessingRoots) is kept in memory.
+func NewPrincipalFromSigner(signer security.Signer, state *PrincipalStateSerializer) (security.Principal, error) {
+	if state == nil {
+		return security.CreatePrincipal(signer, newInMemoryBlessingStore(signer.PublicKey()), newInMemoryBlessingRoots())
+	}
+	serializationSigner, err := security.CreatePrincipal(signer, nil, nil)
+	if err != nil {
+		return nil, verror.New(errCantCreateSigner, nil, err)
+	}
+	blessingRoots, err := newPersistingBlessingRoots(state.BlessingRoots, serializationSigner)
+	if err != nil {
+		return nil, verror.New(errCantLoadBlessingRoots, nil, err)
+	}
+	blessingStore, err := newPersistingBlessingStore(state.BlessingStore, serializationSigner)
+	if err != nil {
+		return nil, verror.New(errCantLoadBlessingStore, nil, err)
+	}
+	return security.CreatePrincipal(signer, blessingStore, blessingRoots)
+}
+
+// LoadPersistentPrincipal reads state for a principal (private key, BlessingRoots, BlessingStore)
+// from the provided directory 'dir' and commits all state changes to the same directory.
+// If private key file does not exist then an error 'err' is returned such that os.IsNotExist(err) is true.
+// If private key file exists then 'passphrase' must be correct, otherwise ErrBadPassphrase will be returned.
+func LoadPersistentPrincipal(dir string, passphrase []byte) (security.Principal, error) {
+	key, err := loadKeyFromDir(dir, passphrase)
+	if err != nil {
+		return nil, err
+	}
+	state, err := NewPrincipalStateSerializer(dir)
+	if err != nil {
+		return nil, err
+	}
+	return NewPrincipalFromSigner(security.NewInMemoryECDSASigner(key), state)
+}
+
+// CreatePersistentPrincipal creates a new principal (private key, BlessingRoots,
+// BlessingStore) and commits all state changes to the provided directory.
+//
+// The generated private key is serialized and saved encrypted if the 'passphrase'
+// is non-nil, and unencrypted otherwise.
+//
+// If the directory has any preexisting principal data, CreatePersistentPrincipal
+// will return an error.
+//
+// The specified directory may not exist, in which case it gets created by this
+// function.
+func CreatePersistentPrincipal(dir string, passphrase []byte) (principal security.Principal, err error) {
+	if err := mkDir(dir); err != nil {
+		return nil, err
+	}
+	key, err := initKey(dir, passphrase)
+	if err != nil {
+		return nil, verror.New(errCantInitPrivateKey, nil, err)
+	}
+	state, err := NewPrincipalStateSerializer(dir)
+	if err != nil {
+		return nil, err
+	}
+	return NewPrincipalFromSigner(security.NewInMemoryECDSASigner(key), state)
+}
+
+// SetDefaultBlessings sets the provided blessings as default and shareable with
+// all peers on provided principal's BlessingStore, and also adds it as a root to
+// the principal's BlessingRoots.
+func SetDefaultBlessings(p security.Principal, blessings security.Blessings) error {
+	if err := p.BlessingStore().SetDefault(blessings); err != nil {
+		return err
+	}
+	if _, err := p.BlessingStore().Set(blessings, security.AllPrincipals); err != nil {
+		return err
+	}
+	if err := p.AddToRoots(blessings); err != nil {
+		return err
+	}
+	return nil
+}
+
+// InitDefaultBlessings uses the provided principal to create a self blessing for name 'name',
+// sets it as default on the principal's BlessingStore and adds it as root to the principal's BlessingRoots.
+// TODO(ataly): Get rid this function given that we have SetDefaultBlessings.
+func InitDefaultBlessings(p security.Principal, name string) error {
+	blessing, err := p.BlessSelf(name)
+	if err != nil {
+		return err
+	}
+	return SetDefaultBlessings(p, blessing)
+}
+
+func mkDir(dir string) error {
+	if finfo, err := os.Stat(dir); err == nil {
+		if !finfo.IsDir() {
+			return verror.New(errNotADirectory, nil, dir)
+		}
+	} else if err := os.MkdirAll(dir, 0700); err != nil {
+		return verror.New(errCantCreate, nil, dir, err)
+	}
+	return nil
+}
+
+func loadKeyFromDir(dir string, passphrase []byte) (*ecdsa.PrivateKey, error) {
+	keyFile := path.Join(dir, privateKeyFile)
+	f, err := os.Open(keyFile)
+	if err != nil {
+		return nil, err
+	}
+	defer f.Close()
+	key, err := LoadPEMKey(f, passphrase)
+	if err != nil {
+		return nil, err
+	}
+	return key.(*ecdsa.PrivateKey), nil
+}
+
+func initKey(dir string, passphrase []byte) (*ecdsa.PrivateKey, error) {
+	keyFile := path.Join(dir, privateKeyFile)
+	f, err := os.OpenFile(keyFile, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600)
+	if err != nil {
+		return nil, verror.New(errCantOpenForWriting, nil, keyFile, err)
+	}
+	defer f.Close()
+	_, key, err := NewPrincipalKey()
+	if err != nil {
+		return nil, verror.New(errCantGenerateKey, nil, err)
+	}
+	if err := SavePEMKey(f, key, passphrase); err != nil {
+		return nil, verror.New(errCantSaveKey, nil, keyFile, err)
+	}
+	return key, nil
+}
diff --git a/lib/security/principal_test.go b/lib/security/principal_test.go
new file mode 100644
index 0000000..9a3234f
--- /dev/null
+++ b/lib/security/principal_test.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 security
+
+import (
+	"crypto/ecdsa"
+	"crypto/elliptic"
+	"crypto/rand"
+	"io/ioutil"
+	"os"
+	"path"
+	"reflect"
+	"testing"
+
+	"v.io/v23/security"
+	"v.io/v23/verror"
+)
+
+func TestLoadPersistentPrincipal(t *testing.T) {
+	// If the directory does not exist want os.IsNotExists.
+	_, err := LoadPersistentPrincipal("/tmp/fake/path/", nil)
+	if !os.IsNotExist(err) {
+		t.Errorf("invalid path should return does not exist error, instead got %v", err)
+	}
+	// If the key file exists and is unencrypted we should succeed.
+	dir := generatePEMFile(nil)
+	if _, err = LoadPersistentPrincipal(dir, nil); err != nil {
+		t.Errorf("unencrypted LoadPersistentPrincipal should have succeeded: %v", err)
+	}
+	os.RemoveAll(dir)
+
+	// If the private key file exists and is encrypted we should succeed with correct passphrase.
+	passphrase := []byte("passphrase")
+	incorrect_passphrase := []byte("incorrect_passphrase")
+	dir = generatePEMFile(passphrase)
+	if _, err = LoadPersistentPrincipal(dir, passphrase); err != nil {
+		t.Errorf("encrypted LoadPersistentPrincipal should have succeeded: %v", err)
+	}
+	// and fail with an incorrect passphrase.
+	if _, err = LoadPersistentPrincipal(dir, incorrect_passphrase); err == nil {
+		t.Errorf("encrypted LoadPersistentPrincipal with incorrect passphrase should fail")
+	}
+	// and return ErrBadPassphrase if the passphrase is nil.
+	if _, err = LoadPersistentPrincipal(dir, nil); verror.ErrorID(err) != ErrBadPassphrase.ID {
+		t.Errorf("encrypted LoadPersistentPrincipal with nil passphrase should return ErrBadPassphrase: %v", err)
+	}
+	os.RemoveAll(dir)
+}
+
+func TestCreatePersistentPrincipal(t *testing.T) {
+	tests := []struct {
+		Message, Passphrase []byte
+	}{
+		{[]byte("unencrypted"), nil},
+		{[]byte("encrypted"), []byte("passphrase")},
+	}
+	for _, test := range tests {
+		testCreatePersistentPrincipal(t, test.Message, test.Passphrase)
+	}
+}
+
+func testCreatePersistentPrincipal(t *testing.T, message, passphrase []byte) {
+	// Persistence of the BlessingRoots and BlessingStore objects is
+	// tested in other files. Here just test the persistence of the key.
+	dir, err := ioutil.TempDir("", "TestCreatePersistentPrincipal")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(dir)
+
+	p, err := CreatePersistentPrincipal(dir, passphrase)
+	if err != nil {
+		t.Fatal(err)
+	}
+	_, err = CreatePersistentPrincipal(dir, passphrase)
+	if err == nil {
+		t.Error("CreatePersistentPrincipal passed unexpectedly")
+	}
+
+	sig, err := p.Sign(message)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	p2, err := LoadPersistentPrincipal(dir, passphrase)
+	if err != nil {
+		t.Fatalf("%s failed: %v", message, err)
+	}
+	if !sig.Verify(p2.PublicKey(), message) {
+		t.Errorf("%s failed: p.PublicKey=%v, p2.PublicKey=%v", message, p.PublicKey(), p2.PublicKey())
+	}
+}
+
+func generatePEMFile(passphrase []byte) (dir string) {
+	dir, err := ioutil.TempDir("", "TestLoadPersistentPrincipal")
+	if err != nil {
+		panic(err)
+	}
+	key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+	if err != nil {
+		panic(err)
+	}
+	f, err := os.Create(path.Join(dir, privateKeyFile))
+	if err != nil {
+		panic(err)
+	}
+	defer f.Close()
+	if err = SavePEMKey(f, key, passphrase); err != nil {
+		panic(err)
+	}
+	return dir
+}
+
+func TestPrincipalBlessingsByName(t *testing.T) {
+	var p1, p2, p3 security.Principal
+	var err error
+
+	if p1, err = NewPrincipal(); err != nil {
+		t.Fatal(err)
+	}
+	if p2, err = NewPrincipal(); err != nil {
+		t.Fatal(err)
+	}
+	alice, err := p1.BlessSelf("alice")
+	if err != nil {
+		t.Fatal(err)
+	}
+	p2.AddToRoots(alice)
+	var aliceworkfriend, alicegymfriend, aliceworkboss security.Blessings
+
+	if aliceworkfriend, err = p1.Bless(p2.PublicKey(), alice, "work/friend", security.UnconstrainedUse()); err != nil {
+		t.Errorf("Bless(work/friend) failed: %v", err)
+	}
+	p2.BlessingStore().Set(aliceworkfriend, "alice/work/friend")
+	if alicegymfriend, err = p1.Bless(p2.PublicKey(), alice, "gym/friend", security.UnconstrainedUse()); err != nil {
+		t.Errorf("Bless(gym/friend) failed: %v", err)
+	}
+	p2.BlessingStore().Set(alicegymfriend, "alice/gym/friend")
+	if aliceworkboss, err = p1.Bless(p2.PublicKey(), alice, "work/boss", security.UnconstrainedUse()); err != nil {
+		t.Errorf("Bless(work/friend) failed: %v", err)
+	}
+	p2.BlessingStore().Set(aliceworkboss, "alice/work/boss")
+
+	// Blessing from an untrusted principal that should never be returned
+	if p3, err = NewPrincipal(); err != nil {
+		t.Fatal(err)
+	}
+	fake, err := p3.BlessSelf("alice")
+	if err != nil {
+		t.Fatal(err)
+	}
+	fakefriend, err := p3.Bless(p2.PublicKey(), fake, "work/friend", security.UnconstrainedUse())
+	if err != nil {
+		t.Errorf("Bless(work/friend) failed: %v", err)
+	}
+	_, err = p2.BlessingStore().Set(fakefriend, "fake/work/friend")
+
+	tests := []struct {
+		matched []security.Blessings
+		pattern security.BlessingPattern
+	}{
+		{
+			matched: []security.Blessings{aliceworkfriend, aliceworkboss},
+			pattern: "alice/work",
+		},
+		{
+			matched: []security.Blessings{aliceworkfriend},
+			pattern: "alice/work/friend",
+		},
+		{
+			matched: []security.Blessings{alicegymfriend},
+			pattern: "alice/gym/friend",
+		},
+		{
+			matched: []security.Blessings{aliceworkfriend, alicegymfriend, aliceworkboss},
+			pattern: "alice",
+		},
+		{
+			matched: []security.Blessings{aliceworkfriend, alicegymfriend, aliceworkboss},
+			pattern: security.AllPrincipals,
+		},
+		{
+			matched: nil,
+			pattern: "alice/school",
+		},
+	}
+
+	for _, test := range tests {
+		matched := p2.BlessingsByName(test.pattern)
+		if len(matched) != len(test.matched) {
+			t.Errorf("BlessingsByName(%s) did not return expected number of matches wanted:%d got:%d", test.pattern, len(test.matched), len(matched))
+		}
+		for _, m := range matched {
+			found := false
+			for _, tm := range test.matched {
+				if reflect.DeepEqual(m, tm) {
+					found = true
+					break
+				}
+			}
+			if !found {
+				t.Errorf("Invalid blessing was returned as a match:%v for pattern:%s", m, test.pattern)
+			}
+		}
+	}
+}
diff --git a/lib/security/securityflag/flag.go b/lib/security/securityflag/flag.go
new file mode 100644
index 0000000..022e643
--- /dev/null
+++ b/lib/security/securityflag/flag.go
@@ -0,0 +1,83 @@
+// 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 securityflag implements utilities for creating security objects based
+// on flags.
+package securityflag
+
+import (
+	"bytes"
+	"flag"
+	"os"
+
+	"v.io/v23/security"
+	"v.io/v23/security/access"
+	"v.io/v23/verror"
+	"v.io/x/ref/lib/flags"
+)
+
+const pkgPath = "v.io/x/ref/lib/security/securityflag"
+
+var (
+	errCantOpenPermissionsFile = verror.Register(pkgPath+".errCantOpenPermissionsFile", verror.NoRetry, "{1:}{2:} cannot open argument to --v23.permissions.file {3}{:_}")
+)
+
+var authFlags *flags.Flags
+
+func init() {
+	authFlags = flags.CreateAndRegister(flag.CommandLine, flags.Permissions)
+}
+
+// NewAuthorizerOrDie constructs an Authorizer based on the provided
+// "--v23.permissions.literal" or "--v23.permissions.file" flags. Otherwise it
+// creates a default Authorizer.
+func NewAuthorizerOrDie() security.Authorizer {
+	flags := authFlags.PermissionsFlags()
+	fname := flags.PermissionsFile("runtime")
+	literal := flags.PermissionsLiteral()
+
+	if fname == "" && literal == "" {
+		return nil
+	}
+	var a security.Authorizer
+	var err error
+	if literal == "" {
+		a, err = access.PermissionsAuthorizerFromFile(fname, access.TypicalTagType())
+	} else {
+		var perms access.Permissions
+		if perms, err = access.ReadPermissions(bytes.NewBufferString(literal)); err == nil {
+			a = access.TypicalTagTypePermissionsAuthorizer(perms)
+		}
+	}
+	if err != nil {
+		panic(err)
+	}
+	return a
+}
+
+// TODO(rjkroege): Refactor these two functions into one by making an Authorizer
+// use a Permissions accessor interface.
+// PermissionsFromFlag reads the same flags as NewAuthorizerOrDie but produces a
+// Permissions for callers that need more control of how Permissions are
+// managed.
+func PermissionsFromFlag() (access.Permissions, error) {
+	flags := authFlags.PermissionsFlags()
+	fname := flags.PermissionsFile("runtime")
+	literal := flags.PermissionsLiteral()
+
+	if fname == "" && literal == "" {
+		return nil, nil
+	}
+
+	if literal == "" {
+		file, err := os.Open(fname)
+		if err != nil {
+			return nil, verror.New(errCantOpenPermissionsFile, nil, fname)
+		}
+		defer file.Close()
+		return access.ReadPermissions(file)
+	} else {
+		return access.ReadPermissions(bytes.NewBufferString(literal))
+	}
+}
diff --git a/lib/security/securityflag/flag_test.go b/lib/security/securityflag/flag_test.go
new file mode 100644
index 0000000..47a00a9
--- /dev/null
+++ b/lib/security/securityflag/flag_test.go
@@ -0,0 +1,115 @@
+// 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 securityflag
+
+import (
+	"bytes"
+	"flag"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"reflect"
+	"testing"
+
+	"v.io/v23/security"
+	"v.io/v23/security/access"
+	"v.io/x/ref/test/modules"
+)
+
+//go:generate v23 test generate
+
+var (
+	perms1 = access.Permissions{}
+	perms2 = access.Permissions{
+		string(access.Read): access.AccessList{
+			In: []security.BlessingPattern{"v23/alice/$", "v23/bob"},
+		},
+		string(access.Write): access.AccessList{
+			In: []security.BlessingPattern{"v23/alice/$"},
+		},
+	}
+
+	expectedAuthorizer = map[string]security.Authorizer{
+		"empty":  access.TypicalTagTypePermissionsAuthorizer(perms1),
+		"perms2": access.TypicalTagTypePermissionsAuthorizer(perms2),
+	}
+)
+
+var permFromFlag = modules.Register(func(env *modules.Env, args ...string) error {
+	nfargs := flag.CommandLine.Args()
+	perms, err := PermissionsFromFlag()
+	if err != nil {
+		fmt.Fprintf(env.Stdout, "PermissionsFromFlag() failed: %v", err)
+		return nil
+	}
+	got := access.TypicalTagTypePermissionsAuthorizer(perms)
+	want := expectedAuthorizer[nfargs[0]]
+	if !reflect.DeepEqual(got, want) {
+		fmt.Fprintf(env.Stdout, "args %#v\n", args)
+		fmt.Fprintf(env.Stdout, "AuthorizerFromFlags() got Authorizer: %v, want: %v", got, want)
+	}
+	return nil
+}, "permFromFlag")
+
+func writePermissionsToFile(perms access.Permissions) (string, error) {
+	f, err := ioutil.TempFile("", "permissions")
+	if err != nil {
+		return "", err
+	}
+	defer f.Close()
+	if err := access.WritePermissions(f, perms); err != nil {
+		return "", err
+	}
+	return f.Name(), nil
+}
+
+func TestNewAuthorizerOrDie(t *testing.T) {
+	sh, err := modules.NewShell(nil, nil, testing.Verbose(), t)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	defer sh.Cleanup(os.Stderr, os.Stderr)
+
+	// Create a file.
+	filename, err := writePermissionsToFile(perms2)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.Remove(filename)
+
+	testdata := []struct {
+		prog  modules.Program
+		flags []string
+		auth  string
+	}{
+		{
+			prog:  permFromFlag,
+			flags: []string{"--v23.permissions.file", "runtime:" + filename},
+			auth:  "perms2",
+		},
+		{
+			prog:  permFromFlag,
+			flags: []string{"--v23.permissions.literal", "{}"},
+			auth:  "empty",
+		},
+		{
+			prog:  permFromFlag,
+			flags: []string{"--v23.permissions.literal", `{"Read": {"In":["v23/alice/$", "v23/bob"]}, "Write": {"In":["v23/alice/$"]}}`},
+			auth:  "perms2",
+		},
+	}
+	for _, td := range testdata {
+		fp := append(td.flags, td.auth)
+		h, err := sh.Start(nil, td.prog, fp...)
+		if err != nil {
+			t.Errorf("unexpected error: %s", err)
+		}
+		b := new(bytes.Buffer)
+		h.Shutdown(b, os.Stderr)
+		if got := b.String(); got != "" {
+			t.Errorf(got)
+		}
+	}
+}
diff --git a/lib/security/securityflag/v23_internal_test.go b/lib/security/securityflag/v23_internal_test.go
new file mode 100644
index 0000000..38cf5e0
--- /dev/null
+++ b/lib/security/securityflag/v23_internal_test.go
@@ -0,0 +1,22 @@
+// 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 securityflag
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	modules.DispatchAndExitIfChild()
+	os.Exit(m.Run())
+}
diff --git a/lib/security/serialization/serialization.go b/lib/security/serialization/serialization.go
new file mode 100644
index 0000000..f2df90d
--- /dev/null
+++ b/lib/security/serialization/serialization.go
@@ -0,0 +1,7 @@
+// 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 serialization implements utilities for reading and writing data with
+// signature-based integrity checking.
+package serialization
diff --git a/lib/security/serialization/serialization_test.go b/lib/security/serialization/serialization_test.go
new file mode 100644
index 0000000..420b4a1
--- /dev/null
+++ b/lib/security/serialization/serialization_test.go
@@ -0,0 +1,184 @@
+// 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 serialization_test
+
+import (
+	"bytes"
+	"crypto/ecdsa"
+	"crypto/elliptic"
+	"crypto/rand"
+	"fmt"
+	"io"
+	"io/ioutil"
+	mrand "math/rand"
+	"os"
+	"reflect"
+	"strings"
+	"testing"
+
+	"v.io/v23/security"
+	"v.io/x/ref/lib/security/serialization"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/testutil"
+)
+
+// We call our own TestMain here because v23 test generate causes an import cycle
+// in this package.
+func TestMain(m *testing.M) {
+	test.Init()
+	os.Exit(m.Run())
+}
+
+type bufferCloser struct {
+	bytes.Buffer
+}
+
+func (*bufferCloser) Close() error {
+	return nil
+}
+
+func signingWrite(d, s io.WriteCloser, signer serialization.Signer, writeList [][]byte, opts *serialization.Options) error {
+	swc, err := serialization.NewSigningWriteCloser(d, s, signer, opts)
+	if err != nil {
+		return fmt.Errorf("NewSigningWriteCloser failed: %s", err)
+	}
+	for _, b := range writeList {
+		if _, err := swc.Write(b); err != nil {
+			return fmt.Errorf("signingWriteCloser.Write failed: %s", err)
+		}
+	}
+	if err := swc.Close(); err != nil {
+		return fmt.Errorf("signingWriteCloser.Close failed: %s", err)
+	}
+	return nil
+}
+
+func verifyingRead(d, s io.Reader, key security.PublicKey) ([]byte, error) {
+	vr, err := serialization.NewVerifyingReader(d, s, key)
+	if err != nil {
+		return nil, fmt.Errorf("NewVerifyingReader failed: %s", err)
+	}
+	return ioutil.ReadAll(vr)
+}
+
+func newSigner() serialization.Signer {
+	key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+	if err != nil {
+		panic(err)
+	}
+	p, err := security.CreatePrincipal(security.NewInMemoryECDSASigner(key), nil, nil)
+	if err != nil {
+		panic(err)
+	}
+	return p
+}
+
+func matchesErrorPattern(err error, pattern string) bool {
+	if (len(pattern) == 0) != (err == nil) {
+		return false
+	}
+	return err == nil || strings.Index(err.Error(), pattern) >= 0
+}
+
+func TestRoundTrip(t *testing.T) {
+	testutil.InitRandGenerator(t.Logf)
+	signer := newSigner()
+	d, s := &bufferCloser{}, &bufferCloser{}
+
+	testdata := []struct {
+		writeList [][]byte
+		opts      *serialization.Options
+	}{
+		{[][]byte{testutil.RandomBytes(1)}, nil},
+		{[][]byte{testutil.RandomBytes(100)}, nil},
+		{[][]byte{testutil.RandomBytes(100)}, &serialization.Options{ChunkSizeBytes: 10}},
+		{[][]byte{testutil.RandomBytes(25), testutil.RandomBytes(15), testutil.RandomBytes(60), testutil.RandomBytes(5)}, &serialization.Options{ChunkSizeBytes: 7}},
+	}
+	for _, test := range testdata {
+		d.Reset()
+		s.Reset()
+
+		if err := signingWrite(d, s, signer, test.writeList, test.opts); err != nil {
+			t.Errorf("signingWrite(_, _, %v, %v) failed: %s", test.writeList, test.opts, err)
+			continue
+		}
+		dataRead, err := verifyingRead(d, s, signer.PublicKey())
+		if err != nil {
+			t.Errorf("verifyingRead failed: %s", err)
+			continue
+		}
+
+		dataWritten := bytes.Join(test.writeList, nil)
+		if !reflect.DeepEqual(dataRead, dataWritten) {
+			t.Errorf("Read-Write mismatch: data read: %v, data written: %v", dataRead, dataWritten)
+			continue
+		}
+	}
+}
+
+func TestIntegrityAndAuthenticity(t *testing.T) {
+	tamper := func(b []byte) []byte {
+		c := make([]byte, len(b))
+		copy(c, b)
+		c[mrand.Int()%len(b)] += 1
+		return c
+	}
+
+	signer := newSigner()
+	d, s := &bufferCloser{}, &bufferCloser{}
+	if err := signingWrite(d, s, signer, [][]byte{testutil.RandomBytes(100)}, &serialization.Options{ChunkSizeBytes: 7}); err != nil {
+		t.Fatalf("signingWrite failed: %s", err)
+	}
+
+	// copy the data and signature bytes written.
+	dataBytes := d.Bytes()
+	sigBytes := s.Bytes()
+
+	// Test that any tampering of the data bytes, or any change
+	// to the signer causes a verifyingRead to fail.
+	testdata := []struct {
+		dataBytes, sigBytes []byte
+		key                 security.PublicKey
+		wantErr             string
+	}{
+		{dataBytes, sigBytes, signer.PublicKey(), ""},
+		{dataBytes, sigBytes, newSigner().PublicKey(), "signature verification failed"},
+		{tamper(dataBytes), sigBytes, signer.PublicKey(), "data has been modified"},
+	}
+	for _, test := range testdata {
+		if _, err := verifyingRead(&bufferCloser{*bytes.NewBuffer(test.dataBytes)}, &bufferCloser{*bytes.NewBuffer(test.sigBytes)}, test.key); !matchesErrorPattern(err, test.wantErr) {
+			t.Errorf("verifyingRead: got error: %s, want to match: %v", err, test.wantErr)
+		}
+	}
+}
+
+func TestEdgeCases(t *testing.T) {
+	var d, s io.ReadWriteCloser
+	var signer serialization.Signer
+	var key security.PublicKey
+
+	for i := 0; i < 3; i++ {
+		d, s = &bufferCloser{}, &bufferCloser{}
+		signer = newSigner()
+		key = signer.PublicKey()
+
+		switch i {
+		case 0:
+			d = nil
+		case 1:
+			s = nil
+		case 2:
+			signer = nil
+			key = nil
+		}
+		matchErr := "cannot be nil"
+		if _, err := serialization.NewSigningWriteCloser(d, s, signer, nil); !matchesErrorPattern(err, matchErr) {
+			t.Errorf("NewSigningWriter(%v, %v, %v, ...) returned: %v, want to match: %v", d, s, signer, err, matchErr)
+		}
+		if _, err := serialization.NewVerifyingReader(d, s, key); !matchesErrorPattern(err, matchErr) {
+			t.Errorf("NewVerifyingReader(%v, %v, %v) returned: %v, want to match: %v", d, s, key, err, matchErr)
+		}
+	}
+}
diff --git a/lib/security/serialization/signing_writer.go b/lib/security/serialization/signing_writer.go
new file mode 100644
index 0000000..93245d7
--- /dev/null
+++ b/lib/security/serialization/signing_writer.go
@@ -0,0 +1,156 @@
+// 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 serialization
+
+import (
+	"bytes"
+	"crypto/sha256"
+	"encoding/binary"
+	"hash"
+	"io"
+
+	"v.io/v23/security"
+	"v.io/v23/verror"
+	"v.io/v23/vom"
+)
+
+var (
+	errCantBeNilSigner = verror.Register(pkgPath+".errCantBeNilSigner", verror.NoRetry, "{1:}{2:} data:{3} signature:{4} signer:{5} cannot be nil{:_}")
+	errCantSign        = verror.Register(pkgPath+".errCantSign", verror.NoRetry, "{1:}{2:} signing failed{:_}")
+)
+
+const defaultChunkSizeBytes = 1 << 20
+
+// signingWriter implements io.WriteCloser.
+type signingWriter struct {
+	data      io.WriteCloser
+	signature io.WriteCloser
+	signer    Signer
+
+	chunkSizeBytes int64
+	curChunk       bytes.Buffer
+	signatureHash  hash.Hash
+	sigEnc         *vom.Encoder
+}
+
+func (w *signingWriter) Write(p []byte) (int, error) {
+	bytesWritten := 0
+	for len(p) > 0 {
+		pLimited := p
+		curChunkFreeBytes := w.chunkSizeBytes - int64(w.curChunk.Len())
+		if int64(len(pLimited)) > curChunkFreeBytes {
+			pLimited = pLimited[:curChunkFreeBytes]
+		}
+
+		n, err := w.curChunk.Write(pLimited)
+		bytesWritten = bytesWritten + n
+		if err != nil {
+			return bytesWritten, err
+		}
+		p = p[n:]
+
+		if err := w.commitChunk(false); err != nil {
+			return bytesWritten, err
+		}
+	}
+	return bytesWritten, nil
+}
+
+func (w *signingWriter) Close() error {
+	if w.curChunk.Len() > 0 {
+		if err := w.commitChunk(true); err != nil {
+			defer w.close()
+			return err
+		}
+	}
+	if err := w.commitSignature(); err != nil {
+		defer w.close()
+		return err
+	}
+	return w.close()
+}
+
+// Options specifies parameters to tune a SigningWriteCloser.
+type Options struct {
+	// ChunkSizeBytes controls the maximum amount of memory devoted to buffering
+	// data provided to Write calls. See NewSigningWriteCloser.
+	ChunkSizeBytes int64
+}
+
+// Signer is the interface for digital signature operations used by NewSigningWriteCloser.
+type Signer interface {
+	Sign(message []byte) (security.Signature, error)
+	PublicKey() security.PublicKey
+}
+
+// NewSigningWriteCloser returns an io.WriteCloser that writes data along
+// with an appropriate signature that establishes the integrity and
+// authenticity of the data. It behaves as follows:
+// * A Write call writes chunks (of size provided by the Options or 1MB by default)
+//   of data to the provided data WriteCloser and a hash of the chunks to the provided
+//   signature WriteCloser.
+// * A Close call writes a signature (computed using the provided signer) of
+//   all the hashes written, and then closes the data and signature WriteClosers.
+func NewSigningWriteCloser(data, signature io.WriteCloser, s Signer, opts *Options) (io.WriteCloser, error) {
+	if (data == nil) || (signature == nil) || (s == nil) {
+		return nil, verror.New(errCantBeNilSigner, nil, data, signature, s)
+	}
+	enc := vom.NewEncoder(signature)
+	w := &signingWriter{data: data, signature: signature, signer: s, signatureHash: sha256.New(), chunkSizeBytes: defaultChunkSizeBytes, sigEnc: enc}
+
+	if opts != nil {
+		w.chunkSizeBytes = opts.ChunkSizeBytes
+	}
+
+	if err := w.commitHeader(); err != nil {
+		return nil, err
+	}
+	return w, nil
+}
+
+func (w *signingWriter) commitHeader() error {
+	if err := binary.Write(w.signatureHash, binary.LittleEndian, int64(w.chunkSizeBytes)); err != nil {
+		return err
+	}
+	if err := w.sigEnc.Encode(SignedHeader{w.chunkSizeBytes}); err != nil {
+		return err
+	}
+	return nil
+}
+
+func (w *signingWriter) commitChunk(force bool) error {
+	if !force && int64(w.curChunk.Len()) < w.chunkSizeBytes {
+		return nil
+	}
+
+	hashBytes := sha256.Sum256(w.curChunk.Bytes())
+	if _, err := io.CopyN(w.data, &w.curChunk, int64(w.curChunk.Len())); err != nil {
+		return err
+	}
+	if _, err := w.signatureHash.Write(hashBytes[:]); err != nil {
+		return err
+	}
+	return w.sigEnc.Encode(SignedDataHash{hashBytes})
+}
+
+func (w *signingWriter) commitSignature() error {
+	sig, err := w.signer.Sign(w.signatureHash.Sum(nil))
+	if err != nil {
+		return verror.New(errCantSign, nil, err)
+	}
+
+	return w.sigEnc.Encode(SignedDataSignature{sig})
+}
+
+func (w *signingWriter) close() error {
+	var closeErr error
+	if err := w.data.Close(); err != nil && closeErr == nil {
+		closeErr = err
+	}
+	if err := w.signature.Close(); err != nil && closeErr == nil {
+		closeErr = err
+	}
+	return closeErr
+}
diff --git a/lib/security/serialization/types.vdl b/lib/security/serialization/types.vdl
new file mode 100644
index 0000000..0f4a8fa
--- /dev/null
+++ b/lib/security/serialization/types.vdl
@@ -0,0 +1,19 @@
+// 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 serialization
+
+import "v.io/v23/security"
+
+type SignedHeader struct {
+  ChunkSizeBytes int64
+}
+
+type HashCode [32]byte
+
+// SignedData describes the information sent by a SigningWriter and read by VerifiyingReader.
+type SignedData union {
+	Signature security.Signature
+	Hash HashCode
+}
diff --git a/lib/security/serialization/types.vdl.go b/lib/security/serialization/types.vdl.go
new file mode 100644
index 0000000..3c361cd
--- /dev/null
+++ b/lib/security/serialization/types.vdl.go
@@ -0,0 +1,77 @@
+// 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 by the vanadium vdl tool.
+// Source: types.vdl
+
+package serialization
+
+import (
+	// VDL system imports
+	"v.io/v23/vdl"
+
+	// VDL user imports
+	"v.io/v23/security"
+)
+
+type SignedHeader struct {
+	ChunkSizeBytes int64
+}
+
+func (SignedHeader) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/lib/security/serialization.SignedHeader"`
+}) {
+}
+
+type HashCode [32]byte
+
+func (HashCode) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/lib/security/serialization.HashCode"`
+}) {
+}
+
+type (
+	// SignedData represents any single field of the SignedData union type.
+	//
+	// SignedData describes the information sent by a SigningWriter and read by VerifiyingReader.
+	SignedData interface {
+		// Index returns the field index.
+		Index() int
+		// Interface returns the field value as an interface.
+		Interface() interface{}
+		// Name returns the field name.
+		Name() string
+		// __VDLReflect describes the SignedData union type.
+		__VDLReflect(__SignedDataReflect)
+	}
+	// SignedDataSignature represents field Signature of the SignedData union type.
+	SignedDataSignature struct{ Value security.Signature }
+	// SignedDataHash represents field Hash of the SignedData union type.
+	SignedDataHash struct{ Value HashCode }
+	// __SignedDataReflect describes the SignedData union type.
+	__SignedDataReflect struct {
+		Name  string `vdl:"v.io/x/ref/lib/security/serialization.SignedData"`
+		Type  SignedData
+		Union struct {
+			Signature SignedDataSignature
+			Hash      SignedDataHash
+		}
+	}
+)
+
+func (x SignedDataSignature) Index() int                       { return 0 }
+func (x SignedDataSignature) Interface() interface{}           { return x.Value }
+func (x SignedDataSignature) Name() string                     { return "Signature" }
+func (x SignedDataSignature) __VDLReflect(__SignedDataReflect) {}
+
+func (x SignedDataHash) Index() int                       { return 1 }
+func (x SignedDataHash) Interface() interface{}           { return x.Value }
+func (x SignedDataHash) Name() string                     { return "Hash" }
+func (x SignedDataHash) __VDLReflect(__SignedDataReflect) {}
+
+func init() {
+	vdl.Register((*SignedHeader)(nil))
+	vdl.Register((*HashCode)(nil))
+	vdl.Register((*SignedData)(nil))
+}
diff --git a/lib/security/serialization/verifying_reader.go b/lib/security/serialization/verifying_reader.go
new file mode 100644
index 0000000..5427009
--- /dev/null
+++ b/lib/security/serialization/verifying_reader.go
@@ -0,0 +1,133 @@
+// 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 serialization
+
+import (
+	"bytes"
+	"crypto/sha256"
+	"encoding/binary"
+	"fmt"
+	"io"
+
+	"v.io/v23/security"
+	"v.io/v23/verror"
+	"v.io/v23/vom"
+)
+
+const pkgPath = "v.io/x/ref/lib/security/serialization"
+
+var (
+	errCantBeNilVerifier      = verror.Register(pkgPath+".errCantBeNilVerifier", verror.NoRetry, "{1:}{2:} data:{3} signature:{4} key:{5} cannot be nil{:_}")
+	errModifiedSinceWritten   = verror.Register(pkgPath+".errModifiedSinceWritten", verror.NoRetry, "{1:}{2:} data has been modified since being written{:_}")
+	errCantDecodeHeader       = verror.Register(pkgPath+".errCantDecodeHeader", verror.NoRetry, "{1:}{2:} failed to decode header{:_}")
+	errCantVerifySig          = verror.Register(pkgPath+".errCantVerifySig", verror.NoRetry, "{1:}{2:} signature verification failed{:_}")
+	errBadTypeFromSigReader   = verror.Register(pkgPath+".errBadTypeFromSigReader", verror.NoRetry, "{1:}{2:} invalid data of type: {3} read from signature Reader{:_}")
+	errUnexpectedDataAfterSig = verror.Register(pkgPath+".errUnexpectedDataAfterSig", verror.NoRetry, "{1:}{2:} unexpected data found after signature{:_}")
+)
+
+// verifyingReader implements io.Reader.
+type verifyingReader struct {
+	data io.Reader
+
+	chunkSizeBytes int64
+	curChunk       bytes.Buffer
+	hashes         bytes.Buffer
+}
+
+func (r *verifyingReader) Read(p []byte) (int, error) {
+	bytesRead := 0
+	for len(p) > 0 {
+		if err := r.readChunk(); err != nil {
+			return bytesRead, err
+		}
+
+		n, err := r.curChunk.Read(p)
+		bytesRead = bytesRead + n
+		if err != nil {
+			return bytesRead, err
+		}
+
+		p = p[n:]
+	}
+	return bytesRead, nil
+}
+
+// NewVerifyingReader returns an io.Reader that ensures that all data returned
+// by Read calls was written using a NewSigningWriter (by a principal possessing
+// a signer corresponding to the provided public key), and has not been modified
+// since (ensuring integrity and authenticity of data).
+func NewVerifyingReader(data, signature io.Reader, key security.PublicKey) (io.Reader, error) {
+	if (data == nil) || (signature == nil) || (key == nil) {
+		return nil, verror.New(errCantBeNilVerifier, nil, data, signature, key)
+	}
+	r := &verifyingReader{data: data}
+	if err := r.verifySignature(signature, key); err != nil {
+		return nil, err
+	}
+	return r, nil
+}
+
+func (r *verifyingReader) readChunk() error {
+	if r.curChunk.Len() > 0 {
+		return nil
+	}
+	hash := make([]byte, sha256.Size)
+	if _, err := r.hashes.Read(hash); err == io.EOF {
+		return nil
+	} else if err != nil {
+		return err
+	}
+
+	if _, err := io.CopyN(&r.curChunk, r.data, int64(r.chunkSizeBytes)); err != nil && err != io.EOF {
+		return err
+	}
+
+	if wantHash := sha256.Sum256(r.curChunk.Bytes()); !bytes.Equal(hash, wantHash[:]) {
+		return verror.New(errModifiedSinceWritten, nil)
+	}
+	return nil
+}
+
+func (r *verifyingReader) verifySignature(signature io.Reader, key security.PublicKey) error {
+	signatureHash := sha256.New()
+	dec := vom.NewDecoder(signature)
+	var h SignedHeader
+	if err := dec.Decode(&h); err != nil {
+		return verror.New(errCantDecodeHeader, nil, err)
+	}
+	r.chunkSizeBytes = h.ChunkSizeBytes
+	if err := binary.Write(signatureHash, binary.LittleEndian, r.chunkSizeBytes); err != nil {
+		return err
+	}
+
+	var signatureFound bool
+	for !signatureFound {
+		var i SignedData
+		if err := dec.Decode(&i); err == io.EOF {
+			break
+		} else if err != nil {
+			return err
+		}
+
+		switch v := i.(type) {
+		case SignedDataHash:
+			if _, err := io.MultiWriter(&r.hashes, signatureHash).Write(v.Value[:]); err != nil {
+				return err
+			}
+		case SignedDataSignature:
+			signatureFound = true
+			if !v.Value.Verify(key, signatureHash.Sum(nil)) {
+				return verror.New(errCantVerifySig, nil)
+			}
+		default:
+			return verror.New(errBadTypeFromSigReader, nil, fmt.Sprintf("%T", i))
+		}
+	}
+	// Verify that no more data can be read from the signature Reader.
+	if _, err := signature.Read(make([]byte, 1)); err != io.EOF {
+		return verror.New(errUnexpectedDataAfterSig, nil)
+	}
+	return nil
+}
diff --git a/lib/security/serializer_reader_writer.go b/lib/security/serializer_reader_writer.go
new file mode 100644
index 0000000..e23d82c
--- /dev/null
+++ b/lib/security/serializer_reader_writer.go
@@ -0,0 +1,65 @@
+// 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 security
+
+import (
+	"io"
+	"os"
+)
+
+// SerializerReaderWriter is a factory for managing the readers and writers used for
+// serialization and deserialization of signed data.
+type SerializerReaderWriter interface {
+	// Readers returns io.ReadCloser for reading serialized data and its
+	// integrity signature.
+	Readers() (data io.ReadCloser, signature io.ReadCloser, err error)
+	// Writers returns io.WriteCloser for writing serialized data and its
+	// integrity signature.
+	Writers() (data io.WriteCloser, signature io.WriteCloser, err error)
+}
+
+// FileSerializer implements SerializerReaderWriter that persists state to files.
+type FileSerializer struct {
+	dataFilePath      string
+	signatureFilePath string
+}
+
+// NewFileSerializer creates a FileSerializer with the given data and signature files.
+func NewFileSerializer(dataFilePath, signatureFilePath string) *FileSerializer {
+	return &FileSerializer{
+		dataFilePath:      dataFilePath,
+		signatureFilePath: signatureFilePath,
+	}
+}
+
+func (fs *FileSerializer) Readers() (io.ReadCloser, io.ReadCloser, error) {
+	data, err := os.Open(fs.dataFilePath)
+	if err != nil && !os.IsNotExist(err) {
+		return nil, nil, err
+	}
+	signature, err := os.Open(fs.signatureFilePath)
+	if err != nil && !os.IsNotExist(err) {
+		return nil, nil, err
+	}
+	if data == nil || signature == nil {
+		return nil, nil, nil
+	}
+	return data, signature, nil
+}
+
+func (fs *FileSerializer) Writers() (io.WriteCloser, io.WriteCloser, error) {
+	// Remove previous version of the files
+	os.Remove(fs.dataFilePath)
+	os.Remove(fs.signatureFilePath)
+	data, err := os.Create(fs.dataFilePath)
+	if err != nil {
+		return nil, nil, err
+	}
+	signature, err := os.Create(fs.signatureFilePath)
+	if err != nil {
+		return nil, nil, err
+	}
+	return data, signature, nil
+}
diff --git a/lib/security/storage.go b/lib/security/storage.go
new file mode 100644
index 0000000..07058f3
--- /dev/null
+++ b/lib/security/storage.go
@@ -0,0 +1,48 @@
+// 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 security
+
+import (
+	"io"
+
+	"v.io/v23/security"
+	"v.io/v23/verror"
+	"v.io/v23/vom"
+	"v.io/x/ref/lib/security/serialization"
+)
+
+var (
+	errBadDataOrSig = verror.Register(pkgPath+".errBadDataOrSig", verror.NoRetry, "{1:}{2:} invalid data/signature handles data:{3} sig:{4}{:_}")
+)
+
+func encodeAndStore(obj interface{}, data, signature io.WriteCloser, signer serialization.Signer) error {
+	if data == nil || signature == nil {
+		return verror.New(errBadDataOrSig, nil, data, signature)
+	}
+	swc, err := serialization.NewSigningWriteCloser(data, signature, signer, nil)
+	if err != nil {
+		return err
+	}
+	enc := vom.NewEncoder(swc)
+	if err := enc.Encode(obj); err != nil {
+		swc.Close()
+		return err
+	}
+	return swc.Close()
+}
+
+func decodeFromStorage(obj interface{}, data, signature io.ReadCloser, publicKey security.PublicKey) error {
+	if data == nil || signature == nil {
+		return verror.New(errBadDataOrSig, nil, data, signature)
+	}
+	defer data.Close()
+	defer signature.Close()
+	vr, err := serialization.NewVerifyingReader(data, signature, publicKey)
+	if err != nil {
+		return err
+	}
+	dec := vom.NewDecoder(vr)
+	return dec.Decode(obj)
+}
diff --git a/lib/security/testutil_test.go b/lib/security/testutil_test.go
new file mode 100644
index 0000000..fb7fee0
--- /dev/null
+++ b/lib/security/testutil_test.go
@@ -0,0 +1,79 @@
+// 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 security
+
+import (
+	"fmt"
+	"strings"
+
+	"v.io/v23/security"
+)
+
+func matchesError(got error, want string) error {
+	if (got == nil) && len(want) == 0 {
+		return nil
+	}
+	if got == nil {
+		return fmt.Errorf("Got nil error, wanted to match %q", want)
+	}
+	if !strings.Contains(got.Error(), want) {
+		return fmt.Errorf("Got error %q, wanted to match %q", got, want)
+	}
+	return nil
+}
+
+func newPrincipal(selfblessings ...string) (security.Principal, security.Blessings) {
+	p, err := NewPrincipal()
+	if err != nil {
+		panic(err)
+	}
+	if len(selfblessings) == 0 {
+		return p, security.Blessings{}
+	}
+	var def security.Blessings
+	for _, str := range selfblessings {
+		b, err := p.BlessSelf(str)
+		if err != nil {
+			panic(err)
+		}
+		if def, err = security.UnionOfBlessings(def, b); err != nil {
+			panic(err)
+		}
+	}
+	if err := p.AddToRoots(def); err != nil {
+		panic(err)
+	}
+	if err := p.BlessingStore().SetDefault(def); err != nil {
+		panic(err)
+	}
+	if _, err := p.BlessingStore().Set(def, security.AllPrincipals); err != nil {
+		panic(err)
+	}
+	return p, def
+}
+
+func bless(blesser, blessed security.Principal, with security.Blessings, extension string) security.Blessings {
+	b, err := blesser.Bless(blessed.PublicKey(), with, extension, security.UnconstrainedUse())
+	if err != nil {
+		panic(err)
+	}
+	return b
+}
+
+func blessSelf(p security.Principal, name string) security.Blessings {
+	b, err := p.BlessSelf(name)
+	if err != nil {
+		panic(err)
+	}
+	return b
+}
+
+func unionOfBlessings(blessings ...security.Blessings) security.Blessings {
+	b, err := security.UnionOfBlessings(blessings...)
+	if err != nil {
+		panic(err)
+	}
+	return b
+}
diff --git a/lib/security/type.vdl b/lib/security/type.vdl
new file mode 100644
index 0000000..9539700
--- /dev/null
+++ b/lib/security/type.vdl
@@ -0,0 +1,28 @@
+// 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 security
+
+import "v.io/v23/security"
+
+type blessingRootsState map[string][]security.BlessingPattern
+
+type blessingStoreState struct {
+	// PeerBlessings maps BlessingPatterns to the Blessings object that is to
+	// be shared with peers which present blessings of their own that match the
+	// pattern.
+	//
+	// All blessings bind to the same public key.
+	PeerBlessings map[security.BlessingPattern]security.WireBlessings
+	// DefaultBlessings is the default Blessings to be shared with peers for which
+	// no other information is available to select blessings.
+	DefaultBlessings security.WireBlessings
+	// DischargeCache is the cache of discharges.
+	DischargeCache map[dischargeCacheKey]security.WireDischarge
+	// CacheKeyFormat is the dischargeCacheKey format version. It should incremented
+	// any time the format of the dischargeCacheKey is changed.
+	CacheKeyFormat uint32
+}
+
+type dischargeCacheKey [32]byte
diff --git a/lib/security/type.vdl.go b/lib/security/type.vdl.go
new file mode 100644
index 0000000..5681451
--- /dev/null
+++ b/lib/security/type.vdl.go
@@ -0,0 +1,58 @@
+// 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 by the vanadium vdl tool.
+// Source: type.vdl
+
+package security
+
+import (
+	// VDL system imports
+	"v.io/v23/vdl"
+
+	// VDL user imports
+	"v.io/v23/security"
+)
+
+type blessingRootsState map[string][]security.BlessingPattern
+
+func (blessingRootsState) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/lib/security.blessingRootsState"`
+}) {
+}
+
+type blessingStoreState struct {
+	// PeerBlessings maps BlessingPatterns to the Blessings object that is to
+	// be shared with peers which present blessings of their own that match the
+	// pattern.
+	//
+	// All blessings bind to the same public key.
+	PeerBlessings map[security.BlessingPattern]security.Blessings
+	// DefaultBlessings is the default Blessings to be shared with peers for which
+	// no other information is available to select blessings.
+	DefaultBlessings security.Blessings
+	// DischargeCache is the cache of discharges.
+	DischargeCache map[dischargeCacheKey]security.Discharge
+	// CacheKeyFormat is the dischargeCacheKey format version. It should incremented
+	// any time the format of the dischargeCacheKey is changed.
+	CacheKeyFormat uint32
+}
+
+func (blessingStoreState) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/lib/security.blessingStoreState"`
+}) {
+}
+
+type dischargeCacheKey [32]byte
+
+func (dischargeCacheKey) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/lib/security.dischargeCacheKey"`
+}) {
+}
+
+func init() {
+	vdl.Register((*blessingRootsState)(nil))
+	vdl.Register((*blessingStoreState)(nil))
+	vdl.Register((*dischargeCacheKey)(nil))
+}
diff --git a/lib/security/util.go b/lib/security/util.go
new file mode 100644
index 0000000..350825a
--- /dev/null
+++ b/lib/security/util.go
@@ -0,0 +1,105 @@
+// 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 security
+
+import (
+	"crypto/ecdsa"
+	"crypto/elliptic"
+	"crypto/rand"
+	"crypto/x509"
+	"encoding/pem"
+	"fmt"
+	"io"
+	"io/ioutil"
+
+	"v.io/v23/security"
+	"v.io/v23/verror"
+)
+
+var (
+	// ErrBadPassphrase is a possible return error from LoadPEMKey()
+	ErrBadPassphrase = verror.Register(pkgPath+".errBadPassphrase", verror.NoRetry, "{1:}{2:} passphrase incorrect for decrypting private key{:_}")
+
+	errNoPEMKeyBlock       = verror.Register(pkgPath+".errNoPEMKeyBlock", verror.NoRetry, "{1:}{2:} no PEM key block read{:_}")
+	errPEMKeyBlockBadType  = verror.Register(pkgPath+".errPEMKeyBlockBadType", verror.NoRetry, "{1:}{2:} PEM key block has an unrecognized type{:_}")
+	errCantSaveKeyType     = verror.Register(pkgPath+".errCantSaveKeyType", verror.NoRetry, "{1:}{2:} key of type {3} cannot be saved{:_}")
+	errCantEncryptPEMBlock = verror.Register(pkgPath+".errCantEncryptPEMBlock", verror.NoRetry, "{1:}{2:} failed to encrypt pem block{:_}")
+)
+
+const ecPrivateKeyPEMType = "EC PRIVATE KEY"
+
+// NewPrincipalKey generates an ECDSA (public, private) key pair.
+func NewPrincipalKey() (security.PublicKey, *ecdsa.PrivateKey, error) {
+	priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+	if err != nil {
+		return nil, nil, err
+	}
+	return security.NewECDSAPublicKey(&priv.PublicKey), priv, nil
+}
+
+// LoadPEMKey loads a key from 'r'. returns ErrBadPassphrase for incorrect Passphrase.
+// If the key held in 'r' is unencrypted, 'passphrase' will be ignored.
+func LoadPEMKey(r io.Reader, passphrase []byte) (interface{}, error) {
+	pemBlockBytes, err := ioutil.ReadAll(r)
+	if err != nil {
+		return nil, err
+	}
+	pemBlock, _ := pem.Decode(pemBlockBytes)
+	if pemBlock == nil {
+		return nil, verror.New(errNoPEMKeyBlock, nil)
+	}
+	var data []byte
+	if x509.IsEncryptedPEMBlock(pemBlock) {
+		data, err = x509.DecryptPEMBlock(pemBlock, passphrase)
+		if err != nil {
+			return nil, verror.New(ErrBadPassphrase, nil)
+		}
+	} else {
+		data = pemBlock.Bytes
+	}
+
+	switch pemBlock.Type {
+	case ecPrivateKeyPEMType:
+		key, err := x509.ParseECPrivateKey(data)
+		if err != nil {
+			return nil, verror.New(ErrBadPassphrase, nil)
+		}
+		return key, nil
+	}
+	return nil, verror.New(errPEMKeyBlockBadType, nil, pemBlock.Type)
+}
+
+// SavePEMKey marshals 'key', encrypts it using 'passphrase', and saves the bytes to 'w' in PEM format.
+// If passphrase is nil, the key will not be encrypted.
+//
+// For example, if key is an ECDSA private key, it will be marshaled
+// in ASN.1, DER format, encrypted, and then written in a PEM block.
+func SavePEMKey(w io.Writer, key interface{}, passphrase []byte) error {
+	var data []byte
+	var err error
+	switch k := key.(type) {
+	case *ecdsa.PrivateKey:
+		if data, err = x509.MarshalECPrivateKey(k); err != nil {
+			return err
+		}
+	default:
+		return verror.New(errCantSaveKeyType, nil, fmt.Sprintf("%T", k))
+	}
+
+	var pemKey *pem.Block
+	if passphrase != nil {
+		pemKey, err = x509.EncryptPEMBlock(rand.Reader, ecPrivateKeyPEMType, data, passphrase, x509.PEMCipherAES256)
+		if err != nil {
+			return verror.New(errCantEncryptPEMBlock, nil, err)
+		}
+	} else {
+		pemKey = &pem.Block{
+			Type:  ecPrivateKeyPEMType,
+			Bytes: data,
+		}
+	}
+
+	return pem.Encode(w, pemKey)
+}
diff --git a/lib/security/util_test.go b/lib/security/util_test.go
new file mode 100644
index 0000000..8c3293e
--- /dev/null
+++ b/lib/security/util_test.go
@@ -0,0 +1,69 @@
+// 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 security
+
+import (
+	"bytes"
+	"crypto/ecdsa"
+	"crypto/elliptic"
+	"crypto/rand"
+	"reflect"
+	"testing"
+
+	"v.io/v23/verror"
+)
+
+func TestLoadSavePEMKey(t *testing.T) {
+	key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+	if err != nil {
+		t.Fatalf("Failed ecdsa.GenerateKey: %v", err)
+	}
+
+	var buf bytes.Buffer
+	if err := SavePEMKey(&buf, key, nil); err != nil {
+		t.Fatalf("Failed to save ECDSA private key: %v", err)
+	}
+
+	loadedKey, err := LoadPEMKey(&buf, nil)
+	if !reflect.DeepEqual(loadedKey, key) {
+		t.Fatalf("Got key %v, but want %v", loadedKey, key)
+	}
+}
+
+func TestLoadSavePEMKeyWithPassphrase(t *testing.T) {
+	pass := []byte("openSesame")
+	incorrect_pass := []byte("wrongPassphrase")
+	key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+	if err != nil {
+		t.Fatalf("Failed ecdsa.GenerateKey: %v", err)
+	}
+	var buf bytes.Buffer
+
+	// Test incorrect passphrase.
+	if err := SavePEMKey(&buf, key, pass); err != nil {
+		t.Fatalf("Failed to save ECDSA private key: %v", err)
+	}
+	loadedKey, err := LoadPEMKey(&buf, incorrect_pass)
+	if loadedKey != nil && err != nil {
+		t.Errorf("expected (nil, err != nil) received (%v,%v)", loadedKey, err)
+	}
+
+	// Test correct password.
+	if err := SavePEMKey(&buf, key, pass); err != nil {
+		t.Fatalf("Failed to save ECDSA private key: %v", err)
+	}
+	loadedKey, err = LoadPEMKey(&buf, pass)
+	if !reflect.DeepEqual(loadedKey, key) {
+		t.Fatalf("Got key %v, but want %v", loadedKey, key)
+	}
+
+	// Test nil passphrase.
+	if err := SavePEMKey(&buf, key, pass); err != nil {
+		t.Fatalf("Failed to save ECDSA private key: %v", err)
+	}
+	if loadedKey, err = LoadPEMKey(&buf, nil); loadedKey != nil || verror.ErrorID(err) != ErrBadPassphrase.ID {
+		t.Fatalf("expected(nil, ErrBadPassphrase), instead got (%v, %v)", loadedKey, err)
+	}
+}
diff --git a/lib/signals/signals.go b/lib/signals/signals.go
new file mode 100644
index 0000000..61600cb
--- /dev/null
+++ b/lib/signals/signals.go
@@ -0,0 +1,112 @@
+// 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 signals implements utilities for managing process shutdown with
+// support for signal-handling.
+package signals
+
+// TODO(caprita): Rename the function to Shutdown() and the package to shutdown
+// since it's not just signals anymore.
+
+import (
+	"os"
+	"os/signal"
+	"syscall"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+)
+
+type stopSignal string
+
+func (stopSignal) Signal()          {}
+func (s stopSignal) String() string { return string(s) }
+
+const (
+	STOP               = stopSignal("")
+	DoubleStopExitCode = 1
+)
+
+// TODO(caprita): Needless to say, this is a hack.  The motivator was getting
+// the device manager (run by the security agent) to shut down cleanly when the
+// process group containing both the agent and device manager receives a signal
+// (and the agent propagates that signal to the child).  We should be able to
+// finesse this by demonizing the device manager and/or being smarter about how
+// and when the agent sends the signal to the child.
+
+// SameSignalTimeWindow specifies the time window during which multiple
+// deliveries of the same signal are counted as one signal.  If set to zero, no
+// such de-duping occurs.  This is useful in situations where a process receives
+// a signal explicitly sent by its parent when the parent receives the signal,
+// but also receives it independently by virtue of being part of the same
+// process group.
+//
+// This is a variable, so that I can be set appropriately.  Note, there is no
+// locking around it, the assumption being that it's set during initialization
+// and never reset afterwards.
+var SameSignalTimeWindow time.Duration
+
+// defaultSignals returns a set of platform-specific signals that an application
+// is encouraged to listen on.
+func defaultSignals() []os.Signal {
+	return []os.Signal{syscall.SIGTERM, syscall.SIGINT, STOP}
+}
+
+// ShutdownOnSignals registers signal handlers for the specified signals, or, if
+// none are specified, the default signals.  The first signal received will be
+// made available on the returned channel; upon receiving a second signal, the
+// process will exit.
+func ShutdownOnSignals(ctx *context.T, signals ...os.Signal) <-chan os.Signal {
+	if len(signals) == 0 {
+		signals = defaultSignals()
+	}
+	// At least a buffer of length two so that we don't drop the first two
+	// signals we get on account of the channel being full.
+	ch := make(chan os.Signal, 2)
+	sawStop := false
+	var signalsNoStop []os.Signal
+	for _, s := range signals {
+		switch s {
+		case STOP:
+			if !sawStop {
+				sawStop = true
+				if ctx != nil {
+					stopWaiter := make(chan string, 1)
+					v23.GetAppCycle(ctx).WaitForStop(ctx, stopWaiter)
+					go func() {
+						for {
+							ch <- stopSignal(<-stopWaiter)
+						}
+					}()
+				}
+			}
+		default:
+			signalsNoStop = append(signalsNoStop, s)
+		}
+	}
+	if len(signalsNoStop) > 0 {
+		signal.Notify(ch, signalsNoStop...)
+	}
+	// At least a buffer of length one so that we don't block on ret <- sig.
+	ret := make(chan os.Signal, 1)
+	go func() {
+		// First signal received.
+		sig := <-ch
+		sigTime := time.Now()
+		ret <- sig
+		// Wait for a second signal, and force an exit if the process is
+		// still executing cleanup code.
+		for {
+			secondSig := <-ch
+			// If signal de-duping is enabled, ignore the signal if
+			// it's the same signal and has occured within the
+			// specified time window.
+			if SameSignalTimeWindow <= 0 || secondSig.String() != sig.String() || sigTime.Add(SameSignalTimeWindow).Before(time.Now()) {
+				os.Exit(DoubleStopExitCode)
+			}
+		}
+	}()
+	return ret
+}
diff --git a/lib/signals/signals_test.go b/lib/signals/signals_test.go
new file mode 100644
index 0000000..46bbefb
--- /dev/null
+++ b/lib/signals/signals_test.go
@@ -0,0 +1,382 @@
+// 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 signals
+
+import (
+	"bufio"
+	"fmt"
+	"io"
+	"os"
+	"path/filepath"
+	"runtime"
+	"syscall"
+	"testing"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/services/appcycle"
+	"v.io/v23/vtrace"
+	"v.io/x/ref/lib/mgmt"
+	"v.io/x/ref/lib/security/securityflag"
+	"v.io/x/ref/lib/xrpc"
+	"v.io/x/ref/services/device"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
+
+	_ "v.io/x/ref/runtime/factories/generic"
+)
+
+//go:generate v23 test generate
+
+func stopLoop(stop func(), stdin io.Reader, ch chan<- struct{}) {
+	scanner := bufio.NewScanner(stdin)
+	for scanner.Scan() {
+		switch scanner.Text() {
+		case "close":
+			close(ch)
+			return
+		case "stop":
+			stop()
+		}
+	}
+}
+
+func program(stdin io.Reader, stdout io.Writer, signals ...os.Signal) {
+	ctx, shutdown := test.V23Init()
+	closeStopLoop := make(chan struct{})
+	// obtain ac here since stopLoop may execute after shutdown is called below
+	ac := v23.GetAppCycle(ctx)
+	go stopLoop(func() { ac.Stop(ctx) }, stdin, closeStopLoop)
+	wait := ShutdownOnSignals(ctx, signals...)
+	fmt.Fprintf(stdout, "ready\n")
+	fmt.Fprintf(stdout, "received signal %s\n", <-wait)
+	shutdown()
+	<-closeStopLoop
+}
+
+var handleDefaults = modules.Register(func(env *modules.Env, args ...string) error {
+	program(env.Stdin, env.Stdout)
+	return nil
+}, "handleDefaults")
+
+var handleCustom = modules.Register(func(env *modules.Env, args ...string) error {
+	program(env.Stdin, env.Stdout, syscall.SIGABRT)
+	return nil
+}, "handleCustom")
+
+var handleCustomWithStop = modules.Register(func(env *modules.Env, args ...string) error {
+	program(env.Stdin, env.Stdout, STOP, syscall.SIGABRT, syscall.SIGHUP)
+	return nil
+}, "handleCustomWithStop")
+
+var handleDefaultsIgnoreChan = modules.Register(func(env *modules.Env, args ...string) error {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	closeStopLoop := make(chan struct{})
+	// obtain ac here since stopLoop may execute after shutdown is called below
+	ac := v23.GetAppCycle(ctx)
+	go stopLoop(func() { ac.Stop(ctx) }, env.Stdin, closeStopLoop)
+	ShutdownOnSignals(ctx)
+	fmt.Fprintf(env.Stdout, "ready\n")
+	<-closeStopLoop
+	return nil
+}, "handleDefaultsIgnoreChan")
+
+func isSignalInSet(sig os.Signal, set []os.Signal) bool {
+	for _, s := range set {
+		if sig == s {
+			return true
+		}
+	}
+	return false
+}
+
+func checkSignalIsDefault(t *testing.T, sig os.Signal) {
+	if !isSignalInSet(sig, defaultSignals()) {
+		t.Errorf("signal %s not in default signal set, as expected", sig)
+	}
+}
+
+func checkSignalIsNotDefault(t *testing.T, sig os.Signal) {
+	if isSignalInSet(sig, defaultSignals()) {
+		t.Errorf("signal %s unexpectedly in default signal set", sig)
+	}
+}
+
+func newShell(t *testing.T, ctx *context.T, prog modules.Program) (*modules.Shell, modules.Handle) {
+	sh, err := modules.NewShell(ctx, nil, testing.Verbose(), t)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	handle, err := sh.Start(nil, prog)
+	if err != nil {
+		sh.Cleanup(os.Stderr, os.Stderr)
+		t.Fatalf("unexpected error: %s", err)
+		return nil, nil
+	}
+	return sh, handle
+}
+
+// TestCleanShutdownSignal verifies that sending a signal to a child that
+// handles it by default causes the child to shut down cleanly.
+func TestCleanShutdownSignal(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	sh, h := newShell(t, ctx, handleDefaults)
+	defer sh.Cleanup(os.Stderr, os.Stderr)
+	h.Expect("ready")
+	checkSignalIsDefault(t, syscall.SIGINT)
+	syscall.Kill(h.Pid(), syscall.SIGINT)
+	h.Expectf("received signal %s", syscall.SIGINT)
+	fmt.Fprintf(h.Stdin(), "close\n")
+	h.ExpectEOF()
+}
+
+// TestCleanShutdownStop verifies that sending a stop comamnd to a child that
+// handles stop commands by default causes the child to shut down cleanly.
+func TestCleanShutdownStop(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	sh, h := newShell(t, ctx, handleDefaults)
+	defer sh.Cleanup(os.Stderr, os.Stderr)
+	h.Expect("ready")
+	fmt.Fprintf(h.Stdin(), "stop\n")
+	h.Expectf("received signal %s", v23.LocalStop)
+	fmt.Fprintf(h.Stdin(), "close\n")
+	h.ExpectEOF()
+
+}
+
+// TestCleanShutdownStopCustom verifies that sending a stop comamnd to a child
+// that handles stop command as part of a custom set of signals handled, causes
+// the child to shut down cleanly.
+func TestCleanShutdownStopCustom(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	sh, h := newShell(t, ctx, handleCustomWithStop)
+	defer sh.Cleanup(os.Stderr, os.Stderr)
+	h.Expect("ready")
+	fmt.Fprintf(h.Stdin(), "stop\n")
+	h.Expectf("received signal %s", v23.LocalStop)
+	fmt.Fprintf(h.Stdin(), "close\n")
+	h.ExpectEOF()
+}
+
+func testExitStatus(t *testing.T, h modules.Handle, code int) {
+	h.ExpectEOF()
+	_, file, line, _ := runtime.Caller(1)
+	file = filepath.Base(file)
+	if got, want := h.Shutdown(os.Stdout, os.Stderr), fmt.Errorf("exit status %d", code); got.Error() != want.Error() {
+		t.Errorf("%s:%d: got %q, want %q", file, line, got, want)
+	}
+}
+
+// TestStopNoHandler verifies that sending a stop command to a child that does
+// not handle stop commands causes the child to exit immediately.
+func TestStopNoHandler(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	sh, h := newShell(t, ctx, handleCustom)
+	defer sh.Cleanup(os.Stderr, os.Stderr)
+	h.Expect("ready")
+	fmt.Fprintf(h.Stdin(), "stop\n")
+	testExitStatus(t, h, v23.UnhandledStopExitCode)
+}
+
+// TestDoubleSignal verifies that sending a succession of two signals to a child
+// that handles these signals by default causes the child to exit immediately
+// upon receiving the second signal.
+func TestDoubleSignal(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	sh, h := newShell(t, ctx, handleDefaults)
+	defer sh.Cleanup(os.Stderr, os.Stderr)
+	h.Expect("ready")
+	checkSignalIsDefault(t, syscall.SIGTERM)
+	syscall.Kill(h.Pid(), syscall.SIGTERM)
+	h.Expectf("received signal %s", syscall.SIGTERM)
+	checkSignalIsDefault(t, syscall.SIGINT)
+	syscall.Kill(h.Pid(), syscall.SIGINT)
+	testExitStatus(t, h, DoubleStopExitCode)
+}
+
+// TestSignalAndStop verifies that sending a signal followed by a stop command
+// to a child that handles these by default causes the child to exit immediately
+// upon receiving the stop command.
+func TestSignalAndStop(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	sh, h := newShell(t, ctx, handleDefaults)
+	defer sh.Cleanup(os.Stderr, os.Stderr)
+	h.Expect("ready")
+	checkSignalIsDefault(t, syscall.SIGTERM)
+	syscall.Kill(h.Pid(), syscall.SIGTERM)
+	h.Expectf("received signal %s", syscall.SIGTERM)
+	fmt.Fprintf(h.Stdin(), "stop\n")
+	testExitStatus(t, h, DoubleStopExitCode)
+}
+
+// TestDoubleStop verifies that sending a succession of stop commands to a child
+// that handles stop commands by default causes the child to exit immediately
+// upon receiving the second stop command.
+func TestDoubleStop(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	sh, h := newShell(t, ctx, handleDefaults)
+	defer sh.Cleanup(os.Stderr, os.Stderr)
+	h.Expect("ready")
+	fmt.Fprintf(h.Stdin(), "stop\n")
+	h.Expectf("received signal %s", v23.LocalStop)
+	fmt.Fprintf(h.Stdin(), "stop\n")
+	testExitStatus(t, h, DoubleStopExitCode)
+}
+
+// TestSendUnhandledSignal verifies that sending a signal that the child does
+// not handle causes the child to exit as per the signal being sent.
+func TestSendUnhandledSignal(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	sh, h := newShell(t, ctx, handleDefaults)
+	defer sh.Cleanup(os.Stderr, os.Stderr)
+	h.Expect("ready")
+	checkSignalIsNotDefault(t, syscall.SIGABRT)
+	syscall.Kill(h.Pid(), syscall.SIGABRT)
+	testExitStatus(t, h, 2)
+}
+
+// TestDoubleSignalIgnoreChan verifies that, even if we ignore the channel that
+// ShutdownOnSignals returns, sending two signals should still cause the
+// process to exit (ensures that there is no dependency in ShutdownOnSignals
+// on having a goroutine read from the returned channel).
+func TestDoubleSignalIgnoreChan(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	sh, h := newShell(t, ctx, handleDefaultsIgnoreChan)
+	defer sh.Cleanup(os.Stderr, os.Stderr)
+	h.Expect("ready")
+	// 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(h.Pid(), syscall.SIGTERM)
+	checkSignalIsDefault(t, syscall.SIGINT)
+	syscall.Kill(h.Pid(), syscall.SIGINT)
+	testExitStatus(t, h, DoubleStopExitCode)
+}
+
+// TestHandlerCustomSignal verifies that sending a non-default signal to a
+// server that listens for that signal causes the server to shut down cleanly.
+func TestHandlerCustomSignal(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	sh, h := newShell(t, ctx, handleCustom)
+	defer sh.Cleanup(os.Stderr, os.Stderr)
+	h.Expect("ready")
+	checkSignalIsNotDefault(t, syscall.SIGABRT)
+	syscall.Kill(h.Pid(), syscall.SIGABRT)
+	h.Expectf("received signal %s", syscall.SIGABRT)
+	fmt.Fprintf(h.Stdin(), "stop\n")
+	h.ExpectEOF()
+}
+
+// TestHandlerCustomSignalWithStop verifies that sending a custom stop signal
+// to a server that listens for that signal causes the server to shut down
+// cleanly, even when a STOP signal is also among the handled signals.
+func TestHandlerCustomSignalWithStop(t *testing.T) {
+	rootCtx, shutdown := test.V23Init()
+	defer shutdown()
+
+	for _, signal := range []syscall.Signal{syscall.SIGABRT, syscall.SIGHUP} {
+		ctx, _ := vtrace.WithNewTrace(rootCtx)
+		sh, h := newShell(t, ctx, handleCustomWithStop)
+		h.Expect("ready")
+		checkSignalIsNotDefault(t, signal)
+		syscall.Kill(h.Pid(), signal)
+		h.Expectf("received signal %s", signal)
+		fmt.Fprintf(h.Stdin(), "close\n")
+		h.ExpectEOF()
+		sh.Cleanup(os.Stderr, os.Stderr)
+	}
+}
+
+// TestParseSignalsList verifies that ShutdownOnSignals correctly interprets
+// the input list of signals.
+func TestParseSignalsList(t *testing.T) {
+	list := []os.Signal{STOP, syscall.SIGTERM}
+	ShutdownOnSignals(nil, list...)
+	if !isSignalInSet(syscall.SIGTERM, list) {
+		t.Errorf("signal %s not in signal set, as expected: %v", syscall.SIGTERM, list)
+	}
+	if !isSignalInSet(STOP, list) {
+		t.Errorf("signal %s not in signal set, as expected: %v", STOP, list)
+	}
+}
+
+type configServer struct {
+	ch chan<- string
+}
+
+func (c *configServer) Set(_ *context.T, _ rpc.ServerCall, key, value string) error {
+	if key != mgmt.AppCycleManagerConfigKey {
+		return fmt.Errorf("Unexpected key: %v", key)
+	}
+	c.ch <- value
+	return nil
+
+}
+
+// TestCleanRemoteShutdown verifies that remote shutdown works correctly.
+func TestCleanRemoteShutdown(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	sh, err := modules.NewShell(ctx, nil, testing.Verbose(), t)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	defer sh.Cleanup(os.Stderr, os.Stderr)
+
+	ch := make(chan string)
+	server, err := xrpc.NewServer(ctx, "", device.ConfigServer(&configServer{ch}), securityflag.NewAuthorizerOrDie())
+	if err != nil {
+		t.Fatalf("Got error: %v", err)
+	}
+	configServiceName := server.Status().Endpoints[0].Name()
+
+	sh.SetConfigKey(mgmt.ParentNameConfigKey, configServiceName)
+	sh.SetConfigKey(mgmt.ProtocolConfigKey, "tcp")
+	sh.SetConfigKey(mgmt.AddressConfigKey, "127.0.0.1:0")
+	h, err := sh.Start(nil, handleDefaults)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	appCycleName := <-ch
+	h.Expect("ready")
+	appCycle := appcycle.AppCycleClient(appCycleName)
+	stream, err := appCycle.Stop(ctx)
+	if err != nil {
+		t.Fatalf("Got error: %v", err)
+	}
+	rStream := stream.RecvStream()
+	if rStream.Advance() || rStream.Err() != nil {
+		t.Errorf("Expected EOF, got (%v, %v) instead: ", rStream.Value(), rStream.Err())
+	}
+	if err := stream.Finish(); err != nil {
+		t.Fatalf("Got error: %v", err)
+	}
+	h.Expectf("received signal %s", v23.RemoteStop)
+	fmt.Fprintf(h.Stdin(), "close\n")
+	h.ExpectEOF()
+}
diff --git a/lib/signals/v23_internal_test.go b/lib/signals/v23_internal_test.go
new file mode 100644
index 0000000..e047d2e
--- /dev/null
+++ b/lib/signals/v23_internal_test.go
@@ -0,0 +1,22 @@
+// 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 signals
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	modules.DispatchAndExitIfChild()
+	os.Exit(m.Run())
+}
diff --git a/lib/stats/counter.go b/lib/stats/counter.go
new file mode 100644
index 0000000..6482149
--- /dev/null
+++ b/lib/stats/counter.go
@@ -0,0 +1,85 @@
+// 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 stats
+
+import (
+	"time"
+
+	"v.io/x/ref/lib/stats/counter"
+)
+
+// NewCounter creates a new Counter StatsObject with the given name and
+// returns a pointer to it.
+func NewCounter(name string) *counter.Counter {
+	lock.Lock()
+	defer lock.Unlock()
+
+	node := findNodeLocked(name, true)
+	c := counter.New()
+	cw := &counterWrapper{c}
+	node.object = cw
+
+	addCounterChild(node, name+"/delta1h", cw, time.Hour, cw.Delta1h)
+	addCounterChild(node, name+"/delta10m", cw, 10*time.Minute, cw.Delta10m)
+	addCounterChild(node, name+"/delta1m", cw, time.Minute, cw.Delta1m)
+	addCounterChild(node, name+"/rate1h", cw, time.Hour, cw.Rate1h)
+	addCounterChild(node, name+"/rate10m", cw, 10*time.Minute, cw.Rate10m)
+	addCounterChild(node, name+"/rate1m", cw, time.Minute, cw.Rate1m)
+	return c
+}
+
+type counterWrapper struct {
+	c *counter.Counter
+}
+
+func (cw counterWrapper) LastUpdate() time.Time {
+	return cw.c.LastUpdate()
+}
+
+func (cw counterWrapper) Value() interface{} {
+	return cw.c.Value()
+}
+
+func (cw counterWrapper) Delta1h() interface{} {
+	return cw.c.Delta1h()
+}
+func (cw counterWrapper) Delta10m() interface{} {
+	return cw.c.Delta10m()
+}
+func (cw counterWrapper) Delta1m() interface{} {
+	return cw.c.Delta1m()
+}
+func (cw counterWrapper) Rate1h() interface{} {
+	return cw.c.Rate1h()
+}
+func (cw counterWrapper) Rate10m() interface{} {
+	return cw.c.Rate10m()
+}
+func (cw counterWrapper) Rate1m() interface{} {
+	return cw.c.Rate1m()
+}
+
+type counterChild struct {
+	c      *counterWrapper
+	period time.Duration
+	value  func() interface{}
+}
+
+func (cc counterChild) LastUpdate() time.Time {
+	now := time.Now()
+	if t := cc.c.LastUpdate().Add(cc.period); t.Before(now) {
+		return t
+	}
+	return now
+}
+
+func (cc counterChild) Value() interface{} {
+	return cc.value()
+}
+
+func addCounterChild(parent *node, name string, c *counterWrapper, period time.Duration, value func() interface{}) {
+	child := findNodeLocked(name, true)
+	child.object = &counterChild{c, period, value}
+}
diff --git a/lib/stats/counter/counter.go b/lib/stats/counter/counter.go
new file mode 100644
index 0000000..947e1b7
--- /dev/null
+++ b/lib/stats/counter/counter.go
@@ -0,0 +1,152 @@
+// 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 counter implements counters that keeps track of their recent values
+// over different periods of time.
+// Example:
+// c := counter.New()
+// c.Incr(n)
+// ...
+// delta1h := c.Delta1h()
+// delta10m := c.Delta10m()
+// delta1m := c.Delta1m()
+// and:
+// rate1h := c.Rate1h()
+// rate10m := c.Rate10m()
+// rate1m := c.Rate1m()
+package counter
+
+import (
+	"sync"
+	"time"
+)
+
+var (
+	// Used for testing.
+	TimeNow func() time.Time = time.Now
+)
+
+const (
+	hour       = 0
+	tenminutes = 1
+	minute     = 2
+)
+
+// Counter is a counter that keeps track of its recent values over a given
+// period of time, and with a given resolution. Use New() to instantiate.
+type Counter struct {
+	mu         sync.RWMutex
+	ts         [3]*timeseries
+	lastUpdate time.Time
+}
+
+// New returns a new Counter.
+func New() *Counter {
+	now := TimeNow()
+	c := &Counter{}
+	c.ts[hour] = newTimeSeries(now, time.Hour, time.Minute)
+	c.ts[tenminutes] = newTimeSeries(now, 10*time.Minute, 10*time.Second)
+	c.ts[minute] = newTimeSeries(now, time.Minute, time.Second)
+	return c
+}
+
+func (c *Counter) advance() time.Time {
+	now := TimeNow()
+	for _, ts := range c.ts {
+		ts.advanceTime(now)
+	}
+	return now
+}
+
+// Value returns the current value of the counter.
+func (c *Counter) Value() int64 {
+	c.mu.RLock()
+	defer c.mu.RUnlock()
+	return c.ts[minute].headValue()
+}
+
+// LastUpdate returns the last update time of the counter.
+func (c *Counter) LastUpdate() time.Time {
+	c.mu.RLock()
+	defer c.mu.RUnlock()
+	return c.lastUpdate
+}
+
+// Set updates the current value of the counter.
+func (c *Counter) Set(value int64) {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+	c.lastUpdate = c.advance()
+	for _, ts := range c.ts {
+		ts.set(value)
+	}
+}
+
+// Incr increments the current value of the counter by 'delta'.
+func (c *Counter) Incr(delta int64) {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+	c.lastUpdate = c.advance()
+	for _, ts := range c.ts {
+		ts.incr(delta)
+	}
+}
+
+// Delta1h returns the delta for the last hour.
+func (c *Counter) Delta1h() int64 {
+	c.mu.RLock()
+	defer c.mu.RUnlock()
+	c.advance()
+	return c.ts[hour].delta()
+}
+
+// Delta10m returns the delta for the last 10 minutes.
+func (c *Counter) Delta10m() int64 {
+	c.mu.RLock()
+	defer c.mu.RUnlock()
+	c.advance()
+	return c.ts[tenminutes].delta()
+}
+
+// Delta1m returns the delta for the last minute.
+func (c *Counter) Delta1m() int64 {
+	c.mu.RLock()
+	defer c.mu.RUnlock()
+	c.advance()
+	return c.ts[minute].delta()
+}
+
+// Rate1h returns the rate of change of the counter in the last hour.
+func (c *Counter) Rate1h() float64 {
+	c.mu.RLock()
+	defer c.mu.RUnlock()
+	c.advance()
+	return c.ts[hour].rate()
+}
+
+// Rate10m returns the rate of change of the counter in the last 10 minutes.
+func (c *Counter) Rate10m() float64 {
+	c.mu.RLock()
+	defer c.mu.RUnlock()
+	c.advance()
+	return c.ts[tenminutes].rate()
+}
+
+// Rate1m returns the rate of change of the counter in the last minute.
+func (c *Counter) Rate1m() float64 {
+	c.mu.RLock()
+	defer c.mu.RUnlock()
+	c.advance()
+	return c.ts[minute].rate()
+}
+
+// Reset resets the counter to an empty state.
+func (c *Counter) Reset() {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+	now := TimeNow()
+	for _, ts := range c.ts {
+		ts.reset(now)
+	}
+}
diff --git a/lib/stats/counter/counter_test.go b/lib/stats/counter/counter_test.go
new file mode 100644
index 0000000..4533adb
--- /dev/null
+++ b/lib/stats/counter/counter_test.go
@@ -0,0 +1,182 @@
+// 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 counter_test
+
+import (
+	"sync"
+	"testing"
+	"time"
+
+	"v.io/x/ref/lib/stats/counter"
+)
+
+func TestCounter(t *testing.T) {
+	now := time.Unix(1, 0)
+	counter.TimeNow = func() time.Time { return now }
+	c := counter.New()
+
+	// Time 1, value=1
+	c.Incr(1)
+
+	// Time 2, value=2
+	now = now.Add(time.Second)
+	c.Incr(1)
+
+	if expected, got := int64(2), c.Delta1m(); got != expected {
+		t.Errorf("Unexpected value. Got %v, want %v", got, expected)
+	}
+	// One second later.
+	now = now.Add(time.Second)
+	if expected, got := int64(2), c.Delta1m(); got != expected {
+		t.Errorf("Unexpected value. Got %v, want %v", got, expected)
+	}
+	now = time.Unix(1, 0)
+	c.Reset()
+
+	// Time 1, value=1
+	c.Incr(1)
+	// Time 2, value=2
+	now = now.Add(time.Second)
+	c.Incr(1)
+	// Time 3, value=3
+	now = now.Add(time.Second)
+	c.Incr(1)
+	// Time 4, value=4
+	now = now.Add(time.Second)
+	c.Incr(1)
+	// Time 5, value=5
+	now = now.Add(time.Second)
+	c.Incr(1)
+	// Expect current value=5 and delta1m=(5-0)=5
+	if expected, got := int64(5), c.Value(); got != expected {
+		t.Errorf("Unexpected value. Got %v, want %v", got, expected)
+	}
+	if expected, got := int64(5), c.Delta1m(); got != expected {
+		t.Errorf("Unexpected value. Got %v, want %v", got, expected)
+	}
+	now = time.Unix(0, 0)
+	c.Reset()
+
+	// ...
+	// Time 64, value=64
+	// Time 65, value=65
+	// Time 66, value=66
+	// Time 67, value=67
+	// Time 68, value=68
+	// Time 69, value=69
+	// Time 70, value=70
+	// Expect current value=70 and delta1m=(70-10)=60
+	for sec := 1; sec <= 70; sec++ {
+		now = now.Add(time.Second)
+		c.Incr(1)
+	}
+	if expected, got := int64(70), c.Value(); got != expected {
+		t.Errorf("Unexpected value. Got %v, want %v", got, expected)
+	}
+	if expected, got := int64(60), c.Delta1m(); got != expected {
+		t.Errorf("Unexpected value. Got %v, want %v", got, expected)
+	}
+
+	// Time is now 80, value is still 70, delta1m is (70-20)=50
+	now = time.Unix(80, 0)
+	if expected, got := int64(70), c.Value(); got != expected {
+		t.Errorf("Unexpected value. Got %v, want %v", got, expected)
+	}
+	if expected, got := int64(50), c.Delta1m(); got != expected {
+		t.Errorf("Unexpected value. Got %v, want %v", got, expected)
+	}
+	c.Reset()
+
+	// Increment by 1 every second for 90 minutes.
+	now = time.Unix(59, 0)
+	for i := 0; i < 60*90; i++ {
+		now = now.Add(time.Second)
+		c.Incr(1)
+	}
+	if expected, got := int64(5400), c.Value(); got != expected {
+		t.Errorf("Unexpected value. Got %v, want %v", got, expected)
+	}
+	if expected, got := int64(60), c.Delta1m(); got != expected {
+		t.Errorf("Unexpected value. Got %v, want %v", got, expected)
+	}
+	if expected, got := int64(600), c.Delta10m(); got != expected {
+		t.Errorf("Unexpected value. Got %v, want %v", got, expected)
+	}
+	if expected, got := int64(3600), c.Delta1h(); got != expected {
+		t.Errorf("Unexpected value. Got %v, want %v", got, expected)
+	}
+}
+
+func TestCounterRate(t *testing.T) {
+	now := time.Unix(1, 0)
+	counter.TimeNow = func() time.Time { return now }
+	c := counter.New()
+
+	// No data, rate is 0.
+	if expected, got := float64(0), c.Rate1m(); got != expected {
+		t.Errorf("Unexpected value. Got %v, want %v", got, expected)
+	}
+
+	// Increment by 1 every second. The rate is 1, even though the counter
+	// doesn't have data for the whole period.
+	for i := 0; i < 10; i++ {
+		now = now.Add(time.Second)
+		c.Incr(1)
+	}
+	if expected, got := float64(1), c.Rate1m(); got != expected {
+		t.Errorf("Unexpected value. Got %v, want %v", got, expected)
+	}
+	c.Reset()
+
+	// Increment by 3 every 2 seconds. The rate is 1.5.
+	for i := 0; i < 10; i++ {
+		now = now.Add(2 * time.Second)
+		c.Incr(3)
+	}
+	if expected, got := float64(1.5), c.Rate1m(); got != expected {
+		t.Errorf("Unexpected value. Got %v, want %v", got, expected)
+	}
+	c.Reset()
+
+	// Increment by 5 every 4 seconds. The rate is 1.25.
+	for i := 0; i < 100; i++ {
+		now = now.Add(4 * time.Second)
+		c.Incr(5)
+	}
+	if expected, got := float64(1.25), c.Rate1m(); got != expected {
+		t.Errorf("Unexpected value. Got %v, want %v", got, expected)
+	}
+	c.Reset()
+}
+
+func TestConcurrent(t *testing.T) {
+	const numGoRoutines = 100
+	const numIncrPerGoRoutine = 100000
+	c := counter.New()
+	var wg sync.WaitGroup
+	wg.Add(numGoRoutines)
+	for i := 0; i < numGoRoutines; i++ {
+		go func() {
+			for x := 0; x < numIncrPerGoRoutine; x++ {
+				c.Incr(1)
+			}
+			wg.Done()
+		}()
+	}
+	wg.Wait()
+	if expected, got := int64(numGoRoutines*numIncrPerGoRoutine), c.Value(); got != expected {
+		t.Errorf("unexpected result. Got %v, want %v", got, expected)
+	}
+}
+
+func BenchmarkCounterIncr(b *testing.B) {
+	c := counter.New()
+	b.SetParallelism(100)
+	b.RunParallel(func(pb *testing.PB) {
+		for pb.Next() {
+			c.Incr(1)
+		}
+	})
+}
diff --git a/lib/stats/counter/timeseries.go b/lib/stats/counter/timeseries.go
new file mode 100644
index 0000000..36dfbd8
--- /dev/null
+++ b/lib/stats/counter/timeseries.go
@@ -0,0 +1,158 @@
+// 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 counter
+
+import (
+	"math"
+	"time"
+)
+
+// timeseries holds the history of a changing value over a predefined period of
+// time.
+type timeseries struct {
+	size       int           // The number of time slots. Equivalent to len(slots).
+	resolution time.Duration // The time resolution of each slot.
+	stepCount  int64         // The number of intervals seen since creation.
+	head       int           // The position of the current time in slots.
+	time       time.Time     // The time at the beginning of the current time slot.
+	slots      []int64       // A circular buffer of time slots.
+}
+
+// newTimeSeries returns a newly allocated timeseries that covers the requested
+// period with the given resolution.
+func newTimeSeries(initialTime time.Time, period, resolution time.Duration) *timeseries {
+	size := int(period.Nanoseconds()/resolution.Nanoseconds()) + 1
+	return &timeseries{
+		size:       size,
+		resolution: resolution,
+		stepCount:  1,
+		time:       initialTime,
+		slots:      make([]int64, size),
+	}
+}
+
+// advanceTimeWithFill moves the timeseries forward to time t and fills in any
+// slots that get skipped in the process with the given value. Values older than
+// the timeseries period are lost.
+func (ts *timeseries) advanceTimeWithFill(t time.Time, value int64) {
+	advanceTo := t.Truncate(ts.resolution)
+	if !advanceTo.After(ts.time) {
+		// This is shortcut for the most common case of a busy counter
+		// where updates come in many times per ts.resolution.
+		ts.time = advanceTo
+		return
+	}
+	steps := int(advanceTo.Sub(ts.time).Nanoseconds() / ts.resolution.Nanoseconds())
+	ts.stepCount += int64(steps)
+	if steps > ts.size {
+		steps = ts.size
+	}
+	for steps > 0 {
+		ts.head = (ts.head + 1) % ts.size
+		ts.slots[ts.head] = value
+		steps--
+	}
+	ts.time = advanceTo
+}
+
+// advanceTime moves the timeseries forward to time t and fills in any slots
+// that get skipped in the process with the head value. Values older than the
+// timeseries period are lost.
+func (ts *timeseries) advanceTime(t time.Time) {
+	ts.advanceTimeWithFill(t, ts.slots[ts.head])
+}
+
+// set sets the current value of the timeseries.
+func (ts *timeseries) set(value int64) {
+	ts.slots[ts.head] = value
+}
+
+// incr sets the current value of the timeseries.
+func (ts *timeseries) incr(delta int64) {
+	ts.slots[ts.head] += delta
+}
+
+// headValue returns the latest value from the timeseries.
+func (ts *timeseries) headValue() int64 {
+	return ts.slots[ts.head]
+}
+
+// headTime returns the time of the latest value from the timeseries.
+func (ts *timeseries) headTime() time.Time {
+	return ts.time
+}
+
+// tailValue returns the oldest value from the timeseries.
+func (ts *timeseries) tailValue() int64 {
+	if ts.stepCount < int64(ts.size) {
+		return 0
+	}
+	return ts.slots[(ts.head+1)%ts.size]
+}
+
+// tailTime returns the time of the oldest value from the timeseries.
+func (ts *timeseries) tailTime() time.Time {
+	size := int64(ts.size)
+	if ts.stepCount < size {
+		size = ts.stepCount
+	}
+	return ts.time.Add(-time.Duration(size-1) * ts.resolution)
+}
+
+// delta returns the difference between the newest and oldest values from the
+// timeseries.
+func (ts *timeseries) delta() int64 {
+	return ts.headValue() - ts.tailValue()
+}
+
+// rate returns the rate of change between the oldest and newest values from
+// the timeseries in units per second.
+func (ts *timeseries) rate() float64 {
+	deltaTime := ts.headTime().Sub(ts.tailTime()).Seconds()
+	if deltaTime == 0 {
+		return 0
+	}
+	return float64(ts.delta()) / deltaTime
+}
+
+// min returns the smallest value from the timeseries.
+func (ts *timeseries) min() int64 {
+	to := ts.size
+	if ts.stepCount < int64(ts.size) {
+		to = ts.head + 1
+	}
+	tail := (ts.head + 1) % ts.size
+	min := int64(math.MaxInt64)
+	for b := 0; b < to; b++ {
+		if b != tail && ts.slots[b] < min {
+			min = ts.slots[b]
+		}
+	}
+	return min
+}
+
+// max returns the largest value from the timeseries.
+func (ts *timeseries) max() int64 {
+	to := ts.size
+	if ts.stepCount < int64(ts.size) {
+		to = ts.head + 1
+	}
+	tail := (ts.head + 1) % ts.size
+	max := int64(math.MinInt64)
+	for b := 0; b < to; b++ {
+		if b != tail && ts.slots[b] > max {
+			max = ts.slots[b]
+		}
+	}
+	return max
+}
+
+// reset resets the timeseries to an empty state.
+func (ts *timeseries) reset(t time.Time) {
+	ts.head = 0
+	ts.time = t
+	ts.stepCount = 1
+	ts.slots = make([]int64, ts.size)
+}
diff --git a/lib/stats/counter/timeseries_test.go b/lib/stats/counter/timeseries_test.go
new file mode 100644
index 0000000..8bd3bbd
--- /dev/null
+++ b/lib/stats/counter/timeseries_test.go
@@ -0,0 +1,123 @@
+// 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 counter
+
+import (
+	"testing"
+	"time"
+)
+
+func TestTimeSeries(t *testing.T) {
+	now := time.Unix(1, 0)
+	ts := newTimeSeries(now, 5*time.Second, time.Second)
+
+	// Time 1
+	ts.advanceTime(now)
+	ts.set(123)
+	if expected, got := int64(123), ts.headValue(); got != expected {
+		t.Errorf("unexpected value. Got %v, want %v", got, expected)
+	}
+	ts.set(234)
+	if expected, got := int64(234), ts.headValue(); got != expected {
+		t.Errorf("unexpected value. Got %v, want %v", got, expected)
+	}
+	if expected, got := int64(234), ts.min(); got != expected {
+		t.Errorf("unexpected value. Got %v, want %v", got, expected)
+	}
+	if expected, got := int64(234), ts.max(); got != expected {
+		t.Errorf("unexpected value. Got %v, want %v", got, expected)
+	}
+
+	// Time 2
+	now = now.Add(time.Second)
+	ts.advanceTime(now)
+	ts.set(345)
+	if expected, got := int64(345), ts.headValue(); got != expected {
+		t.Errorf("unexpected value. Got %v, want %v", got, expected)
+	}
+	if expected, got := int64(0), ts.tailValue(); got != expected {
+		t.Errorf("unexpected value. Got %v, want %v", got, expected)
+	}
+	if expected, got := int64(234), ts.min(); got != expected {
+		t.Errorf("unexpected value. Got %v, want %v", got, expected)
+	}
+	if expected, got := int64(345), ts.max(); got != expected {
+		t.Errorf("unexpected value. Got %v, want %v", got, expected)
+	}
+
+	// Time 4
+	now = now.Add(2 * time.Second)
+	ts.advanceTime(now)
+	ts.set(111)
+	if expected, got := int64(111), ts.headValue(); got != expected {
+		t.Errorf("unexpected value. Got %v, want %v", got, expected)
+	}
+	if expected, got := int64(0), ts.tailValue(); got != expected {
+		t.Errorf("unexpected value. Got %v, want %v", got, expected)
+	}
+	if expected, got := int64(111), ts.min(); got != expected {
+		t.Errorf("unexpected value. Got %v, want %v", got, expected)
+	}
+	if expected, got := int64(345), ts.max(); got != expected {
+		t.Errorf("unexpected value. Got %v, want %v", got, expected)
+	}
+
+	// Time 7
+	now = now.Add(3 * time.Second)
+	ts.advanceTime(now)
+	if expected, got := int64(111), ts.headValue(); got != expected {
+		t.Errorf("unexpected value. Got %v, want %v", got, expected)
+	}
+	if expected, got := int64(345), ts.tailValue(); got != expected {
+		t.Errorf("unexpected value. Got %v, want %v", got, expected)
+	}
+	if expected, got := int64(111), ts.min(); got != expected {
+		t.Errorf("unexpected value. Got %v, want %v", got, expected)
+	}
+	if expected, got := int64(345), ts.max(); got != expected {
+		t.Errorf("unexpected value. Got %v, want %v", got, expected)
+	}
+
+	// Time 8
+	now = now.Add(time.Second)
+	ts.advanceTime(now)
+	if expected, got := int64(111), ts.headValue(); got != expected {
+		t.Errorf("unexpected value. Got %v, want %v", got, expected)
+	}
+	if expected, got := int64(345), ts.tailValue(); got != expected {
+		t.Errorf("unexpected value. Got %v, want %v", got, expected)
+	}
+	if expected, got := int64(111), ts.min(); got != expected {
+		t.Errorf("unexpected value. Got %v, want %v", got, expected)
+	}
+	if expected, got := int64(111), ts.max(); got != expected {
+		t.Errorf("unexpected value. Got %v, want %v", got, expected)
+	}
+
+	// Time 27
+	now = now.Add(20 * time.Second)
+	ts.advanceTime(now)
+	if expected, got := int64(111), ts.headValue(); got != expected {
+		t.Errorf("unexpected value. Got %v, want %v", got, expected)
+	}
+	if expected, got := int64(111), ts.tailValue(); got != expected {
+		t.Errorf("unexpected value. Got %v, want %v", got, expected)
+	}
+}
+
+func TestTimeSeriesRate(t *testing.T) {
+	now := time.Unix(1, 0)
+	ts := newTimeSeries(now, 60*time.Second, time.Second)
+	// Increment by 5 every 4 seconds. The rate is 1.25.
+	for i := 0; i < 10; i++ {
+		now = now.Add(4 * time.Second)
+		ts.advanceTime(now)
+		ts.incr(5)
+	}
+	if expected, got := float64(1.25), ts.rate(); got != expected {
+		t.Errorf("Unexpected value. Got %v, want %v", got, expected)
+	}
+
+}
diff --git a/lib/stats/counter/tracker.go b/lib/stats/counter/tracker.go
new file mode 100644
index 0000000..aea38a6
--- /dev/null
+++ b/lib/stats/counter/tracker.go
@@ -0,0 +1,163 @@
+// 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 counter
+
+import (
+	"math"
+	"sync"
+	"time"
+)
+
+// Tracker is a min/max value tracker that keeps track of its min/max values
+// over a given period of time, and with a given resolution. The initial min
+// and max values are math.MaxInt64 and math.MinInt64 respectively.
+type Tracker struct {
+	mu           sync.RWMutex
+	min, max     int64 // All time min/max.
+	minTS, maxTS [3]*timeseries
+	lastUpdate   time.Time
+}
+
+// NewTracker returns a new Tracker.
+func NewTracker() *Tracker {
+	now := TimeNow()
+	t := &Tracker{}
+	t.minTS[hour] = newTimeSeries(now, time.Hour, time.Minute)
+	t.minTS[tenminutes] = newTimeSeries(now, 10*time.Minute, 10*time.Second)
+	t.minTS[minute] = newTimeSeries(now, time.Minute, time.Second)
+	t.maxTS[hour] = newTimeSeries(now, time.Hour, time.Minute)
+	t.maxTS[tenminutes] = newTimeSeries(now, 10*time.Minute, 10*time.Second)
+	t.maxTS[minute] = newTimeSeries(now, time.Minute, time.Second)
+	t.init()
+	return t
+}
+
+func (t *Tracker) init() {
+	t.min = math.MaxInt64
+	t.max = math.MinInt64
+	for _, ts := range t.minTS {
+		ts.set(math.MaxInt64)
+	}
+	for _, ts := range t.maxTS {
+		ts.set(math.MinInt64)
+	}
+}
+
+func (t *Tracker) advance() time.Time {
+	now := TimeNow()
+	for _, ts := range t.minTS {
+		ts.advanceTimeWithFill(now, math.MaxInt64)
+	}
+	for _, ts := range t.maxTS {
+		ts.advanceTimeWithFill(now, math.MinInt64)
+	}
+	return now
+}
+
+// LastUpdate returns the last update time of the range.
+func (t *Tracker) LastUpdate() time.Time {
+	t.mu.RLock()
+	defer t.mu.RUnlock()
+	return t.lastUpdate
+}
+
+// Push adds a new value if it is a new minimum or maximum.
+func (t *Tracker) Push(value int64) {
+	t.mu.Lock()
+	defer t.mu.Unlock()
+	t.lastUpdate = t.advance()
+	if t.min > value {
+		t.min = value
+	}
+	if t.max < value {
+		t.max = value
+	}
+	for _, ts := range t.minTS {
+		if ts.headValue() > value {
+			ts.set(value)
+		}
+	}
+	for _, ts := range t.maxTS {
+		if ts.headValue() < value {
+			ts.set(value)
+		}
+	}
+}
+
+// Min returns the minimum value of the tracker
+func (t *Tracker) Min() int64 {
+	t.mu.RLock()
+	defer t.mu.RUnlock()
+	return t.min
+}
+
+// Max returns the maximum value of the tracker.
+func (t *Tracker) Max() int64 {
+	t.mu.RLock()
+	defer t.mu.RUnlock()
+	return t.max
+}
+
+// Min1h returns the minimum value for the last hour.
+func (t *Tracker) Min1h() int64 {
+	t.mu.Lock()
+	defer t.mu.Unlock()
+	t.advance()
+	return t.minTS[hour].min()
+}
+
+// Max1h returns the maximum value for the last hour.
+func (t *Tracker) Max1h() int64 {
+	t.mu.Lock()
+	defer t.mu.Unlock()
+	t.advance()
+	return t.maxTS[hour].max()
+}
+
+// Min10m returns the minimum value for the last 10 minutes.
+func (t *Tracker) Min10m() int64 {
+	t.mu.Lock()
+	defer t.mu.Unlock()
+	t.advance()
+	return t.minTS[tenminutes].min()
+}
+
+// Max10m returns the maximum value for the last 10 minutes.
+func (t *Tracker) Max10m() int64 {
+	t.mu.Lock()
+	defer t.mu.Unlock()
+	t.advance()
+	return t.maxTS[tenminutes].max()
+}
+
+// Min1m returns the minimum value for the last 1 minute.
+func (t *Tracker) Min1m() int64 {
+	t.mu.Lock()
+	defer t.mu.Unlock()
+	t.advance()
+	return t.minTS[minute].min()
+}
+
+// Max1m returns the maximum value for the last 1 minute.
+func (t *Tracker) Max1m() int64 {
+	t.mu.Lock()
+	defer t.mu.Unlock()
+	t.advance()
+	return t.maxTS[minute].max()
+}
+
+// Reset resets the range to an empty state.
+func (t *Tracker) Reset() {
+	t.mu.Lock()
+	defer t.mu.Unlock()
+	now := TimeNow()
+	for _, ts := range t.minTS {
+		ts.reset(now)
+	}
+	for _, ts := range t.maxTS {
+		ts.reset(now)
+	}
+	t.init()
+}
diff --git a/lib/stats/counter/tracker_test.go b/lib/stats/counter/tracker_test.go
new file mode 100644
index 0000000..5800148
--- /dev/null
+++ b/lib/stats/counter/tracker_test.go
@@ -0,0 +1,214 @@
+// 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 counter_test
+
+import (
+	"fmt"
+	"math"
+	"math/rand"
+	"sync"
+	"testing"
+	"time"
+
+	"v.io/x/ref/lib/stats/counter"
+)
+
+var trackerTests = []struct {
+	after      time.Duration
+	push       int64   // 0 = none, -1 = reset
+	mins, maxs []int64 // min, 10min, hour, all time
+}{
+	{ // T0
+		after: 0,
+		push:  0,
+		mins:  []int64{math.MaxInt64, math.MaxInt64, math.MaxInt64, math.MaxInt64},
+		maxs:  []int64{math.MinInt64, math.MinInt64, math.MinInt64, math.MinInt64},
+	},
+	{ // T1
+		after: 1 * time.Second,
+		push:  5,
+		mins:  []int64{5, 5, 5, 5},
+		maxs:  []int64{5, 5, 5, 5},
+	},
+	{ // T2
+		after: 1 * time.Second,
+		push:  10,
+		mins:  []int64{5, 5, 5, 5},
+		maxs:  []int64{10, 10, 10, 10},
+	},
+	{ // T3
+		after: 1 * time.Second,
+		push:  1,
+		mins:  []int64{1, 1, 1, 1},
+		maxs:  []int64{10, 10, 10, 10},
+	},
+	{ // T4
+		after: 1 * time.Minute,
+		push:  0,
+		mins:  []int64{math.MaxInt64, 1, 1, 1},
+		maxs:  []int64{math.MinInt64, 10, 10, 10},
+	},
+	{ // T5
+		after: 10 * time.Minute,
+		push:  0,
+		mins:  []int64{math.MaxInt64, math.MaxInt64, 1, 1},
+		maxs:  []int64{math.MinInt64, math.MinInt64, 10, 10},
+	},
+	{ // T6
+		after: 1 * time.Hour,
+		push:  0,
+		mins:  []int64{math.MaxInt64, math.MaxInt64, math.MaxInt64, 1},
+		maxs:  []int64{math.MinInt64, math.MinInt64, math.MinInt64, 10},
+	},
+	{ // T7
+		after: 1 * time.Second,
+		push:  5,
+		mins:  []int64{5, 5, 5, 1},
+		maxs:  []int64{5, 5, 5, 10},
+	},
+	{ // T8
+		after: 1 * time.Minute,
+		push:  20,
+		mins:  []int64{20, 5, 5, 1},
+		maxs:  []int64{20, 20, 20, 20},
+	},
+	{ // T9
+		after: 10 * time.Minute,
+		push:  15,
+		mins:  []int64{15, 15, 5, 1},
+		maxs:  []int64{15, 15, 20, 20},
+	},
+	{ // T10
+		after: 1 * time.Hour,
+		push:  10,
+		mins:  []int64{10, 10, 10, 1},
+		maxs:  []int64{10, 10, 10, 20},
+	},
+	{ // T11
+		after: 1 * time.Second,
+		push:  -1,
+		mins:  []int64{math.MaxInt64, math.MaxInt64, math.MaxInt64, math.MaxInt64},
+		maxs:  []int64{math.MinInt64, math.MinInt64, math.MinInt64, math.MinInt64},
+	},
+	{ // T12
+		after: 1 * time.Second,
+		push:  5,
+		mins:  []int64{5, 5, 5, 5},
+		maxs:  []int64{5, 5, 5, 5},
+	},
+}
+
+func TestTracker(t *testing.T) {
+	now := time.Unix(1, 0)
+	counter.TimeNow = func() time.Time { return now }
+
+	tracker := counter.NewTracker()
+	for i, tt := range trackerTests {
+		now = now.Add(tt.after)
+		name := fmt.Sprintf("[T%d] %s:", i, now.Format("15:04:05"))
+		if tt.push > 0 {
+			tracker.Push(tt.push)
+			t.Logf("%s pushed %d\n", name, tt.push)
+		} else if tt.push < 0 {
+			tracker.Reset()
+			t.Log(name, "reset")
+		} else {
+			t.Log(name, "none")
+		}
+
+		if expected, got := tt.mins[0], tracker.Min1m(); got != expected {
+			t.Errorf("%s Min1m returned %d, want %v", name, got, expected)
+		}
+		if expected, got := tt.maxs[0], tracker.Max1m(); got != expected {
+			t.Errorf("%s Max1m returned %d, want %v", name, got, expected)
+		}
+		if expected, got := tt.mins[1], tracker.Min10m(); got != expected {
+			t.Errorf("%s Min10m returned %d, want %v", name, got, expected)
+		}
+		if expected, got := tt.maxs[1], tracker.Max10m(); got != expected {
+			t.Errorf("%s Max10m returned %d, want %v", name, got, expected)
+		}
+		if expected, got := tt.mins[2], tracker.Min1h(); got != expected {
+			t.Errorf("%s Min1h returned %d, want %v", name, got, expected)
+		}
+		if expected, got := tt.maxs[2], tracker.Max1h(); got != expected {
+			t.Errorf("%s Max1h returned %d, want %v", name, got, expected)
+		}
+		if expected, got := tt.mins[3], tracker.Min(); got != expected {
+			t.Errorf("%s Min returned %d, want %v", name, got, expected)
+		}
+		if expected, got := tt.maxs[3], tracker.Max(); got != expected {
+			t.Errorf("%s Max returned %d, want %v", name, got, expected)
+		}
+	}
+}
+
+func min(a, b int64) int64 {
+	if a > b {
+		return b
+	}
+	return a
+}
+
+func max(a, b int64) int64 {
+	if a < b {
+		return b
+	}
+	return a
+}
+
+func TestTrackerConcurrent(t *testing.T) {
+	rand.Seed(time.Now().UnixNano())
+
+	const numGoRoutines = 100
+	const numPushPerGoRoutine = 100000
+	tracker := counter.NewTracker()
+
+	var mins, maxs [numGoRoutines]int64
+	var wg sync.WaitGroup
+	wg.Add(numGoRoutines)
+	for i := 0; i < numGoRoutines; i++ {
+		go func(i int) {
+			var localMin, localMax int64 = math.MaxInt64, math.MinInt64
+			for j := 0; j < numPushPerGoRoutine; j++ {
+				v := rand.Int63()
+				tracker.Push(v)
+				localMin, localMax = min(localMin, v), max(localMax, v)
+			}
+
+			mins[i], maxs[i] = localMin, localMax
+			wg.Done()
+		}(i)
+	}
+	wg.Wait()
+
+	var expectedMin, expectedMax int64 = math.MaxInt64, math.MinInt64
+	for _, v := range mins {
+		expectedMin = min(expectedMin, v)
+	}
+	for _, v := range maxs {
+		expectedMax = max(expectedMax, v)
+	}
+
+	if got := tracker.Min(); got != expectedMin {
+		t.Errorf("Min returned %d, want %v", got, expectedMin)
+	}
+	if got := tracker.Max(); got != expectedMax {
+		t.Errorf("Max returned %d, want %v", got, expectedMax)
+	}
+}
+
+func BenchmarkTrackerPush(b *testing.B) {
+	const numVals = 10000
+	vals := rand.Perm(numVals)
+	tracker := counter.NewTracker()
+
+	b.SetParallelism(100)
+	b.RunParallel(func(pb *testing.PB) {
+		for i := 0; pb.Next(); i++ {
+			tracker.Push(int64(vals[i%numVals]))
+		}
+	})
+}
diff --git a/lib/stats/float.go b/lib/stats/float.go
new file mode 100644
index 0000000..11ad378
--- /dev/null
+++ b/lib/stats/float.go
@@ -0,0 +1,59 @@
+// 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 stats
+
+import (
+	"sync"
+	"time"
+)
+
+// NewFloat creates a new Float StatsObject with the given name and
+// returns a pointer to it.
+func NewFloat(name string) *Float {
+	lock.Lock()
+	defer lock.Unlock()
+
+	node := findNodeLocked(name, true)
+	f := Float{value: 0}
+	node.object = &f
+	return &f
+}
+
+// Float implements the StatsObject interface.
+type Float struct {
+	mu         sync.RWMutex
+	lastUpdate time.Time
+	value      float64
+}
+
+// Set sets the value of the object.
+func (f *Float) Set(value float64) {
+	f.mu.Lock()
+	defer f.mu.Unlock()
+	f.lastUpdate = time.Now()
+	f.value = value
+}
+
+// Incr increments the value of the object.
+func (f *Float) Incr(delta float64) {
+	f.mu.Lock()
+	defer f.mu.Unlock()
+	f.value += delta
+	f.lastUpdate = time.Now()
+}
+
+// LastUpdate returns the time at which the object was last updated.
+func (f *Float) LastUpdate() time.Time {
+	f.mu.RLock()
+	defer f.mu.RUnlock()
+	return f.lastUpdate
+}
+
+// Value returns the current value of the object.
+func (f *Float) Value() interface{} {
+	f.mu.RLock()
+	defer f.mu.RUnlock()
+	return f.value
+}
diff --git a/lib/stats/func.go b/lib/stats/func.go
new file mode 100644
index 0000000..083c68e
--- /dev/null
+++ b/lib/stats/func.go
@@ -0,0 +1,90 @@
+// 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 stats
+
+import (
+	"sync"
+	"time"
+)
+
+// NewIntegerFunc creates a new StatsObject with the given name. The function
+// argument must return an int64 value.
+func NewIntegerFunc(name string, function func() int64) StatsObject {
+	return newFunc(name, func() interface{} { return function() })
+}
+
+// NewFloatFunc creates a new StatsObject with the given name. The function
+// argument must return a float64 value.
+func NewFloatFunc(name string, function func() float64) StatsObject {
+	return newFunc(name, func() interface{} { return function() })
+}
+
+// NewStringFunc creates a new StatsObject with the given name. The function
+// argument must return a string value.
+func NewStringFunc(name string, function func() string) StatsObject {
+	return newFunc(name, func() interface{} { return function() })
+}
+
+func newFunc(name string, function func() interface{}) StatsObject {
+	f := funcType{function: function}
+	lock.Lock()
+	defer lock.Unlock()
+	node := findNodeLocked(name, true)
+	node.object = &f
+	return &f
+}
+
+// funcType implements the StatsObject interface by calling a user provided
+// function.
+type funcType struct {
+	mu        sync.Mutex
+	function  func() interface{}
+	waiters   []chan interface{} // GUARDED_BY(mu)
+	lastValue interface{}        // GUARDED_BY(mu)
+}
+
+// LastUpdate returns always returns the current time for this type of
+// StatsObject because Value() is expected to get a current (fresh) value.
+func (f *funcType) LastUpdate() time.Time {
+	return time.Now()
+}
+
+// Value returns the value returned by the object's function. If the function
+// takes more than 100 ms to return, the last value is used.
+func (f *funcType) Value() interface{} {
+	// There are two values that can be written to the channel, one from
+	// fetch() and one from time.AfterFunc(). In some cases, they will both
+	// be written but only one will be read. A buffer size of 1 would be
+	// sufficient to avoid deadlocks, but 2 will guarantee that fetch()
+	// never blocks on a channel.
+	ch := make(chan interface{}, 2)
+	f.mu.Lock()
+	if f.waiters = append(f.waiters, ch); len(f.waiters) == 1 {
+		go f.fetch()
+	}
+	f.mu.Unlock()
+
+	defer time.AfterFunc(100*time.Millisecond, func() {
+		f.mu.Lock()
+		defer f.mu.Unlock()
+		ch <- f.lastValue
+	}).Stop()
+
+	return <-ch
+}
+
+func (f *funcType) fetch() {
+	v := f.function()
+
+	f.mu.Lock()
+	waiters := f.waiters
+	f.waiters = nil
+	f.lastValue = v
+	f.mu.Unlock()
+
+	for _, c := range waiters {
+		c <- v
+	}
+}
diff --git a/lib/stats/glob.go b/lib/stats/glob.go
new file mode 100644
index 0000000..3dfc469
--- /dev/null
+++ b/lib/stats/glob.go
@@ -0,0 +1,114 @@
+// 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 stats
+
+import (
+	"path"
+	"sort"
+	"time"
+
+	"v.io/v23/glob"
+	"v.io/v23/verror"
+)
+
+// Glob returns the name and (optionally) the value of all the objects that
+// match the given pattern and have been updated since 'updatedSince'. The
+// 'root' argument is the name of the object where the pattern starts.
+// Example:
+//   a/b/c
+//   a/b/d
+//   b/e/f
+// Glob("", "...", time.Time{}, true) will return "a/b/c", "a/b/d", "b/e/f" and
+// their values.
+// Glob("a/b", "*", time.Time{}, true) will return "c", "d" and their values.
+func Glob(root string, pattern string, updatedSince time.Time, includeValues bool) *GlobIterator {
+	g, err := glob.Parse(pattern)
+	if err != nil {
+		return &GlobIterator{err: err}
+	}
+	lock.RLock()
+	defer lock.RUnlock()
+	node := findNodeLocked(root, false)
+	if node == nil {
+		return &GlobIterator{err: verror.New(verror.ErrNoExist, nil, root)}
+	}
+	var out []KeyValue
+	globStepLocked("", g, node, updatedSince, includeValues, &out)
+	sort.Sort(keyValueSort(out))
+	return &GlobIterator{results: out}
+}
+
+// globStepLocked applies a glob recursively.
+func globStepLocked(prefix string, g *glob.Glob, n *node, updatedSince time.Time, includeValues bool, result *[]KeyValue) {
+	if g.Len() == 0 {
+		if updatedSince.IsZero() || (n.object != nil && !n.object.LastUpdate().Before(updatedSince)) {
+			var v interface{}
+			if includeValues && n.object != nil {
+				v = n.object.Value()
+			}
+			*result = append(*result, KeyValue{prefix, v})
+		}
+	}
+	if g.Empty() {
+		return
+	}
+	matcher, left := g.Head(), g.Tail()
+	for name, child := range n.children {
+		if matcher.Match(name) {
+			globStepLocked(path.Join(prefix, name), left, child, updatedSince, includeValues, result)
+		}
+	}
+}
+
+// KeyValue stores a Key and a Value.
+type KeyValue struct {
+	Key   string
+	Value interface{}
+}
+
+// keyValueSort is used to sort a slice of KeyValue objects.
+type keyValueSort []KeyValue
+
+func (s keyValueSort) Len() int {
+	return len(s)
+}
+
+func (s keyValueSort) Less(i, j int) bool {
+	return s[i].Key < s[j].Key
+}
+
+func (s keyValueSort) Swap(i, j int) {
+	s[i], s[j] = s[j], s[i]
+}
+
+type GlobIterator struct {
+	results []KeyValue
+	next    KeyValue
+	err     error
+}
+
+// Advance stages the next element so that the client can retrieve it with
+// Value(). It returns true iff there is an element to retrieve. The client
+// must call Advance() before calling Value(). Advance may block if an element
+// is not immediately available.
+func (i *GlobIterator) Advance() bool {
+	if len(i.results) == 0 {
+		return false
+	}
+	i.next = i.results[0]
+	i.results = i.results[1:]
+	return true
+}
+
+// Value returns the element that was staged by Advance. Value does not block.
+func (i GlobIterator) Value() KeyValue {
+	return i.next
+}
+
+// Err returns a non-nil error iff the stream encountered any errors.  Err does
+// not block.
+func (i GlobIterator) Err() error {
+	return i.err
+}
diff --git a/lib/stats/histogram.go b/lib/stats/histogram.go
new file mode 100644
index 0000000..7ccdcce
--- /dev/null
+++ b/lib/stats/histogram.go
@@ -0,0 +1,75 @@
+// 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 stats
+
+import (
+	"time"
+
+	"v.io/x/ref/lib/stats/histogram"
+)
+
+// NewHistogram creates a new Histogram StatsObject with the given name and
+// returns a pointer to it.
+func NewHistogram(name string, opts histogram.Options) *histogram.Histogram {
+	lock.Lock()
+	defer lock.Unlock()
+
+	node := findNodeLocked(name, true)
+	h := histogram.New(opts)
+	hw := &histogramWrapper{h}
+	node.object = hw
+
+	addHistogramChild(node, name+"/delta1h", hw, time.Hour, hw.Delta1h)
+	addHistogramChild(node, name+"/delta10m", hw, 10*time.Minute, hw.Delta10m)
+	addHistogramChild(node, name+"/delta1m", hw, time.Minute, hw.Delta1m)
+	return h
+}
+
+type histogramWrapper struct {
+	h *histogram.Histogram
+}
+
+func (hw histogramWrapper) LastUpdate() time.Time {
+	return hw.h.LastUpdate()
+}
+
+func (hw histogramWrapper) Value() interface{} {
+	return hw.h.Value()
+}
+
+func (hw histogramWrapper) Delta1h() interface{} {
+	return hw.h.Delta1h()
+}
+
+func (hw histogramWrapper) Delta10m() interface{} {
+	return hw.h.Delta10m()
+}
+
+func (hw histogramWrapper) Delta1m() interface{} {
+	return hw.h.Delta1m()
+}
+
+type histogramChild struct {
+	h      *histogramWrapper
+	period time.Duration
+	value  func() interface{}
+}
+
+func (hc histogramChild) LastUpdate() time.Time {
+	now := time.Now()
+	if t := hc.h.LastUpdate().Add(hc.period); t.Before(now) {
+		return t
+	}
+	return now
+}
+
+func (hc histogramChild) Value() interface{} {
+	return hc.value()
+}
+
+func addHistogramChild(parent *node, name string, h *histogramWrapper, period time.Duration, value func() interface{}) {
+	child := findNodeLocked(name, true)
+	child.object = &histogramChild{h, period, value}
+}
diff --git a/lib/stats/histogram/histogram.go b/lib/stats/histogram/histogram.go
new file mode 100644
index 0000000..285fabc
--- /dev/null
+++ b/lib/stats/histogram/histogram.go
@@ -0,0 +1,202 @@
+// 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 histogram implements a basic histogram to keep track of data
+// distribution.
+package histogram
+
+import (
+	"time"
+
+	"v.io/v23/verror"
+	"v.io/x/ref/lib/stats/counter"
+	"v.io/x/ref/services/stats"
+)
+
+const pkgPath = "v.io/x/ref/lib/stats/histogram"
+
+var (
+	errNoBucketForValue = verror.Register(pkgPath+".errNoBucketForValue", verror.NoRetry, "{1:}{2:} no bucket for value{:_}")
+)
+
+// A Histogram accumulates values in the form of a histogram. The type of the
+// values is int64, which is suitable for keeping track of things like RPC
+// latency in milliseconds. New histogram objects should be obtained via the
+// New() function.
+type Histogram struct {
+	opts    Options
+	buckets []bucketInternal
+	count   *counter.Counter
+	sum     *counter.Counter
+	tracker *counter.Tracker
+}
+
+// Options contains the parameters that define the histogram's buckets.
+type Options struct {
+	// NumBuckets is the number of buckets.
+	NumBuckets int
+	// GrowthFactor is the growth factor of the buckets. A value of 0.1
+	// indicates that bucket N+1 will be 10% larger than bucket N.
+	GrowthFactor float64
+	// SmallestBucketSize is the size of the first bucket. Bucket sizes are
+	// rounded down to the nearest integer.
+	SmallestBucketSize float64
+	// MinValue is the lower bound of the first bucket.
+	MinValue int64
+}
+
+// bucketInternal is the internal representation of a bucket, which includes a
+// rate counter.
+type bucketInternal struct {
+	lowBound int64
+	count    *counter.Counter
+}
+
+// New returns a pointer to a new Histogram object that was created with the
+// provided options.
+func New(opts Options) *Histogram {
+	if opts.NumBuckets == 0 {
+		opts.NumBuckets = 32
+	}
+	if opts.SmallestBucketSize == 0.0 {
+		opts.SmallestBucketSize = 1.0
+	}
+	h := Histogram{
+		opts:    opts,
+		buckets: make([]bucketInternal, opts.NumBuckets),
+		count:   counter.New(),
+		sum:     counter.New(),
+		tracker: counter.NewTracker(),
+	}
+	low := opts.MinValue
+	delta := opts.SmallestBucketSize
+	for i := 0; i < opts.NumBuckets; i++ {
+		h.buckets[i].lowBound = low
+		h.buckets[i].count = counter.New()
+		low = low + int64(delta)
+		delta = delta * (1.0 + opts.GrowthFactor)
+	}
+	return &h
+}
+
+// Opts returns a copy of the options used to create the Histogram.
+func (h *Histogram) Opts() Options {
+	return h.opts
+}
+
+// Add adds a value to the histogram.
+func (h *Histogram) Add(value int64) error {
+	bucket, err := h.findBucket(value)
+	if err != nil {
+		return err
+	}
+	h.buckets[bucket].count.Incr(1)
+	h.count.Incr(1)
+	h.sum.Incr(value)
+	h.tracker.Push(value)
+	return nil
+}
+
+// LastUpdate returns the time at which the object was last updated.
+func (h *Histogram) LastUpdate() time.Time {
+	return h.count.LastUpdate()
+}
+
+// Value returns the accumulated state of the histogram since it was created.
+func (h *Histogram) Value() stats.HistogramValue {
+	b := make([]stats.HistogramBucket, len(h.buckets))
+	for i, v := range h.buckets {
+		b[i] = stats.HistogramBucket{
+			LowBound: v.lowBound,
+			Count:    v.count.Value(),
+		}
+	}
+
+	v := stats.HistogramValue{
+		Count:   h.count.Value(),
+		Sum:     h.sum.Value(),
+		Min:     h.tracker.Min(),
+		Max:     h.tracker.Max(),
+		Buckets: b,
+	}
+	return v
+}
+
+// Delta1h returns the change in the last hour.
+func (h *Histogram) Delta1h() stats.HistogramValue {
+	b := make([]stats.HistogramBucket, len(h.buckets))
+	for i, v := range h.buckets {
+		b[i] = stats.HistogramBucket{
+			LowBound: v.lowBound,
+			Count:    v.count.Delta1h(),
+		}
+	}
+
+	v := stats.HistogramValue{
+		Count:   h.count.Delta1h(),
+		Sum:     h.sum.Delta1h(),
+		Min:     h.tracker.Min1h(),
+		Max:     h.tracker.Max1h(),
+		Buckets: b,
+	}
+	return v
+}
+
+// Delta10m returns the change in the last 10 minutes.
+func (h *Histogram) Delta10m() stats.HistogramValue {
+	b := make([]stats.HistogramBucket, len(h.buckets))
+	for i, v := range h.buckets {
+		b[i] = stats.HistogramBucket{
+			LowBound: v.lowBound,
+			Count:    v.count.Delta10m(),
+		}
+	}
+
+	v := stats.HistogramValue{
+		Count:   h.count.Delta10m(),
+		Sum:     h.sum.Delta10m(),
+		Min:     h.tracker.Min10m(),
+		Max:     h.tracker.Max10m(),
+		Buckets: b,
+	}
+	return v
+}
+
+// Delta1m returns the change in the last 10 minutes.
+func (h *Histogram) Delta1m() stats.HistogramValue {
+	b := make([]stats.HistogramBucket, len(h.buckets))
+	for i, v := range h.buckets {
+		b[i] = stats.HistogramBucket{
+			LowBound: v.lowBound,
+			Count:    v.count.Delta1m(),
+		}
+	}
+
+	v := stats.HistogramValue{
+		Count:   h.count.Delta1m(),
+		Sum:     h.sum.Delta1m(),
+		Min:     h.tracker.Min1m(),
+		Max:     h.tracker.Max1m(),
+		Buckets: b,
+	}
+	return v
+}
+
+// findBucket does a binary search to find in which bucket the value goes.
+func (h *Histogram) findBucket(value int64) (int, error) {
+	lastBucket := len(h.buckets) - 1
+	min, max := 0, lastBucket
+	for max >= min {
+		b := (min + max) / 2
+		if value >= h.buckets[b].lowBound && (b == lastBucket || value < h.buckets[b+1].lowBound) {
+			return b, nil
+		}
+		if value < h.buckets[b].lowBound {
+			max = b - 1
+			continue
+		}
+		min = b + 1
+	}
+	return 0, verror.New(errNoBucketForValue, nil, value)
+}
diff --git a/lib/stats/histogram/histogram_test.go b/lib/stats/histogram/histogram_test.go
new file mode 100644
index 0000000..7de2348
--- /dev/null
+++ b/lib/stats/histogram/histogram_test.go
@@ -0,0 +1,73 @@
+// 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 histogram_test
+
+import (
+	"testing"
+
+	"v.io/x/ref/lib/stats/histogram"
+)
+
+func TestHistogram(t *testing.T) {
+	// This creates a histogram with the following buckets:
+	//  [1, 2)
+	//  [2, 4)
+	//  [4, 8)
+	//  [8, 16)
+	//  [16, Inf)
+	opts := histogram.Options{
+		NumBuckets:         5,
+		GrowthFactor:       1.0,
+		SmallestBucketSize: 1.0,
+		MinValue:           1.0,
+	}
+	h := histogram.New(opts)
+	// Trying to add a value that's less than MinValue, should return an error.
+	if err := h.Add(0); err == nil {
+		t.Errorf("unexpected return value for Add(0.0). Want != nil, got nil")
+	}
+	// Adding good values. Expect no errors.
+	for i := 1; i <= 50; i++ {
+		if err := h.Add(int64(i)); err != nil {
+			t.Errorf("unexpected return value for Add(%d). Want nil, got %v", i, err)
+		}
+	}
+	expectedCount := []int64{1, 2, 4, 8, 35}
+	buckets := h.Value().Buckets
+	for i := 0; i < opts.NumBuckets; i++ {
+		if buckets[i].Count != expectedCount[i] {
+			t.Errorf("unexpected count for bucket[%d]. Want %d, got %v", i, expectedCount[i], buckets[i].Count)
+		}
+	}
+
+	v := h.Value()
+	if expected, got := int64(50), v.Count; got != expected {
+		t.Errorf("unexpected count in histogram value. Want %d, got %v", expected, got)
+	}
+	if expected, got := int64(50*(1+50)/2), v.Sum; got != expected {
+		t.Errorf("unexpected sum in histogram value. Want %d, got %v", expected, got)
+	}
+	if expected, got := int64(1), v.Min; got != expected {
+		t.Errorf("unexpected min in histogram value. Want %d, got %v", expected, got)
+	}
+	if expected, got := int64(50), v.Max; got != expected {
+		t.Errorf("unexpected max in histogram value. Want %d, got %v", expected, got)
+	}
+}
+
+func BenchmarkHistogram(b *testing.B) {
+	opts := histogram.Options{
+		NumBuckets:         30,
+		GrowthFactor:       1.0,
+		SmallestBucketSize: 1.0,
+		MinValue:           1.0,
+	}
+	h := histogram.New(opts)
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		h.Add(int64(i))
+	}
+}
diff --git a/lib/stats/integer.go b/lib/stats/integer.go
new file mode 100644
index 0000000..b101489
--- /dev/null
+++ b/lib/stats/integer.go
@@ -0,0 +1,59 @@
+// 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 stats
+
+import (
+	"sync"
+	"time"
+)
+
+// NewInteger creates a new Integer StatsObject with the given name and
+// returns a pointer to it.
+func NewInteger(name string) *Integer {
+	lock.Lock()
+	defer lock.Unlock()
+
+	node := findNodeLocked(name, true)
+	i := Integer{value: 0}
+	node.object = &i
+	return &i
+}
+
+// Integer implements the StatsObject interface.
+type Integer struct {
+	mu         sync.RWMutex
+	lastUpdate time.Time
+	value      int64
+}
+
+// Set sets the value of the object.
+func (i *Integer) Set(value int64) {
+	i.mu.Lock()
+	defer i.mu.Unlock()
+	i.lastUpdate = time.Now()
+	i.value = value
+}
+
+// Incr increments the value of the object.
+func (i *Integer) Incr(delta int64) {
+	i.mu.Lock()
+	defer i.mu.Unlock()
+	i.value += delta
+	i.lastUpdate = time.Now()
+}
+
+// LastUpdate returns the time at which the object was last updated.
+func (i *Integer) LastUpdate() time.Time {
+	i.mu.RLock()
+	defer i.mu.RUnlock()
+	return i.lastUpdate
+}
+
+// Value returns the current value of the object.
+func (i *Integer) Value() interface{} {
+	i.mu.RLock()
+	defer i.mu.RUnlock()
+	return i.value
+}
diff --git a/lib/stats/map.go b/lib/stats/map.go
new file mode 100644
index 0000000..6e801eb
--- /dev/null
+++ b/lib/stats/map.go
@@ -0,0 +1,204 @@
+// 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 stats
+
+import (
+	"path"
+	"sort"
+	"sync"
+	"time"
+)
+
+// NewMap creates a new Map StatsObject with the given name and
+// returns a pointer to it.
+func NewMap(name string) *Map {
+	lock.Lock()
+	defer lock.Unlock()
+	node := findNodeLocked(name, true)
+	m := Map{name: name, value: make(map[string]mapValue)}
+	node.object = &m
+	return &m
+}
+
+// Map implements the StatsObject interface. The map keys are strings and the
+// values can be bool, int64, uint64, float64, string, or time.Time.
+type Map struct {
+	mu    sync.RWMutex // ACQUIRED_BEFORE(stats.lock)
+	name  string
+	value map[string]mapValue // GUARDED_BY(mu)
+}
+
+type mapValue struct {
+	lastUpdate time.Time
+	value      interface{}
+}
+
+// Set sets the values of the given keys. There must be exactly one value for
+// each key.
+func (m *Map) Set(kvpairs []KeyValue) {
+	now := time.Now()
+	m.mu.Lock()
+	defer m.mu.Unlock()
+	for _, kv := range kvpairs {
+		var v interface{}
+		switch value := kv.Value.(type) {
+		case bool:
+			v = bool(value)
+		case int:
+			v = int64(value)
+		case int8:
+			v = int64(value)
+		case int16:
+			v = int64(value)
+		case int32:
+			v = int64(value)
+		case int64:
+			v = int64(value)
+		case uint:
+			v = uint64(value)
+		case uint8:
+			v = uint64(value)
+		case uint16:
+			v = uint64(value)
+		case uint32:
+			v = uint64(value)
+		case uint64:
+			v = uint64(value)
+		case float32:
+			v = float64(value)
+		case float64:
+			v = float64(value)
+		case string:
+			v = string(value)
+		case time.Time:
+			v = value.String()
+		default:
+			panic("attempt to use an unsupported type as value")
+		}
+		m.value[kv.Key] = mapValue{now, v}
+	}
+	m.insertMissingNodes()
+}
+
+// Incr increments the value of the given key and returns the new value.
+func (m *Map) Incr(key string, delta int64) interface{} {
+	now := time.Now()
+	m.mu.Lock()
+	defer m.mu.Unlock()
+	if _, exists := m.value[key]; !exists {
+		m.value[key] = mapValue{now, int64(0)}
+		oName := path.Join(m.name, key)
+		lock.Lock()
+		if n := findNodeLocked(oName, true); n.object == nil {
+			n.object = &mapValueWrapper{m, key}
+		}
+		lock.Unlock()
+	}
+	var result interface{}
+	switch value := m.value[key].value.(type) {
+	case int64:
+		result = value + delta
+	case uint64:
+		if delta >= 0 {
+			result = value + uint64(delta)
+		} else {
+			result = value - uint64(-delta)
+		}
+	case float64:
+		result = value + float64(delta)
+	default:
+		return nil
+	}
+	m.value[key] = mapValue{now, result}
+	return result
+}
+
+// Delete deletes the given keys from the map object.
+func (m *Map) Delete(keys []string) {
+	// The lock order is important.
+	m.mu.Lock()
+	defer m.mu.Unlock()
+	lock.Lock()
+	defer lock.Unlock()
+	n := findNodeLocked(m.name, false)
+	for _, k := range keys {
+		delete(m.value, k)
+		if n != nil {
+			delete(n.children, k)
+		}
+	}
+}
+
+// Keys returns a sorted list of all the keys in the map.
+func (m *Map) Keys() []string {
+	m.mu.RLock()
+	defer m.mu.RUnlock()
+	keys := []string{}
+	for k, _ := range m.value {
+		keys = append(keys, k)
+	}
+	sort.Strings(keys)
+	return keys
+}
+
+// LastUpdate always returns a zero-value Time for a Map.
+func (m *Map) LastUpdate() time.Time {
+	return time.Time{}
+}
+
+// Value always returns nil for a Map.
+func (m *Map) Value() interface{} {
+	return nil
+}
+
+// insertMissingNodes inserts all the missing nodes.
+func (m *Map) insertMissingNodes() {
+	missing := []string{}
+	lock.RLock()
+	for key, _ := range m.value {
+		oName := path.Join(m.name, key)
+		if n := findNodeLocked(oName, false); n == nil {
+			missing = append(missing, key)
+		}
+	}
+	lock.RUnlock()
+	if len(missing) == 0 {
+		return
+	}
+
+	lock.Lock()
+	for _, key := range missing {
+		oName := path.Join(m.name, key)
+		if n := findNodeLocked(oName, true); n.object == nil {
+			n.object = &mapValueWrapper{m, key}
+		}
+	}
+	lock.Unlock()
+}
+
+type mapValueWrapper struct {
+	m   *Map
+	key string
+}
+
+// LastUpdate returns the time at which the parent map object was last updated.
+func (w *mapValueWrapper) LastUpdate() time.Time {
+	w.m.mu.RLock()
+	defer w.m.mu.RUnlock()
+	if v, ok := w.m.value[w.key]; ok {
+		return v.lastUpdate
+	}
+	return time.Time{}
+}
+
+// Value returns the current value for the map key.
+func (w *mapValueWrapper) Value() interface{} {
+	w.m.mu.RLock()
+	defer w.m.mu.RUnlock()
+	if v, ok := w.m.value[w.key]; ok {
+		return v.value
+	}
+	return nil
+}
diff --git a/lib/stats/stats.go b/lib/stats/stats.go
new file mode 100644
index 0000000..1ccaf92
--- /dev/null
+++ b/lib/stats/stats.go
@@ -0,0 +1,122 @@
+// 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 stats implements a global repository of stats objects. Each object
+// has a name and a value.
+// Example:
+//   bar1 := stats.NewInteger("foo/bar1")
+//   bar2 := stats.NewFloat("foo/bar2")
+//   bar3 := stats.NewCounter("foo/bar3")
+//   bar1.Set(1)
+//   bar2.Set(2)
+//   bar3.Set(3)
+// The values can be retrieved with:
+//   v, err := stats.Value("foo/bar1")
+package stats
+
+import (
+	"strings"
+	"sync"
+	"time"
+
+	"v.io/v23/services/stats"
+	"v.io/v23/verror"
+)
+
+// StatsObject is the interface for objects stored in the stats repository.
+type StatsObject interface {
+	// LastUpdate is used by WatchGlob to decide which updates to send.
+	LastUpdate() time.Time
+	// Value returns the current value of the object.
+	Value() interface{}
+}
+
+type node struct {
+	object   StatsObject
+	children map[string]*node
+}
+
+var (
+	lock       sync.RWMutex
+	repository *node // GUARDED_BY(lock)
+)
+
+func init() {
+	repository = newNode()
+}
+
+// GetStatsObject returns the object with that given name, or an error if the
+// object doesn't exist.
+func GetStatsObject(name string) (StatsObject, error) {
+	lock.RLock()
+	defer lock.RUnlock()
+	node := findNodeLocked(name, false)
+	if node == nil || node.object == nil {
+		return nil, verror.New(verror.ErrNoExist, nil, name)
+	}
+	return node.object, nil
+}
+
+// Value returns the value of an object, or an error if the object doesn't
+// exist.
+func Value(name string) (interface{}, error) {
+	obj, err := GetStatsObject(name)
+	if err != nil {
+		return 0, err
+	}
+	if obj == nil {
+		return nil, verror.New(stats.ErrNoValue, nil, name)
+	}
+	return obj.Value(), nil
+}
+
+// Delete deletes a StatsObject and all its children, if any.
+func Delete(name string) error {
+	if name == "" {
+		return verror.New(verror.ErrNoExist, nil, name)
+	}
+	elems := strings.Split(name, "/")
+	last := len(elems) - 1
+	dirname, basename := strings.Join(elems[:last], "/"), elems[last]
+	lock.Lock()
+	defer lock.Unlock()
+	parent := findNodeLocked(dirname, false)
+	if parent == nil {
+		return verror.New(verror.ErrNoExist, nil, name)
+	}
+	delete(parent.children, basename)
+	return nil
+}
+
+func newNode() *node {
+	return &node{children: make(map[string]*node)}
+}
+
+// findNodeLocked finds a node, and optionally creates it if it doesn't already
+// exist.
+func findNodeLocked(name string, create bool) *node {
+	elems := strings.Split(name, "/")
+	node := repository
+	for {
+		if len(elems) == 0 {
+			return node
+		}
+		if len(elems[0]) == 0 {
+			elems = elems[1:]
+			continue
+		}
+		if next, ok := node.children[elems[0]]; ok {
+			node = next
+			elems = elems[1:]
+			continue
+		}
+		if create {
+			node.children[elems[0]] = newNode()
+			node = node.children[elems[0]]
+			elems = elems[1:]
+			continue
+		}
+		return nil
+	}
+}
diff --git a/lib/stats/stats_test.go b/lib/stats/stats_test.go
new file mode 100644
index 0000000..4a9cfbd
--- /dev/null
+++ b/lib/stats/stats_test.go
@@ -0,0 +1,453 @@
+// 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 stats_test
+
+import (
+	"path/filepath"
+	"reflect"
+	"runtime"
+	"sync"
+	"testing"
+	"time"
+
+	"v.io/v23/verror"
+	libstats "v.io/x/ref/lib/stats"
+	"v.io/x/ref/lib/stats/counter"
+	"v.io/x/ref/lib/stats/histogram"
+	s_stats "v.io/x/ref/services/stats"
+)
+
+func doGlob(root, pattern string, since time.Time, includeValues bool) ([]libstats.KeyValue, error) {
+	it := libstats.Glob(root, pattern, since, includeValues)
+	out := []libstats.KeyValue{}
+	for it.Advance() {
+		out = append(out, it.Value())
+	}
+	if err := it.Err(); err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func TestStats(t *testing.T) {
+	now := time.Unix(1, 0)
+	counter.TimeNow = func() time.Time { return now }
+
+	a := libstats.NewInteger("rpc/test/aaa")
+	b := libstats.NewFloat("rpc/test/bbb")
+	c := libstats.NewString("rpc/test/ccc")
+	d := libstats.NewCounter("rpc/test/ddd")
+
+	a.Set(1)
+	b.Set(2)
+	c.Set("Hello")
+	d.Set(4)
+
+	got, err := libstats.Value("rpc/test/aaa")
+	if err != nil {
+		t.Errorf("unexpected error: %v", err)
+	}
+	if expected := int64(1); got != expected {
+		t.Errorf("unexpected result. Got %v, want %v", got, expected)
+	}
+
+	if _, err := libstats.Value(""); verror.ErrorID(err) != verror.ErrNoExist.ID {
+		t.Errorf("expected error %s, got err=%s", verror.ErrNoExist.ID, verror.ErrorID(err))
+	}
+	if _, err := libstats.Value("does/not/exist"); verror.ErrorID(err) != verror.ErrNoExist.ID {
+		t.Errorf("expected error %s, got err=%s", verror.ErrNoExist.ID, verror.ErrorID(err))
+	}
+
+	root := libstats.NewInteger("")
+	root.Set(42)
+	got, err = libstats.Value("")
+	if err != nil {
+		t.Errorf("unexpected error: %v", err)
+	}
+	if expected := int64(42); got != expected {
+		t.Errorf("unexpected result. Got %v, want %v", got, expected)
+	}
+
+	foo := libstats.NewInteger("foo")
+	foo.Set(55)
+	got, err = libstats.Value("foo")
+	if err != nil {
+		t.Errorf("unexpected error: %v", err)
+	}
+	if expected := int64(55); got != expected {
+		t.Errorf("unexpected result. Got %v, want %v", got, expected)
+	}
+
+	bar := libstats.NewInteger("foo/bar")
+	bar.Set(44)
+	got, err = libstats.Value("foo/bar")
+	if err != nil {
+		t.Errorf("unexpected error: %v", err)
+	}
+	if expected := int64(44); got != expected {
+		t.Errorf("unexpected result. Got %v, want %v", got, expected)
+	}
+
+	// Glob showing only nodes with a value.
+	result, err := doGlob("", "...", now, true)
+	if err != nil {
+		t.Errorf("unexpected error: %v", err)
+	}
+	expected := []libstats.KeyValue{
+		libstats.KeyValue{Key: "", Value: int64(42)},
+		libstats.KeyValue{Key: "foo", Value: int64(55)},
+		libstats.KeyValue{Key: "foo/bar", Value: int64(44)},
+		libstats.KeyValue{Key: "rpc/test/aaa", Value: int64(1)},
+		libstats.KeyValue{Key: "rpc/test/bbb", Value: float64(2)},
+		libstats.KeyValue{Key: "rpc/test/ccc", Value: string("Hello")},
+		libstats.KeyValue{Key: "rpc/test/ddd", Value: int64(4)},
+		libstats.KeyValue{Key: "rpc/test/ddd/delta10m", Value: int64(4)},
+		libstats.KeyValue{Key: "rpc/test/ddd/delta1h", Value: int64(4)},
+		libstats.KeyValue{Key: "rpc/test/ddd/delta1m", Value: int64(4)},
+		libstats.KeyValue{Key: "rpc/test/ddd/rate10m", Value: float64(0)},
+		libstats.KeyValue{Key: "rpc/test/ddd/rate1h", Value: float64(0)},
+		libstats.KeyValue{Key: "rpc/test/ddd/rate1m", Value: float64(0)},
+	}
+	if !reflect.DeepEqual(result, expected) {
+		t.Errorf("unexpected result. Got %#v, want %#v", result, expected)
+	}
+
+	result, err = doGlob("", "rpc/test/*", now, true)
+	if err != nil {
+		t.Errorf("unexpected error: %v", err)
+	}
+	expected = []libstats.KeyValue{
+		libstats.KeyValue{Key: "rpc/test/aaa", Value: int64(1)},
+		libstats.KeyValue{Key: "rpc/test/bbb", Value: float64(2)},
+		libstats.KeyValue{Key: "rpc/test/ccc", Value: string("Hello")},
+		libstats.KeyValue{Key: "rpc/test/ddd", Value: int64(4)},
+	}
+	if !reflect.DeepEqual(result, expected) {
+		t.Errorf("unexpected result. Got %#v, want %#v", result, expected)
+	}
+
+	// Glob showing all nodes without values
+	result, err = doGlob("", "rpc/...", time.Time{}, false)
+	if err != nil {
+		t.Errorf("unexpected error: %v", err)
+	}
+	expected = []libstats.KeyValue{
+		libstats.KeyValue{Key: "rpc"},
+		libstats.KeyValue{Key: "rpc/test"},
+		libstats.KeyValue{Key: "rpc/test/aaa"},
+		libstats.KeyValue{Key: "rpc/test/bbb"},
+		libstats.KeyValue{Key: "rpc/test/ccc"},
+		libstats.KeyValue{Key: "rpc/test/ddd"},
+		libstats.KeyValue{Key: "rpc/test/ddd/delta10m"},
+		libstats.KeyValue{Key: "rpc/test/ddd/delta1h"},
+		libstats.KeyValue{Key: "rpc/test/ddd/delta1m"},
+		libstats.KeyValue{Key: "rpc/test/ddd/rate10m"},
+		libstats.KeyValue{Key: "rpc/test/ddd/rate1h"},
+		libstats.KeyValue{Key: "rpc/test/ddd/rate1m"},
+	}
+	if !reflect.DeepEqual(result, expected) {
+		t.Errorf("unexpected result. Got %#v, want %#v", result, expected)
+	}
+
+	// Test the rate counter.
+	now = now.Add(10 * time.Second)
+	d.Incr(100)
+	result, err = doGlob("", "rpc/test/ddd/*", now, true)
+	if err != nil {
+		t.Errorf("unexpected error: %v", err)
+	}
+	expected = []libstats.KeyValue{
+		libstats.KeyValue{Key: "rpc/test/ddd/delta10m", Value: int64(104)},
+		libstats.KeyValue{Key: "rpc/test/ddd/delta1h", Value: int64(104)},
+		libstats.KeyValue{Key: "rpc/test/ddd/delta1m", Value: int64(104)},
+		libstats.KeyValue{Key: "rpc/test/ddd/rate10m", Value: float64(10.4)},
+		libstats.KeyValue{Key: "rpc/test/ddd/rate1h", Value: float64(0)},
+		libstats.KeyValue{Key: "rpc/test/ddd/rate1m", Value: float64(10.4)},
+	}
+	if !reflect.DeepEqual(result, expected) {
+		t.Errorf("unexpected result. Got %#v, want %#v", result, expected)
+	}
+
+	// Test Glob on non-root object.
+	result, err = doGlob("rpc/test", "*", time.Time{}, true)
+	if err != nil {
+		t.Errorf("unexpected error: %v", err)
+	}
+	expected = []libstats.KeyValue{
+		libstats.KeyValue{Key: "aaa", Value: int64(1)},
+		libstats.KeyValue{Key: "bbb", Value: float64(2)},
+		libstats.KeyValue{Key: "ccc", Value: string("Hello")},
+		libstats.KeyValue{Key: "ddd", Value: int64(104)},
+	}
+	if !reflect.DeepEqual(result, expected) {
+		t.Errorf("unexpected result. Got %#v, want %#v", result, expected)
+	}
+
+	result, err = doGlob("rpc/test/aaa", "", time.Time{}, true)
+	if err != nil {
+		t.Errorf("unexpected error: %v", err)
+	}
+	expected = []libstats.KeyValue{
+		libstats.KeyValue{Key: "", Value: int64(1)},
+	}
+	if !reflect.DeepEqual(result, expected) {
+		t.Errorf("unexpected result. Got %#v, want %#v", result, expected)
+	}
+
+	// Test LastUpdate. The test only works on Counters.
+	result, err = doGlob("rpc/test", "ddd", now, true)
+	if err != nil {
+		t.Errorf("unexpected error: %v", err)
+	}
+	expected = []libstats.KeyValue{
+		libstats.KeyValue{Key: "ddd", Value: int64(104)},
+	}
+	if !reflect.DeepEqual(result, expected) {
+		t.Errorf("unexpected result. Got %#v, want %#v", result, expected)
+	}
+
+	result, err = doGlob("rpc/test", "ddd", now.Add(time.Second), true)
+	if err != nil {
+		t.Errorf("unexpected error: %v", err)
+	}
+	expected = []libstats.KeyValue{}
+	if !reflect.DeepEqual(result, expected) {
+		t.Errorf("unexpected result. Got %#v, want %#v", result, expected)
+	}
+
+	// Test histogram
+	h := libstats.NewHistogram("rpc/test/hhh", histogram.Options{NumBuckets: 5, GrowthFactor: 0})
+	h.Add(1)
+	h.Add(2)
+
+	result, err = doGlob("", "rpc/test/hhh", now, true)
+	if err != nil {
+		t.Errorf("unexpected error: %v", err)
+	}
+	expected = []libstats.KeyValue{
+		libstats.KeyValue{
+			Key: "rpc/test/hhh",
+			Value: s_stats.HistogramValue{
+				Count: 2,
+				Sum:   3,
+				Min:   1,
+				Max:   2,
+				Buckets: []s_stats.HistogramBucket{
+					s_stats.HistogramBucket{LowBound: 0, Count: 0},
+					s_stats.HistogramBucket{LowBound: 1, Count: 1},
+					s_stats.HistogramBucket{LowBound: 2, Count: 1},
+					s_stats.HistogramBucket{LowBound: 3, Count: 0},
+					s_stats.HistogramBucket{LowBound: 4, Count: 0},
+				},
+			},
+		},
+	}
+	if !reflect.DeepEqual(result, expected) {
+		t.Errorf("unexpected result. Got %#v, want %#v", result, expected)
+	}
+
+	now = now.Add(30 * time.Second)
+	h.Add(2)
+	now = now.Add(30 * time.Second)
+	h.Add(3)
+
+	result, err = doGlob("", "rpc/test/hhh/delta1m", now, true)
+	if err != nil {
+		t.Errorf("unexpected error: %v", err)
+	}
+	expected = []libstats.KeyValue{
+		libstats.KeyValue{
+			Key: "rpc/test/hhh/delta1m",
+			Value: s_stats.HistogramValue{
+				Count: 2,
+				Sum:   5,
+				Min:   2,
+				Max:   3,
+				Buckets: []s_stats.HistogramBucket{
+					s_stats.HistogramBucket{LowBound: 0, Count: 0},
+					s_stats.HistogramBucket{LowBound: 1, Count: 0},
+					s_stats.HistogramBucket{LowBound: 2, Count: 1},
+					s_stats.HistogramBucket{LowBound: 3, Count: 1},
+					s_stats.HistogramBucket{LowBound: 4, Count: 0},
+				},
+			},
+		},
+	}
+	if !reflect.DeepEqual(result, expected) {
+		t.Errorf("unexpected result. Got %#v, want %#v", result, expected)
+	}
+}
+
+func TestMap(t *testing.T) {
+	m := libstats.NewMap("testing/foo")
+	m.Set([]libstats.KeyValue{{"a", uint64(1)}, {"b", 2}, {"c", float64(10.0)}})
+
+	// Test the Value of the map.
+	{
+		got, err := libstats.Value("testing/foo")
+		if err != nil {
+			t.Errorf("unexpected error: %v", err)
+		}
+		if expected := interface{}(nil); got != expected {
+			t.Errorf("unexpected result. Got %v, want %v", got, expected)
+		}
+	}
+	// Test Glob on the map object.
+	{
+		got, err := doGlob("testing", "foo/...", time.Time{}, true)
+		if err != nil {
+			t.Errorf("unexpected error: %v", err)
+		}
+		expected := []libstats.KeyValue{
+			libstats.KeyValue{Key: "foo", Value: nil},
+			libstats.KeyValue{Key: "foo/a", Value: uint64(1)},
+			libstats.KeyValue{Key: "foo/b", Value: int64(2)},
+			libstats.KeyValue{Key: "foo/c", Value: float64(10.0)},
+		}
+		if !reflect.DeepEqual(got, expected) {
+			t.Errorf("unexpected result. Got %#v, want %#v", got, expected)
+		}
+	}
+	// Test Incr
+	testcases := []struct {
+		key      string
+		incr     int64
+		expected interface{}
+	}{
+		{"a", 2, uint64(3)},
+		{"a", -1, uint64(2)},
+		{"b", 5, int64(7)},
+		{"c", -2, float64(8)},
+		{"d", -2, int64(-2)},
+	}
+	for i, tc := range testcases {
+		if got := m.Incr(tc.key, tc.incr); got != tc.expected {
+			t.Errorf("unexpected result for #%d. Got %v, expected %v", i, got, tc.expected)
+		}
+		got, err := libstats.Value("testing/foo/" + tc.key)
+		if err != nil {
+			t.Errorf("unexpected error for #%d: %v", i, err)
+		}
+		if got != tc.expected {
+			t.Errorf("unexpected result for #%d. Got %v, want %v", i, got, tc.expected)
+		}
+	}
+
+	m.Delete([]string{"a"})
+
+	// Test Glob on the map object.
+	{
+		got, err := doGlob("testing", "foo/...", time.Time{}, true)
+		if err != nil {
+			t.Errorf("unexpected error: %v", err)
+		}
+		expected := []libstats.KeyValue{
+			libstats.KeyValue{Key: "foo", Value: nil},
+			libstats.KeyValue{Key: "foo/b", Value: int64(7)},
+			libstats.KeyValue{Key: "foo/c", Value: float64(8)},
+			libstats.KeyValue{Key: "foo/d", Value: int64(-2)},
+		}
+		if !reflect.DeepEqual(got, expected) {
+			t.Errorf("unexpected result. Got %#v, want %#v", got, expected)
+		}
+	}
+}
+
+func TestFunc(t *testing.T) {
+	libstats.NewIntegerFunc("testing/integer", func() int64 { return 123 })
+	libstats.NewFloatFunc("testing/float", func() float64 { return 456.789 })
+	libstats.NewStringFunc("testing/string", func() string { return "Hello World" })
+	ch := make(chan int64, 5)
+
+	libstats.NewIntegerFunc("testing/timeout", func() int64 {
+		time.Sleep(time.Second)
+		return -1
+	})
+
+	libstats.NewIntegerFunc("testing/slowint", func() int64 {
+		return <-ch
+	})
+
+	testcases := []struct {
+		name     string
+		expected interface{}
+	}{
+		{"testing/integer", int64(123)},
+		{"testing/float", float64(456.789)},
+		{"testing/string", "Hello World"},
+		{"testing/timeout", nil}, // Times out
+	}
+	for _, tc := range testcases {
+		checkVariable(t, tc.name, tc.expected)
+	}
+
+	then := time.Now()
+	checkVariable(t, "testing/timeout", nil) // Times out
+	if took := time.Now().Sub(then); took < 100*time.Millisecond {
+		t.Fatalf("expected a timeout: took %s", took)
+	}
+	checkVariable(t, "testing/timeout", nil) // Times out
+	if took := time.Now().Sub(then); took < 100*time.Millisecond {
+		t.Fatalf("expected a timeout: took %s", took)
+	}
+
+	ch <- int64(0)
+	then = time.Now()
+	checkVariable(t, "testing/slowint", int64(0)) // New value
+	if took := time.Now().Sub(then); took > 100*time.Millisecond {
+		t.Fatalf("unexpected timeout: took %s", took)
+	}
+	for i := 1; i <= 5; i++ {
+		ch <- int64(i)
+	}
+	for i := 1; i <= 5; i++ {
+		checkVariable(t, "testing/slowint", int64(i)) // New value each time
+	}
+
+	// Parallel access
+	var wg sync.WaitGroup
+	for i := 1; i <= 5; i++ {
+		wg.Add(1)
+		go func() {
+			checkVariable(t, "testing/slowint", int64(555))
+			wg.Done()
+		}()
+	}
+	ch <- int64(555)
+	wg.Wait()
+}
+
+func checkVariable(t *testing.T, name string, expected interface{}) {
+	got, err := libstats.Value(name)
+	if err != nil {
+		t.Errorf("unexpected error for %q: %v", name, err)
+	}
+	if got != expected {
+		_, file, line, _ := runtime.Caller(1)
+		t.Errorf("%s:%d: unexpected result for %q. Got %v, want %v", filepath.Base(file), line, name, got, expected)
+	}
+}
+
+func TestDelete(t *testing.T) {
+	_ = libstats.NewInteger("a/b/c/d")
+	if _, err := libstats.GetStatsObject("a/b/c/d"); err != nil {
+		t.Errorf("unexpected error value: %v", err)
+	}
+	if err := libstats.Delete("a/b/c/d"); err != nil {
+		t.Errorf("unexpected error value: %v", err)
+	}
+	if _, err := libstats.GetStatsObject("a/b/c/d"); verror.ErrorID(err) != verror.ErrNoExist.ID {
+		t.Errorf("unexpected error value: Got %v, want %v", verror.ErrorID(err), verror.ErrNoExist.ID)
+	}
+	if err := libstats.Delete("a/b"); err != nil {
+		t.Errorf("unexpected error value: %v", err)
+	}
+	if _, err := libstats.GetStatsObject("a/b"); verror.ErrorID(err) != verror.ErrNoExist.ID {
+		t.Errorf("unexpected error value: Got %v, want %v", verror.ErrorID(err), verror.ErrNoExist.ID)
+	}
+	if _, err := libstats.GetStatsObject("a/b/c"); verror.ErrorID(err) != verror.ErrNoExist.ID {
+		t.Errorf("unexpected error value: Got %v, want %v", verror.ErrorID(err), verror.ErrNoExist.ID)
+	}
+}
diff --git a/lib/stats/string.go b/lib/stats/string.go
new file mode 100644
index 0000000..3162ce0
--- /dev/null
+++ b/lib/stats/string.go
@@ -0,0 +1,51 @@
+// 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 stats
+
+import (
+	"sync"
+	"time"
+)
+
+// NewString creates a new String StatsObject with the given name and
+// returns a pointer to it.
+func NewString(name string) *String {
+	lock.Lock()
+	defer lock.Unlock()
+
+	node := findNodeLocked(name, true)
+	s := String{value: ""}
+	node.object = &s
+	return &s
+}
+
+// String implements the StatsObject interface.
+type String struct {
+	mu         sync.RWMutex
+	lastUpdate time.Time
+	value      string
+}
+
+// Set sets the value of the object.
+func (s *String) Set(value string) {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+	s.lastUpdate = time.Now()
+	s.value = value
+}
+
+// LastUpdate returns the time at which the object was last updated.
+func (s *String) LastUpdate() time.Time {
+	s.mu.RLock()
+	defer s.mu.RUnlock()
+	return s.lastUpdate
+}
+
+// Value returns the current value of the object.
+func (s *String) Value() interface{} {
+	s.mu.RLock()
+	defer s.mu.RUnlock()
+	return s.value
+}
diff --git a/lib/stats/sysstats/sysstats.go b/lib/stats/sysstats/sysstats.go
new file mode 100644
index 0000000..98c52d1
--- /dev/null
+++ b/lib/stats/sysstats/sysstats.go
@@ -0,0 +1,98 @@
+// 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 sysstats implements system statistics and updates them periodically.
+//
+// Importing this package causes the stats to be exported via an init function.
+package sysstats
+
+import (
+	"os"
+	"reflect"
+	"runtime"
+	"strings"
+	"time"
+
+	"v.io/x/lib/metadata"
+	"v.io/x/ref/lib/stats"
+)
+
+func init() {
+	now := time.Now()
+	stats.NewInteger("system/start-time-unix").Set(now.Unix())
+	stats.NewString("system/start-time-rfc1123").Set(now.Format(time.RFC1123))
+	stats.NewString("system/cmdline").Set(strings.Join(os.Args, " "))
+	stats.NewInteger("system/num-cpu").Set(int64(runtime.NumCPU()))
+	stats.NewIntegerFunc("system/num-goroutine", func() int64 { return int64(runtime.NumGoroutine()) })
+	stats.NewString("system/version").Set(runtime.Version())
+	stats.NewInteger("system/pid").Set(int64(os.Getpid()))
+	if hostname, err := os.Hostname(); err == nil {
+		stats.NewString("system/hostname").Set(hostname)
+	}
+	stats.NewIntegerFunc("system/GOMAXPROCS", func() int64 { return int64(runtime.GOMAXPROCS(0)) })
+	exportEnv()
+	exportMemStats()
+	exportMetaData()
+}
+
+func exportEnv() {
+	var kv []stats.KeyValue
+	for _, v := range os.Environ() {
+		if parts := strings.SplitN(v, "=", 2); len(parts) == 2 {
+			kv = append(kv, stats.KeyValue{
+				Key:   parts[0],
+				Value: parts[1],
+			})
+		}
+	}
+	stats.NewMap("system/environ").Set(kv)
+}
+
+func exportMemStats() {
+	mstats := stats.NewMap("system/memstats")
+
+	// Get field names to export.
+	var memstats runtime.MemStats
+	fieldNames := []string{}
+	v := reflect.ValueOf(memstats)
+	v.FieldByNameFunc(func(name string) bool {
+		switch v.FieldByName(name).Kind() {
+		case reflect.Bool, reflect.Uint32, reflect.Uint64:
+			fieldNames = append(fieldNames, name)
+		}
+		return false
+	})
+	updateStats := func() {
+		var memstats runtime.MemStats
+		runtime.ReadMemStats(&memstats)
+		v := reflect.ValueOf(memstats)
+		kv := make([]stats.KeyValue, len(fieldNames))
+		for i, name := range fieldNames {
+			kv[i] = stats.KeyValue{
+				Key:   name,
+				Value: v.FieldByName(name).Interface(),
+			}
+		}
+		mstats.Set(kv)
+	}
+	// Update stats now and every 10 seconds afterwards.
+	updateStats()
+	go func() {
+		for {
+			time.Sleep(10 * time.Second)
+			updateStats()
+		}
+	}()
+}
+
+func exportMetaData() {
+	var kv []stats.KeyValue
+	for id, value := range metadata.ToMap() {
+		kv = append(kv, stats.KeyValue{
+			Key:   id,
+			Value: value,
+		})
+	}
+	stats.NewMap("system/metadata").Set(kv)
+}
diff --git a/lib/stats/sysstats/sysstats_test.go b/lib/stats/sysstats/sysstats_test.go
new file mode 100644
index 0000000..79213ad
--- /dev/null
+++ b/lib/stats/sysstats/sysstats_test.go
@@ -0,0 +1,63 @@
+// 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 sysstats_test
+
+import (
+	"os"
+	"runtime"
+	"testing"
+
+	"v.io/x/ref/lib/stats"
+	_ "v.io/x/ref/lib/stats/sysstats"
+)
+
+func TestMemStats(t *testing.T) {
+	alloc, err := stats.GetStatsObject("system/memstats/Alloc")
+	if err != nil {
+		t.Fatalf("unexpected error: %v", err)
+	}
+	if v := alloc.Value(); v == uint64(0) {
+		t.Errorf("unexpected Alloc value. Got %v, want != 0", v)
+	}
+}
+
+func TestVars(t *testing.T) {
+	hostname, err := os.Hostname()
+	if err != nil {
+		t.Fatalf("Hostname: unexpected error: %v", err)
+	}
+	for i, c := range []struct {
+		name     string
+		expected interface{}
+	}{
+		{"system/pid", int64(os.Getpid())},
+		{"system/hostname", hostname},
+		{"system/GOMAXPROCS", int64(runtime.GOMAXPROCS(0))},
+	} {
+		obj, err := stats.GetStatsObject(c.name)
+		if err != nil {
+			t.Fatalf("Case #%d: unexpected error: %v", i, err)
+		}
+		if got, want := obj.Value(), c.expected; got != want {
+			t.Errorf("Case #%d: unexpected result. Got %v, want %v", i, got, want)
+		}
+	}
+	oldGOMAXPROCS := runtime.GOMAXPROCS(0)
+	// set new value of GOMAXPROCS to the old value +/- 1 (doesn't matter as
+	// long as it's different).
+	newGOMAXPROCS := oldGOMAXPROCS - 1
+	if newGOMAXPROCS < 1 {
+		newGOMAXPROCS = oldGOMAXPROCS + 1
+	}
+	runtime.GOMAXPROCS(newGOMAXPROCS)
+	defer runtime.GOMAXPROCS(oldGOMAXPROCS)
+	obj, err := stats.GetStatsObject("system/GOMAXPROCS")
+	if err != nil {
+		t.Fatalf("unexpected error: %v", err)
+	}
+	if got, want := obj.Value(), int64(newGOMAXPROCS); got != want {
+		t.Errorf("unexpected result. Got %v, want %v", got, want)
+	}
+}
diff --git a/lib/timekeeper/timekeeper.go b/lib/timekeeper/timekeeper.go
new file mode 100644
index 0000000..2fee233
--- /dev/null
+++ b/lib/timekeeper/timekeeper.go
@@ -0,0 +1,42 @@
+// 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 timekeeper defines an interface to allow switching between real time
+// and simulated time.
+package timekeeper
+
+import "time"
+
+// TimeKeeper is meant as a drop-in replacement for using the time package
+// directly, and allows testing code to substitute a suitable implementation.
+// The meaning of duration and current time depends on the implementation (may
+// be a simulated time).
+type TimeKeeper interface {
+	// After waits for the duration to elapse and then sends the current
+	// time on the returned channel.
+	After(d time.Duration) <-chan time.Time
+	// Sleep pauses the current goroutine for at least the duration d. A
+	// negative or zero duration causes Sleep to return immediately.
+	Sleep(d time.Duration)
+}
+
+// realTime is the default implementation of TimeKeeper, using the time package.
+type realTime struct{}
+
+var rt realTime
+
+// After implements TimeKeeper.After.
+func (t *realTime) After(d time.Duration) <-chan time.Time {
+	return time.After(d)
+}
+
+// Sleep implements TimeKeeper.Sleep.
+func (t *realTime) Sleep(d time.Duration) {
+	time.Sleep(d)
+}
+
+// RealTime returns a default instance of TimeKeeper.
+func RealTime() TimeKeeper {
+	return &rt
+}
diff --git a/lib/timekeeper/timekeeper_test.go b/lib/timekeeper/timekeeper_test.go
new file mode 100644
index 0000000..e3c3b16
--- /dev/null
+++ b/lib/timekeeper/timekeeper_test.go
@@ -0,0 +1,40 @@
+// 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 timekeeper
+
+import (
+	"testing"
+	"time"
+)
+
+func TestAfter(t *testing.T) {
+	var tk TimeKeeper
+	tk = RealTime()
+	before := time.Now()
+	timeToSleep := 500000000 * time.Nanosecond // Half a second.
+	<-tk.After(timeToSleep)
+	after := time.Now()
+	if after.Before(before.Add(timeToSleep / 2)) {
+		t.Errorf("Too short: %s", after.Sub(before))
+	}
+	if after.After(before.Add(timeToSleep * 2)) {
+		t.Errorf("Too long: %s", after.Sub(before))
+	}
+}
+
+func TestSleep(t *testing.T) {
+	var tk TimeKeeper
+	tk = RealTime()
+	before := time.Now()
+	timeToSleep := 500000000 * time.Nanosecond // Half a second.
+	tk.Sleep(timeToSleep)
+	after := time.Now()
+	if after.Before(before.Add(timeToSleep / 2)) {
+		t.Errorf("Too short: %s", after.Sub(before))
+	}
+	if after.After(before.Add(timeToSleep * 2)) {
+		t.Errorf("Too long: %s", after.Sub(before))
+	}
+}
diff --git a/lib/v23cmd/v23cmd.go b/lib/v23cmd/v23cmd.go
new file mode 100644
index 0000000..3174701
--- /dev/null
+++ b/lib/v23cmd/v23cmd.go
@@ -0,0 +1,93 @@
+// 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 v23cmd implements utilities for running v23 cmdline programs.
+//
+// The cmdline package requires a Runner that implements Run(env, args), but for
+// v23 programs we'd like to implement Run(ctx, env, args) instead.  The
+// initialization of the ctx is also tricky, since v23.Init() calls flag.Parse,
+// but the cmdline package needs to call flag.Parse first.
+//
+// The RunnerFunc package-level function allows us to write run functions of the
+// form Run(ctx, env, args), retaining static type-safety, and also getting the
+// flag.Parse ordering right.
+package v23cmd
+
+import (
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/x/lib/cmdline"
+)
+
+type runner struct {
+	run  func(*context.T, *cmdline.Env, []string) error
+	init func() (*context.T, v23.Shutdown)
+}
+
+func (r runner) Run(env *cmdline.Env, args []string) error {
+	ctx, shutdown := r.init()
+	defer shutdown()
+	return r.run(ctx, env, args)
+}
+
+// RunnerFunc is like cmdline.RunnerFunc, but takes a run function that includes
+// a context as the first arg.  The context is created via v23.Init when Run is
+// called on the returned Runner.
+func RunnerFunc(run func(*context.T, *cmdline.Env, []string) error) cmdline.Runner {
+	return runner{run, v23.Init}
+}
+
+// RunnerFuncWithInit is like RunnerFunc, but allows specifying the init
+// function used to create the context.
+//
+// This is typically used to set properties on the context before it is passed
+// to the run function.  E.g. you may use this to set a deadline on the context:
+//
+//   var cmdRoot = &cmdline.Command{
+//     Runner: v23cmd.RunnerFuncWithInit(runRoot, initWithDeadline)
+//     ...
+//   }
+//
+//   func runRoot(ctx *context.T, env *cmdline.Env, args []string) error {
+//     ...
+//   }
+//
+//   func initWithDeadline() (*context.T, v23.Shutdown) {
+//     ctx, shutdown := v23.Init()
+//     ctx, cancel := context.WithTimeout(ctx, time.Minute)
+//     return ctx, func(){ cancel(); shutdown() }
+//   }
+//
+//   func main() {
+//     cmdline.Main(cmdRoot)
+//   }
+//
+// An alternative to the above example is to call context.WithTimeout within
+// runRoot.  The advantage of using RunnerFuncWithInit is that your regular code
+// can use a context with a 1 minute timeout, while your testing code can use
+// v23cmd.ParseAndRunForTest to pass a context with a 10 second timeout.
+func RunnerFuncWithInit(run func(*context.T, *cmdline.Env, []string) error, init func() (*context.T, v23.Shutdown)) cmdline.Runner {
+	return runner{run, init}
+}
+
+// ParseAndRunForTest parses the cmd with the given env and args, and calls Run
+// on the returned runner.  If the runner was created by the v23cmd package,
+// calls the run function directly with the given ctx, env and args.
+//
+// Doesn't call v23.Init; the context initialization is up to you.
+//
+// Only meant to be called within tests - if used in non-test code the ordering
+// of flag.Parse calls will be wrong.  The correct ordering is for cmdline.Parse
+// to be called before v23.Init, but this function takes a ctx argument and then
+// calls cmdline.Parse.
+func ParseAndRunForTest(cmd *cmdline.Command, ctx *context.T, env *cmdline.Env, args []string) error {
+	r, args, err := cmdline.Parse(cmd, env, args)
+	if err != nil {
+		return err
+	}
+	if x, ok := r.(runner); ok {
+		return x.run(ctx, env, args)
+	}
+	return r.Run(env, args)
+}
diff --git a/lib/v23cmd/v23cmd_test.go b/lib/v23cmd/v23cmd_test.go
new file mode 100644
index 0000000..c053962
--- /dev/null
+++ b/lib/v23cmd/v23cmd_test.go
@@ -0,0 +1,117 @@
+// 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 v23cmd
+
+import (
+	"bytes"
+	"fmt"
+	"testing"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/x/lib/cmdline"
+	_ "v.io/x/ref/runtime/factories/generic"
+)
+
+var cmdRoot = &cmdline.Command{
+	Name:     "root",
+	Short:    "root",
+	Long:     "root",
+	Children: []*cmdline.Command{cmdNoV23, cmdNoInit, cmdWithInit},
+}
+
+var cmdNoV23 = &cmdline.Command{
+	Name:   "no_v23",
+	Short:  "no_v23",
+	Long:   "no_v23",
+	Runner: cmdline.RunnerFunc(runNoV23),
+}
+
+var cmdNoInit = &cmdline.Command{
+	Name:   "no_init",
+	Short:  "no_init",
+	Long:   "no_init",
+	Runner: RunnerFunc(runNoInit),
+}
+
+var cmdWithInit = &cmdline.Command{
+	Name:   "with_init",
+	Short:  "with_init",
+	Long:   "with_init",
+	Runner: RunnerFuncWithInit(runWithInit, initFunc),
+}
+
+func runNoV23(env *cmdline.Env, args []string) error {
+	fmt.Fprintf(env.Stdout, "NoV23")
+	return nil
+}
+
+func runNoInit(ctx *context.T, env *cmdline.Env, args []string) error {
+	fmt.Fprintf(env.Stdout, "NoInit %v %v", ctx.Value(initKey), ctx.Value(forTestKey))
+	return nil
+}
+
+func runWithInit(ctx *context.T, env *cmdline.Env, args []string) error {
+	fmt.Fprintf(env.Stdout, "WithInit %v %v", ctx.Value(initKey), ctx.Value(forTestKey))
+	return nil
+}
+
+const (
+	initKey      = "init key"
+	initValue    = "<init value>"
+	forTestKey   = "for test key"
+	forTestValue = "<for test value>"
+)
+
+func initFunc() (*context.T, v23.Shutdown) {
+	ctx, shutdown := v23.Init()
+	return context.WithValue(ctx, initKey, initValue), shutdown
+}
+
+func TestParseAndRun(t *testing.T) {
+	tests := []struct {
+		cmd    string
+		stdout string
+	}{
+		{"no_v23", "NoV23"},
+		{"no_init", "NoInit <nil> <nil>"},
+		{"with_init", "WithInit <init value> <nil>"},
+	}
+	for _, test := range tests {
+		var stdout bytes.Buffer
+		env := &cmdline.Env{Stdout: &stdout}
+		if err := cmdline.ParseAndRun(cmdRoot, env, []string{test.cmd}); err != nil {
+			t.Errorf("ParseAndRun failed: %v", err)
+		}
+		if got, want := stdout.String(), test.stdout; got != want {
+			t.Errorf("got stdout %q, want %q", got, want)
+		}
+	}
+}
+
+func TestParseAndRunForTest(t *testing.T) {
+	tests := []struct {
+		cmd    string
+		stdout string
+	}{
+		{"no_v23", "NoV23"},
+		{"no_init", "NoInit <nil> <for test value>"},
+		{"with_init", "WithInit <nil> <for test value>"},
+	}
+	for _, test := range tests {
+		ctx, shutdown := v23.Init()
+		ctx = context.WithValue(ctx, forTestKey, forTestValue)
+
+		var stdout bytes.Buffer
+		env := &cmdline.Env{Stdout: &stdout}
+		if err := ParseAndRunForTest(cmdRoot, ctx, env, []string{test.cmd}); err != nil {
+			t.Errorf("ParseAndRunForTest failed: %v", err)
+		}
+		if got, want := stdout.String(), test.stdout; got != want {
+			t.Errorf("got stdout %q, want %q", got, want)
+		}
+		shutdown()
+	}
+}
diff --git a/lib/vdl/build/build.go b/lib/vdl/build/build.go
new file mode 100644
index 0000000..8ce8ee7
--- /dev/null
+++ b/lib/vdl/build/build.go
@@ -0,0 +1,850 @@
+// 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 build implements utilities to collect VDL build information and run
+// the parser and compiler.
+//
+// VDL Packages
+//
+// VDL is organized into packages, where a package is a collection of one or
+// more source files.  The files in a package collectively define the types,
+// constants, services and errors belonging to the package; these are called
+// package elements.
+//
+// The package elements in package P may be used in another package Q.  First
+// package Q must import package P, and then refer to the package elements in P.
+// Imports define the package dependency graph, which must be acyclic.
+//
+// Build Strategy
+//
+// The steps to building a VDL package P:
+//   1) Compute the transitive closure of P's dependencies DEPS.
+//   2) Sort DEPS in dependency order.
+//   3) Build each package D in DEPS.
+//   3) Build package P.
+//
+// Building a package P requires that all elements used by P are understood,
+// including elements defined outside of P.  The only way for a change to
+// package Q to affect the build of P is if Q is in the transitive closure of
+// P's package dependencies.  However there may be false positives; the change
+// to Q might not actually affect P.
+//
+// The build process may perform more work than is strictly necessary, because
+// of these false positives.  However it is simple and correct.
+//
+// The TransitivePackages* functions implement build steps 1 and 2.
+//
+// The Build* functions implement build steps 3 and 4.
+//
+// Other functions provide related build information and utilities.
+package build
+
+import (
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"path"
+	"path/filepath"
+	"reflect"
+	"regexp"
+	"sort"
+	"strings"
+
+	"v.io/v23/vdl"
+	"v.io/v23/vdlroot/vdltool"
+	"v.io/x/lib/set"
+	"v.io/x/lib/toposort"
+	"v.io/x/ref/lib/vdl/compile"
+	"v.io/x/ref/lib/vdl/parse"
+	"v.io/x/ref/lib/vdl/vdlutil"
+)
+
+const vdlrootImportPrefix = "v.io/v23/vdlroot"
+
+// Package represents the build information for a vdl package.
+type Package struct {
+	// Name is the name of the package, specified in the vdl files.
+	// E.g. "bar"
+	Name string
+	// Path is the package path; the path used in VDL import clauses.
+	// E.g. "foo/bar".
+	Path string
+	// GenPath is the package path to use for code generation.  It is typically
+	// the same as Path, except for vdlroot standard packages.
+	// E.g. "v.io/v23/vdlroot/time"
+	GenPath string
+	// Dir is the absolute directory containing the package files.
+	// E.g. "/home/user/vanadium/vdl/src/foo/bar"
+	Dir string
+	// BaseFileNames is the list of sorted base vdl file names for this package.
+	// Join these with Dir to get absolute file names.
+	BaseFileNames []string
+	// Config is the configuration for this package, specified by an optional
+	// "vdl.config" file in the package directory.  If no "vdl.config" file
+	// exists, the zero value of Config is used.
+	Config vdltool.Config
+
+	// OpenFilesFunc is a function that opens the files with the given filenames,
+	// and returns a map from base file name to file contents.
+	OpenFilesFunc func(filenames []string) (map[string]io.ReadCloser, error)
+
+	openedFiles []io.Closer // files that need to be closed
+}
+
+// UnknownPathMode specifies the behavior when an unknown path is encountered.
+type UnknownPathMode int
+
+const (
+	UnknownPathIsIgnored UnknownPathMode = iota // Silently ignore unknown paths
+	UnknownPathIsError                          // Produce error for unknown paths
+)
+
+func (m UnknownPathMode) String() string {
+	switch m {
+	case UnknownPathIsIgnored:
+		return "UnknownPathIsIgnored"
+	case UnknownPathIsError:
+		return "UnknownPathIsError"
+	default:
+		return fmt.Sprintf("UnknownPathMode(%d)", m)
+	}
+}
+
+func (m UnknownPathMode) logOrErrorf(errs *vdlutil.Errors, format string, v ...interface{}) {
+	if m == UnknownPathIsIgnored {
+		vdlutil.Vlog.Printf(format, v...)
+	} else {
+		errs.Errorf(format, v...)
+	}
+}
+
+func pathPrefixDotOrDotDot(path string) bool {
+	// The point of this helper is to catch cases where the path starts with a
+	// . or .. element; note that  ... returns false.
+	spath := filepath.ToSlash(path)
+	return path == "." || path == ".." || strings.HasPrefix(spath, "./") || strings.HasPrefix(spath, "../")
+}
+
+func ignorePathElem(elem string) bool {
+	return (strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_")) &&
+		!pathPrefixDotOrDotDot(elem)
+}
+
+// validPackagePath returns true iff the path is valid; i.e. if none of the path
+// elems is ignored.
+func validPackagePath(path string) bool {
+	for _, elem := range strings.Split(path, "/") {
+		if ignorePathElem(elem) {
+			return false
+		}
+	}
+	return true
+}
+
+// New packages always start with an empty Name, which is filled in when we call
+// ds.addPackageAndDeps.
+func newPackage(path, genPath, dir string, mode UnknownPathMode, opts Opts, vdlenv *compile.Env) *Package {
+	pkg := &Package{Path: path, GenPath: genPath, Dir: dir, OpenFilesFunc: openFiles}
+	if err := pkg.initBaseFileNames(opts.exts()); err != nil {
+		mode.logOrErrorf(vdlenv.Errors, "%s: bad package dir (%v)", pkg.Dir, err)
+		return nil
+	}
+	// TODO(toddw): Add a mechanism in vdlutil.Errors to distinguish categories of
+	// errors, so that it's more obvious when errors are coming from vdl.config
+	// files vs *.vdl files.
+	origErrors := vdlenv.Errors.NumErrors()
+	if pkg.initVDLConfig(opts, vdlenv); origErrors != vdlenv.Errors.NumErrors() {
+		return nil
+	}
+	return pkg
+}
+
+// initBaseFileNames initializes p.BaseFileNames from the contents of p.Dir.
+func (p *Package) initBaseFileNames(exts map[string]bool) error {
+	infos, err := ioutil.ReadDir(p.Dir)
+	if err != nil {
+		return err
+	}
+	for _, info := range infos {
+		if info.IsDir() {
+			continue
+		}
+		if ignorePathElem(info.Name()) || !exts[filepath.Ext(info.Name())] {
+			vdlutil.Vlog.Printf("%s: ignoring file", filepath.Join(p.Dir, info.Name()))
+			continue
+		}
+		vdlutil.Vlog.Printf("%s: adding vdl file", filepath.Join(p.Dir, info.Name()))
+		p.BaseFileNames = append(p.BaseFileNames, info.Name())
+	}
+	if len(p.BaseFileNames) == 0 {
+		return fmt.Errorf("no vdl files")
+	}
+	return nil
+}
+
+// initVDLConfig initializes p.Config based on the optional vdl.config file.
+func (p *Package) initVDLConfig(opts Opts, vdlenv *compile.Env) {
+	name := path.Join(p.Path, opts.vdlConfigName())
+	configData, err := os.Open(filepath.Join(p.Dir, opts.vdlConfigName()))
+	switch {
+	case os.IsNotExist(err):
+		return
+	case err != nil:
+		vdlenv.Errors.Errorf("%s: couldn't open (%v)", name, err)
+		return
+	}
+	// Build the vdl.config file with an implicit "vdltool" import.  Note that the
+	// actual "vdltool" package has already been populated into vdlenv.
+	BuildConfigValue(name, configData, []string{"vdltool"}, vdlenv, &p.Config)
+	if err := configData.Close(); err != nil {
+		vdlenv.Errors.Errorf("%s: couldn't close (%v)", name, err)
+	}
+}
+
+// OpenFiles opens all files in the package and returns a map from base file
+// name to file contents.  CloseFiles must be called to close the files.
+func (p *Package) OpenFiles() (map[string]io.Reader, error) {
+	var filenames []string
+	for _, baseName := range p.BaseFileNames {
+		filenames = append(filenames, filepath.Join(p.Dir, baseName))
+	}
+	files, err := p.OpenFilesFunc(filenames)
+	if err != nil {
+		for _, c := range files {
+			c.Close()
+		}
+		return nil, err
+	}
+	// Convert map elem type from io.ReadCloser to io.Reader.
+	res := make(map[string]io.Reader, len(files))
+	for n, f := range files {
+		res[n] = f
+		p.openedFiles = append(p.openedFiles, f)
+	}
+	return res, nil
+}
+
+func openFiles(filenames []string) (map[string]io.ReadCloser, error) {
+	files := make(map[string]io.ReadCloser, len(filenames))
+	for _, filename := range filenames {
+		file, err := os.Open(filename)
+		if err != nil {
+			for _, c := range files {
+				c.Close()
+			}
+			return nil, err
+		}
+		files[path.Base(filename)] = file
+	}
+	return files, nil
+}
+
+// CloseFiles closes all files returned by OpenFiles.  Returns nil if all files
+// were closed successfully, otherwise returns one of the errors, dropping the
+// others.  Regardless of whether an error is returned, Close will be called on
+// all files.
+func (p *Package) CloseFiles() error {
+	var err error
+	for _, c := range p.openedFiles {
+		if err2 := c.Close(); err == nil {
+			err = err2
+		}
+	}
+	p.openedFiles = nil
+	return err
+}
+
+// SrcDirs returns a list of package root source directories, based on the
+// VDLPATH, VDLROOT and V23_ROOT environment variables.
+//
+// VDLPATH is a list of directories separated by filepath.ListSeparator;
+// e.g. the separator is ":" on UNIX, and ";" on Windows.  Each VDLPATH
+// directory must have a "src/" directory that holds vdl source code.  The path
+// below "src/" determines the import path.
+//
+// VDLROOT is a single directory specifying the location of the standard vdl
+// packages.  It has the same requirements as VDLPATH components.  If VDLROOT is
+// empty, we use V23_ROOT to construct the VDLROOT.  An error is reported if
+// neither VDLROOT nor V23_ROOT is specified.
+func SrcDirs(errs *vdlutil.Errors) []string {
+	var srcDirs []string
+	if root := vdlRootDir(errs); root != "" {
+		srcDirs = append(srcDirs, root)
+	}
+	return append(srcDirs, vdlPathSrcDirs(errs)...)
+}
+
+func vdlRootDir(errs *vdlutil.Errors) string {
+	vdlroot := os.Getenv("VDLROOT")
+	if vdlroot == "" {
+		// Try to construct VDLROOT out of V23_ROOT.
+		vroot := os.Getenv("V23_ROOT")
+		if vroot == "" {
+			errs.Error("Either VDLROOT or V23_ROOT must be set")
+			return ""
+		}
+		vdlroot = filepath.Join(vroot, "release", "go", "src", "v.io", "v23", "vdlroot")
+	}
+	abs, err := filepath.Abs(vdlroot)
+	if err != nil {
+		errs.Errorf("VDLROOT %q can't be made absolute (%v)", vdlroot, err)
+		return ""
+	}
+	return abs
+}
+
+func vdlPathSrcDirs(errs *vdlutil.Errors) []string {
+	var srcDirs []string
+	for _, dir := range filepath.SplitList(os.Getenv("VDLPATH")) {
+		if dir != "" {
+			abs, err := filepath.Abs(dir)
+			if err != nil {
+				errs.Errorf("VDLPATH src dir %q can't be made absolute (%v)", dir, err)
+				continue // keep going to collect all errors
+			}
+			srcDirs = append(srcDirs, abs)
+		}
+	}
+	if len(srcDirs) == 0 {
+		errs.Error("No src dirs; set VDLPATH to a valid value")
+		return nil
+	}
+	return srcDirs
+}
+
+// IsDirPath returns true iff the path is absolute, or begins with a . or
+// .. element.  The path denotes the package in that directory.
+func IsDirPath(path string) bool {
+	return filepath.IsAbs(path) || pathPrefixDotOrDotDot(path)
+}
+
+// IsImportPath returns true iff !IsDirPath.  The path P denotes the package in
+// directory DIR/src/P, for some DIR listed in SrcDirs.
+func IsImportPath(path string) bool {
+	return !IsDirPath(path)
+}
+
+// depSorter does the main work of collecting and sorting packages and their
+// dependencies.  The full syntax from the go cmdline tool is supported; we
+// allow both dirs and import paths, as well as the "all" and "..." wildcards.
+//
+// This is slightly complicated because of dirs, and the potential for symlinks.
+// E.g. let's say we have two directories, one a symlink to the other:
+//   /home/user/go/src/foo/bar/base
+//   /home/user/go/src/foo/bar2     symlink to bar
+//
+// The problem is that if the user has cwd pointing at one of the two "base"
+// dirs and specifies a relative directory ".." it's ambiguous which absolute
+// dir we'll end up with; file paths form a graph rather than a tree.  For more
+// details see http://plan9.bell-labs.com/sys/doc/lexnames.html
+//
+// This means that if the user builds a dir (rather than an import path), we
+// might not be able to deduce the package path.  Note that the error definition
+// mechanism relies on the package path to create implicit error ids, and this
+// must be known at the time the package is compiled.  To handle this we call
+// deducePackagePath and attempt to deduce the package path even if the user
+// builds a directory, and return errors if this fails.
+//
+// TODO(toddw): If we care about performance we could serialize the compiled
+// compile.Package information and write it out as compiler-generated artifacts,
+// similar to how the regular go tool generates *.a files under the top-level
+// pkg directory.
+type depSorter struct {
+	opts    Opts
+	srcDirs []string
+	pathMap map[string]*Package
+	dirMap  map[string]*Package
+	sorter  *toposort.Sorter
+	errs    *vdlutil.Errors
+	vdlenv  *compile.Env
+}
+
+func newDepSorter(opts Opts, errs *vdlutil.Errors) *depSorter {
+	ds := &depSorter{
+		opts:    opts,
+		srcDirs: SrcDirs(errs),
+		errs:    errs,
+		vdlenv:  compile.NewEnvWithErrors(errs),
+	}
+	ds.reset()
+	// Resolve the "vdltool" import and build all transitive packages into vdlenv.
+	// This special env is used when building "vdl.config" files, which have the
+	// implicit "vdltool" import.
+	if !ds.ResolvePath("vdltool", UnknownPathIsError) {
+		errs.Errorf(`can't resolve "vdltool" package`)
+	}
+	for _, pkg := range ds.Sort() {
+		BuildPackage(pkg, ds.vdlenv)
+	}
+	// We must reset back to an empty depSorter, to ensure the transitive packages
+	// returned by the depSorter don't include "vdltool".
+	ds.reset()
+	return ds
+}
+
+func (ds *depSorter) reset() {
+	ds.pathMap = make(map[string]*Package)
+	ds.dirMap = make(map[string]*Package)
+	ds.sorter = new(toposort.Sorter)
+}
+
+func (ds *depSorter) errorf(format string, v ...interface{}) {
+	ds.errs.Errorf(format, v...)
+}
+
+// ResolvePath resolves path into package(s) and adds them to the sorter.
+// Returns true iff path could be resolved.
+func (ds *depSorter) ResolvePath(path string, mode UnknownPathMode) bool {
+	if path == "all" {
+		// Special-case "all", with the same behavior as Go.
+		path = "..."
+	}
+	isDirPath := IsDirPath(path)
+	dots := strings.Index(path, "...")
+	switch {
+	case dots >= 0:
+		return ds.resolveWildcardPath(isDirPath, path[:dots], path[dots:])
+	case isDirPath:
+		return ds.resolveDirPath(path, mode) != nil
+	default:
+		return ds.resolveImportPath(path, mode, "") != nil
+	}
+}
+
+// resolveWildcardPath resolves wildcards for both dir and import paths.  The
+// prefix is everything before the first "...", and the suffix is everything
+// including and after the first "..."; note that multiple "..." wildcards may
+// occur within the suffix.  Returns true iff any packages were resolved.
+//
+// The strategy is to compute one or more root directories that contain
+// everything that could possibly be matched, along with a filename pattern to
+// match against.  Then we walk through each root directory, matching against
+// the pattern.
+func (ds *depSorter) resolveWildcardPath(isDirPath bool, prefix, suffix string) bool {
+	type dirAndSrc struct {
+		dir, src string
+	}
+	var rootDirs []dirAndSrc // root directories to walk through
+	var pattern string       // pattern to match against, starting after root dir
+	switch {
+	case isDirPath:
+		// prefix and suffix are directory paths.
+		dir, pre := filepath.Split(prefix)
+		pattern = filepath.Clean(pre + suffix)
+		rootDirs = append(rootDirs, dirAndSrc{filepath.Clean(dir), ""})
+	default:
+		// prefix and suffix are slash-separated import paths.
+		slashDir, pre := path.Split(prefix)
+		pattern = filepath.Clean(pre + filepath.FromSlash(suffix))
+		dir := filepath.FromSlash(slashDir)
+		for _, srcDir := range ds.srcDirs {
+			rootDirs = append(rootDirs, dirAndSrc{filepath.Join(srcDir, dir), srcDir})
+		}
+	}
+	matcher, err := createMatcher(pattern)
+	if err != nil {
+		ds.errorf("%v", err)
+		return false
+	}
+	// Walk through root dirs and subdirs, looking for matches.
+	resolvedAny := false
+	for _, root := range rootDirs {
+		filepath.Walk(root.dir, func(rootAndPath string, info os.FileInfo, err error) error {
+			// Ignore errors and non-directory elements.
+			if err != nil || !info.IsDir() {
+				return nil
+			}
+			// Skip the dir and subdirs if the elem should be ignored.
+			_, elem := filepath.Split(rootAndPath)
+			if ignorePathElem(elem) {
+				vdlutil.Vlog.Printf("%s: ignoring dir", rootAndPath)
+				return filepath.SkipDir
+			}
+			// Special-case to skip packages with the vdlroot import prefix.  These
+			// packages should only appear at the root of the package path space.
+			if root.src != "" {
+				pkgPath := strings.TrimPrefix(rootAndPath, root.src)
+				pkgPath = strings.TrimPrefix(pkgPath, "/")
+				if strings.HasPrefix(pkgPath, vdlrootImportPrefix) {
+					return filepath.SkipDir
+				}
+			}
+			// Ignore the dir if it doesn't match our pattern.  We still process the
+			// subdirs since they still might match.
+			//
+			// TODO(toddw): We could add an optimization to skip subdirs that can't
+			// possibly match the matcher.  E.g. given pattern "a..." we can skip
+			// the subdirs if the dir doesn't start with "a".
+			matchPath := rootAndPath[len(root.dir):]
+			if strings.HasPrefix(matchPath, pathSeparator) {
+				matchPath = matchPath[len(pathSeparator):]
+			}
+			if !matcher.MatchString(matchPath) {
+				return nil
+			}
+			// Finally resolve the dir.
+			if ds.resolveDirPath(rootAndPath, UnknownPathIsIgnored) != nil {
+				resolvedAny = true
+			}
+			return nil
+		})
+	}
+	return resolvedAny
+}
+
+const pathSeparator = string(filepath.Separator)
+
+// createMatcher creates a regexp matcher out of the file pattern.
+func createMatcher(pattern string) (*regexp.Regexp, error) {
+	rePat := regexp.QuoteMeta(pattern)
+	rePat = strings.Replace(rePat, `\.\.\.`, `.*`, -1)
+	// Add special-case so that x/... also matches x.
+	slashDotStar := regexp.QuoteMeta(pathSeparator) + ".*"
+	if strings.HasSuffix(rePat, slashDotStar) {
+		rePat = rePat[:len(rePat)-len(slashDotStar)] + "(" + slashDotStar + ")?"
+	}
+	rePat = `^` + rePat + `$`
+	matcher, err := regexp.Compile(rePat)
+	if err != nil {
+		return nil, fmt.Errorf("can't compile package path regexp %s: %v", rePat, err)
+	}
+	return matcher, nil
+}
+
+// resolveDirPath resolves dir into a Package.  Returns the package, or nil if
+// it can't be resolved.
+func (ds *depSorter) resolveDirPath(dir string, mode UnknownPathMode) *Package {
+	// If the package already exists in our dir map, we can just return it.
+	absDir, err := filepath.Abs(dir)
+	if err != nil {
+		ds.errorf("%s: can't make absolute (%v)", dir, err)
+	}
+	if pkg := ds.dirMap[absDir]; pkg != nil {
+		return pkg
+	}
+	// Deduce the package path, and ensure it corresponds to exactly one package.
+	// We always deduce the package path from the package directory, even if we
+	// originally resolved from an import path, and thus already "know" the
+	// package path.  This is to ensure we correctly handle vdl standard packages.
+	// E.g. if we're given "v.io/v23/vdlroot/vdltool" as an import path, the
+	// resulting package path must be "vdltool".
+	pkgPath, genPath, err := ds.deducePackagePath(absDir)
+	if err != nil {
+		ds.errorf("%s: can't deduce package path (%v)", absDir, err)
+		return nil
+	}
+	if !validPackagePath(pkgPath) {
+		mode.logOrErrorf(ds.errs, "%s: package path %q is invalid", absDir, pkgPath)
+		return nil
+	}
+	if pkg := ds.pathMap[pkgPath]; pkg != nil {
+		mode.logOrErrorf(ds.errs, "%s: package path %q already resolved from %s", absDir, pkgPath, pkg.Dir)
+		return nil
+	}
+	// Make sure the directory really exists, and add the package and deps.
+	fileInfo, err := os.Stat(absDir)
+	if err != nil {
+		mode.logOrErrorf(ds.errs, "%v", err)
+		return nil
+	}
+	if !fileInfo.IsDir() {
+		mode.logOrErrorf(ds.errs, "%s: package isn't a directory", absDir)
+		return nil
+	}
+	return ds.addPackageAndDeps(pkgPath, genPath, absDir, mode)
+}
+
+// resolveImportPath resolves pkgPath into a Package.  Returns the package, or
+// nil if it can't be resolved.
+func (ds *depSorter) resolveImportPath(pkgPath string, mode UnknownPathMode, prefix string) *Package {
+	pkgPath = path.Clean(pkgPath)
+	if pkg := ds.pathMap[pkgPath]; pkg != nil {
+		return pkg
+	}
+	if !validPackagePath(pkgPath) {
+		mode.logOrErrorf(ds.errs, "%s: import path %q is invalid", prefix, pkgPath)
+		return nil
+	}
+	// Special-case to disallow packages under the vdlroot dir.
+	if strings.HasPrefix(pkgPath, vdlrootImportPrefix) {
+		mode.logOrErrorf(ds.errs, "%s: import path %q is invalid (packages under vdlroot must be specified without the vdlroot prefix)", prefix, pkgPath)
+		return nil
+	}
+	// Look through srcDirs in-order until we find a valid package dir.
+	var dirs []string
+	for _, srcDir := range ds.srcDirs {
+		dir := filepath.Join(srcDir, filepath.FromSlash(pkgPath))
+		if pkg := ds.resolveDirPath(dir, UnknownPathIsIgnored); pkg != nil {
+			vdlutil.Vlog.Printf("%s: resolved import path %q", pkg.Dir, pkgPath)
+			return pkg
+		}
+		dirs = append(dirs, dir)
+	}
+	// We can't find a valid dir corresponding to this import path.
+	detail := "   " + strings.Join(dirs, "\n   ")
+	mode.logOrErrorf(ds.errs, "%s: can't resolve %q in any of:\n%s", prefix, pkgPath, detail)
+	return nil
+}
+
+// addPackageAndDeps adds the pkg and its dependencies to the sorter.
+func (ds *depSorter) addPackageAndDeps(path, genPath, dir string, mode UnknownPathMode) *Package {
+	pkg := newPackage(path, genPath, dir, mode, ds.opts, ds.vdlenv)
+	if pkg == nil {
+		return nil
+	}
+	vdlutil.Vlog.Printf("%s: resolved package path %q", pkg.Dir, pkg.Path)
+	ds.dirMap[pkg.Dir] = pkg
+	ds.pathMap[pkg.Path] = pkg
+	ds.sorter.AddNode(pkg)
+	pfiles := ParsePackage(pkg, parse.Opts{ImportsOnly: true}, ds.errs)
+	pkg.Name = parse.InferPackageName(pfiles, ds.errs)
+	for _, pf := range pfiles {
+		ds.addImportDeps(pkg, pf.Imports, filepath.Join(path, pf.BaseName))
+	}
+	return pkg
+}
+
+// addImportDeps adds transitive dependencies represented by imports to the
+// sorter.  If the pkg is non-nil, an edge is added between the pkg and its
+// dependencies; otherwise each dependency is added as an independent node.
+func (ds *depSorter) addImportDeps(pkg *Package, imports []*parse.Import, file string) {
+	for _, imp := range imports {
+		if dep := ds.resolveImportPath(imp.Path, UnknownPathIsError, file); dep != nil {
+			if pkg != nil {
+				ds.sorter.AddEdge(pkg, dep)
+			} else {
+				ds.sorter.AddNode(dep)
+			}
+		}
+	}
+}
+
+// AddConfigDeps takes a config file represented by its file name and src data,
+// and adds all transitive dependencies to the sorter.
+func (ds *depSorter) AddConfigDeps(fileName string, src io.Reader) {
+	if pconfig := parse.ParseConfig(fileName, src, parse.Opts{ImportsOnly: true}, ds.errs); pconfig != nil {
+		ds.addImportDeps(nil, pconfig.Imports, fileName)
+	}
+}
+
+// deducePackagePath deduces the package path for dir, by looking for prefix
+// matches against the src dirs.  The resulting package path may be incorrect
+// even if no errors are reported; see the depSorter comment for details.
+func (ds *depSorter) deducePackagePath(dir string) (string, string, error) {
+	for ix, srcDir := range ds.srcDirs {
+		if strings.HasPrefix(dir, srcDir) {
+			relPath, err := filepath.Rel(srcDir, dir)
+			if err != nil {
+				return "", "", err
+			}
+			pkgPath := path.Clean(filepath.ToSlash(relPath))
+			genPath := pkgPath
+			if ix == 0 {
+				// We matched against the first srcDir, which is the VDLROOT dir.  The
+				// genPath needs to include the vdlroot prefix.
+				genPath = path.Join(vdlrootImportPrefix, pkgPath)
+			}
+			return pkgPath, genPath, nil
+		}
+	}
+	return "", "", fmt.Errorf("no matching SrcDirs")
+}
+
+// Sort sorts all targets and returns the resulting list of Packages.
+func (ds *depSorter) Sort() []*Package {
+	sorted, cycles := ds.sorter.Sort()
+	if len(cycles) > 0 {
+		cycleStr := toposort.DumpCycles(cycles, printPackagePath)
+		ds.errorf("cyclic package dependency: %v", cycleStr)
+		return nil
+	}
+	if len(sorted) == 0 {
+		return nil
+	}
+	targets := make([]*Package, len(sorted))
+	for ix, iface := range sorted {
+		targets[ix] = iface.(*Package)
+	}
+	return targets
+}
+
+func printPackagePath(v interface{}) string {
+	return v.(*Package).Path
+}
+
+// Opts specifies additional options for collecting build information.
+type Opts struct {
+	// Extensions specifies the file name extensions for valid vdl files.  If
+	// empty we use ".vdl" by default.
+	Extensions []string
+
+	// VDLConfigName specifies the name of the optional config file in each vdl
+	// source package.  If empty we use "vdl.config" by default.
+	VDLConfigName string
+}
+
+func (o Opts) exts() map[string]bool {
+	ret := set.StringBool.FromSlice(o.Extensions)
+	if len(ret) == 0 {
+		ret = map[string]bool{".vdl": true}
+	}
+	return ret
+}
+
+func (o Opts) vdlConfigName() string {
+	if o.VDLConfigName != "" {
+		return o.VDLConfigName
+	}
+	return "vdl.config"
+}
+
+// TransitivePackages takes a list of paths, and returns the corresponding
+// packages and transitive dependencies, ordered by dependency.  Each path may
+// either be a directory (IsDirPath) or an import (IsImportPath).
+//
+// A path is a pattern if it includes one or more "..." wildcards, each of which
+// can match any string, including the empty string and strings containing
+// slashes.  Such a pattern expands to all packages found in SrcDirs with names
+// matching the pattern.  As a special-case, x/... matches x as well as x's
+// subdirectories.
+//
+// The special-case "all" is a synonym for "...", and denotes all packages found
+// in SrcDirs.
+//
+// Import path elements and file names are not allowed to begin with "." or "_";
+// such paths are ignored in wildcard matches, and return errors if specified
+// explicitly.
+//
+// The mode specifies whether we should ignore or produce errors for paths that
+// don't resolve to any packages.  The opts arg specifies additional options.
+func TransitivePackages(paths []string, mode UnknownPathMode, opts Opts, errs *vdlutil.Errors) []*Package {
+	ds := newDepSorter(opts, errs)
+	for _, path := range paths {
+		if !ds.ResolvePath(path, mode) {
+			mode.logOrErrorf(errs, "can't resolve %q to any packages", path)
+		}
+	}
+	return ds.Sort()
+}
+
+// TransitivePackagesForConfig takes a config file represented by its file name
+// and src data, and returns all package dependencies in transitive order.
+//
+// The opts arg specifies additional options.
+func TransitivePackagesForConfig(fileName string, src io.Reader, opts Opts, errs *vdlutil.Errors) []*Package {
+	ds := newDepSorter(opts, errs)
+	ds.AddConfigDeps(fileName, src)
+	return ds.Sort()
+}
+
+// ParsePackage parses the given pkg with the given parse opts, and returns a
+// slice of parsed files, sorted by name.  Errors are reported in errs.
+func ParsePackage(pkg *Package, opts parse.Opts, errs *vdlutil.Errors) (pfiles []*parse.File) {
+	vdlutil.Vlog.Printf("Parsing package %s %q, dir %s", pkg.Name, pkg.Path, pkg.Dir)
+	files, err := pkg.OpenFiles()
+	if err != nil {
+		errs.Errorf("can't open vdl files %v (%v)", pkg.BaseFileNames, err)
+		return nil
+	}
+	for filename, src := range files {
+		if pf := parse.ParseFile(path.Join(pkg.Path, filename), src, opts, errs); pf != nil {
+			pfiles = append(pfiles, pf)
+		}
+	}
+	sort.Sort(byBaseName(pfiles))
+	pkg.CloseFiles()
+	return
+}
+
+// byBaseName implements sort.Interface
+type byBaseName []*parse.File
+
+func (b byBaseName) Len() int           { return len(b) }
+func (b byBaseName) Less(i, j int) bool { return b[i].BaseName < b[j].BaseName }
+func (b byBaseName) Swap(i, j int)      { b[i], b[j] = b[j], b[i] }
+
+// BuildPackage parses and compiles the given pkg, updates env with the compiled
+// package and returns it.  Errors are reported in env.
+//
+// All imports that pkg depend on must have already been compiled and populated
+// into env.
+func BuildPackage(pkg *Package, env *compile.Env) *compile.Package {
+	pfiles := ParsePackage(pkg, parse.Opts{}, env.Errors)
+	return compile.CompilePackage(pkg.Path, pkg.GenPath, pfiles, pkg.Config, env)
+}
+
+// BuildConfig parses and compiles the given config src and returns it.  Errors
+// are reported in env; fileName is only used for error reporting.
+//
+// The implicit type is applied to the exported config const; untyped consts and
+// composite literals with no explicit type assume the implicit type.  Errors
+// are reported if the implicit type isn't assignable from the final value.  If
+// the implicit type is nil, the exported config const must be explicitly typed.
+//
+// All packages that the config src depends on must have already been compiled
+// and populated into env.  The imports are injected into the parsed src,
+// behaving as if the src had listed the imports explicitly.
+func BuildConfig(fileName string, src io.Reader, implicit *vdl.Type, imports []string, env *compile.Env) *vdl.Value {
+	pconfig := parse.ParseConfig(fileName, src, parse.Opts{}, env.Errors)
+	if pconfig != nil {
+		pconfig.AddImports(imports...)
+	}
+	return compile.CompileConfig(implicit, pconfig, env)
+}
+
+// BuildConfigValue is a convenience function that runs BuildConfig, and then
+// converts the result into value.  The implicit type used by BuildConfig is
+// inferred from the value.
+func BuildConfigValue(fileName string, src io.Reader, imports []string, env *compile.Env, value interface{}) {
+	rv := reflect.ValueOf(value)
+	tt, err := vdl.TypeFromReflect(rv.Type())
+	if err != nil {
+		env.Errors.Errorf(err.Error())
+		return
+	}
+	if tt.Kind() == vdl.Optional {
+		// The value is typically a Go pointer, which translates into VDL optional.
+		// Remove the optional when determining the implicit type for BuildConfig.
+		tt = tt.Elem()
+	}
+	vconfig := BuildConfig(fileName, src, tt, imports, env)
+	if vconfig == nil {
+		return
+	}
+	target, err := vdl.ReflectTarget(rv)
+	if err != nil {
+		env.Errors.Errorf("can't create reflect target for %T (%v)", value, err)
+		return
+	}
+	if err := vdl.FromValue(target, vconfig); err != nil {
+		env.Errors.Errorf("can't convert to %T from %v (%v)", value, vconfig, err)
+		return
+	}
+}
+
+// BuildExprs parses and compiles the given data into a slice of values.  The
+// input data is specified in VDL syntax, with commas separating multiple
+// expressions.  There must be at least one expression specified in data.
+// Errors are reported in env.
+//
+// The given types specify the type of each returned value with the same slice
+// position.  If there are more types than returned values, the extra types are
+// ignored.  If there are fewer types than returned values, the last type is
+// used for all remaining values.  Nil entries in types are allowed, and
+// indicate that the expression itself must be fully typed.
+//
+// All imports that the input data depends on must have already been compiled
+// and populated into env.
+func BuildExprs(data string, types []*vdl.Type, env *compile.Env) []*vdl.Value {
+	var values []*vdl.Value
+	var t *vdl.Type
+	for ix, pexpr := range parse.ParseExprs(data, env.Errors) {
+		if ix < len(types) {
+			t = types[ix]
+		}
+		values = append(values, compile.CompileExpr(t, pexpr, env))
+	}
+	return values
+}
diff --git a/lib/vdl/build/build_test.go b/lib/vdl/build/build_test.go
new file mode 100644
index 0000000..38773a1
--- /dev/null
+++ b/lib/vdl/build/build_test.go
@@ -0,0 +1,640 @@
+// 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 build_test
+
+import (
+	"fmt"
+	"os"
+	"path"
+	"path/filepath"
+	"reflect"
+	"strings"
+	"testing"
+
+	"v.io/v23/vdl"
+	"v.io/v23/vdlroot/vdltool"
+	"v.io/x/ref/lib/vdl/build"
+	"v.io/x/ref/lib/vdl/compile"
+	"v.io/x/ref/lib/vdl/internal/vdltest"
+	"v.io/x/ref/lib/vdl/testdata/base"
+	"v.io/x/ref/lib/vdl/vdlutil"
+)
+
+func init() {
+	// Uncomment this to enable verbose logs for debugging.
+	//vdlutil.SetVerbose()
+}
+
+// The cwd is set to the directory containing this file.  Currently we have the
+// following directory structure:
+//   .../release/go/src/v.io/x/ref/lib/vdl/build/build_test.go
+// We want to end up with the following:
+//   VDLROOT = .../release/go/src/v.io/v23/vdlroot
+//   VDLPATH = .../release/go
+//
+// TODO(toddw): Put a full VDLPATH tree under ../testdata and only use that.
+const (
+	defaultVDLRoot = "../../../../../v23/vdlroot"
+	defaultVDLPath = "../../../../../.."
+)
+
+func setEnvironment(t *testing.T, vdlroot, vdlpath string) bool {
+	errRoot := os.Setenv("VDLROOT", vdlroot)
+	errPath := os.Setenv("VDLPATH", vdlpath)
+	if errRoot != nil {
+		t.Errorf("Setenv(VDLROOT, %q) failed: %v", vdlroot, errRoot)
+	}
+	if errPath != nil {
+		t.Errorf("Setenv(VDLPATH, %q) failed: %v", vdlpath, errPath)
+	}
+	return errRoot == nil && errPath == nil
+}
+
+func setVanadiumRoot(t *testing.T, root string) bool {
+	if err := os.Setenv("V23_ROOT", root); err != nil {
+		t.Errorf("Setenv(V23_ROOT, %q) failed: %v", root, err)
+		return false
+	}
+	return true
+}
+
+// Tests the VDLROOT part of SrcDirs().
+func TestSrcDirsVdlRoot(t *testing.T) {
+	cwd, err := os.Getwd()
+	if err != nil {
+		t.Fatalf("Getwd() failed: %v", err)
+	}
+	abs := func(relative string) string {
+		return filepath.Join(cwd, relative)
+	}
+	tests := []struct {
+		VDLRoot      string
+		VanadiumRoot string
+		Want         []string
+		ErrRE        string
+	}{
+		{"", "", nil, "Either VDLROOT or V23_ROOT must be set"},
+		{"/a", "", []string{"/a"}, ""},
+		{"/a/b/c", "", []string{"/a/b/c"}, ""},
+		{"", "/v23", []string{"/v23/release/go/src/v.io/v23/vdlroot"}, ""},
+		{"", "/a/b/c", []string{"/a/b/c/release/go/src/v.io/v23/vdlroot"}, ""},
+		// If both VDLROOT and V23_ROOT are specified, VDLROOT takes precedence.
+		{"/a", "/v23", []string{"/a"}, ""},
+		{"/a/b/c", "/x/y/z", []string{"/a/b/c"}, ""},
+	}
+	for _, test := range tests {
+		if !setEnvironment(t, test.VDLRoot, defaultVDLPath) || !setVanadiumRoot(t, test.VanadiumRoot) {
+			continue
+		}
+		name := fmt.Sprintf("%+v", test)
+		errs := vdlutil.NewErrors(-1)
+		got := build.SrcDirs(errs)
+		vdltest.ExpectResult(t, errs, name, test.ErrRE)
+		// Every result will have our valid VDLPATH srcdir.
+		vdlpath := abs(defaultVDLPath)
+		want := append(test.Want, vdlpath)
+		if !reflect.DeepEqual(got, want) {
+			t.Errorf("SrcDirs(%s) got %v, want %v", name, got, want)
+		}
+	}
+}
+
+// Tests the VDLPATH part of SrcDirs().
+func TestSrcDirsVdlPath(t *testing.T) {
+	cwd, err := os.Getwd()
+	if err != nil {
+		t.Fatalf("Getwd() failed: %v", err)
+	}
+	abs := func(relative string) string {
+		return filepath.Join(cwd, relative)
+	}
+	tests := []struct {
+		VDLPath string
+		Want    []string
+	}{
+		{"", nil},
+		// Test absolute paths.
+		{"/a", []string{"/a"}},
+		{"/a/b", []string{"/a/b"}},
+		{"/a:/b", []string{"/a", "/b"}},
+		{"/a/1:/b/2", []string{"/a/1", "/b/2"}},
+		{"/a/1:/b/2:/c/3", []string{"/a/1", "/b/2", "/c/3"}},
+		{":::/a/1::::/b/2::::/c/3:::", []string{"/a/1", "/b/2", "/c/3"}},
+		// Test relative paths.
+		{"a", []string{abs("a")}},
+		{"a/b", []string{abs("a/b")}},
+		{"a:b", []string{abs("a"), abs("b")}},
+		{"a/1:b/2", []string{abs("a/1"), abs("b/2")}},
+		{"a/1:b/2:c/3", []string{abs("a/1"), abs("b/2"), abs("c/3")}},
+		{":::a/1::::b/2::::c/3:::", []string{abs("a/1"), abs("b/2"), abs("c/3")}},
+		// Test mixed absolute / relative paths.
+		{"a:/b", []string{abs("a"), "/b"}},
+		{"/a/1:b/2", []string{"/a/1", abs("b/2")}},
+		{"/a/1:b/2:/c/3", []string{"/a/1", abs("b/2"), "/c/3"}},
+		{":::/a/1::::b/2::::/c/3:::", []string{"/a/1", abs("b/2"), "/c/3"}},
+	}
+	for _, test := range tests {
+		if !setEnvironment(t, defaultVDLRoot, test.VDLPath) {
+			continue
+		}
+		name := fmt.Sprintf("SrcDirs(%q)", test.VDLPath)
+		errs := vdlutil.NewErrors(-1)
+		got := build.SrcDirs(errs)
+		var errRE string
+		if test.Want == nil {
+			errRE = "No src dirs; set VDLPATH to a valid value"
+		}
+		vdltest.ExpectResult(t, errs, name, errRE)
+		// Every result will have our valid VDLROOT srcdir.
+		want := append([]string{abs(defaultVDLRoot)}, test.Want...)
+		if !reflect.DeepEqual(got, want) {
+			t.Errorf("%s got %v, want %v", name, got, want)
+		}
+	}
+}
+
+// Tests Is{Dir,Import}Path.
+func TestIsDirImportPath(t *testing.T) {
+	tests := []struct {
+		Path  string
+		IsDir bool
+	}{
+		// Import paths.
+		{"", false},
+		{"...", false},
+		{".../", false},
+		{"all", false},
+		{"foo", false},
+		{"foo/", false},
+		{"foo...", false},
+		{"foo/...", false},
+		{"a/b/c", false},
+		{"a/b/c/", false},
+		{"a/b/c...", false},
+		{"a/b/c/...", false},
+		{"...a/b/c...", false},
+		{"...a/b/c/...", false},
+		{".../a/b/c/...", false},
+		{".../a/b/c...", false},
+		// Dir paths.
+		{".", true},
+		{"..", true},
+		{"./", true},
+		{"../", true},
+		{"./...", true},
+		{"../...", true},
+		{".././.././...", true},
+		{"/", true},
+		{"/.", true},
+		{"/..", true},
+		{"/...", true},
+		{"/./...", true},
+		{"/foo", true},
+		{"/foo/", true},
+		{"/foo...", true},
+		{"/foo/...", true},
+		{"/a/b/c", true},
+		{"/a/b/c/", true},
+		{"/a/b/c...", true},
+		{"/a/b/c/...", true},
+		{"/a/b/c/../../...", true},
+	}
+	for _, test := range tests {
+		if got, want := build.IsDirPath(test.Path), test.IsDir; got != want {
+			t.Errorf("IsDirPath(%q) want %v", test.Path, want)
+		}
+		if got, want := build.IsImportPath(test.Path), !test.IsDir; got != want {
+			t.Errorf("IsImportPath(%q) want %v", test.Path, want)
+		}
+	}
+}
+
+var allModes = []build.UnknownPathMode{
+	build.UnknownPathIsIgnored,
+	build.UnknownPathIsError,
+}
+
+// Tests TransitivePackages success cases.
+func TestTransitivePackages(t *testing.T) {
+	if !setEnvironment(t, defaultVDLRoot, defaultVDLPath) {
+		t.Fatalf("Couldn't setEnvironment")
+	}
+	tests := []struct {
+		InPaths  []string // Input paths to TransitivePackages call
+		OutPaths []string // Wanted paths from build.Package.Path.
+		GenPaths []string // Wanted paths from build.Package.GenPath, same as OutPaths if nil.
+	}{
+		{nil, nil, nil},
+		{[]string{}, nil, nil},
+		// Single-package, both import and dir path.
+		{
+			[]string{"v.io/x/ref/lib/vdl/testdata/base"},
+			[]string{"v.io/x/ref/lib/vdl/testdata/base"},
+			nil,
+		},
+		{
+			[]string{"../testdata/base"},
+			[]string{"v.io/x/ref/lib/vdl/testdata/base"},
+			nil,
+		},
+		// Single-package with wildcard, both import and dir path.
+		{
+			[]string{"v.io/x/ref/lib/vdl/testdata/base..."},
+			[]string{"v.io/x/ref/lib/vdl/testdata/base"},
+			nil,
+		},
+		{
+			[]string{"v.io/x/ref/lib/vdl/testdata/base/..."},
+			[]string{"v.io/x/ref/lib/vdl/testdata/base"},
+			nil,
+		},
+		{
+			[]string{"../testdata/base..."},
+			[]string{"v.io/x/ref/lib/vdl/testdata/base"},
+			nil,
+		},
+		{
+			[]string{"../testdata/base/..."},
+			[]string{"v.io/x/ref/lib/vdl/testdata/base"},
+			nil,
+		},
+		// Redundant specification as both import and dir path.
+		{
+			[]string{"v.io/x/ref/lib/vdl/testdata/base", "../testdata/base"},
+			[]string{"v.io/x/ref/lib/vdl/testdata/base"},
+			nil,
+		},
+		{
+			[]string{"v.io/x/ref/lib/vdl/testdata/arith", "../testdata/arith"},
+			[]string{
+				"v.io/x/ref/lib/vdl/testdata/arith/exp",
+				"v.io/x/ref/lib/vdl/testdata/base",
+				"v.io/x/ref/lib/vdl/testdata/arith",
+			},
+			nil,
+		},
+		// Wildcards as both import and dir path.
+		{
+			[]string{"v.io/x/ref/lib/vdl/testdata..."},
+			[]string{
+				"v.io/x/ref/lib/vdl/testdata/arith/exp",
+				"v.io/x/ref/lib/vdl/testdata/base",
+				"v.io/x/ref/lib/vdl/testdata/arith",
+				"v.io/x/ref/lib/vdl/testdata/nativetest",
+				"v.io/x/ref/lib/vdl/testdata/nativedep",
+				"v.io/x/ref/lib/vdl/testdata/nativedep2",
+				"v.io/x/ref/lib/vdl/testdata/testconfig",
+			},
+			nil,
+		},
+		{
+			[]string{"v.io/x/ref/lib/vdl/testdata/..."},
+			[]string{
+				"v.io/x/ref/lib/vdl/testdata/arith/exp",
+				"v.io/x/ref/lib/vdl/testdata/base",
+				"v.io/x/ref/lib/vdl/testdata/arith",
+				"v.io/x/ref/lib/vdl/testdata/nativetest",
+				"v.io/x/ref/lib/vdl/testdata/nativedep",
+				"v.io/x/ref/lib/vdl/testdata/nativedep2",
+				"v.io/x/ref/lib/vdl/testdata/testconfig",
+			},
+			nil,
+		},
+		{
+			[]string{"../testdata..."},
+			[]string{
+				"v.io/x/ref/lib/vdl/testdata/arith/exp",
+				"v.io/x/ref/lib/vdl/testdata/base",
+				"v.io/x/ref/lib/vdl/testdata/arith",
+				"v.io/x/ref/lib/vdl/testdata/nativetest",
+				"v.io/x/ref/lib/vdl/testdata/nativedep",
+				"v.io/x/ref/lib/vdl/testdata/nativedep2",
+				"v.io/x/ref/lib/vdl/testdata/testconfig",
+			},
+			nil,
+		},
+		{
+			[]string{"../testdata/..."},
+			[]string{
+				"v.io/x/ref/lib/vdl/testdata/arith/exp",
+				"v.io/x/ref/lib/vdl/testdata/base",
+				"v.io/x/ref/lib/vdl/testdata/arith",
+				"v.io/x/ref/lib/vdl/testdata/nativetest",
+				"v.io/x/ref/lib/vdl/testdata/nativedep",
+				"v.io/x/ref/lib/vdl/testdata/nativedep2",
+				"v.io/x/ref/lib/vdl/testdata/testconfig",
+			},
+			nil,
+		},
+		// Multi-Wildcards as both import and dir path.
+		{
+			[]string{"v...vdl/testdata/..."},
+			[]string{
+				"v.io/x/ref/lib/vdl/testdata/arith/exp",
+				"v.io/x/ref/lib/vdl/testdata/base",
+				"v.io/x/ref/lib/vdl/testdata/arith",
+				"v.io/x/ref/lib/vdl/testdata/nativetest",
+				"v.io/x/ref/lib/vdl/testdata/nativedep",
+				"v.io/x/ref/lib/vdl/testdata/nativedep2",
+				"v.io/x/ref/lib/vdl/testdata/testconfig",
+			},
+			nil,
+		},
+		{
+			[]string{"../../...vdl/testdata/..."},
+			[]string{
+				"v.io/x/ref/lib/vdl/testdata/arith/exp",
+				"v.io/x/ref/lib/vdl/testdata/base",
+				"v.io/x/ref/lib/vdl/testdata/arith",
+				"v.io/x/ref/lib/vdl/testdata/nativetest",
+				"v.io/x/ref/lib/vdl/testdata/nativedep",
+				"v.io/x/ref/lib/vdl/testdata/nativedep2",
+				"v.io/x/ref/lib/vdl/testdata/testconfig",
+			},
+			nil,
+		},
+		// Multi-Wildcards as both import and dir path.
+		{
+			[]string{"v...vdl/testdata/...exp"},
+			[]string{"v.io/x/ref/lib/vdl/testdata/arith/exp"},
+			nil,
+		},
+		{
+			[]string{"../../...vdl/testdata/...exp"},
+			[]string{"v.io/x/ref/lib/vdl/testdata/arith/exp"},
+			nil,
+		},
+		// Standard vdl package, as both import and dir path.
+		{
+			[]string{"vdltool"},
+			[]string{"vdltool"},
+			[]string{"v.io/v23/vdlroot/vdltool"},
+		},
+		{
+			[]string{"../../../../../v23/vdlroot/vdltool"},
+			[]string{"vdltool"},
+			[]string{"v.io/v23/vdlroot/vdltool"},
+		},
+	}
+	for _, test := range tests {
+		// All modes should result in the same successful output.
+		for _, mode := range allModes {
+			name := fmt.Sprintf("%v %v", mode, test.InPaths)
+			errs := vdlutil.NewErrors(-1)
+			pkgs := build.TransitivePackages(test.InPaths, mode, build.Opts{}, errs)
+			vdltest.ExpectResult(t, errs, name, "")
+			var paths []string
+			for _, pkg := range pkgs {
+				paths = append(paths, pkg.Path)
+			}
+			if got, want := paths, test.OutPaths; !reflect.DeepEqual(got, want) {
+				t.Errorf("%v got path %v, want %v", name, got, want)
+			}
+			wantGen := test.GenPaths
+			if wantGen == nil {
+				wantGen = test.OutPaths
+			}
+			paths = nil
+			for _, pkg := range pkgs {
+				paths = append(paths, pkg.GenPath)
+			}
+			if got, want := paths, wantGen; !reflect.DeepEqual(got, want) {
+				t.Errorf("%v got gen path %v, want %v", name, got, want)
+			}
+		}
+	}
+}
+
+// Tests TransitivePackages error cases.
+func TestTransitivePackagesUnknownPathError(t *testing.T) {
+	if !setEnvironment(t, defaultVDLRoot, defaultVDLPath) {
+		t.Fatalf("Couldn't setEnvironment")
+	}
+	tests := []struct {
+		InPaths []string
+		ErrRE   string
+	}{
+		// Non-existent as both import and dir path.
+		{
+			[]string{"noexist"},
+			`can't resolve "noexist" to any packages`,
+		},
+		{
+			[]string{"./noexist"},
+			`can't resolve "./noexist" to any packages`,
+		},
+		// Invalid package path, as both import and dir path.
+		{
+			[]string{".foo"},
+			`import path ".foo" is invalid`,
+		},
+		{
+			[]string{"foo/.bar"},
+			`import path "foo/.bar" is invalid`,
+		},
+		{
+			[]string{"_foo"},
+			`import path "_foo" is invalid`,
+		},
+		{
+			[]string{"foo/_bar"},
+			`import path "foo/_bar" is invalid`,
+		},
+		{
+			[]string{"../../../../../../.foo"},
+			`package path ".foo" is invalid`,
+		},
+		{
+			[]string{"../../../../../../foo/.bar"},
+			`package path "foo/.bar" is invalid`,
+		},
+		{
+			[]string{"../../../../../../_foo"},
+			`package path "_foo" is invalid`,
+		},
+		{
+			[]string{"../../../../../../foo/_bar"},
+			`package path "foo/_bar" is invalid`,
+		},
+		// Special-case error for packages under vdlroot, which can't be imported
+		// using the vdlroot prefix.
+		{
+			[]string{"v.io/v23/vdlroot/vdltool"},
+			`packages under vdlroot must be specified without the vdlroot prefix`,
+		},
+		{
+			[]string{"v.io/v23/vdlroot/..."},
+			`can't resolve "v.io/v23/vdlroot/..." to any packages`,
+		},
+	}
+	for _, test := range tests {
+		for _, mode := range allModes {
+			name := fmt.Sprintf("%v %v", mode, test.InPaths)
+			errs := vdlutil.NewErrors(-1)
+			pkgs := build.TransitivePackages(test.InPaths, mode, build.Opts{}, errs)
+			errRE := test.ErrRE
+			if mode == build.UnknownPathIsIgnored {
+				// Ignore mode returns success, while error mode returns error.
+				errRE = ""
+			}
+			vdltest.ExpectResult(t, errs, name, errRE)
+			if pkgs != nil {
+				t.Errorf("%v got unexpected packages %v", name, pkgs)
+			}
+		}
+	}
+}
+
+// Tests vdl.config file support.
+func TestPackageConfig(t *testing.T) {
+	if !setEnvironment(t, defaultVDLRoot, defaultVDLPath) {
+		t.Fatalf("Couldn't setEnvironment")
+	}
+	tests := []struct {
+		Path   string
+		Config vdltool.Config
+	}{
+		{"v.io/x/ref/lib/vdl/testdata/base", vdltool.Config{}},
+		{
+			"v.io/x/ref/lib/vdl/testdata/testconfig",
+			vdltool.Config{
+				GenLanguages: map[vdltool.GenLanguage]struct{}{vdltool.GenLanguageGo: struct{}{}},
+			},
+		},
+	}
+	for _, test := range tests {
+		name := path.Base(test.Path)
+		env := compile.NewEnv(-1)
+		deps := build.TransitivePackages([]string{test.Path}, build.UnknownPathIsError, build.Opts{}, env.Errors)
+		vdltest.ExpectResult(t, env.Errors, name, "")
+		if len(deps) != 1 {
+			t.Fatalf("TransitivePackages(%q) got %v, want 1 dep", name, deps)
+		}
+		if got, want := deps[0].Name, name; got != want {
+			t.Errorf("TransitivePackages(%q) got Name %q, want %q", name, got, want)
+		}
+		if got, want := deps[0].Path, test.Path; got != want {
+			t.Errorf("TransitivePackages(%q) got Path %q, want %q", name, got, want)
+		}
+		if got, want := deps[0].Config, test.Config; !reflect.DeepEqual(got, want) {
+			t.Errorf("TransitivePackages(%q) got Config %+v, want %+v", name, got, want)
+		}
+	}
+}
+
+// Tests BuildConfig, BuildConfigValue and TransitivePackagesForConfig.
+func TestBuildConfig(t *testing.T) {
+	if !setEnvironment(t, defaultVDLRoot, defaultVDLPath) {
+		t.Fatalf("Couldn't setEnvironment")
+	}
+	tests := []struct {
+		Src   string
+		Value interface{}
+	}{
+		{
+			`config = x;import "v.io/x/ref/lib/vdl/testdata/base";const x = base.NamedBool(true)`,
+			base.NamedBool(true),
+		},
+		{
+			`config = x;import "v.io/x/ref/lib/vdl/testdata/base";const x = base.NamedString("abc")`,
+			base.NamedString("abc"),
+		},
+		{
+			`config = x;import "v.io/x/ref/lib/vdl/testdata/base";const x = base.Args{1, 2}`,
+			base.Args{1, 2},
+		},
+	}
+	for _, test := range tests {
+		// Build import package dependencies.
+		env := compile.NewEnv(-1)
+		deps := build.TransitivePackagesForConfig("file", strings.NewReader(test.Src), build.Opts{}, env.Errors)
+		for _, dep := range deps {
+			build.BuildPackage(dep, env)
+		}
+		vdltest.ExpectResult(t, env.Errors, test.Src, "")
+		// Test BuildConfig
+		wantV := vdl.ZeroValue(vdl.TypeOf(test.Value))
+		if err := vdl.Convert(wantV, test.Value); err != nil {
+			t.Errorf("Convert(%v) got error %v, want nil", test.Value, err)
+		}
+		gotV := build.BuildConfig("file", strings.NewReader(test.Src), nil, nil, env)
+		if !vdl.EqualValue(gotV, wantV) {
+			t.Errorf("BuildConfig(%v) got %v, want %v", test.Src, gotV, wantV)
+		}
+		vdltest.ExpectResult(t, env.Errors, test.Src, "")
+		// TestBuildConfigValue
+		gotRV := reflect.New(reflect.TypeOf(test.Value))
+		build.BuildConfigValue("file", strings.NewReader(test.Src), nil, env, gotRV.Interface())
+		if got, want := gotRV.Elem().Interface(), test.Value; !reflect.DeepEqual(got, want) {
+			t.Errorf("BuildConfigValue(%v) got %v, want %v", test.Src, got, want)
+		}
+		vdltest.ExpectResult(t, env.Errors, test.Src, "")
+	}
+}
+
+type ts []*vdl.Type
+type vs []*vdl.Value
+
+func TestBuildExprs(t *testing.T) {
+	ttArray := vdl.ArrayType(2, vdl.Int32Type)
+	ttStruct := vdl.StructType(
+		vdl.Field{
+			Name: "A",
+			Type: vdl.Int32Type,
+		}, vdl.Field{
+			Name: "B",
+			Type: vdl.StringType,
+		},
+	)
+	vvArray := vdl.ZeroValue(ttArray)
+	vvArray.Index(0).AssignInt(1)
+	vvArray.Index(1).AssignInt(-2)
+	vvStruct := vdl.ZeroValue(ttStruct)
+	vvStruct.StructField(0).AssignInt(1)
+	vvStruct.StructField(1).AssignString("abc")
+	tests := []struct {
+		Data  string
+		Types ts
+		Want  vs
+		Err   string
+	}{
+		{``, nil, nil, "syntax error"},
+		{`true`, nil, vs{vdl.BoolValue(true)}, ""},
+		{`false`, nil, vs{vdl.BoolValue(false)}, ""},
+		{`"abc"`, nil, vs{vdl.StringValue("abc")}, ""},
+		{`1`, nil, vs{nil}, "1 must be assigned a type"},
+		{`1`, ts{vdl.Int64Type}, vs{vdl.Int64Value(1)}, ""},
+		{`1.0`, ts{vdl.Int64Type}, vs{vdl.Int64Value(1)}, ""},
+		{`1.5`, ts{vdl.Int64Type}, vs{nil}, "loses precision"},
+		{`1.0`, ts{vdl.Float64Type}, vs{vdl.Float64Value(1.0)}, ""},
+		{`1.5`, ts{vdl.Float64Type}, vs{vdl.Float64Value(1.5)}, ""},
+		{`1+2`, ts{vdl.Int64Type}, vs{vdl.Int64Value(3)}, ""},
+		{`1+2,"abc"`, ts{vdl.Int64Type, nil}, vs{vdl.Int64Value(3), vdl.StringValue("abc")}, ""},
+		{`1,2,3`, ts{vdl.Int64Type}, vs{vdl.Int64Value(1), vdl.Int64Value(2), vdl.Int64Value(3)}, ""},
+		{`{1,-2}`, ts{ttArray}, vs{vvArray}, ""},
+		{`{0+1,1-3}`, ts{ttArray}, vs{vvArray}, ""},
+		{`{1,"abc"}`, ts{ttStruct}, vs{vvStruct}, ""},
+		{`{A:1,B:"abc"}`, ts{ttStruct}, vs{vvStruct}, ""},
+		{`{B:"abc",A:1}`, ts{ttStruct}, vs{vvStruct}, ""},
+		{`{B:"a"+"bc",A:1*1}`, ts{ttStruct}, vs{vvStruct}, ""},
+	}
+	for _, test := range tests {
+		env := compile.NewEnv(-1)
+		values := build.BuildExprs(test.Data, test.Types, env)
+		vdltest.ExpectResult(t, env.Errors, test.Data, test.Err)
+		if got, want := len(values), len(test.Want); got != want {
+			t.Errorf("%s got len %d, want %d", test.Data, got, want)
+		}
+		for ix, want := range test.Want {
+			var got *vdl.Value
+			if ix < len(values) {
+				got = values[ix]
+			}
+			if !vdl.EqualValue(got, want) {
+				t.Errorf("%s got value #%d %v, want %v", test.Data, ix, got, want)
+			}
+		}
+	}
+}
diff --git a/lib/vdl/codegen/golang/const.go b/lib/vdl/codegen/golang/const.go
new file mode 100644
index 0000000..de61370
--- /dev/null
+++ b/lib/vdl/codegen/golang/const.go
@@ -0,0 +1,227 @@
+// 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 golang
+
+import (
+	"fmt"
+	"strconv"
+
+	"v.io/v23/vdl"
+	"v.io/x/ref/lib/vdl/compile"
+	"v.io/x/ref/lib/vdl/parse"
+)
+
+func constDefGo(data goData, def *compile.ConstDef) string {
+	v := def.Value
+	return fmt.Sprintf("%s%s %s = %s%s", def.Doc, constOrVar(v.Kind()), def.Name, typedConst(data, v), def.DocSuffix)
+}
+
+func constOrVar(k vdl.Kind) string {
+	switch k {
+	case vdl.Bool, vdl.Byte, vdl.Uint16, vdl.Uint32, vdl.Uint64, vdl.Int16, vdl.Int32, vdl.Int64, vdl.Float32, vdl.Float64, vdl.Complex64, vdl.Complex128, vdl.String, vdl.Enum:
+		return "const"
+	}
+	return "var"
+}
+
+func isByteList(t *vdl.Type) bool {
+	return t.Kind() == vdl.List && t.Elem().Kind() == vdl.Byte
+}
+
+func tagValue(data goData, v *vdl.Value) string {
+	return typedConst(data, vdl.AnyValue(v))
+}
+
+// TODO(bprosnitz): Generate the full tag name e.g. security.Read instead of
+// security.Label(1)
+//
+// TODO(toddw): This doesn't work at all if v.Type() is a native type, or has
+// subtypes that are native types.  It's also broken for optional types that
+// can't be represented using a composite literal (e.g. optional primitives).
+//
+// https://github.com/vanadium/issues/issues/213
+func typedConst(data goData, v *vdl.Value) string {
+	k, t := v.Kind(), v.Type()
+	if k == vdl.Optional {
+		if elem := v.Elem(); elem != nil {
+			return "&" + typedConst(data, elem)
+		}
+		return "(" + typeGo(data, t) + ")(nil)" // results in (*Foo)(nil)
+	}
+	valstr := untypedConst(data, v)
+	// Enum, TypeObject and Any already include the type in their values.
+	// Built-in bool and string are implicitly convertible from literals.
+	if k == vdl.Enum || k == vdl.TypeObject || k == vdl.Any || t == vdl.BoolType || t == vdl.StringType {
+		return valstr
+	}
+	// Everything else requires an explicit type.
+	typestr := typeGo(data, t)
+	// { } are used instead of ( ) for composites
+	switch k {
+	case vdl.Array, vdl.Struct:
+		return typestr + valstr
+	case vdl.List, vdl.Set, vdl.Map:
+		// Special-case []byte, which we generate as a type conversion from string,
+		// and empty variable-length collections, which we generate as a type
+		// conversion from nil.
+		if !isByteList(t) && !v.IsZero() {
+			return typestr + valstr
+		}
+	}
+	return typestr + "(" + valstr + ")"
+}
+
+func untypedConst(data goData, v *vdl.Value) string {
+	k, t := v.Kind(), v.Type()
+	if isByteList(t) {
+		if v.IsZero() {
+			return "nil"
+		}
+		return strconv.Quote(string(v.Bytes()))
+	}
+	switch k {
+	case vdl.Any:
+		if elem := v.Elem(); elem != nil {
+			// We need to generate a Go expression of type *vdl.Value that represents
+			// elem.  Since the rest of our logic can already generate the Go code for
+			// any value, we just wrap it in vdl.ValueOf to produce the final result.
+			//
+			// This may seem like a strange roundtrip, but results in less generator
+			// and generated code.
+			return data.Pkg("v.io/v23/vdl") + "ValueOf(" + typedConst(data, elem) + ")"
+		}
+		return "(*" + data.Pkg("v.io/v23/vdl") + "Value)(nil)"
+	case vdl.Optional:
+		if elem := v.Elem(); elem != nil {
+			return untypedConst(data, elem)
+		}
+		return "nil"
+	case vdl.TypeObject:
+		// We special-case Any and TypeObject, since they cannot be named by the
+		// user, and are simple to return statically.
+		switch v.TypeObject().Kind() {
+		case vdl.Any:
+			return data.Pkg("v.io/v23/vdl") + "AnyType"
+		case vdl.TypeObject:
+			return data.Pkg("v.io/v23/vdl") + "TypeObjectType"
+		}
+		// We need to generate a Go expression of type *vdl.Type that represents the
+		// type.  Since the rest of our logic can already generate the Go code for
+		// any value, we just wrap it in vdl.TypeOf to produce the final result.
+		//
+		// This may seem like a strange roundtrip, but results in less generator
+		// and generated code.
+		zero := vdl.ZeroValue(v.TypeObject())
+		return data.Pkg("v.io/v23/vdl") + "TypeOf(" + typedConst(data, zero) + ")"
+	case vdl.Bool:
+		return strconv.FormatBool(v.Bool())
+	case vdl.Byte:
+		return strconv.FormatUint(uint64(v.Byte()), 10)
+	case vdl.Uint16, vdl.Uint32, vdl.Uint64:
+		return strconv.FormatUint(v.Uint(), 10)
+	case vdl.Int16, vdl.Int32, vdl.Int64:
+		return strconv.FormatInt(v.Int(), 10)
+	case vdl.Float32, vdl.Float64:
+		return formatFloat(v.Float(), k)
+	case vdl.Complex64, vdl.Complex128:
+		switch re, im := real(v.Complex()), imag(v.Complex()); {
+		case im > 0:
+			return formatFloat(re, k) + "+" + formatFloat(im, k) + "i"
+		case im < 0:
+			return formatFloat(re, k) + formatFloat(im, k) + "i"
+		default:
+			return formatFloat(re, k)
+		}
+	case vdl.String:
+		return strconv.Quote(v.RawString())
+	case vdl.Enum:
+		return typeGo(data, t) + v.EnumLabel()
+	case vdl.Array:
+		if v.IsZero() && !t.ContainsKind(vdl.WalkInline, vdl.TypeObject, vdl.Union) {
+			// We can't rely on the golang zero-value array if t contains inline
+			// typeobject or union, since the golang zero-value for these types is
+			// different from the vdl zero-value for these types.
+			return "{}"
+		}
+		s := "{"
+		for ix := 0; ix < v.Len(); ix++ {
+			s += "\n" + untypedConst(data, v.Index(ix)) + ","
+		}
+		return s + "\n}"
+	case vdl.List:
+		if v.IsZero() {
+			return "nil"
+		}
+		s := "{"
+		for ix := 0; ix < v.Len(); ix++ {
+			s += "\n" + untypedConst(data, v.Index(ix)) + ","
+		}
+		return s + "\n}"
+	case vdl.Set, vdl.Map:
+		if v.IsZero() {
+			return "nil"
+		}
+		s := "{"
+		for _, key := range vdl.SortValuesAsString(v.Keys()) {
+			s += "\n" + subConst(data, key)
+			if k == vdl.Set {
+				s += ": struct{}{},"
+			} else {
+				s += ": " + untypedConst(data, v.MapIndex(key)) + ","
+			}
+		}
+		return s + "\n}"
+	case vdl.Struct:
+		s := "{"
+		hasFields := false
+		for ix := 0; ix < t.NumField(); ix++ {
+			vf := v.StructField(ix)
+			if !vf.IsZero() || vf.Type().ContainsKind(vdl.WalkInline, vdl.TypeObject, vdl.Union) {
+				// We can't rely on the golang zero-value for this field, even if it's a
+				// vdl zero value, if the field contains inline typeobject or union,
+				// since the golang zero-value for these types is different from the vdl
+				// zero-value for these types.
+				s += "\n" + t.Field(ix).Name + ": " + subConst(data, vf) + ","
+				hasFields = true
+			}
+		}
+		if hasFields {
+			s += "\n"
+		}
+		return s + "}"
+	case vdl.Union:
+		ix, field := v.UnionField()
+		return typeGo(data, t) + t.Field(ix).Name + "{" + typedConst(data, field) + "}"
+	default:
+		data.Env.Errorf(data.File, parse.Pos{}, "%v untypedConst not implemented for %v %v", t, k)
+		return "INVALID"
+	}
+}
+
+// subConst deals with a quirk regarding Go composite literals.  Go allows us to
+// elide the type from composite literal Y when the type is implied; basically
+// when Y is contained in another composite literal X.  However it requires the
+// type for Y when X is a struct, and when X is a map and Y is the key.  As such
+// subConst is called for map keys and struct fields.
+func subConst(data goData, v *vdl.Value) string {
+	switch v.Kind() {
+	case vdl.Array, vdl.List, vdl.Set, vdl.Map, vdl.Struct, vdl.Optional:
+		return typedConst(data, v)
+	}
+	return untypedConst(data, v)
+}
+
+func formatFloat(x float64, kind vdl.Kind) string {
+	var bitSize int
+	switch kind {
+	case vdl.Float32, vdl.Complex64:
+		bitSize = 32
+	case vdl.Float64, vdl.Complex128:
+		bitSize = 64
+	default:
+		panic(fmt.Errorf("vdl: formatFloat unhandled kind: %v", kind))
+	}
+	return strconv.FormatFloat(x, 'g', -1, bitSize)
+}
diff --git a/lib/vdl/codegen/golang/const_test.go b/lib/vdl/codegen/golang/const_test.go
new file mode 100644
index 0000000..6439337
--- /dev/null
+++ b/lib/vdl/codegen/golang/const_test.go
@@ -0,0 +1,131 @@
+// 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 golang
+
+import (
+	"testing"
+
+	"v.io/v23/vdl"
+	"v.io/x/ref/lib/vdl/compile"
+)
+
+func TestConst(t *testing.T) {
+	testingMode = true
+	tests := []struct {
+		Name string
+		V    *vdl.Value
+		Want string
+	}{
+		{"True", vdl.BoolValue(true), `true`},
+		{"False", vdl.BoolValue(false), `false`},
+		{"String", vdl.StringValue("abc"), `"abc"`},
+		{"Bytes", vdl.BytesValue([]byte("abc")), `[]byte("abc")`},
+		{"Byte", vdl.ByteValue(111), `byte(111)`},
+		{"Uint16", vdl.Uint16Value(222), `uint16(222)`},
+		{"Uint32", vdl.Uint32Value(333), `uint32(333)`},
+		{"Uint64", vdl.Uint64Value(444), `uint64(444)`},
+		{"Int16", vdl.Int16Value(-555), `int16(-555)`},
+		{"Int32", vdl.Int32Value(-666), `int32(-666)`},
+		{"Int64", vdl.Int64Value(-777), `int64(-777)`},
+		{"Float32", vdl.Float32Value(1.5), `float32(1.5)`},
+		{"Float64", vdl.Float64Value(2.5), `float64(2.5)`},
+		{"Complex64", vdl.Complex64Value(1 + 2i), `complex64(1+2i)`},
+		{"Complex128", vdl.Complex128Value(3 + 4i), `complex128(3+4i)`},
+		{"Enum", vdl.ZeroValue(tEnum).AssignEnumLabel("B"), `TestEnumB`},
+		{"EmptyArray", vEmptyArray, "[3]string{}"},
+		{"EmptyList", vEmptyList, "[]string(nil)"},
+		{"EmptySet", vEmptySet, "map[string]struct{}(nil)"},
+		{"EmptyMap", vEmptyMap, "map[string]int64(nil)"},
+		{"EmptyStruct", vEmptyStruct, "TestStruct{}"},
+		{"Array", vArray, `[3]string{
+"A",
+"B",
+"C",
+}`},
+		{"List", vList, `[]string{
+"A",
+"B",
+"C",
+}`},
+		{"Set", vSet, `map[string]struct{}{
+"A": struct{}{},
+}`},
+		{"Map", vMap, `map[string]int64{
+"A": 1,
+}`},
+		{"Struct", vStruct, `TestStruct{
+A: "foo",
+B: 123,
+}`},
+		{"UnionABC", vUnionABC, `TestUnion(TestUnionA{"abc"})`},
+		{"Union123", vUnion123, `TestUnion(TestUnionB{int64(123)})`},
+		{"AnyABC", vAnyABC, `vdl.ValueOf("abc")`},
+		{"Any123", vAny123, `vdl.ValueOf(int64(123))`},
+		{"TypeObjectBool", vdl.TypeObjectValue(vdl.BoolType), `vdl.TypeOf(false)`},
+		{"TypeObjectString", vdl.TypeObjectValue(vdl.StringType), `vdl.TypeOf("")`},
+		{"TypeObjectBytes", vdl.TypeObjectValue(vdl.ListType(vdl.ByteType)), `vdl.TypeOf([]byte(nil))`},
+		{"TypeObjectByte", vdl.TypeObjectValue(vdl.ByteType), `vdl.TypeOf(byte(0))`},
+		{"TypeObjectUint16", vdl.TypeObjectValue(vdl.Uint16Type), `vdl.TypeOf(uint16(0))`},
+		{"TypeObjectInt16", vdl.TypeObjectValue(vdl.Int16Type), `vdl.TypeOf(int16(0))`},
+		{"TypeObjectFloat32", vdl.TypeObjectValue(vdl.Float32Type), `vdl.TypeOf(float32(0))`},
+		{"TypeObjectComplex64", vdl.TypeObjectValue(vdl.Complex64Type), `vdl.TypeOf(complex64(0))`},
+		{"TypeObjectEnum", vdl.TypeObjectValue(tEnum), `vdl.TypeOf(TestEnumA)`},
+		{"TypeObjectArray", vdl.TypeObjectValue(tArray), `vdl.TypeOf([3]string{})`},
+		{"TypeObjectList", vdl.TypeObjectValue(tList), `vdl.TypeOf([]string(nil))`},
+		{"TypeObjectSet", vdl.TypeObjectValue(tSet), `vdl.TypeOf(map[string]struct{}(nil))`},
+		{"TypeObjectMap", vdl.TypeObjectValue(tMap), `vdl.TypeOf(map[string]int64(nil))`},
+		{"TypeObjectStruct", vdl.TypeObjectValue(tStruct), `vdl.TypeOf(TestStruct{})`},
+		{"TypeObjectUnion", vdl.TypeObjectValue(tUnion), `vdl.TypeOf(TestUnion(TestUnionA{""}))`},
+		{"TypeObjectAny", vdl.TypeObjectValue(vdl.AnyType), `vdl.AnyType`},
+		{"TypeObjectTypeObject", vdl.TypeObjectValue(vdl.TypeObjectType), `vdl.TypeObjectType`},
+		// TODO(toddw): Add tests for optional types.
+	}
+	data := goData{Env: compile.NewEnv(-1)}
+	for _, test := range tests {
+		if got, want := typedConst(data, test.V), test.Want; got != want {
+			t.Errorf("%s\n GOT %s\nWANT %s", test.Name, got, want)
+		}
+	}
+}
+
+var (
+	vEmptyArray  = vdl.ZeroValue(tArray)
+	vEmptyList   = vdl.ZeroValue(tList)
+	vEmptySet    = vdl.ZeroValue(tSet)
+	vEmptyMap    = vdl.ZeroValue(tMap)
+	vEmptyStruct = vdl.ZeroValue(tStruct)
+
+	vArray    = vdl.ZeroValue(tArray)
+	vList     = vdl.ZeroValue(tList)
+	vSet      = vdl.ZeroValue(tSet)
+	vMap      = vdl.ZeroValue(tMap)
+	vStruct   = vdl.ZeroValue(tStruct)
+	vUnionABC = vdl.ZeroValue(tUnion)
+	vUnion123 = vdl.ZeroValue(tUnion)
+	vAnyABC   = vdl.ZeroValue(vdl.AnyType)
+	vAny123   = vdl.ZeroValue(vdl.AnyType)
+)
+
+func init() {
+	vArray.Index(0).AssignString("A")
+	vArray.Index(1).AssignString("B")
+	vArray.Index(2).AssignString("C")
+	vList.AssignLen(3)
+	vList.Index(0).AssignString("A")
+	vList.Index(1).AssignString("B")
+	vList.Index(2).AssignString("C")
+	// TODO(toddw): Assign more items once the ordering is fixed.
+	vSet.AssignSetKey(vdl.StringValue("A"))
+	vMap.AssignMapIndex(vdl.StringValue("A"), vdl.Int64Value(1))
+
+	vStruct.StructField(0).AssignString("foo")
+	vStruct.StructField(1).AssignInt(123)
+
+	vUnionABC.AssignUnionField(0, vdl.StringValue("abc"))
+	vUnion123.AssignUnionField(1, vdl.Int64Value(123))
+
+	vAnyABC.Assign(vdl.StringValue("abc"))
+	vAny123.Assign(vdl.Int64Value(123))
+}
diff --git a/lib/vdl/codegen/golang/gen.go b/lib/vdl/codegen/golang/gen.go
new file mode 100644
index 0000000..1d92ac0
--- /dev/null
+++ b/lib/vdl/codegen/golang/gen.go
@@ -0,0 +1,802 @@
+// 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 golang implements Go code generation from compiled VDL packages.
+package golang
+
+import (
+	"bytes"
+	"fmt"
+	"go/format"
+	"path"
+	"strings"
+	"text/template"
+
+	"v.io/v23/vdl"
+	"v.io/v23/vdlroot/vdltool"
+	"v.io/x/ref/lib/vdl/compile"
+	"v.io/x/ref/lib/vdl/parse"
+	"v.io/x/ref/lib/vdl/vdlutil"
+)
+
+type goData struct {
+	File    *compile.File
+	Env     *compile.Env
+	Imports *goImports
+}
+
+// testingMode is only set to true in tests, to make testing simpler.
+var testingMode = false
+
+func (data goData) Pkg(pkgPath string) string {
+	if testingMode {
+		return path.Base(pkgPath) + "."
+	}
+	// Special-case to avoid adding package qualifiers if we're generating code
+	// for that package.
+	if data.File.Package.GenPath == pkgPath {
+		return ""
+	}
+	if local := data.Imports.LookupLocal(pkgPath); local != "" {
+		return local + "."
+	}
+	data.Env.Errorf(data.File, parse.Pos{}, "missing package %q", pkgPath)
+	return ""
+}
+
+// Generate takes a populated compile.File and returns a byte slice containing
+// the generated Go source code.
+func Generate(file *compile.File, env *compile.Env) []byte {
+	validateGoConfig(file, env)
+	data := goData{
+		File:    file,
+		Env:     env,
+		Imports: newImports(file, env),
+	}
+	// The implementation uses the template mechanism from text/template and
+	// executes the template against the goData instance.
+	var buf bytes.Buffer
+	if err := goTemplate.Execute(&buf, data); err != nil {
+		// We shouldn't see an error; it means our template is buggy.
+		panic(fmt.Errorf("vdl: couldn't execute template: %v", err))
+	}
+	// Use gofmt to format the generated source.
+	pretty, err := format.Source(buf.Bytes())
+	if err != nil {
+		// We shouldn't see an error; it means we generated invalid code.
+		fmt.Printf("%s", buf.Bytes())
+		panic(fmt.Errorf("vdl: generated invalid Go code: %v", err))
+	}
+	return pretty
+}
+
+// The native types feature is hard to use correctly.  E.g. the package
+// containing the wire type must be imported into your Go binary in order for
+// the wire<->native registration to work, which is hard to ensure.  E.g.
+//
+//   package base    // VDL package
+//   type Wire int   // has native type native.Int
+//
+//   package dep     // VDL package
+//   import "base"
+//   type Foo struct {
+//     X base.Wire
+//   }
+//
+// The Go code for package "dep" imports "native", rather than "base":
+//
+//   package dep     // Go package generated from VDL package
+//   import "native"
+//   type Foo struct {
+//     X native.Int
+//   }
+//
+// Note that when you import the "dep" package in your own code, you always use
+// native.Int, rather than base.Wire; the base.Wire representation is only used
+// as the wire format, but doesn't appear in generated code.  But in order for
+// this to work correctly, the "base" package must imported.  This is tricky.
+//
+// Restrict the feature to these whitelisted VDL packages for now.
+var nativeTypePackageWhitelist = map[string]bool{
+	"time": true,
+	"v.io/x/ref/lib/vdl/testdata/nativetest": true,
+	"v.io/v23/security":                      true,
+}
+
+func validateGoConfig(file *compile.File, env *compile.Env) {
+	pkg := file.Package
+	vdlconfig := path.Join(pkg.GenPath, "vdl.config")
+	// Validate native type configuration.  Since native types are hard to use, we
+	// restrict them to a built-in whitelist of packages for now.
+	if len(pkg.Config.Go.WireToNativeTypes) > 0 && !nativeTypePackageWhitelist[pkg.Path] {
+		env.Errors.Errorf("%s: Go.WireToNativeTypes is restricted to whitelisted VDL packages", vdlconfig)
+	}
+	// Make sure each wire type is actually defined in the package, and required
+	// fields are all filled in.
+	for wire, native := range pkg.Config.Go.WireToNativeTypes {
+		if def := pkg.ResolveType(wire); def == nil {
+			env.Errors.Errorf("%s: type %s specified in Go.WireToNativeTypes undefined", vdlconfig, wire)
+		}
+		if native.Type == "" {
+			env.Errors.Errorf("%s: type %s specified in Go.WireToNativeTypes invalid (empty GoType.Type)", vdlconfig, wire)
+		}
+		for _, imp := range native.Imports {
+			if imp.Path == "" || imp.Name == "" {
+				env.Errors.Errorf("%s: type %s specified in Go.WireToNativeTypes invalid (empty GoImport.Path or Name)", vdlconfig, wire)
+				continue
+			}
+			importPrefix := imp.Name + "."
+			if !strings.Contains(native.Type, importPrefix) {
+				env.Errors.Errorf("%s: type %s specified in Go.WireToNativeTypes invalid (native type %q doesn't contain import prefix %q)", vdlconfig, wire, native.Type, importPrefix)
+			}
+		}
+	}
+}
+
+var goTemplate *template.Template
+
+// The template mechanism is great at high-level formatting and simple
+// substitution, but is bad at more complicated logic.  We define some functions
+// that we can use in the template so that when things get complicated we back
+// off to a regular function.
+func init() {
+	funcMap := template.FuncMap{
+		"firstRuneToExport":       vdlutil.FirstRuneToExportCase,
+		"firstRuneToUpper":        vdlutil.FirstRuneToUpper,
+		"firstRuneToLower":        vdlutil.FirstRuneToLower,
+		"errorName":               errorName,
+		"nativeIdent":             nativeIdent,
+		"typeGo":                  typeGo,
+		"typeDefGo":               typeDefGo,
+		"constDefGo":              constDefGo,
+		"tagValue":                tagValue,
+		"embedGo":                 embedGo,
+		"isStreamingMethod":       isStreamingMethod,
+		"hasStreamingMethods":     hasStreamingMethods,
+		"docBreak":                docBreak,
+		"quoteStripDoc":           parse.QuoteStripDoc,
+		"argNames":                argNames,
+		"argTypes":                argTypes,
+		"argNameTypes":            argNameTypes,
+		"argParens":               argParens,
+		"uniqueName":              uniqueName,
+		"uniqueNameImpl":          uniqueNameImpl,
+		"serverCallVar":           serverCallVar,
+		"serverCallStubVar":       serverCallStubVar,
+		"outArgsClient":           outArgsClient,
+		"clientStubImpl":          clientStubImpl,
+		"clientFinishImpl":        clientFinishImpl,
+		"serverStubImpl":          serverStubImpl,
+		"reInitStreamValue":       reInitStreamValue,
+		"nativeConversionsInFile": nativeConversionsInFile,
+	}
+	goTemplate = template.Must(template.New("genGo").Funcs(funcMap).Parse(genGo))
+}
+
+func errorName(def *compile.ErrorDef, file *compile.File) string {
+	switch {
+	case def.Exported:
+		return "Err" + def.Name
+	default:
+		return "err" + vdlutil.FirstRuneToUpper(def.Name)
+	}
+}
+
+func isStreamingMethod(method *compile.Method) bool {
+	return method.InStream != nil || method.OutStream != nil
+}
+
+func hasStreamingMethods(methods []*compile.Method) bool {
+	for _, method := range methods {
+		if isStreamingMethod(method) {
+			return true
+		}
+	}
+	return false
+}
+
+// docBreak adds a "//\n" break to separate previous comment lines and doc.  If
+// doc is empty it returns the empty string.
+func docBreak(doc string) string {
+	if doc == "" {
+		return ""
+	}
+	return "//\n" + doc
+}
+
+// argTypes returns a comma-separated list of each type from args.
+func argTypes(first, last string, data goData, args []*compile.Field) string {
+	var result []string
+	if first != "" {
+		result = append(result, first)
+	}
+	for _, arg := range args {
+		result = append(result, typeGo(data, arg.Type))
+	}
+	if last != "" {
+		result = append(result, last)
+	}
+	return strings.Join(result, ", ")
+}
+
+// argNames returns a comma-separated list of each name from args.  If argPrefix
+// is empty, the name specified in args is used; otherwise the name is prefixD,
+// where D is the position of the argument.
+func argNames(boxPrefix, argPrefix, first, second, last string, args []*compile.Field) string {
+	var result []string
+	if first != "" {
+		result = append(result, first)
+	}
+	if second != "" {
+		result = append(result, second)
+	}
+	for ix, arg := range args {
+		name := arg.Name
+		if argPrefix != "" {
+			name = fmt.Sprintf("%s%d", argPrefix, ix)
+		}
+		if arg.Type == vdl.ErrorType {
+			// TODO(toddw): Also need to box user-defined external interfaces.  Or can
+			// we remove this special-case now?
+			name = boxPrefix + name
+		}
+		result = append(result, name)
+	}
+	if last != "" {
+		result = append(result, last)
+	}
+	return strings.Join(result, ", ")
+}
+
+// argNameTypes returns a comma-separated list of "name type" from args.  If
+// argPrefix is empty, the name specified in args is used; otherwise the name is
+// prefixD, where D is the position of the argument.  If argPrefix is empty and
+// no names are specified in args, no names will be output.
+func argNameTypes(argPrefix, first, second, last string, data goData, args []*compile.Field) string {
+	noNames := argPrefix == "" && !hasArgNames(args)
+	var result []string
+	if first != "" {
+		result = append(result, maybeStripArgName(first, noNames))
+	}
+	if second != "" {
+		result = append(result, maybeStripArgName(second, noNames))
+	}
+	for ax, arg := range args {
+		var name string
+		switch {
+		case noNames:
+			break
+		case argPrefix == "":
+			name = arg.Name + " "
+		default:
+			name = fmt.Sprintf("%s%d ", argPrefix, ax)
+		}
+		result = append(result, name+typeGo(data, arg.Type))
+	}
+	if last != "" {
+		result = append(result, maybeStripArgName(last, noNames))
+	}
+	return strings.Join(result, ", ")
+}
+
+func hasArgNames(args []*compile.Field) bool {
+	// VDL guarantees that either all args are named, or none of them are.
+	return len(args) > 0 && args[0].Name != ""
+}
+
+// maybeStripArgName strips away the first space-terminated token from arg, only
+// if strip is true.
+func maybeStripArgName(arg string, strip bool) string {
+	if index := strings.Index(arg, " "); index != -1 && strip {
+		return arg[index+1:]
+	}
+	return arg
+}
+
+// argParens takes a list of 0 or more arguments, and adds parens only when
+// necessary; if args contains any commas or spaces, we must add parens.
+func argParens(argList string) string {
+	if strings.IndexAny(argList, ", ") > -1 {
+		return "(" + argList + ")"
+	}
+	return argList
+}
+
+// uniqueName returns a unique name based on the interface, method and suffix.
+func uniqueName(iface *compile.Interface, method *compile.Method, suffix string) string {
+	return iface.Name + method.Name + suffix
+}
+
+// uniqueNameImpl returns uniqueName with an "impl" prefix.
+func uniqueNameImpl(iface *compile.Interface, method *compile.Method, suffix string) string {
+	return "impl" + uniqueName(iface, method, suffix)
+}
+
+// The first arg of every server method is a context; the type is either a typed
+// context for streams, or rpc.ServerCall for non-streams.
+func serverCallVar(data goData, iface *compile.Interface, method *compile.Method) string {
+	if isStreamingMethod(method) {
+		return "call " + uniqueName(iface, method, "ServerCall")
+	}
+	return "call " + data.Pkg("v.io/v23/rpc") + "ServerCall"
+}
+
+// The first arg of every server stub method is a context; the type is either a
+// typed context stub for streams, or rpc.ServerCall for non-streams.
+func serverCallStubVar(data goData, iface *compile.Interface, method *compile.Method) string {
+	if isStreamingMethod(method) {
+		return "call *" + uniqueName(iface, method, "ServerCallStub")
+	}
+	return "call " + data.Pkg("v.io/v23/rpc") + "ServerCall"
+}
+
+// outArgsClient returns the out args of an interface method on the client,
+// wrapped in parens if necessary.  The client side always returns a final
+// error, in addition to the regular out-args.
+func outArgsClient(argPrefix string, data goData, iface *compile.Interface, method *compile.Method) string {
+	first, args := "", method.OutArgs
+	if isStreamingMethod(method) {
+		first, args = "ocall "+uniqueName(iface, method, "ClientCall"), nil
+	}
+	return argParens(argNameTypes(argPrefix, first, "", "err error", data, args))
+}
+
+// clientStubImpl returns the interface method client stub implementation.
+func clientStubImpl(data goData, iface *compile.Interface, method *compile.Method) string {
+	var buf bytes.Buffer
+	inargs := "nil"
+	if len(method.InArgs) > 0 {
+		inargs = "[]interface{}{" + argNames("&", "i", "", "", "", method.InArgs) + "}"
+	}
+	switch {
+	case isStreamingMethod(method):
+		fmt.Fprint(&buf, "\tvar call "+data.Pkg("v.io/v23/rpc")+"ClientCall\n")
+		fmt.Fprintf(&buf, "\tif call, err = "+data.Pkg("v.io/v23")+"GetClient(ctx).StartCall(ctx, c.name, %q, %s, opts...); err != nil {\n\t\treturn\n\t}\n", method.Name, inargs)
+		fmt.Fprintf(&buf, "ocall = &%s{ClientCall: call}\n", uniqueNameImpl(iface, method, "ClientCall"))
+	default:
+		outargs := "nil"
+		if len(method.OutArgs) > 0 {
+			outargs = "[]interface{}{" + argNames("", "&o", "", "", "", method.OutArgs) + "}"
+		}
+		fmt.Fprintf(&buf, "\terr = "+data.Pkg("v.io/v23")+"GetClient(ctx).Call(ctx, c.name, %q, %s, %s, opts...)\n", method.Name, inargs, outargs)
+	}
+	fmt.Fprint(&buf, "\treturn")
+	return buf.String() // the caller writes the trailing newline
+}
+
+// clientFinishImpl returns the client finish implementation for method.
+func clientFinishImpl(varname string, method *compile.Method) string {
+	outargs := argNames("", "&o", "", "", "", method.OutArgs)
+	return fmt.Sprintf("\terr = %s.Finish(%s)", varname, outargs)
+}
+
+// serverStubImpl returns the interface method server stub implementation.
+func serverStubImpl(data goData, iface *compile.Interface, method *compile.Method) string {
+	var buf bytes.Buffer
+	inargs := argNames("", "i", "ctx", "call", "", method.InArgs)
+	fmt.Fprintf(&buf, "\treturn s.impl.%s(%s)", method.Name, inargs)
+	return buf.String() // the caller writes the trailing newline
+}
+
+func reInitStreamValue(data goData, t *vdl.Type, name string) string {
+	switch t.Kind() {
+	case vdl.Struct:
+		return name + " = " + typeGo(data, t) + "{}\n"
+	case vdl.Any:
+		return name + " = nil\n"
+	}
+	return ""
+}
+
+// nativeConversionsInFile returns the map between wire and native types for
+// wire types defined in file.
+func nativeConversionsInFile(file *compile.File) map[string]vdltool.GoType {
+	all := file.Package.Config.Go.WireToNativeTypes
+	infile := make(map[string]vdltool.GoType)
+	for wire, gotype := range all {
+		for _, tdef := range file.TypeDefs {
+			if tdef.Name == wire {
+				infile[wire] = gotype
+				break
+			}
+		}
+	}
+	return infile
+}
+
+// The template that we execute against a goData instance to generate our
+// code.  Most of this is fairly straightforward substitution and ranges; more
+// complicated logic is delegated to the helper functions above.
+//
+// We try to generate code that has somewhat reasonable formatting, and leave
+// the fine-tuning to the go/format package.  Note that go/format won't fix
+// some instances of spurious newlines, so we try to keep it reasonable.
+const genGo = `
+{{$data := .}}
+{{$file := $data.File}}
+{{$file.Package.FileDoc}}
+
+// This file was auto-generated by the vanadium vdl tool.
+// Source: {{$file.BaseName}}
+
+{{$file.PackageDef.Doc}}package {{$file.PackageDef.Name}}{{$file.PackageDef.DocSuffix}}
+
+{{if or $data.Imports.System $data.Imports.User}}
+import ( {{if $data.Imports.System}}
+	// VDL system imports{{range $imp := $data.Imports.System}}
+	{{if $imp.Name}}{{$imp.Name}} {{end}}"{{$imp.Path}}"{{end}}{{end}}
+{{if $data.Imports.User}}
+	// VDL user imports{{range $imp := $data.Imports.User}}
+	{{if $imp.Name}}{{$imp.Name}} {{end}}"{{$imp.Path}}"{{end}}{{end}}
+){{end}}
+
+{{if $file.TypeDefs}}
+{{range $tdef := $file.TypeDefs}}
+{{typeDefGo $data $tdef}}
+{{end}}
+{{$nativeConversions := nativeConversionsInFile $file}}
+func init() { {{range $wire, $native := $nativeConversions}}{{$lwire := firstRuneToLower $wire}}
+	{{$data.Pkg "v.io/v23/vdl"}}RegisterNative({{$lwire}}ToNative, {{$lwire}}FromNative){{end}}{{range $tdef := $file.TypeDefs}}
+	{{$data.Pkg "v.io/v23/vdl"}}Register((*{{$tdef.Name}})(nil)){{end}}
+}
+{{range $wire, $native := $nativeConversions}}{{$lwire := firstRuneToLower $wire}}{{$nat := nativeIdent $data $native}}
+// Type-check {{$wire}} conversion functions.
+var _ func({{$wire}}, *{{$nat}}) error = {{$lwire}}ToNative
+var _ func(*{{$wire}}, {{$nat}}) error = {{$lwire}}FromNative
+{{end}}
+{{end}}
+
+{{range $cdef := $file.ConstDefs}}
+{{constDefGo $data $cdef}}
+{{end}}
+
+{{if $file.ErrorDefs}}var ( {{range $edef := $file.ErrorDefs}}
+	{{$edef.Doc}}{{errorName $edef $file}} = {{$data.Pkg "v.io/v23/verror"}}Register("{{$edef.ID}}", {{$data.Pkg "v.io/v23/verror"}}{{$edef.RetryCode}}, "{{$edef.English}}"){{end}}
+)
+
+{{/* TODO(toddw): Don't set "en-US" or "en" again, since it's already set by Register */}}
+func init() { {{range $edef := $file.ErrorDefs}}{{range $lf := $edef.Formats}}
+	{{$data.Pkg "v.io/v23/i18n"}}Cat().SetWithBase({{$data.Pkg "v.io/v23/i18n"}}LangID("{{$lf.Lang}}"), {{$data.Pkg "v.io/v23/i18n"}}MsgID({{errorName $edef $file}}.ID), "{{$lf.Fmt}}"){{end}}{{end}}
+}
+{{range $edef := $file.ErrorDefs}}
+{{$errName := errorName $edef $file}}
+{{$newErr := print (firstRuneToExport "New" $edef.Exported) (firstRuneToUpper $errName)}}
+// {{$newErr}} returns an error with the {{$errName}} ID.
+func {{$newErr}}(ctx {{argNameTypes "" (print "*" ($data.Pkg "v.io/v23/context") "T") "" "" $data $edef.Params}}) error {
+	return {{$data.Pkg "v.io/v23/verror"}}New({{$errName}}, {{argNames "" "" "ctx" "" "" $edef.Params}})
+}
+{{end}}{{end}}
+
+{{range $iface := $file.Interfaces}}
+{{$ifaceStreaming := hasStreamingMethods $iface.AllMethods}}
+{{$rpc_ := $data.Pkg "v.io/v23/rpc"}}
+{{$optsVar := print "opts ..." $rpc_ "CallOpt"}}
+{{$ctxVar := print "ctx *" ($data.Pkg "v.io/v23/context") "T"}}
+// {{$iface.Name}}ClientMethods is the client interface
+// containing {{$iface.Name}} methods.
+{{docBreak $iface.Doc}}type {{$iface.Name}}ClientMethods interface { {{range $embed := $iface.Embeds}}
+	{{$embed.Doc}}{{embedGo $data $embed}}ClientMethods{{$embed.DocSuffix}}{{end}}{{range $method := $iface.Methods}}
+	{{$method.Doc}}{{$method.Name}}({{argNameTypes "" $ctxVar "" $optsVar $data $method.InArgs}}) {{outArgsClient "" $data $iface $method}}{{$method.DocSuffix}}{{end}}
+}
+
+// {{$iface.Name}}ClientStub adds universal methods to {{$iface.Name}}ClientMethods.
+type {{$iface.Name}}ClientStub interface {
+	{{$iface.Name}}ClientMethods
+	{{$rpc_}}UniversalServiceMethods
+}
+
+// {{$iface.Name}}Client returns a client stub for {{$iface.Name}}.
+func {{$iface.Name}}Client(name string) {{$iface.Name}}ClientStub {
+	return impl{{$iface.Name}}ClientStub{name{{range $embed := $iface.Embeds}}, {{embedGo $data $embed}}Client(name){{end}} }
+}
+
+type impl{{$iface.Name}}ClientStub struct {
+	name   string
+{{range $embed := $iface.Embeds}}
+	{{embedGo $data $embed}}ClientStub{{end}}
+}
+
+{{range $method := $iface.Methods}}
+func (c impl{{$iface.Name}}ClientStub) {{$method.Name}}({{argNameTypes "i" $ctxVar "" $optsVar $data $method.InArgs}}) {{outArgsClient "o" $data $iface $method}} {
+{{clientStubImpl $data $iface $method}}
+}
+{{end}}
+
+{{range $method := $iface.Methods}}{{if isStreamingMethod $method}}
+{{$clientStream := uniqueName $iface $method "ClientStream"}}
+{{$clientCall := uniqueName $iface $method "ClientCall"}}
+{{$clientCallImpl := uniqueNameImpl $iface $method "ClientCall"}}
+{{$clientRecvImpl := uniqueNameImpl $iface $method "ClientCallRecv"}}
+{{$clientSendImpl := uniqueNameImpl $iface $method "ClientCallSend"}}
+
+// {{$clientStream}} is the client stream for {{$iface.Name}}.{{$method.Name}}.
+type {{$clientStream}} interface { {{if $method.OutStream}}
+	// RecvStream returns the receiver side of the {{$iface.Name}}.{{$method.Name}} client stream.
+	RecvStream() interface {
+		// Advance stages an item so that it may be retrieved via Value.  Returns
+		// true iff there is an item to retrieve.  Advance must be called before
+		// Value is called.  May block if an item is not available.
+		Advance() bool
+		// Value returns the item that was staged by Advance.  May panic if Advance
+		// returned false or was not called.  Never blocks.
+		Value() {{typeGo $data $method.OutStream}}
+		// Err returns any error encountered by Advance.  Never blocks.
+		Err() error
+	} {{end}}{{if $method.InStream}}
+	// SendStream returns the send side of the {{$iface.Name}}.{{$method.Name}} client stream.
+	SendStream() interface {
+		// Send places the item onto the output stream.  Returns errors
+		// encountered while sending, or if Send is called after Close or
+		// the stream has been canceled.  Blocks if there is no buffer
+		// space; will unblock when buffer space is available or after
+		// the stream has been canceled.
+		Send(item {{typeGo $data $method.InStream}}) error
+		// Close indicates to the server that no more items will be sent;
+		// server Recv calls will receive io.EOF after all sent items.
+		// This is an optional call - e.g. a client might call Close if it
+		// needs to continue receiving items from the server after it's
+		// done sending.  Returns errors encountered while closing, or if
+		// Close is called after the stream has been canceled.  Like Send,
+		// blocks if there is no buffer space available.
+		Close() error
+	} {{end}}
+}
+
+// {{$clientCall}} represents the call returned from {{$iface.Name}}.{{$method.Name}}.
+type {{$clientCall}} interface {
+	{{$clientStream}} {{if $method.InStream}}
+	// Finish performs the equivalent of SendStream().Close, then blocks until
+	// the server is done, and returns the positional return values for the call.{{else}}
+	// Finish blocks until the server is done, and returns the positional return
+	// values for call.{{end}}
+	//
+	// Finish returns immediately if the call has been canceled; depending on the
+	// timing the output could either be an error signaling cancelation, or the
+	// valid positional return values from the server.
+	//
+	// Calling Finish is mandatory for releasing stream resources, unless the call
+	// has been canceled or any of the other methods return an error.  Finish should
+	// be called at most once.
+	Finish() {{argParens (argNameTypes "" "" "" "err error" $data $method.OutArgs)}}
+}
+
+type {{$clientCallImpl}} struct {
+	{{$rpc_}}ClientCall{{if $method.OutStream}}
+	valRecv {{typeGo $data $method.OutStream}}
+	errRecv error{{end}}
+}
+
+{{if $method.OutStream}}func (c *{{$clientCallImpl}}) RecvStream() interface {
+	Advance() bool
+	Value() {{typeGo $data $method.OutStream}}
+	Err() error
+} {
+	return {{$clientRecvImpl}}{c}
+}
+
+type {{$clientRecvImpl}} struct {
+	c *{{$clientCallImpl}}
+}
+
+func (c {{$clientRecvImpl}}) Advance() bool {
+	{{reInitStreamValue $data $method.OutStream "c.c.valRecv"}}c.c.errRecv = c.c.Recv(&c.c.valRecv)
+	return c.c.errRecv == nil
+}
+func (c {{$clientRecvImpl}}) Value() {{typeGo $data $method.OutStream}} {
+	return c.c.valRecv
+}
+func (c {{$clientRecvImpl}}) Err() error {
+	if c.c.errRecv == {{$data.Pkg "io"}}EOF {
+		return nil
+	}
+	return c.c.errRecv
+}
+{{end}}{{if $method.InStream}}func (c *{{$clientCallImpl}}) SendStream() interface {
+	Send(item {{typeGo $data $method.InStream}}) error
+	Close() error
+} {
+	return {{$clientSendImpl}}{c}
+}
+
+type {{$clientSendImpl}} struct {
+	c *{{$clientCallImpl}}
+}
+
+func (c {{$clientSendImpl}}) Send(item {{typeGo $data $method.InStream}}) error {
+	return c.c.Send(item)
+}
+func (c {{$clientSendImpl}}) Close() error {
+	return c.c.CloseSend()
+}
+{{end}}func (c *{{$clientCallImpl}}) Finish() {{argParens (argNameTypes "o" "" "" "err error" $data $method.OutArgs)}} {
+{{clientFinishImpl "c.ClientCall" $method}}
+	return
+}
+{{end}}{{end}}
+
+// {{$iface.Name}}ServerMethods is the interface a server writer
+// implements for {{$iface.Name}}.
+{{docBreak $iface.Doc}}type {{$iface.Name}}ServerMethods interface { {{range $embed := $iface.Embeds}}
+	{{$embed.Doc}}{{embedGo $data $embed}}ServerMethods{{$embed.DocSuffix}}{{end}}{{range $method := $iface.Methods}}
+	{{$method.Doc}}{{$method.Name}}({{argNameTypes "" $ctxVar (serverCallVar $data $iface $method) "" $data $method.InArgs}}) {{argParens (argNameTypes "" "" "" "err error" $data $method.OutArgs)}}{{$method.DocSuffix}}{{end}}
+}
+
+// {{$iface.Name}}ServerStubMethods is the server interface containing
+// {{$iface.Name}} methods, as expected by rpc.Server.{{if $ifaceStreaming}}
+// The only difference between this interface and {{$iface.Name}}ServerMethods
+// is the streaming methods.{{else}}
+// There is no difference between this interface and {{$iface.Name}}ServerMethods
+// since there are no streaming methods.{{end}}
+type {{$iface.Name}}ServerStubMethods {{if $ifaceStreaming}}interface { {{range $embed := $iface.Embeds}}
+	{{$embed.Doc}}{{embedGo $data $embed}}ServerStubMethods{{$embed.DocSuffix}}{{end}}{{range $method := $iface.Methods}}
+	{{$method.Doc}}{{$method.Name}}({{argNameTypes "" $ctxVar (serverCallStubVar $data $iface $method) "" $data $method.InArgs}}) {{argParens (argNameTypes "" "" "" "err error" $data $method.OutArgs)}}{{$method.DocSuffix}}{{end}}
+}
+{{else}}{{$iface.Name}}ServerMethods
+{{end}}
+
+// {{$iface.Name}}ServerStub adds universal methods to {{$iface.Name}}ServerStubMethods.
+type {{$iface.Name}}ServerStub interface {
+	{{$iface.Name}}ServerStubMethods
+	// Describe the {{$iface.Name}} interfaces.
+	Describe__() []{{$rpc_}}InterfaceDesc
+}
+
+// {{$iface.Name}}Server returns a server stub for {{$iface.Name}}.
+// It converts an implementation of {{$iface.Name}}ServerMethods into
+// an object that may be used by rpc.Server.
+func {{$iface.Name}}Server(impl {{$iface.Name}}ServerMethods) {{$iface.Name}}ServerStub {
+	stub := impl{{$iface.Name}}ServerStub{
+		impl: impl,{{range $embed := $iface.Embeds}}
+		{{$embed.Name}}ServerStub: {{embedGo $data $embed}}Server(impl),{{end}}
+	}
+	// Initialize GlobState; always check the stub itself first, to handle the
+	// case where the user has the Glob method defined in their VDL source.
+	if gs := {{$rpc_}}NewGlobState(stub); gs != nil {
+		stub.gs = gs
+	} else if gs := {{$rpc_}}NewGlobState(impl); gs != nil {
+		stub.gs = gs
+	}
+	return stub
+}
+
+type impl{{$iface.Name}}ServerStub struct {
+	impl {{$iface.Name}}ServerMethods{{range $embed := $iface.Embeds}}
+	{{embedGo $data $embed}}ServerStub{{end}}
+	gs *{{$rpc_}}GlobState
+}
+
+{{range $method := $iface.Methods}}
+func (s impl{{$iface.Name}}ServerStub) {{$method.Name}}({{argNameTypes "i" $ctxVar (serverCallStubVar $data $iface $method) "" $data $method.InArgs}}) {{argParens (argTypes "" "error" $data $method.OutArgs)}} {
+{{serverStubImpl $data $iface $method}}
+}
+{{end}}
+
+func (s impl{{$iface.Name}}ServerStub) Globber() *{{$rpc_}}GlobState {
+	return s.gs
+}
+
+func (s impl{{$iface.Name}}ServerStub) Describe__() []{{$rpc_}}InterfaceDesc {
+	return []{{$rpc_}}InterfaceDesc{ {{$iface.Name}}Desc{{range $embed := $iface.TransitiveEmbeds}}, {{embedGo $data $embed}}Desc{{end}} }
+}
+
+// {{$iface.Name}}Desc describes the {{$iface.Name}} interface.
+var {{$iface.Name}}Desc {{$rpc_}}InterfaceDesc = desc{{$iface.Name}}
+
+// desc{{$iface.Name}} hides the desc to keep godoc clean.
+var desc{{$iface.Name}} = {{$rpc_}}InterfaceDesc{ {{if $iface.Name}}
+	Name: "{{$iface.Name}}",{{end}}{{if $iface.File.Package.Path}}
+	PkgPath: "{{$iface.File.Package.Path}}",{{end}}{{if $iface.Doc}}
+	Doc: {{quoteStripDoc $iface.Doc}},{{end}}{{if $iface.Embeds}}
+	Embeds: []{{$rpc_}}EmbedDesc{ {{range $embed := $iface.Embeds}}
+		{ "{{$embed.Name}}", "{{$embed.File.Package.Path}}", {{quoteStripDoc $embed.Doc}} },{{end}}
+	},{{end}}{{if $iface.Methods}}
+	Methods: []{{$rpc_}}MethodDesc{ {{range $method := $iface.Methods}}
+		{ {{if $method.Name}}
+			Name: "{{$method.Name}}",{{end}}{{if $method.Doc}}
+			Doc: {{quoteStripDoc $method.Doc}},{{end}}{{if $method.InArgs}}
+			InArgs: []{{$rpc_}}ArgDesc{ {{range $arg := $method.InArgs}}
+				{ "{{$arg.Name}}", {{quoteStripDoc $arg.Doc}} }, // {{typeGo $data $arg.Type}}{{end}}
+			},{{end}}{{if $method.OutArgs}}
+			OutArgs: []{{$rpc_}}ArgDesc{ {{range $arg := $method.OutArgs}}
+				{ "{{$arg.Name}}", {{quoteStripDoc $arg.Doc}} }, // {{typeGo $data $arg.Type}}{{end}}
+			},{{end}}{{if $method.Tags}}
+			Tags: []*{{$data.Pkg "v.io/v23/vdl"}}Value{ {{range $tag := $method.Tags}}{{tagValue $data $tag}} ,{{end}} },{{end}}
+		},{{end}}
+	},{{end}}
+}
+
+{{range $method := $iface.Methods}}
+{{if isStreamingMethod $method}}
+{{$serverStream := uniqueName $iface $method "ServerStream"}}
+{{$serverCall := uniqueName $iface $method "ServerCall"}}
+{{$serverCallStub := uniqueName $iface $method "ServerCallStub"}}
+{{$serverRecvImpl := uniqueNameImpl $iface $method "ServerCallRecv"}}
+{{$serverSendImpl := uniqueNameImpl $iface $method "ServerCallSend"}}
+
+// {{$serverStream}} is the server stream for {{$iface.Name}}.{{$method.Name}}.
+type {{$serverStream}} interface { {{if $method.InStream}}
+	// RecvStream returns the receiver side of the {{$iface.Name}}.{{$method.Name}} server stream.
+	RecvStream() interface {
+		// Advance stages an item so that it may be retrieved via Value.  Returns
+		// true iff there is an item to retrieve.  Advance must be called before
+		// Value is called.  May block if an item is not available.
+		Advance() bool
+		// Value returns the item that was staged by Advance.  May panic if Advance
+		// returned false or was not called.  Never blocks.
+		Value() {{typeGo $data $method.InStream}}
+		// Err returns any error encountered by Advance.  Never blocks.
+		Err() error
+	} {{end}}{{if $method.OutStream}}
+	// SendStream returns the send side of the {{$iface.Name}}.{{$method.Name}} server stream.
+	SendStream() interface {
+		// Send places the item onto the output stream.  Returns errors encountered
+		// while sending.  Blocks if there is no buffer space; will unblock when
+		// buffer space is available.
+		Send(item {{typeGo $data $method.OutStream}}) error
+	} {{end}}
+}
+
+// {{$serverCall}} represents the context passed to {{$iface.Name}}.{{$method.Name}}.
+type {{$serverCall}} interface {
+	{{$rpc_}}ServerCall
+	{{$serverStream}}
+}
+
+// {{$serverCallStub}} is a wrapper that converts rpc.StreamServerCall into
+// a typesafe stub that implements {{$serverCall}}.
+type {{$serverCallStub}} struct {
+	{{$rpc_}}StreamServerCall{{if $method.InStream}}
+	valRecv {{typeGo $data $method.InStream}}
+	errRecv error{{end}}
+}
+
+// Init initializes {{$serverCallStub}} from rpc.StreamServerCall.
+func (s *{{$serverCallStub}}) Init(call {{$rpc_}}StreamServerCall) {
+	s.StreamServerCall = call
+}
+
+{{if $method.InStream}}// RecvStream returns the receiver side of the {{$iface.Name}}.{{$method.Name}} server stream.
+func (s  *{{$serverCallStub}}) RecvStream() interface {
+	Advance() bool
+	Value() {{typeGo $data $method.InStream}}
+	Err() error
+} {
+	return {{$serverRecvImpl}}{s}
+}
+
+type {{$serverRecvImpl}} struct {
+	s *{{$serverCallStub}}
+}
+
+func (s {{$serverRecvImpl}}) Advance() bool {
+	{{reInitStreamValue $data $method.InStream "s.s.valRecv"}}s.s.errRecv = s.s.Recv(&s.s.valRecv)
+	return s.s.errRecv == nil
+}
+func (s {{$serverRecvImpl}}) Value() {{typeGo $data $method.InStream}} {
+	return s.s.valRecv
+}
+func (s {{$serverRecvImpl}}) Err() error {
+	if s.s.errRecv == {{$data.Pkg "io"}}EOF {
+		return nil
+	}
+	return s.s.errRecv
+}
+{{end}}{{if $method.OutStream}}// SendStream returns the send side of the {{$iface.Name}}.{{$method.Name}} server stream.
+func (s *{{$serverCallStub}}) SendStream() interface {
+	Send(item {{typeGo $data $method.OutStream}}) error
+} {
+	return {{$serverSendImpl}}{s}
+}
+
+type {{$serverSendImpl}} struct {
+	s *{{$serverCallStub}}
+}
+
+func (s {{$serverSendImpl}}) Send(item {{typeGo $data $method.OutStream}}) error {
+	return s.s.Send(item)
+}
+{{end}}{{end}}{{end}}
+
+{{end}}
+`
diff --git a/lib/vdl/codegen/golang/import.go b/lib/vdl/codegen/golang/import.go
new file mode 100644
index 0000000..78298a0
--- /dev/null
+++ b/lib/vdl/codegen/golang/import.go
@@ -0,0 +1,360 @@
+// 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 golang
+
+// TODO(toddw): Add tests for this logic.
+
+import (
+	"sort"
+	"strconv"
+
+	"v.io/v23/vdl"
+	"v.io/x/ref/lib/vdl/compile"
+)
+
+// goImport represents a single import in the generated Go file.
+//   Example A: import     "v.io/v23/abc"
+//   Example B: import foo "v.io/v23/abc"
+type goImport struct {
+	// Name of the import.
+	//   Example A: ""
+	//   Example B: "foo"
+	Name string
+	// Path of the import.
+	//   Example A: "v.io/v23/abc"
+	//   Example B: "v.io/v23/abc"
+	Path string
+	// Local identifier within the generated go file to reference the imported
+	// package.
+	//   Example A: "abc"
+	//   Example B: "foo"
+	Local string
+}
+
+// goImports holds all imports for a generated Go file, splitting into two
+// groups "system" and "user".  The splitting is just for slightly nicer output,
+// and to ensure we prefer system over user imports when dealing with package
+// name collisions.
+type goImports struct {
+	System, User []goImport
+}
+
+func newImports(file *compile.File, env *compile.Env) *goImports {
+	deps, user := computeDeps(file, env)
+	system := systemImports(deps, file)
+	seen := make(map[string]bool)
+	return &goImports{
+		System: system.Sort(seen),
+		User:   user.Sort(seen),
+	}
+}
+
+// importMap maps from package path to package name.  It's used to collect
+// package import information.
+type importMap map[string]string
+
+// AddPackage adds a regular dependency on pkg; some block of generated code
+// will reference the pkg.
+func (im importMap) AddPackage(pkg *compile.Package) {
+	im[pkg.GenPath] = pkg.Name
+}
+
+// AddForcedPackage adds a "forced" dependency on pkg.  This means that we need
+// to import pkg even if no other block of generated code references the pkg.
+func (im importMap) AddForcedPackage(pkg *compile.Package) {
+	if im[pkg.GenPath] == "" {
+		im[pkg.GenPath] = "_"
+	}
+}
+
+func (im importMap) DeletePackage(pkg *compile.Package) {
+	delete(im, pkg.GenPath)
+}
+
+func (im importMap) Sort(seen map[string]bool) []goImport {
+	var sortedPaths []string
+	for path := range im {
+		sortedPaths = append(sortedPaths, path)
+	}
+	sort.Strings(sortedPaths)
+	var ret []goImport
+	for _, path := range sortedPaths {
+		ret = append(ret, uniqueImport(im[path], path, seen))
+	}
+	return ret
+}
+
+// Each import must end up with a unique local name.  Here's some examples.
+//   uniqueImport("a", "v.io/a", {})           -> goImport{"", "v.io/a", "a"}
+//   uniqueImport("z", "v.io/a", {})           -> goImport{"", "v.io/a", "z"}
+//   uniqueImport("a", "v.io/a", {"a"})        -> goImport{"a_2", "v.io/a", "a_2"}
+//   uniqueImport("a", "v.io/a", {"a", "a_2"}) -> goImport{"a_3", "v.io/a", "a_3"}
+//   uniqueImport("_", "v.io/a", {})           -> goImport{"_", "v.io/a", ""}
+//   uniqueImport("_", "v.io/a", {"a"})        -> goImport{"_", "v.io/a", ""}
+//   uniqueImport("_", "v.io/a", {"a", "a_2"}) -> goImport{"_", "v.io/a", ""}
+func uniqueImport(pkgName, pkgPath string, seen map[string]bool) goImport {
+	if pkgName == "_" {
+		return goImport{"_", pkgPath, ""}
+	}
+	name := ""
+	iter := 1
+	for {
+		local := pkgName
+		if iter > 1 {
+			local += "_" + strconv.Itoa(iter)
+			name = local
+		}
+		if !seen[local] {
+			// Found a unique local name - return the import.
+			seen[local] = true
+			return goImport{name, pkgPath, local}
+		}
+		iter++
+	}
+}
+
+// LookupLocal returns the local identifier within the generated go file that
+// identifies the given pkgPath.
+func (x *goImports) LookupLocal(pkgPath string) string {
+	if local := lookupLocal(pkgPath, x.System); local != "" {
+		return local
+	}
+	return lookupLocal(pkgPath, x.User)
+}
+
+func lookupLocal(pkgPath string, imports []goImport) string {
+	ix := sort.Search(
+		len(imports),
+		func(i int) bool { return imports[i].Path >= pkgPath },
+	)
+	if ix < len(imports) && imports[ix].Path == pkgPath {
+		return imports[ix].Local
+	}
+	return ""
+}
+
+type deps struct {
+	any              bool
+	typeObject       bool
+	enumTypeDef      bool
+	streamingMethods bool
+	methodTags       bool
+}
+
+func computeDeps(file *compile.File, env *compile.Env) (deps, importMap) {
+	deps, user := &deps{}, make(importMap)
+	// TypeDef.Type is always defined in our package; add deps on the base type.
+	for _, def := range file.TypeDefs {
+		addTypeDeps(def.BaseType, env, deps, user)
+		if def.Type.Kind() == vdl.Enum {
+			deps.enumTypeDef = true
+		}
+	}
+	// Consts contribute their value types.
+	for _, def := range file.ConstDefs {
+		addValueTypeDeps(def.Value, env, deps, user)
+	}
+	// Interfaces contribute their arg types and tag values, as well as embedded
+	// interfaces.
+	for _, iface := range file.Interfaces {
+		for _, embed := range iface.TransitiveEmbeds() {
+			user.AddPackage(embed.File.Package)
+		}
+		for _, method := range iface.Methods {
+			for _, arg := range method.InArgs {
+				addTypeDeps(arg.Type, env, deps, user)
+			}
+			for _, arg := range method.OutArgs {
+				addTypeDeps(arg.Type, env, deps, user)
+			}
+			if stream := method.InStream; stream != nil {
+				addTypeDeps(stream, env, deps, user)
+				deps.streamingMethods = true
+			}
+			if stream := method.OutStream; stream != nil {
+				addTypeDeps(stream, env, deps, user)
+				deps.streamingMethods = true
+			}
+			for _, tag := range method.Tags {
+				addValueTypeDeps(tag, env, deps, user)
+				deps.methodTags = true
+			}
+		}
+	}
+	// Errors contribute their param types.
+	for _, def := range file.ErrorDefs {
+		for _, param := range def.Params {
+			addTypeDeps(param.Type, env, deps, user)
+		}
+	}
+	// Native types contribute their imports, for the auto-generated native
+	// conversion function type assertion.
+	for _, native := range file.Package.Config.Go.WireToNativeTypes {
+		for _, imp := range native.Imports {
+			user[imp.Path] = imp.Name
+		}
+	}
+	// Now remove self and built-in package dependencies.  Every package can use
+	// itself and the built-in package, so we don't need to record this.
+	user.DeletePackage(file.Package)
+	user.DeletePackage(compile.BuiltInPackage)
+	return *deps, user
+}
+
+// Add package deps iff t is a defined (named) type.
+func addTypeDepIfDefined(t *vdl.Type, env *compile.Env, deps *deps, user importMap) bool {
+	if t == vdl.AnyType {
+		deps.any = true
+	}
+	if t == vdl.TypeObjectType {
+		deps.typeObject = true
+	}
+	if def := env.FindTypeDef(t); def != nil {
+		pkg := def.File.Package
+		if native, ok := pkg.Config.Go.WireToNativeTypes[def.Name]; ok {
+			// There is a native type configured for this defined type.  Add the
+			// imports corresponding to the native type.
+			for _, imp := range native.Imports {
+				user[imp.Path] = imp.Name
+			}
+			// Also add a "forced" import on the regular VDL package, to ensure the
+			// wire type is registered, to establish the wire<->native mapping.
+			user.AddForcedPackage(pkg)
+		} else {
+			// There's no native type configured for this defined type.  Add the
+			// imports corresponding to the VDL package.
+			user.AddPackage(pkg)
+		}
+		return true
+	}
+	return false
+}
+
+// Add immediate package deps for t and subtypes of t.
+func addTypeDeps(t *vdl.Type, env *compile.Env, deps *deps, user importMap) {
+	if addTypeDepIfDefined(t, env, deps, user) {
+		// We don't track transitive dependencies, only immediate dependencies.
+		return
+	}
+	// Not all types have TypeDefs; e.g. unnamed lists have no corresponding
+	// TypeDef, so we need to traverse those recursively.
+	addSubTypeDeps(t, env, deps, user)
+}
+
+// Add immediate package deps for subtypes of t.
+func addSubTypeDeps(t *vdl.Type, env *compile.Env, deps *deps, user importMap) {
+	switch t.Kind() {
+	case vdl.Array, vdl.List, vdl.Optional:
+		addTypeDeps(t.Elem(), env, deps, user)
+	case vdl.Set:
+		addTypeDeps(t.Key(), env, deps, user)
+	case vdl.Map:
+		addTypeDeps(t.Key(), env, deps, user)
+		addTypeDeps(t.Elem(), env, deps, user)
+	case vdl.Struct, vdl.Union:
+		for ix := 0; ix < t.NumField(); ix++ {
+			addTypeDeps(t.Field(ix).Type, env, deps, user)
+		}
+	}
+}
+
+// Add immediate package deps for v.Type(), and subvalues.  We must traverse the
+// value to know which types are actually used; e.g. an empty struct doesn't
+// have a dependency on its field types.
+//
+// The purpose of this method is to identify the package and type dependencies
+// for const or tag values.
+func addValueTypeDeps(v *vdl.Value, env *compile.Env, deps *deps, user importMap) {
+	t := v.Type()
+	addTypeDepIfDefined(t, env, deps, user)
+	// Track transitive dependencies, by traversing subvalues recursively.
+	switch t.Kind() {
+	case vdl.Array, vdl.List:
+		for ix := 0; ix < v.Len(); ix++ {
+			addValueTypeDeps(v.Index(ix), env, deps, user)
+		}
+	case vdl.Set:
+		for _, key := range v.Keys() {
+			addValueTypeDeps(key, env, deps, user)
+		}
+	case vdl.Map:
+		for _, key := range v.Keys() {
+			addValueTypeDeps(key, env, deps, user)
+			addValueTypeDeps(v.MapIndex(key), env, deps, user)
+		}
+	case vdl.Struct:
+		if v.IsZero() {
+			// We print zero-valued structs as {}, so we stop the traversal here.
+			return
+		}
+		for ix := 0; ix < t.NumField(); ix++ {
+			addValueTypeDeps(v.StructField(ix), env, deps, user)
+		}
+	case vdl.Union:
+		_, field := v.UnionField()
+		addValueTypeDeps(field, env, deps, user)
+	case vdl.Any, vdl.Optional:
+		if elem := v.Elem(); elem != nil {
+			addValueTypeDeps(elem, env, deps, user)
+		}
+	case vdl.TypeObject:
+		// TypeObject has dependencies on everything its zero value depends on.
+		addValueTypeDeps(vdl.ZeroValue(v.TypeObject()), env, deps, user)
+	}
+}
+
+// systemImports returns the vdl system imports for the given file and deps.
+// You might think we could simply capture the imports during code generation,
+// and then dump out all imports afterwards.  Unfortunately that strategy
+// doesn't work, because of potential package name collisions in the imports.
+//
+// When generating code we need to know the local identifier used to reference a
+// given imported package.  But the local identifier changes if there are
+// collisions with other imported packages.  An example:
+//
+//   package pkg
+//
+//   import       "a/foo"
+//   import foo_2 "b/foo"
+//
+//   type X foo.T
+//   type Y foo_2.T
+//
+// Note that in order to generate code for X and Y, we need to know what local
+// identifier to use.  But we only know what local identifier to use after we've
+// collected all imports and resolved collisions.
+func systemImports(deps deps, file *compile.File) importMap {
+	system := make(importMap)
+	if deps.any || deps.typeObject || deps.methodTags || len(file.TypeDefs) > 0 {
+		// System import for vdl.Value, vdl.Type and vdl.Register.
+		system["v.io/v23/vdl"] = "vdl"
+	}
+	if deps.enumTypeDef {
+		system["fmt"] = "fmt"
+	}
+	if len(file.Interfaces) > 0 {
+		system["v.io/v23"] = "v23"
+		system["v.io/v23/context"] = "context"
+		system["v.io/v23/rpc"] = "rpc"
+	}
+	if deps.streamingMethods {
+		system["io"] = "io"
+	}
+	if len(file.ErrorDefs) > 0 {
+		system["v.io/v23/context"] = "context"
+		system["v.io/v23/i18n"] = "i18n"
+		// If the user has specified any errors, typically we need to import the
+		// "v.io/v23/verror" package.  However we allow vdl code-generation
+		// in the "v.io/v23/verror" package itself, to specify common
+		// errors.  Special-case this scenario to avoid self-cyclic package
+		// dependencies.
+		if file.Package.Path != "v.io/v23/verror" {
+			system["v.io/v23/verror"] = "verror"
+		}
+	}
+	// Now remove self package dependencies.
+	system.DeletePackage(file.Package)
+	return system
+}
diff --git a/lib/vdl/codegen/golang/type.go b/lib/vdl/codegen/golang/type.go
new file mode 100644
index 0000000..7c6d612
--- /dev/null
+++ b/lib/vdl/codegen/golang/type.go
@@ -0,0 +1,223 @@
+// 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 golang
+
+import (
+	"fmt"
+	"strconv"
+	"strings"
+
+	"v.io/v23/vdl"
+	"v.io/v23/vdlroot/vdltool"
+	"v.io/x/ref/lib/vdl/compile"
+)
+
+func localIdent(data goData, file *compile.File, ident string) string {
+	if testingMode {
+		return ident
+	}
+	return data.Pkg(file.Package.GenPath) + ident
+}
+
+func nativeIdent(data goData, native vdltool.GoType) string {
+	ident := native.Type
+	for _, imp := range native.Imports {
+		// Translate the packages specified in the native type into local package
+		// identifiers.  E.g. if the native type is "foo.Type" with import
+		// "path/foo", we need replace "foo." in the native type with the local
+		// package identifier for "path/foo".
+		pkg := data.Pkg(imp.Path)
+		ident = strings.Replace(ident, imp.Name+".", pkg, -1)
+	}
+	return ident
+}
+
+func packageIdent(file *compile.File, ident string) string {
+	if testingMode {
+		return ident
+	}
+	return file.Package.Name + "." + ident
+}
+
+func qualifiedIdent(file *compile.File, ident string) string {
+	if testingMode {
+		return ident
+	}
+	return file.Package.QualifiedName(ident)
+}
+
+// typeGo translates vdl.Type into a Go type.
+func typeGo(data goData, t *vdl.Type) string {
+	if testingMode {
+		if t.Name() != "" {
+			return t.Name()
+		}
+	}
+	// Terminate recursion at defined types, which include both user-defined types
+	// (enum, struct, union) and built-in types.
+	if def := data.Env.FindTypeDef(t); def != nil {
+		switch {
+		case t == vdl.AnyType:
+			return "*" + data.Pkg("v.io/v23/vdl") + "Value"
+		case t == vdl.TypeObjectType:
+			return "*" + data.Pkg("v.io/v23/vdl") + "Type"
+		case def.File == compile.BuiltInFile:
+			// Built-in primitives just use their name.
+			return def.Name
+		}
+		pkg := def.File.Package
+		if native, ok := pkg.Config.Go.WireToNativeTypes[def.Name]; ok {
+			// There is a Go native type configured for this defined type.
+			return nativeIdent(data, native)
+		}
+		return localIdent(data, def.File, def.Name)
+	}
+	// Otherwise recurse through the type.
+	switch t.Kind() {
+	case vdl.Optional:
+		return "*" + typeGo(data, t.Elem())
+	case vdl.Array:
+		return "[" + strconv.Itoa(t.Len()) + "]" + typeGo(data, t.Elem())
+	case vdl.List:
+		return "[]" + typeGo(data, t.Elem())
+	case vdl.Set:
+		return "map[" + typeGo(data, t.Key()) + "]struct{}"
+	case vdl.Map:
+		return "map[" + typeGo(data, t.Key()) + "]" + typeGo(data, t.Elem())
+	default:
+		panic(fmt.Errorf("vdl: typeGo unhandled type %v %v", t.Kind(), t))
+	}
+}
+
+// typeDefGo prints the type definition for a type.
+func typeDefGo(data goData, def *compile.TypeDef) string {
+	s := fmt.Sprintf("%stype %s ", def.Doc, def.Name)
+	switch t := def.Type; t.Kind() {
+	case vdl.Enum:
+		s += fmt.Sprintf("int%s\nconst (", def.DocSuffix)
+		for ix := 0; ix < t.NumEnumLabel(); ix++ {
+			s += fmt.Sprintf("\n\t%s%s%s", def.LabelDoc[ix], def.Name, t.EnumLabel(ix))
+			if ix == 0 {
+				s += fmt.Sprintf(" %s = iota", def.Name)
+			}
+			s += def.LabelDocSuffix[ix]
+		}
+		s += fmt.Sprintf("\n)"+
+			"\n\n// %[1]sAll holds all labels for %[1]s."+
+			"\nvar %[1]sAll = [...]%[1]s{%[2]s}"+
+			"\n\n// %[1]sFromString creates a %[1]s from a string label."+
+			"\nfunc %[1]sFromString(label string) (x %[1]s, err error) {"+
+			"\n\terr = x.Set(label)"+
+			"\n\treturn"+
+			"\n}"+
+			"\n\n// Set assigns label to x."+
+			"\nfunc (x *%[1]s) Set(label string) error {"+
+			"\n\tswitch label {",
+			def.Name,
+			commaEnumLabels(def.Name, t))
+		for ix := 0; ix < t.NumEnumLabel(); ix++ {
+			s += fmt.Sprintf("\n\tcase %[2]q, %[3]q:"+
+				"\n\t\t*x = %[1]s%[2]s"+
+				"\n\t\treturn nil", def.Name, t.EnumLabel(ix), strings.ToLower(t.EnumLabel(ix)))
+		}
+		s += fmt.Sprintf("\n\t}"+
+			"\n\t*x = -1"+
+			"\n\treturn "+data.Pkg("fmt")+"Errorf(\"unknown label %%q in %[2]s\", label)"+
+			"\n}"+
+			"\n\n// String returns the string label of x."+
+			"\nfunc (x %[1]s) String() string {"+
+			"\n\tswitch x {", def.Name, packageIdent(def.File, def.Name))
+		for ix := 0; ix < t.NumEnumLabel(); ix++ {
+			s += fmt.Sprintf("\n\tcase %[1]s%[2]s:"+
+				"\n\t\treturn %[2]q", def.Name, t.EnumLabel(ix))
+		}
+		s += fmt.Sprintf("\n\t}"+
+			"\n\treturn \"\""+
+			"\n}"+
+			"\n\nfunc (%[1]s) __VDLReflect(struct{"+
+			"\n\tName string `vdl:%[3]q`"+
+			"\n\tEnum struct{ %[2]s string }"+
+			"\n}) {"+
+			"\n}",
+			def.Name, commaEnumLabels("", t), qualifiedIdent(def.File, def.Name))
+		return s
+	case vdl.Struct:
+		s += "struct {"
+		for ix := 0; ix < t.NumField(); ix++ {
+			f := t.Field(ix)
+			s += "\n\t" + def.FieldDoc[ix] + f.Name + " "
+			s += typeGo(data, f.Type) + def.FieldDocSuffix[ix]
+		}
+		s += "\n}" + def.DocSuffix
+		s += fmt.Sprintf("\n"+
+			"\nfunc (%[1]s) __VDLReflect(struct{"+
+			"\n\tName string `vdl:%[2]q`"+
+			"\n}) {"+
+			"\n}",
+			def.Name, qualifiedIdent(def.File, def.Name))
+		return s
+	case vdl.Union:
+		s = fmt.Sprintf("type ("+
+			"\n\t// %[1]s represents any single field of the %[1]s union type."+
+			"\n\t%[2]s%[1]s interface {"+
+			"\n\t\t// Index returns the field index."+
+			"\n\t\tIndex() int"+
+			"\n\t\t// Interface returns the field value as an interface."+
+			"\n\t\tInterface() interface{}"+
+			"\n\t\t// Name returns the field name."+
+			"\n\t\tName() string"+
+			"\n\t\t// __VDLReflect describes the %[1]s union type."+
+			"\n\t\t__VDLReflect(__%[1]sReflect)"+
+			"\n\t}%[3]s", def.Name, docBreak(def.Doc), def.DocSuffix)
+		for ix := 0; ix < t.NumField(); ix++ {
+			f := t.Field(ix)
+			s += fmt.Sprintf("\n\t// %[1]s%[2]s represents field %[2]s of the %[1]s union type."+
+				"\n\t%[4]s%[1]s%[2]s struct{ Value %[3]s }%[5]s",
+				def.Name, f.Name, typeGo(data, f.Type),
+				docBreak(def.FieldDoc[ix]), def.FieldDocSuffix[ix])
+		}
+		s += fmt.Sprintf("\n\t// __%[1]sReflect describes the %[1]s union type."+
+			"\n\t__%[1]sReflect struct {"+
+			"\n\t\tName string `vdl:%[2]q`"+
+			"\n\t\tType %[1]s"+
+			"\n\t\tUnion struct {", def.Name, qualifiedIdent(def.File, def.Name))
+		for ix := 0; ix < t.NumField(); ix++ {
+			s += fmt.Sprintf("\n\t\t\t%[2]s %[1]s%[2]s", def.Name, t.Field(ix).Name)
+		}
+		s += fmt.Sprintf("\n\t\t}\n\t}\n)")
+		for ix := 0; ix < t.NumField(); ix++ {
+			s += fmt.Sprintf("\n\nfunc (x %[1]s%[2]s) Index() int { return %[3]d }"+
+				"\nfunc (x %[1]s%[2]s) Interface() interface{} { return x.Value }"+
+				"\nfunc (x %[1]s%[2]s) Name() string { return \"%[2]s\" }"+
+				"\nfunc (x %[1]s%[2]s) __VDLReflect(__%[1]sReflect) {}",
+				def.Name, t.Field(ix).Name, ix)
+		}
+		return s
+	default:
+		s += typeGo(data, def.BaseType) + def.DocSuffix
+		s += fmt.Sprintf("\n"+
+			"\nfunc (%[1]s) __VDLReflect(struct{"+
+			"\n\tName string `vdl:%[2]q`"+
+			"\n}) {"+
+			"\n}",
+			def.Name, qualifiedIdent(def.File, def.Name))
+		return s
+	}
+}
+
+func commaEnumLabels(prefix string, t *vdl.Type) (s string) {
+	for ix := 0; ix < t.NumEnumLabel(); ix++ {
+		if ix > 0 {
+			s += ", "
+		}
+		s += prefix
+		s += t.EnumLabel(ix)
+	}
+	return
+}
+
+func embedGo(data goData, embed *compile.Interface) string {
+	return localIdent(data, embed.File, embed.Name)
+}
diff --git a/lib/vdl/codegen/golang/type_test.go b/lib/vdl/codegen/golang/type_test.go
new file mode 100644
index 0000000..fe2e20e
--- /dev/null
+++ b/lib/vdl/codegen/golang/type_test.go
@@ -0,0 +1,202 @@
+// 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 golang
+
+import (
+	"testing"
+	"unicode"
+	"unicode/utf8"
+
+	"v.io/v23/vdl"
+	"v.io/x/ref/lib/vdl/compile"
+)
+
+func TestType(t *testing.T) {
+	testingMode = true
+	tests := []struct {
+		T    *vdl.Type
+		Want string
+	}{
+		{vdl.AnyType, `*vdl.Value`},
+		{vdl.TypeObjectType, `*vdl.Type`},
+		{vdl.BoolType, `bool`},
+		{vdl.StringType, `string`},
+		{vdl.ListType(vdl.ByteType), `[]byte`},
+		{vdl.ByteType, `byte`},
+		{vdl.Uint16Type, `uint16`},
+		{vdl.Uint32Type, `uint32`},
+		{vdl.Uint64Type, `uint64`},
+		{vdl.Int16Type, `int16`},
+		{vdl.Int32Type, `int32`},
+		{vdl.Int64Type, `int64`},
+		{vdl.Float32Type, `float32`},
+		{vdl.Float64Type, `float64`},
+		{vdl.Complex64Type, `complex64`},
+		{vdl.Complex128Type, `complex128`},
+		{tArray, `[3]string`},
+		{tList, `[]string`},
+		{tSet, `map[string]struct{}`},
+		{tMap, `map[string]int64`},
+	}
+	data := goData{Env: compile.NewEnv(-1)}
+	for _, test := range tests {
+		if got, want := typeGo(data, test.T), test.Want; got != want {
+			t.Errorf("%s\nGOT %s\nWANT %s", test.T, got, want)
+		}
+	}
+}
+
+func TestTypeDef(t *testing.T) {
+	testingMode = true
+	tests := []struct {
+		T    *vdl.Type
+		Want string
+	}{
+		{tEnum, `type TestEnum int
+const (
+	TestEnumA TestEnum = iota
+	TestEnumB
+	TestEnumC
+)
+
+// TestEnumAll holds all labels for TestEnum.
+var TestEnumAll = [...]TestEnum{TestEnumA, TestEnumB, TestEnumC}
+
+// TestEnumFromString creates a TestEnum from a string label.
+func TestEnumFromString(label string) (x TestEnum, err error) {
+	err = x.Set(label)
+	return
+}
+
+// Set assigns label to x.
+func (x *TestEnum) Set(label string) error {
+	switch label {
+	case "A", "a":
+		*x = TestEnumA
+		return nil
+	case "B", "b":
+		*x = TestEnumB
+		return nil
+	case "C", "c":
+		*x = TestEnumC
+		return nil
+	}
+	*x = -1
+	return fmt.Errorf("unknown label %q in TestEnum", label)
+}
+
+// String returns the string label of x.
+func (x TestEnum) String() string {
+	switch x {
+	case TestEnumA:
+		return "A"
+	case TestEnumB:
+		return "B"
+	case TestEnumC:
+		return "C"
+	}
+	return ""
+}
+
+func (TestEnum) __VDLReflect(struct{
+	Name string ` + "`vdl:\"TestEnum\"`" + `
+	Enum struct{ A, B, C string }
+}) {
+}`},
+		{tStruct, `type TestStruct struct {
+	A string
+	B int64
+}
+
+func (TestStruct) __VDLReflect(struct{
+	Name string ` + "`vdl:\"TestStruct\"`" + `
+}) {
+}`},
+		{tUnion, `type (
+	// TestUnion represents any single field of the TestUnion union type.
+	TestUnion interface {
+		// Index returns the field index.
+		Index() int
+		// Interface returns the field value as an interface.
+		Interface() interface{}
+		// Name returns the field name.
+		Name() string
+		// __VDLReflect describes the TestUnion union type.
+		__VDLReflect(__TestUnionReflect)
+	}
+	// TestUnionA represents field A of the TestUnion union type.
+	TestUnionA struct{ Value string }
+	// TestUnionB represents field B of the TestUnion union type.
+	TestUnionB struct{ Value int64 }
+	// __TestUnionReflect describes the TestUnion union type.
+	__TestUnionReflect struct {
+		Name string ` + "`vdl:\"TestUnion\"`" + `
+		Type TestUnion
+		Union struct {
+			A TestUnionA
+			B TestUnionB
+		}
+	}
+)
+
+func (x TestUnionA) Index() int { return 0 }
+func (x TestUnionA) Interface() interface{} { return x.Value }
+func (x TestUnionA) Name() string { return "A" }
+func (x TestUnionA) __VDLReflect(__TestUnionReflect) {}
+
+func (x TestUnionB) Index() int { return 1 }
+func (x TestUnionB) Interface() interface{} { return x.Value }
+func (x TestUnionB) Name() string { return "B" }
+func (x TestUnionB) __VDLReflect(__TestUnionReflect) {}`},
+	}
+	data := goData{Env: compile.NewEnv(-1)}
+	for _, test := range tests {
+		firstLetter, _ := utf8.DecodeRuneInString(test.T.Name())
+		def := &compile.TypeDef{
+			NamePos:  compile.NamePos{Name: test.T.Name()},
+			Type:     test.T,
+			Exported: unicode.IsUpper(firstLetter),
+		}
+		switch test.T.Kind() {
+		case vdl.Enum:
+			def.LabelDoc = make([]string, test.T.NumEnumLabel())
+			def.LabelDocSuffix = make([]string, test.T.NumEnumLabel())
+		case vdl.Struct, vdl.Union:
+			def.FieldDoc = make([]string, test.T.NumField())
+			def.FieldDocSuffix = make([]string, test.T.NumField())
+		}
+		if got, want := typeDefGo(data, def), test.Want; got != want {
+			t.Errorf("%s\n GOT %s\nWANT %s", test.T, got, want)
+		}
+	}
+}
+
+var (
+	tEnum   = vdl.NamedType("TestEnum", vdl.EnumType("A", "B", "C"))
+	tArray  = vdl.ArrayType(3, vdl.StringType)
+	tList   = vdl.ListType(vdl.StringType)
+	tSet    = vdl.SetType(vdl.StringType)
+	tMap    = vdl.MapType(vdl.StringType, vdl.Int64Type)
+	tStruct = vdl.NamedType("TestStruct", vdl.StructType(
+		vdl.Field{
+			Name: "A",
+			Type: vdl.StringType,
+		},
+		vdl.Field{
+			Name: "B",
+			Type: vdl.Int64Type,
+		},
+	))
+	tUnion = vdl.NamedType("TestUnion", vdl.UnionType(
+		vdl.Field{
+			Name: "A",
+			Type: vdl.StringType,
+		},
+		vdl.Field{
+			Name: "B",
+			Type: vdl.Int64Type,
+		},
+	))
+)
diff --git a/lib/vdl/codegen/import.go b/lib/vdl/codegen/import.go
new file mode 100644
index 0000000..591470e
--- /dev/null
+++ b/lib/vdl/codegen/import.go
@@ -0,0 +1,150 @@
+// 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 codegen implements utilities for VDL code generators.  Code
+// generators for specific languages live in sub-directories.
+package codegen
+
+import (
+	"path"
+	"sort"
+	"strconv"
+
+	"v.io/v23/vdl"
+	"v.io/x/lib/set"
+	"v.io/x/ref/lib/vdl/compile"
+)
+
+// TODO(toddw): Remove this file, after all code generators have been updated to
+// compute their own import dependencies.
+
+// Import represents a single package import.
+type Import struct {
+	Name string // Name of the import; may be empty.
+	Path string // Path of the imported package; e.g. "v.io/x/ref/lib/vdl/testdata/arith"
+
+	// Local name that refers to the imported package; either the non-empty import
+	// name, or the name of the imported package.
+	Local string
+}
+
+// Imports is a collection of package imports.
+// REQUIRED: The imports must be sorted by path.
+type Imports []Import
+
+// LookupLocal returns the local name that identifies the given pkgPath.
+func (x Imports) LookupLocal(pkgPath string) string {
+	ix := sort.Search(len(x), func(i int) bool { return x[i].Path >= pkgPath })
+	if ix < len(x) && x[ix].Path == pkgPath {
+		return x[ix].Local
+	}
+	return ""
+}
+
+// Each import must end up with a unique local name - when we see a collision we
+// simply add a "_N" suffix where N starts at 2 and increments.
+func uniqueImport(pkgName, pkgPath string, seen map[string]bool) Import {
+	name := ""
+	iter := 1
+	for {
+		local := pkgName
+		if iter > 1 {
+			local += "_" + strconv.Itoa(iter)
+			name = local
+		}
+		if !seen[local] {
+			// Found a unique local name - return the import.
+			seen[local] = true
+			return Import{name, pkgPath, local}
+		}
+		iter++
+	}
+}
+
+type pkgSorter []*compile.Package
+
+func (s pkgSorter) Len() int { return len(s) }
+
+func (s pkgSorter) Less(i, j int) bool { return s[i].Path < s[j].Path }
+
+func (s pkgSorter) Swap(i, j int) { s[j], s[i] = s[i], s[j] }
+
+// ImportsForFiles returns the imports required for the given files.
+func ImportsForFiles(files ...*compile.File) Imports {
+	seenPath := make(map[string]bool)
+	pkgs := pkgSorter{}
+
+	for _, f := range files {
+		for _, dep := range f.PackageDeps {
+			if seenPath[dep.Path] {
+				continue
+			}
+			seenPath[dep.Path] = true
+			pkgs = append(pkgs, dep)
+		}
+	}
+	sort.Sort(pkgs)
+
+	var ret Imports
+	seenName := make(map[string]bool)
+	for _, dep := range pkgs {
+		ret = append(ret, uniqueImport(dep.Name, dep.Path, seenName))
+	}
+	return ret
+}
+
+// ImportsForValue returns the imports required to represent the given value v,
+// from within the given pkgPath.  It requires that type names used in
+// v are of the form "PKGPATH.NAME".
+func ImportsForValue(v *vdl.Value, pkgPath string) Imports {
+	deps := pkgDeps{}
+	deps.MergeValue(v)
+	var ret Imports
+	seen := make(map[string]bool)
+	for _, p := range deps.SortedPkgPaths() {
+		if p != pkgPath {
+			ret = append(ret, uniqueImport(path.Base(p), p, seen))
+		}
+	}
+	return ret
+}
+
+// pkgDeps maintains a set of package path dependencies.
+type pkgDeps map[string]bool
+
+func (deps pkgDeps) insertIdent(ident string) {
+	if pkgPath, _ := vdl.SplitIdent(ident); pkgPath != "" {
+		deps[pkgPath] = true
+	}
+}
+
+// MergeValue merges the package paths required to represent v into deps.
+func (deps pkgDeps) MergeValue(v *vdl.Value) {
+	deps.insertIdent(v.Type().Name())
+	switch v.Kind() {
+	case vdl.Any, vdl.Union, vdl.Optional:
+		elem := v.Elem()
+		if elem != nil {
+			deps.MergeValue(elem)
+		}
+	case vdl.Array, vdl.List:
+		deps.insertIdent(v.Type().Elem().Name())
+	case vdl.Set:
+		deps.insertIdent(v.Type().Key().Name())
+	case vdl.Map:
+		deps.insertIdent(v.Type().Key().Name())
+		deps.insertIdent(v.Type().Elem().Name())
+	case vdl.Struct:
+		for fx := 0; fx < v.Type().NumField(); fx++ {
+			deps.insertIdent(v.Type().Field(fx).Type.Name())
+		}
+	}
+}
+
+// SortedPkgPaths deps as a sorted slice.
+func (deps pkgDeps) SortedPkgPaths() []string {
+	ret := set.StringBool.ToSlice(deps)
+	sort.Strings(ret)
+	return ret
+}
diff --git a/lib/vdl/codegen/java/const.go b/lib/vdl/codegen/java/const.go
new file mode 100644
index 0000000..f572908
--- /dev/null
+++ b/lib/vdl/codegen/java/const.go
@@ -0,0 +1,12 @@
+// 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 java
+
+// The data passed into every template must include a FileDoc field, which
+// contains the comment for each generated file; e.g. the boilerplate copyright
+// header.
+const header = `{{.FileDoc}}
+// This file was auto-generated by the vanadium vdl tool.
+`
diff --git a/lib/vdl/codegen/java/file_array.go b/lib/vdl/codegen/java/file_array.go
new file mode 100644
index 0000000..633e8a1
--- /dev/null
+++ b/lib/vdl/codegen/java/file_array.go
@@ -0,0 +1,114 @@
+// 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 java
+
+import (
+	"bytes"
+	"fmt"
+	"log"
+	"strings"
+
+	"v.io/x/ref/lib/vdl/compile"
+)
+
+const arrayTmpl = header + `
+// Source: {{.SourceFile}}
+
+package {{.Package}};
+
+{{ .Doc }}
+@io.v.v23.vdl.GeneratedFromVdl(name = "{{.VdlTypeName}}")
+@io.v.v23.vdl.ArrayLength({{.Length}})
+{{ .AccessModifier }} class {{.Name}} extends io.v.v23.vdl.VdlArray<{{.ElemType}}> {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Vdl type for {@link {{.Name}}}.
+     */
+    public static final io.v.v23.vdl.VdlType VDL_TYPE =
+            io.v.v23.vdl.Types.getVdlTypeFromReflect({{.Name}}.class);
+
+    /**
+     * Creates a new instance of {@link {{.Name}}} with the given underlying array.
+     *
+     * @param arr underlying array
+     */
+    public {{.Name}}({{.ElemType}}[] arr) {
+        super(VDL_TYPE, arr);
+    }
+
+    /**
+     * Creates a new zero-value instance of {@link {{.Name}}}.
+     */
+    public {{.Name}}() {
+        this({{.ZeroValue}});
+    }
+
+    {{ if .ElemIsPrimitive }}
+    /**
+     * Creates a new instance of {@link {{.Name}}} with the given underlying array.
+     *
+     * @param arr underlying array
+     */
+    public {{.Name}}({{ .ElemPrimitiveType }}[] arr) {
+        super(VDL_TYPE, convert(arr));
+    }
+
+    private static {{ .ElemType }}[] convert({{ .ElemPrimitiveType }}[] arr) {
+        {{ .ElemType }}[] ret = new {{ .ElemType }}[arr.length];
+        for (int i = 0; i < arr.length; ++i) {
+            ret[i] = arr[i];
+        }
+        return ret;
+    }
+    {{ end }}
+}
+`
+
+// genJavaArrayFile generates the Java class file for the provided named array type.
+func genJavaArrayFile(tdef *compile.TypeDef, env *compile.Env) JavaFileInfo {
+	name, access := javaTypeName(tdef, env)
+	elemType := javaType(tdef.Type.Elem(), true, env)
+	elems := strings.TrimSuffix(strings.Repeat(javaZeroValue(tdef.Type.Elem(), env)+", ", tdef.Type.Len()), ", ")
+	zeroValue := fmt.Sprintf("new %s[] {%s}", elemType, elems)
+	data := struct {
+		AccessModifier    string
+		Doc               string
+		ElemType          string
+		ElemIsPrimitive   bool
+		ElemPrimitiveType string
+		FileDoc           string
+		Length            int
+		Name              string
+		Package           string
+		SourceFile        string
+		VdlTypeName       string
+		VdlTypeString     string
+		ZeroValue         string
+	}{
+		AccessModifier:    access,
+		Doc:               javaDoc(tdef.Doc, tdef.DocSuffix),
+		ElemType:          elemType,
+		ElemIsPrimitive:   !isClass(tdef.Type.Elem(), env),
+		ElemPrimitiveType: javaType(tdef.Type.Elem(), false, env),
+		FileDoc:           tdef.File.Package.FileDoc,
+		Length:            tdef.Type.Len(),
+		Name:              name,
+		Package:           javaPath(javaGenPkgPath(tdef.File.Package.GenPath)),
+		SourceFile:        tdef.File.BaseName,
+		VdlTypeName:       tdef.Type.Name(),
+		VdlTypeString:     tdef.Type.String(),
+		ZeroValue:         zeroValue,
+	}
+	var buf bytes.Buffer
+	err := parseTmpl("array", arrayTmpl).Execute(&buf, data)
+	if err != nil {
+		log.Fatalf("vdl: couldn't execute array template: %v", err)
+	}
+	return JavaFileInfo{
+		Name: name + ".java",
+		Data: buf.Bytes(),
+	}
+}
diff --git a/lib/vdl/codegen/java/file_client_factory.go b/lib/vdl/codegen/java/file_client_factory.go
new file mode 100644
index 0000000..3de8a5f
--- /dev/null
+++ b/lib/vdl/codegen/java/file_client_factory.go
@@ -0,0 +1,77 @@
+// 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 java
+
+import (
+	"bytes"
+	"log"
+
+	"v.io/x/ref/lib/vdl/compile"
+	"v.io/x/ref/lib/vdl/vdlutil"
+)
+
+const clientFactoryTmpl = header + `
+// Source(s):  {{ .Sources }}
+package {{ .PackagePath }};
+
+/**
+ * Factory for {@link {{ .ServiceName }}Client}s.
+ */
+public final class {{ .ServiceName }}ClientFactory {
+    /**
+     * Creates a new {@link {{ .ServiceName }}Client}, binding it to the provided name.
+     *
+     * @param name name to bind to
+     */
+    public static {{ .ServiceName }}Client get{{ .ServiceName }}Client(java.lang.String name) {
+        return get{{ .ServiceName }}Client(name, null);
+    }
+
+    /**
+     * Creates a new {@link {{ .ServiceName }}Client}, binding it to the provided name and using the
+     * provided options.  Currently supported options are:
+     * <p><ul>
+     * <li>{@link io.v.v23.OptionDefs#CLIENT}, which specifies a {@link io.v.v23.rpc.Client} to use for all rpc calls.</li>
+     * </ul>
+     *
+     * @param name name to bind to
+     * @param opts creation options
+     */
+    public static {{ .ServiceName }}Client get{{ .ServiceName }}Client(java.lang.String name, io.v.v23.Options opts) {
+        io.v.v23.rpc.Client client = null;
+        if (opts != null && opts.get(io.v.v23.OptionDefs.CLIENT) != null) {
+            client = opts.get(io.v.v23.OptionDefs.CLIENT, io.v.v23.rpc.Client.class);
+        }
+        return new {{ .ServiceName }}ClientImpl(client, name);
+    }
+
+    private {{ .ServiceName }}ClientFactory() {}
+}
+`
+
+// genJavaClientFactoryFile generates the Java client factory file.
+func genJavaClientFactoryFile(iface *compile.Interface, env *compile.Env) JavaFileInfo {
+	javaServiceName := vdlutil.FirstRuneToUpper(iface.Name)
+	data := struct {
+		FileDoc     string
+		Sources     string
+		ServiceName string
+		PackagePath string
+	}{
+		FileDoc:     iface.File.Package.FileDoc,
+		Sources:     iface.File.BaseName,
+		ServiceName: javaServiceName,
+		PackagePath: javaPath(javaGenPkgPath(iface.File.Package.GenPath)),
+	}
+	var buf bytes.Buffer
+	err := parseTmpl("client factory", clientFactoryTmpl).Execute(&buf, data)
+	if err != nil {
+		log.Fatalf("vdl: couldn't execute client template: %v", err)
+	}
+	return JavaFileInfo{
+		Name: javaServiceName + "ClientFactory.java",
+		Data: buf.Bytes(),
+	}
+}
diff --git a/lib/vdl/codegen/java/file_client_impl.go b/lib/vdl/codegen/java/file_client_impl.go
new file mode 100644
index 0000000..be2d92c
--- /dev/null
+++ b/lib/vdl/codegen/java/file_client_impl.go
@@ -0,0 +1,276 @@
+// 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 java
+
+import (
+	"bytes"
+	"fmt"
+	"log"
+	"path"
+
+	"v.io/x/ref/lib/vdl/compile"
+	"v.io/x/ref/lib/vdl/vdlutil"
+)
+
+const clientImplTmpl = header + `
+// Source(s):  {{ .Source }}
+package {{ .PackagePath }};
+
+/**
+ * Implementation of the {@link {{ .ServiceName }}Client} interface.
+ */
+final class {{ .ServiceName }}ClientImpl implements {{ .FullServiceName }}Client {
+    private final io.v.v23.rpc.Client client;
+    private final java.lang.String vName;
+
+    {{/* Define fields to hold each of the embedded object impls*/}}
+    {{ range $embed := .Embeds }}
+    {{/* e.g. private final com.somepackage.ArithClient implArith; */}}
+    private final {{ $embed.FullName }}Client impl{{ $embed.Name }};
+    {{ end }}
+
+    /**
+     * Creates a new instance of {@link {{ .ServiceName }}ClientImpl}.
+     *
+     * @param client Vanadium client
+     * @param vName  remote server name
+     */
+    public {{ .ServiceName }}ClientImpl(io.v.v23.rpc.Client client, java.lang.String vName) {
+        this.client = client;
+        this.vName = vName;
+        {{/* Initialize the embeded impls */}}
+        {{ range $embed := .Embeds }}
+        {
+            io.v.v23.Options opts = new io.v.v23.Options();
+            opts.set(io.v.v23.OptionDefs.CLIENT, client);
+            this.impl{{ $embed.Name }} = {{ $embed.FullName }}ClientFactory.get{{ $embed.Name }}Client(vName, opts);
+        }
+        {{ end }}
+    }
+
+    private io.v.v23.rpc.Client getClient(io.v.v23.context.VContext context) {
+        return this.client != null ? client : io.v.v23.V.getClient(context);
+
+    }
+
+    // Methods from interface {{ .ServiceName }}Client.
+{{/* Iterate over methods defined directly in the body of this service */}}
+{{ range $method := .Methods }}
+    {{/* The optionless overload simply calls the overload with options */}}
+    @Override
+    public {{ $method.RetType }} {{ $method.Name }}(io.v.v23.context.VContext context{{ $method.DeclarationArgs }}) throws io.v.v23.verror.VException {
+        {{if $method.Returns }}return{{ end }} {{ $method.Name }}(context{{ $method.CallingArgsLeadingComma }}, null);
+    }
+    {{/* The main client impl method body */}}
+    @Override
+    public {{ $method.RetType }} {{ $method.Name }}(io.v.v23.context.VContext context{{ $method.DeclarationArgs }}, io.v.v23.Options vOpts) throws io.v.v23.verror.VException {
+        {{/* Start the vanadium call */}}
+        // Start the call.
+        java.lang.Object[] _args = new java.lang.Object[]{ {{ $method.CallingArgs }} };
+        java.lang.reflect.Type[] _argTypes = new java.lang.reflect.Type[]{ {{ $method.CallingArgTypes }} };
+        final io.v.v23.rpc.ClientCall _call = getClient(context).startCall(context, this.vName, "{{ $method.Name }}", _args, _argTypes, vOpts);
+
+        // Finish the call.
+        {{/* Now handle returning from the function. */}}
+        {{ if $method.NotStreaming }}
+
+        {{ if $method.IsVoid }}
+        java.lang.reflect.Type[] _resultTypes = new java.lang.reflect.Type[]{};
+        _call.finish(_resultTypes);
+        {{ else }} {{/* else $method.IsVoid */}}
+        java.lang.reflect.Type[] _resultTypes = new java.lang.reflect.Type[]{
+            {{ range $outArg := $method.OutArgs }}
+            new com.google.common.reflect.TypeToken<{{ $outArg.Type }}>() {}.getType(),
+            {{ end }}
+        };
+        java.lang.Object[] _results = _call.finish(_resultTypes);
+        {{ if $method.MultipleReturn }}
+        {{ $method.DeclaredObjectRetType }} _ret = new {{ $method.DeclaredObjectRetType }}();
+            {{ range $i, $outArg := $method.OutArgs }}
+        _ret.{{ $outArg.FieldName }} = ({{ $outArg.Type }})_results[{{ $i }}];
+            {{ end }} {{/* end range over outargs */}}
+        return _ret;
+        {{ else }} {{/* end if $method.MultipleReturn */}}
+        return ({{ $method.DeclaredObjectRetType }})_results[0];
+        {{ end }} {{/* end if $method.MultipleReturn */}}
+
+        {{ end }} {{/* end if $method.IsVoid */}}
+
+        {{else }} {{/* else $method.NotStreaming */}}
+        return new io.v.v23.vdl.TypedClientStream<{{ $method.SendType }}, {{ $method.RecvType }}, {{ $method.DeclaredObjectRetType }}>() {
+            @Override
+            public void send({{ $method.SendType }} item) throws io.v.v23.verror.VException {
+                java.lang.reflect.Type type = new com.google.common.reflect.TypeToken<{{ $method.SendType }}>() {}.getType();
+                _call.send(item, type);
+            }
+            @Override
+            public {{ $method.RecvType }} recv() throws java.io.EOFException, io.v.v23.verror.VException {
+                java.lang.reflect.Type type = new com.google.common.reflect.TypeToken<{{ $method.RecvType }}>() {}.getType();
+                java.lang.Object result = _call.recv(type);
+                try {
+                    return ({{ $method.RecvType }})result;
+                } catch (java.lang.ClassCastException e) {
+                    throw new io.v.v23.verror.VException("Unexpected result type: " + result.getClass().getCanonicalName());
+                }
+            }
+            @Override
+            public {{ $method.DeclaredObjectRetType }} finish() throws io.v.v23.verror.VException {
+                {{ if $method.IsVoid }}
+                java.lang.reflect.Type[] resultTypes = new java.lang.reflect.Type[]{};
+                _call.finish(resultTypes);
+                return null;
+                {{ else }} {{/* else $method.IsVoid */}}
+                java.lang.reflect.Type[] resultTypes = new java.lang.reflect.Type[]{
+                    new com.google.common.reflect.TypeToken<{{ $method.DeclaredObjectRetType }}>() {}.getType()
+                };
+                return ({{ $method.DeclaredObjectRetType }})_call.finish(resultTypes)[0];
+                {{ end }} {{/* end if $method.IsVoid */}}
+            }
+        };
+        {{ end }}{{/* end if $method.NotStreaming */}}
+    }
+{{ end }}{{/* end range over methods */}}
+
+{{/* Iterate over methods from embeded services and generate code to delegate the work */}}
+{{ range $eMethod := .EmbedMethods }}
+    @Override
+    public {{ $eMethod.RetType }} {{ $eMethod.Name }}(io.v.v23.context.VContext context{{ $eMethod.DeclarationArgs }}) throws io.v.v23.verror.VException {
+        {{/* e.g. return this.implArith.cosine(context, [args]) */}}
+        {{ if $eMethod.Returns }}return{{ end }} this.impl{{ $eMethod.IfaceName }}.{{ $eMethod.Name }}(context{{ $eMethod.CallingArgsLeadingComma }});
+    }
+    @Override
+    public {{ $eMethod.RetType }} {{ $eMethod.Name }}(io.v.v23.context.VContext context{{ $eMethod.DeclarationArgs }}, io.v.v23.Options vOpts) throws io.v.v23.verror.VException {
+        {{/* e.g. return this.implArith.cosine(context, [args], options) */}}
+        {{ if $eMethod.Returns }}return{{ end }}  this.impl{{ $eMethod.IfaceName }}.{{ $eMethod.Name }}(context{{ $eMethod.CallingArgsLeadingComma }}, vOpts);
+    }
+{{ end }}
+
+}
+`
+
+type clientImplMethodOutArg struct {
+	FieldName string
+	Type      string
+}
+
+type clientImplMethod struct {
+	CallingArgs             string
+	CallingArgTypes         string
+	CallingArgsLeadingComma string
+	DeclarationArgs         string
+	DeclaredObjectRetType   string
+	IsVoid                  bool
+	MultipleReturn          bool
+	Name                    string
+	NotStreaming            bool
+	OutArgs                 []clientImplMethodOutArg
+	RecvType                string
+	RetType                 string
+	Returns                 bool
+	SendType                string
+	ServiceName             string
+}
+
+type clientImplEmbedMethod struct {
+	CallingArgsLeadingComma string
+	DeclarationArgs         string
+	IfaceName               string
+	Name                    string
+	RetType                 string
+	Returns                 bool
+}
+
+type clientImplEmbed struct {
+	Name     string
+	FullName string
+}
+
+func processClientImplMethod(iface *compile.Interface, method *compile.Method, env *compile.Env) clientImplMethod {
+	outArgs := make([]clientImplMethodOutArg, len(method.OutArgs))
+	for i := 0; i < len(method.OutArgs); i++ {
+		if method.OutArgs[i].Name != "" {
+			outArgs[i].FieldName = vdlutil.FirstRuneToLower(method.OutArgs[i].Name)
+		} else {
+			outArgs[i].FieldName = fmt.Sprintf("ret%d", i+1)
+		}
+		outArgs[i].Type = javaType(method.OutArgs[i].Type, true, env)
+	}
+	return clientImplMethod{
+		CallingArgs:             javaCallingArgStr(method.InArgs, false),
+		CallingArgTypes:         javaCallingArgTypeStr(method.InArgs, env),
+		CallingArgsLeadingComma: javaCallingArgStr(method.InArgs, true),
+		DeclarationArgs:         javaDeclarationArgStr(method.InArgs, env, true),
+		DeclaredObjectRetType:   clientInterfaceNonStreamingOutArg(iface, method, true, env),
+		IsVoid:                  len(method.OutArgs) < 1,
+		MultipleReturn:          len(method.OutArgs) > 1,
+		Name:                    vdlutil.FirstRuneToLower(method.Name),
+		NotStreaming:            !isStreamingMethod(method),
+		OutArgs:                 outArgs,
+		RecvType:                javaType(method.OutStream, true, env),
+		RetType:                 clientInterfaceOutArg(iface, method, env),
+		Returns:                 len(method.OutArgs) >= 1 || isStreamingMethod(method),
+		SendType:                javaType(method.InStream, true, env),
+		ServiceName:             vdlutil.FirstRuneToUpper(iface.Name),
+	}
+}
+
+func processClientImplEmbedMethod(iface *compile.Interface, embedMethod *compile.Method, env *compile.Env) clientImplEmbedMethod {
+	return clientImplEmbedMethod{
+		CallingArgsLeadingComma: javaCallingArgStr(embedMethod.InArgs, true),
+		DeclarationArgs:         javaDeclarationArgStr(embedMethod.InArgs, env, true),
+		IfaceName:               vdlutil.FirstRuneToUpper(iface.Name),
+		Name:                    vdlutil.FirstRuneToLower(embedMethod.Name),
+		RetType:                 clientInterfaceOutArg(iface, embedMethod, env),
+		Returns:                 len(embedMethod.OutArgs) >= 1 || isStreamingMethod(embedMethod),
+	}
+}
+
+// genJavaClientImplFile generates a client impl for the specified interface.
+func genJavaClientImplFile(iface *compile.Interface, env *compile.Env) JavaFileInfo {
+	embeds := []clientImplEmbed{}
+	for _, embed := range allEmbeddedIfaces(iface) {
+		embeds = append(embeds, clientImplEmbed{
+			Name:     vdlutil.FirstRuneToUpper(embed.Name),
+			FullName: javaPath(javaGenPkgPath(path.Join(embed.File.Package.GenPath, vdlutil.FirstRuneToUpper(embed.Name)))),
+		})
+	}
+	embedMethods := []clientImplEmbedMethod{}
+	for _, embedMao := range dedupedEmbeddedMethodAndOrigins(iface) {
+		embedMethods = append(embedMethods, processClientImplEmbedMethod(embedMao.Origin, embedMao.Method, env))
+	}
+	methods := make([]clientImplMethod, len(iface.Methods))
+	for i, method := range iface.Methods {
+		methods[i] = processClientImplMethod(iface, method, env)
+	}
+	javaServiceName := vdlutil.FirstRuneToUpper(iface.Name)
+	data := struct {
+		FileDoc         string
+		EmbedMethods    []clientImplEmbedMethod
+		Embeds          []clientImplEmbed
+		FullServiceName string
+		Methods         []clientImplMethod
+		PackagePath     string
+		ServiceName     string
+		Source          string
+	}{
+		FileDoc:         iface.File.Package.FileDoc,
+		EmbedMethods:    embedMethods,
+		Embeds:          embeds,
+		FullServiceName: javaPath(interfaceFullyQualifiedName(iface)),
+		Methods:         methods,
+		PackagePath:     javaPath(javaGenPkgPath(iface.File.Package.GenPath)),
+		ServiceName:     javaServiceName,
+		Source:          iface.File.BaseName,
+	}
+	var buf bytes.Buffer
+	err := parseTmpl("client impl", clientImplTmpl).Execute(&buf, data)
+	if err != nil {
+		log.Fatalf("vdl: couldn't execute client impl template: %v", err)
+	}
+	return JavaFileInfo{
+		Name: javaServiceName + "ClientImpl.java",
+		Data: buf.Bytes(),
+	}
+}
diff --git a/lib/vdl/codegen/java/file_client_interface.go b/lib/vdl/codegen/java/file_client_interface.go
new file mode 100644
index 0000000..f4f8c32
--- /dev/null
+++ b/lib/vdl/codegen/java/file_client_interface.go
@@ -0,0 +1,134 @@
+// 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 java
+
+import (
+	"bytes"
+	"fmt"
+	"log"
+	"path"
+
+	"v.io/x/ref/lib/vdl/compile"
+	"v.io/x/ref/lib/vdl/vdlutil"
+)
+
+const clientInterfaceTmpl = header + `
+// Source: {{ .Source }}
+package {{ .PackagePath }};
+
+{{ .ServiceDoc }}
+public interface {{ .ServiceName }}Client {{ .Extends }} {
+{{ range $method := .Methods }}
+    {{/* If this method has multiple return arguments, generate the class. */}}
+    {{ if $method.IsMultipleRet }}
+    /**
+     * Multi-return value for method {@link #{{$method.Name}}}.
+     */
+    @io.v.v23.vdl.MultiReturn
+    public static class {{ $method.UppercaseMethodName }}Out {
+        {{ range $retArg := $method.RetArgs }}
+        public {{ $retArg.Type }} {{ $retArg.Name }};
+        {{ end }}
+    }
+    {{ end }}
+
+    {{/* Generate the method signature. */}}
+    {{ $method.Doc }}
+    {{ $method.RetType }} {{ $method.Name }}(io.v.v23.context.VContext context{{ $method.Args }}) throws io.v.v23.verror.VException;
+    {{ $method.RetType }} {{ $method.Name }}(io.v.v23.context.VContext context{{ $method.Args }}, io.v.v23.Options vOpts) throws io.v.v23.verror.VException;
+{{ end }}
+}
+`
+
+type clientInterfaceArg struct {
+	Type string
+	Name string
+}
+
+type clientInterfaceMethod struct {
+	Args                string
+	Doc                 string
+	Name                string
+	IsMultipleRet       bool
+	RetArgs             []clientInterfaceArg
+	RetType             string
+	UppercaseMethodName string
+}
+
+func clientInterfaceNonStreamingOutArg(iface *compile.Interface, method *compile.Method, useClass bool, env *compile.Env) string {
+	switch len(method.OutArgs) {
+	case 0:
+		// "void" or "Void"
+		return javaType(nil, useClass, env)
+	case 1:
+		return javaType(method.OutArgs[0].Type, useClass, env)
+	default:
+		return javaPath(path.Join(interfaceFullyQualifiedName(iface)+"Client", method.Name+"Out"))
+	}
+}
+
+func clientInterfaceOutArg(iface *compile.Interface, method *compile.Method, env *compile.Env) string {
+	if isStreamingMethod(method) {
+		return fmt.Sprintf("io.v.v23.vdl.TypedClientStream<%s, %s, %s>", javaType(method.InStream, true, env), javaType(method.OutStream, true, env), clientInterfaceNonStreamingOutArg(iface, method, true, env))
+	}
+	return clientInterfaceNonStreamingOutArg(iface, method, false, env)
+}
+
+func processClientInterfaceMethod(iface *compile.Interface, method *compile.Method, env *compile.Env) clientInterfaceMethod {
+	retArgs := make([]clientInterfaceArg, len(method.OutArgs))
+	for i := 0; i < len(method.OutArgs); i++ {
+		if method.OutArgs[i].Name != "" {
+			retArgs[i].Name = vdlutil.FirstRuneToLower(method.OutArgs[i].Name)
+		} else {
+			retArgs[i].Name = fmt.Sprintf("ret%d", i+1)
+		}
+		retArgs[i].Type = javaType(method.OutArgs[i].Type, false, env)
+	}
+	return clientInterfaceMethod{
+		Args:                javaDeclarationArgStr(method.InArgs, env, true),
+		Doc:                 javaDoc(method.Doc, method.DocSuffix),
+		Name:                vdlutil.FirstRuneToLower(method.Name),
+		IsMultipleRet:       len(retArgs) > 1,
+		RetArgs:             retArgs,
+		RetType:             clientInterfaceOutArg(iface, method, env),
+		UppercaseMethodName: method.Name,
+	}
+}
+
+// genJavaClientInterfaceFile generates the Java interface file for the provided
+// interface.
+func genJavaClientInterfaceFile(iface *compile.Interface, env *compile.Env) JavaFileInfo {
+	javaServiceName := vdlutil.FirstRuneToUpper(iface.Name)
+	methods := make([]clientInterfaceMethod, len(iface.Methods))
+	for i, method := range iface.Methods {
+		methods[i] = processClientInterfaceMethod(iface, method, env)
+	}
+	data := struct {
+		FileDoc     string
+		Extends     string
+		Methods     []clientInterfaceMethod
+		PackagePath string
+		ServiceDoc  string
+		ServiceName string
+		Source      string
+	}{
+		FileDoc:     iface.File.Package.FileDoc,
+		Extends:     javaClientExtendsStr(iface.Embeds),
+		Methods:     methods,
+		PackagePath: javaPath(javaGenPkgPath(iface.File.Package.GenPath)),
+		ServiceDoc:  javaDoc(iface.Doc, iface.DocSuffix),
+		ServiceName: javaServiceName,
+		Source:      iface.File.BaseName,
+	}
+	var buf bytes.Buffer
+	err := parseTmpl("client interface", clientInterfaceTmpl).Execute(&buf, data)
+	if err != nil {
+		log.Fatalf("vdl: couldn't execute struct template: %v", err)
+	}
+	return JavaFileInfo{
+		Name: javaServiceName + "Client.java",
+		Data: buf.Bytes(),
+	}
+}
diff --git a/lib/vdl/codegen/java/file_complex.go b/lib/vdl/codegen/java/file_complex.go
new file mode 100644
index 0000000..092bfe1
--- /dev/null
+++ b/lib/vdl/codegen/java/file_complex.go
@@ -0,0 +1,104 @@
+// 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 java
+
+import (
+	"bytes"
+	"fmt"
+	"log"
+
+	"v.io/v23/vdl"
+	"v.io/x/ref/lib/vdl/compile"
+)
+
+const complexTmpl = header + `
+// Source: {{.Source}}
+package {{.PackagePath}};
+
+{{ .Doc }}
+@io.v.v23.vdl.GeneratedFromVdl(name = "{{.VdlTypeName}}")
+{{ .AccessModifier }} class {{.Name}} extends {{.VdlComplex}} {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Vdl type for {@link {{.Name}}}.
+     */
+    public static final io.v.v23.vdl.VdlType VDL_TYPE =
+            io.v.v23.vdl.Types.getVdlTypeFromReflect({{.Name}}.class);
+
+    /**
+     * Creates a new instance of {@link {{.Name}}} with the given real and imaginary parts.
+     *
+     * @param real real part
+     * @param imag imaginary part
+     */
+    public {{.Name}}({{.ValueType}} real, {{.ValueType}} imag) {
+        super(VDL_TYPE, real, imag);
+    }
+
+    /**
+     * Creates a new instance of {@link {{.Name}}} with the given real part and a zero imaginary
+     * part.
+     *
+     * @param real real part
+     */
+    public {{.Name}}({{.ValueType}} real) {
+        this(real, 0);
+    }
+
+    /**
+     * Creates a new zero-value instance of {@link {{.Name}}}.
+     */
+    public {{.Name}}() {
+        this(0, 0);
+    }
+}
+`
+
+// genJavaComplexFile generates the Java class file for the provided user-defined VDL complex type.
+func genJavaComplexFile(tdef *compile.TypeDef, env *compile.Env) JavaFileInfo {
+	var ValueType string
+	switch kind := tdef.Type.Kind(); kind {
+	case vdl.Complex64:
+		ValueType = "float"
+	case vdl.Complex128:
+		ValueType = "double"
+	default:
+		panic(fmt.Errorf("val: unhandled kind: %v", kind))
+	}
+	name, access := javaTypeName(tdef, env)
+	data := struct {
+		AccessModifier string
+		Doc            string
+		FileDoc        string
+		Name           string
+		PackagePath    string
+		Source         string
+		ValueType      string
+		VdlComplex     string
+		VdlTypeName    string
+		VdlTypeString  string
+	}{
+		AccessModifier: access,
+		Doc:            javaDoc(tdef.Doc, tdef.DocSuffix),
+		FileDoc:        tdef.File.Package.FileDoc,
+		Name:           name,
+		PackagePath:    javaPath(javaGenPkgPath(tdef.File.Package.GenPath)),
+		Source:         tdef.File.BaseName,
+		ValueType:      ValueType,
+		VdlComplex:     javaVdlPrimitiveType(tdef.Type.Kind()),
+		VdlTypeName:    tdef.Type.Name(),
+		VdlTypeString:  tdef.Type.String(),
+	}
+	var buf bytes.Buffer
+	err := parseTmpl("complex", complexTmpl).Execute(&buf, data)
+	if err != nil {
+		log.Fatalf("vdl: couldn't execute VDL complex template: %v", err)
+	}
+	return JavaFileInfo{
+		Name: name + ".java",
+		Data: buf.Bytes(),
+	}
+}
diff --git a/lib/vdl/codegen/java/file_constants.go b/lib/vdl/codegen/java/file_constants.go
new file mode 100644
index 0000000..9e892a6
--- /dev/null
+++ b/lib/vdl/codegen/java/file_constants.go
@@ -0,0 +1,103 @@
+// 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 java
+
+import (
+	"bytes"
+	"log"
+
+	"v.io/x/ref/lib/vdl/compile"
+	"v.io/x/ref/lib/vdl/vdlutil"
+)
+
+const constTmpl = header + `
+// Source(s): {{ .Source }}
+package {{ .PackagePath }};
+
+/**
+ * Constants defined in all VDL files in this package.
+ */
+public final class {{ .ClassName }} {
+    {{ range $file := .Files }}
+
+    /* The following constants originate in file: {{ $file.Name }} */
+    {{ range $const := $file.Consts }}
+    {{ $const.Doc }}
+    {{ $const.AccessModifier }} static final {{ $const.Type }} {{ $const.Name }} = {{ $const.Value }};
+    {{ end }} {{/* end range $file.Consts */}}
+    {{ end }} {{/* range .Files */}}
+
+    private {{ .ClassName }}() {}
+}
+`
+
+type constConst struct {
+	AccessModifier string
+	Doc            string
+	Type           string
+	Name           string
+	Value          string
+}
+
+type constFile struct {
+	Name   string
+	Consts []constConst
+}
+
+func shouldGenerateConstFile(pkg *compile.Package) bool {
+	for _, file := range pkg.Files {
+		if len(file.ConstDefs) > 0 {
+			return true
+		}
+	}
+	return false
+}
+
+// genJavaConstFile generates the (single) Java file that contains constant
+// definitions from all the VDL files.
+func genJavaConstFile(pkg *compile.Package, env *compile.Env) *JavaFileInfo {
+	if !shouldGenerateConstFile(pkg) {
+		return nil
+	}
+
+	className := "Constants"
+
+	files := make([]constFile, len(pkg.Files))
+	for i, file := range pkg.Files {
+		consts := make([]constConst, len(file.ConstDefs))
+		for j, cnst := range file.ConstDefs {
+			consts[j].AccessModifier = accessModifierForName(cnst.Name)
+			consts[j].Doc = javaDoc(cnst.Doc, cnst.DocSuffix)
+			consts[j].Type = javaType(cnst.Value.Type(), false, env)
+			consts[j].Name = vdlutil.ToConstCase(cnst.Name)
+			consts[j].Value = javaConstVal(cnst.Value, env)
+		}
+		files[i].Name = file.BaseName
+		files[i].Consts = consts
+	}
+
+	data := struct {
+		ClassName   string
+		FileDoc     string
+		Files       []constFile
+		PackagePath string
+		Source      string
+	}{
+		ClassName:   className,
+		FileDoc:     pkg.FileDoc,
+		Files:       files,
+		PackagePath: javaPath(javaGenPkgPath(pkg.GenPath)),
+		Source:      javaFileNames(pkg.Files),
+	}
+	var buf bytes.Buffer
+	err := parseTmpl("const", constTmpl).Execute(&buf, data)
+	if err != nil {
+		log.Fatalf("vdl: couldn't execute const template: %v", err)
+	}
+	return &JavaFileInfo{
+		Name: className + ".java",
+		Data: buf.Bytes(),
+	}
+}
diff --git a/lib/vdl/codegen/java/file_enum.go b/lib/vdl/codegen/java/file_enum.go
new file mode 100644
index 0000000..9b80380
--- /dev/null
+++ b/lib/vdl/codegen/java/file_enum.go
@@ -0,0 +1,102 @@
+// 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 java
+
+import (
+	"bytes"
+	"log"
+
+	"v.io/x/ref/lib/vdl/compile"
+)
+
+const enumTmpl = header + `
+// Source: {{.Source}}
+package {{.PackagePath}};
+
+{{ .Doc }}
+@io.v.v23.vdl.GeneratedFromVdl(name = "{{.VdlTypeName}}")
+{{ .AccessModifier }} class {{.Name}} extends io.v.v23.vdl.VdlEnum {
+    {{ range $index, $label := .EnumLabels }}
+        @io.v.v23.vdl.GeneratedFromVdl(name = "{{$label.Name}}", index = {{$index}})
+        {{ $label.Doc }}
+        public static final {{$.Name}} {{$label.Name}};
+    {{ end }}
+
+    /**
+     * Vdl type for {@link {{.Name}}}.
+     */
+    public static final io.v.v23.vdl.VdlType VDL_TYPE =
+            io.v.v23.vdl.Types.getVdlTypeFromReflect({{.Name}}.class);
+
+    static {
+        {{ range $label := .EnumLabels }}
+            {{$label.Name}} = new {{$.Name}}("{{$label.Name}}");
+        {{ end }}
+    }
+
+    private {{.Name}}(String name) {
+        super(VDL_TYPE, name);
+    }
+
+    /**
+     * Returns the enum with the given name.
+     */
+    public static {{.Name}} valueOf(String name) {
+        {{ range $label := .EnumLabels }}
+            if ("{{$label.Name}}".equals(name)) {
+                return {{$label.Name}};
+            }
+        {{ end }}
+        throw new java.lang.IllegalArgumentException();
+    }
+}
+`
+
+type enumLabel struct {
+	Doc  string
+	Name string
+}
+
+// genJavaEnumFile generates the Java class file for the provided user-defined enum type.
+func genJavaEnumFile(tdef *compile.TypeDef, env *compile.Env) JavaFileInfo {
+	labels := make([]enumLabel, tdef.Type.NumEnumLabel())
+	for i := 0; i < tdef.Type.NumEnumLabel(); i++ {
+		labels[i] = enumLabel{
+			Doc:  javaDoc(tdef.LabelDoc[i], tdef.LabelDocSuffix[i]),
+			Name: tdef.Type.EnumLabel(i),
+		}
+	}
+	name, access := javaTypeName(tdef, env)
+	data := struct {
+		AccessModifier string
+		Doc            string
+		EnumLabels     []enumLabel
+		FileDoc        string
+		Name           string
+		PackagePath    string
+		Source         string
+		VdlTypeName    string
+		VdlTypeString  string
+	}{
+		AccessModifier: access,
+		Doc:            javaDoc(tdef.Doc, tdef.DocSuffix),
+		EnumLabels:     labels,
+		FileDoc:        tdef.File.Package.FileDoc,
+		Name:           name,
+		PackagePath:    javaPath(javaGenPkgPath(tdef.File.Package.GenPath)),
+		Source:         tdef.File.BaseName,
+		VdlTypeName:    tdef.Type.Name(),
+		VdlTypeString:  tdef.Type.String(),
+	}
+	var buf bytes.Buffer
+	err := parseTmpl("enum", enumTmpl).Execute(&buf, data)
+	if err != nil {
+		log.Fatalf("vdl: couldn't execute enum template: %v", err)
+	}
+	return JavaFileInfo{
+		Name: name + ".java",
+		Data: buf.Bytes(),
+	}
+}
diff --git a/lib/vdl/codegen/java/file_errors.go b/lib/vdl/codegen/java/file_errors.go
new file mode 100644
index 0000000..a9edffe
--- /dev/null
+++ b/lib/vdl/codegen/java/file_errors.go
@@ -0,0 +1,152 @@
+// 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 java
+
+import (
+	"bytes"
+	"log"
+
+	"v.io/x/ref/lib/vdl/compile"
+	"v.io/x/ref/lib/vdl/vdlutil"
+)
+
+const errorTmpl = header + `
+// Source(s): {{ .Source }}
+package {{ .PackagePath }};
+
+/**
+ * Errors defined in all VDL files in this package.
+ */
+public final class {{ .ClassName }} {
+    {{ range $file := .Files }}
+
+    /* The following errors originate in file: {{ $file.Name }} */
+    {{/*Error Defs*/}}
+    {{ range $error := $file.Errors }}
+    {{ $error.Doc }}
+    {{ $error.AccessModifier }} static final io.v.v23.verror.VException.IDAction {{ $error.Name }} = io.v.v23.verror.VException.register("{{ $error.ID }}", io.v.v23.verror.VException.ActionCode.{{ $error.ActionName }}, "{{ $error.EnglishFmt }}");
+    {{ end }} {{/* range $file.Errors */}}
+
+    {{ end }} {{/* range .Files */}}
+
+    static {
+        {{ range $file := .Files }}
+        /* The following errors originate in file: {{ $file.Name }} */
+        {{ range $error := $file.Errors }}
+        {{ range $format := $error.Formats}}
+        io.v.v23.i18n.Language.getDefaultCatalog().setWithBase("{{ $format.Lang }}", {{ $error.Name }}.getID(), "{{ $format.Fmt }}");
+        {{ end }} {{/* range $error.Formats */}}
+        {{ end }} {{/* range $file.Errors */}}
+        {{ end }} {{/* range .Files */}}
+    }
+
+    {{ range $file := .Files }}
+    /* The following error creator methods originate in file: {{ $file.Name }} */
+    {{ range $error := $file.Errors }}
+    /**
+     * Creates an error with the {@link #{{ $error.Name }}} identifier.
+     */
+    {{ $error.AccessModifier }} static io.v.v23.verror.VException {{ $error.MethodName }}(io.v.v23.context.VContext _ctx{{ $error.MethodArgs}}) {
+        java.lang.Object[] _params = new java.lang.Object[] { {{ $error.Params }} };
+        java.lang.reflect.Type[] _paramTypes = new java.lang.reflect.Type[]{ {{ $error.ParamTypes }} };
+        return new io.v.v23.verror.VException({{ $error.Name }}, _ctx, _paramTypes, _params);
+    }
+    {{ end }} {{/* range $file.Errors */}}
+    {{ end }} {{/* range .Files */}}
+
+    private {{ .ClassName }}() {}
+}
+`
+
+type errorDef struct {
+	AccessModifier string
+	Doc            string
+	Name           string
+	ID             string
+	ActionName     string
+	EnglishFmt     string
+	Formats        []errorFormat
+	MethodName     string
+	MethodArgs     string
+	Params         string
+	ParamTypes     string
+}
+
+type errorFormat struct {
+	Lang string
+	Fmt  string
+}
+
+type errorFile struct {
+	Name   string
+	Errors []errorDef
+}
+
+func shouldGenerateErrorFile(pkg *compile.Package) bool {
+	for _, file := range pkg.Files {
+		if len(file.ErrorDefs) > 0 {
+			return true
+		}
+	}
+	return false
+}
+
+// genJavaErrorFile generates the (single) Java file that contains error
+// definitions from all the VDL files.
+func genJavaErrorFile(pkg *compile.Package, env *compile.Env) *JavaFileInfo {
+	if !shouldGenerateErrorFile(pkg) {
+		return nil
+	}
+
+	className := "Errors"
+
+	files := make([]errorFile, len(pkg.Files))
+	for i, file := range pkg.Files {
+		errors := make([]errorDef, len(file.ErrorDefs))
+		for j, err := range file.ErrorDefs {
+			formats := make([]errorFormat, len(err.Formats))
+			for k, format := range err.Formats {
+				formats[k].Lang = string(format.Lang)
+				formats[k].Fmt = format.Fmt
+			}
+			errors[j].AccessModifier = accessModifierForName(err.Name)
+			errors[j].Doc = javaDoc(err.Doc, err.DocSuffix)
+			errors[j].Name = vdlutil.ToConstCase(err.Name)
+			errors[j].ID = err.ID
+			errors[j].ActionName = vdlutil.ToConstCase(err.RetryCode.String())
+			errors[j].EnglishFmt = err.English
+			errors[j].Formats = formats
+			errors[j].MethodName = "new" + vdlutil.FirstRuneToUpper(err.Name)
+			errors[j].MethodArgs = javaDeclarationArgStr(err.Params, env, true)
+			errors[j].Params = javaCallingArgStr(err.Params, false)
+			errors[j].ParamTypes = javaCallingArgTypeStr(err.Params, env)
+		}
+		files[i].Name = file.BaseName
+		files[i].Errors = errors
+	}
+
+	data := struct {
+		ClassName   string
+		FileDoc     string
+		Source      string
+		PackagePath string
+		Files       []errorFile
+	}{
+		ClassName:   className,
+		FileDoc:     pkg.FileDoc,
+		Source:      javaFileNames(pkg.Files),
+		PackagePath: javaPath(javaGenPkgPath(pkg.GenPath)),
+		Files:       files,
+	}
+	var buf bytes.Buffer
+	err := parseTmpl("error", errorTmpl).Execute(&buf, data)
+	if err != nil {
+		log.Fatalf("vdl: couldn't execute error template: %v", err)
+	}
+	return &JavaFileInfo{
+		Name: className + ".java",
+		Data: buf.Bytes(),
+	}
+}
diff --git a/lib/vdl/codegen/java/file_list.go b/lib/vdl/codegen/java/file_list.go
new file mode 100644
index 0000000..4e6cf7e
--- /dev/null
+++ b/lib/vdl/codegen/java/file_list.go
@@ -0,0 +1,80 @@
+// 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 java
+
+import (
+	"bytes"
+	"log"
+
+	"v.io/x/ref/lib/vdl/compile"
+)
+
+const listTmpl = header + `
+// Source: {{.SourceFile}}
+package {{.Package}};
+
+{{ .Doc }}
+@io.v.v23.vdl.GeneratedFromVdl(name = "{{.VdlTypeName}}")
+{{ .AccessModifier }} class {{.Name}} extends io.v.v23.vdl.VdlList<{{.ElemType}}> {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Vdl type for {@link {{.Name}}}.
+     */
+    public static final io.v.v23.vdl.VdlType VDL_TYPE =
+            io.v.v23.vdl.Types.getVdlTypeFromReflect({{.Name}}.class);
+
+    /**
+     * Creates a new instance of {@link {{.Name}}} with the given underlying value.
+     *
+     * @param impl underlying value
+     */
+    public {{.Name}}(java.util.List<{{.ElemType}}> impl) {
+        super(VDL_TYPE, impl);
+    }
+
+    /**
+     * Creates a new empty instance of {@link {{.Name}}}.
+     */
+    public {{.Name}}() {
+        this(new java.util.ArrayList<{{.ElemType}}>());
+    }
+}
+`
+
+// genJavaListFile generates the Java class file for the provided named list type.
+func genJavaListFile(tdef *compile.TypeDef, env *compile.Env) JavaFileInfo {
+	name, access := javaTypeName(tdef, env)
+	data := struct {
+		AccessModifier string
+		Doc            string
+		ElemType       string
+		FileDoc        string
+		Name           string
+		Package        string
+		SourceFile     string
+		VdlTypeName    string
+		VdlTypeString  string
+	}{
+		AccessModifier: access,
+		Doc:            javaDoc(tdef.Doc, tdef.DocSuffix),
+		ElemType:       javaType(tdef.Type.Elem(), true, env),
+		FileDoc:        tdef.File.Package.FileDoc,
+		Name:           name,
+		Package:        javaPath(javaGenPkgPath(tdef.File.Package.GenPath)),
+		SourceFile:     tdef.File.BaseName,
+		VdlTypeName:    tdef.Type.Name(),
+		VdlTypeString:  tdef.Type.String(),
+	}
+	var buf bytes.Buffer
+	err := parseTmpl("list", listTmpl).Execute(&buf, data)
+	if err != nil {
+		log.Fatalf("vdl: couldn't execute list template: %v", err)
+	}
+	return JavaFileInfo{
+		Name: name + ".java",
+		Data: buf.Bytes(),
+	}
+}
diff --git a/lib/vdl/codegen/java/file_map.go b/lib/vdl/codegen/java/file_map.go
new file mode 100644
index 0000000..2cfe3fe
--- /dev/null
+++ b/lib/vdl/codegen/java/file_map.go
@@ -0,0 +1,83 @@
+// 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 java
+
+import (
+	"bytes"
+	"log"
+
+	"v.io/x/ref/lib/vdl/compile"
+)
+
+const mapTmpl = header + `
+// Source: {{.SourceFile}}
+
+package {{.Package}};
+
+{{ .Doc }}
+@io.v.v23.vdl.GeneratedFromVdl(name = "{{.VdlTypeName}}")
+{{ .AccessModifier }} class {{.Name}} extends io.v.v23.vdl.VdlMap<{{.KeyType}}, {{.ElemType}}> {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Vdl type for {@link {{.Name}}}.
+     */
+    public static final io.v.v23.vdl.VdlType VDL_TYPE =
+            io.v.v23.vdl.Types.getVdlTypeFromReflect({{.Name}}.class);
+
+    /**
+     * Creates a new instance of {@link {{.Name}}} with the given underlying value.
+     *
+     * @param impl underlying value
+     */
+    public {{.Name}}(java.util.Map<{{.KeyType}}, {{.ElemType}}> impl) {
+        super(VDL_TYPE, impl);
+    }
+
+    /**
+     * Creates a new empty instance of {@link {{.Name}}}.
+     */
+    public {{.Name}}() {
+        this(new java.util.HashMap<{{.KeyType}}, {{.ElemType}}>());
+    }
+}
+`
+
+// genJavaMapFile generates the Java class file for the provided named map type.
+func genJavaMapFile(tdef *compile.TypeDef, env *compile.Env) JavaFileInfo {
+	name, access := javaTypeName(tdef, env)
+	data := struct {
+		AccessModifier string
+		Doc            string
+		ElemType       string
+		FileDoc        string
+		KeyType        string
+		Name           string
+		Package        string
+		SourceFile     string
+		VdlTypeName    string
+		VdlTypeString  string
+	}{
+		AccessModifier: access,
+		Doc:            javaDoc(tdef.Doc, tdef.DocSuffix),
+		ElemType:       javaType(tdef.Type.Elem(), true, env),
+		FileDoc:        tdef.File.Package.FileDoc,
+		KeyType:        javaType(tdef.Type.Key(), true, env),
+		Name:           name,
+		Package:        javaPath(javaGenPkgPath(tdef.File.Package.GenPath)),
+		SourceFile:     tdef.File.BaseName,
+		VdlTypeName:    tdef.Type.Name(),
+		VdlTypeString:  tdef.BaseType.String(),
+	}
+	var buf bytes.Buffer
+	err := parseTmpl("map", mapTmpl).Execute(&buf, data)
+	if err != nil {
+		log.Fatalf("vdl: couldn't execute map template: %v", err)
+	}
+	return JavaFileInfo{
+		Name: name + ".java",
+		Data: buf.Bytes(),
+	}
+}
diff --git a/lib/vdl/codegen/java/file_package_info.go b/lib/vdl/codegen/java/file_package_info.go
new file mode 100644
index 0000000..a845a78
--- /dev/null
+++ b/lib/vdl/codegen/java/file_package_info.go
@@ -0,0 +1,56 @@
+// 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 java
+
+import (
+	"bytes"
+	"log"
+
+	"v.io/x/ref/lib/vdl/compile"
+)
+
+const packageTmpl = header + `
+// Source: {{ .Source }}
+
+{{ .Doc }}
+package {{ .PackagePath }};
+`
+
+// genPackageFileJava generates the Java package info file, iff any package
+// comments were specified in the package's VDL files.
+func genJavaPackageFile(pkg *compile.Package, env *compile.Env) *JavaFileInfo {
+	generated := false
+	for _, file := range pkg.Files {
+		if file.PackageDef.Doc != "" {
+			if generated {
+				log.Printf("WARNING: Multiple vdl files with package documentation. One will be overwritten.")
+				return nil
+			}
+			generated = true
+
+			data := struct {
+				Doc         string
+				FileDoc     string
+				PackagePath string
+				Source      string
+			}{
+				Doc:         javaDoc(file.PackageDef.Doc, file.PackageDef.DocSuffix),
+				FileDoc:     pkg.FileDoc,
+				PackagePath: javaPath(javaGenPkgPath(pkg.GenPath)),
+				Source:      javaFileNames(pkg.Files),
+			}
+			var buf bytes.Buffer
+			err := parseTmpl("package", packageTmpl).Execute(&buf, data)
+			if err != nil {
+				log.Fatalf("vdl: couldn't execute package template: %v", err)
+			}
+			return &JavaFileInfo{
+				Name: "package-info.java",
+				Data: buf.Bytes(),
+			}
+		}
+	}
+	return nil
+}
diff --git a/lib/vdl/codegen/java/file_primitive.go b/lib/vdl/codegen/java/file_primitive.go
new file mode 100644
index 0000000..c624e5c
--- /dev/null
+++ b/lib/vdl/codegen/java/file_primitive.go
@@ -0,0 +1,117 @@
+// 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 java
+
+import (
+	"bytes"
+	"log"
+
+	"v.io/v23/vdl"
+	"v.io/x/ref/lib/vdl/compile"
+)
+
+const primitiveTmpl = header + `
+// Source: {{ .Source }}
+package {{ .PackagePath }};
+
+{{ .Doc }}
+@io.v.v23.vdl.GeneratedFromVdl(name = "{{.VdlTypeName}}")
+{{ .AccessModifier }} class {{.Name}} extends {{.VdlType}} {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Vdl type for {@link {{.Name}}}.
+     */
+    public static final io.v.v23.vdl.VdlType VDL_TYPE =
+            io.v.v23.vdl.Types.getVdlTypeFromReflect({{.Name}}.class);
+
+    /**
+     * Creates a new instance of {@link {{.Name}}} with the given value.
+     *
+     * @param value value
+     */
+    public {{.Name}}({{.ConstructorType}} value) {
+        super(VDL_TYPE, value);
+    }
+
+    /**
+     * Creates a new zero-value instance of {@link {{.Name}}}.
+     */
+    public {{.Name}}() {
+        super(VDL_TYPE);
+    }
+}
+`
+
+// javaConstructorType returns java type that is used as a constructor argument
+// type for a VDL primitive.
+func javaConstructorType(t *vdl.Type) string {
+	switch t.Kind() {
+	case vdl.Uint16:
+		return "short"
+	case vdl.Uint32:
+		return "int"
+	case vdl.Uint64:
+		return "long"
+	default:
+		constructorType, _ := javaBuiltInType(t, false)
+		return constructorType
+	}
+}
+
+// javaConstructorType returns java class that is used as a type adapter delegate
+// argument for a VDL primitive.
+func javaTypeAdapterDelegateClass(t *vdl.Type) string {
+	switch t.Kind() {
+	case vdl.Uint16:
+		return "java.lang.Short"
+	case vdl.Uint32:
+		return "java.lang.Integer"
+	case vdl.Uint64:
+		return "java.lang.Long"
+	default:
+		typeAdapterDelegateClass, _ := javaBuiltInType(t, true)
+		return typeAdapterDelegateClass
+	}
+}
+
+// genJavaPrimitiveFile generates the Java class file for the provided user-defined type.
+func genJavaPrimitiveFile(tdef *compile.TypeDef, env *compile.Env) JavaFileInfo {
+	name, access := javaTypeName(tdef, env)
+	data := struct {
+		AccessModifier           string
+		ConstructorType          string
+		Doc                      string
+		FileDoc                  string
+		Name                     string
+		PackagePath              string
+		Source                   string
+		TypeAdapterDelegateClass string
+		VdlType                  string
+		VdlTypeName              string
+		VdlTypeString            string
+	}{
+		AccessModifier:  access,
+		Doc:             javaDoc(tdef.Doc, tdef.DocSuffix),
+		ConstructorType: javaConstructorType(tdef.Type),
+		FileDoc:         tdef.File.Package.FileDoc,
+		Name:            name,
+		PackagePath:     javaPath(javaGenPkgPath(tdef.File.Package.GenPath)),
+		Source:          tdef.File.BaseName,
+		TypeAdapterDelegateClass: javaTypeAdapterDelegateClass(tdef.Type),
+		VdlType:                  javaVdlPrimitiveType(tdef.Type.Kind()),
+		VdlTypeName:              tdef.Type.Name(),
+		VdlTypeString:            tdef.Type.String(),
+	}
+	var buf bytes.Buffer
+	err := parseTmpl("primitive", primitiveTmpl).Execute(&buf, data)
+	if err != nil {
+		log.Fatalf("vdl: couldn't execute primitive template: %v", err)
+	}
+	return JavaFileInfo{
+		Name: name + ".java",
+		Data: buf.Bytes(),
+	}
+}
diff --git a/lib/vdl/codegen/java/file_server_interface.go b/lib/vdl/codegen/java/file_server_interface.go
new file mode 100644
index 0000000..e978a4e
--- /dev/null
+++ b/lib/vdl/codegen/java/file_server_interface.go
@@ -0,0 +1,137 @@
+// 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 java
+
+import (
+	"bytes"
+	"fmt"
+	"log"
+	"path"
+
+	"v.io/x/ref/lib/vdl/compile"
+	"v.io/x/ref/lib/vdl/vdlutil"
+)
+
+const serverInterfaceTmpl = header + `
+// Source: {{ .Source }}
+package {{ .PackagePath }};
+
+{{ .ServerDoc }}
+@io.v.v23.vdl.VServer(
+    serverWrapper = {{ .ServerWrapperPath }}.class
+)
+public interface {{ .ServiceName }}Server {{ .Extends }} {
+{{ range $method := .Methods }}
+    {{/* If this method has multiple return arguments, generate the class. */}}
+    {{ if $method.IsMultipleRet }}
+    /**
+     * Multi-return value for method {@link #{{$method.Name}}}.
+     */
+    @io.v.v23.vdl.MultiReturn
+    public static class {{ $method.UppercaseMethodName }}Out {
+        {{ range $retArg := $method.RetArgs }}
+        public {{ $retArg.Type }} {{ $retArg.Name }};
+        {{ end }}
+    }
+    {{ end }}
+
+    {{/* Generate the method signature. */}}
+    {{ $method.Doc }}
+    {{ $method.RetType }} {{ $method.Name }}(io.v.v23.context.VContext ctx, io.v.v23.rpc.ServerCall call{{ $method.Args }}) throws io.v.v23.verror.VException;
+{{ end }}
+}
+`
+
+func serverInterfaceOutArg(iface *compile.Interface, method *compile.Method, env *compile.Env) string {
+	switch len(method.OutArgs) {
+	case 0:
+		return "void"
+	case 1:
+		return javaType(method.OutArgs[0].Type, false, env)
+	default:
+		return javaPath(path.Join(interfaceFullyQualifiedName(iface)+"Server", method.Name+"Out"))
+	}
+}
+
+type serverInterfaceArg struct {
+	Type string
+	Name string
+}
+
+type serverInterfaceMethod struct {
+	Args                string
+	Doc                 string
+	Name                string
+	IsMultipleRet       bool
+	RetArgs             []serverInterfaceArg
+	RetType             string
+	UppercaseMethodName string
+}
+
+func processServerInterfaceMethod(method *compile.Method, iface *compile.Interface, env *compile.Env) serverInterfaceMethod {
+	args := javaDeclarationArgStr(method.InArgs, env, true)
+	if isStreamingMethod(method) {
+		args += fmt.Sprintf(", io.v.v23.vdl.TypedStream<%s, %s> stream", javaType(method.OutStream, true, env), javaType(method.InStream, true, env))
+	}
+	retArgs := make([]serverInterfaceArg, len(method.OutArgs))
+	for i := 0; i < len(method.OutArgs); i++ {
+		if method.OutArgs[i].Name != "" {
+			retArgs[i].Name = vdlutil.FirstRuneToLower(method.OutArgs[i].Name)
+		} else {
+			retArgs[i].Name = fmt.Sprintf("ret%d", i+1)
+		}
+		retArgs[i].Type = javaType(method.OutArgs[i].Type, false, env)
+	}
+
+	return serverInterfaceMethod{
+		Args:                args,
+		Doc:                 javaDoc(method.Doc, method.DocSuffix),
+		Name:                vdlutil.FirstRuneToLower(method.Name),
+		IsMultipleRet:       len(retArgs) > 1,
+		RetArgs:             retArgs,
+		RetType:             serverInterfaceOutArg(iface, method, env),
+		UppercaseMethodName: method.Name,
+	}
+}
+
+// genJavaServerInterfaceFile generates the Java interface file for the provided
+// interface.
+func genJavaServerInterfaceFile(iface *compile.Interface, env *compile.Env) JavaFileInfo {
+	methods := make([]serverInterfaceMethod, len(iface.Methods))
+	for i, method := range iface.Methods {
+		methods[i] = processServerInterfaceMethod(method, iface, env)
+	}
+	javaServiceName := vdlutil.FirstRuneToUpper(iface.Name)
+	data := struct {
+		FileDoc           string
+		Extends           string
+		Methods           []serverInterfaceMethod
+		PackagePath       string
+		ServerDoc         string
+		ServerVDLPath     string
+		ServiceName       string
+		ServerWrapperPath string
+		Source            string
+	}{
+		FileDoc:           iface.File.Package.FileDoc,
+		Extends:           javaServerExtendsStr(iface.Embeds),
+		Methods:           methods,
+		PackagePath:       javaPath(javaGenPkgPath(iface.File.Package.GenPath)),
+		ServerDoc:         javaDoc(iface.Doc, iface.DocSuffix),
+		ServiceName:       javaServiceName,
+		ServerVDLPath:     path.Join(iface.File.Package.GenPath, iface.Name+"ServerMethods"),
+		ServerWrapperPath: javaPath(javaGenPkgPath(path.Join(iface.File.Package.GenPath, javaServiceName+"ServerWrapper"))),
+		Source:            iface.File.BaseName,
+	}
+	var buf bytes.Buffer
+	err := parseTmpl("server interface", serverInterfaceTmpl).Execute(&buf, data)
+	if err != nil {
+		log.Fatalf("vdl: couldn't execute struct template: %v", err)
+	}
+	return JavaFileInfo{
+		Name: javaServiceName + "Server.java",
+		Data: buf.Bytes(),
+	}
+}
diff --git a/lib/vdl/codegen/java/file_server_wrapper.go b/lib/vdl/codegen/java/file_server_wrapper.go
new file mode 100644
index 0000000..6889463
--- /dev/null
+++ b/lib/vdl/codegen/java/file_server_wrapper.go
@@ -0,0 +1,292 @@
+// 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 java
+
+import (
+	"bytes"
+	"log"
+	"path"
+	"strings"
+
+	"v.io/x/ref/lib/vdl/compile"
+	"v.io/x/ref/lib/vdl/vdlutil"
+)
+
+const serverWrapperTmpl = header + `
+// Source(s):  {{ .Source }}
+package {{ .PackagePath }};
+
+/**
+ * Wrapper for {@link {{ .ServiceName }}Server}.  This wrapper is used by
+ * {@link io.v.v23.rpc.ReflectInvoker} to indirectly invoke server methods.
+ */
+public final class {{ .ServiceName }}ServerWrapper {
+    private final {{ .FullServiceName }}Server server;
+
+{{/* Define fields to hold each of the embedded server wrappers*/}}
+{{ range $embed := .Embeds }}
+    {{/* e.g. private final com.somepackage.gen_impl.ArithStub stubArith; */}}
+    private final {{ $embed.FullName }}ServerWrapper wrapper{{ $embed.Name }};
+    {{ end }}
+
+    /**
+     * Creates a new {@link {{ .ServiceName }}ServerWrapper} to invoke the methods of the
+     * provided server.
+     *
+     * @param server server whose methods are to be invoked
+     */
+    public {{ .ServiceName }}ServerWrapper({{ .FullServiceName }}Server server) {
+        this.server = server;
+        {{/* Initialize the embeded server wrappers */}}
+        {{ range $embed := .Embeds }}
+        this.wrapper{{ $embed.Name }} = new {{ $embed.FullName }}ServerWrapper(server);
+        {{ end }}
+    }
+
+    /**
+     * Returns a description of this server.
+     */
+    public io.v.v23.vdlroot.signature.Interface signature() {
+        java.util.List<io.v.v23.vdlroot.signature.Embed> embeds = new java.util.ArrayList<io.v.v23.vdlroot.signature.Embed>();
+        java.util.List<io.v.v23.vdlroot.signature.Method> methods = new java.util.ArrayList<io.v.v23.vdlroot.signature.Method>();
+        {{ range $method := .Methods }}
+        {
+            java.util.List<io.v.v23.vdlroot.signature.Arg> inArgs = new java.util.ArrayList<io.v.v23.vdlroot.signature.Arg>();
+            {{ range $arg := $method.CallingArgTypes }}
+            inArgs.add(new io.v.v23.vdlroot.signature.Arg("", "", new io.v.v23.vdl.VdlTypeObject({{ $arg }})));
+            {{ end }}
+            java.util.List<io.v.v23.vdlroot.signature.Arg> outArgs = new java.util.ArrayList<io.v.v23.vdlroot.signature.Arg>();
+            {{ range $arg := $method.RetJavaTypes }}
+            outArgs.add(new io.v.v23.vdlroot.signature.Arg("", "", new io.v.v23.vdl.VdlTypeObject({{ $arg }})));
+            {{ end }}
+            java.util.List<io.v.v23.vdl.VdlAny> tags = new java.util.ArrayList<io.v.v23.vdl.VdlAny>();
+            {{ range $tag := .Tags }}
+            tags.add(new io.v.v23.vdl.VdlAny(io.v.v23.vdl.VdlValue.valueOf({{ $tag.Value }}, {{ $tag.Type }})));
+            {{ end }}
+            methods.add(new io.v.v23.vdlroot.signature.Method(
+                "{{ $method.Name }}",
+                "{{ $method.Doc }}",
+                inArgs,
+                outArgs,
+                null,
+                null,
+                tags));
+        }
+        {{ end }}
+
+        return new io.v.v23.vdlroot.signature.Interface("{{ .ServiceName }}", "{{ .PackagePath }}", "{{ .Doc }}", embeds, methods);
+    }
+
+    /**
+     * Returns all tags associated with the provided method or {@code null} if the method isn't
+     * implemented by this server.
+     *
+     * @param method method whose tags are to be returned
+     */
+    @SuppressWarnings("unused")
+    public io.v.v23.vdl.VdlValue[] getMethodTags(java.lang.String method) throws io.v.v23.verror.VException {
+        {{ range $methodName, $tags := .MethodTags }}
+        if ("{{ $methodName }}".equals(method)) {
+            try {
+                return new io.v.v23.vdl.VdlValue[] {
+                    {{ range $tag := $tags }} io.v.v23.vdl.VdlValue.valueOf({{ $tag.Value }}, {{ $tag.Type }}), {{ end }}
+                };
+            } catch (IllegalArgumentException e) {
+                throw new io.v.v23.verror.VException(String.format("Couldn't get tags for method \"{{ $methodName }}\": %s", e.getMessage()));
+            }
+        }
+        {{ end }}
+        {{ range $embed := .Embeds }}
+        {
+            io.v.v23.vdl.VdlValue[] tags = this.wrapper{{ $embed.Name }}.getMethodTags(method);
+            if (tags != null) {
+                return tags;
+            }
+        }
+        {{ end }}
+        return null;  // method not found
+    }
+
+     {{/* Iterate over methods defined directly in the body of this server */}}
+    {{ range $method := .Methods }}
+    {{ $method.JavaDoc }}
+    public {{ $method.RetType }} {{ $method.Name }}(io.v.v23.context.VContext ctx, final io.v.v23.rpc.StreamServerCall call{{ $method.DeclarationArgs }}) throws io.v.v23.verror.VException {
+        {{ if $method.IsStreaming }}
+        io.v.v23.vdl.TypedStream<{{ $method.SendType }}, {{ $method.RecvType }}> _stream = new io.v.v23.vdl.TypedStream<{{ $method.SendType }}, {{ $method.RecvType }}>() {
+            @Override
+            public void send({{ $method.SendType }} item) throws io.v.v23.verror.VException {
+                java.lang.reflect.Type type = new com.google.common.reflect.TypeToken< {{ $method.SendType }} >() {}.getType();
+                call.send(item, type);
+            }
+            @Override
+            public {{ $method.RecvType }} recv() throws java.io.EOFException, io.v.v23.verror.VException {
+                java.lang.reflect.Type type = new com.google.common.reflect.TypeToken< {{ $method.RecvType }} >() {}.getType();
+                java.lang.Object result = call.recv(type);
+                try {
+                    return ({{ $method.RecvType }})result;
+                } catch (java.lang.ClassCastException e) {
+                    throw new io.v.v23.verror.VException("Unexpected result type: " + result.getClass().getCanonicalName());
+                }
+            }
+        };
+        {{ end }} {{/* end if $method.IsStreaming */}}
+        {{ if $method.Returns }} return {{ end }} this.server.{{ $method.Name }}(ctx, call {{ $method.CallingArgs }} {{ if $method.IsStreaming }} ,_stream {{ end }} );
+    }
+{{end}}
+
+{{/* Iterate over methods from embeded servers and generate code to delegate the work */}}
+{{ range $eMethod := .EmbedMethods }}
+    {{ $eMethod.JavaDoc }}
+    public {{ $eMethod.RetType }} {{ $eMethod.Name }}(io.v.v23.context.VContext ctx, io.v.v23.rpc.StreamServerCall call{{ $eMethod.DeclarationArgs }}) throws io.v.v23.verror.VException {
+        {{/* e.g. return this.stubArith.cosine(ctx, call, [args], options) */}}
+        {{ if $eMethod.Returns }}return{{ end }}  this.wrapper{{ $eMethod.IfaceName }}.{{ $eMethod.Name }}(ctx, call{{ $eMethod.CallingArgs }});
+    }
+{{ end }} {{/* end range .EmbedMethods */}}
+
+}
+`
+
+type serverWrapperMethod struct {
+	CallingArgs     string
+	CallingArgTypes []string
+	DeclarationArgs string
+	Doc             string
+	IsStreaming     bool
+	JavaDoc         string
+	Name            string
+	RecvType        string
+	RetType         string
+	RetJavaTypes    []string
+	Returns         bool
+	SendType        string
+	Tags            []methodTag
+}
+
+type serverWrapperEmbedMethod struct {
+	CallingArgs     string
+	DeclarationArgs string
+	Doc             string
+	IfaceName       string
+	JavaDoc         string
+	Name            string
+	RetType         string
+	Returns         bool
+}
+
+type serverWrapperEmbed struct {
+	Name     string
+	FullName string
+}
+
+type methodTag struct {
+	Value string
+	Type  string
+}
+
+// TODO(sjr): move this to somewhere in util_*.
+func toJavaString(goString string) string {
+	result := strings.Replace(goString, "\"", "\\\"", -1)
+	result = strings.Replace(result, "\n", "\" + \n\"", -1)
+	return result
+}
+
+func processServerWrapperMethod(iface *compile.Interface, method *compile.Method, env *compile.Env, tags []methodTag) serverWrapperMethod {
+	callArgTypes := make([]string, len(method.InArgs))
+	for i, arg := range method.InArgs {
+		callArgTypes[i] = javaReflectType(arg.Type, env)
+	}
+	retArgTypes := make([]string, len(method.OutArgs))
+	for i, arg := range method.OutArgs {
+		retArgTypes[i] = javaReflectType(arg.Type, env)
+	}
+	return serverWrapperMethod{
+		CallingArgs:     javaCallingArgStr(method.InArgs, true),
+		CallingArgTypes: callArgTypes,
+		DeclarationArgs: javaDeclarationArgStr(method.InArgs, env, true),
+		Doc:             toJavaString(method.Doc),
+		IsStreaming:     isStreamingMethod(method),
+		JavaDoc:         javaDoc(method.Doc, method.DocSuffix),
+		Name:            vdlutil.FirstRuneToLower(method.Name),
+		RecvType:        javaType(method.InStream, true, env),
+		RetType:         serverInterfaceOutArg(iface, method, env),
+		RetJavaTypes:    retArgTypes,
+		Returns:         len(method.OutArgs) >= 1,
+		SendType:        javaType(method.OutStream, true, env),
+		Tags:            tags,
+	}
+}
+
+func processServerWrapperEmbedMethod(iface *compile.Interface, embedMethod *compile.Method, env *compile.Env) serverWrapperEmbedMethod {
+	return serverWrapperEmbedMethod{
+		CallingArgs:     javaCallingArgStr(embedMethod.InArgs, true),
+		DeclarationArgs: javaDeclarationArgStr(embedMethod.InArgs, env, true),
+		IfaceName:       vdlutil.FirstRuneToUpper(iface.Name),
+		JavaDoc:         javaDoc(embedMethod.Doc, embedMethod.DocSuffix),
+		Name:            vdlutil.FirstRuneToLower(embedMethod.Name),
+		RetType:         serverInterfaceOutArg(iface, embedMethod, env),
+		Returns:         len(embedMethod.OutArgs) >= 1,
+	}
+}
+
+// genJavaServerWrapperFile generates a java file containing a server wrapper for the specified
+// interface.
+func genJavaServerWrapperFile(iface *compile.Interface, env *compile.Env) JavaFileInfo {
+	embeds := []serverWrapperEmbed{}
+	for _, embed := range allEmbeddedIfaces(iface) {
+		embeds = append(embeds, serverWrapperEmbed{
+			Name:     vdlutil.FirstRuneToUpper(embed.Name),
+			FullName: javaPath(javaGenPkgPath(path.Join(embed.File.Package.GenPath, vdlutil.FirstRuneToUpper(embed.Name)))),
+		})
+	}
+	methodTags := make(map[string][]methodTag)
+	// Copy method tags off of the interface.
+	methods := make([]serverWrapperMethod, len(iface.Methods))
+	for i, method := range iface.Methods {
+		tags := make([]methodTag, len(method.Tags))
+		for j, tag := range method.Tags {
+			tags[j].Value = javaConstVal(tag, env)
+			tags[j].Type = javaReflectType(tag.Type(), env)
+		}
+		methodTags[vdlutil.FirstRuneToLower(method.Name)] = tags
+		methods[i] = processServerWrapperMethod(iface, method, env, tags)
+	}
+	embedMethods := []serverWrapperEmbedMethod{}
+	for _, embedMao := range dedupedEmbeddedMethodAndOrigins(iface) {
+		embedMethods = append(embedMethods, processServerWrapperEmbedMethod(embedMao.Origin, embedMao.Method, env))
+	}
+	javaServiceName := vdlutil.FirstRuneToUpper(iface.Name)
+	data := struct {
+		FileDoc         string
+		EmbedMethods    []serverWrapperEmbedMethod
+		Embeds          []serverWrapperEmbed
+		FullServiceName string
+		Methods         []serverWrapperMethod
+		MethodTags      map[string][]methodTag
+		PackagePath     string
+		ServiceName     string
+		Source          string
+		Doc             string
+	}{
+		FileDoc:         iface.File.Package.FileDoc,
+		EmbedMethods:    embedMethods,
+		Embeds:          embeds,
+		FullServiceName: javaPath(interfaceFullyQualifiedName(iface)),
+		Methods:         methods,
+		MethodTags:      methodTags,
+		PackagePath:     javaPath(javaGenPkgPath(iface.File.Package.GenPath)),
+		ServiceName:     javaServiceName,
+		Source:          iface.File.BaseName,
+		Doc:             toJavaString(iface.NamePos.Doc),
+	}
+	var buf bytes.Buffer
+	err := parseTmpl("server wrapper", serverWrapperTmpl).Execute(&buf, data)
+	if err != nil {
+		log.Fatalf("vdl: couldn't execute server wrapper template: %v", err)
+	}
+	return JavaFileInfo{
+		Name: javaServiceName + "ServerWrapper.java",
+		Data: buf.Bytes(),
+	}
+}
diff --git a/lib/vdl/codegen/java/file_set.go b/lib/vdl/codegen/java/file_set.go
new file mode 100644
index 0000000..175d7d7
--- /dev/null
+++ b/lib/vdl/codegen/java/file_set.go
@@ -0,0 +1,81 @@
+// 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 java
+
+import (
+	"bytes"
+	"log"
+
+	"v.io/x/ref/lib/vdl/compile"
+)
+
+const setTmpl = header + `
+// Source: {{.SourceFile}}
+
+package {{.Package}};
+
+{{ .Doc }}
+@io.v.v23.vdl.GeneratedFromVdl(name = "{{.VdlTypeName}}")
+{{ .AccessModifier }} class {{.Name}} extends io.v.v23.vdl.VdlSet<{{.KeyType}}> {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Vdl type for {@link {{.Name}}}.
+     */
+    public static final io.v.v23.vdl.VdlType VDL_TYPE =
+            io.v.v23.vdl.Types.getVdlTypeFromReflect({{.Name}}.class);
+
+    /**
+     * Creates a new instance of {@link {{.Name}}} with the given underlying value.
+     *
+     * @param impl underlying value
+     */
+    public {{.Name}}(java.util.Set<{{.KeyType}}> impl) {
+        super(VDL_TYPE, impl);
+    }
+
+    /**
+     * Creates a new zero-value instance of {@link {{.Name}}}.
+     */
+    public {{.Name}}() {
+        this(new java.util.HashSet<{{.KeyType}}>());
+    }
+}
+`
+
+// genJavaSetFile generates the Java class file for the provided named set type.
+func genJavaSetFile(tdef *compile.TypeDef, env *compile.Env) JavaFileInfo {
+	name, access := javaTypeName(tdef, env)
+	data := struct {
+		AccessModifier string
+		Doc            string
+		FileDoc        string
+		KeyType        string
+		Name           string
+		Package        string
+		SourceFile     string
+		VdlTypeName    string
+		VdlTypeString  string
+	}{
+		AccessModifier: access,
+		Doc:            javaDoc(tdef.Doc, tdef.DocSuffix),
+		FileDoc:        tdef.File.Package.FileDoc,
+		KeyType:        javaType(tdef.Type.Key(), true, env),
+		Name:           name,
+		Package:        javaPath(javaGenPkgPath(tdef.File.Package.GenPath)),
+		SourceFile:     tdef.File.BaseName,
+		VdlTypeName:    tdef.Type.Name(),
+		VdlTypeString:  tdef.Type.String(),
+	}
+	var buf bytes.Buffer
+	err := parseTmpl("set", setTmpl).Execute(&buf, data)
+	if err != nil {
+		log.Fatalf("vdl: couldn't execute set template: %v", err)
+	}
+	return JavaFileInfo{
+		Name: name + ".java",
+		Data: buf.Bytes(),
+	}
+}
diff --git a/lib/vdl/codegen/java/file_struct.go b/lib/vdl/codegen/java/file_struct.go
new file mode 100644
index 0000000..941b9fa
--- /dev/null
+++ b/lib/vdl/codegen/java/file_struct.go
@@ -0,0 +1,210 @@
+// 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 java
+
+import (
+	"bytes"
+	"log"
+
+	"v.io/v23/vdl"
+	"v.io/x/ref/lib/vdl/compile"
+	"v.io/x/ref/lib/vdl/vdlutil"
+)
+
+const structTmpl = header + `
+// Source: {{.Source}}
+package {{.PackagePath}};
+
+{{ .Doc }}
+@io.v.v23.vdl.GeneratedFromVdl(name = "{{.VdlTypeName}}")
+{{ .AccessModifier }} class {{.Name}} extends io.v.v23.vdl.AbstractVdlStruct {
+    private static final long serialVersionUID = 1L;
+
+    {{/* Field declarations */}}
+    {{ range $index, $field := .Fields }}
+      @io.v.v23.vdl.GeneratedFromVdl(name = "{{$field.Name}}", index = {{$index}})
+      private {{$field.Type}} {{$field.LowercaseName}};
+    {{ end }}
+
+    /**
+     * Vdl type for {@link {{.Name}}}.
+     */
+    public static final io.v.v23.vdl.VdlType VDL_TYPE =
+            io.v.v23.vdl.Types.getVdlTypeFromReflect({{.Name}}.class);
+
+    {{/* Constructors */}}
+    /**
+     * Creates a new zero-value instance of {@link {{.Name}}}.
+     */
+    public {{.Name}}() {
+        super(VDL_TYPE);
+        {{ range $field := .Fields }}
+            this.{{$field.LowercaseName}} = {{$field.ZeroValue}};
+        {{ end }}
+    }
+
+    {{ if .FieldsAsArgs }}
+    /**
+     * Creates a new instance of {@link {{ .Name }}} with the provided field values.
+     */
+    public {{.Name}}({{ .FieldsAsArgs }}) {
+        super(VDL_TYPE);
+        {{ range $field := .Fields }}
+            this.{{$field.LowercaseName}} = {{$field.LowercaseName}};
+        {{ end }}
+    }
+    {{ end }}
+
+    {{/* Getters and setters */}}
+    {{ range $field := .Fields }}
+    {{ $field.Doc }}
+    {{ $field.AccessModifier }} {{$field.Type}} get{{$field.Name}}() {
+        return this.{{$field.LowercaseName}};
+    }
+
+    {{ $field.Doc }}
+    {{ $field.AccessModifier }} void set{{$field.Name}}({{$field.Type}} {{$field.LowercaseName}}) {
+        this.{{$field.LowercaseName}} = {{$field.LowercaseName}};
+    }
+    {{ end }}
+
+    @Override
+    public boolean equals(java.lang.Object obj) {
+        if (this == obj) return true;
+        if (obj == null) return false;
+        if (this.getClass() != obj.getClass()) return false;
+	{{ if gt (len .Fields) 0 }} {{.Name}} other = ({{.Name}})obj; {{ end }}
+
+        {{ range $field := .Fields }}
+        {{ if .IsArray }}
+        if (!java.util.Arrays.equals(this.{{$field.LowercaseName}}, other.{{$field.LowercaseName}})) {
+            return false;
+        }
+        {{ else }}
+        {{ if .IsClass }}
+        if (this.{{$field.LowercaseName}} == null) {
+            if (other.{{$field.LowercaseName}} != null) {
+                return false;
+            }
+        } else if (!this.{{$field.LowercaseName}}.equals(other.{{$field.LowercaseName}})) {
+            return false;
+        }
+        {{ else }}
+        if (this.{{$field.LowercaseName}} != other.{{$field.LowercaseName}}) {
+            return false;
+        }
+        {{ end }} {{/* if is class */}}
+        {{ end }} {{/* if is array */}}
+        {{ end }} {{/* range over fields */}}
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 1;
+        int prime = 31;
+        {{ range $field := .Fields }}
+        result = prime * result + {{$field.HashcodeComputation}};
+        {{ end }}
+        return result;
+    }
+
+    @Override
+    public java.lang.String toString() {
+        String result = "{";
+        {{ range $index, $field := .Fields }}
+            {{ if gt $index 0 }}
+                result += ", ";
+            {{ end }}
+            {{ if .IsArray }}
+                result += "{{$field.LowercaseName}}:" + java.util.Arrays.toString(this.{{$field.LowercaseName}});
+            {{ else }}
+            result += "{{$field.LowercaseName}}:" + this.{{$field.LowercaseName}};
+            {{ end}} {{/* if is array */}}
+        {{ end }} {{/* range over fields */}}
+        return result + "}";
+    }
+}`
+
+type structDefinitionField struct {
+	AccessModifier      string
+	Class               string
+	Doc                 string
+	HashcodeComputation string
+	IsClass             bool
+	IsArray             bool
+	LowercaseName       string
+	Name                string
+	Type                string
+	ZeroValue           string
+}
+
+func javaFieldArgStr(structType *vdl.Type, env *compile.Env) string {
+	var buf bytes.Buffer
+	for i := 0; i < structType.NumField(); i++ {
+		if i > 0 {
+			buf.WriteString(", ")
+		}
+		fld := structType.Field(i)
+		buf.WriteString(javaType(fld.Type, false, env))
+		buf.WriteString(" ")
+		buf.WriteString(vdlutil.FirstRuneToLower(fld.Name))
+	}
+	return buf.String()
+}
+
+// genJavaStructFile generates the Java class file for the provided user-defined type.
+func genJavaStructFile(tdef *compile.TypeDef, env *compile.Env) JavaFileInfo {
+	fields := make([]structDefinitionField, tdef.Type.NumField())
+	for i := 0; i < tdef.Type.NumField(); i++ {
+		fld := tdef.Type.Field(i)
+		fields[i] = structDefinitionField{
+			AccessModifier:      accessModifierForName(fld.Name),
+			Class:               javaType(fld.Type, true, env),
+			Doc:                 javaDoc(tdef.FieldDoc[i], tdef.FieldDocSuffix[i]),
+			HashcodeComputation: javaHashCode("this." + vdlutil.FirstRuneToLower(fld.Name), fld.Type, env),
+			IsClass:             isClass(fld.Type, env),
+			IsArray:             isJavaNativeArray(fld.Type, env),
+			LowercaseName:       vdlutil.FirstRuneToLower(fld.Name),
+			Name:                fld.Name,
+			Type:                javaType(fld.Type, false, env),
+			ZeroValue:           javaZeroValue(fld.Type, env),
+		}
+	}
+
+	name, access := javaTypeName(tdef, env)
+	data := struct {
+		FileDoc        string
+		AccessModifier string
+		Doc            string
+		Fields         []structDefinitionField
+		FieldsAsArgs   string
+		Name           string
+		PackagePath    string
+		Source         string
+		VdlTypeName    string
+		VdlTypeString  string
+	}{
+		FileDoc:        tdef.File.Package.FileDoc,
+		AccessModifier: access,
+		Doc:            javaDoc(tdef.Doc, tdef.DocSuffix),
+		Fields:         fields,
+		FieldsAsArgs:   javaFieldArgStr(tdef.Type, env),
+		Name:           name,
+		PackagePath:    javaPath(javaGenPkgPath(tdef.File.Package.GenPath)),
+		Source:         tdef.File.BaseName,
+		VdlTypeName:    tdef.Type.Name(),
+		VdlTypeString:  tdef.Type.String(),
+	}
+	var buf bytes.Buffer
+	err := parseTmpl("struct", structTmpl).Execute(&buf, data)
+	if err != nil {
+		log.Fatalf("vdl: couldn't execute struct template: %v", err)
+	}
+	return JavaFileInfo{
+		Name: name + ".java",
+		Data: buf.Bytes(),
+	}
+}
diff --git a/lib/vdl/codegen/java/file_union.go b/lib/vdl/codegen/java/file_union.go
new file mode 100644
index 0000000..085587f
--- /dev/null
+++ b/lib/vdl/codegen/java/file_union.go
@@ -0,0 +1,114 @@
+// 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 java
+
+import (
+	"bytes"
+	"log"
+
+	"v.io/x/ref/lib/vdl/compile"
+)
+
+const unionTmpl = header + `
+// Source: {{.Source}}
+package {{.PackagePath}};
+
+{{ .Doc }}
+@io.v.v23.vdl.GeneratedFromVdl(name = "{{.VdlTypeName}}")
+{{ .AccessModifier }} class {{.Name}} extends io.v.v23.vdl.VdlUnion {
+	private static final long serialVersionUID = 1L;
+
+    {{ range $index, $field := .Fields }}
+    {{ $field.Doc }}
+    @io.v.v23.vdl.GeneratedFromVdl(name = "{{$field.Name}}", index = {{$index}})
+    public static class {{$field.Name}} extends {{$.Name}} {
+    	private static final long serialVersionUID = 1L;
+        private {{$field.Type}} elem;
+
+        public {{$field.Name}}({{$field.Type}} elem) {
+            super({{$index}}, elem);
+            this.elem = elem;
+        }
+
+        public {{$field.Name}}() {
+            this({{$field.ZeroValue}});
+        }
+
+        @Override
+        public {{$field.Class}} getElem() {
+            return elem;
+        }
+
+        @Override
+        public int hashCode() {
+            return {{$field.HashcodeComputation}};
+        }
+    }
+    {{ end }}
+
+    public static final io.v.v23.vdl.VdlType VDL_TYPE =
+            io.v.v23.vdl.Types.getVdlTypeFromReflect({{.Name}}.class);
+
+    public {{.Name}}(int index, Object value) {
+        super(VDL_TYPE, index, value);
+    }
+}
+`
+
+type unionDefinitionField struct {
+	Class               string
+	Doc                 string
+	HashcodeComputation string
+	Name                string
+	Type                string
+	ZeroValue           string
+}
+
+// genJavaUnionFile generates the Java class file for the provided user-defined union type.
+func genJavaUnionFile(tdef *compile.TypeDef, env *compile.Env) JavaFileInfo {
+	fields := make([]unionDefinitionField, tdef.Type.NumField())
+	for i := 0; i < tdef.Type.NumField(); i++ {
+		fld := tdef.Type.Field(i)
+		fields[i] = unionDefinitionField{
+			Class:               javaType(fld.Type, true, env),
+			Doc:                 javaDoc(tdef.FieldDoc[i], tdef.FieldDocSuffix[i]),
+			HashcodeComputation: javaHashCode("elem", fld.Type, env),
+			Name:                fld.Name,
+			Type:                javaType(fld.Type, false, env),
+			ZeroValue:           javaZeroValue(fld.Type, env),
+		}
+	}
+	name, access := javaTypeName(tdef, env)
+	data := struct {
+		FileDoc        string
+		AccessModifier string
+		Doc            string
+		Fields         []unionDefinitionField
+		Name           string
+		PackagePath    string
+		Source         string
+		VdlTypeName    string
+		VdlTypeString  string
+	}{
+		FileDoc:        tdef.File.Package.FileDoc,
+		AccessModifier: access,
+		Doc:            javaDoc(tdef.Doc, tdef.DocSuffix),
+		Fields:         fields,
+		Name:           name,
+		PackagePath:    javaPath(javaGenPkgPath(tdef.File.Package.GenPath)),
+		Source:         tdef.File.BaseName,
+		VdlTypeName:    tdef.Type.Name(),
+		VdlTypeString:  tdef.Type.String(),
+	}
+	var buf bytes.Buffer
+	err := parseTmpl("union", unionTmpl).Execute(&buf, data)
+	if err != nil {
+		log.Fatalf("vdl: couldn't execute union template: %v", err)
+	}
+	return JavaFileInfo{
+		Name: name + ".java",
+		Data: buf.Bytes(),
+	}
+}
diff --git a/lib/vdl/codegen/java/generate.go b/lib/vdl/codegen/java/generate.go
new file mode 100644
index 0000000..da23e68
--- /dev/null
+++ b/lib/vdl/codegen/java/generate.go
@@ -0,0 +1,121 @@
+// 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 java implements Java code generation from compiled VDL packages.
+package java
+
+import (
+	"path"
+
+	"v.io/v23/vdl"
+	"v.io/x/ref/lib/vdl/compile"
+)
+
+// pkgPathXlator is the function used to translate a VDL package path
+// into a Java package path.  If nil, no translation takes place.
+var pkgPathXlator func(path string) string
+
+// SetPkgPathXlator sets the function used to translate a VDL package
+// path into a Java package path.
+func SetPkgPathXlator(xlator func(path string) string) {
+	pkgPathXlator = xlator
+}
+
+// javaGenPkgPath returns the Java package path given the VDL package path.
+func javaGenPkgPath(vdlPkgPath string) string {
+	if pkgPathXlator == nil {
+		return vdlPkgPath
+	}
+	return pkgPathXlator(vdlPkgPath)
+}
+
+// JavaFileInfo stores the name and contents of the generated Java file.
+type JavaFileInfo struct {
+	Dir  string
+	Name string
+	Data []byte
+}
+
+// Generate generates Java files for all VDL files in the provided package,
+// returning the list of generated Java files as a slice.  Since Java requires
+// that each public class/interface gets defined in a separate file, this method
+// will return one generated file per struct.  (Interfaces actually generate
+// two files because we create separate interfaces for clients and servers.)
+// In addition, since Java doesn't support global variables (i.e., variables
+// defined outside of a class), all constants are moved into a special "Consts"
+// class and stored in a separate file.  All client bindings are stored in a
+// separate Client.java file. Finally, package documentation (if any) is stored
+// in a "package-info.java" file.
+//
+// TODO(spetrovic): Run Java formatters on the generated files.
+func Generate(pkg *compile.Package, env *compile.Env) (ret []JavaFileInfo) {
+	validateJavaConfig(pkg, env)
+	// One file for package documentation (if any).
+	if g := genJavaPackageFile(pkg, env); g != nil {
+		ret = append(ret, *g)
+	}
+	// Single file for all constants' definitions.
+	if g := genJavaConstFile(pkg, env); g != nil {
+		ret = append(ret, *g)
+	}
+	// Single file for all errors' definitions.
+	if g := genJavaErrorFile(pkg, env); g != nil {
+		ret = append(ret, *g)
+	}
+	for _, file := range pkg.Files {
+		// Separate file for all typedefs.
+		for _, tdef := range file.TypeDefs {
+			switch tdef.Type.Kind() {
+			case vdl.Array:
+				ret = append(ret, genJavaArrayFile(tdef, env))
+			case vdl.Complex64, vdl.Complex128:
+				ret = append(ret, genJavaComplexFile(tdef, env))
+			case vdl.Enum:
+				ret = append(ret, genJavaEnumFile(tdef, env))
+			case vdl.List:
+				ret = append(ret, genJavaListFile(tdef, env))
+			case vdl.Map:
+				ret = append(ret, genJavaMapFile(tdef, env))
+			case vdl.Union:
+				ret = append(ret, genJavaUnionFile(tdef, env))
+			case vdl.Set:
+				ret = append(ret, genJavaSetFile(tdef, env))
+			case vdl.Struct:
+				ret = append(ret, genJavaStructFile(tdef, env))
+			default:
+				ret = append(ret, genJavaPrimitiveFile(tdef, env))
+			}
+		}
+		// Separate file for all interface definitions.
+		for _, iface := range file.Interfaces {
+			ret = append(ret, genJavaClientFactoryFile(iface, env))
+			ret = append(ret, genJavaClientInterfaceFile(iface, env)) // client interface
+			ret = append(ret, genJavaClientImplFile(iface, env))
+			ret = append(ret, genJavaServerInterfaceFile(iface, env)) // server interface
+			ret = append(ret, genJavaServerWrapperFile(iface, env))
+		}
+	}
+	return
+}
+
+// The native types feature is hard to use correctly.  E.g. the wire type
+// must be statically registered in Java vdl package in order for the
+// wire<->native conversion to work, which is hard to ensure.
+//
+// Restrict the feature to these whitelisted VDL packages for now.
+var nativeTypePackageWhitelist = map[string]bool{
+	"time":                                   true,
+	"v.io/v23/security":                      true,
+	"v.io/v23/security/access":               true,
+	"v.io/x/ref/lib/vdl/testdata/nativetest": true,
+}
+
+func validateJavaConfig(pkg *compile.Package, env *compile.Env) {
+	vdlconfig := path.Join(pkg.GenPath, "vdl.config")
+	// Validate native type configuration.  Since native types are hard to use, we
+	// restrict them to a built-in whitelist of packages for now.
+	if len(pkg.Config.Java.WireToNativeTypes) > 0 && !nativeTypePackageWhitelist[pkg.Path] {
+		env.Errors.Errorf("%s: Java.WireToNativeTypes is restricted to whitelisted VDL packages", vdlconfig)
+	}
+}
diff --git a/lib/vdl/codegen/java/util.go b/lib/vdl/codegen/java/util.go
new file mode 100644
index 0000000..c379886
--- /dev/null
+++ b/lib/vdl/codegen/java/util.go
@@ -0,0 +1,21 @@
+// 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 java
+
+import (
+	"unicode"
+	"unicode/utf8"
+)
+
+// accessModifierForName returns the Java access modifier given the name.
+// It follows VDL naming conventions, indicating that an uppercase name
+// denotes a public type and a lowercase name a package-protected type.
+func accessModifierForName(name string) string {
+	r, _ := utf8.DecodeRuneInString(name)
+	if unicode.IsUpper(r) {
+		return "public"
+	}
+	return ""
+}
diff --git a/lib/vdl/codegen/java/util_args.go b/lib/vdl/codegen/java/util_args.go
new file mode 100644
index 0000000..226a738
--- /dev/null
+++ b/lib/vdl/codegen/java/util_args.go
@@ -0,0 +1,63 @@
+// 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 java
+
+import (
+	"bytes"
+	"fmt"
+
+	"v.io/x/ref/lib/vdl/compile"
+)
+
+// javaDeclarationArgStr creates a comma separated string of args to be used in a function declaration
+// e.g. "final int x, final Object o"
+func javaDeclarationArgStr(args []*compile.Field, env *compile.Env, leadingComma bool) string {
+	var buf bytes.Buffer
+	for i, arg := range args {
+		if leadingComma || i > 0 {
+			buf.WriteString(", ")
+		}
+		buf.WriteString("final ")
+		buf.WriteString(javaType(arg.Type, false, env))
+		buf.WriteString(" ")
+		if arg.Name != "" {
+			buf.WriteString(arg.Name)
+		} else {
+			buf.WriteString(fmt.Sprintf("arg%d", i+1))
+		}
+	}
+	return buf.String()
+}
+
+// javaCallingArgStr creates a comma separated string of arg to be used in calling a function
+// e.g. "x, o"
+func javaCallingArgStr(args []*compile.Field, leadingComma bool) string {
+	var buf bytes.Buffer
+	for i, arg := range args {
+		if leadingComma || i > 0 {
+			buf.WriteString(", ")
+		}
+		if arg.Name != "" {
+			buf.WriteString(arg.Name)
+		} else {
+			buf.WriteString(fmt.Sprintf("arg%d", i+1))
+		}
+	}
+	return buf.String()
+}
+
+// javaCallingArgTypeStr creates a comma separated string of arg types.
+func javaCallingArgTypeStr(args []*compile.Field, env *compile.Env) string {
+	var buf bytes.Buffer
+	for i, arg := range args {
+		if i > 0 {
+			buf.WriteString(", ")
+		}
+		buf.WriteString("new com.google.common.reflect.TypeToken<")
+		buf.WriteString(javaType(arg.Type, true, env))
+		buf.WriteString(">(){}.getType()")
+	}
+	return buf.String()
+}
diff --git a/lib/vdl/codegen/java/util_doc.go b/lib/vdl/codegen/java/util_doc.go
new file mode 100644
index 0000000..d84fb15
--- /dev/null
+++ b/lib/vdl/codegen/java/util_doc.go
@@ -0,0 +1,42 @@
+// 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 java
+
+import (
+	"bufio"
+	"strings"
+)
+
+// javaDoc transforms the provided VDL doc and a doc suffix into the JavaDoc format.
+func javaDoc(doc, suffix string) string {
+	if doc == "" && suffix == "" {
+		return ""
+	}
+	if doc == "" {
+		doc = suffix
+	} else if suffix != "" {
+		doc = doc + "\n" + suffix
+	}
+	ret := "/**\n"
+	reader := bufio.NewReader(strings.NewReader(doc))
+	for {
+		line, err := reader.ReadString('\n')
+		line = strings.TrimSpace(line)
+		if strings.HasPrefix(line, "//") {
+			ret += " *"
+			if len(line[2:]) == 0 { // empty line
+				ret += "<p>"
+			} else {
+				ret += line[2:]
+			}
+			ret += "\n"
+		}
+		if err != nil {
+			break
+		}
+	}
+	ret += "*/"
+	return ret
+}
diff --git a/lib/vdl/codegen/java/util_file.go b/lib/vdl/codegen/java/util_file.go
new file mode 100644
index 0000000..739ead6
--- /dev/null
+++ b/lib/vdl/codegen/java/util_file.go
@@ -0,0 +1,23 @@
+// 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 java
+
+import (
+	"bytes"
+
+	"v.io/x/ref/lib/vdl/compile"
+)
+
+// javaFileNames constructs a comma separated string with the short (basename) of the input files
+func javaFileNames(files []*compile.File) string {
+	var buf bytes.Buffer
+	for i, file := range files {
+		if i > 0 {
+			buf.WriteString(", ")
+		}
+		buf.WriteString(file.BaseName)
+	}
+	return buf.String()
+}
diff --git a/lib/vdl/codegen/java/util_interface.go b/lib/vdl/codegen/java/util_interface.go
new file mode 100644
index 0000000..6d3ffdf
--- /dev/null
+++ b/lib/vdl/codegen/java/util_interface.go
@@ -0,0 +1,72 @@
+// 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 java
+
+import (
+	"bytes"
+	"path"
+
+	"v.io/x/ref/lib/vdl/compile"
+	"v.io/x/ref/lib/vdl/vdlutil"
+)
+
+// allEmbeddedIfaces returns all unique interfaces in the embed tree
+// starting at the provided interface (not including that interface).
+func allEmbeddedIfaces(iface *compile.Interface) (ret []*compile.Interface) {
+	added := make(map[string]bool)
+	for _, eIface := range iface.Embeds {
+		for _, eIface = range append(allEmbeddedIfaces(eIface), eIface) {
+			path := path.Join(eIface.File.Package.GenPath, vdlutil.FirstRuneToUpper(eIface.Name))
+			if added[path] { // already added iface
+				continue
+			}
+			ret = append(ret, eIface)
+			added[path] = true
+		}
+	}
+	return
+}
+
+// interfaceFullyQualifiedName outputs the fully qualified name of an interface
+// e.g. "com.a.B"
+func interfaceFullyQualifiedName(iface *compile.Interface) string {
+	return path.Join(javaGenPkgPath(iface.File.Package.GenPath), vdlutil.FirstRuneToUpper(iface.Name))
+}
+
+// javaClientExtendsStr creates an extends clause for a client interface
+// e.g. "extends com.a.B, com.d.E"
+func javaClientExtendsStr(embeds []*compile.Interface) string {
+	if len(embeds) == 0 {
+		return ""
+	}
+	var buf bytes.Buffer
+	buf.WriteString("extends ")
+	for i, embed := range embeds {
+		if i > 0 {
+			buf.WriteString(", ")
+		}
+		buf.WriteString(javaPath(interfaceFullyQualifiedName(embed)))
+		buf.WriteString("Client")
+	}
+	return buf.String()
+}
+
+// javaServerExtendsStr creates an extends clause for a server interface
+// e.g. "extends com.a.B, com.d.E"
+func javaServerExtendsStr(embeds []*compile.Interface) string {
+	if len(embeds) == 0 {
+		return ""
+	}
+	var buf bytes.Buffer
+	buf.WriteString("extends ")
+	for i, embed := range embeds {
+		if i > 0 {
+			buf.WriteString(", ")
+		}
+		buf.WriteString(javaPath(interfaceFullyQualifiedName(embed)))
+		buf.WriteString("Server")
+	}
+	return buf.String()
+}
diff --git a/lib/vdl/codegen/java/util_method.go b/lib/vdl/codegen/java/util_method.go
new file mode 100644
index 0000000..40a5dc2
--- /dev/null
+++ b/lib/vdl/codegen/java/util_method.go
@@ -0,0 +1,96 @@
+// 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 java
+
+import (
+	"bytes"
+	"fmt"
+	"sort"
+
+	"v.io/x/ref/lib/vdl/compile"
+)
+
+func isStreamingMethod(method *compile.Method) bool {
+	return method.InStream != nil || method.OutStream != nil
+}
+
+// methodAndOrigin is simply a pair of a method and its origin (the interface from which it came from)
+// The usefulness of this pair of data is that methods can be defined on multiple interfaces and embeds can clash for the same method.
+// Therefore, we need to keep track of the originating interface.
+type methodAndOrigin struct {
+	Method *compile.Method
+	Origin *compile.Interface
+}
+
+// allMethodsAndOrigins constructs a list of all methods in an interface (including embeded interfaces) along with their corresponding origin interface.
+func allMethodsAndOrigin(iface *compile.Interface) []methodAndOrigin {
+	result := make([]methodAndOrigin, len(iface.Methods))
+	for i, method := range iface.Methods {
+		result[i] = methodAndOrigin{
+			Method: method,
+			Origin: iface,
+		}
+	}
+	for _, embed := range iface.Embeds {
+		result = append(result, allMethodsAndOrigin(embed)...)
+	}
+	return result
+}
+
+// dedupedEmbeddedMethodAndOrigins returns the set of methods only defined in embedded interfaces and dedupes methods with the same name.
+// This is used to generate a set of methods for a given service that are not (re)defined in the interface body (and instead only in embeddings).
+func dedupedEmbeddedMethodAndOrigins(iface *compile.Interface) []methodAndOrigin {
+	ifaceMethods := map[string]bool{}
+	for _, method := range iface.Methods {
+		ifaceMethods[method.Name] = true
+	}
+
+	embeddedMao := map[string]methodAndOrigin{}
+	for _, mao := range allMethodsAndOrigin(iface) {
+		if _, found := ifaceMethods[mao.Method.Name]; found {
+			continue
+		}
+		if _, found := embeddedMao[mao.Method.Name]; found {
+			continue
+		}
+		embeddedMao[mao.Method.Name] = mao
+	}
+
+	ret := []methodAndOrigin{}
+	for _, mao := range embeddedMao {
+		ret = append(ret, mao)
+	}
+	sort.Sort(bySignature(ret))
+
+	return ret
+}
+
+// bySignature implements sort.Interface
+type bySignature []methodAndOrigin
+
+func (b bySignature) Len() int {
+	return len(b)
+}
+
+func (b bySignature) Less(i, j int) bool {
+	return b.signature(i) < b.signature(j)
+}
+
+func (b bySignature) signature(i int) string {
+	var buf bytes.Buffer
+	buf.WriteString(fmt.Sprintf("%s|%s|%s",
+		b[i].Origin.File.Package.GenPath,
+		b[i].Origin.Name,
+		b[i].Method.Name,
+	))
+	for _, arg := range b[i].Method.InArgs {
+		buf.WriteString(fmt.Sprintf("|%s", arg.Type.Name()))
+	}
+	return buf.String()
+}
+
+func (b bySignature) Swap(i, j int) {
+	b[i], b[j] = b[j], b[i]
+}
diff --git a/lib/vdl/codegen/java/util_path.go b/lib/vdl/codegen/java/util_path.go
new file mode 100644
index 0000000..faa0460
--- /dev/null
+++ b/lib/vdl/codegen/java/util_path.go
@@ -0,0 +1,15 @@
+// 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 java
+
+import (
+	"strings"
+)
+
+// javaPath converts the provided Go path into the Java path.  It replaces all "/"
+// with "." in the path.
+func javaPath(goPath string) string {
+	return strings.Replace(goPath, "/", ".", -1)
+}
diff --git a/lib/vdl/codegen/java/util_template.go b/lib/vdl/codegen/java/util_template.go
new file mode 100644
index 0000000..5845aab
--- /dev/null
+++ b/lib/vdl/codegen/java/util_template.go
@@ -0,0 +1,24 @@
+// 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 java
+
+import (
+	"text/template"
+)
+
+var tmplCache = map[string]*template.Template{}
+
+// parseTmpl parses a template and caches the parsed value.
+// Each template body must be associated with a unique name.
+func parseTmpl(name string, body string) *template.Template {
+	if tmpl, ok := tmplCache[name]; ok {
+		return tmpl
+	}
+
+	tmpl := template.Must(template.New(name).Parse(body))
+
+	tmplCache[name] = tmpl
+	return tmpl
+}
diff --git a/lib/vdl/codegen/java/util_type.go b/lib/vdl/codegen/java/util_type.go
new file mode 100644
index 0000000..b369b2a
--- /dev/null
+++ b/lib/vdl/codegen/java/util_type.go
@@ -0,0 +1,224 @@
+// 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 java
+
+import (
+	"fmt"
+	"log"
+	"path"
+	"strings"
+
+	"v.io/v23/vdl"
+	"v.io/x/ref/lib/vdl/compile"
+	"v.io/x/ref/lib/vdl/vdlutil"
+)
+
+func javaFullyQualifiedNamedType(def *compile.TypeDef, forceClass bool, env *compile.Env) string {
+	if def.File == compile.BuiltInFile {
+		name, _ := javaBuiltInType(def.Type, forceClass)
+		return name
+	}
+	name, _ := javaTypeName(def, env)
+	return javaPath(path.Join(javaGenPkgPath(def.File.Package.GenPath), name))
+}
+
+// javaReflectType returns java.reflect.Type string for provided VDL type.
+func javaReflectType(t *vdl.Type, env *compile.Env) string {
+	return fmt.Sprintf("new com.google.common.reflect.TypeToken<%s>(){}.getType()", javaType(t, true, env))
+}
+
+// javaBuiltInType returns the type name for the provided built in type
+// definition, forcing the use of a java class (e.g., java.lang.Integer) if so
+// desired.  This method also returns a boolean value indicating whether the
+// returned type is a class.
+func javaBuiltInType(typ *vdl.Type, forceClass bool) (string, bool) {
+	if typ == nil {
+		if forceClass {
+			return "java.lang.Void", true
+		} else {
+			return "void", false
+		}
+	}
+	switch typ.Kind() {
+	case vdl.Bool:
+		if forceClass {
+			return "java.lang.Boolean", true
+		} else {
+			return "boolean", false
+		}
+	case vdl.Byte:
+		if forceClass {
+			return "java.lang.Byte", true
+		} else {
+			return "byte", false
+		}
+	case vdl.Uint16:
+		return "io.v.v23.vdl.VdlUint16", true
+	case vdl.Int16:
+		if forceClass {
+			return "java.lang.Short", true
+		} else {
+			return "short", false
+		}
+	case vdl.Uint32:
+		return "io.v.v23.vdl.VdlUint32", true
+	case vdl.Int32:
+		if forceClass {
+			return "java.lang.Integer", true
+		} else {
+			return "int", false
+		}
+	case vdl.Uint64:
+		return "io.v.v23.vdl.VdlUint64", true
+	case vdl.Int64:
+		if forceClass {
+			return "java.lang.Long", true
+		} else {
+			return "long", false
+		}
+	case vdl.Float32:
+		if forceClass {
+			return "java.lang.Float", true
+		} else {
+			return "float", false
+		}
+	case vdl.Float64:
+		if forceClass {
+			return "java.lang.Double", true
+		} else {
+			return "double", false
+		}
+	case vdl.Complex64:
+		return "io.v.v23.vdl.VdlComplex64", true
+	case vdl.Complex128:
+		return "io.v.v23.vdl.VdlComplex128", true
+	case vdl.String:
+		return "java.lang.String", true
+	case vdl.TypeObject:
+		return "io.v.v23.vdl.VdlTypeObject", true
+	case vdl.Any:
+		return "io.v.v23.vdl.VdlAny", true
+	default:
+		return "", false
+	}
+}
+
+func javaTypeName(def *compile.TypeDef, env *compile.Env) (string, string) {
+	if native, ok := def.File.Package.Config.Java.WireTypeRenames[def.Name]; ok {
+		return native, accessModifierForName(native)
+	}
+	return vdlutil.FirstRuneToUpper(def.Name), accessModifierForName(def.Name)
+}
+
+func javaNativeType(t *vdl.Type, env *compile.Env) (string, bool) {
+	if t == vdl.ErrorType {
+		return "io.v.v23.verror.VException", true
+	}
+	if def := env.FindTypeDef(t); def != nil {
+		pkg := def.File.Package
+		name, _ := javaTypeName(def, env)
+		if native, ok := pkg.Config.Java.WireToNativeTypes[name]; ok {
+			// There is a Java native type configured for this defined type.
+			return native, true
+		}
+	}
+	return "", false
+}
+
+func javaType(t *vdl.Type, forceClass bool, env *compile.Env) string {
+	if t == nil {
+		name, _ := javaBuiltInType(nil, forceClass)
+		return name
+	}
+	if native, ok := javaNativeType(t, env); ok {
+		return native
+	}
+	if def := env.FindTypeDef(t); def != nil {
+		return javaFullyQualifiedNamedType(def, forceClass, env)
+	}
+	switch t.Kind() {
+	case vdl.Array:
+		return fmt.Sprintf("%s[]", javaType(t.Elem(), false, env))
+	case vdl.List:
+		// NOTE(spetrovic): We represent byte lists as Java byte arrays, as it's doubtful anybody
+		// would want to use them as Java lists.
+		if javaType(t.Elem(), false, env) == "byte" {
+			return fmt.Sprintf("byte[]")
+		}
+		return fmt.Sprintf("%s<%s>", "java.util.List", javaType(t.Elem(), true, env))
+	case vdl.Set:
+		return fmt.Sprintf("%s<%s>", "java.util.Set", javaType(t.Key(), true, env))
+	case vdl.Map:
+		return fmt.Sprintf("%s<%s, %s>", "java.util.Map", javaType(t.Key(), true, env), javaType(t.Elem(), true, env))
+	case vdl.Optional:
+		return fmt.Sprintf("io.v.v23.vdl.VdlOptional<%s>", javaType(t.Elem(), true, env))
+	default:
+		log.Fatalf("vdl: javaType unhandled type %v %v", t.Kind(), t)
+		return ""
+	}
+}
+
+func javaVdlPrimitiveType(kind vdl.Kind) string {
+	switch kind {
+	case vdl.Bool, vdl.Byte, vdl.Uint16, vdl.Uint32, vdl.Uint64, vdl.Int16, vdl.Int32, vdl.Int64, vdl.Float32, vdl.Float64, vdl.Complex128, vdl.Complex64, vdl.String:
+		return "io.v.v23.vdl.Vdl" + vdlutil.FirstRuneToUpper(kind.String())
+	}
+	log.Fatalf("val: unhandled kind: %v", kind)
+	return ""
+}
+
+// javaHashCode returns the java code for the hashCode() computation for a given type.
+func javaHashCode(name string, ty *vdl.Type, env *compile.Env) string {
+	if isJavaNativeArray(ty, env) {
+		return fmt.Sprintf("java.util.Arrays.hashCode(%s)", name)
+	}
+	if def := env.FindTypeDef(ty); def != nil && def.File == compile.BuiltInFile {
+		switch ty.Kind() {
+		case vdl.Bool:
+			return fmt.Sprintf("java.lang.Boolean.valueOf(%s).hashCode()", name)
+		case vdl.Byte, vdl.Int16:
+			return "(int)" + name
+		case vdl.Int32:
+			return name
+		case vdl.Int64:
+			return fmt.Sprintf("java.lang.Long.valueOf(%s).hashCode()", name)
+		case vdl.Float32:
+			return fmt.Sprintf("java.lang.Float.valueOf(%s).hashCode()", name)
+		case vdl.Float64:
+			return fmt.Sprintf("java.lang.Double.valueOf(%s).hashCode()", name)
+		}
+	}
+	return fmt.Sprintf("(%s == null ? 0 : %s.hashCode())", name, name)
+}
+
+// isClass returns true iff the provided type is represented by a Java class.
+func isClass(t *vdl.Type, env *compile.Env) bool {
+	if t == nil { // void type
+		return false
+	}
+	if def := env.FindTypeDef(t); def != nil && def.File == compile.BuiltInFile {
+		// Built-in type.  See if it's represented by a class.
+		if tname, isClass := javaBuiltInType(t, false); tname != "" && !isClass {
+			return false
+		}
+	}
+	return true
+}
+
+// isJavaNativeArray returns true iff the provided type is represented by a Java array.
+func isJavaNativeArray(t *vdl.Type, env *compile.Env) bool {
+	typeStr := javaType(t, false, env)
+	return strings.HasSuffix(typeStr, "[]")
+}
+
+func bitlen(kind vdl.Kind) int {
+	switch kind {
+	case vdl.Float32, vdl.Complex64:
+		return 32
+	case vdl.Float64, vdl.Complex128:
+		return 64
+	}
+	panic(fmt.Errorf("vdl: bitLen unhandled kind %v", kind))
+}
diff --git a/lib/vdl/codegen/java/util_val.go b/lib/vdl/codegen/java/util_val.go
new file mode 100644
index 0000000..963a1fc
--- /dev/null
+++ b/lib/vdl/codegen/java/util_val.go
@@ -0,0 +1,204 @@
+// 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 java
+
+import (
+	"fmt"
+	"strconv"
+	"strings"
+
+	"v.io/v23/vdl"
+	"v.io/x/ref/lib/vdl/compile"
+)
+
+// javaConstVal returns the value string for the provided constant value.
+func javaConstVal(v *vdl.Value, env *compile.Env) (ret string) {
+	if v == nil {
+		return "null"
+	}
+	if v.IsZero() {
+		return javaZeroValue(v.Type(), env)
+	}
+
+	ret = javaVal(v, env)
+	switch v.Type().Kind() {
+	case vdl.Complex64, vdl.Complex128, vdl.Enum, vdl.Union, vdl.Uint16, vdl.Uint32, vdl.Uint64:
+		return
+	}
+	if def := env.FindTypeDef(v.Type()); def != nil && def.File != compile.BuiltInFile { // User-defined type.
+		ret = fmt.Sprintf("new %s(%s)", javaType(v.Type(), false, env), ret)
+	}
+	return
+}
+
+// javaVal returns the value string for the provided Value.
+func javaVal(v *vdl.Value, env *compile.Env) string {
+	const longSuffix = "L"
+	const floatSuffix = "f"
+
+	if v.Kind() == vdl.Array || (v.Kind() == vdl.List && v.Type().Elem().Kind() == vdl.Byte && v.Type().Name() == "") {
+		ret := fmt.Sprintf("new %s[] {", javaType(v.Type().Elem(), false, env))
+		for i := 0; i < v.Len(); i++ {
+			if i > 0 {
+				ret = ret + ", "
+			}
+			ret = ret + javaConstVal(v.Index(i), env)
+		}
+		return ret + "}"
+	}
+
+	switch v.Kind() {
+	case vdl.Bool:
+		if v.Bool() {
+			return "true"
+		} else {
+			return "false"
+		}
+	case vdl.Byte:
+		return "(byte)" + strconv.FormatUint(uint64(v.Byte()), 10)
+	case vdl.Uint16:
+		return fmt.Sprintf("new %s((short) %s)", javaType(v.Type(), true, env), strconv.FormatUint(v.Uint(), 10))
+	case vdl.Int16:
+		return "(short)" + strconv.FormatInt(v.Int(), 10)
+	case vdl.Uint32:
+		return fmt.Sprintf("new %s((int) %s)", javaType(v.Type(), true, env), strconv.FormatUint(v.Uint(), 10)+longSuffix)
+	case vdl.Int32:
+		return strconv.FormatInt(v.Int(), 10)
+	case vdl.Uint64:
+		return fmt.Sprintf("new %s(%s)", javaType(v.Type(), true, env), strconv.FormatInt(int64(v.Uint()), 10)+longSuffix)
+	case vdl.Int64:
+		return strconv.FormatInt(v.Int(), 10) + longSuffix
+	case vdl.Float32, vdl.Float64:
+		c := strconv.FormatFloat(v.Float(), 'g', -1, bitlen(v.Kind()))
+		if strings.Index(c, ".") == -1 {
+			c += ".0"
+		}
+		if v.Kind() == vdl.Float32 {
+			return c + floatSuffix
+		}
+		return c
+	case vdl.Complex64, vdl.Complex128:
+		r := strconv.FormatFloat(real(v.Complex()), 'g', -1, bitlen(v.Kind()))
+		i := strconv.FormatFloat(imag(v.Complex()), 'g', -1, bitlen(v.Kind()))
+		if v.Kind() == vdl.Complex64 {
+			r = r + "f"
+			i = i + "f"
+		}
+		return fmt.Sprintf("new %s(%s, %s)", javaType(v.Type(), true, env), r, i)
+	case vdl.String:
+		return strconv.Quote(v.RawString())
+	case vdl.Any:
+		if v.Elem() == nil {
+			return fmt.Sprintf("new %s()", javaType(v.Type(), false, env))
+		}
+		elemReflectTypeStr := javaReflectType(v.Elem().Type(), env)
+		elemStr := javaConstVal(v.Elem(), env)
+		return fmt.Sprintf("new %s(%s, %s)", javaType(v.Type(), false, env), elemReflectTypeStr, elemStr)
+	case vdl.Enum:
+		return fmt.Sprintf("%s.%s", javaType(v.Type(), false, env), v.EnumLabel())
+	case vdl.List:
+		elemTypeStr := javaType(v.Type().Elem(), true, env)
+		ret := fmt.Sprintf("new com.google.common.collect.ImmutableList.Builder<%s>()", elemTypeStr)
+		for i := 0; i < v.Len(); i++ {
+			ret = fmt.Sprintf("%s.add(%s)", ret, javaConstVal(v.Index(i), env))
+		}
+		return ret + ".build()"
+	case vdl.Map:
+		keyTypeStr := javaType(v.Type().Key(), true, env)
+		elemTypeStr := javaType(v.Type().Elem(), true, env)
+		ret := fmt.Sprintf("new com.google.common.collect.ImmutableMap.Builder<%s, %s>()", keyTypeStr, elemTypeStr)
+		for _, key := range vdl.SortValuesAsString(v.Keys()) {
+			keyStr := javaConstVal(key, env)
+			elemStr := javaConstVal(v.MapIndex(key), env)
+			ret = fmt.Sprintf("%s.put(%s, %s)", ret, keyStr, elemStr)
+		}
+		return ret + ".build()"
+	case vdl.Union:
+		index, value := v.UnionField()
+		name := v.Type().Field(index).Name
+		elemStr := javaConstVal(value, env)
+		return fmt.Sprintf("new %s.%s(%s)", javaType(v.Type(), false, env), name, elemStr)
+	case vdl.Set:
+		keyTypeStr := javaType(v.Type().Key(), true, env)
+		ret := fmt.Sprintf("new com.google.common.collect.ImmutableSet.Builder<%s>()", keyTypeStr)
+		for _, key := range vdl.SortValuesAsString(v.Keys()) {
+			ret = fmt.Sprintf("%s.add(%s)", ret, javaConstVal(key, env))
+		}
+		return ret + ".build()"
+	case vdl.Struct:
+		var ret string
+		for i := 0; i < v.Type().NumField(); i++ {
+			if i > 0 {
+				ret = ret + ", "
+			}
+			ret = ret + javaConstVal(v.StructField(i), env)
+		}
+		return ret
+	case vdl.TypeObject:
+		return fmt.Sprintf("new %s(%s)", javaType(v.Type(), false, env), javaReflectType(v.TypeObject(), env))
+	case vdl.Optional:
+		if v.Elem() != nil {
+			return fmt.Sprintf("io.v.v23.vdl.VdlOptional.of(%s)", javaConstVal(v.Elem(), env))
+		} else {
+			return fmt.Sprintf("new %s(%s)", javaType(v.Type(), false, env), javaReflectType(v.Type(), env))
+		}
+	}
+	panic(fmt.Errorf("vdl: javaVal unhandled type %v %v", v.Kind(), v.Type()))
+}
+
+// javaZeroValue returns the zero value string for the provided VDL value.
+// We assume that default constructor of user-defined types returns a zero value.
+func javaZeroValue(t *vdl.Type, env *compile.Env) string {
+	if _, ok := javaNativeType(t, env); ok {
+		return "null"
+	}
+
+	// First process user-defined types.
+	switch t.Kind() {
+	case vdl.Enum:
+		return fmt.Sprintf("%s.%s", javaType(t, false, env), t.EnumLabel(0))
+	case vdl.Union:
+		return fmt.Sprintf("new %s.%s()", javaType(t, false, env), t.Field(0).Name)
+	}
+	if def := env.FindTypeDef(t); def != nil && def.File != compile.BuiltInFile {
+		return fmt.Sprintf("new %s()", javaType(t, false, env))
+	}
+
+	// Arrays, enums, structs and unions can be user-defined only.
+	if t.Kind() == vdl.List && t.Elem().Kind() == vdl.Byte {
+		return fmt.Sprintf("new %s[]{}", javaType(t.Elem(), false, env))
+	}
+	switch t.Kind() {
+	case vdl.Bool:
+		return "false"
+	case vdl.Byte:
+		return "(byte) 0"
+	case vdl.Int16:
+		return "(short) 0"
+	case vdl.Int32:
+		return "0"
+	case vdl.Int64:
+		return "0L"
+	case vdl.Float32:
+		return "0.0f"
+	case vdl.Float64:
+		return "0.0"
+	case vdl.Any, vdl.Complex64, vdl.Complex128, vdl.TypeObject, vdl.Uint16, vdl.Uint32, vdl.Uint64:
+		return fmt.Sprintf("new %s()", javaType(t, false, env))
+	case vdl.String:
+		return "\"\""
+	case vdl.List:
+		return fmt.Sprintf("new java.util.ArrayList<%s>()", javaType(t.Elem(), true, env))
+	case vdl.Map:
+		keyTypeStr := javaType(t.Key(), true, env)
+		elemTypeStr := javaType(t.Elem(), true, env)
+		return fmt.Sprintf("new java.util.HashMap<%s, %s>()", keyTypeStr, elemTypeStr)
+	case vdl.Set:
+		return fmt.Sprintf("new java.util.HashSet<%s>()", javaType(t.Key(), true, env))
+	case vdl.Optional:
+		return fmt.Sprintf("new %s(%s)", javaType(t, false, env), javaReflectType(t, env))
+	}
+	panic(fmt.Errorf("vdl: javaZeroValue unhandled type %v", t))
+}
diff --git a/lib/vdl/codegen/javascript/const_test.go b/lib/vdl/codegen/javascript/const_test.go
new file mode 100644
index 0000000..1cc7c61
--- /dev/null
+++ b/lib/vdl/codegen/javascript/const_test.go
@@ -0,0 +1,59 @@
+// 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 javascript
+
+import (
+	"testing"
+
+	"v.io/v23/vdl"
+)
+
+func TestTypedConst(t *testing.T) {
+	names, structType, _, _, err := getTestTypes()
+	if err != nil {
+		t.Fatalf("Error in getTestTypes(): %v", err)
+	}
+	structValue := vdl.ZeroValue(structType)
+	_, index := structValue.Type().FieldByName(unnamedTypeFieldName)
+	structValue.StructField(index).AssignLen(1)
+	structValue.StructField(index).Index(0).AssignString("AStringVal")
+
+	tests := []struct {
+		name       string
+		inputValue *vdl.Value
+		expected   string
+	}{
+		{
+			name:       "struct test",
+			inputValue: structValue,
+			expected: `canonicalize.reduce(new (vdl.registry.lookupOrCreateConstructor(_typeNamedStruct))({
+  'list': [
+],
+  'bool': false,
+  'unnamedTypeField': [
+"AStringVal",
+],
+}, true), _typeNamedStruct)`,
+		},
+		{
+			name:       "bytes test",
+			inputValue: vdl.BytesValue([]byte{1, 2, 3, 4}),
+			expected: `canonicalize.reduce(new (vdl.registry.lookupOrCreateConstructor(_type2))(new Uint8Array([
+1,
+2,
+3,
+4,
+]), true), _type2)`,
+		},
+	}
+	for _, test := range tests {
+		strVal := typedConst(names, test.inputValue)
+		if strVal != test.expected {
+			t.Errorf("In %q, expected %q, but got %q", test.name, test.expected, strVal)
+		}
+	}
+}
+
+// TODO(bjornick) Add more thorough tests.
diff --git a/lib/vdl/codegen/javascript/error_test.go b/lib/vdl/codegen/javascript/error_test.go
new file mode 100644
index 0000000..36fecfd
--- /dev/null
+++ b/lib/vdl/codegen/javascript/error_test.go
@@ -0,0 +1,60 @@
+// 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 javascript
+
+import (
+	"testing"
+
+	"v.io/v23/i18n"
+	"v.io/v23/vdl"
+	"v.io/x/ref/lib/vdl/compile"
+)
+
+func TestError(t *testing.T) {
+	e := &compile.ErrorDef{
+		NamePos: compile.NamePos{
+			Name: "Test",
+		},
+		ID:        "v.io/x/ref/lib/vdl/codegen/javascript.Test",
+		RetryCode: vdl.WireRetryCodeNoRetry,
+		Params: []*compile.Field{
+			&compile.Field{
+				NamePos: compile.NamePos{
+					Name: "x",
+				},
+				Type: vdl.BoolType,
+			},
+			&compile.Field{
+				NamePos: compile.NamePos{
+					Name: "y",
+				},
+				Type: vdl.Int32Type,
+			},
+		},
+		Formats: []compile.LangFmt{
+			compile.LangFmt{
+				Lang: i18n.LangID("en-US"),
+				Fmt:  "english string",
+			},
+			compile.LangFmt{
+				Lang: i18n.LangID("fr"),
+				Fmt:  "french string",
+			},
+		},
+	}
+	var names typeNames
+	result := generateErrorConstructor(names, e)
+	expected := `module.exports.TestError = makeError('v.io/x/ref/lib/vdl/codegen/javascript.Test', actions.NO_RETRY, {
+  'en-US': 'english string',
+  'fr': 'french string',
+}, [
+  vdl.types.BOOL,
+  vdl.types.INT32,
+]);
+`
+	if result != expected {
+		t.Errorf("got %s, expected %s", result, expected)
+	}
+}
diff --git a/lib/vdl/codegen/javascript/errors.go b/lib/vdl/codegen/javascript/errors.go
new file mode 100644
index 0000000..57f0f10
--- /dev/null
+++ b/lib/vdl/codegen/javascript/errors.go
@@ -0,0 +1,26 @@
+// 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 javascript
+
+import (
+	"fmt"
+
+	"v.io/x/ref/lib/vdl/compile"
+	"v.io/x/ref/lib/vdl/vdlutil"
+)
+
+func generateErrorConstructor(names typeNames, e *compile.ErrorDef) string {
+	name := e.Name + "Error"
+	result := fmt.Sprintf("module.exports.%s = makeError('%s', actions.%s, ", name, e.ID, vdlutil.ToConstCase(e.RetryCode.String()))
+	result += "{\n"
+	for _, f := range e.Formats {
+		result += fmt.Sprintf("  '%s': '%s',\n", f.Lang, f.Fmt)
+	}
+	result += "}, [\n"
+	for _, param := range e.Params {
+		result += "  " + names.LookupType(param.Type) + ",\n"
+	}
+	return result + "]);\n"
+}
diff --git a/lib/vdl/codegen/javascript/gen.go b/lib/vdl/codegen/javascript/gen.go
new file mode 100644
index 0000000..bb86600
--- /dev/null
+++ b/lib/vdl/codegen/javascript/gen.go
@@ -0,0 +1,481 @@
+// 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 javascript implements Javascript code generation from compiled VDL packages.
+package javascript
+
+// Generates the javascript source code for vdl files.  The generated output in javascript
+// differs from most other languages, since we don't generate stubs. Instead generate an
+// object that contains the parsed VDL structures that will be used by the Javascript code
+// to valid servers.
+
+import (
+	"bytes"
+	"encoding/binary"
+	"fmt"
+	"path/filepath"
+	"strconv"
+	"strings"
+	"text/template"
+
+	"v.io/v23/vdl"
+	"v.io/x/ref/lib/vdl/compile"
+	"v.io/x/ref/lib/vdl/vdlutil"
+)
+
+type data struct {
+	Pkg            *compile.Package
+	Env            *compile.Env
+	GenerateImport func(string) string
+	UserImports    jsImports
+	PathToCoreJS   string
+	TypeNames      typeNames
+}
+
+// Generate takes a populated compile.Package and produces a byte slice
+// containing the generated Javascript code.
+func Generate(pkg *compile.Package, env *compile.Env, genImport func(string) string, pathToCoreJS string) []byte {
+	data := data{
+		Pkg:            pkg,
+		Env:            env,
+		GenerateImport: genImport,
+		UserImports:    jsImportsForFiles(pkg.Files...),
+		TypeNames:      newTypeNames(pkg),
+		PathToCoreJS:   pathToCoreJS,
+	}
+	var buf bytes.Buffer
+	if err := javascriptTemplate.Execute(&buf, data); err != nil {
+		// We shouldn't see an error; it means our template is buggy.
+		panic(fmt.Errorf("vdl: couldn't execute template: %v", err))
+	}
+	return buf.Bytes()
+}
+
+var javascriptTemplate *template.Template
+
+func bitlen(kind vdl.Kind) int {
+	switch kind {
+	case vdl.Float32, vdl.Complex64:
+		return 32
+	case vdl.Float64, vdl.Complex128:
+		return 64
+	}
+	panic(fmt.Errorf("vdl: bitLen unhandled kind %v", kind))
+}
+
+func genMethodTags(names typeNames, method *compile.Method) string {
+	tags := method.Tags
+	result := "["
+	for _, tag := range tags {
+		result += typedConst(names, tag) + ", "
+	}
+	result += "]"
+	return result
+}
+
+// Format the given int64 into a JS BigInt.
+func formatUint64BigInt(v uint64) string {
+	buffer := make([]byte, 8)
+	binary.BigEndian.PutUint64(buffer, v)
+	sign := "0"
+	if v > 0 {
+		sign = "1"
+	}
+	return fmt.Sprintf("new vdl.BigInt(%s, %s)", sign, formatByteBuffer(buffer))
+}
+
+// Format the given int64 into a JS BigInt.
+func formatInt64BigInt(v int64) string {
+	buffer := make([]byte, 8)
+	var sign int64 = 0
+	if v > 0 {
+		sign = 1
+	} else if v < 0 {
+		sign = -1
+	}
+	binary.BigEndian.PutUint64(buffer, uint64(v*sign)) // Adjust value by sign.
+
+	return fmt.Sprintf("new vdl.BigInt(%d, %s)", sign, formatByteBuffer(buffer))
+}
+
+// Given a buffer of bytes, create the JS Uint8Array that corresponds to it (to be used with BigInt).
+func formatByteBuffer(buffer []byte) string {
+	buf := bytes.TrimLeft(buffer, "\x00") // trim leading zeros
+	str := "new Uint8Array(["
+	for i, b := range buf {
+		if i > 0 {
+			str += ", "
+		}
+		str += fmt.Sprintf("%#x", b)
+	}
+	str += "])"
+	return str
+}
+
+// untypedConst generates a javascript string representing a constant that is
+// not wrapped with type information.
+func untypedConst(names typeNames, v *vdl.Value) string {
+	switch v.Kind() {
+	case vdl.Bool:
+		if v.Bool() {
+			return "true"
+		} else {
+			return "false"
+		}
+	case vdl.Byte:
+		return strconv.FormatUint(uint64(v.Byte()), 10)
+	case vdl.Uint16, vdl.Uint32:
+		return strconv.FormatUint(v.Uint(), 10)
+	case vdl.Int16, vdl.Int32:
+		return strconv.FormatInt(v.Int(), 10)
+	case vdl.Uint64:
+		return formatUint64BigInt(v.Uint())
+	case vdl.Int64:
+		return formatInt64BigInt(v.Int())
+	case vdl.Float32, vdl.Float64:
+		return strconv.FormatFloat(v.Float(), 'g', -1, bitlen(v.Kind()))
+	case vdl.String:
+		return strconv.Quote(v.RawString())
+	case vdl.Any:
+		if v.Elem() != nil {
+			return typedConst(names, v.Elem())
+		}
+		return "null"
+	case vdl.Optional:
+		if v.Elem() != nil {
+			return untypedConst(names, v.Elem())
+		}
+		return "null"
+	case vdl.Complex64, vdl.Complex128:
+		return fmt.Sprintf("new vdl.Complex(%f, %f)", real(v.Complex()), imag(v.Complex()))
+	case vdl.Enum:
+		return fmt.Sprintf("'%s'", v.EnumLabel())
+	case vdl.Array, vdl.List:
+		result := "["
+		for ix := 0; ix < v.Len(); ix++ {
+			val := untypedConst(names, v.Index(ix))
+			result += "\n" + val + ","
+		}
+		result += "\n]"
+		if v.Type().Elem().Kind() == vdl.Byte {
+			return "new Uint8Array(" + result + ")"
+		}
+		return result
+	case vdl.Set:
+		result := "new Set(["
+		for _, key := range vdl.SortValuesAsString(v.Keys()) {
+			result += "\n  " + untypedConst(names, key) + ", "
+		}
+		result += "])"
+		return result
+	case vdl.Map:
+		result := "new Map(["
+		for i, key := range vdl.SortValuesAsString(v.Keys()) {
+			if i > 0 {
+				result += ","
+			}
+			result += fmt.Sprintf("\n  [%s, %s]",
+				untypedConst(names, key),
+				untypedConst(names, v.MapIndex(key)))
+
+		}
+		result += "])"
+		return result
+	case vdl.Struct:
+		result := "{"
+		t := v.Type()
+		for ix := 0; ix < t.NumField(); ix++ {
+			result += "\n  '" +
+				vdlutil.FirstRuneToLower(t.Field(ix).Name) +
+				"': " +
+				untypedConst(names, v.StructField(ix)) +
+				","
+		}
+		return result + "\n}"
+	case vdl.Union:
+		ix, innerVal := v.UnionField()
+		return fmt.Sprintf("{ %q: %v }", vdlutil.FirstRuneToLower(v.Type().Field(ix).Name), untypedConst(names, innerVal))
+	case vdl.TypeObject:
+		return names.LookupType(v.TypeObject())
+	default:
+		panic(fmt.Errorf("vdl: untypedConst unhandled type %v %v", v.Kind(), v.Type()))
+	}
+}
+
+func primitiveWithOptionalName(primitive, name string) string {
+	if name == "" {
+		return "types." + primitive
+	}
+	return "new vdl.Type({kind: vdl.kind." + primitive + ", name: '" + name + "'})"
+}
+
+// typedConst returns a javascript string representing a const that is always
+// wrapped with type information
+func typedConst(names typeNames, v *vdl.Value) string {
+	switch v.Kind() {
+	case vdl.Any, vdl.TypeObject:
+		return untypedConst(names, v)
+	default:
+		// We call canonicalize.reduce so that we convert to native types
+		// The constructor would have done the reduction of the field values
+		// but it doesn't convert to native types.
+		return fmt.Sprintf("canonicalize.reduce(new %s(%s, true), %s)",
+			names.LookupConstructor(v.Type()),
+			untypedConst(names, v),
+			names.LookupType(v.Type()))
+	}
+}
+
+// Returns a Not Implemented stub for the method
+func generateMethodStub(method *compile.Method) string {
+	args := "ctx, serverCall"
+	for _, arg := range method.InArgs {
+		args += fmt.Sprintf(", %s", arg.Name)
+	}
+
+	return fmt.Sprintf(`function(%s) {
+  throw new Error('Method %s not implemented');
+}`, args, method.Name)
+}
+
+// Returns the JS version of the method signature.
+func generateMethodSignature(method *compile.Method, names typeNames) string {
+	return fmt.Sprintf(`{
+    name: '%s',
+    doc: %s,
+    inArgs: %s,
+    outArgs: %s,
+    inStream: %s,
+    outStream: %s,
+    tags: %s
+  }`,
+		method.Name,
+		quoteStripDoc(method.Doc),
+		generateMethodArguments(method.InArgs, names),
+		generateMethodArguments(method.OutArgs, names),
+		generateMethodStreaming(method.InStream, names),
+		generateMethodStreaming(method.OutStream, names),
+		genMethodTags(names, method))
+}
+
+// Returns a slice describing the method's arguments.
+func generateMethodArguments(args []*compile.Field, names typeNames) string {
+	ret := "["
+	for _, arg := range args {
+		ret += fmt.Sprintf(
+			`{
+      name: '%s',
+      doc: %s,
+      type: %s
+    },
+    `, arg.Name, quoteStripDoc(arg.Doc), names.LookupType(arg.Type))
+	}
+	ret += "]"
+	return ret
+}
+
+// Returns the VOM type of the stream.
+func generateMethodStreaming(streaming *vdl.Type, names typeNames) string {
+	if streaming == nil {
+		return "null"
+	}
+	return fmt.Sprintf(
+		`{
+      name: '',
+      doc: '',
+      type: %s
+    }`,
+		names.LookupType(streaming))
+}
+
+// Returns a slice of embeddings with the proper qualified identifiers.
+func generateEmbeds(embeds []*compile.Interface) string {
+	result := "["
+	for _, embed := range embeds {
+		result += fmt.Sprintf(`{
+      name: '%s',
+      pkgPath: '%s',
+      doc: %s
+    },
+    `, embed.Name, embed.File.Package.Path, quoteStripDoc(embed.Doc))
+	}
+	result += "]"
+	return result
+}
+
+func importPath(data data, path string) string {
+	// We need to prefix all of these paths with a ./ to tell node that the path is relative to
+	// the current directory.  Sadly filepath.Join(".", foo) == foo, so we have to do it
+	// explicitly.
+	return "." + string(filepath.Separator) + data.GenerateImport(path)
+}
+
+func quoteStripDoc(doc string) string {
+	// TODO(alexfandrianto): We need to handle '// ' and '\n' in the docstring.
+	// It would also be nice to single-quote the whole string.
+	trimmed := strings.Trim(doc, "\n")
+	return strconv.Quote(trimmed)
+}
+
+func hasErrors(pkg *compile.Package) bool {
+	for _, file := range pkg.Files {
+		if len(file.ErrorDefs) > 0 {
+			return true
+		}
+	}
+	return false
+}
+
+func hasConsts(pkg *compile.Package) bool {
+	for _, file := range pkg.Files {
+		if len(file.ConstDefs) > 0 {
+			return true
+		}
+	}
+	return false
+}
+
+func hasEnums(pkg *compile.Package) bool {
+	for _, file := range pkg.Files {
+		for _, def := range file.TypeDefs {
+			if def.Type.Kind() == vdl.Enum {
+				return true
+			}
+		}
+	}
+	return false
+}
+
+func hasMethodTags(pkg *compile.Package) bool {
+	for _, file := range pkg.Files {
+		for _, iface := range file.Interfaces {
+			for _, method := range iface.Methods {
+				if len(method.Tags) > 0 {
+					return true
+				}
+			}
+		}
+	}
+	return false
+}
+
+func generateSystemImports(data data) string {
+	res := "var vdl = require('"
+	packagePrefix := ""
+	if data.PathToCoreJS != "" {
+		packagePrefix = strings.Repeat("../", strings.Count(data.Pkg.Path, "/")+1) + data.PathToCoreJS
+	}
+	if data.PathToCoreJS != "" {
+		res += packagePrefix + "/vdl');"
+	} else {
+		res += "vanadium').vdl;"
+	}
+	res += "\n"
+	if hasErrors(data.Pkg) {
+		if data.PathToCoreJS != "" {
+			res += "var makeError = require('" + packagePrefix + "/verror/make-errors');\n"
+			res += "var actions = require('" + packagePrefix + "/verror/actions');\n"
+		} else {
+			res += "var makeError = require('vanadium').verror.makeError;\n"
+			res += "var actions = require('vanadium').verror.actions;\n"
+		}
+	}
+
+	if hasConsts(data.Pkg) || hasEnums(data.Pkg) || hasMethodTags(data.Pkg) {
+		if data.PathToCoreJS != "" {
+			res += "var canonicalize = require('" + packagePrefix + "/vdl/canonicalize');\n"
+		} else {
+			res += "var canonicalize = require('vanadium').vdl.canonicalize;\n"
+		}
+	}
+	return res
+}
+
+func init() {
+	funcMap := template.FuncMap{
+		"firstRuneToLower":          vdlutil.FirstRuneToLower,
+		"genMethodTags":             genMethodTags,
+		"makeTypeDefinitionsString": makeTypeDefinitionsString,
+		"typedConst":                typedConst,
+		"generateEmbeds":            generateEmbeds,
+		"generateMethodStub":        generateMethodStub,
+		"generateMethodSignature":   generateMethodSignature,
+		"importPath":                importPath,
+		"quoteStripDoc":             quoteStripDoc,
+		"generateErrorConstructor":  generateErrorConstructor,
+		"generateSystemImports":     generateSystemImports,
+	}
+	javascriptTemplate = template.Must(template.New("genJS").Funcs(funcMap).Parse(genJS))
+}
+
+// The template that we execute against a compile.Package instance to generate our
+// code.  Most of this is fairly straightforward substitution and ranges; more
+// complicated logic is delegated to the helper functions above.
+//
+// We try to generate code that has somewhat reasonable formatting.
+const genJS = `{{with $data := .}}{{$data.Pkg.FileDoc}}
+// This file was auto-generated by the vanadium vdl tool.
+{{generateSystemImports $data}}
+
+{{/* Define additional imported modules. */}}
+{{$pkg := $data.Pkg}}
+{{if $data.UserImports}}{{range $imp := $data.UserImports}}
+var {{$imp.Local}} = require('{{importPath $data $imp.Path}}');{{end}}{{end}}
+
+module.exports = {};
+
+
+{{/* Define any types introduced by the VDL file. */}}
+// Types:
+{{makeTypeDefinitionsString $data.TypeNames }}
+
+
+{{/* Define all constants as typed constants. */}}
+// Consts:
+{{range $file := $pkg.Files}}{{range $const := $file.ConstDefs}}
+  module.exports.{{$const.Name}} = {{typedConst $data.TypeNames $const.Value}};
+{{end}}{{end}}
+
+{{/* Define all errors. */}}
+// Errors:
+{{range $file := $pkg.Files}}{{range $error := $file.ErrorDefs}}
+{{generateErrorConstructor $data.TypeNames $error}}
+{{end}}{{end}}
+
+{{/* Define each of those service interfaces here, including method stubs and
+     service signature. */}}
+// Services:
+{{range $file := $pkg.Files}}
+  {{range $iface := $file.Interfaces}}
+    {{/* Define the service interface. */}}
+function {{$iface.Name}}(){}
+module.exports.{{$iface.Name}} = {{$iface.Name}};
+
+    {{range $method := $iface.AllMethods}}
+      {{/* Add each method to the service prototype. */}}
+{{$iface.Name}}.prototype.{{firstRuneToLower $method.Name}} = {{generateMethodStub $method}};
+    {{end}} {{/* end range $iface.AllMethods */}}
+
+    {{/* The service signature encodes the same info as signature.Interface.
+         TODO(alexfandrianto): We want to associate the signature type here, but
+         it's complicated.
+         For now, we need to pass the type in manually into encode. */}}
+{{$iface.Name}}.prototype._serviceDescription = {
+  name: '{{$iface.Name}}',
+  pkgPath: '{{$pkg.Path}}',
+  doc: {{quoteStripDoc $iface.Doc}},
+  embeds: {{generateEmbeds $iface.Embeds}},
+  methods: [
+    {{range $method := $iface.AllMethods}}
+      {{/* Each method signature contains the information in rpc.MethodSig. */}}
+    {{generateMethodSignature $method $data.TypeNames}},
+    {{end}} {{/*end range $iface.AllMethods*/}}
+  ]
+};
+
+  {{end}} {{/* end range $files.Interfaces */}}
+{{end}} {{/* end range $pkg.Files */}}
+
+
+{{end}}`
diff --git a/lib/vdl/codegen/javascript/gen_type_def.go b/lib/vdl/codegen/javascript/gen_type_def.go
new file mode 100644
index 0000000..19c60bf
--- /dev/null
+++ b/lib/vdl/codegen/javascript/gen_type_def.go
@@ -0,0 +1,244 @@
+// 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 javascript
+
+import (
+	"fmt"
+
+	"v.io/v23/vdl"
+	"v.io/x/ref/lib/vdl/vdlutil"
+)
+
+// makeTypeDefinitionsString generates a string that defines the specified types.
+// It consists of the following sections:
+// - Definitions. e.g. "var _typeNamedBool = new Type();"
+// - Field assignments. e.g. "_typeNamedBool.name = \"NamedBool\";"
+// - Type Freezes, e.g. "_typedNamedBool.freeze();"
+// - Constructor definitions. e.g. "types.NamedBool = Registry.lookupOrCreateConstructor(_typeNamedBool)"
+func makeTypeDefinitionsString(jsnames typeNames) string {
+	str := ""
+	sortedDefs := jsnames.SortedList()
+
+	for _, def := range sortedDefs {
+		str += makeDefString(def.Name)
+	}
+
+	for _, def := range sortedDefs {
+		str += makeTypeFieldAssignmentString(def.Name, def.Type, jsnames)
+	}
+
+	for _, def := range sortedDefs {
+		str += makeTypeFreezeString(def.Name)
+	}
+
+	for _, def := range sortedDefs {
+		if def.Type.Name() != "" {
+			if def.Type.Kind() == vdl.Enum {
+				str += makeEnumLabelString(def.Type, jsnames)
+			} else {
+				str += makeConstructorDefinitionString(def.Type, jsnames)
+			}
+		}
+	}
+
+	return str
+}
+
+// makeDefString generates a type definition for the specified type name.
+// e.g. "var _typeNamedBool = new Type();"
+func makeDefString(jsname string) string {
+	return fmt.Sprintf("var %s = new vdl.Type();\n", jsname)
+}
+
+// makeTypeFreezeString calls the type's freeze function to finalize it.
+// e.g. "typeNamedBool.freeze();"
+func makeTypeFreezeString(jsname string) string {
+	return fmt.Sprintf("%s.freeze();\n", jsname)
+}
+
+// makeTypeFieldAssignmentString generates assignments for type fields.
+// e.g. "_typeNamedBool.name = \"NamedBool\";"
+func makeTypeFieldAssignmentString(jsname string, t *vdl.Type, jsnames typeNames) string {
+	// kind
+	str := fmt.Sprintf("%s.kind = %s;\n", jsname, jsKind(t.Kind()))
+
+	// name
+	str += fmt.Sprintf("%s.name = %q;\n", jsname, t.Name())
+
+	// labels
+	if t.Kind() == vdl.Enum {
+		str += fmt.Sprintf("%s.labels = [", jsname)
+		for i := 0; i < t.NumEnumLabel(); i++ {
+			if i > 0 {
+				str += ", "
+			}
+			str += fmt.Sprintf("%q", t.EnumLabel(i))
+		}
+		str += "];\n"
+	}
+
+	// len
+	if t.Kind() == vdl.Array { // Array is the only type where len is valid.
+		str += fmt.Sprintf("%s.len = %d;\n", jsname, t.Len())
+	}
+
+	// elem
+	switch t.Kind() {
+	case vdl.Optional, vdl.Array, vdl.List, vdl.Map:
+		str += fmt.Sprintf("%s.elem = %s;\n", jsname, jsnames.LookupType(t.Elem()))
+	}
+
+	// key
+	switch t.Kind() {
+	case vdl.Set, vdl.Map:
+		str += fmt.Sprintf("%s.key = %s;\n", jsname, jsnames.LookupType(t.Key()))
+	}
+
+	// fields
+	switch t.Kind() {
+	case vdl.Struct, vdl.Union:
+		str += fmt.Sprintf("%s.fields = [", jsname)
+		for i := 0; i < t.NumField(); i++ {
+			if i > 0 {
+				str += ", "
+			}
+			field := t.Field(i)
+			str += fmt.Sprintf("{name: %q, type: %s}", field.Name, jsnames.LookupType(field.Type))
+		}
+		str += "];\n"
+	}
+
+	return str
+}
+
+// makeConstructorDefinitionString creates a string that defines the constructor for the type.
+// e.g. "module.exports.NamedBool = Registry.lookupOrCreateConstructor(_typeNamedBool)"
+func makeConstructorDefinitionString(t *vdl.Type, jsnames typeNames) string {
+	_, name := vdl.SplitIdent(t.Name())
+	ctorName := jsnames.LookupConstructor(t)
+	return fmt.Sprintf("module.exports.%s = %s;\n", name, ctorName)
+}
+
+// makeEnumLabelString creates a string that defines the labels in an enum.
+// e.g. `module.Exports.MyEnum = {
+//   ALabel: (Registry.lookupOrCreateConstructor(_typeMyEnum))("ALabel"),
+//   BLabel: (Registry.lookupOrCreateConstructor(_typeMyEnum))("BLabel"),
+// }`
+
+func makeEnumLabelString(t *vdl.Type, jsnames typeNames) string {
+	_, name := vdl.SplitIdent(t.Name())
+	str := fmt.Sprintf("module.exports.%s = {\n", name)
+	for i := 0; i < t.NumEnumLabel(); i++ {
+		enumVal := vdl.ZeroValue(t)
+		enumVal.AssignEnumIndex(i)
+		str += fmt.Sprintf("  %s: %s,\n", vdlutil.ToConstCase(t.EnumLabel(i)), typedConst(jsnames, enumVal))
+	}
+	str += "};\n"
+	return str
+}
+
+func jsKind(k vdl.Kind) string {
+	switch k {
+	case vdl.Any:
+		return "vdl.kind.ANY"
+	case vdl.Union:
+		return "vdl.kind.UNION"
+	case vdl.Optional:
+		return "vdl.kind.OPTIONAL"
+	case vdl.Bool:
+		return "vdl.kind.BOOL"
+	case vdl.Byte:
+		return "vdl.kind.BYTE"
+	case vdl.Uint16:
+		return "vdl.kind.UINT16"
+	case vdl.Uint32:
+		return "vdl.kind.UINT32"
+	case vdl.Uint64:
+		return "vdl.kind.UINT64"
+	case vdl.Int16:
+		return "vdl.kind.INT16"
+	case vdl.Int32:
+		return "vdl.kind.INT32"
+	case vdl.Int64:
+		return "vdl.kind.INT64"
+	case vdl.Float32:
+		return "vdl.kind.FLOAT32"
+	case vdl.Float64:
+		return "vdl.kind.FLOAT64"
+	case vdl.Complex64:
+		return "vdl.kind.COMPLEX64"
+	case vdl.Complex128:
+		return "vdl.kind.COMPLEX128"
+	case vdl.String:
+		return "vdl.kind.STRING"
+	case vdl.Enum:
+		return "vdl.kind.ENUM"
+	case vdl.TypeObject:
+		return "vdl.kind.TYPEOBJECT"
+	case vdl.Array:
+		return "vdl.kind.ARRAY"
+	case vdl.List:
+		return "vdl.kind.LIST"
+	case vdl.Set:
+		return "vdl.kind.SET"
+	case vdl.Map:
+		return "vdl.kind.MAP"
+	case vdl.Struct:
+		return "vdl.kind.STRUCT"
+	}
+	panic(fmt.Errorf("val: unhandled kind: %d", k))
+}
+
+// builtinJSType indicates whether a vdl.Type has built-in type definition in vdl.js
+// If true, then it returns a pointer to the type definition in javascript/types.js
+// It assumes a variable named "vdl.Types" is already pointing to vom.Types
+func builtinJSType(t *vdl.Type) (string, bool) {
+	_, n := vdl.SplitIdent(t.Name())
+
+	if t == vdl.ErrorType {
+		return "vdl.types.ERROR", true
+	}
+
+	// named types are not built-in.
+	if n != "" {
+		return "", false
+	}
+
+	// switch on supported types in vdl.js
+	switch t.Kind() {
+	case vdl.Any:
+		return "vdl.types.ANY", true
+	case vdl.Bool:
+		return "vdl.types.BOOL", true
+	case vdl.Byte:
+		return "vdl.types.BYTE", true
+	case vdl.Uint16:
+		return "vdl.types.UINT16", true
+	case vdl.Uint32:
+		return "vdl.types.UINT32", true
+	case vdl.Uint64:
+		return "vdl.types.UINT64", true
+	case vdl.Int16:
+		return "vdl.types.INT16", true
+	case vdl.Int32:
+		return "vdl.types.INT32", true
+	case vdl.Int64:
+		return "vdl.types.INT64", true
+	case vdl.Float32:
+		return "vdl.types.FLOAT32", true
+	case vdl.Float64:
+		return "vdl.types.FLOAT64", true
+	case vdl.Complex64:
+		return "vdl.types.COMPLEX64", true
+	case vdl.Complex128:
+		return "vdl.types.COMPLEX128", true
+	case vdl.String:
+		return "vdl.types.STRING", true
+	case vdl.TypeObject:
+		return "vdl.types.TYPEOBJECT", true
+	}
+
+	return "", false
+}
diff --git a/lib/vdl/codegen/javascript/import.go b/lib/vdl/codegen/javascript/import.go
new file mode 100644
index 0000000..a4c3488
--- /dev/null
+++ b/lib/vdl/codegen/javascript/import.go
@@ -0,0 +1,105 @@
+// 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 javascript
+
+import (
+	"sort"
+	"strconv"
+
+	"v.io/v23/vdl"
+	"v.io/x/lib/set"
+	"v.io/x/ref/lib/vdl/compile"
+)
+
+// TODO(bjornick): Merge with pkg_types.go
+
+// jsImport represents a single package import.
+type jsImport struct {
+	Path string // Path of the imported package; e.g. "v.io/x/ref/lib/vdl/testdata/arith"
+
+	// Local name that refers to the imported package; either the non-empty import
+	// name, or the name of the imported package.
+	Local string
+}
+
+// jsImports is a collection of package imports.
+// REQUIRED: The imports must be sorted by path.
+type jsImports []jsImport
+
+// LookupLocal returns the local name that identifies the given pkgPath.
+func (x jsImports) LookupLocal(pkgPath string) string {
+	ix := sort.Search(len(x), func(i int) bool { return x[i].Path >= pkgPath })
+	if ix < len(x) && x[ix].Path == pkgPath {
+		return x[ix].Local
+	}
+	return ""
+}
+
+// Each import must end up with a unique local name - when we see a collision we
+// simply add a "_N" suffix where N starts at 2 and increments.
+func uniqueImport(pkgName, pkgPath string, seen map[string]bool) jsImport {
+	iter := 1
+	for {
+		local := pkgName
+		if iter > 1 {
+			local += "_" + strconv.Itoa(iter)
+		}
+		if !seen[local] {
+			// Found a unique local name - return the import.
+			seen[local] = true
+			return jsImport{pkgPath, local}
+		}
+		iter++
+	}
+}
+
+type pkgSorter []*compile.Package
+
+func (s pkgSorter) Len() int { return len(s) }
+
+func (s pkgSorter) Less(i, j int) bool { return s[i].Path < s[j].Path }
+
+func (s pkgSorter) Swap(i, j int) { s[j], s[i] = s[i], s[j] }
+
+// jsImportsForFiles returns the imports required for the given files.
+func jsImportsForFiles(files ...*compile.File) jsImports {
+	seenPath := make(map[string]bool)
+	pkgs := pkgSorter{}
+
+	for _, f := range files {
+		// TODO(toddw,bjornick): Remove File.PackageDeps and replace by walking through each type.
+		for _, dep := range f.PackageDeps {
+			if seenPath[dep.Path] {
+				continue
+			}
+			seenPath[dep.Path] = true
+			pkgs = append(pkgs, dep)
+		}
+	}
+	sort.Sort(pkgs)
+
+	var ret jsImports
+	seenName := make(map[string]bool)
+	for _, dep := range pkgs {
+		ret = append(ret, uniqueImport(dep.Name, dep.GenPath, seenName))
+	}
+	return ret
+}
+
+// pkgDeps maintains a set of package path dependencies.
+type pkgDeps map[string]bool
+
+func (deps pkgDeps) insertIdent(ident string) {
+	if pkgPath, _ := vdl.SplitIdent(ident); pkgPath != "" {
+		deps[pkgPath] = true
+	}
+}
+
+// SortedPkgPaths deps as a sorted slice.
+func (deps pkgDeps) SortedPkgPaths() []string {
+	ret := set.StringBool.ToSlice(deps)
+	sort.Strings(ret)
+	return ret
+}
diff --git a/lib/vdl/codegen/javascript/pkg_types.go b/lib/vdl/codegen/javascript/pkg_types.go
new file mode 100644
index 0000000..b2dc8fd
--- /dev/null
+++ b/lib/vdl/codegen/javascript/pkg_types.go
@@ -0,0 +1,230 @@
+// 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 javascript
+
+import (
+	"fmt"
+	"sort"
+	"strings"
+
+	"v.io/x/ref/lib/vdl/compile"
+	"v.io/x/ref/lib/vdl/vdlutil"
+
+	"v.io/v23/vdl"
+)
+
+// typeNames holds a mapping between VDL type and generated type name.
+type typeNames map[*vdl.Type]string
+
+// LookupConstructor returns a string representing the constructor of the type.
+// Several cases:
+// - Local package type (and has been added to tn), return
+// Registry.lookupOrCreateConstructor(_typeNameHere)
+// - Builtin
+// This is not supported. Fail.
+// - Type in other package
+// Return pkgName.ConstructorName
+func (tn typeNames) LookupConstructor(t *vdl.Type) string {
+	if builtInName, ok := builtinJSType(t); ok {
+		return tn.constructorFromTypeName(builtInName)
+	}
+
+	if name, ok := tn[t]; ok {
+		return tn.constructorFromTypeName(name)
+	}
+
+	pkgPath, name := vdl.SplitIdent(t.Name())
+	pkgParts := strings.Split(pkgPath, "/")
+	pkgName := pkgParts[len(pkgParts)-1]
+	return fmt.Sprintf("%s.%s", pkgName, name)
+}
+
+func (tn typeNames) constructorFromTypeName(name string) string {
+	return "(vdl.registry.lookupOrCreateConstructor(" + name + "))"
+}
+
+// LookupType returns a string representing the type.
+// - If it is a built in type, return the name.
+// - Otherwise get type type from the constructor.
+func (tn typeNames) LookupType(t *vdl.Type) string {
+	if builtInName, ok := builtinJSType(t); ok {
+		return builtInName
+	}
+
+	if name, ok := tn[t]; ok {
+		return name
+	}
+
+	if t.Kind() == vdl.Enum {
+		return fmt.Sprintf("%s.%s._type", tn.LookupConstructor(t), vdlutil.ToConstCase(t.EnumLabel(0)))
+	}
+
+	return "new " + tn.LookupConstructor(t) + "()._type"
+}
+
+// SortedList returns a list of type and name pairs, sorted by name.
+// This is needed to make the output stable.
+func (tn typeNames) SortedList() typeNamePairList {
+	pairs := typeNamePairList{}
+	for t, name := range tn {
+		pairs = append(pairs, typeNamePair{t, name})
+	}
+	sort.Sort(pairs)
+	return pairs
+}
+
+type typeNamePairList []typeNamePair
+type typeNamePair struct {
+	Type *vdl.Type
+	Name string
+}
+
+func (l typeNamePairList) Len() int           { return len(l) }
+func (l typeNamePairList) Less(i, j int) bool { return l[i].Name < l[j].Name }
+func (l typeNamePairList) Swap(i, j int)      { l[i], l[j] = l[j], l[i] }
+
+// newTypeNames generates typeNames for all new types in a package.
+func newTypeNames(pkg *compile.Package) typeNames {
+	ptn := pkgTypeNames{
+		nextIndex: 1,
+		names:     typeNames{},
+		pkg:       pkg,
+	}
+	return ptn.getNames()
+}
+
+// pkgTypeNames tracks information necessary to define JS types from VDL.
+// The next index that a new type will be auto-generated with previously auto-generated type names
+// package being generated
+type pkgTypeNames struct {
+	nextIndex int
+	names     typeNames
+	pkg       *compile.Package
+}
+
+// getNames generates typeNames for all new types in a package.
+func (p pkgTypeNames) getNames() typeNames {
+	for _, file := range p.pkg.Files {
+		for _, def := range file.TypeDefs {
+			p.addInnerTypes(def.Type)
+		}
+		for _, constdef := range file.ConstDefs {
+			p.addInnerTypes(constdef.Value.Type())
+			p.addTypesInConst(constdef.Value)
+		}
+		for _, errordef := range file.ErrorDefs {
+			for _, field := range errordef.Params {
+				p.addInnerTypes(field.Type)
+			}
+		}
+		for _, interfacedef := range file.Interfaces {
+			for _, method := range interfacedef.AllMethods() {
+				for _, inarg := range method.InArgs {
+					p.addInnerTypes(inarg.Type)
+				}
+				for _, outarg := range method.OutArgs {
+					p.addInnerTypes(outarg.Type)
+				}
+				if method.InStream != nil {
+					p.addInnerTypes(method.InStream)
+				}
+				if method.OutStream != nil {
+					p.addInnerTypes(method.OutStream)
+				}
+			}
+		}
+	}
+
+	return p.names
+}
+
+// addName generates a new name for t if necessary.  Returns true if a new name
+// was generated, and thus names for inner-types should also be generated.
+func (p *pkgTypeNames) addName(t *vdl.Type) bool {
+	// Name has already been generated.
+	if _, ok := p.names[t]; ok {
+		return false
+	}
+
+	// Do not create name for built-in JS types (primitives, any, etc..)
+	if _, ok := builtinJSType(t); ok {
+		return false
+	}
+
+	var name string
+	if t.Name() != "" {
+		pp, n := vdl.SplitIdent(t.Name())
+		// Do not create name for types from other packages.
+		if pp != p.pkg.Path {
+			return false
+		}
+		name = "_type" + n
+	} else {
+		name = fmt.Sprintf("_type%d", p.nextIndex)
+		p.nextIndex++
+	}
+	p.names[t] = name
+	return true
+}
+
+func (p *pkgTypeNames) addInnerTypes(t *vdl.Type) {
+	if !p.addName(t) {
+		return
+	}
+	switch t.Kind() {
+	case vdl.Optional, vdl.Array, vdl.List:
+		p.addInnerTypes(t.Elem())
+	case vdl.Set:
+		p.addInnerTypes(t.Key())
+	case vdl.Map:
+		p.addInnerTypes(t.Key())
+		p.addInnerTypes(t.Elem())
+	case vdl.Struct, vdl.Union:
+		for i := 0; i < t.NumField(); i++ {
+			p.addInnerTypes(t.Field(i).Type)
+		}
+	}
+}
+
+func (p *pkgTypeNames) addTypesInConst(v *vdl.Value) {
+	// Generate the type if it is a typeobject or any.
+	switch v.Kind() {
+	case vdl.TypeObject:
+		p.addInnerTypes(v.TypeObject())
+	case vdl.Any:
+		if !v.IsNil() {
+			p.addInnerTypes(v.Elem().Type())
+		}
+	}
+
+	// Recurse.
+	switch v.Kind() {
+	case vdl.List, vdl.Array:
+		for i := 0; i < v.Len(); i++ {
+			p.addTypesInConst(v.Index(i))
+		}
+	case vdl.Set:
+		for _, key := range vdl.SortValuesAsString(v.Keys()) {
+			p.addTypesInConst(key)
+		}
+	case vdl.Map:
+		for _, key := range vdl.SortValuesAsString(v.Keys()) {
+			p.addTypesInConst(key)
+			p.addTypesInConst(v.MapIndex(key))
+
+		}
+	case vdl.Struct:
+		for i := 0; i < v.Type().NumField(); i++ {
+			p.addTypesInConst(v.StructField(i))
+		}
+	case vdl.Union:
+		_, innerVal := v.UnionField()
+		p.addTypesInConst(innerVal)
+	case vdl.Any, vdl.Optional:
+		if !v.IsNil() {
+			p.addTypesInConst(v.Elem())
+		}
+	}
+}
diff --git a/lib/vdl/codegen/javascript/type_test.go b/lib/vdl/codegen/javascript/type_test.go
new file mode 100644
index 0000000..4191ca1
--- /dev/null
+++ b/lib/vdl/codegen/javascript/type_test.go
@@ -0,0 +1,116 @@
+// 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 javascript
+
+import (
+	"fmt"
+	"testing"
+
+	"v.io/v23/vdl"
+	"v.io/x/ref/lib/vdl/compile"
+)
+
+const unnamedTypeFieldName = "UnnamedTypeField"
+
+func getTestTypes() (names typeNames, tyStruct, tyList, tyBool *vdl.Type, outErr error) {
+	var builder vdl.TypeBuilder
+	namedBool := builder.Named("otherPkg.NamedBool").AssignBase(vdl.BoolType)
+	listType := builder.List()
+	namedList := builder.Named("NamedList").AssignBase(listType)
+	structType := builder.Struct()
+	namedStruct := builder.Named("NamedStruct").AssignBase(structType)
+	structType.AppendField("List", namedList)
+	structType.AppendField("Bool", namedBool)
+	structType.AppendField(unnamedTypeFieldName, builder.List().AssignElem(vdl.StringType))
+	listType.AssignElem(namedStruct)
+	builder.Build()
+
+	builtBool, err := namedBool.Built()
+	if err != nil {
+		outErr = fmt.Errorf("Error creating NamedBool: %v", err)
+		return
+	}
+
+	builtList, err := namedList.Built()
+	if err != nil {
+		outErr = fmt.Errorf("Error creating NamedList %v", err)
+		return
+	}
+
+	builtStruct, err := namedStruct.Built()
+	if err != nil {
+		outErr = fmt.Errorf("Error creating NamedStruct: %v", err)
+		return
+	}
+
+	pkg := &compile.Package{
+		Files: []*compile.File{
+			&compile.File{
+				TypeDefs: []*compile.TypeDef{
+					{
+						Type: builtList,
+					},
+					{
+						Type: builtStruct,
+					},
+					{
+						Type: vdl.ListType(vdl.ByteType),
+					},
+					{
+						Type: vdl.NamedType("ColorsBeginningWithAOrB", vdl.EnumType("Aqua", "Beige")),
+					},
+				},
+			},
+		},
+	}
+
+	return newTypeNames(pkg), builtStruct, builtList, builtBool, nil
+}
+
+// TestType tests that the output string of generated types is what we expect.
+func TestType(t *testing.T) {
+	jsnames, _, _, _, err := getTestTypes()
+	if err != nil {
+		t.Fatalf("Error in getTestTypes(): %v", err)
+	}
+	result := makeTypeDefinitionsString(jsnames)
+
+	expectedResult := `var _type1 = new vdl.Type();
+var _type2 = new vdl.Type();
+var _typeColorsBeginningWithAOrB = new vdl.Type();
+var _typeNamedList = new vdl.Type();
+var _typeNamedStruct = new vdl.Type();
+_type1.kind = vdl.kind.LIST;
+_type1.name = "";
+_type1.elem = vdl.types.STRING;
+_type2.kind = vdl.kind.LIST;
+_type2.name = "";
+_type2.elem = vdl.types.BYTE;
+_typeColorsBeginningWithAOrB.kind = vdl.kind.ENUM;
+_typeColorsBeginningWithAOrB.name = "ColorsBeginningWithAOrB";
+_typeColorsBeginningWithAOrB.labels = ["Aqua", "Beige"];
+_typeNamedList.kind = vdl.kind.LIST;
+_typeNamedList.name = "NamedList";
+_typeNamedList.elem = _typeNamedStruct;
+_typeNamedStruct.kind = vdl.kind.STRUCT;
+_typeNamedStruct.name = "NamedStruct";
+_typeNamedStruct.fields = [{name: "List", type: _typeNamedList}, {name: "Bool", type: new otherPkg.NamedBool()._type}, {name: "UnnamedTypeField", type: _type1}];
+_type1.freeze();
+_type2.freeze();
+_typeColorsBeginningWithAOrB.freeze();
+_typeNamedList.freeze();
+_typeNamedStruct.freeze();
+module.exports.ColorsBeginningWithAOrB = {
+  AQUA: canonicalize.reduce(new (vdl.registry.lookupOrCreateConstructor(_typeColorsBeginningWithAOrB))('Aqua', true), _typeColorsBeginningWithAOrB),
+  BEIGE: canonicalize.reduce(new (vdl.registry.lookupOrCreateConstructor(_typeColorsBeginningWithAOrB))('Beige', true), _typeColorsBeginningWithAOrB),
+};
+module.exports.NamedList = (vdl.registry.lookupOrCreateConstructor(_typeNamedList));
+module.exports.NamedStruct = (vdl.registry.lookupOrCreateConstructor(_typeNamedStruct));
+`
+
+	if result != expectedResult {
+		t.Errorf("Expected %q, but got %q", expectedResult, result)
+	}
+}
diff --git a/lib/vdl/codegen/vdlgen/const.go b/lib/vdl/codegen/vdlgen/const.go
new file mode 100644
index 0000000..0f75fad
--- /dev/null
+++ b/lib/vdl/codegen/vdlgen/const.go
@@ -0,0 +1,152 @@
+// 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 vdlgen
+
+// TODO(toddw): Add tests
+
+import (
+	"fmt"
+	"strconv"
+
+	"v.io/v23/vdl"
+	"v.io/x/ref/lib/vdl/codegen"
+)
+
+// TypedConst returns the explicitly-typed vdl const corresponding to v, in the
+// given pkgPath, with the given imports.
+func TypedConst(v *vdl.Value, pkgPath string, imports codegen.Imports) string {
+	if v == nil {
+		return "nil"
+	}
+	k, t := v.Kind(), v.Type()
+	typestr := Type(t, pkgPath, imports)
+	if k == vdl.Optional {
+		// TODO(toddw): This only works if the optional elem is a composite literal.
+		if elem := v.Elem(); elem != nil {
+			return typestr + UntypedConst(elem, pkgPath, imports)
+		}
+		return typestr + "(nil)"
+	}
+	valstr := UntypedConst(v, pkgPath, imports)
+	if k == vdl.Enum || k == vdl.TypeObject || t == vdl.BoolType || t == vdl.StringType {
+		// Enum and TypeObject already include the type in their value.
+		// Built-in bool and string are implicitly convertible from literals.
+		return valstr
+	}
+	switch k {
+	case vdl.Array, vdl.List, vdl.Set, vdl.Map, vdl.Struct, vdl.Union:
+		// { } are used instead of ( ) for composites, except for []byte and [N]byte
+		if !t.IsBytes() {
+			return typestr + valstr
+		}
+	}
+	return typestr + "(" + valstr + ")"
+}
+
+// UntypedConst returns the untyped vdl const corresponding to v, in the given
+// pkgPath, with the given imports.
+func UntypedConst(v *vdl.Value, pkgPath string, imports codegen.Imports) string {
+	k, t := v.Kind(), v.Type()
+	if t.IsBytes() {
+		return strconv.Quote(string(v.Bytes()))
+	}
+	switch k {
+	case vdl.Any:
+		if elem := v.Elem(); elem != nil {
+			return TypedConst(elem, pkgPath, imports)
+		}
+		return "nil"
+	case vdl.Optional:
+		if elem := v.Elem(); elem != nil {
+			return UntypedConst(elem, pkgPath, imports)
+		}
+		return "nil"
+	case vdl.Bool:
+		return strconv.FormatBool(v.Bool())
+	case vdl.Byte:
+		return strconv.FormatUint(uint64(v.Byte()), 10)
+	case vdl.Uint16, vdl.Uint32, vdl.Uint64:
+		return strconv.FormatUint(v.Uint(), 10)
+	case vdl.Int16, vdl.Int32, vdl.Int64:
+		return strconv.FormatInt(v.Int(), 10)
+	case vdl.Float32, vdl.Float64:
+		return formatFloat(v.Float(), k)
+	case vdl.Complex64, vdl.Complex128:
+		switch re, im := real(v.Complex()), imag(v.Complex()); {
+		case im > 0:
+			return formatFloat(re, k) + "+" + formatFloat(im, k) + "i"
+		case im < 0:
+			return formatFloat(re, k) + formatFloat(im, k) + "i"
+		default:
+			return formatFloat(re, k)
+		}
+	case vdl.String:
+		return strconv.Quote(v.RawString())
+	case vdl.Array, vdl.List:
+		if v.IsZero() {
+			return "{}"
+		}
+		s := "{"
+		for ix := 0; ix < v.Len(); ix++ {
+			if ix > 0 {
+				s += ", "
+			}
+			s += UntypedConst(v.Index(ix), pkgPath, imports)
+		}
+		return s + "}"
+	case vdl.Set, vdl.Map:
+		s := "{"
+		for ix, key := range vdl.SortValuesAsString(v.Keys()) {
+			if ix > 0 {
+				s += ", "
+			}
+			s += UntypedConst(key, pkgPath, imports)
+			if k == vdl.Map {
+				s += ": " + UntypedConst(v.MapIndex(key), pkgPath, imports)
+			}
+		}
+		return s + "}"
+	case vdl.Struct:
+		s := "{"
+		hasFields := false
+		for ix := 0; ix < t.NumField(); ix++ {
+			vf := v.StructField(ix)
+			if vf.IsZero() {
+				continue
+			}
+			if hasFields {
+				s += ", "
+			}
+			s += t.Field(ix).Name + ": " + UntypedConst(vf, pkgPath, imports)
+			hasFields = true
+		}
+		return s + "}"
+	case vdl.Union:
+		index, value := v.UnionField()
+		return "{" + t.Field(index).Name + ": " + UntypedConst(value, pkgPath, imports) + "}"
+	}
+	// Enum and TypeObject always require the typestr.
+	switch k {
+	case vdl.Enum:
+		return Type(t, pkgPath, imports) + "." + v.EnumLabel()
+	case vdl.TypeObject:
+		return "typeobject(" + Type(v.TypeObject(), pkgPath, imports) + ")"
+	default:
+		panic(fmt.Errorf("vdlgen.Const unhandled type: %v %v", k, t))
+	}
+}
+
+func formatFloat(x float64, kind vdl.Kind) string {
+	var bitSize int
+	switch kind {
+	case vdl.Float32, vdl.Complex64:
+		bitSize = 32
+	case vdl.Float64, vdl.Complex128:
+		bitSize = 64
+	default:
+		panic(fmt.Errorf("formatFloat unhandled kind: %v", kind))
+	}
+	return strconv.FormatFloat(x, 'g', -1, bitSize)
+}
diff --git a/lib/vdl/codegen/vdlgen/import.go b/lib/vdl/codegen/vdlgen/import.go
new file mode 100644
index 0000000..248b936
--- /dev/null
+++ b/lib/vdl/codegen/vdlgen/import.go
@@ -0,0 +1,30 @@
+// 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 vdlgen implements VDL code generation from compiled VDL packages.
+package vdlgen
+
+// TODO(toddw): Add tests
+
+import (
+	"v.io/x/ref/lib/vdl/codegen"
+)
+
+// Imports returns the vdl imports clause corresponding to imports; empty if
+// there are no imports.
+func Imports(imports codegen.Imports) string {
+	var s string
+	if len(imports) > 0 {
+		s += "import ("
+		for _, imp := range imports {
+			s += "\n\t"
+			if imp.Name != "" {
+				s += imp.Name + " "
+			}
+			s += imp.Path
+		}
+		s += "\n)"
+	}
+	return s
+}
diff --git a/lib/vdl/codegen/vdlgen/signature.go b/lib/vdl/codegen/vdlgen/signature.go
new file mode 100644
index 0000000..a41366a
--- /dev/null
+++ b/lib/vdl/codegen/vdlgen/signature.go
@@ -0,0 +1,179 @@
+// 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 vdlgen
+
+// TODO(toddw): Add tests.
+
+import (
+	"fmt"
+	"io"
+	"sort"
+	"strings"
+
+	"v.io/v23/vdl"
+	"v.io/v23/vdlroot/signature"
+	"v.io/x/lib/textutil"
+)
+
+// NamedTypes represents a set of unique named types.  The main usage is to
+// collect the named types from one or more signatures, and to print all of the
+// named types.
+type NamedTypes struct {
+	types map[*vdl.Type]bool
+}
+
+// Add adds to x all named types from the type graph rooted at t.
+func (x *NamedTypes) Add(t *vdl.Type) {
+	if t == nil {
+		return
+	}
+	t.Walk(vdl.WalkAll, func(t *vdl.Type) bool {
+		if t.Name() != "" {
+			if x.types == nil {
+				x.types = make(map[*vdl.Type]bool)
+			}
+			x.types[t] = true
+		}
+		return true
+	})
+}
+
+// Print pretty-prints x to w.
+func (x NamedTypes) Print(w io.Writer) {
+	var sorted typesByPkgAndName
+	for t, _ := range x.types {
+		sorted = append(sorted, t)
+	}
+	sort.Sort(sorted)
+	for _, t := range sorted {
+		path, name := vdl.SplitIdent(t.Name())
+		fmt.Fprintf(w, "\ntype %s %s\n", qualifiedName(path, name), BaseType(t, "", nil))
+	}
+}
+
+// typesByPkgAndName orders types by package path, then by name.
+type typesByPkgAndName []*vdl.Type
+
+func (x typesByPkgAndName) Len() int      { return len(x) }
+func (x typesByPkgAndName) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
+func (x typesByPkgAndName) Less(i, j int) bool {
+	ipkg, iname := vdl.SplitIdent(x[i].Name())
+	jpkg, jname := vdl.SplitIdent(x[j].Name())
+	if ipkg != jpkg {
+		return ipkg < jpkg
+	}
+	return iname < jname
+}
+
+// PrintInterface pretty-prints x to w.  The types are used to collect named
+// types, so that they may be printed later.
+func PrintInterface(w io.Writer, x signature.Interface, types *NamedTypes) {
+	printDoc(w, x.Doc)
+	fmt.Fprintf(w, "type %s interface {", qualifiedName(x.PkgPath, x.Name))
+	indentW := textutil.ByteReplaceWriter(w, '\n', "\n\t")
+	for _, embed := range x.Embeds {
+		fmt.Fprint(indentW, "\n")
+		PrintEmbed(indentW, embed)
+	}
+	for _, method := range x.Methods {
+		fmt.Fprint(indentW, "\n")
+		PrintMethod(indentW, method, types)
+	}
+	fmt.Fprintf(w, "\n}")
+}
+
+// PrintEmbed pretty-prints x to w.
+func PrintEmbed(w io.Writer, x signature.Embed) {
+	printDoc(w, x.Doc)
+	fmt.Fprint(w, qualifiedName(x.PkgPath, x.Name))
+}
+
+// PrintMethod pretty-prints x to w.  The types are used to collect named types,
+// so that they may be printed later.
+func PrintMethod(w io.Writer, x signature.Method, types *NamedTypes) {
+	printDoc(w, x.Doc)
+	fmt.Fprintf(w, "%s(%s)%s%s%s", x.Name, inArgsStr(x.InArgs, types), streamArgsStr(x.InStream, x.OutStream, types), outArgsStr(x.OutArgs, types), tagsStr(x.Tags, types))
+}
+
+func inArgsStr(args []signature.Arg, types *NamedTypes) string {
+	var ret []string
+	for _, arg := range args {
+		ret = append(ret, argStr(arg, types))
+	}
+	return strings.Join(ret, ", ")
+}
+
+func outArgsStr(args []signature.Arg, types *NamedTypes) string {
+	if len(args) == 0 {
+		return " error"
+	}
+	out := make([]string, len(args))
+	for i, arg := range args {
+		out[i] = argStr(arg, types)
+	}
+	return fmt.Sprintf(" (%s | error)", strings.Join(out, ", "))
+}
+
+func argStr(arg signature.Arg, types *NamedTypes) string {
+	// TODO(toddw): Print arg.Doc somewhere?
+	var ret string
+	if arg.Name != "" {
+		ret += arg.Name + " "
+	}
+	types.Add(arg.Type)
+	return ret + Type(arg.Type, "", nil)
+}
+
+func streamArgsStr(in, out *signature.Arg, types *NamedTypes) string {
+	if in == nil && out == nil {
+		return ""
+	}
+	return fmt.Sprintf(" stream<%s, %s>", streamArgStr(in, types), streamArgStr(out, types))
+}
+
+func streamArgStr(arg *signature.Arg, types *NamedTypes) string {
+	// TODO(toddw): Print arg.Name and arg.Doc?
+	if arg == nil {
+		return "_"
+	}
+	types.Add(arg.Type)
+	return Type(arg.Type, "", nil)
+}
+
+func tagsStr(tags []*vdl.Value, types *NamedTypes) string {
+	if len(tags) == 0 {
+		return ""
+	}
+	var ret []string
+	for _, tag := range tags {
+		types.Add(tag.Type())
+		ret = append(ret, TypedConst(tag, "", nil))
+	}
+	return fmt.Sprintf(" {%s}", strings.Join(ret, ", "))
+}
+
+func qualifiedName(pkgpath, name string) string {
+	if pkgpath == "" {
+		if name == "" {
+			return "<empty>"
+		}
+		return name
+	}
+	return fmt.Sprintf("%q.%s", pkgpath, name)
+}
+
+func printDoc(w io.Writer, doc string) {
+	if doc == "" {
+		return
+	}
+	// TODO(toddw): Normalize the doc strings so that it never has the // or /**/
+	// comment markers, and add them consistently here.  And use LineWriter to
+	// pretty-print the doc.
+	if !strings.HasPrefix(doc, "//") {
+		fmt.Fprintln(w, "// "+doc)
+	} else {
+		fmt.Fprintln(w, doc)
+	}
+}
diff --git a/lib/vdl/codegen/vdlgen/type.go b/lib/vdl/codegen/vdlgen/type.go
new file mode 100644
index 0000000..94a4cfc
--- /dev/null
+++ b/lib/vdl/codegen/vdlgen/type.go
@@ -0,0 +1,79 @@
+// 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 vdlgen
+
+// TODO(toddw): Add tests
+
+import (
+	"fmt"
+	"strings"
+
+	"v.io/v23/vdl"
+	"v.io/x/ref/lib/vdl/codegen"
+)
+
+// Type returns t using VDL syntax, returning the qualified name if t is named,
+// otherwise returning the base type of t.  The pkgPath and imports are used to
+// determine the local package qualifier to add to named types; if a local
+// package qualifier cannot be found, a full package path qualifier is added.
+func Type(t *vdl.Type, pkgPath string, imports codegen.Imports) string {
+	if t.Name() == "" {
+		return BaseType(t, pkgPath, imports)
+	}
+	path, name := vdl.SplitIdent(t.Name())
+	if path == "" && name == "" {
+		return "<empty>"
+	}
+	if path == "" || path == pkgPath {
+		return name
+	}
+	if local := imports.LookupLocal(path); local != "" {
+		return local + "." + name
+	}
+	return `"` + path + `".` + name
+}
+
+// BaseType returns the base type of t using VDL syntax, where the base type is
+// the type of t disregarding its name.  Subtypes contained in t are output via
+// calls to Type.
+func BaseType(t *vdl.Type, pkgPath string, imports codegen.Imports) string {
+	switch k := t.Kind(); k {
+	case vdl.Any, vdl.Bool, vdl.Byte, vdl.Uint16, vdl.Uint32, vdl.Uint64, vdl.Int16, vdl.Int32, vdl.Int64, vdl.Float32, vdl.Float64, vdl.Complex64, vdl.Complex128, vdl.String, vdl.TypeObject:
+		// Built-in types are named the same as their kind.
+		return k.String()
+	case vdl.Optional:
+		return "?" + Type(t.Elem(), pkgPath, imports)
+	case vdl.Enum:
+		ret := "enum{"
+		for i := 0; i < t.NumEnumLabel(); i++ {
+			if i > 0 {
+				ret += ";"
+			}
+			ret += t.EnumLabel(i)
+		}
+		return ret + "}"
+	case vdl.Array:
+		return fmt.Sprintf("[%d]%s", t.Len(), Type(t.Elem(), pkgPath, imports))
+	case vdl.List:
+		return fmt.Sprintf("[]%s", Type(t.Elem(), pkgPath, imports))
+	case vdl.Set:
+		return fmt.Sprintf("set[%s]", Type(t.Key(), pkgPath, imports))
+	case vdl.Map:
+		key := Type(t.Key(), pkgPath, imports)
+		elem := Type(t.Elem(), pkgPath, imports)
+		return fmt.Sprintf("map[%s]%s", key, elem)
+	case vdl.Struct, vdl.Union:
+		ret := k.String() + " {\n"
+		for i := 0; i < t.NumField(); i++ {
+			f := t.Field(i)
+			ftype := Type(f.Type, pkgPath, imports)
+			ftype = strings.Replace(ftype, "\n", "\n\t", -1)
+			ret += fmt.Sprintf("\t%s %s\n", f.Name, ftype)
+		}
+		return ret + "}"
+	default:
+		panic(fmt.Errorf("vdlgen.BaseType: unhandled type: %v %v", k, t))
+	}
+}
diff --git a/lib/vdl/compile/builtin.go b/lib/vdl/compile/builtin.go
new file mode 100644
index 0000000..a6711a8
--- /dev/null
+++ b/lib/vdl/compile/builtin.go
@@ -0,0 +1,63 @@
+// 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 compile
+
+import (
+	"v.io/v23/vdl"
+	"v.io/v23/vdlroot/vdltool"
+)
+
+var (
+	// The BuiltInPackage and BuiltInFile are used to hold the built-ins.
+	BuiltInPackage = newPackage("", "_builtin", "_builtin", vdltool.Config{})
+	BuiltInFile    = &File{BaseName: "_builtin.vdl"}
+)
+
+func init() {
+	// Link the BuiltIn{Package,File} to each other before defining built-ins.
+	BuiltInPackage.Files = []*File{BuiltInFile}
+	BuiltInFile.Package = BuiltInPackage
+	// Built-in types
+	builtInType("any", vdl.AnyType)
+	builtInType("bool", vdl.BoolType)
+	builtInType("byte", vdl.ByteType)
+	builtInType("uint16", vdl.Uint16Type)
+	builtInType("uint32", vdl.Uint32Type)
+	builtInType("uint64", vdl.Uint64Type)
+	builtInType("int16", vdl.Int16Type)
+	builtInType("int32", vdl.Int32Type)
+	builtInType("int64", vdl.Int64Type)
+	builtInType("float32", vdl.Float32Type)
+	builtInType("float64", vdl.Float64Type)
+	builtInType("complex64", vdl.Complex64Type)
+	builtInType("complex128", vdl.Complex128Type)
+	builtInType("string", vdl.StringType)
+	builtInType("typeobject", vdl.TypeObjectType)
+	builtInType("error", vdl.ErrorType)
+	// Built-in consts
+	builtInConst("nil", NilConst)
+	builtInConst("true", TrueConst)
+	builtInConst("false", FalseConst)
+}
+
+func builtInType(name string, t *vdl.Type) {
+	def := &TypeDef{
+		NamePos:  NamePos{Name: name},
+		Exported: true,
+		Type:     t,
+		File:     BuiltInFile,
+	}
+	addTypeDef(def, nil)
+}
+
+func builtInConst(name string, v *vdl.Value) {
+	def := &ConstDef{
+		NamePos:  NamePos{Name: name},
+		Exported: true,
+		Value:    v,
+		File:     BuiltInFile,
+	}
+	addConstDef(def, nil)
+}
diff --git a/lib/vdl/compile/compile.go b/lib/vdl/compile/compile.go
new file mode 100644
index 0000000..c4bd775
--- /dev/null
+++ b/lib/vdl/compile/compile.go
@@ -0,0 +1,344 @@
+// 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 compile implements the VDL compiler, converting a parse tree into
+// compiled results.  The CompilePackage function is the main entry point.
+package compile
+
+// The job of the compiler is to take parse results as input, and output
+// compiled results.  The concepts between the parser and compiler are very
+// similar, thus the naming of parse/compile results is also similar.
+// E.g. parse.File represents a parsed file, while compile.File represents a
+// compiled file.
+//
+// The flow of the compiler is contained in the Compile function below, and
+// basically defines one concept across all files in the package before moving
+// onto the next concept.  E.g. we define all types in the package before
+// defining all consts in the package.
+//
+// The logic for simple concepts (e.g. imports) is contained directly in this
+// file, while more complicated concepts (types, consts and interfaces) each get
+// their own file.
+
+import (
+	"path/filepath"
+	"sort"
+
+	"v.io/v23/vdl"
+	"v.io/v23/vdlroot/vdltool"
+	"v.io/x/ref/lib/vdl/parse"
+)
+
+// CompilePackage compiles a list of parse.Files into a Package.  Updates env
+// with the compiled package and returns it on success, or returns nil and
+// guarantees !env.Errors.IsEmpty().  All imports that the parsed package depend
+// on must already have been compiled and populated into env.
+func CompilePackage(pkgpath, genpath string, pfiles []*parse.File, config vdltool.Config, env *Env) *Package {
+	if pkgpath == "" {
+		env.Errors.Errorf("Compile called with empty pkgpath")
+		return nil
+	}
+	if env.pkgs[pkgpath] != nil {
+		env.Errors.Errorf("%q invalid recompile (already exists in env)", pkgpath)
+		return nil
+	}
+	pkg := compile(pkgpath, genpath, pfiles, config, env)
+	if pkg == nil {
+		return nil
+	}
+	if computeDeps(pkg, env); !env.Errors.IsEmpty() {
+		return nil
+	}
+	env.pkgs[pkg.Path] = pkg
+	return pkg
+}
+
+// CompileConfig compiles a parse.Config into a value.  Returns the compiled
+// value on success, or returns nil and guarantees !env.Errors.IsEmpty().  All
+// imports that the parsed config depend on must already have been compiled and
+// populated into env.  If t is non-nil, the returned value will be of that
+// type.
+func CompileConfig(t *vdl.Type, pconfig *parse.Config, env *Env) *vdl.Value {
+	if pconfig == nil || env == nil {
+		env.Errors.Errorf("CompileConfig called with nil config or env")
+		return nil
+	}
+	// Since the concepts are so similar between config files and vdl files, we
+	// just compile it as a single-file vdl package, and compile the exported
+	// config const to retrieve the final exported config value.
+	pfile := &parse.File{
+		BaseName:   filepath.Base(pconfig.FileName),
+		PackageDef: pconfig.ConfigDef,
+		Imports:    pconfig.Imports,
+		ConstDefs:  pconfig.ConstDefs,
+	}
+	pkgpath := filepath.ToSlash(filepath.Dir(pconfig.FileName))
+	pkg := compile(pkgpath, pkgpath, []*parse.File{pfile}, vdltool.Config{}, env)
+	if pkg == nil {
+		return nil
+	}
+	config := compileConst("config", t, pconfig.Config, pkg.Files[0], env)
+	// Wait to compute deps after we've compiled the config const expression,
+	// since it might include the only usage of some of the imports.
+	if computeDeps(pkg, env); !env.Errors.IsEmpty() {
+		return nil
+	}
+	return config
+}
+
+// CompileExpr compiles expr into a value.  Returns the compiled value on
+// success, or returns nil and guarantees !env.Errors.IsEmpty().  All imports
+// that expr depends on must already have been compiled and populated into env.
+// If t is non-nil, the returned value will be of that type.
+func CompileExpr(t *vdl.Type, expr parse.ConstExpr, env *Env) *vdl.Value {
+	file := &File{
+		BaseName: "_expr.vdl",
+		Package:  newPackage("_expr", "_expr", "_expr", vdltool.Config{}),
+		imports:  make(map[string]*importPath),
+	}
+	// Add imports to the "File" if the are in env and used in the Expression.
+	for _, pkg := range parse.ExtractExprPackagePaths(expr) {
+		if env.pkgs[pkg] != nil {
+			file.imports[pkg] = &importPath{path: pkg}
+		}
+	}
+	return compileConst("expression", t, expr, file, env)
+}
+
+func compile(pkgpath, genpath string, pfiles []*parse.File, config vdltool.Config, env *Env) *Package {
+	if len(pfiles) == 0 {
+		env.Errors.Errorf("%q compile called with no files", pkgpath)
+		return nil
+	}
+	// Initialize each file and put it in pkg.
+	pkgName := parse.InferPackageName(pfiles, env.Errors)
+	if _, err := validIdent(pkgName, reservedNormal); err != nil {
+		env.Errors.Errorf("package %s invalid name (%s)", pkgName, err.Error())
+		return nil
+	}
+	pkg := newPackage(pkgName, pkgpath, genpath, config)
+	for _, pfile := range pfiles {
+		pkg.Files = append(pkg.Files, &File{
+			BaseName:   pfile.BaseName,
+			PackageDef: NamePos(pfile.PackageDef),
+			Package:    pkg,
+			imports:    make(map[string]*importPath),
+		})
+	}
+	// Compile our various structures.  The order of these operations matters;
+	// e.g. we must compile types before consts, since consts may use a type
+	// defined in this package.
+	if compileFileDoc(pkg, pfiles, env); !env.Errors.IsEmpty() {
+		return nil
+	}
+	if compileImports(pkg, pfiles, env); !env.Errors.IsEmpty() {
+		return nil
+	}
+	if compileTypeDefs(pkg, pfiles, env); !env.Errors.IsEmpty() {
+		return nil
+	}
+	if compileErrorDefs(pkg, pfiles, env); !env.Errors.IsEmpty() {
+		return nil
+	}
+	if compileConstDefs(pkg, pfiles, env); !env.Errors.IsEmpty() {
+		return nil
+	}
+	if compileInterfaces(pkg, pfiles, env); !env.Errors.IsEmpty() {
+		return nil
+	}
+	return pkg
+}
+
+func compileFileDoc(pkg *Package, pfiles []*parse.File, env *Env) {
+	for index := range pfiles {
+		file, pfile := pkg.Files[index], pfiles[index]
+		if index == 0 {
+			pkg.FileDoc = pfile.Doc
+		} else if pkg.FileDoc != pfile.Doc {
+			// We force all file-doc to be the same, since *.vdl files aren't 1-to-1
+			// with the generated files in each language, e.g. Java creates one file
+			// per class, while Javascript creates a single file for the entire
+			// package.  For the common-case where we use file-doc for copyright
+			// headers, it also prevents the user from accidentally adding copyright
+			// headers to one file but not another, in the same package.
+			env.Errorf(file, parse.Pos{1, 1}, "all files in a package must have the same file doc (the comment on the first line of each *.vdl file that isn't package doc)")
+		}
+	}
+}
+
+func compileImports(pkg *Package, pfiles []*parse.File, env *Env) {
+	for index := range pfiles {
+		file, pfile := pkg.Files[index], pfiles[index]
+		for _, pimp := range pfile.Imports {
+			if dep := env.ResolvePackage(pimp.Path); dep == nil {
+				env.Errorf(file, pimp.Pos, "import path %q not found", pimp.Path)
+			}
+			local := pimp.LocalName()
+			if dup := file.imports[local]; dup != nil {
+				env.Errorf(file, pimp.Pos, "import %s reused (previous at %s)", local, dup.pos)
+				continue
+			}
+			file.imports[local] = &importPath{pimp.Path, pimp.Pos, false}
+		}
+	}
+}
+
+// TODO(toddw): Remove this function and all helpers, after all code generators
+// have been updated to compute their own dependencies.  The only code that will
+// remain below this point is the loop checking for unused imports.
+func computeDeps(pkg *Package, env *Env) {
+	// Check for unused user-supplied imports.
+	for _, file := range pkg.Files {
+		for _, imp := range file.imports {
+			if !imp.used {
+				env.Errorf(file, imp.pos, "import path %q unused", imp.path)
+			}
+		}
+	}
+	// Compute type and package dependencies per-file, based on the types and
+	// interfaces that are actually used.  We ignore const dependencies, since
+	// we've already evaluated the const expressions.
+	for _, file := range pkg.Files {
+		tdeps := make(map[*vdl.Type]bool)
+		pdeps := make(map[*Package]bool)
+		// TypeDef.Type is always defined in our package; start with sub types.
+		for _, def := range file.TypeDefs {
+			addSubTypeDeps(def.Type, pkg, env, tdeps, pdeps)
+		}
+		// Consts contribute their value types.
+		for _, def := range file.ConstDefs {
+			addValueTypeDeps(def.Value, pkg, env, tdeps, pdeps)
+		}
+		// Interfaces contribute their arg types and tag values, as well as embedded
+		// interfaces.
+		for _, iface := range file.Interfaces {
+			for _, embed := range iface.TransitiveEmbeds() {
+				pdeps[embed.File.Package] = true
+			}
+			for _, method := range iface.Methods {
+				for _, arg := range method.InArgs {
+					addTypeDeps(arg.Type, pkg, env, tdeps, pdeps)
+				}
+				for _, arg := range method.OutArgs {
+					addTypeDeps(arg.Type, pkg, env, tdeps, pdeps)
+				}
+				if stream := method.InStream; stream != nil {
+					addTypeDeps(stream, pkg, env, tdeps, pdeps)
+				}
+				if stream := method.OutStream; stream != nil {
+					addTypeDeps(stream, pkg, env, tdeps, pdeps)
+				}
+				for _, tag := range method.Tags {
+					addValueTypeDeps(tag, pkg, env, tdeps, pdeps)
+				}
+			}
+		}
+		// Errors contribute their param types.
+		for _, def := range file.ErrorDefs {
+			for _, param := range def.Params {
+				addTypeDeps(param.Type, pkg, env, tdeps, pdeps)
+			}
+		}
+		file.TypeDeps = tdeps
+		// Now remove self and built-in package dependencies.  Every package can use
+		// itself and the built-in package, so we don't need to record this.
+		delete(pdeps, pkg)
+		delete(pdeps, BuiltInPackage)
+		// Finally populate PackageDeps and sort by package path.
+		file.PackageDeps = make([]*Package, 0, len(pdeps))
+		for pdep, _ := range pdeps {
+			file.PackageDeps = append(file.PackageDeps, pdep)
+		}
+		sort.Sort(pkgSorter(file.PackageDeps))
+	}
+}
+
+// Add immediate package deps for t and subtypes of t.
+func addTypeDeps(t *vdl.Type, pkg *Package, env *Env, tdeps map[*vdl.Type]bool, pdeps map[*Package]bool) {
+	if def := env.typeDefs[t]; def != nil {
+		// We don't track transitive dependencies, only immediate dependencies.
+		tdeps[t] = true
+		pdeps[def.File.Package] = true
+		if t == vdl.TypeObjectType {
+			// Special-case: usage of typeobject implies usage of any, since the zero
+			// value for typeobject is any.
+			addTypeDeps(vdl.AnyType, pkg, env, tdeps, pdeps)
+		}
+		return
+	}
+	// Not all types have TypeDefs; e.g. unnamed lists have no corresponding
+	// TypeDef, so we need to traverse those recursively.
+	addSubTypeDeps(t, pkg, env, tdeps, pdeps)
+}
+
+// Add immediate package deps for subtypes of t.
+func addSubTypeDeps(t *vdl.Type, pkg *Package, env *Env, tdeps map[*vdl.Type]bool, pdeps map[*Package]bool) {
+	switch t.Kind() {
+	case vdl.Array, vdl.List:
+		addTypeDeps(t.Elem(), pkg, env, tdeps, pdeps)
+	case vdl.Set:
+		addTypeDeps(t.Key(), pkg, env, tdeps, pdeps)
+	case vdl.Map:
+		addTypeDeps(t.Key(), pkg, env, tdeps, pdeps)
+		addTypeDeps(t.Elem(), pkg, env, tdeps, pdeps)
+	case vdl.Struct, vdl.Union:
+		for ix := 0; ix < t.NumField(); ix++ {
+			addTypeDeps(t.Field(ix).Type, pkg, env, tdeps, pdeps)
+		}
+	}
+}
+
+// Add immediate package deps for v.Type(), and subvalues.  We must traverse the
+// value to know which types are actually used; e.g. an empty struct doesn't
+// have a dependency on its field types.
+//
+// The purpose of this method is to identify the package and type dependencies
+// for const or tag values.
+func addValueTypeDeps(v *vdl.Value, pkg *Package, env *Env, tdeps map[*vdl.Type]bool, pdeps map[*Package]bool) {
+	t := v.Type()
+	if def := env.typeDefs[t]; def != nil {
+		tdeps[t] = true
+		pdeps[def.File.Package] = true
+		// Fall through to track transitive dependencies, based on the subvalues.
+	}
+	// Traverse subvalues recursively.
+	switch t.Kind() {
+	case vdl.Array, vdl.List:
+		for ix := 0; ix < v.Len(); ix++ {
+			addValueTypeDeps(v.Index(ix), pkg, env, tdeps, pdeps)
+		}
+	case vdl.Set, vdl.Map:
+		for _, key := range v.Keys() {
+			addValueTypeDeps(key, pkg, env, tdeps, pdeps)
+			if t.Kind() == vdl.Map {
+				addValueTypeDeps(v.MapIndex(key), pkg, env, tdeps, pdeps)
+			}
+		}
+	case vdl.Struct:
+		// There are no subvalues to track if the value is 0.
+		if v.IsZero() {
+			return
+		}
+		for ix := 0; ix < t.NumField(); ix++ {
+			addValueTypeDeps(v.StructField(ix), pkg, env, tdeps, pdeps)
+		}
+	case vdl.Union:
+		_, field := v.UnionField()
+		addValueTypeDeps(field, pkg, env, tdeps, pdeps)
+	case vdl.Any, vdl.Optional:
+		if elem := v.Elem(); elem != nil {
+			addValueTypeDeps(elem, pkg, env, tdeps, pdeps)
+		}
+	case vdl.TypeObject:
+		// TypeObject has dependencies on everything its zero value depends on.
+		addValueTypeDeps(vdl.ZeroValue(v.TypeObject()), pkg, env, tdeps, pdeps)
+	}
+}
+
+// pkgSorter implements sort.Interface, sorting by package path.
+type pkgSorter []*Package
+
+func (s pkgSorter) Len() int           { return len(s) }
+func (s pkgSorter) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
+func (s pkgSorter) Less(i, j int) bool { return s[i].Path < s[j].Path }
diff --git a/lib/vdl/compile/compile_test.go b/lib/vdl/compile/compile_test.go
new file mode 100644
index 0000000..76164b5
--- /dev/null
+++ b/lib/vdl/compile/compile_test.go
@@ -0,0 +1,163 @@
+// 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 compile_test
+
+import (
+	"fmt"
+	"path"
+	"testing"
+
+	"v.io/v23/vdl"
+	"v.io/x/ref/lib/vdl/build"
+	"v.io/x/ref/lib/vdl/compile"
+	"v.io/x/ref/lib/vdl/internal/vdltest"
+)
+
+type f map[string]string
+
+func TestParseAndCompile(t *testing.T) {
+	tests := []struct {
+		name   string
+		files  map[string]string
+		errRE  string
+		expect func(t *testing.T, name string, pkg *compile.Package)
+	}{
+		{"test1", f{"1.vdl": pkg1file1, "2.vdl": pkg1file2}, "", expectPkg1},
+		{"test2", f{"1.vdl": "package native"}, `reserved word in a generated language`, nil},
+	}
+	for _, test := range tests {
+		path := path.Join("a/b", test.name)
+		buildPkg := vdltest.FakeBuildPackage(test.name, path, test.files)
+		env := compile.NewEnv(-1)
+		pkg := build.BuildPackage(buildPkg, env)
+		vdltest.ExpectResult(t, env.Errors, test.name, test.errRE)
+		if pkg == nil {
+			continue
+		}
+		if got, want := pkg.Name, test.name; got != want {
+			t.Errorf("%v got package name %s, want %s", buildPkg, got, want)
+		}
+		if got, want := pkg.Path, path; got != want {
+			t.Errorf("%v got package path %s, want %s", buildPkg, got, want)
+		}
+		test.expect(t, test.name, pkg)
+	}
+}
+
+func TestParseAndCompileExprs(t *testing.T) {
+	env := compile.NewEnv(-1)
+	path := path.Join("a/b/test1")
+	buildPkg := vdltest.FakeBuildPackage("test1", path, f{"1.vdl": pkg1file1, "2.vdl": pkg1file2})
+	pkg := build.BuildPackage(buildPkg, env)
+	if pkg == nil {
+		t.Fatal("failed to build package")
+	}
+	// Test that expressions from the built packages compile correctly.
+	scalarsType := env.ResolvePackage("a/b/test1").ResolveType("Scalars").Type
+	exprTests := []struct {
+		data  string
+		vtype *vdl.Type
+	}{
+		{`"a/b/test1".Cint64`, vdl.Int64Type},
+		{`"a/b/test1".FiveSquared + "a/b/test1".Cint32`, vdl.Int32Type},
+		{`"a/b/test1".Scalars{A:true,C:"a/b/test1".FiveSquared+"a/b/test1".Cint32}`, scalarsType},
+	}
+	for _, test := range exprTests {
+		vals := build.BuildExprs(test.data, []*vdl.Type{test.vtype}, env)
+		if !env.Errors.IsEmpty() || len(vals) != 1 {
+			t.Errorf("failed to build %v: %v", test.data, env.Errors)
+		}
+	}
+}
+
+const pkg1file1 = `package test1
+
+type Scalars struct {
+	A bool
+	B byte
+	C int32
+	D int64
+	E uint32
+	F uint64
+	G float32
+	H float64
+	I complex64
+	J complex128
+	K string
+	L error
+	M any
+}
+
+type KeyScalars struct {
+	A bool
+	B byte
+	C int32
+	D int64
+	E uint32
+	F uint64
+	G float32
+	H float64
+	I complex64
+	J complex128
+	K string
+}
+
+type CompComp struct {
+	A Composites
+	B []Composites
+	C map[string]Composites
+}
+
+const (
+	Cbool = true
+	Cbyte = byte(1)
+	Cint32 = int32(2)
+	Cint64 = int64(3)
+	Cuint32 = uint32(4)
+	Cuint64 = uint64(5)
+	Cfloat32 = float32(6)
+	Cfloat64 = float64(7)
+	Ccomplex64 = complex64(8+9i)
+	Ccomplex128 = complex128(10+11i)
+	Cstring = "foo"
+	Cany = Cbool
+
+	True = true
+	Foo = "foo"
+	Five = int32(5)
+	SixSquared = Six*Six
+)
+
+type ServiceA interface {
+	MethodA1() error
+	MethodA2(a int32, b string) (s string | error)
+	MethodA3(a int32) stream<_, Scalars> (s string | error) {"tag", Six}
+	MethodA4(a int32) stream<int32, string> error
+}
+`
+
+const pkg1file2 = `package test1
+type Composites struct {
+	A Scalars
+	B []Scalars
+	C map[string]Scalars
+	D map[KeyScalars][]map[string]complex128
+}
+
+const (
+	FiveSquared = Five*Five
+	Six = uint64(6)
+)
+
+type ServiceB interface {
+	ServiceA
+	MethodB1(a Scalars, b Composites) (c CompComp | error)
+}
+`
+
+func expectPkg1(t *testing.T, name string, pkg *compile.Package) {
+	// TODO(toddw): verify real expectations, and add more tests.
+	fmt.Println(pkg)
+}
diff --git a/lib/vdl/compile/const.go b/lib/vdl/compile/const.go
new file mode 100644
index 0000000..bfe5bf7
--- /dev/null
+++ b/lib/vdl/compile/const.go
@@ -0,0 +1,632 @@
+// 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 compile
+
+import (
+	"fmt"
+	"math/big"
+
+	"v.io/v23/vdl"
+	"v.io/x/lib/toposort"
+	"v.io/x/ref/lib/vdl/opconst"
+	"v.io/x/ref/lib/vdl/parse"
+)
+
+// ConstDef represents a user-defined named const definition in the compiled
+// results.
+type ConstDef struct {
+	NamePos             // name, parse position and docs
+	Exported bool       // is this const definition exported?
+	Value    *vdl.Value // const value
+	File     *File      // parent file that this const is defined in
+}
+
+func (x *ConstDef) String() string {
+	c := *x
+	c.File = nil // avoid infinite loop
+	return fmt.Sprintf("%+v", c)
+}
+
+// compileConstDefs is the "entry point" to the rest of this file.  It takes the
+// consts defined in pfiles and compiles them into ConstDefs in pkg.
+func compileConstDefs(pkg *Package, pfiles []*parse.File, env *Env) {
+	cd := constDefiner{pkg, pfiles, env, make(map[string]*constBuilder)}
+	if cd.Declare(); !env.Errors.IsEmpty() {
+		return
+	}
+	cd.SortAndDefine()
+}
+
+// constDefiner defines consts in a package.  This is split into two phases:
+// 1) Declare ensures local const references can be resolved.
+// 2) SortAndDefine sorts in dependency order, and evaluates and defines each
+//    const.
+//
+// It holds a builders map from const name to constBuilder, where the
+// constBuilder is responsible for compiling and defining a single const.
+type constDefiner struct {
+	pkg      *Package
+	pfiles   []*parse.File
+	env      *Env
+	builders map[string]*constBuilder
+}
+
+type constBuilder struct {
+	def   *ConstDef
+	pexpr parse.ConstExpr
+}
+
+func printConstBuilderName(ibuilder interface{}) string {
+	return ibuilder.(*constBuilder).def.Name
+}
+
+// Declare creates builders for each const defined in the package.
+func (cd constDefiner) Declare() {
+	for ix := range cd.pkg.Files {
+		file, pfile := cd.pkg.Files[ix], cd.pfiles[ix]
+		for _, pdef := range pfile.ConstDefs {
+			export, err := validConstIdent(pdef.Name, reservedNormal)
+			if err != nil {
+				cd.env.prefixErrorf(file, pdef.Pos, err, "const %s invalid name", pdef.Name)
+				continue // keep going to catch more errors
+			}
+			detail := identDetail("const", file, pdef.Pos)
+			if err := file.DeclareIdent(pdef.Name, detail); err != nil {
+				cd.env.prefixErrorf(file, pdef.Pos, err, "const %s name conflict", pdef.Name)
+				continue
+			}
+			def := &ConstDef{NamePos: NamePos(pdef.NamePos), Exported: export, File: file}
+			cd.builders[pdef.Name] = &constBuilder{def, pdef.Expr}
+		}
+	}
+}
+
+// Sort and define consts.  We sort by dependencies on other named consts in
+// this package.  We don't allow cycles.  The ordering is necessary to perform
+// simple single-pass evaluation.
+//
+// The dependency analysis is performed on consts, not the files they occur in;
+// consts in the same package may be defined in any file, even if they cause
+// cyclic file dependencies.
+func (cd constDefiner) SortAndDefine() {
+	// Populate sorter with dependency information.  The sorting ensures that the
+	// list of const defs within each file is topologically sorted, and also
+	// deterministic; other than dependencies, const defs are listed in the same
+	// order they were defined in the parsed files.
+	var sorter toposort.Sorter
+	for _, pfile := range cd.pfiles {
+		for _, pdef := range pfile.ConstDefs {
+			b := cd.builders[pdef.Name]
+			sorter.AddNode(b)
+			for dep, _ := range cd.getLocalDeps(b.pexpr) {
+				sorter.AddEdge(b, dep)
+			}
+		}
+	}
+	// Sort and check for cycles.
+	sorted, cycles := sorter.Sort()
+	if len(cycles) > 0 {
+		cycleStr := toposort.DumpCycles(cycles, printConstBuilderName)
+		first := cycles[0][0].(*constBuilder)
+		cd.env.Errorf(first.def.File, first.def.Pos, "package %v has cyclic consts: %v", cd.pkg.Name, cycleStr)
+		return
+	}
+	// Define all consts.  Since we add the const defs as we go and evaluate in
+	// topological order, dependencies are guaranteed to be resolvable when we get
+	// around to evaluating the consts that depend on them.
+	for _, ibuilder := range sorted {
+		b := ibuilder.(*constBuilder)
+		def, file := b.def, b.def.File
+		if value := compileConst("const", nil, b.pexpr, file, cd.env); value != nil {
+			def.Value = value
+			addConstDef(def, cd.env)
+		}
+	}
+}
+
+// addConstDef updates our various structures to add a new const def.
+func addConstDef(def *ConstDef, env *Env) {
+	def.File.ConstDefs = append(def.File.ConstDefs, def)
+	def.File.Package.constDefs[def.Name] = def
+	if env != nil {
+		// env should only be nil during initialization of the built-in package;
+		// NewEnv ensures new environments have the built-in consts.
+		env.constDefs[def.Value] = def
+	}
+}
+
+// getLocalDeps returns the set of named const dependencies for pexpr that are
+// in this package.
+func (cd constDefiner) getLocalDeps(pexpr parse.ConstExpr) constBuilderSet {
+	switch pe := pexpr.(type) {
+	case nil, *parse.ConstLit, *parse.ConstTypeObject:
+		return nil
+	case *parse.ConstCompositeLit:
+		var deps constBuilderSet
+		for _, kv := range pe.KVList {
+			deps = mergeConstBuilderSets(deps, cd.getLocalDeps(kv.Key))
+			deps = mergeConstBuilderSets(deps, cd.getLocalDeps(kv.Value))
+		}
+		return deps
+	case *parse.ConstNamed:
+		// Named references to other consts in this package are all we care about.
+		if b := cd.builders[pe.Name]; b != nil {
+			return constBuilderSet{b: true}
+		}
+		return nil
+	case *parse.ConstIndexed:
+		e, i := cd.getLocalDeps(pe.Expr), cd.getLocalDeps(pe.IndexExpr)
+		return mergeConstBuilderSets(e, i)
+	case *parse.ConstTypeConv:
+		return cd.getLocalDeps(pe.Expr)
+	case *parse.ConstUnaryOp:
+		return cd.getLocalDeps(pe.Expr)
+	case *parse.ConstBinaryOp:
+		l, r := cd.getLocalDeps(pe.Lexpr), cd.getLocalDeps(pe.Rexpr)
+		return mergeConstBuilderSets(l, r)
+	}
+	panic(fmt.Errorf("vdl: unhandled parse.ConstExpr %T %#v", pexpr, pexpr))
+}
+
+type constBuilderSet map[*constBuilder]bool
+
+// mergeConstBuilderSets returns the union of a and b.  It may mutate either a
+// or b and return the mutated set as a result.
+func mergeConstBuilderSets(a, b constBuilderSet) constBuilderSet {
+	if a != nil {
+		for builder, _ := range b {
+			a[builder] = true
+		}
+		return a
+	}
+	return b
+}
+
+// compileConst compiles pexpr into a *vdl.Value.  All named types and consts
+// referenced by pexpr must already be defined.
+//
+// The implicit type is applied to pexpr; untyped consts and composite literals
+// with no explicit type assume the implicit type.  Errors are reported if the
+// implicit type isn't assignable from the final value.  If the implicit type is
+// nil, the exported config const must be explicitly typed.
+func compileConst(what string, implicit *vdl.Type, pexpr parse.ConstExpr, file *File, env *Env) *vdl.Value {
+	c := evalConstExpr(implicit, pexpr, file, env)
+	if !c.IsValid() {
+		return nil
+	}
+	if implicit != nil && c.Type() == nil {
+		// Convert untyped const into the implicit type.
+		conv, err := c.Convert(implicit)
+		if err != nil {
+			env.prefixErrorf(file, pexpr.Pos(), err, "invalid %v", what)
+			return nil
+		}
+		c = conv
+	}
+	v, err := c.ToValue()
+	if err != nil {
+		env.prefixErrorf(file, pexpr.Pos(), err, "invalid %s", what)
+		return nil
+	}
+	if implicit != nil && !implicit.AssignableFrom(v) {
+		env.Errorf(file, pexpr.Pos(), "invalid %v (%v not assignable from %v)", what, implicit, v)
+		return nil
+	}
+	return v
+}
+
+// compileConstExplicit is similar to compileConst, but instead of an optional
+// implicit type, requires a non-nil explicit type.  The compiled const is
+// explicitly converted to the explicit type.
+func compileConstExplicit(what string, explicit *vdl.Type, pexpr parse.ConstExpr, file *File, env *Env) *vdl.Value {
+	c := evalConstExpr(explicit, pexpr, file, env)
+	if !c.IsValid() {
+		return nil
+	}
+	conv, err := c.Convert(explicit)
+	if err != nil {
+		env.prefixErrorf(file, pexpr.Pos(), err, "invalid %v", what)
+		return nil
+	}
+	v, err := conv.ToValue()
+	if err != nil {
+		env.prefixErrorf(file, pexpr.Pos(), err, "invalid %s", what)
+		return nil
+	}
+	return v
+}
+
+var bigRatZero = new(big.Rat)
+
+// evalConstExpr returns the result of evaluating pexpr into a opconst.Const.
+// If implicit is non-nil, we apply it to pexpr if it doesn't have an explicit
+// type specified.  E.g. composite literals and enum labels with no explicit
+// type assume the implicit type.
+func evalConstExpr(implicit *vdl.Type, pexpr parse.ConstExpr, file *File, env *Env) opconst.Const {
+	switch pe := pexpr.(type) {
+	case *parse.ConstLit:
+		// All literal constants start out untyped.
+		switch tlit := pe.Lit.(type) {
+		case string:
+			return opconst.String(tlit)
+		case *big.Int:
+			return opconst.Integer(tlit)
+		case *big.Rat:
+			return opconst.Rational(tlit)
+		case *parse.BigImag:
+			return opconst.Complex(bigRatZero, (*big.Rat)(tlit))
+		default:
+			panic(fmt.Errorf("vdl: unhandled parse.ConstLit %T %#v", tlit, tlit))
+		}
+	case *parse.ConstCompositeLit:
+		t := implicit
+		if pe.Type != nil {
+			// If an explicit type is specified for the composite literal, it
+			// overrides the implicit type.
+			t = compileType(pe.Type, file, env)
+			if t == nil {
+				break
+			}
+		}
+		v := evalCompLit(t, pe, file, env)
+		if v == nil {
+			break
+		}
+		return opconst.FromValue(v)
+	case *parse.ConstNamed:
+		c, err := env.EvalConst(pe.Name, file)
+		if err != nil {
+			if implicit != nil {
+				// Try applying the name as a selector against the implicit type.  This
+				// allows a shortened form for enum labels, without redundantly
+				// specifying the enum type.
+				if c, err2 := env.evalSelectorOnType(implicit, pe.Name); err2 == nil {
+					return c
+				}
+			}
+			env.prefixErrorf(file, pe.Pos(), err, "const %s invalid", pe.Name)
+			break
+		}
+		return c
+	case *parse.ConstIndexed:
+		value := compileConst("const", nil, pe.Expr, file, env)
+		if value == nil {
+			break
+		}
+		// TODO(bprosnitz) Should indexing on set also be supported?
+		switch value.Kind() {
+		case vdl.Array, vdl.List:
+			v := evalListIndex(value, pe.IndexExpr, file, env)
+			if v != nil {
+				return opconst.FromValue(v)
+			}
+		case vdl.Map:
+			v := evalMapIndex(value, pe.IndexExpr, file, env)
+			if v != nil {
+				return opconst.FromValue(v)
+			}
+		default:
+			env.Errorf(file, pe.Pos(), "illegal use of index operator with unsupported type")
+		}
+	case *parse.ConstTypeConv:
+		t := compileType(pe.Type, file, env)
+		x := evalConstExpr(nil, pe.Expr, file, env)
+		if t == nil || !x.IsValid() {
+			break
+		}
+		res, err := x.Convert(t)
+		if err != nil {
+			env.prefixErrorf(file, pe.Pos(), err, "invalid type conversion")
+			break
+		}
+		return res
+	case *parse.ConstTypeObject:
+		t := compileType(pe.Type, file, env)
+		if t == nil {
+			break
+		}
+		return opconst.FromValue(vdl.TypeObjectValue(t))
+	case *parse.ConstUnaryOp:
+		x := evalConstExpr(nil, pe.Expr, file, env)
+		op := opconst.ToUnaryOp(pe.Op)
+		if op == opconst.InvalidUnaryOp {
+			env.Errorf(file, pe.Pos(), "unary %s undefined", pe.Op)
+			break
+		}
+		if !x.IsValid() {
+			break
+		}
+		res, err := opconst.EvalUnary(op, x)
+		if err != nil {
+			env.prefixErrorf(file, pe.Pos(), err, "unary %s invalid", pe.Op)
+			break
+		}
+		return res
+	case *parse.ConstBinaryOp:
+		x := evalConstExpr(nil, pe.Lexpr, file, env)
+		y := evalConstExpr(nil, pe.Rexpr, file, env)
+		op := opconst.ToBinaryOp(pe.Op)
+		if op == opconst.InvalidBinaryOp {
+			env.Errorf(file, pe.Pos(), "binary %s undefined", pe.Op)
+			break
+		}
+		if !x.IsValid() || !y.IsValid() {
+			break
+		}
+		res, err := opconst.EvalBinary(op, x, y)
+		if err != nil {
+			env.prefixErrorf(file, pe.Pos(), err, "binary %s invalid", pe.Op)
+			break
+		}
+		return res
+	default:
+		panic(fmt.Errorf("vdl: unhandled parse.ConstExpr %T %#v", pexpr, pexpr))
+	}
+	return opconst.Const{}
+}
+
+// evalListIndex evalutes base[index], where base is a list or array.
+func evalListIndex(base *vdl.Value, indexExpr parse.ConstExpr, file *File, env *Env) *vdl.Value {
+	index := compileConstExplicit(base.Kind().String()+" index", vdl.Uint64Type, indexExpr, file, env)
+	if index == nil {
+		return nil
+	}
+	ix := int(index.Uint())
+	if ix >= base.Len() {
+		env.Errorf(file, indexExpr.Pos(), "index %d out of range", ix)
+		return nil
+	}
+	return base.Index(ix)
+}
+
+// evalMapIndex evaluates base[index], where base is a map.
+func evalMapIndex(base *vdl.Value, indexExpr parse.ConstExpr, file *File, env *Env) *vdl.Value {
+	key := compileConst("map key", base.Type().Key(), indexExpr, file, env)
+	if key == nil {
+		return nil
+	}
+	item := base.MapIndex(key)
+	if item == nil {
+		// Unlike normal go code, it is probably undesirable to return the zero
+		// value here.  It is very likely this is an error.
+		env.Errorf(file, indexExpr.Pos(), "map key %v not found in map", key)
+		return nil
+	}
+	return item
+}
+
+// evalCompLit evaluates a composite literal, returning it as a vdl.Value.  The
+// type t is required, but note that subtypes enclosed in a composite type can
+// always use the implicit type from the parent composite type.
+func evalCompLit(t *vdl.Type, lit *parse.ConstCompositeLit, file *File, env *Env) *vdl.Value {
+	if t == nil {
+		env.Errorf(file, lit.Pos(), "missing type for composite literal")
+		return nil
+	}
+	isOptional := false
+	if t.Kind() == vdl.Optional {
+		isOptional = true
+		t = t.Elem()
+	}
+	var v *vdl.Value
+	switch t.Kind() {
+	case vdl.Array, vdl.List:
+		v = evalListLit(t, lit, file, env)
+	case vdl.Set:
+		v = evalSetLit(t, lit, file, env)
+	case vdl.Map:
+		v = evalMapLit(t, lit, file, env)
+	case vdl.Struct:
+		v = evalStructLit(t, lit, file, env)
+	case vdl.Union:
+		v = evalUnionLit(t, lit, file, env)
+	default:
+		env.Errorf(file, lit.Pos(), "%v invalid type for composite literal", t)
+		return nil
+	}
+	if v != nil && isOptional {
+		v = vdl.OptionalValue(v)
+	}
+	return v
+}
+
+func evalListLit(t *vdl.Type, lit *parse.ConstCompositeLit, file *File, env *Env) *vdl.Value {
+	listv := vdl.ZeroValue(t)
+	desc := fmt.Sprintf("%v %s literal", t, t.Kind())
+	var index int
+	assigned := make(map[int]bool)
+	for _, kv := range lit.KVList {
+		if kv.Value == nil {
+			env.Errorf(file, lit.Pos(), "missing value in %s", desc)
+			return nil
+		}
+		// Set the index to the key, if it exists.  Semantics are looser than
+		// values; we allow any key that's convertible to uint64, even if the key is
+		// already typed.
+		if kv.Key != nil {
+			key := compileConstExplicit("list index", vdl.Uint64Type, kv.Key, file, env)
+			if key == nil {
+				return nil
+			}
+			index = int(key.Uint())
+		}
+		// Make sure the index hasn't been assigned already, and adjust the list
+		// length as necessary.
+		if assigned[index] {
+			env.Errorf(file, kv.Value.Pos(), "duplicate index %d in %s", index, desc)
+			return nil
+		}
+		assigned[index] = true
+		if index >= listv.Len() {
+			if t.Kind() == vdl.Array {
+				env.Errorf(file, kv.Value.Pos(), "index %d out of range in %s", index, desc)
+				return nil
+			}
+			listv.AssignLen(index + 1)
+		}
+		// Evaluate the value and perform the assignment.
+		value := compileConst(t.Kind().String()+" value", t.Elem(), kv.Value, file, env)
+		if value == nil {
+			return nil
+		}
+		listv.Index(index).Assign(value)
+		index++
+	}
+	return listv
+}
+
+func evalSetLit(t *vdl.Type, lit *parse.ConstCompositeLit, file *File, env *Env) *vdl.Value {
+	setv := vdl.ZeroValue(t)
+	desc := fmt.Sprintf("%v set literal", t)
+	for _, kv := range lit.KVList {
+		if kv.Key != nil {
+			env.Errorf(file, kv.Key.Pos(), "invalid index in %s", desc)
+			return nil
+		}
+		if kv.Value == nil {
+			env.Errorf(file, lit.Pos(), "missing key in %s", desc)
+			return nil
+		}
+		// Evaluate the key and make sure it hasn't been assigned already.
+		key := compileConst("set key", t.Key(), kv.Value, file, env)
+		if key == nil {
+			return nil
+		}
+		if setv.ContainsKey(key) {
+			env.Errorf(file, kv.Value.Pos(), "duplicate key %v in %s", key, desc)
+			return nil
+		}
+		setv.AssignSetKey(key)
+	}
+	return setv
+}
+
+func evalMapLit(t *vdl.Type, lit *parse.ConstCompositeLit, file *File, env *Env) *vdl.Value {
+	mapv := vdl.ZeroValue(t)
+	desc := fmt.Sprintf("%v map literal", t)
+	for _, kv := range lit.KVList {
+		if kv.Key == nil {
+			env.Errorf(file, lit.Pos(), "missing key in %s", desc)
+			return nil
+		}
+		if kv.Value == nil {
+			env.Errorf(file, lit.Pos(), "missing elem in %s", desc)
+			return nil
+		}
+		// Evaluate the key and make sure it hasn't been assigned already.
+		key := compileConst("map key", t.Key(), kv.Key, file, env)
+		if key == nil {
+			return nil
+		}
+		if mapv.ContainsKey(key) {
+			env.Errorf(file, kv.Key.Pos(), "duplicate key %v in %s", key, desc)
+			return nil
+		}
+		// Evaluate the value and perform the assignment.
+		value := compileConst("map value", t.Elem(), kv.Value, file, env)
+		if value == nil {
+			return nil
+		}
+		mapv.AssignMapIndex(key, value)
+	}
+	return mapv
+}
+
+func evalStructLit(t *vdl.Type, lit *parse.ConstCompositeLit, file *File, env *Env) *vdl.Value {
+	// We require that either all items have keys, or none of them do.
+	structv := vdl.ZeroValue(t)
+	desc := fmt.Sprintf("%v struct literal", t)
+	haskeys := len(lit.KVList) > 0 && lit.KVList[0].Key != nil
+	assigned := make(map[int]bool)
+	for index, kv := range lit.KVList {
+		if kv.Value == nil {
+			env.Errorf(file, lit.Pos(), "missing field value in %s", desc)
+			return nil
+		}
+		if haskeys != (kv.Key != nil) {
+			env.Errorf(file, kv.Value.Pos(), "mixed key:value and value in %s", desc)
+			return nil
+		}
+		// Get the field description, either from the key or the index.
+		var field vdl.Field
+		if kv.Key != nil {
+			// There is an explicit field name specified.
+			fname, ok := kv.Key.(*parse.ConstNamed)
+			if !ok {
+				env.Errorf(file, kv.Key.Pos(), "invalid field name %q in %s", kv.Key.String(), desc)
+				return nil
+			}
+			field, index = t.FieldByName(fname.Name)
+			if index < 0 {
+				env.Errorf(file, kv.Key.Pos(), "unknown field %q in %s", fname.Name, desc)
+				return nil
+			}
+		} else {
+			// No field names, just use the index position.
+			if index >= t.NumField() {
+				env.Errorf(file, kv.Value.Pos(), "too many fields in %s", desc)
+				return nil
+			}
+			field = t.Field(index)
+		}
+		// Make sure the field hasn't been assigned already.
+		if assigned[index] {
+			env.Errorf(file, kv.Value.Pos(), "duplicate field %q in %s", field.Name, desc)
+			return nil
+		}
+		assigned[index] = true
+		// Evaluate the value and perform the assignment.
+		value := compileConst("struct field", field.Type, kv.Value, file, env)
+		if value == nil {
+			return nil
+		}
+		structv.StructField(index).Assign(value)
+	}
+	if !haskeys && 0 < len(assigned) && len(assigned) < t.NumField() {
+		env.Errorf(file, lit.Pos(), "too few fields in %s", desc)
+		return nil
+	}
+	return structv
+}
+
+func evalUnionLit(t *vdl.Type, lit *parse.ConstCompositeLit, file *File, env *Env) *vdl.Value {
+	// We require exactly one kv with an explicit key.
+	unionv := vdl.ZeroValue(t)
+	desc := fmt.Sprintf("%v union literal", t)
+	if len(lit.KVList) != 1 {
+		env.Errorf(file, lit.Pos(), "invalid %s (must have exactly one entry)", desc)
+		return nil
+	}
+	kv := lit.KVList[0]
+	if kv.Key == nil || kv.Value == nil {
+		env.Errorf(file, lit.Pos(), "invalid %s (must have explicit key and value)", desc)
+		return nil
+	}
+	// Get the field description.
+	fname, ok := kv.Key.(*parse.ConstNamed)
+	if !ok {
+		env.Errorf(file, kv.Key.Pos(), "invalid field name %q in %s", kv.Key.String(), desc)
+		return nil
+	}
+	field, index := t.FieldByName(fname.Name)
+	if index < 0 {
+		env.Errorf(file, kv.Key.Pos(), "unknown field %q in %s", fname.Name, desc)
+		return nil
+	}
+	// Evaluate the value and perform the assignment.
+	value := compileConst("union field", field.Type, kv.Value, file, env)
+	if value == nil {
+		return nil
+	}
+	unionv.AssignUnionField(index, value)
+	return unionv
+}
+
+var (
+	// Built-in consts defined by the compiler.
+	NilConst   = vdl.ZeroValue(vdl.AnyType) // nil == any(nil)
+	TrueConst  = vdl.BoolValue(true)
+	FalseConst = vdl.BoolValue(false)
+)
diff --git a/lib/vdl/compile/const_test.go b/lib/vdl/compile/const_test.go
new file mode 100644
index 0000000..2419f86
--- /dev/null
+++ b/lib/vdl/compile/const_test.go
@@ -0,0 +1,1096 @@
+// 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 compile_test
+
+import (
+	"fmt"
+	"strings"
+	"testing"
+
+	"v.io/v23/vdl"
+	"v.io/x/ref/lib/vdl/build"
+	"v.io/x/ref/lib/vdl/compile"
+	"v.io/x/ref/lib/vdl/internal/vdltest"
+)
+
+func testConstPackage(t *testing.T, name string, tpkg constPkg, env *compile.Env) *compile.Package {
+	// Compile the package with a single file, and adding the "package foo"
+	// prefix to the source data automatically.
+	files := map[string]string{
+		tpkg.Name + ".vdl": "package " + tpkg.Name + "\n" + tpkg.Data,
+	}
+	pkgPath := "p.kg/" + tpkg.Name // use dots in pkgpath to test tricky cases
+	buildPkg := vdltest.FakeBuildPackage(tpkg.Name, pkgPath, files)
+	pkg := build.BuildPackage(buildPkg, env)
+	vdltest.ExpectResult(t, env.Errors, name, tpkg.ErrRE)
+	if pkg == nil || tpkg.ErrRE != "" {
+		return nil
+	}
+	matchConstRes(t, name, tpkg, pkg.Files[0].ConstDefs)
+	return pkg
+}
+
+func matchConstRes(t *testing.T, tname string, tpkg constPkg, cdefs []*compile.ConstDef) {
+	if tpkg.ExpectRes == nil {
+		return
+	}
+	// Look for a ConstDef called "Res" to compare our expected results.
+	for _, cdef := range cdefs {
+		if cdef.Name == "Res" {
+			if got, want := cdef.Value, tpkg.ExpectRes; !vdl.EqualValue(got, want) {
+				t.Errorf("%s value got %s, want %s", tname, got, want)
+			}
+			return
+		}
+	}
+	t.Errorf("%s couldn't find Res in package %s", tname, tpkg.Name)
+}
+
+func testConfigFile(t *testing.T, name string, tpkg constPkg, env *compile.Env) {
+	// Take advantage of the fact that vdl files and config files have very
+	// similar syntax.  Just prefix the data with "config Res\n" rather than
+	// "package a\n" and we have a valid config file.
+	fname := tpkg.Name + ".config"
+	data := "config = Res\n" + tpkg.Data
+	config := build.BuildConfig(fname, strings.NewReader(data), nil, nil, env)
+	vdltest.ExpectResult(t, env.Errors, name, tpkg.ErrRE)
+	if config == nil || tpkg.ErrRE != "" {
+		return
+	}
+	if got, want := config, tpkg.ExpectRes; !vdl.EqualValue(got, want) {
+		t.Errorf("%s value got %s, want %s", name, got, want)
+	}
+}
+
+func TestConst(t *testing.T) {
+	for _, test := range constTests {
+		env := compile.NewEnv(-1)
+		for _, tpkg := range test.Pkgs {
+			testConstPackage(t, test.Name, tpkg, env)
+		}
+	}
+}
+
+func TestConfig(t *testing.T) {
+	for _, test := range constTests {
+		env := compile.NewEnv(-1)
+		// Compile all but the last tpkg as regular packages.
+		for _, tpkg := range test.Pkgs[:len(test.Pkgs)-1] {
+			testConstPackage(t, test.Name, tpkg, env)
+		}
+		// Compile the last tpkg as a regular package to see if it defines anything
+		// other than consts.
+		last := test.Pkgs[len(test.Pkgs)-1]
+		pkg := testConstPackage(t, test.Name, last, env)
+		if pkg == nil ||
+			len(pkg.Files[0].ErrorDefs) > 0 ||
+			len(pkg.Files[0].TypeDefs) > 0 ||
+			len(pkg.Files[0].Interfaces) > 0 {
+			continue // has non-const stuff, can't be a valid config file
+		}
+		// Finally compile the config file.
+		testConfigFile(t, test.Name, last, env)
+	}
+}
+
+func namedZero(name string, base *vdl.Type) *vdl.Value {
+	return vdl.ZeroValue(vdl.NamedType(name, base))
+}
+
+func makeIntList(vals ...int64) *vdl.Value {
+	listv := vdl.ZeroValue(vdl.ListType(vdl.Int64Type)).AssignLen(len(vals))
+	for index, v := range vals {
+		listv.Index(index).AssignInt(v)
+	}
+	return listv
+}
+
+func makeIntArray(name string, vals ...int64) *vdl.Value {
+	arrayv := vdl.ZeroValue(vdl.NamedType(name, vdl.ArrayType(len(vals), vdl.Int64Type)))
+	for index, v := range vals {
+		arrayv.Index(index).AssignInt(v)
+	}
+	return arrayv
+}
+
+func makeByteList(vals ...byte) *vdl.Value {
+	arrayv := vdl.ZeroValue(vdl.ListType(vdl.ByteType)).AssignLen(len(vals))
+	for index, v := range vals {
+		arrayv.Index(index).AssignByte(v)
+	}
+	return arrayv
+}
+
+func makeByteArray(name string, vals ...byte) *vdl.Value {
+	arrayv := vdl.ZeroValue(vdl.NamedType(name, vdl.ArrayType(len(vals), vdl.ByteType)))
+	for index, v := range vals {
+		arrayv.Index(index).AssignByte(v)
+	}
+	return arrayv
+}
+
+func makeStringSet(keys ...string) *vdl.Value {
+	setv := vdl.ZeroValue(vdl.SetType(vdl.StringType))
+	for _, k := range keys {
+		setv.AssignSetKey(vdl.StringValue(k))
+	}
+	return setv
+}
+
+func makeStringIntMap(m map[string]int64) *vdl.Value {
+	mapv := vdl.ZeroValue(vdl.MapType(vdl.StringType, vdl.Int64Type))
+	for k, v := range m {
+		mapv.AssignMapIndex(vdl.StringValue(k), vdl.Int64Value(v))
+	}
+	return mapv
+}
+
+func makeStructType(name string) *vdl.Type {
+	return vdl.NamedType(name, vdl.StructType([]vdl.Field{
+		{"X", vdl.Int64Type}, {"Y", vdl.StringType}, {"Z", vdl.BoolType},
+	}...))
+}
+
+func makeStruct(name string, x int64, y string, z bool) *vdl.Value {
+	structv := vdl.ZeroValue(makeStructType(name))
+	structv.StructField(0).AssignInt(x)
+	structv.StructField(1).AssignString(y)
+	structv.StructField(2).AssignBool(z)
+	return structv
+}
+
+func makeUnionType(name string) *vdl.Type {
+	return vdl.NamedType(name, vdl.UnionType([]vdl.Field{
+		{"X", vdl.Int64Type}, {"Y", vdl.StringType}, {"Z", vdl.BoolType},
+	}...))
+}
+
+func makeUnion(name string, val interface{}) *vdl.Value {
+	unionv := vdl.ZeroValue(makeUnionType(name))
+	switch tval := val.(type) {
+	case int64:
+		unionv.AssignUnionField(0, vdl.Int64Value(tval))
+	case string:
+		unionv.AssignUnionField(1, vdl.StringValue(tval))
+	case bool:
+		unionv.AssignUnionField(2, vdl.BoolValue(tval))
+	default:
+		panic(fmt.Errorf("makeUnion unhandled %T %v", val, val))
+	}
+	return unionv
+}
+
+func makeStructTypeObjectType(name string) *vdl.Type {
+	return vdl.NamedType(name, vdl.StructType(vdl.Field{
+		Name: "T",
+		Type: vdl.TypeObjectType,
+	}))
+}
+
+func makeStructTypeObject(name string, t *vdl.Type) *vdl.Value {
+	structv := vdl.ZeroValue(makeStructTypeObjectType(name))
+	structv.StructField(0).AssignTypeObject(t)
+	return structv
+}
+
+func makeABStruct() *vdl.Value {
+	tA := vdl.NamedType("p.kg/a.A", vdl.StructType([]vdl.Field{
+		{"X", vdl.Int64Type}, {"Y", vdl.StringType},
+	}...))
+	tB := vdl.NamedType("p.kg/a.B", vdl.StructType(vdl.Field{
+		Name: "Z",
+		Type: vdl.ListType(tA),
+	}))
+	res := vdl.ZeroValue(tB)
+	listv := res.StructField(0).AssignLen(2)
+	listv.Index(0).StructField(0).AssignInt(1)
+	listv.Index(0).StructField(1).AssignString("a")
+	listv.Index(1).StructField(0).AssignInt(2)
+	listv.Index(1).StructField(1).AssignString("b")
+	return res
+}
+
+func makeEnumXYZ(name, label string) *vdl.Value {
+	t := vdl.NamedType(name, vdl.EnumType("X", "Y", "Z"))
+	return vdl.ZeroValue(t).AssignEnumLabel(label)
+}
+
+func makeInnerEnum(label string) *vdl.Value {
+	tA := vdl.NamedType("p.kg/a.A", vdl.EnumType("X", "Y", "Z"))
+	tB := vdl.NamedType("p.kg/a.B", vdl.StructType(vdl.Field{
+		Name: "A",
+		Type: tA,
+	}))
+	res := vdl.ZeroValue(tB)
+	res.StructField(0).AssignEnumLabel(label)
+	return res
+}
+
+func makeCyclicStructType() *vdl.Type {
+	// type A struct {X string;Z ?A}
+	var builder vdl.TypeBuilder
+	a := builder.Struct().AppendField("X", vdl.StringType)
+	n := builder.Named("p.kg/a.A").AssignBase(a)
+	a.AppendField("Z", builder.Optional().AssignElem(n))
+	builder.Build()
+	ty, err := n.Built()
+	if err != nil {
+		panic(fmt.Errorf("Builder failed: %v", err))
+	}
+	return ty
+}
+
+func makeCyclicStruct(x string, z *vdl.Value) *vdl.Value {
+	ty := makeCyclicStructType()
+	ret := vdl.ZeroValue(ty)
+	ret.StructField(0).AssignString(x)
+	if z != nil {
+		ret.StructField(1).Assign(vdl.OptionalValue(z))
+	}
+	return ret
+}
+
+type constPkg struct {
+	Name      string
+	Data      string
+	ExpectRes *vdl.Value
+	ErrRE     string
+}
+
+type cp []constPkg
+
+var constTests = []struct {
+	Name string
+	Pkgs cp
+}{
+	// Test literals.
+	{
+		"UntypedBool",
+		cp{{"a", `const Res = true`, vdl.BoolValue(true), ""}}},
+	{
+		"UntypedString",
+		cp{{"a", `const Res = "abc"`, vdl.StringValue("abc"), ""}}},
+	{
+		"UntypedInteger",
+		cp{{"a", `const Res = 123`, nil,
+			`invalid const \(123 must be assigned a type\)`}}},
+	{
+		"UntypedFloat",
+		cp{{"a", `const Res = 1.5`, nil,
+			`invalid const \(1\.5 must be assigned a type\)`}}},
+	{
+		"UntypedComplex",
+		cp{{"a", `const Res = 3.4+9.8i`, nil,
+			`invalid const \(3\.4\+9\.8i must be assigned a type\)`}}},
+
+	// Test list literals.
+	{
+		"IntList",
+		cp{{"a", `const Res = []int64{0,1,2}`, makeIntList(0, 1, 2), ""}}},
+	{
+		"IntListKeys",
+		cp{{"a", `const Res = []int64{1:1, 2:2, 0:0}`, makeIntList(0, 1, 2), ""}}},
+	{
+		"IntListMixedKey",
+		cp{{"a", `const Res = []int64{1:1, 2, 0:0}`, makeIntList(0, 1, 2), ""}}},
+	{
+		"IntListDupKey",
+		cp{{"a", `const Res = []int64{2:2, 1:1, 0}`, nil, "duplicate index 2"}}},
+	{
+		"IntListInvalidIndex",
+		cp{{"a", `const Res = []int64{"a":2, 1:1, 2:2}`, nil, `can't convert "a" to uint64`}}},
+	{
+		"IntListInvalidValue",
+		cp{{"a", `const Res = []int64{0,1,"c"}`, nil, "invalid list value"}}},
+	{
+		"IndexingNamedList",
+		cp{{"a", `const A = []int64{3,4,2}; const Res=A[1]`, vdl.Int64Value(4), ""}}},
+	{
+		"IndexingUnnamedList",
+		cp{{"a", `const Res = []int64{3,4,2}[1]`, nil, "cannot apply index operator to unnamed constant"}}},
+	{
+		"TypedListIndexing",
+		cp{{"a", `const A = []int64{3,4,2};  const Res = A[int16(1)]`, vdl.Int64Value(4), ""}}},
+	{
+		"NegativeListIndexing",
+		cp{{"a", `const A = []int64{3,4,2}; const Res = A[-1]`, nil, `\(const -1 overflows uint64\)`}}},
+	{
+		"OutOfRangeListIndexing",
+		cp{{"a", `const A = []int64{3,4,2}; const Res = A[10]`, nil, "index 10 out of range"}}},
+	{
+		"InvalidIndexType",
+		cp{{"a", `const A = []int64{3,4,2}; const Res = A["ok"]`, nil, "invalid list index"}}},
+	{
+		"InvalidIndexBaseType",
+		cp{{"a", `type A struct{}; const B = A{}; const Res = B["ok"]`, nil, "illegal use of index operator with unsupported type"}}},
+
+	// Test array literals.
+	{
+		"IntArray",
+		cp{{"a", `type T [3]int64; const Res = T{0,1,2}`, makeIntArray("p.kg/a.T", 0, 1, 2), ""}}},
+	{
+		"IntArrayShorterInit",
+		cp{{"a", `type T [3]int64; const Res = T{0,1}`, makeIntArray("p.kg/a.T", 0, 1, 0), ""}}},
+	{
+		"IntArrayLongerInit",
+		cp{{"a", `type T [3]int64; const Res = T{0,1,2,3}`, nil, "index 3 out of range"}}},
+	{
+		"IntArrayKeys",
+		cp{{"a", `type T [3]int64; const Res = T{1:1, 2:2, 0:0}`, makeIntArray("p.kg/a.T", 0, 1, 2), ""}}},
+	{
+		"IntArrayMixedKey",
+		cp{{"a", `type T [3]int64; const Res = T{1:1, 2, 0:0}`, makeIntArray("p.kg/a.T", 0, 1, 2), ""}}},
+	{
+		"IntArrayDupKey",
+		cp{{"a", `type T [3]int64; const Res = T{2:2, 1:1, 0}`, nil, "duplicate index 2"}}},
+	{
+		"IntArrayInvalidIndex",
+		cp{{"a", `type T [3]int64; const Res = T{"a":2, 1:1, 2:2}`, nil, `can't convert "a" to uint64`}}},
+	{
+		"IntArrayInvalidValue",
+		cp{{"a", `type T [3]int64; const Res = T{0,1,"c"}`, nil, "invalid array value"}}},
+	{
+		"IndexingNamedList",
+		cp{{"a", `type T [3]int64; const A = T{3,4,2}; const Res=A[1]`, vdl.Int64Value(4), ""}}},
+	{
+		"IndexingUnnamedArray",
+		cp{{"a", `type T [3]int64; const Res = T{3,4,2}[1]`, nil, "cannot apply index operator to unnamed constant"}}},
+	{
+		"TypedArrayIndexing",
+		cp{{"a", `type T [3]int64; const A = T{3,4,2};  const Res = A[int16(1)]`, vdl.Int64Value(4), ""}}},
+	{
+		"NegativeArrayIndexing",
+		cp{{"a", `type T [3]int64; const A = T{3,4,2}; const Res = A[-1]`, nil, `\(const -1 overflows uint64\)`}}},
+	{
+		"OutOfRangeArrayIndexing",
+		cp{{"a", `type T [3]int64; const A = T{3,4,2}; const Res = A[10]`, nil, "index 10 out of range"}}},
+	{
+		"InvalidIndexType",
+		cp{{"a", `type T [3]int64; const A = T{3,4,2}; const Res = A["ok"]`, nil, "invalid array index"}}},
+
+	// Test byte list literals.
+	{
+		"ByteList",
+		cp{{"a", `const Res = []byte{0,1,2}`, makeByteList(0, 1, 2), ""}}},
+
+	// Test byte array literals.
+	{
+		"ByteArray",
+		cp{{"a", `type T [3]byte; const Res = T{0,1,2}`, makeByteArray("p.kg/a.T", 0, 1, 2), ""}}},
+	{
+		"ByteArrayShorterInit",
+		cp{{"a", `type T [3]byte; const Res = T{0,1}`, makeByteArray("p.kg/a.T", 0, 1, 0), ""}}},
+	{
+		"ByteArrayLongerInit",
+		cp{{"a", `type T [3]byte; const Res = T{0,1,2,3}`, nil, "index 3 out of range"}}},
+
+	// Test set literals.
+	{
+		"StringSet",
+		cp{{"a", `const Res = set[string]{"a","b","c"}`, makeStringSet("a", "b", "c"), ""}}},
+	{
+		"StringSetInvalidIndex",
+		cp{{"a", `const Res = set[string]{"a","b","c":3}`, nil, "invalid index"}}},
+	{
+		"StringSetDupKey",
+		cp{{"a", `const Res = set[string]{"a","b","b"}`, nil, "duplicate key"}}},
+	{
+		"StringSetInvalidKey",
+		cp{{"a", `const Res = set[string]{"a","b",3}`, nil, "invalid set key"}}},
+
+	// Test map literals.
+	{
+		"StringIntMap",
+		cp{{"a", `const Res = map[string]int64{"a":1, "b":2, "c":3}`, makeStringIntMap(map[string]int64{"a": 1, "b": 2, "c": 3}), ""}}},
+	{
+		"StringIntMapNoKey",
+		cp{{"a", `const Res = map[string]int64{"a":1, "b":2, 3}`, nil, "missing key"}}},
+	{
+		"StringIntMapDupKey",
+		cp{{"a", `const Res = map[string]int64{"a":1, "b":2, "a":3}`, nil, "duplicate key"}}},
+	{
+		"StringIntMapInvalidKey",
+		cp{{"a", `const Res = map[string]int64{"a":1, "b":2, 3:3}`, nil, "invalid map key"}}},
+	{
+		"StringIntMapInvalidValue",
+		cp{{"a", `const Res = map[string]int64{"a":1, "b":2, "c":"c"}`, nil, "invalid map value"}}},
+	{
+		"MapIndexing",
+		cp{{"a", `const A = map[int64]int64{1:4}; const Res=A[1]`, vdl.Int64Value(4), ""}}},
+	{
+		"MapUnnamedIndexing",
+		cp{{"a", `const Res = map[int64]int64{1:4}[1]`, nil, "cannot apply index operator to unnamed constant"}}},
+	{
+		"MapTypedIndexing",
+		cp{{"a", `const A = map[int64]int64{1:4}; const Res = A[int64(1)]`, vdl.Int64Value(4), ""}}},
+	{
+		"MapIncorrectlyTypedIndexing",
+		cp{{"a", `const A = map[int64]int64{1:4};const Res = A[int16(1)]`, nil, `invalid map key \(int64 not assignable from int16\(1\)\)`}}},
+	{
+		"MapIndexingMissingValue",
+		cp{{"a", `const A = map[int64]int64{1:4}; const Res = A[0]`, nil, `map key int64\(0\) not found in map`}}},
+
+	// Test struct literals.
+	{
+		"StructNoKeys",
+		cp{{"a", `type A struct{X int64;Y string;Z bool}; const Res = A{1,"b",true}`, makeStruct("p.kg/a.A", 1, "b", true), ""}}},
+	{
+		"StructKeys",
+		cp{{"a", `type A struct{X int64;Y string;Z bool}; const Res = A{X:1,Y:"b",Z:true}`, makeStruct("p.kg/a.A", 1, "b", true), ""}}},
+	{
+		"StructKeysShort",
+		cp{{"a", `type A struct{X int64;Y string;Z bool}; const Res = A{Y:"b"}`, makeStruct("p.kg/a.A", 0, "b", false), ""}}},
+	{
+		"StructMixedKeys",
+		cp{{"a", `type A struct{X int64;Y string;Z bool}; const Res = A{X:1,"b",Z:true}`, nil, "mixed key:value and value"}}},
+	{
+		"StructInvalidFieldName",
+		cp{{"a", `type A struct{X int64;Y string;Z bool}; const Res = A{1+1:1}`, nil, `invalid field name`}}},
+	{
+		"StructUnknownFieldName",
+		cp{{"a", `type A struct{X int64;Y string;Z bool}; const Res = A{ZZZ:1}`, nil, `unknown field "ZZZ"`}}},
+	{
+		"StructDupFieldName",
+		cp{{"a", `type A struct{X int64;Y string;Z bool}; const Res = A{X:1,X:2}`, nil, `duplicate field "X"`}}},
+	{
+		"StructTooManyFields",
+		cp{{"a", `type A struct{X int64;Y string;Z bool}; const Res = A{1,"b",true,4}`, nil, `too many fields`}}},
+	{
+		"StructTooFewFields",
+		cp{{"a", `type A struct{X int64;Y string;Z bool}; const Res = A{1,"b"}`, nil, `too few fields`}}},
+	{
+		"StructInvalidField",
+		cp{{"a", `type A struct{X int64;Y string;Z bool}; const Res = A{Y:1}`, nil, "invalid struct field"}}},
+	{
+		"ImplicitSubTypes",
+		cp{{"a", `type A struct{X int64;Y string}; type B struct{Z []A}; const Res = B{{{1, "a"}, A{X:2,Y:"b"}}}`, makeABStruct(), ""}}},
+	{
+		"StructSelector",
+		cp{{"a", `type A struct{X int64;Y string}; const x = A{2,"b"}; const Res = x.Y`, vdl.StringValue("b"), ""}}},
+	{
+		"StructMultipleSelector",
+		cp{{"a", `type A struct{X int64;Y B}; type B struct{Z bool}; const x = A{2,B{true}}; const Res = x.Y.Z`, vdl.BoolValue(true), ""}}},
+
+	{
+		"InvalidStructSelectorName",
+		cp{{"a", `type A struct{X int64;Y string}; const x = A{2,"b"}; const Res = x.Z`, nil, "invalid field name"}}},
+	{
+		"StructSelectorOnNonStructType",
+		cp{{"a", `type A []int32; const x = A{2}; const Res = x.Z`, nil, "invalid selector on const of kind: list"}}},
+	{
+		"SelectorOnUnnamedStruct",
+		cp{{"a", `type A struct{X int64;Y string}; const Res = A{2,"b"}.Y`, nil, "cannot apply selector operator to unnamed constant"}}},
+
+	// Test union literals.
+	{
+		"UnionX",
+		cp{{"a", `type A union{X int64;Y string;Z bool}; const Res = A{X: 123}`, makeUnion("p.kg/a.A", int64(123)), ""}}},
+	{
+		"UnionY",
+		cp{{"a", `type A union{X int64;Y string;Z bool}; const Res = A{Y: "abc"}`, makeUnion("p.kg/a.A", "abc"), ""}}},
+	{
+		"UnionZ",
+		cp{{"a", `type A union{X int64;Y string;Z bool}; const Res = A{Z: true}`, makeUnion("p.kg/a.A", true), ""}}},
+	{
+		"UnionInvalidFieldName",
+		cp{{"a", `type A union{X int64;Y string;Z bool}; const Res = A{1+1: true}`, nil, `invalid field name`}}},
+	{
+		"UnionUnknownFieldName",
+		cp{{"a", `type A union{X int64;Y string;Z bool}; const Res = A{ZZZ: true}`, nil, `unknown field "ZZZ"`}}},
+	{
+		"UnionTooManyFields",
+		cp{{"a", `type A union{X int64;Y string;Z bool}; const Res = A{X: 123, Y: "abc"}`, nil, `must have exactly one entry`}}},
+	{
+		"UnionTooFewFields",
+		cp{{"a", `type A union{X int64;Y string;Z bool}; const Res = A{}`, nil, `must have exactly one entry`}}},
+	{
+		"UnionInvalidField",
+		cp{{"a", `type A union{X int64;Y string;Z bool}; const Res = A{Y: 1}`, nil, `invalid union field`}}},
+	{
+		"UnionNoValue",
+		cp{{"a", `type A union{X int64;Y string;Z bool}; const Res = A{Y}`, nil, `must have explicit key and value`}}},
+
+	// Test optional and nil.
+	{
+		"OptionalNil",
+		cp{{"a", `type A struct{X int64;Y string;Z bool}; const Res = ?A(nil)`, vdl.ZeroValue(vdl.OptionalType(makeStructType("p.kg/a.A"))), ""}}},
+	{
+		"Optional",
+		cp{{"a", `type A struct{X int64;Y string;Z bool}; const Res = ?A{1,"b",true}`, vdl.OptionalValue(makeStruct("p.kg/a.A", 1, "b", true)), ""}}},
+	{
+		"OptionalCyclicNil",
+		cp{{"a", `type A struct{X string;Z ?A}; const Res = A{"a",nil}`, makeCyclicStruct("a", nil), ""}}},
+	{
+		"OptionalCyclic",
+		cp{{"a", `type A struct{X string;Z ?A}; const Res = A{"a",{"b",{"c",nil}}}`, makeCyclicStruct("a", makeCyclicStruct("b", makeCyclicStruct("c", nil))), ""}}},
+	{
+		"OptionalCyclicExplicitType",
+		cp{{"a", `type A struct{X string;Z ?A}; const Res = A{"a",?A{"b",?A{"c",nil}}}`, makeCyclicStruct("a", makeCyclicStruct("b", makeCyclicStruct("c", nil))), ""}}},
+	{
+		"OptionalCyclicTypeMismatch",
+		cp{{"a", `type A struct{X string;Z ?A}; const Res = A{"a","b"}`, nil, `can't convert "b" to \?p.kg/a.A`}}},
+	{
+		"OptionalCyclicExplicitTypeMismatch",
+		cp{{"a", `type A struct{X string;Z ?A}; const Res = A{"a",A{}}`, nil, `not assignable from p.kg/a.A`}}},
+
+	// Test enums.
+	{
+		"Enum",
+		cp{{"a", `type A enum{X;Y;Z}; const Res = A.X`, makeEnumXYZ("p.kg/a.A", "X"), ""}}},
+	{
+		"EnumNoLabel",
+		cp{{"a", `type A enum{X;Y;Z}; const Res = A`, nil, "A is a type"}}},
+	{
+		"InnerEnumExplicit",
+		cp{{"a", `type A enum{X;Y;Z}; type B struct{A A}; const Res = B{A: A.Y}`, makeInnerEnum("Y"), ""}}},
+	{
+		"InnerEnumImplicit",
+		cp{{"a", `type A enum{X;Y;Z}; type B struct{A A}; const Res = B{A: Z}`, makeInnerEnum("Z"), ""}}},
+
+	// Test explicit primitive type conversions.
+	{
+		"TypedBool",
+		cp{{"a", `const Res = bool(false)`, vdl.BoolValue(false), ""}}},
+	{
+		"TypedString",
+		cp{{"a", `const Res = string("abc")`, vdl.StringValue("abc"), ""}}},
+	{
+		"TypedInt32",
+		cp{{"a", `const Res = int32(123)`, vdl.Int32Value(123), ""}}},
+	{
+		"TypedFloat32",
+		cp{{"a", `const Res = float32(1.5)`, vdl.Float32Value(1.5), ""}}},
+	{
+		"TypedComplex64",
+		cp{{"a", `const Res = complex64(2+1.5i)`, vdl.Complex64Value(2 + 1.5i), ""}}},
+	{
+		"TypedBoolMismatch",
+		cp{{"a", `const Res = bool(1)`, nil,
+			"can't convert 1 to bool"}}},
+	{
+		"TypedStringMismatch",
+		cp{{"a", `const Res = string(1)`, nil,
+			"can't convert 1 to string"}}},
+	{
+		"TypedInt32Mismatch",
+		cp{{"a", `const Res = int32(true)`, nil,
+			`can't convert true to int32`}}},
+	{
+		"TypedFloat32Mismatch",
+		cp{{"a", `const Res = float32(true)`, nil,
+			`can't convert true to float32`}}},
+
+	// Test explicit user type conversions.
+	{
+		"TypedUserBool",
+		cp{{"a", `type TypedBool bool;const Res = TypedBool(true)`, namedZero("p.kg/a.TypedBool", vdl.BoolType).AssignBool(true), ""}}},
+	{
+		"TypedUserString",
+		cp{{"a", `type TypedStr string;const Res = TypedStr("abc")`, namedZero("p.kg/a.TypedStr", vdl.StringType).AssignString("abc"), ""}}},
+	{
+		"TypedUserInt32",
+		cp{{"a", `type TypedInt int32;const Res = TypedInt(123)`, namedZero("p.kg/a.TypedInt", vdl.Int32Type).AssignInt(123), ""}}},
+	{
+		"TypedUserFloat32",
+		cp{{"a", `type TypedFlt float32;const Res = TypedFlt(1.5)`, namedZero("p.kg/a.TypedFlt", vdl.Float32Type).AssignFloat(1.5), ""}}},
+	{
+		"TypedUserComplex64",
+		cp{{"a", `type TypedCpx complex64;const Res = TypedCpx(1.5+2i)`, namedZero("p.kg/a.TypedCpx", vdl.Complex64Type).AssignComplex(1.5 + 2i), ""}}},
+	{
+		"TypedUserBoolMismatch",
+		cp{{"a", `type TypedBool bool;const Res = TypedBool(1)`, nil,
+			`invalid type conversion \(can't convert 1 to p.kg/a.TypedBool bool\)`}}},
+	{
+		"TypedUserStringMismatch",
+		cp{{"a", `type TypedStr string;const Res = TypedStr(1)`, nil,
+			`invalid type conversion \(can't convert 1 to p.kg/a.TypedStr string\)`}}},
+	{
+		"TypedUserInt32Mismatch",
+		cp{{"a", `type TypedInt int32;const Res = TypedInt(true)`, nil,
+			`can't convert true to p.kg/a.TypedInt int32`}}},
+	{
+		"TypedUserFloat32Mismatch",
+		cp{{"a", `type TypedFlt float32;const Res = TypedFlt(true)`, nil,
+			`can't convert true to p.kg/a.TypedFlt float32`}}},
+
+	// Test typeobject consts.
+	{
+		"TypeObjectBool",
+		cp{{"a", `const Res = typeobject(bool)`, vdl.TypeObjectValue(vdl.BoolType), ""}}},
+	{
+		"TypeObjectBoolInvalid",
+		cp{{"a", `const Res = bool`, nil, "bool is a type"}}},
+	{
+		"TypeObjectString",
+		cp{{"a", `const Res = typeobject(string)`, vdl.TypeObjectValue(vdl.StringType), ""}}},
+	{
+		"TypeObjectStringInvalid",
+		cp{{"a", `const Res = string`, nil, "string is a type"}}},
+	{
+		"TypeObjectInt32",
+		cp{{"a", `const Res = typeobject(int32)`, vdl.TypeObjectValue(vdl.Int32Type), ""}}},
+	{
+		"TypeObjectInt32Invalid",
+		cp{{"a", `const Res = int32`, nil, "int32 is a type"}}},
+	{
+		"TypeObjectFloat32",
+		cp{{"a", `const Res = typeobject(float32)`, vdl.TypeObjectValue(vdl.Float32Type), ""}}},
+	{
+		"TypeObjectFloat32Invalid",
+		cp{{"a", `const Res = float32`, nil, "float32 is a type"}}},
+	{
+		"TypeObjectComplex64",
+		cp{{"a", `const Res = typeobject(complex64)`, vdl.TypeObjectValue(vdl.Complex64Type), ""}}},
+	{
+		"TypeObjectComplex64Invalid",
+		cp{{"a", `const Res = complex64`, nil, "complex64 is a type"}}},
+	{
+		"TypeObjectTypeObject",
+		cp{{"a", `const Res = typeobject(typeobject)`, vdl.TypeObjectValue(vdl.TypeObjectType), ""}}},
+	{
+		"TypeObjectTypeObjectInvalid",
+		cp{{"a", `const Res = typeobject`, nil, "syntax error"}}},
+	{
+		"TypeObjectList",
+		cp{{"a", `const Res = typeobject([]string)`, vdl.TypeObjectValue(vdl.ListType(vdl.StringType)), ""}}},
+	{
+		"TypeObjectListInvalid",
+		cp{{"a", `const Res = []string`, nil, `syntax error`}}},
+	{
+		"TypeObjectArray",
+		cp{{"a", `type T [3]int64; const Res = typeobject(T)`, vdl.TypeObjectValue(vdl.NamedType("p.kg/a.T", vdl.ArrayType(3, vdl.Int64Type))), ""}}},
+	{
+		"TypeObjectArrayInvalid",
+		cp{{"a", `const Res = [3]int64`, nil, `syntax error`}}},
+	{
+		"TypeObjectSet",
+		cp{{"a", `const Res = typeobject(set[string])`, vdl.TypeObjectValue(vdl.SetType(vdl.StringType)), ""}}},
+	{
+		"TypeObjectSetInvalid",
+		cp{{"a", `const Res = set[string]`, nil, `syntax error`}}},
+	{
+		"TypeObjectMap",
+		cp{{"a", `const Res = typeobject(map[string]int32)`, vdl.TypeObjectValue(vdl.MapType(vdl.StringType, vdl.Int32Type)), ""}}},
+	{
+		"TypeObjectMapInvalid",
+		cp{{"a", `const Res = map[string]int32`, nil, `syntax error`}}},
+	{
+		"TypeObjectStruct",
+		cp{{"a", `type A struct{X int64;Y string;Z bool}; const Res = typeobject(A)`, vdl.TypeObjectValue(makeStructType("p.kg/a.A")), ""}}},
+	{
+		"TypeObjectStructInvalid",
+		cp{{"a", `type A struct{X int64;Y string;Z bool}; const Res = A`, nil, `A is a type`}}},
+	{
+		"TypeObjectStructField",
+		cp{{"a", `type A struct{T typeobject}; const Res = A{typeobject(bool)}`, makeStructTypeObject("p.kg/a.A", vdl.BoolType), ""}}},
+	{
+		"TypeObjectStructFieldInvalid",
+		cp{{"a", `type A struct{T typeobject}; const Res = A{bool}`, nil, `bool is a type`}}},
+	{
+		"TypeObjectEnum",
+		cp{{"a", `type A enum{X;Y;Z}; const Res = typeobject(A)`, vdl.TypeObjectValue(vdl.NamedType("p.kg/a.A", vdl.EnumType("X", "Y", "Z"))), ""}}},
+	{
+		"TypeObjectEnumInvalid",
+		cp{{"a", `type A enum{X;Y;Z}; const Res = A`, nil, `A is a type`}}},
+
+	// Test named consts.
+	{
+		"NamedBool",
+		cp{{"a", `const foo = true;const Res = foo`, vdl.BoolValue(true), ""}}},
+	{
+		"NamedString",
+		cp{{"a", `const foo = "abc";const Res = foo`, vdl.StringValue("abc"), ""}}},
+	{
+		"NamedInt32",
+		cp{{"a", `const foo = int32(123);const Res = foo`, vdl.Int32Value(123), ""}}},
+	{
+		"NamedFloat32",
+		cp{{"a", `const foo = float32(1.5);const Res = foo`, vdl.Float32Value(1.5), ""}}},
+	{
+		"NamedComplex64",
+		cp{{"a", `const foo = complex64(3+2i);const Res = foo`, vdl.Complex64Value(3 + 2i), ""}}},
+	{
+		"NamedUserBool",
+		cp{{"a", `type TypedBool bool;const foo = TypedBool(true);const Res = foo`,
+			namedZero("p.kg/a.TypedBool", vdl.BoolType).AssignBool(true), ""}}},
+	{
+		"NamedUserString",
+		cp{{"a", `type TypedStr string;const foo = TypedStr("abc");const Res = foo`,
+			namedZero("p.kg/a.TypedStr", vdl.StringType).AssignString("abc"), ""}}},
+	{
+		"NamedUserInt32",
+		cp{{"a", `type TypedInt int32;const foo = TypedInt(123);const Res = foo`,
+			namedZero("p.kg/a.TypedInt", vdl.Int32Type).AssignInt(123), ""}}},
+	{
+		"NamedUserFloat32",
+		cp{{"a", `type TypedFlt float32;const foo = TypedFlt(1.5);const Res = foo`,
+			namedZero("p.kg/a.TypedFlt", vdl.Float32Type).AssignFloat(1.5), ""}}},
+	{
+		"ConstNamedI",
+		cp{{"a", `const I = true;const Res = I`, vdl.BoolValue(true), ""}}},
+
+	// Test unary ops.
+	{
+		"Not",
+		cp{{"a", `const Res = !true`, vdl.BoolValue(false), ""}}},
+	{
+		"Pos",
+		cp{{"a", `const Res = int32(+123)`, vdl.Int32Value(123), ""}}},
+	{
+		"Neg",
+		cp{{"a", `const Res = int32(-123)`, vdl.Int32Value(-123), ""}}},
+	{
+		"Complement",
+		cp{{"a", `const Res = int32(^1)`, vdl.Int32Value(-2), ""}}},
+	{
+		"TypedNot",
+		cp{{"a", `type TypedBool bool;const Res = !TypedBool(true)`, namedZero("p.kg/a.TypedBool", vdl.BoolType), ""}}},
+	{
+		"TypedPos",
+		cp{{"a", `type TypedInt int32;const Res = TypedInt(+123)`, namedZero("p.kg/a.TypedInt", vdl.Int32Type).AssignInt(123), ""}}},
+	{
+		"TypedNeg",
+		cp{{"a", `type TypedInt int32;const Res = TypedInt(-123)`, namedZero("p.kg/a.TypedInt", vdl.Int32Type).AssignInt(-123), ""}}},
+	{
+		"TypedComplement",
+		cp{{"a", `type TypedInt int32;const Res = TypedInt(^1)`, namedZero("p.kg/a.TypedInt", vdl.Int32Type).AssignInt(-2), ""}}},
+	{
+		"NamedNot",
+		cp{{"a", `const foo = bool(true);const Res = !foo`, vdl.BoolValue(false), ""}}},
+	{
+		"NamedPos",
+		cp{{"a", `const foo = int32(123);const Res = +foo`, vdl.Int32Value(123), ""}}},
+	{
+		"NamedNeg",
+		cp{{"a", `const foo = int32(123);const Res = -foo`, vdl.Int32Value(-123), ""}}},
+	{
+		"NamedComplement",
+		cp{{"a", `const foo = int32(1);const Res = ^foo`, vdl.Int32Value(-2), ""}}},
+	{
+		"ErrNot",
+		cp{{"a", `const Res = !1`, nil, `unary \! invalid \(untyped integer not supported\)`}}},
+	{
+		"ErrPos",
+		cp{{"a", `const Res = +"abc"`, nil, `unary \+ invalid \(untyped string not supported\)`}}},
+	{
+		"ErrNeg",
+		cp{{"a", `const Res = -false`, nil, `unary \- invalid \(untyped boolean not supported\)`}}},
+	{
+		"ErrComplement",
+		cp{{"a", `const Res = ^1.5`, nil, `unary \^ invalid \(converting untyped rational 1.5 to integer loses precision\)`}}},
+
+	// Test logical and comparison ops.
+	{
+		"Or",
+		cp{{"a", `const Res = true || false`, vdl.BoolValue(true), ""}}},
+	{
+		"And",
+		cp{{"a", `const Res = true && false`, vdl.BoolValue(false), ""}}},
+	{
+		"Lt11",
+		cp{{"a", `const Res = 1 < 1`, vdl.BoolValue(false), ""}}},
+	{
+		"Lt12",
+		cp{{"a", `const Res = 1 < 2`, vdl.BoolValue(true), ""}}},
+	{
+		"Lt21",
+		cp{{"a", `const Res = 2 < 1`, vdl.BoolValue(false), ""}}},
+	{
+		"Gt11",
+		cp{{"a", `const Res = 1 > 1`, vdl.BoolValue(false), ""}}},
+	{
+		"Gt12",
+		cp{{"a", `const Res = 1 > 2`, vdl.BoolValue(false), ""}}},
+	{
+		"Gt21",
+		cp{{"a", `const Res = 2 > 1`, vdl.BoolValue(true), ""}}},
+	{
+		"Le11",
+		cp{{"a", `const Res = 1 <= 1`, vdl.BoolValue(true), ""}}},
+	{
+		"Le12",
+		cp{{"a", `const Res = 1 <= 2`, vdl.BoolValue(true), ""}}},
+	{
+		"Le21",
+		cp{{"a", `const Res = 2 <= 1`, vdl.BoolValue(false), ""}}},
+	{
+		"Ge11",
+		cp{{"a", `const Res = 1 >= 1`, vdl.BoolValue(true), ""}}},
+	{
+		"Ge12",
+		cp{{"a", `const Res = 1 >= 2`, vdl.BoolValue(false), ""}}},
+	{
+		"Ge21",
+		cp{{"a", `const Res = 2 >= 1`, vdl.BoolValue(true), ""}}},
+	{
+		"Ne11",
+		cp{{"a", `const Res = 1 != 1`, vdl.BoolValue(false), ""}}},
+	{
+		"Ne12",
+		cp{{"a", `const Res = 1 != 2`, vdl.BoolValue(true), ""}}},
+	{
+		"Ne21",
+		cp{{"a", `const Res = 2 != 1`, vdl.BoolValue(true), ""}}},
+	{
+		"Eq11",
+		cp{{"a", `const Res = 1 == 1`, vdl.BoolValue(true), ""}}},
+	{
+		"Eq12",
+		cp{{"a", `const Res = 1 == 2`, vdl.BoolValue(false), ""}}},
+	{
+		"Eq21",
+		cp{{"a", `const Res = 2 == 1`, vdl.BoolValue(false), ""}}},
+
+	// Test arithmetic ops.
+	{
+		"IntPlus",
+		cp{{"a", `const Res = int32(1) + 1`, vdl.Int32Value(2), ""}}},
+	{
+		"IntMinus",
+		cp{{"a", `const Res = int32(2) - 1`, vdl.Int32Value(1), ""}}},
+	{
+		"IntTimes",
+		cp{{"a", `const Res = int32(3) * 2`, vdl.Int32Value(6), ""}}},
+	{
+		"IntDivide",
+		cp{{"a", `const Res = int32(5) / 2`, vdl.Int32Value(2), ""}}},
+	{
+		"FloatPlus",
+		cp{{"a", `const Res = float32(1) + 1`, vdl.Float32Value(2), ""}}},
+	{
+		"FloatMinus",
+		cp{{"a", `const Res = float32(2) - 1`, vdl.Float32Value(1), ""}}},
+	{
+		"FloatTimes",
+		cp{{"a", `const Res = float32(3) * 2`, vdl.Float32Value(6), ""}}},
+	{
+		"FloatDivide",
+		cp{{"a", `const Res = float32(5) / 2`, vdl.Float32Value(2.5), ""}}},
+	{
+		"ComplexPlus",
+		cp{{"a", `const Res = 3i + complex64(1+2i) + 1`, vdl.Complex64Value(2 + 5i), ""}}},
+	{
+		"ComplexMinus",
+		cp{{"a", `const Res = complex64(1+2i) -4 -1i`, vdl.Complex64Value(-3 + 1i), ""}}},
+	{
+		"ComplexTimes",
+		cp{{"a", `const Res = complex64(1+3i) * (5+1i)`, vdl.Complex64Value(2 + 16i), ""}}},
+	{
+		"ComplexDivide",
+		cp{{"a", `const Res = complex64(2+16i) / (5+1i)`, vdl.Complex64Value(1 + 3i), ""}}},
+
+	// Test integer arithmetic ops.
+	{
+		"Mod",
+		cp{{"a", `const Res = int32(8) % 3`, vdl.Int32Value(2), ""}}},
+	{
+		"BitOr",
+		cp{{"a", `const Res = int32(8) | 7`, vdl.Int32Value(15), ""}}},
+	{
+		"BitAnd",
+		cp{{"a", `const Res = int32(8) & 15`, vdl.Int32Value(8), ""}}},
+	{
+		"BitXor",
+		cp{{"a", `const Res = int32(8) ^ 5`, vdl.Int32Value(13), ""}}},
+	{
+		"UntypedFloatMod",
+		cp{{"a", `const Res = int32(8.0 % 3.0)`, vdl.Int32Value(2), ""}}},
+	{
+		"UntypedFloatBitOr",
+		cp{{"a", `const Res = int32(8.0 | 7.0)`, vdl.Int32Value(15), ""}}},
+	{
+		"UntypedFloatBitAnd",
+		cp{{"a", `const Res = int32(8.0 & 15.0)`, vdl.Int32Value(8), ""}}},
+	{
+		"UntypedFloatBitXor",
+		cp{{"a", `const Res = int32(8.0 ^ 5.0)`, vdl.Int32Value(13), ""}}},
+	{
+		"TypedFloatMod",
+		cp{{"a", `const Res = int32(float32(8.0) % 3.0)`, nil,
+			`binary % invalid \(can't convert typed float32 to integer\)`}}},
+	{
+		"TypedFloatBitOr",
+		cp{{"a", `const Res = int32(float32(8.0) | 7.0)`, nil,
+			`binary | invalid \(can't convert typed float32 to integer\)`}}},
+	{
+		"TypedFloatBitAnd",
+		cp{{"a", `const Res = int32(float32(8.0) & 15.0)`, nil,
+			`binary & invalid \(can't convert typed float32 to integer\)`}}},
+	{
+		"TypedFloatBitXor",
+		cp{{"a", `const Res = int32(float32(8.0) ^ 5.0)`, nil,
+			`binary \^ invalid \(can't convert typed float32 to integer\)`}}},
+
+	// Test shift ops.
+	{
+		"Lsh",
+		cp{{"a", `const Res = int32(8) << 2`, vdl.Int32Value(32), ""}}},
+	{
+		"Rsh",
+		cp{{"a", `const Res = int32(8) >> 2`, vdl.Int32Value(2), ""}}},
+	{
+		"UntypedFloatLsh",
+		cp{{"a", `const Res = int32(8.0 << 2.0)`, vdl.Int32Value(32), ""}}},
+	{
+		"UntypedFloatRsh",
+		cp{{"a", `const Res = int32(8.0 >> 2.0)`, vdl.Int32Value(2), ""}}},
+
+	// Test mixed ops.
+	{
+		"Mixed",
+		cp{{"a", `const F = "f";const Res = "f" == F && (1+2) == 3`, vdl.BoolValue(true), ""}}},
+	{
+		"MixedPrecedence",
+		cp{{"a", `const Res = int32(1+2*3-4)`, vdl.Int32Value(3), ""}}},
+
+	// Test uint conversion.
+	{
+		"MaxUint32",
+		cp{{"a", `const Res = uint32(4294967295)`, vdl.Uint32Value(4294967295), ""}}},
+	{
+		"MaxUint64",
+		cp{{"a", `const Res = uint64(18446744073709551615)`,
+			vdl.Uint64Value(18446744073709551615), ""}}},
+	{
+		"OverflowUint32",
+		cp{{"a", `const Res = uint32(4294967296)`, nil,
+			"const 4294967296 overflows uint32"}}},
+	{
+		"OverflowUint64",
+		cp{{"a", `const Res = uint64(18446744073709551616)`, nil,
+			"const 18446744073709551616 overflows uint64"}}},
+	{
+		"NegUint32",
+		cp{{"a", `const Res = uint32(-3)`, nil,
+			"const -3 overflows uint32"}}},
+	{
+		"NegUint64",
+		cp{{"a", `const Res = uint64(-4)`, nil,
+			"const -4 overflows uint64"}}},
+	{
+		"ZeroUint32",
+		cp{{"a", `const Res = uint32(0)`, vdl.Uint32Value(0), ""}}},
+
+	// Test int conversion.
+	{
+		"MinInt32",
+		cp{{"a", `const Res = int32(-2147483648)`, vdl.Int32Value(-2147483648), ""}}},
+	{
+		"MinInt64",
+		cp{{"a", `const Res = int64(-9223372036854775808)`,
+			vdl.Int64Value(-9223372036854775808), ""}}},
+	{
+		"MinOverflowInt32",
+		cp{{"a", `const Res = int32(-2147483649)`, nil,
+			"const -2147483649 overflows int32"}}},
+	{
+		"MinOverflowInt64",
+		cp{{"a", `const Res = int64(-9223372036854775809)`, nil,
+			"const -9223372036854775809 overflows int64"}}},
+	{
+		"MaxInt32",
+		cp{{"a", `const Res = int32(2147483647)`,
+			vdl.Int32Value(2147483647), ""}}},
+	{
+		"MaxInt64",
+		cp{{"a", `const Res = int64(9223372036854775807)`,
+			vdl.Int64Value(9223372036854775807), ""}}},
+	{
+		"MaxOverflowInt32",
+		cp{{"a", `const Res = int32(2147483648)`, nil,
+			"const 2147483648 overflows int32"}}},
+	{
+		"MaxOverflowInt64",
+		cp{{"a", `const Res = int64(9223372036854775808)`, nil,
+			"const 9223372036854775808 overflows int64"}}},
+	{
+		"ZeroInt32",
+		cp{{"a", `const Res = int32(0)`, vdl.Int32Value(0), ""}}},
+
+	// Test float conversion.
+	{
+		"SmallestFloat32",
+		cp{{"a", `const Res = float32(1.401298464324817070923729583289916131281e-45)`,
+			vdl.Float32Value(1.401298464324817070923729583289916131281e-45), ""}}},
+	{
+		"SmallestFloat64",
+		cp{{"a", `const Res = float64(4.940656458412465441765687928682213723651e-324)`,
+			vdl.Float64Value(4.940656458412465441765687928682213723651e-324), ""}}},
+	{
+		"MaxFloat32",
+		cp{{"a", `const Res = float32(3.40282346638528859811704183484516925440e+38)`,
+			vdl.Float32Value(3.40282346638528859811704183484516925440e+38), ""}}},
+	{
+		"MaxFloat64",
+		cp{{"a", `const Res = float64(1.797693134862315708145274237317043567980e+308)`,
+			vdl.Float64Value(1.797693134862315708145274237317043567980e+308), ""}}},
+	{
+		"UnderflowFloat32",
+		cp{{"a", `const Res = float32(1.401298464324817070923729583289916131280e-45)`,
+			nil, "underflows float32"}}},
+	{
+		"UnderflowFloat64",
+		cp{{"a", `const Res = float64(4.940656458412465441765687928682213723650e-324)`,
+			nil, "underflows float64"}}},
+	{
+		"OverflowFloat32",
+		cp{{"a", `const Res = float32(3.40282346638528859811704183484516925441e+38)`,
+			nil, "overflows float32"}}},
+	{
+		"OverflowFloat64",
+		cp{{"a", `const Res = float64(1.797693134862315708145274237317043567981e+308)`,
+			nil, "overflows float64"}}},
+	{
+		"ZeroFloat32",
+		cp{{"a", `const Res = float32(0)`, vdl.Float32Value(0), ""}}},
+
+	// Test complex conversion.
+	{
+		"RealComplexToFloat",
+		cp{{"a", `const Res = float64(1+0i)`, vdl.Float64Value(1), ""}}},
+	{
+		"RealComplexToInt",
+		cp{{"a", `const Res = int32(1+0i)`, vdl.Int32Value(1), ""}}},
+	{
+		"FloatToRealComplex",
+		cp{{"a", `const Res = complex64(1.5)`, vdl.Complex64Value(1.5), ""}}},
+	{
+		"IntToRealComplex",
+		cp{{"a", `const Res = complex64(2)`, vdl.Complex64Value(2), ""}}},
+
+	// Test float rounding - note that 1.1 incurs loss of precision.
+	{
+		"RoundedCompareFloat32",
+		cp{{"a", `const Res = float32(1.1) == 1.1`, vdl.BoolValue(true), ""}}},
+	{
+		"RoundedCompareFloat64",
+		cp{{"a", `const Res = float64(1.1) == 1.1`, vdl.BoolValue(true), ""}}},
+	{
+		"RoundedTruncation",
+		cp{{"a", `const Res = float64(float32(1.1)) != 1.1`, vdl.BoolValue(true), ""}}},
+
+	// Test multi-package consts
+	{"MultiPkgSameConstName", cp{
+		{"a", `const Res = true`, vdl.BoolValue(true), ""},
+		{"b", `const Res = true`, vdl.BoolValue(true), ""}}},
+	{"MultiPkgDep", cp{
+		{"a", `const Res = x;const x = true`, vdl.BoolValue(true), ""},
+		{"b", `import "p.kg/a";const Res = a.Res && false`, vdl.BoolValue(false), ""}}},
+	{"MultiPkgDepQualifiedPath", cp{
+		{"a", `const Res = x;const x = true`, vdl.BoolValue(true), ""},
+		{"b", `import "p.kg/a";const Res = "p.kg/a".Res && false`, vdl.BoolValue(false), ""}}},
+	{"MultiPkgUnexportedConst", cp{
+		{"a", `const Res = x;const x = true`, vdl.BoolValue(true), ""},
+		{"b", `import "p.kg/a";const Res = a.x && false`, nil, "a.x undefined"}}},
+	{"MultiPkgSamePkgName", cp{
+		{"a", `const Res = true`, vdl.BoolValue(true), ""},
+		{"a", `const Res = true`, nil, "invalid recompile"}}},
+	{"MultiPkgUnimportedPkg", cp{
+		{"a", `const Res = true`, vdl.BoolValue(true), ""},
+		{"b", `const Res = a.Res && false`, nil, "a.Res undefined"}}},
+	{"RedefinitionOfImportedName", cp{
+		{"a", `const Res = true`, vdl.BoolValue(true), ""},
+		{"b", `import "p.kg/a"; const a = "test"; const Res = a`, nil, "const a name conflict"}}},
+}
diff --git a/lib/vdl/compile/error.go b/lib/vdl/compile/error.go
new file mode 100644
index 0000000..61f82e6
--- /dev/null
+++ b/lib/vdl/compile/error.go
@@ -0,0 +1,190 @@
+// 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 compile
+
+import (
+	"fmt"
+	"regexp"
+	"strconv"
+
+	"v.io/v23/i18n"
+	"v.io/v23/vdl"
+	"v.io/x/ref/lib/vdl/parse"
+)
+
+// ErrorDef represents a user-defined error definition in the compiled results.
+type ErrorDef struct {
+	NamePos                     // name, parse position and docs
+	Exported  bool              // is this error definition exported?
+	ID        string            // error ID
+	RetryCode vdl.WireRetryCode // retry action to be performed by client
+	Params    []*Field          // list of positional parameter names and types
+	Formats   []LangFmt         // list of language / format pairs
+	English   string            // English format text from Formats
+}
+
+// LangFmt represents a language / format string pair.
+type LangFmt struct {
+	Lang i18n.LangID // IETF language tag
+	Fmt  string      // i18n format string in the given language.
+}
+
+func (x *ErrorDef) String() string {
+	return fmt.Sprintf("%+v", *x)
+}
+
+// compileErrorDefs fills in pkg with compiled error definitions.
+func compileErrorDefs(pkg *Package, pfiles []*parse.File, env *Env) {
+	for index := range pkg.Files {
+		file, pfile := pkg.Files[index], pfiles[index]
+		for _, ped := range pfile.ErrorDefs {
+			name, detail := ped.Name, identDetail("error", file, ped.Pos)
+			export, err := validIdent(name, reservedNormal)
+			if err != nil {
+				env.prefixErrorf(file, ped.Pos, err, "error %s invalid name", name)
+				continue
+			}
+			if err := file.DeclareIdent(name, detail); err != nil {
+				env.prefixErrorf(file, ped.Pos, err, "error %s name conflict", name)
+				continue
+			}
+			id := pkg.Path + "." + name
+			ed := &ErrorDef{NamePos: NamePos(ped.NamePos), Exported: export, ID: id}
+			defineErrorActions(ed, name, ped.Actions, file, env)
+			ed.Params = defineErrorParams(name, ped.Params, file, env)
+			ed.Formats = defineErrorFormats(name, ped.Formats, ed.Params, file, env)
+			// We require the "en" base language for at least one of the Formats, and
+			// favor "en-US" if it exists.  This requirement is an attempt to ensure
+			// there is at least one common language across all errors.
+			for _, lf := range ed.Formats {
+				if lf.Lang == i18n.LangID("en-US") {
+					ed.English = lf.Fmt
+					break
+				}
+				if ed.English == "" && i18n.BaseLangID(lf.Lang) == i18n.LangID("en") {
+					ed.English = lf.Fmt
+				}
+			}
+			if ed.English == "" {
+				env.Errorf(file, ed.Pos, "error %s invalid (must define at least one English format)", name)
+				continue
+			}
+			file.ErrorDefs = append(file.ErrorDefs, ed)
+		}
+	}
+}
+
+func defineErrorActions(ed *ErrorDef, name string, pactions []parse.StringPos, file *File, env *Env) {
+	// We allow multiple actions to be specified in the parser, so that it's easy
+	// to add new actions in the future.
+	seenRetry := false
+	for _, pact := range pactions {
+		if retry, err := vdl.WireRetryCodeFromString(pact.String); err == nil {
+			if seenRetry {
+				env.Errorf(file, pact.Pos, "error %s action %s invalid (retry action specified multiple times)", name, pact.String)
+				continue
+			}
+			seenRetry = true
+			ed.RetryCode = retry
+			continue
+		}
+		env.Errorf(file, pact.Pos, "error %s action %s invalid (unknown action)", name, pact.String)
+	}
+}
+
+func defineErrorParams(name string, pparams []*parse.Field, file *File, env *Env) []*Field {
+	var params []*Field
+	seen := make(map[string]*parse.Field)
+	for _, pparam := range pparams {
+		pname, pos := pparam.Name, pparam.Pos
+		if pname == "" {
+			env.Errorf(file, pos, "error %s invalid (parameters must be named)", name)
+			return nil
+		}
+		if dup := seen[pname]; dup != nil {
+			env.Errorf(file, pos, "error %s param %s duplicate name (previous at %s)", name, pname, dup.Pos)
+			continue
+		}
+		seen[pname] = pparam
+		if _, err := validIdent(pname, reservedFirstRuneLower); err != nil {
+			env.prefixErrorf(file, pos, err, "error %s param %s invalid", name, pname)
+			continue
+		}
+		param := &Field{NamePos(pparam.NamePos), compileType(pparam.Type, file, env)}
+		params = append(params, param)
+	}
+	return params
+}
+
+func defineErrorFormats(name string, plfs []parse.LangFmt, params []*Field, file *File, env *Env) []LangFmt {
+	var lfs []LangFmt
+	seen := make(map[i18n.LangID]parse.LangFmt)
+	for _, plf := range plfs {
+		pos, lang, fmt := plf.Pos(), i18n.LangID(plf.Lang.String), plf.Fmt.String
+		if lang == "" {
+			env.Errorf(file, pos, "error %s has empty language identifier", name)
+			continue
+		}
+		if dup, ok := seen[lang]; ok {
+			env.Errorf(file, pos, "error %s duplicate language %s (previous at %s)", name, lang, dup.Pos())
+			continue
+		}
+		seen[lang] = plf
+		xfmt, err := xlateErrorFormat(fmt, params)
+		if err != nil {
+			env.prefixErrorf(file, pos, err, "error %s language %s format invalid", name, lang)
+			continue
+		}
+		lfs = append(lfs, LangFmt{lang, xfmt})
+	}
+	return lfs
+}
+
+// xlateErrorFormat translates the user-supplied format into the format
+// expected by i18n, mainly translating parameter names into numeric indexes.
+func xlateErrorFormat(format string, params []*Field) (string, error) {
+	const prefix = "{1:}{2:}"
+	if format == "" {
+		return prefix, nil
+	}
+	// Create a map from param name to index.  The index numbering starts at 3,
+	// since the first two params are the component and op name, and i18n formats
+	// use 1-based indices.
+	pmap := make(map[string]string)
+	for ix, param := range params {
+		pmap[param.Name] = strconv.Itoa(ix + 3)
+	}
+	tagRE, err := regexp.Compile(`\{\:?([0-9a-zA-Z_]+)\:?\}`)
+	if err != nil {
+		return "", err
+	}
+	result, pos := prefix+" ", 0
+	for _, match := range tagRE.FindAllStringSubmatchIndex(format, -1) {
+		// The tag submatch indices are available as match[2], match[3]
+		if len(match) != 4 || match[2] < pos || match[2] > match[3] {
+			return "", fmt.Errorf("internal error: bad regexp indices %v", match)
+		}
+		beg, end := match[2], match[3]
+		tag := format[beg:end]
+		if tag == "_" {
+			continue // Skip underscore tags.
+		}
+		if _, err := strconv.Atoi(tag); err == nil {
+			continue // Skip number tags.
+		}
+		xtag, ok := pmap[tag]
+		if !ok {
+			return "", fmt.Errorf("unknown param %q", tag)
+		}
+		// Replace tag with xtag in the result.
+		result += format[pos:beg]
+		result += xtag
+		pos = end
+	}
+	if end := len(format); pos < end {
+		result += format[pos:end]
+	}
+	return result, nil
+}
diff --git a/lib/vdl/compile/error_format_test.go b/lib/vdl/compile/error_format_test.go
new file mode 100644
index 0000000..2f34b03
--- /dev/null
+++ b/lib/vdl/compile/error_format_test.go
@@ -0,0 +1,62 @@
+// 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 compile
+
+import (
+	"fmt"
+	"strings"
+	"testing"
+)
+
+func TestXlateErrorFormat(t *testing.T) {
+	const pre = "{1:}{2:}"
+	tests := []struct {
+		Format string
+		Want   string
+		Err    string
+	}{
+		{``, pre, ``},
+		{`abc`, pre + ` abc`, ``},
+
+		{`{_}{:_}{_:}{:_:}`, pre + ` {_}{:_}{_:}{:_:}`, ``},
+		{`{1}{:2}{3:}{:4:}`, pre + ` {1}{:2}{3:}{:4:}`, ``},
+		{`{a}{:b}{c:}{:d:}`, pre + ` {3}{:4}{5:}{:6:}`, ``},
+
+		{`A{_}B{:_}C{_:}D{:_:}E`, pre + ` A{_}B{:_}C{_:}D{:_:}E`, ``},
+		{`A{1}B{:2}C{3:}D{:4:}E`, pre + ` A{1}B{:2}C{3:}D{:4:}E`, ``},
+		{`A{a}B{:b}C{c:}D{:d:}E`, pre + ` A{3}B{:4}C{5:}D{:6:}E`, ``},
+
+		{
+			`{_}{1}{a}{:_}{:2}{:b}{_:}{3:}{c:}{:_:}{:4:}{:d:}`,
+			pre + ` {_}{1}{3}{:_}{:2}{:4}{_:}{3:}{5:}{:_:}{:4:}{:6:}`,
+			``,
+		},
+		{
+			`A{_}B{1}C{a}D{:_}E{:2}F{:b}G{_:}H{3:}I{c:}J{:_:}K{:4:}L{:d:}M`,
+			pre + ` A{_}B{1}C{3}D{:_}E{:2}F{:4}G{_:}H{3:}I{5:}J{:_:}K{:4:}L{:6:}M`,
+			``,
+		},
+
+		{`{ {a}{b}{c} }`, pre + ` { {3}{4}{5} }`, ``},
+		{`{x{a}{b}{c}y}`, pre + ` {x{3}{4}{5}y}`, ``},
+
+		{`{foo}`, ``, `unknown param "foo"`},
+	}
+	params := []*Field{
+		{NamePos: NamePos{Name: "a"}},
+		{NamePos: NamePos{Name: "b"}},
+		{NamePos: NamePos{Name: "c"}},
+		{NamePos: NamePos{Name: "d"}},
+	}
+	for _, test := range tests {
+		xlate, err := xlateErrorFormat(test.Format, params)
+		if got, want := fmt.Sprint(err), test.Err; !strings.Contains(got, want) {
+			t.Errorf(`"%s" got error %q, want substr %q`, test.Format, got, want)
+		}
+		if got, want := xlate, test.Want; got != want {
+			t.Errorf(`"%s" got "%s", want "%s"`, test.Format, got, want)
+		}
+	}
+}
diff --git a/lib/vdl/compile/error_test.go b/lib/vdl/compile/error_test.go
new file mode 100644
index 0000000..845c511
--- /dev/null
+++ b/lib/vdl/compile/error_test.go
@@ -0,0 +1,305 @@
+// 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 compile_test
+
+import (
+	"reflect"
+	"testing"
+
+	"v.io/v23/i18n"
+	"v.io/v23/vdl"
+	"v.io/x/ref/lib/vdl/build"
+	"v.io/x/ref/lib/vdl/compile"
+	"v.io/x/ref/lib/vdl/internal/vdltest"
+	"v.io/x/ref/lib/vdl/parse"
+)
+
+func TestError(t *testing.T) {
+	for _, test := range errorTests {
+		testError(t, test)
+	}
+}
+
+func testError(t *testing.T, test errorTest) {
+	env := compile.NewEnv(-1)
+	for _, epkg := range test.Pkgs {
+		// Compile the package with a single file, and adding the "package foo"
+		// prefix to the source data automatically.
+		files := map[string]string{
+			epkg.Name + ".vdl": "package " + epkg.Name + "\n" + epkg.Data,
+		}
+		buildPkg := vdltest.FakeBuildPackage(epkg.Name, epkg.Name, files)
+		pkg := build.BuildPackage(buildPkg, env)
+		vdltest.ExpectResult(t, env.Errors, test.Name, epkg.ErrRE)
+		if pkg == nil || epkg.ErrRE != "" {
+			continue
+		}
+		matchErrorRes(t, test.Name, epkg, pkg.Files[0].ErrorDefs)
+	}
+}
+
+func matchErrorRes(t *testing.T, tname string, epkg errorPkg, edefs []*compile.ErrorDef) {
+	// Look for an ErrorDef called "Res" to compare our expected results.
+	for _, edef := range edefs {
+		if edef.ID == epkg.Name+".Res" {
+			got, want := cleanErrorDef(*edef), cleanErrorDef(epkg.Want)
+			if !reflect.DeepEqual(got, want) {
+				t.Errorf("%s got %+v, want %+v", tname, got, want)
+			}
+			return
+		}
+	}
+	t.Errorf("%s couldn't find Res in package %s", tname, epkg.Name)
+}
+
+// cleanErrorDef resets fields that we don't care about testing.
+func cleanErrorDef(ed compile.ErrorDef) compile.ErrorDef {
+	ed.NamePos = compile.NamePos{}
+	ed.Exported = false
+	ed.ID = ""
+	for _, param := range ed.Params {
+		param.Pos = parse.Pos{}
+	}
+	return ed
+}
+
+type errorPkg struct {
+	Name  string
+	Data  string
+	Want  compile.ErrorDef
+	ErrRE string
+}
+
+type ep []errorPkg
+
+type errorTest struct {
+	Name string
+	Pkgs ep
+}
+
+const (
+	en i18n.LangID = "en"
+	zh             = "zh"
+)
+
+func arg(name string, t *vdl.Type) *compile.Field {
+	arg := new(compile.Field)
+	arg.Name = name
+	arg.Type = t
+	return arg
+}
+
+const pre = "{1:}{2:} "
+
+var errorTests = []errorTest{
+	{"NoParams1", ep{{"a", `error Res() {"en":"msg1"}`,
+		compile.ErrorDef{
+			Formats: []compile.LangFmt{{en, pre + "msg1"}},
+			English: pre + "msg1",
+		},
+		"",
+	}}},
+	{"NoParams2", ep{{"a", `error Res() {"en":"msg1","zh":"msg2"}`,
+		compile.ErrorDef{
+			Formats: []compile.LangFmt{{en, pre + "msg1"}, {zh, pre + "msg2"}},
+			English: pre + "msg1",
+		},
+		"",
+	}}},
+	{"NoParamsNoRetry", ep{{"a", `error Res() {NoRetry,"en":"msg1"}`,
+		compile.ErrorDef{
+			RetryCode: vdl.WireRetryCodeNoRetry,
+			Formats:   []compile.LangFmt{{en, pre + "msg1"}},
+			English:   pre + "msg1",
+		},
+		"",
+	}}},
+	{"NoParamsRetryConnection", ep{{"a", `error Res() {RetryConnection,"en":"msg1"}`,
+		compile.ErrorDef{
+			RetryCode: vdl.WireRetryCodeRetryConnection,
+			Formats:   []compile.LangFmt{{en, pre + "msg1"}},
+			English:   pre + "msg1",
+		},
+		"",
+	}}},
+	{"NoParamsRetryRefetch", ep{{"a", `error Res() {RetryRefetch,"en":"msg1"}`,
+		compile.ErrorDef{
+			RetryCode: vdl.WireRetryCodeRetryRefetch,
+			Formats:   []compile.LangFmt{{en, pre + "msg1"}},
+			English:   pre + "msg1",
+		},
+		"",
+	}}},
+	{"NoParamsRetryBackoff", ep{{"a", `error Res() {RetryBackoff,"en":"msg1"}`,
+		compile.ErrorDef{
+			RetryCode: vdl.WireRetryCodeRetryBackoff,
+			Formats:   []compile.LangFmt{{en, pre + "msg1"}},
+			English:   pre + "msg1",
+		},
+		"",
+	}}},
+	{"NoParamsMulti", ep{{"a", `error Res() {RetryRefetch,"en":"msg1","zh":"msg2"}`,
+		compile.ErrorDef{
+			RetryCode: vdl.WireRetryCodeRetryRefetch,
+			Formats:   []compile.LangFmt{{en, pre + "msg1"}, {zh, pre + "msg2"}},
+			English:   pre + "msg1",
+		},
+		"",
+	}}},
+
+	{"WithParams1", ep{{"a", `error Res(x string, y int32) {"en":"msg1"}`,
+		compile.ErrorDef{
+			Params:  []*compile.Field{arg("x", vdl.StringType), arg("y", vdl.Int32Type)},
+			Formats: []compile.LangFmt{{en, pre + "msg1"}},
+			English: pre + "msg1",
+		},
+		"",
+	}}},
+	{"WithParams2", ep{{"a", `error Res(x string, y int32) {"en":"msg1","zh":"msg2"}`,
+		compile.ErrorDef{
+			Params:  []*compile.Field{arg("x", vdl.StringType), arg("y", vdl.Int32Type)},
+			Formats: []compile.LangFmt{{en, pre + "msg1"}, {zh, pre + "msg2"}},
+			English: pre + "msg1",
+		},
+		"",
+	}}},
+	{"WithParamsNoRetry", ep{{"a", `error Res(x string, y int32) {NoRetry,"en":"msg1"}`,
+		compile.ErrorDef{
+			Params:    []*compile.Field{arg("x", vdl.StringType), arg("y", vdl.Int32Type)},
+			RetryCode: vdl.WireRetryCodeNoRetry,
+			Formats:   []compile.LangFmt{{en, pre + "msg1"}},
+			English:   pre + "msg1",
+		},
+		"",
+	}}},
+	{"WithParamsRetryConnection", ep{{"a", `error Res(x string, y int32) {RetryConnection,"en":"msg1"}`,
+		compile.ErrorDef{
+			Params:    []*compile.Field{arg("x", vdl.StringType), arg("y", vdl.Int32Type)},
+			RetryCode: vdl.WireRetryCodeRetryConnection,
+			Formats:   []compile.LangFmt{{en, pre + "msg1"}},
+			English:   pre + "msg1",
+		},
+		"",
+	}}},
+	{"WithParamsRetryRefetch", ep{{"a", `error Res(x string, y int32) {RetryRefetch,"en":"msg1"}`,
+		compile.ErrorDef{
+			Params:    []*compile.Field{arg("x", vdl.StringType), arg("y", vdl.Int32Type)},
+			RetryCode: vdl.WireRetryCodeRetryRefetch,
+			Formats:   []compile.LangFmt{{en, pre + "msg1"}},
+			English:   pre + "msg1",
+		},
+		"",
+	}}},
+	{"WithParamsRetryBackoff", ep{{"a", `error Res(x string, y int32) {RetryBackoff,"en":"msg1"}`,
+		compile.ErrorDef{
+			Params:    []*compile.Field{arg("x", vdl.StringType), arg("y", vdl.Int32Type)},
+			RetryCode: vdl.WireRetryCodeRetryBackoff,
+			Formats:   []compile.LangFmt{{en, pre + "msg1"}},
+			English:   pre + "msg1",
+		},
+		"",
+	}}},
+	{"WithParamsMulti", ep{{"a", `error Res(x string, y int32) {RetryRefetch,"en":"msg1","zh":"msg2"}`,
+		compile.ErrorDef{
+			Params:    []*compile.Field{arg("x", vdl.StringType), arg("y", vdl.Int32Type)},
+			RetryCode: vdl.WireRetryCodeRetryRefetch,
+			Formats:   []compile.LangFmt{{en, pre + "msg1"}, {zh, pre + "msg2"}},
+			English:   pre + "msg1",
+		},
+		"",
+	}}},
+	{"WithParamsFormat", ep{{"a", `error Res(x string, y int32) {"en":"en {x} {y}","zh":"zh {y} {x}"}`,
+		compile.ErrorDef{
+			Params:  []*compile.Field{arg("x", vdl.StringType), arg("y", vdl.Int32Type)},
+			Formats: []compile.LangFmt{{en, pre + "en {3} {4}"}, {zh, pre + "zh {4} {3}"}},
+			English: pre + "en {3} {4}",
+		},
+		"",
+	}}},
+	{"WithSamePackageParam", ep{{"a", `error Res(x Bool) {"en":"en {x}"};type Bool bool`,
+		compile.ErrorDef{
+			Params:  []*compile.Field{arg("x", vdl.NamedType("a.Bool", vdl.BoolType))},
+			Formats: []compile.LangFmt{{en, pre + "en {3}"}},
+			English: pre + "en {3}",
+		},
+		"",
+	}}},
+
+	// Test multi-package errors.
+	{"MultiPkgSameErrorName", ep{
+		{
+			"a", `error Res() {"en":"msg1"}`,
+			compile.ErrorDef{
+				Formats: []compile.LangFmt{{en, pre + "msg1"}},
+				English: pre + "msg1",
+			},
+			"",
+		},
+		{
+			"b", `error Res() {"en":"msg2"}`,
+			compile.ErrorDef{
+				Formats: []compile.LangFmt{{en, pre + "msg2"}},
+				English: pre + "msg2",
+			},
+			"",
+		},
+	}},
+	{"MultiPkgTypeDep", ep{
+		{
+			"a", `error Res() {"en":"msg1"};type Bool bool`,
+			compile.ErrorDef{
+				Formats: []compile.LangFmt{{en, pre + "msg1"}},
+				English: pre + "msg1",
+			},
+			"",
+		},
+		{
+			"b", `import "a";error Res(x a.Bool) {"en":"en {x}"}`,
+			compile.ErrorDef{
+				Params:  []*compile.Field{arg("x", vdl.NamedType("a.Bool", vdl.BoolType))},
+				Formats: []compile.LangFmt{{en, pre + "en {3}"}},
+				English: pre + "en {3}",
+			},
+			"",
+		},
+	}},
+	{"RedefinitionOfImportName", ep{
+		{
+			"a", `error Res() {"en":"msg1"}`,
+			compile.ErrorDef{
+				Formats: []compile.LangFmt{{en, pre + "msg1"}},
+				English: pre + "msg1",
+			},
+			"",
+		},
+		{
+			"b", `import "a";error a() {"en":"en {}"}`, compile.ErrorDef{},
+			"error a name conflict",
+		},
+	}},
+
+	// Test errors.
+	{"NoParamsNoLangFmt1", ep{{"a", `error Res()`, compile.ErrorDef{}, englishFormat}}},
+	{"NoParamsNoLangFmt2", ep{{"a", `error Res() {}`, compile.ErrorDef{}, englishFormat}}},
+	{"NoParamsNoLangFmt3", ep{{"a", `error Res() {NoRetry}`, compile.ErrorDef{}, englishFormat}}},
+
+	{"WithParamsNoLangFmt1", ep{{"a", `error Res(x string, y int32)`, compile.ErrorDef{}, englishFormat}}},
+	{"WithParamsNoLangFmt2", ep{{"a", `error Res(x string, y int32) {}`, compile.ErrorDef{}, englishFormat}}},
+	{"WithParamsNoLangFmt3", ep{{"a", `error Res(x string, y int32) {NoRetry}`, compile.ErrorDef{}, englishFormat}}},
+
+	{"MissingParamName1", ep{{"a", `error Res(bool) {"en":"msg1"}`, compile.ErrorDef{}, "parameters must be named"}}},
+	{"MissingParamName2", ep{{"a", `error Res(bool, int32) {"en":"msg1"}`, compile.ErrorDef{}, "parameters must be named"}}},
+
+	{"UnknownType", ep{{"a", `error Res(x foo) {"en":"msg1"}`, compile.ErrorDef{}, "type foo undefined"}}},
+	{"InvalidParam", ep{{"a", `error Res(_x foo) {"en":"msg1"}`, compile.ErrorDef{}, "param _x invalid"}}},
+	{"DupParam", ep{{"a", `error Res(x bool, x int32) {"en":"msg1"}`, compile.ErrorDef{}, "param x duplicate name"}}},
+	{"UnknownAction", ep{{"a", `error Res() {Foo,"en":"msg1"}`, compile.ErrorDef{}, "unknown action"}}},
+	{"EmptyLanguage", ep{{"a", `error Res() {"":"msg"}`, compile.ErrorDef{}, "empty language"}}},
+	{"DupLanguage", ep{{"a", `error Res() {"en":"msg1","en":"msg2"}`, compile.ErrorDef{}, "duplicate language en"}}},
+	{"UnknownParam", ep{{"a", `error Res() {"en":"{foo}"}`, compile.ErrorDef{}, `unknown param "foo"`}}},
+	{"DupError", ep{{"a", `error Res() {"en":"msg1"};error Res() {"en":"msg1"}`, compile.ErrorDef{}, "error Res name conflict"}}},
+}
+
+const englishFormat = "must define at least one English format"
diff --git a/lib/vdl/compile/ident_test.go b/lib/vdl/compile/ident_test.go
new file mode 100644
index 0000000..4a61fe4
--- /dev/null
+++ b/lib/vdl/compile/ident_test.go
@@ -0,0 +1,59 @@
+// 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 compile_test
+
+import (
+	"testing"
+
+	"v.io/x/ref/lib/vdl/build"
+	"v.io/x/ref/lib/vdl/compile"
+	"v.io/x/ref/lib/vdl/internal/vdltest"
+)
+
+func TestIdentConflict(t *testing.T) {
+	tests := []struct {
+		Name string
+		Data string
+	}{
+		// Test conflicting identifiers.
+		{"Type", `type foo int64; type foo int64`},
+		{"TypeMixed", `type FoO int64; type foo int64`},
+
+		{"Const", `const foo = true; const foo = true`},
+		{"ConstMixed", `const FoO = true; const foo = true`},
+
+		{"Interface", `type foo interface{}; type foo interface{}`},
+		{"InterfaceMixed", `type FoO interface{}; type foo interface{}`},
+
+		{"Error", `error foo() {"en":"a"}; error foo() {"en":"a"}`},
+		{"ErrorMixed", `error FoO() {"en":"a"}; error foo() {"en":"a"}`},
+
+		{"TypeAndConst", `type foo int64; const foo = true`},
+		{"TypeAndConstMixed", `type FoO int64; const foo = true`},
+		{"TypeAndInterface", `type foo int64; type foo interface{}`},
+		{"TypeAndInterfaceMixed", `type FoO int64; type foo interface{}`},
+		{"TypeAndError", `type foo int64; error foo() {"en":"a"}`},
+		{"TypeAndErrorMixed", `type foo int64; error FoO() {"en":"a"}`},
+
+		{"ConstAndInterface", `const foo = true; type foo interface{}`},
+		{"ConstAndInterfaceMixed", `const FoO = true; type foo interface{}`},
+		{"ConstAndError", `const foo = true; error foo() {"en":"a"}`},
+		{"ConstAndErrorMixed", `const foo = true; error FoO() {"en":"a"}`},
+
+		{"InterfaceAndError", `type foo interface{}; error foo() {"en":"a"}`},
+		{"InterfaceAndErrorMixed", `type foo interface{}; error FoO() {"en":"a"}`},
+	}
+	for _, test := range tests {
+		env := compile.NewEnv(-1)
+		files := map[string]string{
+			test.Name + ".vdl": "package a\n" + test.Data,
+		}
+		buildPkg := vdltest.FakeBuildPackage(test.Name, test.Name, files)
+		if pkg := build.BuildPackage(buildPkg, env); pkg != nil {
+			t.Errorf("%s got package, want nil", test.Name)
+		}
+		vdltest.ExpectResult(t, env.Errors, test.Name, "name conflict")
+	}
+}
diff --git a/lib/vdl/compile/interface.go b/lib/vdl/compile/interface.go
new file mode 100644
index 0000000..fc3a161
--- /dev/null
+++ b/lib/vdl/compile/interface.go
@@ -0,0 +1,225 @@
+// 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 compile
+
+import (
+	"v.io/v23/vdl"
+	"v.io/x/lib/toposort"
+	"v.io/x/ref/lib/vdl/parse"
+)
+
+// compileInterfaces is the "entry point" to the rest of this file.  It takes
+// the interfaces defined in pfiles and compiles them into Interfaces in pkg.
+func compileInterfaces(pkg *Package, pfiles []*parse.File, env *Env) {
+	id := ifaceDefiner{pkg, pfiles, env, make(map[string]*ifaceBuilder)}
+	if id.Declare(); !env.Errors.IsEmpty() {
+		return
+	}
+	id.SortAndDefine()
+}
+
+// ifaceDefiner defines interfaces in a package.  This is split into two phases:
+// 1) Declare ensures local interface references can be resolved.
+// 2) SortAndDefine sorts in dependency order, and evaluates and defines each
+//    const.
+//
+// It holds a builders map from interface name to ifaceBuilder, where the
+// ifaceBuilder is responsible for compiling and defining a single interface.
+type ifaceDefiner struct {
+	pkg      *Package
+	pfiles   []*parse.File
+	env      *Env
+	builders map[string]*ifaceBuilder
+}
+
+type ifaceBuilder struct {
+	def  *Interface
+	pdef *parse.Interface
+}
+
+func printIfaceBuilderName(ibuilder interface{}) string {
+	return ibuilder.(*ifaceBuilder).def.Name
+}
+
+// Declare creates builders for each interface defined in the package.
+func (id ifaceDefiner) Declare() {
+	for ix := range id.pkg.Files {
+		file, pfile := id.pkg.Files[ix], id.pfiles[ix]
+		for _, pdef := range pfile.Interfaces {
+			export, err := validIdent(pdef.Name, reservedNormal)
+			if err != nil {
+				id.env.prefixErrorf(file, pdef.Pos, err, "interface %s invalid name", pdef.Name)
+				continue // keep going to catch more errors
+			}
+			detail := identDetail("interface", file, pdef.Pos)
+			if err := file.DeclareIdent(pdef.Name, detail); err != nil {
+				id.env.prefixErrorf(file, pdef.Pos, err, "interface %s name conflict", pdef.Name)
+				continue
+			}
+			def := &Interface{NamePos: NamePos(pdef.NamePos), Exported: export, File: file}
+			id.builders[pdef.Name] = &ifaceBuilder{def, pdef}
+		}
+	}
+}
+
+// Sort and define interfaces.  We sort by dependencies on other interfaces in
+// this package.  The sorting is to ensure there are no cycles.
+func (id ifaceDefiner) SortAndDefine() {
+	// Populate sorter with dependency information.  The sorting ensures that the
+	// list of interfaces within each file is topologically sorted, and also
+	// deterministic; in the absence of interface embeddings, interfaces are
+	// listed in the same order they were defined in the parsed files.
+	var sorter toposort.Sorter
+	for _, pfile := range id.pfiles {
+		for _, pdef := range pfile.Interfaces {
+			b := id.builders[pdef.Name]
+			sorter.AddNode(b)
+			for _, dep := range id.getLocalDeps(b) {
+				sorter.AddEdge(b, dep)
+			}
+		}
+	}
+	// Sort and check for cycles.
+	sorted, cycles := sorter.Sort()
+	if len(cycles) > 0 {
+		cycleStr := toposort.DumpCycles(cycles, printIfaceBuilderName)
+		first := cycles[0][0].(*ifaceBuilder)
+		id.env.Errorf(first.def.File, first.def.Pos, "package %v has cyclic interfaces: %v", id.pkg.Name, cycleStr)
+		return
+	}
+	// Define all interfaces.  Since we add the interfaces as we go and evaluate
+	// in topological order, dependencies are guaranteed to be resolvable when we
+	// get around to defining the interfaces that embed on them.
+	for _, ibuilder := range sorted {
+		b := ibuilder.(*ifaceBuilder)
+		id.define(b)
+		addIfaceDef(b.def)
+	}
+}
+
+// addIfaceDef updates our various structures to add a new interface.
+func addIfaceDef(def *Interface) {
+	def.File.Interfaces = append(def.File.Interfaces, def)
+	def.File.Package.ifaceDefs[def.Name] = def
+}
+
+// getLocalDeps returns the list of interface dependencies for b that are in
+// this package.
+func (id ifaceDefiner) getLocalDeps(b *ifaceBuilder) (deps []*ifaceBuilder) {
+	for _, pe := range b.pdef.Embeds {
+		// Embeddings of other interfaces in this package are all we care about.
+		if dep := id.builders[pe.Name]; dep != nil {
+			deps = append(deps, dep)
+		}
+	}
+	return
+}
+
+func (id ifaceDefiner) define(b *ifaceBuilder) {
+	id.defineEmbeds(b)
+	id.defineMethods(b)
+}
+
+func (id ifaceDefiner) defineEmbeds(b *ifaceBuilder) {
+	// TODO(toddw): Check for duplicate methods.
+	def, file := b.def, b.def.File
+	seen := make(map[string]*parse.NamePos)
+	for _, pe := range b.pdef.Embeds {
+		if dup := seen[pe.Name]; dup != nil {
+			id.env.Errorf(file, pe.Pos, "interface %s duplicate embedding (previous at %s)", pe.Name, dup.Pos)
+			continue // keep going to catch more errors
+		}
+		seen[pe.Name] = pe
+		// Resolve the embedded interface.
+		embed, matched := id.env.ResolveInterface(pe.Name, file)
+		if embed == nil {
+			id.env.Errorf(file, pe.Pos, "interface %s undefined", pe.Name)
+			continue // keep going to catch more errors
+		}
+		if len(matched) < len(pe.Name) {
+			id.env.Errorf(file, pe.Pos, "interface %s invalid (%s unmatched)", pe.Name, pe.Name[len(matched):])
+			continue // keep going to catch more errors
+		}
+		def.Embeds = append(def.Embeds, embed)
+	}
+}
+
+func (id ifaceDefiner) defineMethods(b *ifaceBuilder) {
+	def, file := b.def, b.def.File
+	seen := make(map[string]*parse.Method)
+	for _, pm := range b.pdef.Methods {
+		if dup := seen[pm.Name]; dup != nil {
+			id.env.Errorf(file, pm.Pos, "method %s redefined (previous at %s)", pm.Name, dup.Pos)
+			continue // keep going to catch more errors
+		}
+		seen[pm.Name] = pm
+		if err := validExportedIdent(pm.Name, reservedFirstRuneLower); err != nil {
+			id.env.Errorf(file, pm.Pos, "method %s name (%s)", pm.Name, err)
+			continue // keep going to catch more errors
+		}
+		m := &Method{NamePos: NamePos(pm.NamePos)}
+		m.InArgs = id.defineArgs(in, m.NamePos, pm.InArgs, file)
+		m.OutArgs = id.defineArgs(out, m.NamePos, pm.OutArgs, file)
+		m.InStream = id.defineStreamType(pm.InStream, file)
+		m.OutStream = id.defineStreamType(pm.OutStream, file)
+		m.Tags = id.defineTags(pm.Tags, file)
+		def.Methods = append(def.Methods, m)
+	}
+}
+
+type inout string
+
+const (
+	in  inout = "in"
+	out inout = "out"
+)
+
+func (id ifaceDefiner) defineArgs(io inout, method NamePos, pargs []*parse.Field, file *File) (args []*Field) {
+	seen := make(map[string]*parse.Field)
+	for _, parg := range pargs {
+		if dup := seen[parg.Name]; dup != nil && parg.Name != "" {
+			id.env.Errorf(file, parg.Pos, "method %s arg %s duplicate name (previous at %s)", method.Name, parg.Name, dup.Pos)
+			continue // keep going to catch more errors
+		}
+		seen[parg.Name] = parg
+		switch {
+		case io == in && parg.Name == "":
+			id.env.Errorf(file, parg.Pos, "method %s in-arg unnamed (must name all in-args)", method.Name)
+			continue // keep going to catch more errors
+		case io == out && len(pargs) > 1 && parg.Name == "":
+			id.env.Errorf(file, parg.Pos, "method %s out-arg unnamed (must name all out-args if there are more than 1)", method.Name)
+			continue // keep going to catch more errors
+		}
+		if parg.Name != "" {
+			if _, err := validIdent(parg.Name, reservedFirstRuneLower); err != nil {
+				id.env.prefixErrorf(file, parg.Pos, err, "method %s invalid arg %s", method.Name, parg.Name)
+				continue // keep going to catch more errors
+			}
+		}
+		arg := &Field{NamePos(parg.NamePos), compileType(parg.Type, file, id.env)}
+		args = append(args, arg)
+	}
+	return
+}
+
+func (id ifaceDefiner) defineStreamType(ptype parse.Type, file *File) *vdl.Type {
+	if ptype == nil {
+		return nil
+	}
+	if tn, ok := ptype.(*parse.TypeNamed); ok && tn.Name == "_" {
+		// Special-case the _ placeholder, which means there's no stream type.
+		return nil
+	}
+	return compileType(ptype, file, id.env)
+}
+
+func (id ifaceDefiner) defineTags(ptags []parse.ConstExpr, file *File) (tags []*vdl.Value) {
+	for _, ptag := range ptags {
+		if tag := compileConst("tag", nil, ptag, file, id.env); tag != nil {
+			tags = append(tags, tag)
+		}
+	}
+	return
+}
diff --git a/lib/vdl/compile/interface_test.go b/lib/vdl/compile/interface_test.go
new file mode 100644
index 0000000..0546e49
--- /dev/null
+++ b/lib/vdl/compile/interface_test.go
@@ -0,0 +1,189 @@
+// 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 compile_test
+
+import (
+	"reflect"
+	"testing"
+
+	"v.io/v23/vdl"
+	"v.io/x/ref/lib/vdl/build"
+	"v.io/x/ref/lib/vdl/compile"
+	"v.io/x/ref/lib/vdl/internal/vdltest"
+	"v.io/x/ref/lib/vdl/parse"
+)
+
+func TestInterface(t *testing.T) {
+	for _, test := range ifaceTests {
+		env := compile.NewEnv(-1)
+		for _, tpkg := range test.Pkgs {
+			// Compile the package with a single file, adding the "package a" prefix
+			// to the source data automatically.
+			files := map[string]string{
+				tpkg.Name + ".vdl": "package " + tpkg.Name + "\n" + tpkg.Data,
+			}
+			pkgPath := "p.kg/" + tpkg.Name // use dots in pkgpath to test tricky cases
+			buildPkg := vdltest.FakeBuildPackage(tpkg.Name, pkgPath, files)
+			pkg := build.BuildPackage(buildPkg, env)
+			vdltest.ExpectResult(t, env.Errors, test.Name, tpkg.ErrRE)
+			if pkg == nil || tpkg.ErrRE != "" {
+				continue
+			}
+			matchIfaceRes(t, test.Name, tpkg, pkg.Files[0].Interfaces)
+		}
+	}
+}
+
+func matchIfaceRes(t *testing.T, tname string, tpkg ifacePkg, ifaces []*compile.Interface) {
+	if tpkg.Iface == nil {
+		return
+	}
+	// Look for an interface called "Res" to compare our expected results.
+	for _, iface := range ifaces {
+		if iface.Name == "Res" {
+			if got, want := normalizeIface(*iface), normalizeIface(*tpkg.Iface); !reflect.DeepEqual(got, want) {
+				t.Errorf("%s got %v, want %v", tname, got, want)
+			}
+			return
+		}
+	}
+	t.Errorf("%s couldn't find Res in package %s", tname, tpkg.Name)
+}
+
+func normalizeIface(x compile.Interface) compile.Interface {
+	// Don't compare uninteresting portions, to make tests more succinct.
+	x.Pos = parse.Pos{}
+	x.Exported = false
+	x.File = nil
+	embeds := x.Embeds
+	x.Embeds = nil
+	for _, embed := range embeds {
+		norm := normalizeIface(*embed)
+		x.Embeds = append(x.Embeds, &norm)
+	}
+	methods := x.Methods
+	x.Methods = nil
+	for _, method := range methods {
+		norm := normalizeMethod(*method)
+		x.Methods = append(x.Methods, &norm)
+	}
+	return x
+}
+
+func normalizeMethod(x compile.Method) compile.Method {
+	x.Pos = parse.Pos{}
+	x.InArgs = normalizeArgs(x.InArgs)
+	x.OutArgs = normalizeArgs(x.OutArgs)
+	return x
+}
+
+func normalizeArgs(x []*compile.Field) (ret []*compile.Field) {
+	for _, arg := range x {
+		norm := normalizeArg(*arg)
+		ret = append(ret, &norm)
+	}
+	return
+}
+
+func normalizeArg(x compile.Field) compile.Field {
+	x.Pos = parse.Pos{}
+	return x
+}
+
+func np(name string) compile.NamePos {
+	return compile.NamePos{Name: name}
+}
+
+type ifaceTest struct {
+	Name string
+	Pkgs ip
+}
+
+type ip []ifacePkg
+
+type ifacePkg struct {
+	Name  string
+	Data  string
+	Iface *compile.Interface
+	ErrRE string
+}
+
+var ifaceTests = []ifaceTest{
+	{"Empty", ip{{"a", `type Res interface{}`, &compile.Interface{NamePos: np("Res")}, ""}}},
+	{"NoArgs", ip{{"a", `type Res interface{NoArgs() error}`,
+		&compile.Interface{
+			NamePos: np("Res"),
+			Methods: []*compile.Method{{NamePos: np("NoArgs")}},
+		},
+		"",
+	}}},
+	{"HasArgs", ip{{"a", `type Res interface{HasArgs(x bool) (string | error)}`,
+		&compile.Interface{
+			NamePos: np("Res"),
+			Methods: []*compile.Method{{
+				NamePos: np("HasArgs"),
+				InArgs:  []*compile.Field{{NamePos: np("x"), Type: vdl.BoolType}},
+				OutArgs: []*compile.Field{{Type: vdl.StringType}},
+			}},
+		},
+		"",
+	}}},
+	{"NamedOutArg", ip{{"a", `type Res interface{NamedOutArg() (s string | error)}`,
+		&compile.Interface{
+			NamePos: np("Res"),
+			Methods: []*compile.Method{{
+				NamePos: np("NamedOutArg"),
+				OutArgs: []*compile.Field{{NamePos: np("s"), Type: vdl.StringType}},
+			}},
+		},
+		"",
+	}}},
+	{"Embed", ip{{"a", `type A interface{};type Res interface{A}`,
+		&compile.Interface{
+			NamePos: np("Res"),
+			Embeds:  []*compile.Interface{{NamePos: np("A")}},
+		},
+		"",
+	}}},
+	{"MultiEmbed", ip{{"a", `type A interface{};type B interface{};type Res interface{A;B}`,
+		&compile.Interface{
+			NamePos: np("Res"),
+			Embeds:  []*compile.Interface{{NamePos: np("A")}, {NamePos: np("B")}},
+		},
+		"",
+	}}},
+	{"MultiPkgEmbed", ip{
+		{"a", `type Res interface{}`, &compile.Interface{NamePos: np("Res")}, ""},
+		{"b", `import "p.kg/a";type Res interface{a.Res}`,
+			&compile.Interface{
+				NamePos: np("Res"),
+				Embeds:  []*compile.Interface{{NamePos: np("Res")}},
+			},
+			"",
+		},
+	}},
+	{"MultiPkgEmbedQualifiedPath", ip{
+		{"a", `type Res interface{}`, &compile.Interface{NamePos: np("Res")}, ""},
+		{"b", `import "p.kg/a";type Res interface{"p.kg/a".Res}`,
+			&compile.Interface{
+				NamePos: np("Res"),
+				Embeds:  []*compile.Interface{{NamePos: np("Res")}},
+			},
+			"",
+		},
+	}},
+	{"UnmatchedEmbed", ip{{"a", `type A interface{};type Res interface{A.foobar}`, nil,
+		`\(\.foobar unmatched\)`,
+	}}},
+	{"NoErrorReturn", ip{{"a", `type Res interface{NoArgs()}`, nil,
+		`syntax error`,
+	}}},
+	{"UnnamedInArg", ip{{"a", `type Res interface{UnnamedInArg(string) error}`, nil,
+		`must name all in-args`,
+	}}},
+	{"UnnamedOutArgs", ip{{"a", `type Res interface{UnnamedOutArgs() (bool, string | error)}`, nil,
+		`must name all out-args if there are more than 1`,
+	}}},
+}
diff --git a/lib/vdl/compile/reserved_words.go b/lib/vdl/compile/reserved_words.go
new file mode 100644
index 0000000..3662935
--- /dev/null
+++ b/lib/vdl/compile/reserved_words.go
@@ -0,0 +1,155 @@
+// 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 compile
+
+import (
+	"v.io/x/ref/lib/vdl/vdlutil"
+)
+
+// reservedMode indicates which mode to perform reserved-word checking:
+//   reservedNormal         - Check identifier as-is.
+//   reservedFirstRuneLower - Check identifier with the first rune lowercased.
+type reservedMode int
+
+const (
+	reservedNormal reservedMode = iota
+	reservedFirstRuneLower
+)
+
+// reservedWord checks if identifiers are reserved after they are converted to the native form for the language.
+func reservedWord(ident string, mode reservedMode) bool {
+	return reservedWordJava(ident, mode) ||
+		reservedWordJavascript(ident, mode) ||
+		reservedWordGo(ident)
+	// TODO(bprosnitz) Other identifiers? (set, assert, raise, with, etc)
+}
+
+func reservedWordJava(ident string, mode reservedMode) bool {
+	if mode == reservedFirstRuneLower {
+		ident = vdlutil.FirstRuneToLower(ident)
+	}
+	return javaReservedWords[ident]
+}
+
+var javaReservedWords = map[string]bool{
+	"abstract":     true,
+	"assert":       true,
+	"boolean":      true,
+	"break":        true,
+	"byte":         true,
+	"case":         true,
+	"catch":        true,
+	"char":         true,
+	"class":        true,
+	"const":        true,
+	"continue":     true,
+	"default":      true,
+	"do":           true,
+	"double":       true,
+	"else":         true,
+	"enum":         true,
+	"extends":      true,
+	"final":        true,
+	"finally":      true,
+	"float":        true,
+	"for":          true,
+	"goto":         true,
+	"if":           true,
+	"implements":   true,
+	"import":       true,
+	"instanceof":   true,
+	"int":          true,
+	"interface":    true,
+	"long":         true,
+	"native":       true,
+	"new":          true,
+	"package":      true,
+	"private":      true,
+	"protected":    true,
+	"public":       true,
+	"return":       true,
+	"short":        true,
+	"static":       true,
+	"strictfp":     true,
+	"super":        true,
+	"switch":       true,
+	"synchronized": true,
+	"this":         true,
+	"throw":        true,
+	"throws":       true,
+	"transient":    true,
+	"try":          true,
+	"void":         true,
+	"volatile":     true,
+	"while":        true,
+}
+
+func reservedWordGo(ident string) bool {
+	return goReservedWords[ident]
+}
+
+var goReservedWords = map[string]bool{
+	"break":       true,
+	"case":        true,
+	"chan":        true,
+	"const":       true,
+	"continue":    true,
+	"default":     true,
+	"defer":       true,
+	"else":        true,
+	"fallthrough": true,
+	"for":         true,
+	"func":        true,
+	"go":          true,
+	"goto":        true,
+	"if":          true,
+	"import":      true,
+	"interface":   true,
+	"map":         true,
+	"package":     true,
+	"range":       true,
+	"return":      true,
+	"select":      true,
+	"struct":      true,
+	"switch":      true,
+	"type":        true,
+	"var":         true,
+}
+
+func reservedWordJavascript(ident string, mode reservedMode) bool {
+	if mode == reservedFirstRuneLower {
+		ident = vdlutil.FirstRuneToLower(ident)
+	}
+	return javascriptReservedWords[ident]
+}
+
+var javascriptReservedWords = map[string]bool{
+	"break":    true,
+	"case":     true,
+	"catch":    true,
+	"continue": true,
+	"debugger": true,
+	"default":  true,
+	//"delete":     true, // TODO(bprosnitz) Look into adding this back. This conflicts with Delete() on Content in repository.vdlutil.
+	"do":       true,
+	"else":     true,
+	"finally":  true,
+	"for":      true,
+	"function": true,
+	"if":       true,
+	//"in":         true, // TODO(bprosnitz) Look into addint this back. It conflicts with In in access/service.vdlutil.
+	"instanceof": true,
+	"new":        true,
+	"return":     true,
+	"switch":     true,
+	"this":       true,
+	"throw":      true,
+	"try":        true,
+	"typeof":     true,
+	"var":        true,
+	"void":       true,
+	"while":      true,
+	"with":       true,
+}
diff --git a/lib/vdl/compile/result.go b/lib/vdl/compile/result.go
new file mode 100644
index 0000000..3de65f8
--- /dev/null
+++ b/lib/vdl/compile/result.go
@@ -0,0 +1,549 @@
+// 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 compile
+
+import (
+	"fmt"
+	"path"
+	"regexp"
+	"strings"
+	"unicode"
+
+	"v.io/v23/vdl"
+	"v.io/v23/vdlroot/vdltool"
+	"v.io/x/ref/lib/vdl/opconst"
+	"v.io/x/ref/lib/vdl/parse"
+	"v.io/x/ref/lib/vdl/vdlutil"
+)
+
+// Env is the environment for compilation.  It contains all errors that were
+// collected during the execution - you can pass Errors to the parse phase to
+// collect all errors together.  As packages are compiled it also collects the
+// output; after a sequence of dependent packages is compiled, all compiled
+// output will be collected.
+//
+// Always create a new Env via NewEnv; the zero Env is invalid.
+type Env struct {
+	Errors    *vdlutil.Errors
+	pkgs      map[string]*Package
+	typeDefs  map[*vdl.Type]*TypeDef
+	constDefs map[*vdl.Value]*ConstDef
+
+	disallowPathQualifiers bool // Disallow syntax like "a/b/c".Type
+}
+
+// NewEnv creates a new Env, allowing up to maxErrors errors before we stop.
+func NewEnv(maxErrors int) *Env {
+	return NewEnvWithErrors(vdlutil.NewErrors(maxErrors))
+}
+
+// NewEnvWithErrors creates a new Env, using the given errs to collect errors.
+func NewEnvWithErrors(errs *vdlutil.Errors) *Env {
+	env := &Env{
+		Errors:    errs,
+		pkgs:      make(map[string]*Package),
+		typeDefs:  make(map[*vdl.Type]*TypeDef),
+		constDefs: make(map[*vdl.Value]*ConstDef),
+	}
+	// The env always starts out with the built-in package.
+	env.pkgs[BuiltInPackage.Name] = BuiltInPackage
+	for _, def := range BuiltInFile.TypeDefs {
+		env.typeDefs[def.Type] = def
+	}
+	for _, def := range BuiltInFile.ConstDefs {
+		env.constDefs[def.Value] = def
+	}
+	return env
+}
+
+// FindTypeDef returns the type definition corresponding to t, or nil if t isn't
+// a defined type.  All built-in and user-defined named types are considered
+// defined; e.g. unnamed lists don't have a corresponding type def.
+func (e *Env) FindTypeDef(t *vdl.Type) *TypeDef { return e.typeDefs[t] }
+
+// FindConstDef returns the const definition corresponding to v, or nil if v
+// isn't a defined const.  All user-defined named consts are considered defined;
+// e.g. method tags don't have a corresponding const def.
+func (e *Env) FindConstDef(v *vdl.Value) *ConstDef { return e.constDefs[v] }
+
+// ResolvePackage resolves a package path to its previous compiled results.
+func (e *Env) ResolvePackage(path string) *Package {
+	return e.pkgs[path]
+}
+
+// Resolves a name against the current package and imported package namespace.
+func (e *Env) resolve(name string, file *File) (val interface{}, matched string) {
+	// First handle package-path qualified identifiers, which look like this:
+	//   "a/b/c".Ident   (qualified with package path "a/b/c")
+	// These must be handled first, since the package-path may include dots.
+	if strings.HasPrefix(name, `"`) {
+		if parts := strings.SplitN(name[1:], `".`, 2); len(parts) == 2 {
+			path, remain := parts[0], parts[1]
+			if e.disallowPathQualifiers {
+				// TODO(toddw): Add real position.
+				e.Errorf(file, parse.Pos{}, "package path qualified identifier %s not allowed", name)
+			}
+			if file.ValidateImportPackagePath(path) {
+				if pkg := e.ResolvePackage(path); pkg != nil {
+					if dotParts := strings.Split(remain, "."); len(dotParts) > 0 {
+						if val := pkg.resolve(dotParts[0], false); val != nil {
+							return val, `"` + path + `".` + dotParts[0]
+						}
+					}
+				}
+			}
+		}
+	}
+	// Now handle built-in and package-local identifiers.  Examples:
+	//   string
+	//   TypeName
+	//   EnumType.Label
+	//   ConstName
+	//   StructConst.Field
+	//   InterfaceName
+	nameParts := strings.Split(name, ".")
+	if len(nameParts) == 0 {
+		return nil, ""
+	}
+	if builtin := BuiltInPackage.resolve(nameParts[0], false); builtin != nil {
+		return builtin, nameParts[0]
+	}
+	if local := file.Package.resolve(nameParts[0], true); local != nil {
+		return local, nameParts[0]
+	}
+	// Now handle package qualified identifiers, which look like this:
+	//   pkg.Ident   (qualified with local package identifier pkg)
+	if len(nameParts) > 1 {
+		if path := file.LookupImportPath(nameParts[0]); path != "" {
+			if pkg := e.ResolvePackage(path); pkg != nil {
+				if val := pkg.resolve(nameParts[1], false); val != nil {
+					return val, nameParts[0] + "." + nameParts[1]
+				}
+			}
+		}
+	}
+	// No match found.
+	return nil, ""
+}
+
+// ResolveType resolves a name to a type definition.
+// Returns the type def and the portion of name that was matched.
+func (e *Env) ResolveType(name string, file *File) (td *TypeDef, matched string) {
+	v, matched := e.resolve(name, file)
+	td, _ = v.(*TypeDef)
+	if td == nil {
+		return nil, ""
+	}
+	return td, matched
+}
+
+// ResolveConst resolves a name to a const definition.
+// Returns the const def and the portion of name that was matched.
+func (e *Env) ResolveConst(name string, file *File) (cd *ConstDef, matched string) {
+	v, matched := e.resolve(name, file)
+	cd, _ = v.(*ConstDef)
+	if cd == nil {
+		return nil, ""
+	}
+	return cd, matched
+}
+
+// ResolveInterface resolves a name to an interface definition.
+// Returns the interface and the portion of name that was matched.
+func (e *Env) ResolveInterface(name string, file *File) (i *Interface, matched string) {
+	v, matched := e.resolve(name, file)
+	i, _ = v.(*Interface)
+	if i == nil {
+		return nil, ""
+	}
+	return i, matched
+}
+
+// evalSelectorOnValue evaluates the selector on v.
+func (e *Env) evalSelectorOnValue(v *vdl.Value, selector string) (opconst.Const, error) {
+	for _, fieldName := range strings.Split(selector, ".") {
+		if v.Kind() != vdl.Struct {
+			return opconst.Const{}, fmt.Errorf("invalid selector on const of kind: %v", v.Type().Kind())
+		}
+		next := v.StructFieldByName(fieldName)
+		if next == nil {
+			return opconst.Const{}, fmt.Errorf("invalid field name on struct %s: %s", v, fieldName)
+		}
+		v = next
+	}
+	return opconst.FromValue(v), nil
+}
+
+// evalSelectorOnType evaluates the selector on t.
+func (e *Env) evalSelectorOnType(t *vdl.Type, selector string) (opconst.Const, error) {
+	if t.Kind() != vdl.Enum {
+		return opconst.Const{}, fmt.Errorf("invalid selector on type of kind: %v", t.Kind())
+	}
+	index := t.EnumIndex(selector)
+	if index < 0 {
+		return opconst.Const{}, fmt.Errorf("invalid label on enum %s: %s", t.Name(), selector)
+	}
+	enum := vdl.ZeroValue(t).AssignEnumIndex(index)
+	return opconst.FromValue(enum), nil
+}
+
+// EvalConst resolves and evaluates a name to a const.
+func (e *Env) EvalConst(name string, file *File) (opconst.Const, error) {
+	if cd, matched := e.ResolveConst(name, file); cd != nil {
+		if matched == name {
+			return opconst.FromValue(cd.Value), nil
+		}
+		remainder := name[len(matched)+1:]
+		c, err := e.evalSelectorOnValue(cd.Value, remainder)
+		if err != nil {
+			return opconst.Const{}, err
+		}
+		return c, nil
+	}
+	if td, matched := e.ResolveType(name, file); td != nil {
+		if matched == name {
+			return opconst.Const{}, fmt.Errorf("%s is a type", name)
+		}
+		remainder := name[len(matched)+1:]
+		c, err := e.evalSelectorOnType(td.Type, remainder)
+		if err != nil {
+			return opconst.Const{}, err
+		}
+		return c, nil
+	}
+	return opconst.Const{}, fmt.Errorf("%s undefined", name)
+}
+
+// Errorf is a helper for error reporting, to consistently contain the file and
+// position of the error when possible.
+func (e *Env) Errorf(file *File, pos parse.Pos, format string, v ...interface{}) {
+	e.Errors.Error(fpStringf(file, pos, format, v...))
+}
+
+func (e *Env) prefixErrorf(file *File, pos parse.Pos, err error, format string, v ...interface{}) {
+	e.Errors.Error(fpStringf(file, pos, format, v...) + " (" + err.Error() + ")")
+}
+
+func fpString(file *File, pos parse.Pos) string {
+	return path.Join(file.Package.Path, file.BaseName) + ":" + pos.String()
+}
+
+func fpStringf(file *File, pos parse.Pos, format string, v ...interface{}) string {
+	return fmt.Sprintf(fpString(file, pos)+" "+format, v...)
+}
+
+// DisallowPathQualifiers disables syntax like "a/b/c".Type.
+func (e *Env) DisallowPathQualifiers() *Env {
+	e.disallowPathQualifiers = true
+	return e
+}
+
+// Representation of the components of an vdl file.  These data types represent
+// the results of the compilation, used by generators for different languages.
+
+// Package represents an vdl package, containing a set of files.
+type Package struct {
+	// Name is the name of the package, specified in the vdl files.
+	// E.g. "bar"
+	Name string
+	// Path is the package path; the path used in VDL import clauses.
+	// E.g. "foo/bar".
+	Path string
+	// GenPath is the package path to use for code generation.  It is typically
+	// the same as Path, except for vdlroot standard packages.
+	// E.g. "v.io/v23/vdlroot/time"
+	GenPath string
+	// Files holds the files contained in the package.
+	Files []*File
+	// FileDoc holds the top-level file documentation, which must be the same for
+	// every file in the package.  This is typically used to hold boilerplate that
+	// must appear in every generated file, e.g. a copyright notice.
+	FileDoc string
+	// Config holds the configuration for this package, specifying options used
+	// during compilation and code generation.
+	Config vdltool.Config
+
+	// We hold some internal maps to make local name resolution cheap and easy.
+	typeDefs  map[string]*TypeDef
+	constDefs map[string]*ConstDef
+	ifaceDefs map[string]*Interface
+
+	// lowercaseIdents maps from lowercased identifier to a detail string; it's
+	// used to detect and report identifier conflicts.
+	lowercaseIdents map[string]string
+}
+
+func newPackage(name, pkgPath, genPath string, config vdltool.Config) *Package {
+	return &Package{
+		Name:            name,
+		Path:            pkgPath,
+		GenPath:         genPath,
+		Config:          config,
+		typeDefs:        make(map[string]*TypeDef),
+		constDefs:       make(map[string]*ConstDef),
+		ifaceDefs:       make(map[string]*Interface),
+		lowercaseIdents: make(map[string]string),
+	}
+}
+
+// QualifiedName returns the fully-qualified name of an identifier, by
+// prepending the identifier with the package path.
+func (p *Package) QualifiedName(id string) string {
+	if p.Path == "" {
+		return id
+	}
+	return p.Path + "." + id
+}
+
+// ResolveType resolves the type name to its definition.
+func (p *Package) ResolveType(name string) *TypeDef { return p.typeDefs[name] }
+
+// ResolveConst resolves the const name to its definition.
+func (p *Package) ResolveConst(name string) *ConstDef { return p.constDefs[name] }
+
+// ResolveInterface resolves the interface name to its definition.
+func (p *Package) ResolveInterface(name string) *Interface { return p.ifaceDefs[name] }
+
+// resolve resolves a name against the package.
+// Checks for duplicate definitions should be performed before this is called.
+func (p *Package) resolve(name string, isLocal bool) interface{} {
+	if t := p.ResolveType(name); t != nil && (t.Exported || isLocal) {
+		return t
+	}
+	if c := p.ResolveConst(name); c != nil && (c.Exported || isLocal) {
+		return c
+	}
+	if i := p.ResolveInterface(name); i != nil && (i.Exported || isLocal) {
+		return i
+	}
+	return nil
+}
+
+// File represents a compiled vdl file.
+type File struct {
+	BaseName   string       // Base name of the vdl file, e.g. "foo.vdl"
+	PackageDef NamePos      // Name, position and docs of the "package" clause
+	ErrorDefs  []*ErrorDef  // Errors defined in this file
+	TypeDefs   []*TypeDef   // Types defined in this file
+	ConstDefs  []*ConstDef  // Consts defined in this file
+	Interfaces []*Interface // Interfaces defined in this file
+	Package    *Package     // Parent package
+
+	TypeDeps    map[*vdl.Type]bool // Types the file depends on
+	PackageDeps []*Package         // Packages the file depends on, sorted by path
+
+	// Imports maps the user-supplied imports from local package name to package
+	// path.  They may be different from PackageDeps since we evaluate all consts
+	// to their final typed value.  E.g. let's say we have three vdl files:
+	//
+	//   a/a.vdl  type Foo int32; const A1 = Foo(1)
+	//   b/b.vdl  import "a";     const B1 = a.Foo(1); const B2 = a.A1 + 1
+	//   c/c.vdl  import "b";     const C1 = b.B1;     const C2 = b.B1 + 1
+	//
+	// The final type and value of the constants:
+	//   A1 = a.Foo(1); B1 = a.Foo(1); C1 = a.Foo(1)
+	//                  B2 = a.Foo(2); C2 = a.Foo(2)
+	//
+	// Note that C1 and C2 both have final type a.Foo, even though c.vdl doesn't
+	// explicitly import "a", and the generated c.go shouldn't import "b" since
+	// it's not actually used anymore.
+	imports map[string]*importPath
+}
+
+type importPath struct {
+	path string
+	pos  parse.Pos
+	used bool // was this import path ever used?
+}
+
+// LookupImportPath translates local into a package path name, based on the
+// imports associated with the file.  Returns the empty string "" if local
+// couldn't be found; every valid package path is non-empty.
+func (f *File) LookupImportPath(local string) string {
+	if imp, ok := f.imports[local]; ok {
+		imp.used = true
+		return imp.path
+	}
+	return ""
+}
+
+// ValidateImportPackagePath returns true iff path is listed in the file's
+// imports, and marks the import as used.
+func (f *File) ValidateImportPackagePath(path string) bool {
+	for _, imp := range f.imports {
+		if imp.path == path {
+			imp.used = true
+			return true
+		}
+	}
+	return false
+}
+
+// identDetail formats a detail string for calls to DeclareIdent.
+func identDetail(kind string, file *File, pos parse.Pos) string {
+	return fmt.Sprintf("%s at %s:%s", kind, file.BaseName, pos)
+}
+
+// DeclareIdent declares ident with the given detail string.  Returns an error
+// if ident conflicts with an existing identifier in this file or package, where
+// the error includes the the previous declaration detail.
+func (f *File) DeclareIdent(ident, detail string) error {
+	// Identifiers must be distinct from the the import names used in this file,
+	// but can differ by only their capitalization.  E.g.
+	//   import "foo"
+	//   type foo string // BAD, type "foo" collides with import "foo"
+	//   type Foo string //  OK, type "Foo" distinct from import "foo"
+	//   type FoO string //  OK, type "FoO" distinct from import "foo"
+	if i, ok := f.imports[ident]; ok {
+		return fmt.Errorf("previous import at %s", i.pos)
+	}
+	// Identifiers must be distinct from all other identifiers within this
+	// package, and cannot differ by only their capitalization.  E.g.
+	//   type foo string
+	//   const foo = "a" // BAD, const "foo" collides with type "foo"
+	//   const Foo = "A" // BAD, const "Foo" collides with type "foo"
+	//   const FoO = "A" // BAD, const "FoO" collides with type "foo"
+	lower := strings.ToLower(ident)
+	if prevDetail := f.Package.lowercaseIdents[lower]; prevDetail != "" {
+		return fmt.Errorf("previous %s", prevDetail)
+	}
+	f.Package.lowercaseIdents[lower] = detail
+	return nil
+}
+
+// Interface represents a set of embedded interfaces and methods.
+type Interface struct {
+	NamePos               // interface name, pos and doc
+	Exported bool         // is this interface exported?
+	Embeds   []*Interface // list of embedded interfaces
+	Methods  []*Method    // list of methods
+	File     *File        // parent file
+}
+
+// Method represents a method in an interface.
+type Method struct {
+	NamePos                // method name, pos and doc
+	InArgs    []*Field     // list of positional in-args
+	OutArgs   []*Field     // list of positional out-args
+	InStream  *vdl.Type    // in-stream type, may be nil
+	OutStream *vdl.Type    // out-stream type, may be nil
+	Tags      []*vdl.Value // list of method tags
+}
+
+// Field represents method arguments and error params.
+type Field struct {
+	NamePos           // arg name, pos and doc
+	Type    *vdl.Type // arg type, never nil
+}
+
+// NamePos represents a name, its associated position and documentation.
+type NamePos parse.NamePos
+
+func (x *Method) String() string  { return fmt.Sprintf("%+v", *x) }
+func (x *Field) String() string   { return fmt.Sprintf("%+v", *x) }
+func (x *NamePos) String() string { return fmt.Sprintf("%+v", *x) }
+func (x *Package) String() string {
+	c := *x
+	c.typeDefs = nil
+	c.constDefs = nil
+	c.ifaceDefs = nil
+	return fmt.Sprintf("%+v", c)
+}
+func (x *File) String() string {
+	c := *x
+	c.Package = nil // avoid infinite loop
+	return fmt.Sprintf("%+v", c)
+}
+func (x *Interface) String() string {
+	c := *x
+	c.File = nil // avoid infinite loop
+	return fmt.Sprintf("%+v", c)
+}
+func (x *Interface) AllMethods() []*Method {
+	result := make([]*Method, len(x.Methods))
+	copy(result, x.Methods)
+	for _, embed := range x.Embeds {
+		result = append(result, embed.AllMethods()...)
+	}
+	return result
+}
+func (x *Interface) TransitiveEmbeds() []*Interface {
+	return x.transitiveEmbeds(make(map[*Interface]bool))
+}
+func (x *Interface) transitiveEmbeds(seen map[*Interface]bool) []*Interface {
+	var ret []*Interface
+	for _, e := range x.Embeds {
+		if !seen[e] {
+			seen[e] = true
+			ret = append(ret, e)
+			ret = append(ret, e.transitiveEmbeds(seen)...)
+		}
+	}
+	return ret
+}
+
+// We might consider allowing more characters, but we'll need to ensure they're
+// allowed in all our codegen languages.
+var (
+	regexpIdent = regexp.MustCompile("^[A-Za-z][A-Za-z0-9_]*$")
+)
+
+// hasUpperAcronym returns true if s contains an uppercase acronym; if s
+// contains a run of two uppercase letters not followed by a lowercase letter.
+// The lowercase letter special-case is to allow identifiers like "AMethod".
+func hasUpperAcronym(s string) bool {
+	upperRun := 0
+	for _, r := range s {
+		switch {
+		case upperRun == 2 && !unicode.IsLower(r):
+			return true
+		case unicode.IsUpper(r):
+			upperRun++
+		default:
+			upperRun = 0
+		}
+	}
+	return upperRun >= 2
+}
+
+// validConstIdent returns (exported, err) where err is non-nil iff the
+// identifer is valid as the name of a const.  Exported is true if the
+// identifier is exported.  Valid: "^[A-Za-z][A-Za-z0-9_]*$"
+func validConstIdent(ident string, mode reservedMode) (bool, error) {
+	if re := regexpIdent; !re.MatchString(ident) {
+		return false, fmt.Errorf("allowed regexp: %q", re)
+	}
+	if reservedWord(ident, mode) {
+		return false, fmt.Errorf("reserved word in a generated language")
+	}
+	return ident[0] >= 'A' && ident[0] <= 'Z', nil
+}
+
+// validIdent is like validConstIdent, but applies to all non-const identifiers.
+// It adds an additional check for uppercase acronyms.
+func validIdent(ident string, mode reservedMode) (bool, error) {
+	exported, err := validConstIdent(ident, mode)
+	if err != nil {
+		return false, err
+	}
+	if hasUpperAcronym(ident) {
+		// TODO(toddw): Link to documentation explaining why.
+		return false, fmt.Errorf("acronyms must use CamelCase")
+	}
+	return exported, nil
+}
+
+// validExportedIdent returns a non-nil error iff the identifier is valid and
+// exported.
+func validExportedIdent(ident string, mode reservedMode) error {
+	exported, err := validIdent(ident, mode)
+	if err != nil {
+		return err
+	}
+	if !exported {
+		return fmt.Errorf("must be exported")
+	}
+	return nil
+}
diff --git a/lib/vdl/compile/result_test.go b/lib/vdl/compile/result_test.go
new file mode 100644
index 0000000..223e9ab
--- /dev/null
+++ b/lib/vdl/compile/result_test.go
@@ -0,0 +1,145 @@
+// 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 compile
+
+import (
+	"fmt"
+	"strings"
+	"testing"
+)
+
+func TestValidConstIdent(t *testing.T) {
+	tests := []struct {
+		name     string
+		exported bool
+		errstr   string
+	}{
+		{"", false, "allowed regexp"},
+		{"0FirstLetterDigit", false, "allowed regexp"},
+		{"_FirstLetterPunct", false, "allowed regexp"},
+		{" FirstLetterSpace", false, "allowed regexp"},
+		{"x.InvalidPunct", false, "allowed regexp"},
+		{"x InvalidSpace", false, "allowed regexp"},
+		{"x\nNonAlphaNum", false, "allowed regexp"},
+		{"ABC", true, ""},
+		{"xxABC", false, ""},
+		{"xxABCyy", false, ""},
+		{"ID", true, ""},
+		{"ID_", true, ""},
+		{"ID2", true, ""},
+		{"xxID", false, ""},
+		{"xxIDyy", false, ""},
+		{"A_M", true, ""},
+		{"B2B", true, ""},
+		{"X", true, ""},
+		{"Xyz", true, ""},
+		{"Xyz123", true, ""},
+		{"Xyz_123", true, ""},
+		{"x", false, ""},
+		{"xyz", false, ""},
+		{"xyz123", false, ""},
+		{"xyz_123", false, ""},
+	}
+	for _, test := range tests {
+		exported, err := validConstIdent(test.name, reservedNormal)
+		errstr := fmt.Sprint(err)
+		if test.errstr != "" && !strings.Contains(errstr, test.errstr) {
+			t.Errorf(`validConstIdent(%s) got error %q, want substr %q`, test.name, errstr, test.errstr)
+		}
+		if test.errstr == "" && err != nil {
+			t.Errorf(`validConstIdent(%s) got error %q, want nil`, test.name, errstr)
+		}
+		if got, want := exported, test.exported; got != want {
+			t.Errorf(`validConstIdent(%s) got exported %v, want %v`, test.name, got, want)
+		}
+	}
+}
+
+func TestValidIdent(t *testing.T) {
+	tests := []struct {
+		name     string
+		exported bool
+		errstr   string
+	}{
+		{"", false, "allowed regexp"},
+		{"0FirstLetterDigit", false, "allowed regexp"},
+		{"_FirstLetterPunct", false, "allowed regexp"},
+		{" FirstLetterSpace", false, "allowed regexp"},
+		{"x.InvalidPunct", false, "allowed regexp"},
+		{"x InvalidSpace", false, "allowed regexp"},
+		{"x\nNonAlphaNum", false, "allowed regexp"},
+		{"ABC", false, "acronyms must use CamelCase"},
+		{"xxABC", false, "acronyms must use CamelCase"},
+		{"xxABCyy", false, "acronyms must use CamelCase"},
+		{"ID", false, "acronyms must use CamelCase"},
+		{"ID_", false, "acronyms must use CamelCase"},
+		{"ID2", false, "acronyms must use CamelCase"},
+		{"xxID", false, "acronyms must use CamelCase"},
+		{"xxIDyy", false, ""},
+		{"A_M", true, ""},
+		{"B2B", true, ""},
+		{"X", true, ""},
+		{"Xyz", true, ""},
+		{"Xyz123", true, ""},
+		{"Xyz_123", true, ""},
+		{"x", false, ""},
+		{"xyz", false, ""},
+		{"xyz123", false, ""},
+		{"xyz_123", false, ""},
+	}
+	for _, test := range tests {
+		exported, err := validIdent(test.name, reservedNormal)
+		errstr := fmt.Sprint(err)
+		if test.errstr != "" && !strings.Contains(errstr, test.errstr) {
+			t.Errorf(`validIdent(%s) got error %q, want substr %q`, test.name, errstr, test.errstr)
+		}
+		if test.errstr == "" && err != nil {
+			t.Errorf(`validIdent(%s) got error %q, want nil`, test.name, errstr)
+		}
+		if got, want := exported, test.exported; got != want {
+			t.Errorf(`validIdent(%s) got exported %v, want %v`, test.name, got, want)
+		}
+	}
+}
+
+func TestValidExportedIdent(t *testing.T) {
+	tests := []struct {
+		ident  string
+		errstr string
+	}{
+		{"", "allowed regexp"},
+		{"xFirstLetterLower", "must be exported"},
+		{"0FirstLetterDigit", "allowed regexp"},
+		{"_FirstLetterPunct", "allowed regexp"},
+		{" FirstLetterSpace", "allowed regexp"},
+		{"X.InvalidPunct", "allowed regexp"},
+		{"X InvalidSpace", "allowed regexp"},
+		{"X\nNonAlphaNum", "allowed regexp"},
+		{"ABC", "acronyms must use CamelCase"},
+		{"XxABC", "acronyms must use CamelCase"},
+		{"XxABCyy", "acronyms must use CamelCase"},
+		{"ID", "acronyms must use CamelCase"},
+		{"ID_", "acronyms must use CamelCase"},
+		{"ID2", "acronyms must use CamelCase"},
+		{"XxID", "acronyms must use CamelCase"},
+		{"XxIDyy", ""},
+		{"A_M", ""},
+		{"B2B", ""},
+		{"X", ""},
+		{"Xyz", ""},
+		{"Xyz123", ""},
+		{"Xyz_123", ""},
+	}
+	for _, test := range tests {
+		err := validExportedIdent(test.ident, reservedNormal)
+		errstr := fmt.Sprint(err)
+		if test.errstr != "" && !strings.Contains(errstr, test.errstr) {
+			t.Errorf(`validExportedIdent(%s) got error %q, want substr %q`, test.ident, errstr, test.errstr)
+		}
+		if test.errstr == "" && err != nil {
+			t.Errorf(`validExportedIdent(%s) got error %q, want nil`, test.ident, errstr)
+		}
+	}
+}
diff --git a/lib/vdl/compile/type.go b/lib/vdl/compile/type.go
new file mode 100644
index 0000000..df873c9
--- /dev/null
+++ b/lib/vdl/compile/type.go
@@ -0,0 +1,377 @@
+// 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 compile
+
+import (
+	"fmt"
+
+	"v.io/v23/vdl"
+	"v.io/x/ref/lib/vdl/parse"
+)
+
+// TypeDef represents a user-defined named type definition in the compiled
+// results.
+type TypeDef struct {
+	NamePos            // name, parse position and docs
+	Exported bool      // is this type definition exported?
+	Type     *vdl.Type // type of this type definition
+
+	// BaseType is the type that Type is based on.  The BaseType may be named or
+	// unnamed.  E.g.
+	//                                 BaseType
+	//   type Bool    bool;            bool
+	//   type Bool2   Bool;            Bool
+	//   type List    []int32;         []int32
+	//   type List2   List;            List
+	//   type Struct  struct{A bool};  struct{A bool}
+	//   type Struct2 Struct;          Struct
+	BaseType *vdl.Type
+
+	LabelDoc       []string // [valid for enum] docs for each label
+	LabelDocSuffix []string // [valid for enum] suffix docs for each label
+	FieldDoc       []string // [valid for struct, union] docs for each field
+	FieldDocSuffix []string // [valid for struct, union] suffix docs for each field
+	File           *File    // parent file that this type is defined in
+}
+
+func (x *TypeDef) String() string {
+	c := *x
+	c.File = nil // avoid infinite loop
+	return fmt.Sprintf("%+v", c)
+}
+
+// compileTypeDefs is the "entry point" to the rest of this file.  It takes the
+// types defined in pfiles and compiles them into TypeDefs in pkg.
+func compileTypeDefs(pkg *Package, pfiles []*parse.File, env *Env) {
+	td := typeDefiner{
+		pkg:      pkg,
+		pfiles:   pfiles,
+		env:      env,
+		tbuilder: &vdl.TypeBuilder{},
+		builders: make(map[string]*typeDefBuilder),
+	}
+	if td.Declare(); !env.Errors.IsEmpty() {
+		return
+	}
+	if td.Define(); !env.Errors.IsEmpty() {
+		return
+	}
+	if td.Build(); !env.Errors.IsEmpty() {
+		return
+	}
+	td.AttachDoc()
+	// TODO(toddw): should we disallow inter-file cyclic type dependencies?  That
+	// might be an issue for generated C++.
+}
+
+// typeDefiner defines types in a package.  This is split into three phases:
+// 1) Declare ensures local type references can be resolved.
+// 2) Define describes each type, resolving named references.
+// 3) Build builds all types.
+//
+// It holds a builders map from type name to typeDefBuilder, where the
+// typeDefBuilder is responsible for compiling and defining a single type.
+type typeDefiner struct {
+	pkg      *Package
+	pfiles   []*parse.File
+	env      *Env
+	tbuilder *vdl.TypeBuilder
+	builders map[string]*typeDefBuilder
+}
+
+type typeDefBuilder struct {
+	def     *TypeDef
+	ptype   parse.Type
+	pending vdl.PendingNamed // named type that's being built
+	base    vdl.PendingType  // base type that pending is based on
+}
+
+// Declare creates builders for each type defined in the package.
+func (td typeDefiner) Declare() {
+	for ix := range td.pkg.Files {
+		file, pfile := td.pkg.Files[ix], td.pfiles[ix]
+		for _, pdef := range pfile.TypeDefs {
+			detail := identDetail("type", file, pdef.Pos)
+			if err := file.DeclareIdent(pdef.Name, detail); err != nil {
+				td.env.prefixErrorf(file, pdef.Pos, err, "type %s name conflict", pdef.Name)
+				continue
+			}
+			td.builders[pdef.Name] = td.makeTypeDefBuilder(file, pdef)
+		}
+	}
+}
+
+func (td typeDefiner) makeTypeDefBuilder(file *File, pdef *parse.TypeDef) *typeDefBuilder {
+	export, err := validIdent(pdef.Name, reservedNormal)
+	if err != nil {
+		td.env.prefixErrorf(file, pdef.Pos, err, "type %s invalid name", pdef.Name)
+		return nil
+	}
+	ret := new(typeDefBuilder)
+	ret.def = &TypeDef{NamePos: NamePos(pdef.NamePos), Exported: export, File: file}
+	ret.ptype = pdef.Type
+	// We use the qualified name to actually name the type, to ensure types
+	// defined in separate packages are hash-consed separately.
+	qname := file.Package.QualifiedName(pdef.Name)
+	ret.pending = td.tbuilder.Named(qname)
+	switch pt := pdef.Type.(type) {
+	case *parse.TypeEnum:
+		ret.def.LabelDoc = make([]string, len(pt.Labels))
+		ret.def.LabelDocSuffix = make([]string, len(pt.Labels))
+		for index, plabel := range pt.Labels {
+			if err := validExportedIdent(plabel.Name, reservedFirstRuneLower); err != nil {
+				td.env.prefixErrorf(file, plabel.Pos, err, "invalid enum label name %s", plabel.Name)
+				return nil
+			}
+			ret.def.LabelDoc[index] = plabel.Doc
+			ret.def.LabelDocSuffix[index] = plabel.DocSuffix
+		}
+	case *parse.TypeStruct:
+		ret = attachFieldDoc(ret, pt.Fields, file, td.env)
+	case *parse.TypeUnion:
+		ret = attachFieldDoc(ret, pt.Fields, file, td.env)
+	}
+	return ret
+}
+
+func attachFieldDoc(ret *typeDefBuilder, fields []*parse.Field, file *File, env *Env) *typeDefBuilder {
+	ret.def.FieldDoc = make([]string, len(fields))
+	ret.def.FieldDocSuffix = make([]string, len(fields))
+	for index, pfield := range fields {
+		if err := validExportedIdent(pfield.Name, reservedFirstRuneLower); err != nil {
+			env.prefixErrorf(file, pfield.Pos, err, "invalid field name %s", pfield.Name)
+			return nil
+		}
+		ret.def.FieldDoc[index] = pfield.Doc
+		ret.def.FieldDocSuffix[index] = pfield.DocSuffix
+	}
+	return ret
+}
+
+// Define uses the builders to describe each type.  Named types defined in
+// other packages must have already been compiled, and in env.  Named types
+// defined in this package are represented by the builders.
+func (td typeDefiner) Define() {
+	for _, b := range td.builders {
+		def, file := b.def, b.def.File
+		base := compileDefinedType(b.ptype, file, td.env, td.tbuilder, td.builders)
+		switch tbase := base.(type) {
+		case nil:
+			continue // keep going to catch  more errors
+		case *vdl.Type:
+			if tbase == vdl.ErrorType {
+				td.env.Errorf(file, def.Pos, "error cannot be renamed")
+				continue // keep going to catch more errors
+			}
+			def.BaseType = tbase
+		case vdl.PendingType:
+			b.base = tbase
+		default:
+			panic(fmt.Errorf("vdl: typeDefiner.Define unhandled TypeOrPending %T %v", tbase, tbase))
+		}
+		b.pending.AssignBase(base)
+	}
+}
+
+// compileType returns the *vdl.Type corresponding to ptype.  All named types
+// referenced by ptype must already be defined.
+func compileType(ptype parse.Type, file *File, env *Env) *vdl.Type {
+	var tbuilder vdl.TypeBuilder
+	typeOrPending := compileLiteralType(ptype, file, env, &tbuilder, nil)
+	tbuilder.Build()
+	switch top := typeOrPending.(type) {
+	case nil:
+		return nil
+	case *vdl.Type:
+		return top
+	case vdl.PendingType:
+		t, err := top.Built()
+		if err != nil {
+			env.prefixErrorf(file, ptype.Pos(), err, "invalid type")
+			return nil
+		}
+		return t
+	default:
+		panic(fmt.Errorf("vdl: compileType unhandled TypeOrPending %T %v", top, top))
+	}
+}
+
+// compileDefinedType compiles ptype.  It can handle definitions based on array,
+// enum, struct and union, as well as definitions based on any literal type.
+func compileDefinedType(ptype parse.Type, file *File, env *Env, tbuilder *vdl.TypeBuilder, builders map[string]*typeDefBuilder) vdl.TypeOrPending {
+	switch pt := ptype.(type) {
+	case *parse.TypeArray:
+		elem := compileLiteralType(pt.Elem, file, env, tbuilder, builders)
+		if elem == nil {
+			return nil
+		}
+		return tbuilder.Array().AssignLen(pt.Len).AssignElem(elem)
+	case *parse.TypeEnum:
+		enum := tbuilder.Enum()
+		for _, plabel := range pt.Labels {
+			enum.AppendLabel(plabel.Name)
+		}
+		return enum
+	case *parse.TypeStruct:
+		st := tbuilder.Struct()
+		for _, pfield := range pt.Fields {
+			ftype := compileLiteralType(pfield.Type, file, env, tbuilder, builders)
+			if ftype == nil {
+				return nil
+			}
+			st.AppendField(pfield.Name, ftype)
+		}
+		return st
+	case *parse.TypeUnion:
+		union := tbuilder.Union()
+		for _, pfield := range pt.Fields {
+			ftype := compileLiteralType(pfield.Type, file, env, tbuilder, builders)
+			if ftype == nil {
+				return nil
+			}
+			union.AppendField(pfield.Name, ftype)
+		}
+		return union
+	}
+	lit := compileLiteralType(ptype, file, env, tbuilder, builders)
+	if _, ok := lit.(vdl.PendingOptional); ok {
+		// Don't allow Optional at the top-level of a type definition.  The purpose
+		// of this rule is twofold:
+		// 1) Reduce confusion; the Optional modifier cannot be hidden in a type
+		//    definition, it must be explicitly mentioned on each use.
+		// 2) The Optional concept is typically translated to pointers in generated
+		//    languages, and many languages don't support named pointer types.
+		//
+		//   type A string            // ok
+		//   type B []?string         // ok
+		//   type C struct{X ?string} // ok
+		//   type D ?string           // bad
+		//   type E ?struct{X string} // bad
+		env.Errorf(file, ptype.Pos(), "can't define type based on top-level optional")
+		return nil
+	}
+	return lit
+}
+
+// compileLiteralType compiles ptype.  It can handle any literal type.  Note
+// that array, enum, struct and union are required to be defined and named,
+// and aren't allowed as regular literal types.
+func compileLiteralType(ptype parse.Type, file *File, env *Env, tbuilder *vdl.TypeBuilder, builders map[string]*typeDefBuilder) vdl.TypeOrPending {
+	switch pt := ptype.(type) {
+	case *parse.TypeNamed:
+		if def, matched := env.ResolveType(pt.Name, file); def != nil {
+			if len(matched) < len(pt.Name) {
+				env.Errorf(file, pt.Pos(), "type %s invalid (%s unmatched)", pt.Name, pt.Name[len(matched):])
+				return nil
+			}
+			return def.Type
+		}
+		if b, ok := builders[pt.Name]; ok {
+			return b.pending
+		}
+		env.Errorf(file, pt.Pos(), "type %s undefined", pt.Name)
+		return nil
+	case *parse.TypeList:
+		elem := compileLiteralType(pt.Elem, file, env, tbuilder, builders)
+		if elem == nil {
+			return nil
+		}
+		return tbuilder.List().AssignElem(elem)
+	case *parse.TypeSet:
+		key := compileLiteralType(pt.Key, file, env, tbuilder, builders)
+		if key == nil {
+			return nil
+		}
+		return tbuilder.Set().AssignKey(key)
+	case *parse.TypeMap:
+		key := compileLiteralType(pt.Key, file, env, tbuilder, builders)
+		elem := compileLiteralType(pt.Elem, file, env, tbuilder, builders)
+		if key == nil || elem == nil {
+			return nil
+		}
+		return tbuilder.Map().AssignKey(key).AssignElem(elem)
+	case *parse.TypeOptional:
+		elem := compileLiteralType(pt.Base, file, env, tbuilder, builders)
+		if elem == nil {
+			return nil
+		}
+		return tbuilder.Optional().AssignElem(elem)
+	default:
+		env.Errorf(file, pt.Pos(), "unnamed %s type invalid (type must be defined)", ptype.Kind())
+		return nil
+	}
+}
+
+// Build actually builds each type and updates the package with the typedefs.
+// The order we call each pending type doesn't matter; the v.io/v23/vdl package
+// deals with arbitrary orders, and supports recursive types.  However we want
+// the order to be deterministic, otherwise the output will constantly change.
+// So we use the same order as the parsed file.
+func (td typeDefiner) Build() {
+	td.tbuilder.Build()
+	for _, pfile := range td.pfiles {
+		for _, pdef := range pfile.TypeDefs {
+			b := td.builders[pdef.Name]
+			def, file := b.def, b.def.File
+			if b.base != nil {
+				base, err := b.base.Built()
+				if err != nil {
+					td.env.prefixErrorf(file, b.ptype.Pos(), err, "%s base type invalid", def.Name)
+					return
+				}
+				def.BaseType = base
+			}
+			t, err := b.pending.Built()
+			if err != nil {
+				td.env.prefixErrorf(file, def.Pos, err, "%s invalid", def.Name)
+				return
+			}
+			def.Type = t
+			addTypeDef(def, td.env)
+		}
+	}
+}
+
+// AttachDoc makes another pass to fill in doc and doc suffix slices for enums,
+// structs and unions.  Typically these are initialized in makeTypeDefBuilder,
+// based on the underlying parse data.  But type definitions based on other
+// named types can't be updated until the base type is actually compiled.
+//
+// TODO(toddw): This doesn't actually attach comments from the base type, it
+// just leaves everything empty.  This is fine for now, but we should revamp the
+// vdl parsing / comment attaching strategy in the future.
+func (td typeDefiner) AttachDoc() {
+	for _, file := range td.pkg.Files {
+		for _, def := range file.TypeDefs {
+			switch t := def.Type; t.Kind() {
+			case vdl.Enum:
+				if len(def.LabelDoc) == 0 {
+					def.LabelDoc = make([]string, t.NumEnumLabel())
+				}
+				if len(def.LabelDocSuffix) == 0 {
+					def.LabelDocSuffix = make([]string, t.NumEnumLabel())
+				}
+			case vdl.Struct, vdl.Union:
+				if len(def.FieldDoc) == 0 {
+					def.FieldDoc = make([]string, t.NumField())
+				}
+				if len(def.FieldDocSuffix) == 0 {
+					def.FieldDocSuffix = make([]string, t.NumField())
+				}
+			}
+		}
+	}
+}
+
+// addTypeDef updates our various structures to add a new type def.
+func addTypeDef(def *TypeDef, env *Env) {
+	def.File.TypeDefs = append(def.File.TypeDefs, def)
+	def.File.Package.typeDefs[def.Name] = def
+	if env != nil {
+		// env should only be nil during initialization of the built-in package;
+		// NewEnv ensures new environments have the built-in types.
+		env.typeDefs[def.Type] = def
+	}
+}
diff --git a/lib/vdl/compile/type_test.go b/lib/vdl/compile/type_test.go
new file mode 100644
index 0000000..1c43299
--- /dev/null
+++ b/lib/vdl/compile/type_test.go
@@ -0,0 +1,200 @@
+// 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 compile_test
+
+import (
+	"testing"
+
+	"v.io/v23/vdl"
+	"v.io/x/ref/lib/vdl/build"
+	"v.io/x/ref/lib/vdl/compile"
+	"v.io/x/ref/lib/vdl/internal/vdltest"
+)
+
+const qual = "package path qualified identifier"
+
+func testType(t *testing.T, test typeTest, qualifiedPaths bool) {
+	env := compile.NewEnv(-1)
+	if !qualifiedPaths {
+		env.DisallowPathQualifiers()
+		test.Name = "NoQual" + test.Name
+	}
+	for _, tpkg := range test.Pkgs {
+		// Compile the package with a single file, and adding the "package foo"
+		// prefix to the source data automatically.
+		files := map[string]string{
+			tpkg.Name + ".vdl": "package " + tpkg.Name + "\n" + tpkg.Data,
+		}
+		pkgPath := "p.kg/" + tpkg.Name // use dots in pkgpath to test tricky cases
+		buildPkg := vdltest.FakeBuildPackage(tpkg.Name, pkgPath, files)
+		pkg := build.BuildPackage(buildPkg, env)
+		if tpkg.ErrRE == qual {
+			if qualifiedPaths {
+				tpkg.ErrRE = "" // the test should pass if running with qualified paths.
+			} else {
+				tpkg.ExpectBase = nil // otherwise the test should fail
+			}
+		}
+		vdltest.ExpectResult(t, env.Errors, test.Name, tpkg.ErrRE)
+		if pkg == nil || tpkg.ErrRE != "" {
+			continue
+		}
+		matchTypeRes(t, test.Name, tpkg, pkg.Files[0].TypeDefs)
+	}
+}
+
+func TestType(t *testing.T) {
+	// Run all tests in both regular and qualfiedPaths mode
+	for _, test := range typeTests {
+		testType(t, test, false)
+	}
+	for _, test := range typeTests {
+		testType(t, test, true)
+	}
+}
+
+func matchTypeRes(t *testing.T, tname string, tpkg typePkg, tdefs []*compile.TypeDef) {
+	if tpkg.ExpectBase == nil {
+		return
+	}
+	// Look for a TypeDef called "Res" to compare our expected results.
+	for _, tdef := range tdefs {
+		if tdef.Name == "Res" {
+			base := tpkg.ExpectBase
+			resname := "p.kg/" + tpkg.Name + ".Res"
+			res := vdl.NamedType(resname, base)
+			if got, want := tdef.Type, res; got != want {
+				t.Errorf("%s type got %s, want %s", tname, got, want)
+			}
+			if got, want := tdef.BaseType, base; got != want {
+				t.Errorf("%s base type got %s, want %s", tname, got, want)
+			}
+			return
+		}
+	}
+	t.Errorf("%s couldn't find Res in package %s", tname, tpkg.Name)
+}
+
+func namedX(base *vdl.Type) *vdl.Type   { return vdl.NamedType("p.kg/a.x", base) }
+func namedRes(base *vdl.Type) *vdl.Type { return vdl.NamedType("p.kg/a.Res", base) }
+
+var byteListType = vdl.ListType(vdl.ByteType)
+var byteArrayType = vdl.ArrayType(4, vdl.ByteType)
+
+type typePkg struct {
+	Name       string
+	Data       string
+	ExpectBase *vdl.Type
+	ErrRE      string
+}
+
+type tp []typePkg
+
+type typeTest struct {
+	Name string
+	Pkgs tp
+}
+
+var typeTests = []typeTest{
+	// Test named built-ins.
+	{"Bool", tp{{"a", `type Res bool`, vdl.BoolType, ""}}},
+	{"Byte", tp{{"a", `type Res byte`, vdl.ByteType, ""}}},
+	{"Uint16", tp{{"a", `type Res uint16`, vdl.Uint16Type, ""}}},
+	{"Uint32", tp{{"a", `type Res uint32`, vdl.Uint32Type, ""}}},
+	{"Uint64", tp{{"a", `type Res uint64`, vdl.Uint64Type, ""}}},
+	{"Int16", tp{{"a", `type Res int16`, vdl.Int16Type, ""}}},
+	{"Int32", tp{{"a", `type Res int32`, vdl.Int32Type, ""}}},
+	{"Int64", tp{{"a", `type Res int64`, vdl.Int64Type, ""}}},
+	{"Float32", tp{{"a", `type Res float32`, vdl.Float32Type, ""}}},
+	{"Float64", tp{{"a", `type Res float64`, vdl.Float64Type, ""}}},
+	{"Complex64", tp{{"a", `type Res complex64`, vdl.Complex64Type, ""}}},
+	{"Complex128", tp{{"a", `type Res complex128`, vdl.Complex128Type, ""}}},
+	{"String", tp{{"a", `type Res string`, vdl.StringType, ""}}},
+	{"ByteList", tp{{"a", `type Res []byte`, byteListType, ""}}},
+	{"ByteArray", tp{{"a", `type Res [4]byte`, byteArrayType, ""}}},
+	{"Typeobject", tp{{"a", `type Res typeobject`, nil, "any and typeobject cannot be renamed"}}},
+	{"Any", tp{{"a", `type Res any`, nil, "any and typeobject cannot be renamed"}}},
+	{"Error", tp{{"a", `type Res error`, nil, "error cannot be renamed"}}},
+
+	// Test composite vdl.
+	{"Enum", tp{{"a", `type Res enum{A;B;C}`, vdl.EnumType("A", "B", "C"), ""}}},
+	{"Array", tp{{"a", `type Res [2]bool`, vdl.ArrayType(2, vdl.BoolType), ""}}},
+	{"List", tp{{"a", `type Res []int32`, vdl.ListType(vdl.Int32Type), ""}}},
+	{"Set", tp{{"a", `type Res set[int32]`, vdl.SetType(vdl.Int32Type), ""}}},
+	{"Map", tp{{"a", `type Res map[int32]string`, vdl.MapType(vdl.Int32Type, vdl.StringType), ""}}},
+	{"Struct", tp{{"a", `type Res struct{A int32;B string}`, vdl.StructType([]vdl.Field{{"A", vdl.Int32Type}, {"B", vdl.StringType}}...), ""}}},
+	{"Union", tp{{"a", `type Res union{A bool;B int32;C string}`, vdl.UnionType([]vdl.Field{{"A", vdl.BoolType}, {"B", vdl.Int32Type}, {"C", vdl.StringType}}...), ""}}},
+	{"Optional", tp{{"a", `type Res []?x;type x struct{A bool}`, vdl.ListType(vdl.OptionalType(namedX(vdl.StructType(vdl.Field{
+		Name: "A",
+		Type: vdl.BoolType,
+	})))), ""}}},
+
+	// Test named types based on named types.
+	{"NBool", tp{{"a", `type Res x;type x bool`, namedX(vdl.BoolType), ""}}},
+	{"NByte", tp{{"a", `type Res x;type x byte`, namedX(vdl.ByteType), ""}}},
+	{"NUint16", tp{{"a", `type Res x;type x uint16`, namedX(vdl.Uint16Type), ""}}},
+	{"NUint32", tp{{"a", `type Res x;type x uint32`, namedX(vdl.Uint32Type), ""}}},
+	{"NUint64", tp{{"a", `type Res x;type x uint64`, namedX(vdl.Uint64Type), ""}}},
+	{"NInt16", tp{{"a", `type Res x;type x int16`, namedX(vdl.Int16Type), ""}}},
+	{"NInt32", tp{{"a", `type Res x;type x int32`, namedX(vdl.Int32Type), ""}}},
+	{"NInt64", tp{{"a", `type Res x;type x int64`, namedX(vdl.Int64Type), ""}}},
+	{"NFloat32", tp{{"a", `type Res x;type x float32`, namedX(vdl.Float32Type), ""}}},
+	{"NFloat64", tp{{"a", `type Res x;type x float64`, namedX(vdl.Float64Type), ""}}},
+	{"NComplex64", tp{{"a", `type Res x;type x complex64`, namedX(vdl.Complex64Type), ""}}},
+	{"NComplex128", tp{{"a", `type Res x;type x complex128`, namedX(vdl.Complex128Type), ""}}},
+	{"NString", tp{{"a", `type Res x;type x string`, namedX(vdl.StringType), ""}}},
+	{"NByteList", tp{{"a", `type Res x;type x []byte`, namedX(byteListType), ""}}},
+	{"NByteArray", tp{{"a", `type Res x;type x [4]byte`, namedX(byteArrayType), ""}}},
+	{"NEnum", tp{{"a", `type Res x;type x enum{A;B;C}`, namedX(vdl.EnumType("A", "B", "C")), ""}}},
+	{"NArray", tp{{"a", `type Res x;type x [2]bool`, namedX(vdl.ArrayType(2, vdl.BoolType)), ""}}},
+	{"NList", tp{{"a", `type Res x;type x []int32`, namedX(vdl.ListType(vdl.Int32Type)), ""}}},
+	{"NSet", tp{{"a", `type Res x;type x set[int32]`, namedX(vdl.SetType(vdl.Int32Type)), ""}}},
+	{"NMap", tp{{"a", `type Res x;type x map[int32]string`, namedX(vdl.MapType(vdl.Int32Type, vdl.StringType)), ""}}},
+	{"NStruct", tp{{"a", `type Res x;type x struct{A int32;B string}`, namedX(vdl.StructType([]vdl.Field{{"A", vdl.Int32Type}, {"B", vdl.StringType}}...)), ""}}},
+	{"NUnion", tp{{"a", `type Res x; type x union{A bool;B int32;C string}`, namedX(vdl.UnionType([]vdl.Field{{"A", vdl.BoolType}, {"B", vdl.Int32Type}, {"C", vdl.StringType}}...)), ""}}},
+
+	// Test multi-package types
+	{"MultiPkgSameTypeName", tp{
+		{"a", `type Res bool`, vdl.BoolType, ""},
+		{"b", `type Res bool`, vdl.BoolType, ""}}},
+	{"MultiPkgDep", tp{
+		{"a", `type Res x;type x bool`, namedX(vdl.BoolType), ""},
+		{"b", `import "p.kg/a";type Res []a.Res`, vdl.ListType(namedRes(vdl.BoolType)), ""}}},
+	{"MultiPkgDepQualifiedPath", tp{
+		{"a", `type Res x;type x bool`, namedX(vdl.BoolType), ""},
+		{"b", `import "p.kg/a";type Res []"p.kg/a".Res`, vdl.ListType(namedRes(vdl.BoolType)), qual}}},
+	{"MultiPkgUnexportedType", tp{
+		{"a", `type Res x;type x bool`, namedX(vdl.BoolType), ""},
+		{"b", `import "p.kg/a";type Res []a.x`, nil, "type a.x undefined"}}},
+	{"MultiPkgSamePkgName", tp{
+		{"a", `type Res bool`, vdl.BoolType, ""},
+		{"a", `type Res bool`, nil, "invalid recompile"}}},
+	{"MultiPkgUnimportedPkg", tp{
+		{"a", `type Res bool`, vdl.BoolType, ""},
+		{"b", `type Res []a.Res`, nil, "type a.Res undefined"}}},
+	{"RedefinitionOfImportedName", tp{
+		{"a", `type Res bool`, vdl.BoolType, ""},
+		{"b", `import "p.kg/a"; type a string; type Res a`, nil, "type a name conflict"}}},
+
+	// Test errors.
+	{"InvalidName", tp{{"a", `type _Res bool`, nil, "type _Res invalid name"}}},
+	{"Undefined", tp{{"a", `type Res foo`, nil, "type foo undefined"}}},
+	{"UnnamedArray", tp{{"a", `type Res [][3]int64`, nil, "unnamed array type invalid"}}},
+	{"UnnamedEnum", tp{{"a", `type Res []enum{A;B;C}`, nil, "unnamed enum type invalid"}}},
+	{"UnnamedStruct", tp{{"a", `type Res []struct{A int32}`, nil, "unnamed struct type invalid"}}},
+	{"UnnamedUnion", tp{{"a", `type Res []union{A bool;B int32;C string}`, nil, "unnamed union type invalid"}}},
+	{"TopLevelOptional", tp{{"a", `type Res ?bool`, nil, "can't define type based on top-level optional"}}},
+	{"MultiPkgUnmatchedType", tp{
+		{"a", `type Res bool`, vdl.BoolType, ""},
+		{"b", `import "p.kg/a";type Res a.Res.foobar`, nil, `\(\.foobar unmatched\)`}}},
+	{"UnterminatedPath1", tp{
+		{"a", `type Res bool`, vdl.BoolType, ""},
+		{"b", `import "p.kg/a";type Res "a.Res`, nil, "syntax error"}}},
+	{"UnterminatedPath2", tp{
+		{"a", `type Res bool`, vdl.BoolType, ""},
+		{"b", `import "p.kg/a";type Res a".Res`, nil, "syntax error"}}},
+	{"ZeroLengthArray", tp{{"a", `type Res [0]int32`, nil, "negative or zero array length"}}},
+	{"InvalidOptionalFollowedByValidType", tp{{"a", `type Res struct{X ?int32};type x string`, nil, "invalid optional type"}}},
+}
diff --git a/lib/vdl/internal/vdltest/vdltest.go b/lib/vdl/internal/vdltest/vdltest.go
new file mode 100644
index 0000000..d190b29
--- /dev/null
+++ b/lib/vdl/internal/vdltest/vdltest.go
@@ -0,0 +1,84 @@
+// 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 vdltest provides testing utilities for v.io/x/ref/lib/vdl/...
+package vdltest
+
+import (
+	"io"
+	"io/ioutil"
+	"regexp"
+	"strings"
+	"testing"
+
+	"v.io/x/ref/lib/vdl/build"
+	"v.io/x/ref/lib/vdl/vdlutil"
+)
+
+// ExpectPass makes sure errs has no errors.
+func ExpectPass(t *testing.T, errs *vdlutil.Errors, testName string) {
+	if !errs.IsEmpty() {
+		t.Errorf("%v expected no errors but saw: %v", testName, errs.ToError())
+		errs.Reset()
+	}
+}
+
+// ExpectFail makes sure errs has an error that matches all the re regexps.
+func ExpectFail(t *testing.T, errs *vdlutil.Errors, testName string, re ...string) {
+	if errs.IsEmpty() {
+		t.Errorf("%v expected errors but didn't see any", testName)
+		return
+	}
+	actual := errs.ToError().Error()
+	errs.Reset()
+	for index, errRe := range re {
+		matched, err := regexp.Match(errRe, []byte(actual))
+		if err != nil {
+			t.Errorf("%v bad regexp pattern [%v] %q", testName, index, errRe)
+			return
+		}
+		if !matched {
+			t.Errorf("%v couldn't match pattern [%v] %q against %q", testName, index, errRe, actual)
+		}
+	}
+}
+
+// ExpectResult ensures errs has an error that matches all the re regexps, or
+// that errs has no errors if no regexps were provided, or only one was provided
+// with the empty string.
+func ExpectResult(t *testing.T, errs *vdlutil.Errors, testName string, re ...string) {
+	if len(re) == 0 || len(re) == 1 && re[0] == "" {
+		ExpectPass(t, errs, testName)
+	} else {
+		ExpectFail(t, errs, testName, re...)
+	}
+}
+
+// FakeBuildPackage constructs a fake build package for testing, with files
+// mapping from file names to file contents.
+func FakeBuildPackage(name, path string, files map[string]string) *build.Package {
+	var fnames []string
+	for fname, _ := range files {
+		fnames = append(fnames, fname)
+	}
+	return &build.Package{
+		Dir:           "",
+		Name:          name,
+		Path:          path,
+		BaseFileNames: fnames,
+		OpenFilesFunc: FakeOpenFiles(files),
+	}
+}
+
+// FakeOpenFiles returns a function that obeys the build.Package.OpenFilesFunc
+// signature, that simply uses the files map to return readers.
+func FakeOpenFiles(files map[string]string) func(fnames []string) (map[string]io.ReadCloser, error) {
+	return func(fnames []string) (map[string]io.ReadCloser, error) {
+		ret := make(map[string]io.ReadCloser, len(fnames))
+		for _, fname := range fnames {
+			ret[fname] = ioutil.NopCloser(strings.NewReader(files[fname]))
+		}
+		return ret, nil
+	}
+}
diff --git a/lib/vdl/opconst/big_complex.go b/lib/vdl/opconst/big_complex.go
new file mode 100644
index 0000000..3d204d1
--- /dev/null
+++ b/lib/vdl/opconst/big_complex.go
@@ -0,0 +1,92 @@
+// 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 opconst
+
+import (
+	"math/big"
+)
+
+// bigComplex represents a constant complex number.  The semantics are similar
+// to big.Rat; methods are typically of the form:
+//   func (z *bigComplex) Op(x, y *bigComplex) *bigComplex
+// and implement operations z = x Op y with the result as receiver.
+type bigComplex struct {
+	re, im big.Rat
+}
+
+func newComplex(re, im *big.Rat) *bigComplex {
+	return &bigComplex{*re, *im}
+}
+
+// realComplex returns a bigComplex with real part re, and imaginary part zero.
+func realComplex(re *big.Rat) *bigComplex {
+	return &bigComplex{re: *re}
+}
+
+// imagComplex returns a bigComplex with real part zero, and imaginary part im.
+func imagComplex(im *big.Rat) *bigComplex {
+	return &bigComplex{im: *im}
+}
+
+func (z *bigComplex) SetComplex128(c complex128) *bigComplex {
+	z.re.SetFloat64(real(c))
+	z.im.SetFloat64(imag(c))
+	return z
+}
+
+func (z *bigComplex) Equal(x *bigComplex) bool {
+	return z.re.Cmp(&x.re) == 0 && z.im.Cmp(&x.im) == 0
+}
+
+func (z *bigComplex) Add(x, y *bigComplex) *bigComplex {
+	z.re.Add(&x.re, &y.re)
+	z.im.Add(&x.im, &y.im)
+	return z
+}
+
+func (z *bigComplex) Sub(x, y *bigComplex) *bigComplex {
+	z.re.Sub(&x.re, &y.re)
+	z.im.Sub(&x.im, &y.im)
+	return z
+}
+
+func (z *bigComplex) Neg(x *bigComplex) *bigComplex {
+	z.re.Neg(&x.re)
+	z.im.Neg(&x.im)
+	return z
+}
+
+func (z *bigComplex) Mul(x, y *bigComplex) *bigComplex {
+	// (a+bi) * (c+di) = (ac-bd) + (bc+ad)i
+	var ac, ad, bc, bd big.Rat
+	ac.Mul(&x.re, &y.re)
+	ad.Mul(&x.re, &y.im)
+	bc.Mul(&x.im, &y.re)
+	bd.Mul(&x.im, &y.im)
+	z.re.Sub(&ac, &bd)
+	z.im.Add(&bc, &ad)
+	return z
+}
+
+func (z *bigComplex) Div(x, y *bigComplex) (*bigComplex, error) {
+	// (a+bi) / (c+di) = (a+bi)(c-di) / (c+di)(c-di)
+	//                 = ((ac+bd) + (bc-ad)i) / (cc+dd)
+	//                 = (ac+bd)/(cc+dd) + ((bc-ad)/(cc+dd))i
+	a, b, c, d := &x.re, &x.im, &y.re, &y.im
+	var ac, ad, bc, bd, cc, dd, ccdd big.Rat
+	ac.Mul(a, c)
+	ad.Mul(a, d)
+	bc.Mul(b, c)
+	bd.Mul(b, d)
+	cc.Mul(c, c)
+	dd.Mul(d, d)
+	ccdd.Add(&cc, &dd)
+	if ccdd.Cmp(bigRatZero) == 0 {
+		return nil, errDivZero
+	}
+	z.re.Add(&ac, &bd).Quo(&z.re, &ccdd)
+	z.im.Sub(&bc, &ad).Quo(&z.im, &ccdd)
+	return z, nil
+}
diff --git a/lib/vdl/opconst/const.go b/lib/vdl/opconst/const.go
new file mode 100644
index 0000000..f9d0859
--- /dev/null
+++ b/lib/vdl/opconst/const.go
@@ -0,0 +1,906 @@
+// 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 opconst defines the representation and operations for VDL constants.
+package opconst
+
+import (
+	"errors"
+	"fmt"
+	"math"
+	"math/big"
+	"strconv"
+
+	"v.io/v23/vdl"
+)
+
+var (
+	bigIntZero     = new(big.Int)
+	bigRatZero     = new(big.Rat)
+	bigIntOne      = big.NewInt(1)
+	bigRatAbsMin32 = new(big.Rat).SetFloat64(math.SmallestNonzeroFloat32)
+	bigRatAbsMax32 = new(big.Rat).SetFloat64(math.MaxFloat32)
+	bigRatAbsMin64 = new(big.Rat).SetFloat64(math.SmallestNonzeroFloat64)
+	bigRatAbsMax64 = new(big.Rat).SetFloat64(math.MaxFloat64)
+	maxShiftSize   = big.NewInt(463) // use the same max as Go
+
+	errInvalidConst = errors.New("invalid const")
+	errConvertNil   = errors.New("invalid conversion to untyped const")
+	errDivZero      = errors.New("divide by zero")
+)
+
+// Const represents a constant value, similar in spirit to Go constants.  Consts
+// may be typed or untyped.  Typed consts represent unchanging Values; all
+// Values may be converted into valid typed consts, and all typed consts may be
+// converted into valid Values.  Untyped consts belong to one of the following
+// categories:
+//   untyped boolean
+//   untyped string
+//   untyped integer
+//   untyped rational
+//   untyped complex
+// Literal consts are untyped, as are expressions only containing untyped
+// consts.  The result of comparison operations is untyped boolean.
+//
+// Operations are represented by UnaryOp and BinaryOp, and are supported on
+// Consts, but not Values.  We support common logical, bitwise, comparison and
+// arithmetic operations.  Not all operations are supported on all consts.
+//
+// Binary ops where both sides are typed consts return errors on type
+// mismatches; e.g. uint32(1) + uint64(1) is an invalid binary add.  Ops on
+// typed consts also return errors on loss of precision; e.g. uint32(1.1)
+// returns an error.
+//
+// Binary ops where one or both sides are untyped consts perform implicit type
+// conversion.  E.g. uint32(1) + 1 is a valid binary add, where the
+// right-hand-side is the untyped integer const 1, which is coerced to the
+// uint32 type before the op is performed.  Operations only containing untyped
+// consts are performed with "infinite" precision.
+//
+// The zero Const is invalid.
+type Const struct {
+	// rep holds the underlying representation, it may be one of:
+	//   bool        - Represents typed and untyped boolean constants.
+	//   string      - Represents typed and untyped string constants.
+	//   *big.Int    - Represents typed and untyped integer constants.
+	//   *big.Rat    - Represents typed and untyped rational constants.
+	//   *bigComplex - Represents typed and untyped complex constants.
+	//   *Value      - Represents all other typed constants.
+	rep interface{}
+
+	// repType holds the type of rep.  If repType is nil the constant is untyped,
+	// otherwise the constant is typed, and rep must match the kind of repType.
+	// If rep is a *Value, repType is always non-nil.
+	repType *vdl.Type
+}
+
+// Boolean returns an untyped boolean Const.
+func Boolean(x bool) Const { return Const{x, nil} }
+
+// String returns an untyped string Const.
+func String(x string) Const { return Const{x, nil} }
+
+// Integer returns an untyped integer Const.
+func Integer(x *big.Int) Const { return Const{x, nil} }
+
+// Rational returns an untyped rational Const.
+func Rational(x *big.Rat) Const { return Const{x, nil} }
+
+// Complex returns an untyped complex Const.
+func Complex(re, im *big.Rat) Const { return Const{newComplex(re, im), nil} }
+
+// FromValue returns a typed Const based on value v.
+func FromValue(v *vdl.Value) Const {
+	if v.Type().IsBytes() {
+		// Represent []byte and [N]byte as a string, so that conversions are easy.
+		return Const{string(v.Bytes()), v.Type()}
+	}
+	switch v.Kind() {
+	case vdl.Bool:
+		if v.Type() == vdl.BoolType { // Treat unnamed bool as untyped bool.
+			return Boolean(v.Bool())
+		}
+		return Const{v.Bool(), v.Type()}
+	case vdl.String:
+		if v.Type() == vdl.StringType { // Treat unnamed string as untyped string.
+			return String(v.RawString())
+		}
+		return Const{v.RawString(), v.Type()}
+	case vdl.Byte:
+		return Const{new(big.Int).SetUint64(uint64(v.Byte())), v.Type()}
+	case vdl.Uint16, vdl.Uint32, vdl.Uint64:
+		return Const{new(big.Int).SetUint64(v.Uint()), v.Type()}
+	case vdl.Int16, vdl.Int32, vdl.Int64:
+		return Const{new(big.Int).SetInt64(v.Int()), v.Type()}
+	case vdl.Float32, vdl.Float64:
+		return Const{new(big.Rat).SetFloat64(v.Float()), v.Type()}
+	case vdl.Complex64, vdl.Complex128:
+		return Const{new(bigComplex).SetComplex128(v.Complex()), v.Type()}
+	default:
+		return Const{v, v.Type()}
+	}
+}
+
+// IsValid returns true iff the c represents a const; it returns false for the
+// zero Const.
+func (c Const) IsValid() bool {
+	return c.rep != nil
+}
+
+// Type returns the type of c.  Nil indicates c is an untyped const.
+func (c Const) Type() *vdl.Type {
+	return c.repType
+}
+
+// Convert converts c to the target type t, and returns the resulting const.
+// Returns an error if t is nil; you're not allowed to convert into an untyped
+// const.
+func (c Const) Convert(t *vdl.Type) (Const, error) {
+	if t == nil {
+		return Const{}, errConvertNil
+	}
+	// If we're trying to convert to Any or Union, or if c is already a vdl.Value,
+	// use vdl.Convert to convert as a vdl.Value.
+	_, isValue := c.rep.(*vdl.Value)
+	if isValue || t.Kind() == vdl.Any || t.Kind() == vdl.Union {
+		src, err := c.ToValue()
+		if err != nil {
+			return Const{}, err
+		}
+		dst := vdl.ZeroValue(t)
+		if err := vdl.Convert(dst, src); err != nil {
+			return Const{}, err
+		}
+		return FromValue(dst), nil
+	}
+	// Otherwise use makeConst to convert as a Const.
+	return makeConst(c.rep, t)
+}
+
+func (c Const) String() string {
+	if !c.IsValid() {
+		return "invalid"
+	}
+	if v, ok := c.rep.(*vdl.Value); ok {
+		return v.String()
+	}
+	if c.repType == nil {
+		// E.g. 12345
+		return cRepString(c.rep)
+	}
+	// E.g. int32(12345)
+	return c.typeString() + "(" + cRepString(c.rep) + ")"
+}
+
+func (c Const) typeString() string {
+	return cRepTypeString(c.rep, c.repType)
+}
+
+// cRepString returns a human-readable string representing the const value.
+func cRepString(rep interface{}) string {
+	switch trep := rep.(type) {
+	case nil:
+		return "" // invalid const
+	case bool:
+		if trep {
+			return "true"
+		}
+		return "false"
+	case string:
+		return strconv.Quote(trep)
+	case *big.Int:
+		return trep.String()
+	case *big.Rat:
+		if trep.IsInt() {
+			return trep.Num().String() + ".0"
+		}
+		frep, _ := trep.Float64()
+		return strconv.FormatFloat(frep, 'g', -1, 64)
+	case *bigComplex:
+		return fmt.Sprintf("%v+%vi", cRepString(&trep.re), cRepString(&trep.im))
+	case *vdl.Value:
+		return trep.String()
+	default:
+		panic(fmt.Errorf("val: unhandled const type %T value %v", rep, rep))
+	}
+}
+
+// cRepTypeString returns a human-readable string representing the type of
+// the const value.
+func cRepTypeString(rep interface{}, t *vdl.Type) string {
+	if t != nil {
+		return t.String()
+	}
+	switch rep.(type) {
+	case nil:
+		return "invalid"
+	case bool:
+		return "untyped boolean"
+	case string:
+		return "untyped string"
+	case *big.Int:
+		return "untyped integer"
+	case *big.Rat:
+		return "untyped rational"
+	case *bigComplex:
+		return "untyped complex"
+	default:
+		panic(fmt.Errorf("val: unhandled const type %T value %v", rep, rep))
+	}
+}
+
+// ToValue converts Const c to a Value.
+func (c Const) ToValue() (*vdl.Value, error) {
+	if c.rep == nil {
+		return nil, errInvalidConst
+	}
+	// All const defs must have a type.  We implicitly assign bool and string, but
+	// the user must explicitly assign a type for numeric consts.
+	if c.repType == nil {
+		switch c.rep.(type) {
+		case bool:
+			c.repType = vdl.BoolType
+		case string:
+			c.repType = vdl.StringType
+		default:
+			return nil, fmt.Errorf("%s must be assigned a type", c)
+		}
+	}
+	// Create a value of the appropriate type.
+	vx := vdl.ZeroValue(c.repType)
+	switch trep := c.rep.(type) {
+	case bool:
+		switch vx.Kind() {
+		case vdl.Bool:
+			return vx.AssignBool(trep), nil
+		}
+	case string:
+		switch {
+		case vx.Kind() == vdl.String:
+			return vx.AssignString(trep), nil
+		case vx.Type().IsBytes():
+			if vx.Kind() == vdl.Array {
+				if vx.Len() != len(trep) {
+					return nil, fmt.Errorf("%s has a different length than %v", c, vx.Type())
+				}
+			}
+			return vx.AssignBytes([]byte(trep)), nil
+		}
+	case *big.Int:
+		switch vx.Kind() {
+		case vdl.Byte:
+			return vx.AssignByte(byte(trep.Uint64())), nil
+		case vdl.Uint16, vdl.Uint32, vdl.Uint64:
+			return vx.AssignUint(trep.Uint64()), nil
+		case vdl.Int16, vdl.Int32, vdl.Int64:
+			return vx.AssignInt(trep.Int64()), nil
+		}
+	case *big.Rat:
+		switch vx.Kind() {
+		case vdl.Float32, vdl.Float64:
+			f64, _ := trep.Float64()
+			return vx.AssignFloat(f64), nil
+		}
+	case *bigComplex:
+		switch vx.Kind() {
+		case vdl.Complex64, vdl.Complex128:
+			re64, _ := trep.re.Float64()
+			im64, _ := trep.im.Float64()
+			return vx.AssignComplex(complex(re64, im64)), nil
+		}
+	case *vdl.Value:
+		return trep, nil
+	}
+	// Type mismatches shouldn't occur, since makeConst always ensures the rep and
+	// repType are in sync.  If something's wrong we want to know about it.
+	panic(fmt.Errorf("val: mismatched const rep type for %v", c))
+}
+
+func errNotSupported(rep interface{}, t *vdl.Type) error {
+	return fmt.Errorf("%s not supported", cRepTypeString(rep, t))
+}
+
+// EvalUnary returns the result of evaluating (op x).
+func EvalUnary(op UnaryOp, x Const) (Const, error) {
+	if x.rep == nil {
+		return Const{}, errInvalidConst
+	}
+	if _, ok := x.rep.(*vdl.Value); ok {
+		// There are no valid unary ops on *Value consts.
+		return Const{}, errNotSupported(x.rep, x.repType)
+	}
+	switch op {
+	case LogicNot:
+		switch tx := x.rep.(type) {
+		case bool:
+			return makeConst(!tx, x.repType)
+		}
+	case Pos:
+		switch x.rep.(type) {
+		case *big.Int, *big.Rat, *bigComplex:
+			return x, nil
+		}
+	case Neg:
+		switch tx := x.rep.(type) {
+		case *big.Int:
+			return makeConst(new(big.Int).Neg(tx), x.repType)
+		case *big.Rat:
+			return makeConst(new(big.Rat).Neg(tx), x.repType)
+		case *bigComplex:
+			return makeConst(new(bigComplex).Neg(tx), x.repType)
+		}
+	case BitNot:
+		ix, err := constToInt(x)
+		if err != nil {
+			return Const{}, err
+		}
+		// big.Int.Not implements bit-not for signed integers, but we need to
+		// special-case unsigned integers.  E.g. ^int8(1)=-2, ^uint8(1)=254
+		not := new(big.Int)
+		switch {
+		case x.repType != nil && x.repType.Kind() == vdl.Byte:
+			not.SetUint64(uint64(^uint8(ix.Uint64())))
+		case x.repType != nil && x.repType.Kind() == vdl.Uint16:
+			not.SetUint64(uint64(^uint16(ix.Uint64())))
+		case x.repType != nil && x.repType.Kind() == vdl.Uint32:
+			not.SetUint64(uint64(^uint32(ix.Uint64())))
+		case x.repType != nil && x.repType.Kind() == vdl.Uint64:
+			not.SetUint64(^ix.Uint64())
+		default:
+			not.Not(ix)
+		}
+		return makeConst(not, x.repType)
+	}
+	return Const{}, errNotSupported(x.rep, x.repType)
+}
+
+// EvalBinary returns the result of evaluating (x op y).
+func EvalBinary(op BinaryOp, x, y Const) (Const, error) {
+	if x.rep == nil || y.rep == nil {
+		return Const{}, errInvalidConst
+	}
+	switch op {
+	case LeftShift, RightShift:
+		// Shift ops are special since they require an integer lhs and unsigned rhs.
+		return evalShift(op, x, y)
+	}
+	// All other binary ops behave similarly.  First we perform implicit
+	// conversion of x and y.  If either side is untyped, we may need to
+	// implicitly convert it to the type of the other side.  If both sides are
+	// typed they need to match.  The resulting tx and ty are guaranteed to have
+	// the same type, and resType tells us which type we need to convert the
+	// result into when we're done.
+	cx, cy, resType, err := coerceConsts(x, y)
+	if err != nil {
+		return Const{}, err
+	}
+	// Now we perform the actual binary op.
+	var res interface{}
+	switch op {
+	case LogicOr, LogicAnd:
+		res, err = opLogic(op, cx, cy, resType)
+	case EQ, NE, LT, LE, GT, GE:
+		res, err = opComp(op, cx, cy, resType)
+		resType = nil // comparisons always result in untyped bool.
+	case Add, Sub, Mul, Div:
+		res, err = opArith(op, cx, cy, resType)
+	case Mod, BitAnd, BitOr, BitXor:
+		res, err = opIntArith(op, cx, cy, resType)
+	default:
+		err = errNotSupported(cx, resType)
+	}
+	if err != nil {
+		return Const{}, err
+	}
+	// As a final step we convert to the result type.
+	return makeConst(res, resType)
+}
+
+func opLogic(op BinaryOp, x, y interface{}, resType *vdl.Type) (interface{}, error) {
+	switch tx := x.(type) {
+	case bool:
+		switch op {
+		case LogicOr:
+			return tx || y.(bool), nil
+		case LogicAnd:
+			return tx && y.(bool), nil
+		}
+	}
+	return nil, errNotSupported(x, resType)
+}
+
+func opComp(op BinaryOp, x, y interface{}, resType *vdl.Type) (interface{}, error) {
+	switch tx := x.(type) {
+	case bool:
+		switch op {
+		case EQ:
+			return tx == y.(bool), nil
+		case NE:
+			return tx != y.(bool), nil
+		}
+	case string:
+		return compString(op, tx, y.(string)), nil
+	case *big.Int:
+		return opCmpToBool(op, tx.Cmp(y.(*big.Int))), nil
+	case *big.Rat:
+		return opCmpToBool(op, tx.Cmp(y.(*big.Rat))), nil
+	case *bigComplex:
+		switch op {
+		case EQ:
+			return tx.Equal(y.(*bigComplex)), nil
+		case NE:
+			return !tx.Equal(y.(*bigComplex)), nil
+		}
+	case *vdl.Value:
+		switch op {
+		case EQ:
+			return vdl.EqualValue(tx, y.(*vdl.Value)), nil
+		case NE:
+			return !vdl.EqualValue(tx, y.(*vdl.Value)), nil
+		}
+	}
+	return nil, errNotSupported(x, resType)
+}
+
+func opArith(op BinaryOp, x, y interface{}, resType *vdl.Type) (interface{}, error) {
+	switch tx := x.(type) {
+	case string:
+		if op == Add {
+			return tx + y.(string), nil
+		}
+	case *big.Int:
+		return arithBigInt(op, tx, y.(*big.Int))
+	case *big.Rat:
+		return arithBigRat(op, tx, y.(*big.Rat))
+	case *bigComplex:
+		return arithBigComplex(op, tx, y.(*bigComplex))
+	}
+	return nil, errNotSupported(x, resType)
+}
+
+func opIntArith(op BinaryOp, x, y interface{}, resType *vdl.Type) (interface{}, error) {
+	ix, err := constToInt(Const{x, resType})
+	if err != nil {
+		return nil, err
+	}
+	iy, err := constToInt(Const{y, resType})
+	if err != nil {
+		return nil, err
+	}
+	return arithBigInt(op, ix, iy)
+}
+
+func evalShift(op BinaryOp, x, y Const) (Const, error) {
+	// lhs must be an integer.
+	ix, err := constToInt(x)
+	if err != nil {
+		return Const{}, err
+	}
+	// rhs must be a small unsigned integer.
+	iy, err := constToInt(y)
+	if err != nil {
+		return Const{}, err
+	}
+	if iy.Sign() < 0 {
+		return Const{}, fmt.Errorf("shift amount %v isn't unsigned", cRepString(iy))
+	}
+	if iy.Cmp(maxShiftSize) > 0 {
+		return Const{}, fmt.Errorf("shift amount %v greater than max allowed %v", cRepString(iy), cRepString(maxShiftSize))
+	}
+	// Perform the shift and convert it back to the lhs type.
+	return makeConst(shiftBigInt(op, ix, uint(iy.Uint64())), x.repType)
+}
+
+// bigRatToInt converts rational to integer values as long as there isn't any
+// loss in precision, checking resType to make sure the conversion is allowed.
+func bigRatToInt(rat *big.Rat, resType *vdl.Type) (*big.Int, error) {
+	// As a special-case we allow untyped rat consts to be converted to integers,
+	// as long as they can do so without loss of precision.  This is safe since
+	// untyped rat consts have "unbounded" precision.  Typed float consts may have
+	// been rounded at some point, so we don't allow this.  This is the same
+	// behavior as Go.
+	if resType != nil {
+		return nil, fmt.Errorf("can't convert typed %s to integer", cRepTypeString(rat, resType))
+	}
+	if !rat.IsInt() {
+		return nil, fmt.Errorf("converting %s %s to integer loses precision", cRepTypeString(rat, resType), cRepString(rat))
+	}
+	return new(big.Int).Set(rat.Num()), nil
+}
+
+// bigComplexToRat converts complex to rational values as long as the complex
+// value has a zero imaginary component.
+func bigComplexToRat(b *bigComplex) (*big.Rat, error) {
+	if b.im.Cmp(bigRatZero) != 0 {
+		return nil, fmt.Errorf("can't convert complex %s to rational: nonzero imaginary", cRepString(b))
+	}
+	return &b.re, nil
+}
+
+// constToInt converts x to an integer value as long as there isn't any loss in
+// precision.
+func constToInt(x Const) (*big.Int, error) {
+	switch tx := x.rep.(type) {
+	case *big.Int:
+		return tx, nil
+	case *big.Rat:
+		return bigRatToInt(tx, x.repType)
+	case *bigComplex:
+		rat, err := bigComplexToRat(tx)
+		if err != nil {
+			return nil, err
+		}
+		return bigRatToInt(rat, x.repType)
+	}
+	return nil, fmt.Errorf("can't convert %s to integer", x.typeString())
+}
+
+// makeConst creates a Const with value rep and type totype, performing overflow
+// and conversion checks on numeric values.  If totype is nil the resulting
+// const is untyped.
+//
+// TODO(toddw): Update to handle conversions to optional types.
+func makeConst(rep interface{}, totype *vdl.Type) (Const, error) {
+	if rep == nil {
+		return Const{}, errInvalidConst
+	}
+	if totype == nil {
+		if v, ok := rep.(*vdl.Value); ok {
+			return Const{}, fmt.Errorf("can't make typed value %s untyped", v.Type())
+		}
+		return Const{rep, nil}, nil
+	}
+	switch trep := rep.(type) {
+	case bool:
+		if totype.Kind() == vdl.Bool {
+			return Const{trep, totype}, nil
+		}
+	case string:
+		if totype.Kind() == vdl.String || totype.IsBytes() {
+			return Const{trep, totype}, nil
+		}
+	case *big.Int:
+		switch totype.Kind() {
+		case vdl.Byte, vdl.Uint16, vdl.Uint32, vdl.Uint64, vdl.Int16, vdl.Int32, vdl.Int64:
+			if err := checkOverflowInt(trep, totype.Kind()); err != nil {
+				return Const{}, err
+			}
+			return Const{trep, totype}, nil
+		case vdl.Float32, vdl.Float64, vdl.Complex64, vdl.Complex128:
+			return makeConst(new(big.Rat).SetInt(trep), totype)
+		}
+	case *big.Rat:
+		switch totype.Kind() {
+		case vdl.Byte, vdl.Uint16, vdl.Uint32, vdl.Uint64, vdl.Int16, vdl.Int32, vdl.Int64:
+			// The only way we reach this conversion from big.Rat to a typed integer
+			// is for explicit type conversions.  We pass a nil Type to bigRatToInt
+			// indicating trep is untyped, to allow all conversions from float to int
+			// as long as trep is actually an integer.
+			irep, err := bigRatToInt(trep, nil)
+			if err != nil {
+				return Const{}, err
+			}
+			return makeConst(irep, totype)
+		case vdl.Float32, vdl.Float64:
+			frep, err := convertTypedRat(trep, totype.Kind())
+			if err != nil {
+				return Const{}, err
+			}
+			return Const{frep, totype}, nil
+		case vdl.Complex64, vdl.Complex128:
+			frep, err := convertTypedRat(trep, totype.Kind())
+			if err != nil {
+				return Const{}, err
+			}
+			return Const{realComplex(frep), totype}, nil
+		}
+	case *bigComplex:
+		switch totype.Kind() {
+		case vdl.Byte, vdl.Uint16, vdl.Uint32, vdl.Uint64, vdl.Int16, vdl.Int32, vdl.Int64, vdl.Float32, vdl.Float64:
+			v, err := bigComplexToRat(trep)
+			if err != nil {
+				return Const{}, err
+			}
+			return makeConst(v, totype)
+		case vdl.Complex64, vdl.Complex128:
+			v, err := convertTypedComplex(trep, totype.Kind())
+			if err != nil {
+				return Const{}, err
+			}
+			return Const{v, totype}, nil
+		}
+	}
+	return Const{}, fmt.Errorf("can't convert %s to %v", cRepString(rep), cRepTypeString(rep, totype))
+}
+
+func bitLenInt(kind vdl.Kind) int {
+	switch kind {
+	case vdl.Byte:
+		return 8
+	case vdl.Uint16, vdl.Int16:
+		return 16
+	case vdl.Uint32, vdl.Int32:
+		return 32
+	case vdl.Uint64, vdl.Int64:
+		return 64
+	default:
+		panic(fmt.Errorf("val: bitLen unhandled kind %v", kind))
+	}
+}
+
+// checkOverflowInt returns an error iff converting b to the typed integer will
+// cause overflow.
+func checkOverflowInt(b *big.Int, kind vdl.Kind) error {
+	switch bitlen := bitLenInt(kind); kind {
+	case vdl.Byte, vdl.Uint16, vdl.Uint32, vdl.Uint64:
+		if b.Sign() < 0 || b.BitLen() > bitlen {
+			return fmt.Errorf("const %v overflows uint%d", cRepString(b), bitlen)
+		}
+	case vdl.Int16, vdl.Int32, vdl.Int64:
+		// Account for two's complement, where e.g. int8 ranges from -128 to 127
+		if b.Sign() >= 0 {
+			// Positives and 0 - just check bitlen, accounting for the sign bit.
+			if b.BitLen() >= bitlen {
+				return fmt.Errorf("const %v overflows int%d", cRepString(b), bitlen)
+			}
+		} else {
+			// Negatives need to take an extra value into account (e.g. -128 for int8)
+			bplus1 := new(big.Int).Add(b, bigIntOne)
+			if bplus1.BitLen() >= bitlen {
+				return fmt.Errorf("const %v overflows int%d", cRepString(b), bitlen)
+			}
+		}
+	default:
+		panic(fmt.Errorf("val: checkOverflowInt unhandled kind %v", kind))
+	}
+	return nil
+}
+
+// checkOverflowRat returns an error iff converting b to the typed rat will
+// cause overflow or underflow.
+func checkOverflowRat(b *big.Rat, kind vdl.Kind) error {
+	// Exact zero is special cased in ieee754.
+	if b.Cmp(bigRatZero) == 0 {
+		return nil
+	}
+	// TODO(toddw): perhaps allow slightly smaller and larger values, to account
+	// for ieee754 round-to-even rules.
+	switch abs := new(big.Rat).Abs(b); kind {
+	case vdl.Float32, vdl.Complex64:
+		if abs.Cmp(bigRatAbsMin32) < 0 {
+			return fmt.Errorf("const %v underflows float32", cRepString(b))
+		}
+		if abs.Cmp(bigRatAbsMax32) > 0 {
+			return fmt.Errorf("const %v overflows float32", cRepString(b))
+		}
+	case vdl.Float64, vdl.Complex128:
+		if abs.Cmp(bigRatAbsMin64) < 0 {
+			return fmt.Errorf("const %v underflows float64", cRepString(b))
+		}
+		if abs.Cmp(bigRatAbsMax64) > 0 {
+			return fmt.Errorf("const %v overflows float64", cRepString(b))
+		}
+	default:
+		panic(fmt.Errorf("val: checkOverflowRat unhandled kind %v", kind))
+	}
+	return nil
+}
+
+// convertTypedRat converts b to the typed rat, rounding as necessary.
+func convertTypedRat(b *big.Rat, kind vdl.Kind) (*big.Rat, error) {
+	if err := checkOverflowRat(b, kind); err != nil {
+		return nil, err
+	}
+	switch f64, _ := b.Float64(); kind {
+	case vdl.Float32, vdl.Complex64:
+		return new(big.Rat).SetFloat64(float64(float32(f64))), nil
+	case vdl.Float64, vdl.Complex128:
+		return new(big.Rat).SetFloat64(f64), nil
+	default:
+		panic(fmt.Errorf("val: convertTypedRat unhandled kind %v", kind))
+	}
+}
+
+// convertTypedComplex converts b to the typed complex, rounding as necessary.
+func convertTypedComplex(b *bigComplex, kind vdl.Kind) (*bigComplex, error) {
+	re, err := convertTypedRat(&b.re, kind)
+	if err != nil {
+		return nil, err
+	}
+	im, err := convertTypedRat(&b.im, kind)
+	if err != nil {
+		return nil, err
+	}
+	return newComplex(re, im), nil
+}
+
+// coerceConsts performs implicit conversion of cl and cr based on their
+// respective types.  Returns the converted values vl and vr which are
+// guaranteed to be of the same type represented by the returned Type, which may
+// be nil if both consts are untyped.
+func coerceConsts(cl, cr Const) (interface{}, interface{}, *vdl.Type, error) {
+	var err error
+	if cl.repType != nil && cr.repType != nil {
+		// Both consts are typed - their types must match (no implicit conversion).
+		if cl.repType != cr.repType {
+			return nil, nil, nil, fmt.Errorf("type mismatch %v and %v", cl.typeString(), cr.typeString())
+		}
+		return cl.rep, cr.rep, cl.repType, nil
+	}
+	if cl.repType != nil {
+		// Convert rhs to the type of the lhs.
+		cr, err = makeConst(cr.rep, cl.repType)
+		if err != nil {
+			return nil, nil, nil, err
+		}
+		return cl.rep, cr.rep, cl.repType, nil
+	}
+	if cr.repType != nil {
+		// Convert lhs to the type of the rhs.
+		cl, err = makeConst(cl.rep, cr.repType)
+		if err != nil {
+			return nil, nil, nil, err
+		}
+		return cl.rep, cr.rep, cr.repType, nil
+	}
+	// Both consts are untyped, might need to implicitly promote untyped consts.
+	switch vl := cl.rep.(type) {
+	case bool:
+		switch vr := cr.rep.(type) {
+		case bool:
+			return vl, vr, nil, nil
+		}
+	case string:
+		switch vr := cr.rep.(type) {
+		case string:
+			return vl, vr, nil, nil
+		}
+	case *big.Int:
+		switch vr := cr.rep.(type) {
+		case *big.Int:
+			return vl, vr, nil, nil
+		case *big.Rat:
+			// Promote lhs to rat
+			return new(big.Rat).SetInt(vl), vr, nil, nil
+		case *bigComplex:
+			// Promote lhs to complex
+			return realComplex(new(big.Rat).SetInt(vl)), vr, nil, nil
+		}
+	case *big.Rat:
+		switch vr := cr.rep.(type) {
+		case *big.Int:
+			// Promote rhs to rat
+			return vl, new(big.Rat).SetInt(vr), nil, nil
+		case *big.Rat:
+			return vl, vr, nil, nil
+		case *bigComplex:
+			// Promote lhs to complex
+			return realComplex(vl), vr, nil, nil
+		}
+	case *bigComplex:
+		switch vr := cr.rep.(type) {
+		case *big.Int:
+			// Promote rhs to complex
+			return vl, realComplex(new(big.Rat).SetInt(vr)), nil, nil
+		case *big.Rat:
+			// Promote rhs to complex
+			return vl, realComplex(vr), nil, nil
+		case *bigComplex:
+			return vl, vr, nil, nil
+		}
+	}
+	return nil, nil, nil, fmt.Errorf("mismatched %s and %s", cl.typeString(), cr.typeString())
+}
+
+func compString(op BinaryOp, l, r string) bool {
+	switch op {
+	case EQ:
+		return l == r
+	case NE:
+		return l != r
+	case LT:
+		return l < r
+	case LE:
+		return l <= r
+	case GT:
+		return l > r
+	case GE:
+		return l >= r
+	default:
+		panic(fmt.Errorf("val: unhandled op %q", op))
+	}
+}
+
+func opCmpToBool(op BinaryOp, cmp int) bool {
+	switch op {
+	case EQ:
+		return cmp == 0
+	case NE:
+		return cmp != 0
+	case LT:
+		return cmp < 0
+	case LE:
+		return cmp <= 0
+	case GT:
+		return cmp > 0
+	case GE:
+		return cmp >= 0
+	default:
+		panic(fmt.Errorf("val: unhandled op %q", op))
+	}
+}
+
+func arithBigInt(op BinaryOp, l, r *big.Int) (*big.Int, error) {
+	switch op {
+	case Add:
+		return new(big.Int).Add(l, r), nil
+	case Sub:
+		return new(big.Int).Sub(l, r), nil
+	case Mul:
+		return new(big.Int).Mul(l, r), nil
+	case Div:
+		if r.Cmp(bigIntZero) == 0 {
+			return nil, errDivZero
+		}
+		return new(big.Int).Quo(l, r), nil
+	case Mod:
+		if r.Cmp(bigIntZero) == 0 {
+			return nil, errDivZero
+		}
+		return new(big.Int).Rem(l, r), nil
+	case BitAnd:
+		return new(big.Int).And(l, r), nil
+	case BitOr:
+		return new(big.Int).Or(l, r), nil
+	case BitXor:
+		return new(big.Int).Xor(l, r), nil
+	default:
+		panic(fmt.Errorf("val: unhandled op %q", op))
+	}
+}
+
+func arithBigRat(op BinaryOp, l, r *big.Rat) (*big.Rat, error) {
+	switch op {
+	case Add:
+		return new(big.Rat).Add(l, r), nil
+	case Sub:
+		return new(big.Rat).Sub(l, r), nil
+	case Mul:
+		return new(big.Rat).Mul(l, r), nil
+	case Div:
+		if r.Cmp(bigRatZero) == 0 {
+			return nil, errDivZero
+		}
+		inv := new(big.Rat).Inv(r)
+		return inv.Mul(inv, l), nil
+	default:
+		panic(fmt.Errorf("val: unhandled op %q", op))
+	}
+}
+
+func arithBigComplex(op BinaryOp, l, r *bigComplex) (*bigComplex, error) {
+	switch op {
+	case Add:
+		return new(bigComplex).Add(l, r), nil
+	case Sub:
+		return new(bigComplex).Sub(l, r), nil
+	case Mul:
+		return new(bigComplex).Mul(l, r), nil
+	case Div:
+		return new(bigComplex).Div(l, r)
+	default:
+		panic(fmt.Errorf("val: unhandled op %q", op))
+	}
+}
+
+func shiftBigInt(op BinaryOp, l *big.Int, n uint) *big.Int {
+	switch op {
+	case LeftShift:
+		return new(big.Int).Lsh(l, n)
+	case RightShift:
+		return new(big.Int).Rsh(l, n)
+	default:
+		panic(fmt.Errorf("val: unhandled op %q", op))
+	}
+}
diff --git a/lib/vdl/opconst/const_test.go b/lib/vdl/opconst/const_test.go
new file mode 100644
index 0000000..9d69093
--- /dev/null
+++ b/lib/vdl/opconst/const_test.go
@@ -0,0 +1,616 @@
+// 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 opconst
+
+import (
+	"fmt"
+	"math/big"
+	"testing"
+
+	"v.io/v23/vdl"
+)
+
+const (
+	noType           = "must be assigned a type"
+	cantConvert      = "can't convert"
+	overflows        = "overflows"
+	underflows       = "underflows"
+	losesPrecision   = "loses precision"
+	nonzeroImaginary = "nonzero imaginary"
+	notSupported     = "not supported"
+	divByZero        = "divide by zero"
+)
+
+var (
+	bi0              = new(big.Int)
+	bi1, bi2, bi3    = big.NewInt(1), big.NewInt(2), big.NewInt(3)
+	bi4, bi5, bi6    = big.NewInt(4), big.NewInt(5), big.NewInt(6)
+	bi7, bi8, bi9    = big.NewInt(7), big.NewInt(8), big.NewInt(9)
+	bi_neg1, bi_neg2 = big.NewInt(-1), big.NewInt(-2)
+
+	br0              = new(big.Rat)
+	br1, br2, br3    = big.NewRat(1, 1), big.NewRat(2, 1), big.NewRat(3, 1)
+	br4, br5, br6    = big.NewRat(4, 1), big.NewRat(5, 1), big.NewRat(6, 1)
+	br7, br8, br9    = big.NewRat(7, 1), big.NewRat(8, 1), big.NewRat(9, 1)
+	br_neg1, br_neg2 = big.NewRat(-1, 1), big.NewRat(-2, 1)
+)
+
+func boolConst(t *vdl.Type, x bool) Const          { return FromValue(boolValue(t, x)) }
+func stringConst(t *vdl.Type, x string) Const      { return FromValue(stringValue(t, x)) }
+func bytesConst(t *vdl.Type, x string) Const       { return FromValue(bytesValue(t, x)) }
+func bytes3Const(t *vdl.Type, x string) Const      { return FromValue(bytes3Value(t, x)) }
+func intConst(t *vdl.Type, x int64) Const          { return FromValue(intValue(t, x)) }
+func uintConst(t *vdl.Type, x uint64) Const        { return FromValue(uintValue(t, x)) }
+func floatConst(t *vdl.Type, x float64) Const      { return FromValue(floatValue(t, x)) }
+func complexConst(t *vdl.Type, x complex128) Const { return FromValue(complexValue(t, x)) }
+func structNumConst(t *vdl.Type, x float64) Const {
+	return FromValue(structNumValue(t, sn{"A", x}))
+}
+
+func constEqual(a, b Const) bool {
+	if !a.IsValid() && !b.IsValid() {
+		return true
+	}
+	res, err := EvalBinary(EQ, a, b)
+	if err != nil || !res.IsValid() {
+		return false
+	}
+	val, err := res.ToValue()
+	return err == nil && val != nil && val.Kind() == vdl.Bool && val.Bool()
+}
+
+func TestConstInvalid(t *testing.T) {
+	x := Const{}
+	if x.IsValid() {
+		t.Errorf("zero Const IsValid")
+	}
+	if got, want := x.String(), "invalid"; got != want {
+		t.Errorf("ToValue got string %v, want %v", got, want)
+	}
+	{
+		value, err := x.ToValue()
+		if value != nil {
+			t.Errorf("ToValue got valid value %v, want nil", value)
+		}
+		if got, want := fmt.Sprint(err), "invalid const"; got != want {
+			t.Errorf("ToValue got error %q, want %q", got, want)
+		}
+	}
+	{
+		result, err := x.Convert(vdl.BoolType)
+		if result.IsValid() {
+			t.Errorf("Convert got valid result %v, want invalid", result)
+		}
+		if got, want := fmt.Sprint(err), "invalid const"; got != want {
+			t.Errorf("Convert got error %q, want %q", got, want)
+		}
+	}
+	unary := []UnaryOp{LogicNot, Pos, Neg, BitNot}
+	for _, op := range unary {
+		result, err := EvalUnary(op, Const{})
+		if result.IsValid() {
+			t.Errorf("EvalUnary got valid result %v, want invalid", result)
+		}
+		if got, want := fmt.Sprint(err), "invalid const"; got != want {
+			t.Errorf("EvalUnary got error %q, want %q", got, want)
+		}
+	}
+	binary := []BinaryOp{LogicAnd, LogicOr, EQ, NE, LT, LE, GT, GE, Add, Sub, Mul, Div, Mod, BitAnd, BitOr, BitXor, LeftShift, RightShift}
+	for _, op := range binary {
+		result, err := EvalBinary(op, Const{}, Const{})
+		if result.IsValid() {
+			t.Errorf("EvalBinary got valid result %v, want invalid", result)
+		}
+		if got, want := fmt.Sprint(err), "invalid const"; got != want {
+			t.Errorf("EvalBinary got error %q, want %q", got, want)
+		}
+	}
+}
+
+func TestConstToValueOK(t *testing.T) {
+	tests := []*vdl.Value{
+		boolValue(vdl.BoolType, true), boolValue(boolTypeN, true),
+		stringValue(vdl.StringType, "abc"), stringValue(stringTypeN, "abc"),
+		bytesValue(bytesType, "abc"), bytesValue(bytesTypeN, "abc"),
+		bytes3Value(bytesType, "abc"), bytes3Value(bytesTypeN, "abc"),
+		intValue(vdl.Int32Type, 123), intValue(int32TypeN, 123),
+		uintValue(vdl.Uint32Type, 123), uintValue(uint32TypeN, 123),
+		floatValue(vdl.Float32Type, 123), floatValue(float32TypeN, 123),
+		complexValue(vdl.Complex64Type, 123), complexValue(complex64TypeN, 123),
+		structNumValue(structAIntType, sn{"A", 123}), structNumValue(structAIntTypeN, sn{"A", 123}),
+	}
+	for _, test := range tests {
+		c := FromValue(test)
+		v, err := c.ToValue()
+		if got, want := v, test; !vdl.EqualValue(got, want) {
+			t.Errorf("%v.ToValue got %v, want %v", c, got, want)
+		}
+		expectErr(t, err, "", "%v.ToValue", c)
+	}
+}
+
+func TestConstToValueImplicit(t *testing.T) {
+	tests := []struct {
+		C Const
+		V *vdl.Value
+	}{
+		{Boolean(true), vdl.BoolValue(true)},
+		{String("abc"), vdl.StringValue("abc")},
+	}
+	for _, test := range tests {
+		c := FromValue(test.V)
+		if got, want := c, test.C; !constEqual(got, want) {
+			t.Errorf("FromValue(%v) got %v, want %v", test.C, got, want)
+		}
+		v, err := test.C.ToValue()
+		if got, want := v, test.V; !vdl.EqualValue(got, want) {
+			t.Errorf("%v.ToValue got %v, want %v", test.C, got, want)
+		}
+		expectErr(t, err, "", "%v.ToValue", test.C)
+	}
+}
+
+func TestConstToValueError(t *testing.T) {
+	tests := []struct {
+		C      Const
+		errstr string
+	}{
+		{Integer(bi1), noType},
+		{Rational(br1), noType},
+		{Complex(br1, br0), noType},
+	}
+	for _, test := range tests {
+		v, err := test.C.ToValue()
+		if v != nil {
+			t.Errorf("%v.ToValue got %v, want nil", test.C, v)
+		}
+		expectErr(t, err, test.errstr, "%v.ToValue", test.C)
+	}
+}
+
+type c []Const
+type v []*vdl.Value
+
+func TestConstConvertOK(t *testing.T) {
+	// Each test has a set of consts C and values V that are all convertible to
+	// each other and equivalent.
+	tests := []struct {
+		C c
+		V v
+	}{
+		{c{Boolean(true)},
+			v{boolValue(vdl.BoolType, true), boolValue(boolTypeN, true)}},
+		{c{String("abc")},
+			v{stringValue(vdl.StringType, "abc"), stringValue(stringTypeN, "abc"),
+				bytesValue(bytesType, "abc"), bytesValue(bytesTypeN, "abc"),
+				bytes3Value(bytes3Type, "abc"), bytes3Value(bytes3TypeN, "abc")}},
+		{c{Integer(bi1), Rational(br1), Complex(br1, br0)},
+			v{intValue(vdl.Int32Type, 1), intValue(int32TypeN, 1),
+				uintValue(vdl.Uint32Type, 1), uintValue(uint32TypeN, 1),
+				floatValue(vdl.Float32Type, 1), floatValue(float32TypeN, 1),
+				complexValue(vdl.Complex64Type, 1), complexValue(complex64TypeN, 1)}},
+		{c{Integer(bi_neg1), Rational(br_neg1), Complex(br_neg1, br0)},
+			v{intValue(vdl.Int32Type, -1), intValue(int32TypeN, -1),
+				floatValue(vdl.Float32Type, -1), floatValue(float32TypeN, -1),
+				complexValue(vdl.Complex64Type, -1), complexValue(complex64TypeN, -1)}},
+		{c{Rational(big.NewRat(1, 2)), Complex(big.NewRat(1, 2), br0)},
+			v{floatValue(vdl.Float32Type, 0.5), floatValue(float32TypeN, 0.5),
+				complexValue(vdl.Complex64Type, 0.5), complexValue(complex64TypeN, 0.5)}},
+		{c{Complex(br1, br1)},
+			v{complexValue(vdl.Complex64Type, 1+1i), complexValue(complex64TypeN, 1+1i)}},
+		// Check implicit conversion of untyped bool and string consts.
+		{c{Boolean(true)},
+			v{boolValue(vdl.BoolType, true), anyValue(boolValue(vdl.BoolType, true))}},
+		{c{String("abc")},
+			v{stringValue(vdl.StringType, "abc"), anyValue(stringValue(vdl.StringType, "abc"))}},
+	}
+	for _, test := range tests {
+		// Create a slice of consts containing everything in C and V.
+		consts := make([]Const, len(test.C))
+		copy(consts, test.C)
+		for _, v := range test.V {
+			consts = append(consts, FromValue(v))
+		}
+		// Loop through the consts, and convert each one to each item in V.
+		for _, c := range consts {
+			for _, v := range test.V {
+				vt := v.Type()
+				got, err := c.Convert(vt)
+				if want := FromValue(v); !constEqual(got, want) {
+					t.Errorf("%v.Convert(%v) got %v, want %v", c, vt, got, want)
+				}
+				expectErr(t, err, "", "%v.Convert(%v)", c, vt)
+			}
+		}
+	}
+}
+
+type ty []*vdl.Type
+
+func TestConstConvertError(t *testing.T) {
+	// Each test has a single const C that returns an error that contains errstr
+	// when converted to any of the types in the set T.
+	tests := []struct {
+		C      Const
+		T      ty
+		errstr string
+	}{
+		{Boolean(true),
+			ty{vdl.StringType, stringTypeN, bytesType, bytesTypeN, bytes3Type, bytes3TypeN,
+				vdl.Int32Type, int32TypeN, vdl.Uint32Type, uint32TypeN,
+				vdl.Float32Type, float32TypeN, vdl.Complex64Type, complex64TypeN,
+				structAIntType, structAIntTypeN},
+			cantConvert},
+		{String("abc"),
+			ty{vdl.BoolType, boolTypeN,
+				vdl.Int32Type, int32TypeN, vdl.Uint32Type, uint32TypeN,
+				vdl.Float32Type, float32TypeN, vdl.Complex64Type, complex64TypeN,
+				structAIntType, structAIntTypeN},
+			cantConvert},
+		{Integer(bi1),
+			ty{vdl.BoolType, boolTypeN,
+				vdl.StringType, stringTypeN, bytesType, bytesTypeN, bytes3Type, bytes3TypeN,
+				structAIntType, structAIntTypeN},
+			cantConvert},
+		{Rational(br1),
+			ty{vdl.BoolType, boolTypeN,
+				vdl.StringType, stringTypeN, bytesType, bytesTypeN, bytes3Type, bytes3TypeN,
+				structAIntType, structAIntTypeN},
+			cantConvert},
+		{Complex(br1, br0),
+			ty{vdl.BoolType, boolTypeN,
+				vdl.StringType, stringTypeN, bytesType, bytesTypeN, bytes3Type, bytes3TypeN,
+				structAIntType, structAIntTypeN},
+			cantConvert},
+		// Bounds tests
+		{Integer(bi_neg1), ty{vdl.Uint32Type, uint32TypeN}, overflows},
+		{Integer(big.NewInt(1 << 32)), ty{vdl.Int32Type, int32TypeN}, overflows},
+		{Integer(big.NewInt(1 << 33)), ty{vdl.Uint32Type, uint32TypeN}, overflows},
+		{Rational(br_neg1), ty{vdl.Uint32Type, uint32TypeN}, overflows},
+		{Rational(big.NewRat(1<<32, 1)), ty{vdl.Int32Type, int32TypeN}, overflows},
+		{Rational(big.NewRat(1<<33, 1)), ty{vdl.Uint32Type, uint32TypeN}, overflows},
+		{Rational(big.NewRat(1, 2)),
+			ty{vdl.Int32Type, int32TypeN, vdl.Uint32Type, uint32TypeN},
+			losesPrecision},
+		{Rational(bigRatAbsMin64), ty{vdl.Float32Type, float32TypeN}, underflows},
+		{Rational(bigRatAbsMax64), ty{vdl.Float32Type, float32TypeN}, overflows},
+		{Complex(br_neg1, br0), ty{vdl.Uint32Type, uint32TypeN}, overflows},
+		{Complex(big.NewRat(1<<32, 1), br0), ty{vdl.Int32Type, int32TypeN}, overflows},
+		{Complex(big.NewRat(1<<33, 1), br0), ty{vdl.Uint32Type, uint32TypeN}, overflows},
+		{Complex(big.NewRat(1, 2), br0),
+			ty{vdl.Int32Type, int32TypeN, vdl.Uint32Type, uint32TypeN},
+			losesPrecision},
+		{Complex(bigRatAbsMin64, br0), ty{vdl.Float32Type, float32TypeN}, underflows},
+		{Complex(bigRatAbsMax64, br0), ty{vdl.Float32Type, float32TypeN}, overflows},
+		{Complex(br0, br1),
+			ty{vdl.Int32Type, int32TypeN, vdl.Uint32Type, uint32TypeN, vdl.Float32Type, float32TypeN},
+			nonzeroImaginary},
+	}
+	for _, test := range tests {
+		for _, ct := range test.T {
+			result, err := test.C.Convert(ct)
+			if result.IsValid() {
+				t.Errorf("%v.Convert(%v) result got %v, want invalid", test.C, ct, result)
+			}
+			expectErr(t, err, test.errstr, "%v.Convert(%v)", test.C, ct)
+		}
+	}
+}
+
+func TestConstUnaryOpOK(t *testing.T) {
+	tests := []struct {
+		op        UnaryOp
+		x, expect Const
+	}{
+		{LogicNot, Boolean(true), Boolean(false)},
+		{LogicNot, boolConst(vdl.BoolType, false), boolConst(vdl.BoolType, true)},
+		{LogicNot, boolConst(boolTypeN, true), boolConst(boolTypeN, false)},
+
+		{Pos, Integer(bi1), Integer(bi1)},
+		{Pos, Rational(br1), Rational(br1)},
+		{Pos, Complex(br1, br1), Complex(br1, br1)},
+		{Pos, intConst(vdl.Int32Type, 1), intConst(vdl.Int32Type, 1)},
+		{Pos, floatConst(float32TypeN, 1), floatConst(float32TypeN, 1)},
+		{Pos, complexConst(complex64TypeN, 1), complexConst(complex64TypeN, 1)},
+
+		{Neg, Integer(bi1), Integer(bi_neg1)},
+		{Neg, Rational(br1), Rational(br_neg1)},
+		{Neg, Complex(br1, br1), Complex(br_neg1, br_neg1)},
+		{Neg, intConst(vdl.Int32Type, 1), intConst(vdl.Int32Type, -1)},
+		{Neg, floatConst(float32TypeN, 1), floatConst(float32TypeN, -1)},
+		{Neg, complexConst(complex64TypeN, 1), complexConst(complex64TypeN, -1)},
+
+		{BitNot, Integer(bi1), Integer(bi_neg2)},
+		{BitNot, Rational(br1), Integer(bi_neg2)},
+		{BitNot, Complex(br1, br0), Integer(bi_neg2)},
+		{BitNot, intConst(vdl.Int32Type, 1), intConst(vdl.Int32Type, -2)},
+		{BitNot, uintConst(uint32TypeN, 1), uintConst(uint32TypeN, 1<<32-2)},
+	}
+	for _, test := range tests {
+		result, err := EvalUnary(test.op, test.x)
+		if got, want := result, test.expect; !constEqual(got, want) {
+			t.Errorf("EvalUnary(%v, %v) result got %v, want %v", test.op, test.x, got, want)
+		}
+		expectErr(t, err, "", "EvalUnary(%v, %v)", test.op, test.x)
+	}
+}
+
+func TestConstUnaryOpError(t *testing.T) {
+	tests := []struct {
+		op     UnaryOp
+		x      Const
+		errstr string
+	}{
+		{LogicNot, String("abc"), notSupported},
+		{LogicNot, Integer(bi1), notSupported},
+		{LogicNot, Rational(br1), notSupported},
+		{LogicNot, Complex(br1, br1), notSupported},
+		{LogicNot, structNumConst(structAIntTypeN, 999), notSupported},
+
+		{Pos, Boolean(false), notSupported},
+		{Pos, String("abc"), notSupported},
+		{Pos, structNumConst(structAIntTypeN, 999), notSupported},
+
+		{Neg, Boolean(false), notSupported},
+		{Neg, String("abc"), notSupported},
+		{Neg, structNumConst(structAIntTypeN, 999), notSupported},
+		{Neg, intConst(vdl.Int32Type, 1<<32-1), overflows},
+
+		{BitNot, Boolean(false), cantConvert},
+		{BitNot, String("abc"), cantConvert},
+		{BitNot, Rational(big.NewRat(1, 2)), losesPrecision},
+		{BitNot, Complex(br1, br1), nonzeroImaginary},
+		{BitNot, structNumConst(structAIntTypeN, 999), notSupported},
+		{BitNot, floatConst(float32TypeN, 1), cantConvert},
+		{BitNot, complexConst(complex64TypeN, 1), cantConvert},
+	}
+	for _, test := range tests {
+		result, err := EvalUnary(test.op, test.x)
+		if result.IsValid() {
+			t.Errorf("EvalUnary(%v, %v) result got %v, want invalid", test.op, test.x, result)
+		}
+		expectErr(t, err, test.errstr, "EvalUnary(%v, %v)", test.op, test.x)
+	}
+}
+
+func TestConstBinaryOpOK(t *testing.T) {
+	tests := []struct {
+		op           BinaryOp
+		x, y, expect Const
+	}{
+		{LogicAnd, Boolean(true), Boolean(true), Boolean(true)},
+		{LogicAnd, Boolean(true), Boolean(false), Boolean(false)},
+		{LogicAnd, Boolean(false), Boolean(true), Boolean(false)},
+		{LogicAnd, Boolean(false), Boolean(false), Boolean(false)},
+		{LogicAnd, boolConst(boolTypeN, true), boolConst(boolTypeN, true), boolConst(boolTypeN, true)},
+		{LogicAnd, boolConst(boolTypeN, true), boolConst(boolTypeN, false), boolConst(boolTypeN, false)},
+		{LogicAnd, boolConst(boolTypeN, false), boolConst(boolTypeN, true), boolConst(boolTypeN, false)},
+		{LogicAnd, boolConst(boolTypeN, false), boolConst(boolTypeN, false), boolConst(boolTypeN, false)},
+
+		{LogicOr, Boolean(true), Boolean(true), Boolean(true)},
+		{LogicOr, Boolean(true), Boolean(false), Boolean(true)},
+		{LogicOr, Boolean(false), Boolean(true), Boolean(true)},
+		{LogicOr, Boolean(false), Boolean(false), Boolean(false)},
+		{LogicOr, boolConst(boolTypeN, true), boolConst(boolTypeN, true), boolConst(boolTypeN, true)},
+		{LogicOr, boolConst(boolTypeN, true), boolConst(boolTypeN, false), boolConst(boolTypeN, true)},
+		{LogicOr, boolConst(boolTypeN, false), boolConst(boolTypeN, true), boolConst(boolTypeN, true)},
+		{LogicOr, boolConst(boolTypeN, false), boolConst(boolTypeN, false), boolConst(boolTypeN, false)},
+
+		{Add, String("abc"), String("def"), String("abcdef")},
+		{Add, Integer(bi1), Integer(bi1), Integer(bi2)},
+		{Add, Rational(br1), Rational(br1), Rational(br2)},
+		{Add, Complex(br1, br1), Complex(br1, br1), Complex(br2, br2)},
+		{Add, stringConst(stringTypeN, "abc"), stringConst(stringTypeN, "def"), stringConst(stringTypeN, "abcdef")},
+		{Add, bytesConst(bytesTypeN, "abc"), bytesConst(bytesTypeN, "def"), bytesConst(bytesTypeN, "abcdef")},
+		{Add, intConst(int32TypeN, 1), intConst(int32TypeN, 1), intConst(int32TypeN, 2)},
+		{Add, uintConst(uint32TypeN, 1), uintConst(uint32TypeN, 1), uintConst(uint32TypeN, 2)},
+		{Add, floatConst(float32TypeN, 1), floatConst(float32TypeN, 1), floatConst(float32TypeN, 2)},
+		{Add, complexConst(complex64TypeN, 1), complexConst(complex64TypeN, 1), complexConst(complex64TypeN, 2)},
+
+		{Sub, Integer(bi2), Integer(bi1), Integer(bi1)},
+		{Sub, Rational(br2), Rational(br1), Rational(br1)},
+		{Sub, Complex(br2, br2), Complex(br1, br1), Complex(br1, br1)},
+		{Sub, intConst(int32TypeN, 2), intConst(int32TypeN, 1), intConst(int32TypeN, 1)},
+		{Sub, uintConst(uint32TypeN, 2), uintConst(uint32TypeN, 1), uintConst(uint32TypeN, 1)},
+		{Sub, floatConst(float32TypeN, 2), floatConst(float32TypeN, 1), floatConst(float32TypeN, 1)},
+		{Sub, complexConst(complex64TypeN, 2), complexConst(complex64TypeN, 1), complexConst(complex64TypeN, 1)},
+
+		{Mul, Integer(bi2), Integer(bi2), Integer(bi4)},
+		{Mul, Rational(br2), Rational(br2), Rational(br4)},
+		{Mul, Complex(br2, br2), Complex(br2, br2), Complex(br0, br8)},
+		{Mul, intConst(int32TypeN, 2), intConst(int32TypeN, 2), intConst(int32TypeN, 4)},
+		{Mul, uintConst(uint32TypeN, 2), uintConst(uint32TypeN, 2), uintConst(uint32TypeN, 4)},
+		{Mul, floatConst(float32TypeN, 2), floatConst(float32TypeN, 2), floatConst(float32TypeN, 4)},
+		{Mul, complexConst(complex64TypeN, 2+2i), complexConst(complex64TypeN, 2+2i), complexConst(complex64TypeN, 8i)},
+
+		{Div, Integer(bi4), Integer(bi2), Integer(bi2)},
+		{Div, Rational(br4), Rational(br2), Rational(br2)},
+		{Div, Complex(br4, br4), Complex(br2, br2), Complex(br2, br0)},
+		{Div, intConst(int32TypeN, 4), intConst(int32TypeN, 2), intConst(int32TypeN, 2)},
+		{Div, uintConst(uint32TypeN, 4), uintConst(uint32TypeN, 2), uintConst(uint32TypeN, 2)},
+		{Div, floatConst(float32TypeN, 4), floatConst(float32TypeN, 2), floatConst(float32TypeN, 2)},
+		{Div, complexConst(complex64TypeN, 4+4i), complexConst(complex64TypeN, 2+2i), complexConst(complex64TypeN, 2)},
+
+		{Mod, Integer(bi3), Integer(bi2), Integer(bi1)},
+		{Mod, Rational(br3), Rational(br2), Rational(br1)},
+		{Mod, Complex(br3, br0), Complex(br2, br0), Complex(br1, br0)},
+		{Mod, intConst(int32TypeN, 3), intConst(int32TypeN, 2), intConst(int32TypeN, 1)},
+		{Mod, uintConst(uint32TypeN, 3), uintConst(uint32TypeN, 2), uintConst(uint32TypeN, 1)},
+
+		{BitAnd, Integer(bi3), Integer(bi2), Integer(bi2)},
+		{BitAnd, Rational(br3), Rational(br2), Rational(br2)},
+		{BitAnd, Complex(br3, br0), Complex(br2, br0), Complex(br2, br0)},
+		{BitAnd, intConst(int32TypeN, 3), intConst(int32TypeN, 2), intConst(int32TypeN, 2)},
+		{BitAnd, uintConst(uint32TypeN, 3), uintConst(uint32TypeN, 2), uintConst(uint32TypeN, 2)},
+
+		{BitOr, Integer(bi5), Integer(bi3), Integer(bi7)},
+		{BitOr, Rational(br5), Rational(br3), Rational(br7)},
+		{BitOr, Complex(br5, br0), Complex(br3, br0), Complex(br7, br0)},
+		{BitOr, intConst(int32TypeN, 5), intConst(int32TypeN, 3), intConst(int32TypeN, 7)},
+		{BitOr, uintConst(uint32TypeN, 5), uintConst(uint32TypeN, 3), uintConst(uint32TypeN, 7)},
+
+		{BitXor, Integer(bi5), Integer(bi3), Integer(bi6)},
+		{BitXor, Rational(br5), Rational(br3), Rational(br6)},
+		{BitXor, Complex(br5, br0), Complex(br3, br0), Complex(br6, br0)},
+		{BitXor, intConst(int32TypeN, 5), intConst(int32TypeN, 3), intConst(int32TypeN, 6)},
+		{BitXor, uintConst(uint32TypeN, 5), uintConst(uint32TypeN, 3), uintConst(uint32TypeN, 6)},
+
+		{LeftShift, Integer(bi3), Integer(bi1), Integer(bi6)},
+		{LeftShift, Rational(br3), Rational(br1), Rational(br6)},
+		{LeftShift, Complex(br3, br0), Complex(br1, br0), Complex(br6, br0)},
+		{LeftShift, intConst(int32TypeN, 3), intConst(int32TypeN, 1), intConst(int32TypeN, 6)},
+		{LeftShift, uintConst(uint32TypeN, 3), uintConst(uint32TypeN, 1), uintConst(uint32TypeN, 6)},
+
+		{RightShift, Integer(bi5), Integer(bi1), Integer(bi2)},
+		{RightShift, Rational(br5), Rational(br1), Rational(br2)},
+		{RightShift, Complex(br5, br0), Complex(br1, br0), Complex(br2, br0)},
+		{RightShift, intConst(int32TypeN, 5), intConst(int32TypeN, 1), intConst(int32TypeN, 2)},
+		{RightShift, uintConst(uint32TypeN, 5), uintConst(uint32TypeN, 1), uintConst(uint32TypeN, 2)},
+	}
+	for _, test := range tests {
+		result, err := EvalBinary(test.op, test.x, test.y)
+		if got, want := result, test.expect; !constEqual(got, want) {
+			t.Errorf("EvalBinary(%v, %v, %v) result got %v, want %v", test.op, test.x, test.y, got, want)
+		}
+		expectErr(t, err, "", "EvalBinary(%v, %v, %v)", test.op, test.x, test.y)
+	}
+}
+
+func expectComp(t *testing.T, op BinaryOp, x, y Const, expect bool) {
+	result, err := EvalBinary(op, x, y)
+	if got, want := result, Boolean(expect); !constEqual(got, want) {
+		t.Errorf("EvalBinary(%v, %v, %v) result got %v, want %v", op, x, y, got, want)
+	}
+	expectErr(t, err, "", "EvalBinary(%v, %v, %v)", op, x, y)
+}
+
+func TestConstEQNE(t *testing.T) {
+	tests := []struct {
+		x, y Const // x != y
+	}{
+		{Boolean(false), Boolean(true)},
+		{String("abc"), String("def")},
+		{Complex(br1, br1), Complex(br2, br2)},
+
+		{boolConst(boolTypeN, false), boolConst(boolTypeN, true)},
+		{complexConst(complex64TypeN, 1), complexConst(complex64TypeN, 2)},
+		{structNumConst(structAIntTypeN, 1), structNumConst(structAIntTypeN, 2)},
+	}
+	for _, test := range tests {
+		expectComp(t, EQ, test.x, test.x, true)
+		expectComp(t, EQ, test.x, test.y, false)
+		expectComp(t, EQ, test.y, test.x, false)
+		expectComp(t, EQ, test.y, test.y, true)
+
+		expectComp(t, NE, test.x, test.x, false)
+		expectComp(t, NE, test.x, test.y, true)
+		expectComp(t, NE, test.y, test.x, true)
+		expectComp(t, NE, test.y, test.y, false)
+	}
+}
+
+func TestConstOrdered(t *testing.T) {
+	tests := []struct {
+		x, y Const // x < y
+	}{
+		{String("abc"), String("def")},
+		{Integer(bi1), Integer(bi2)},
+		{Rational(br1), Rational(br2)},
+
+		{stringConst(stringTypeN, "abc"), stringConst(stringTypeN, "def")},
+		{bytesConst(bytesTypeN, "abc"), bytesConst(bytesTypeN, "def")},
+		{bytes3Const(bytes3TypeN, "abc"), bytes3Const(bytes3TypeN, "def")},
+		{intConst(int32TypeN, 1), intConst(int32TypeN, 2)},
+		{uintConst(uint32TypeN, 1), uintConst(uint32TypeN, 2)},
+		{floatConst(float32TypeN, 1), floatConst(float32TypeN, 2)},
+	}
+	for _, test := range tests {
+		expectComp(t, EQ, test.x, test.x, true)
+		expectComp(t, EQ, test.x, test.y, false)
+		expectComp(t, EQ, test.y, test.x, false)
+		expectComp(t, EQ, test.y, test.y, true)
+
+		expectComp(t, NE, test.x, test.x, false)
+		expectComp(t, NE, test.x, test.y, true)
+		expectComp(t, NE, test.y, test.x, true)
+		expectComp(t, NE, test.y, test.y, false)
+
+		expectComp(t, LT, test.x, test.x, false)
+		expectComp(t, LT, test.x, test.y, true)
+		expectComp(t, LT, test.y, test.x, false)
+		expectComp(t, LT, test.y, test.y, false)
+
+		expectComp(t, LE, test.x, test.x, true)
+		expectComp(t, LE, test.x, test.y, true)
+		expectComp(t, LE, test.y, test.x, false)
+		expectComp(t, LE, test.y, test.y, true)
+
+		expectComp(t, GT, test.x, test.x, false)
+		expectComp(t, GT, test.x, test.y, false)
+		expectComp(t, GT, test.y, test.x, true)
+		expectComp(t, GT, test.y, test.y, false)
+
+		expectComp(t, GE, test.x, test.x, true)
+		expectComp(t, GE, test.x, test.y, false)
+		expectComp(t, GE, test.y, test.x, true)
+		expectComp(t, GE, test.y, test.y, true)
+	}
+}
+
+type bo []BinaryOp
+
+func TestConstBinaryOpError(t *testing.T) {
+	// For each op in Bops and each x in C, (x op x) returns errstr.
+	tests := []struct {
+		Bops   bo
+		C      c
+		errstr string
+	}{
+		// Type not supported / can't convert errors
+		{bo{LogicAnd, LogicOr},
+			c{String("abc"),
+				stringConst(stringTypeN, "abc"),
+				bytesConst(bytesTypeN, "abc"), bytes3Const(bytes3TypeN, "abc"),
+				Integer(bi1), intConst(int32TypeN, 1), uintConst(uint32TypeN, 1),
+				Rational(br1), floatConst(float32TypeN, 1),
+				Complex(br1, br1), complexConst(complex64TypeN, 1),
+				structNumConst(structAIntType, 1), structNumConst(structAIntTypeN, 1)},
+			notSupported},
+		{bo{LT, LE, GT, GE},
+			c{Boolean(true), boolConst(boolTypeN, false),
+				Complex(br1, br1), complexConst(complex64TypeN, 1),
+				structNumConst(structAIntType, 1), structNumConst(structAIntTypeN, 1)},
+			notSupported},
+		{bo{Add},
+			c{structNumConst(structAIntType, 1), structNumConst(structAIntTypeN, 1)},
+			notSupported},
+		{bo{Sub, Mul, Div},
+			c{String("abc"), stringConst(stringTypeN, "abc"),
+				bytesConst(bytesTypeN, "abc"), bytes3Const(bytes3TypeN, "abc"),
+				structNumConst(structAIntType, 1), structNumConst(structAIntTypeN, 1)},
+			notSupported},
+		{bo{Mod, BitAnd, BitOr, BitXor, LeftShift, RightShift},
+			c{String("abc"), stringConst(stringTypeN, "abc"),
+				bytesConst(bytesTypeN, "abc"), bytes3Const(bytes3TypeN, "abc"),
+				structNumConst(structAIntType, 1), structNumConst(structAIntTypeN, 1)},
+			cantConvert},
+		// Bounds checking
+		{bo{Add}, c{uintConst(uint32TypeN, 1<<31)}, overflows},
+		{bo{Mul}, c{uintConst(uint32TypeN, 1<<16)}, overflows},
+		{bo{Div}, c{uintConst(uint32TypeN, 0)}, divByZero},
+		{bo{LeftShift}, c{uintConst(uint32TypeN, 32)}, overflows},
+	}
+	for _, test := range tests {
+		for _, op := range test.Bops {
+			for _, c := range test.C {
+				result, err := EvalBinary(op, c, c)
+				if result.IsValid() {
+					t.Errorf("EvalBinary(%v, %v, %v) result got %v, want invalid", op, c, c, result)
+				}
+				expectErr(t, err, test.errstr, "EvalBinary(%v, %v, %v)", op, c, c)
+			}
+		}
+	}
+}
diff --git a/lib/vdl/opconst/op.go b/lib/vdl/opconst/op.go
new file mode 100644
index 0000000..bb55c41
--- /dev/null
+++ b/lib/vdl/opconst/op.go
@@ -0,0 +1,100 @@
+// 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 opconst
+
+// UnaryOp represents a unary operation to be performed on a Const.
+type UnaryOp uint
+
+// BinaryOp represents a binary operation to be performed on two Consts.
+type BinaryOp uint
+
+const (
+	InvalidUnaryOp UnaryOp = iota
+	LogicNot               //  ! logical not
+	Pos                    //  + positive
+	Neg                    //  - negate
+	BitNot                 //  ^ bitwise not
+)
+
+const (
+	InvalidBinaryOp BinaryOp = iota
+	LogicAnd                 //  && logical and
+	LogicOr                  //  || logical or
+	EQ                       //  == equal
+	NE                       //  != not equal
+	LT                       //  <  less than
+	LE                       //  <= less than or equal
+	GT                       //  >  greater than
+	GE                       //  >= greater than or equal
+	Add                      //  +  add
+	Sub                      //  -  subtract
+	Mul                      //  *  multiply
+	Div                      //  /  divide
+	Mod                      //  %  modulo
+	BitAnd                   //  &  bitwise and
+	BitOr                    //  |  bitwise or
+	BitXor                   //  ^  bitwise xor
+	LeftShift                //  << left shift
+	RightShift               //  >> right shift
+)
+
+var unaryOpTable = [...]struct {
+	symbol, desc string
+}{
+	InvalidUnaryOp: {"invalid", "invalid"},
+	LogicNot:       {"!", "logic_not"},
+	Pos:            {"+", "pos"},
+	Neg:            {"-", "neg"},
+	BitNot:         {"^", "bit_not"},
+}
+
+var binaryOpTable = [...]struct {
+	symbol, desc string
+}{
+	InvalidBinaryOp: {"invalid", "invalid"},
+	LogicAnd:        {"&&", "logic_and"},
+	LogicOr:         {"||", "logic_or"},
+	EQ:              {"==", "eq"},
+	NE:              {"!=", "ne"},
+	LT:              {"<", "lt"},
+	LE:              {"<=", "le"},
+	GT:              {">", "gt"},
+	GE:              {">=", "ge"},
+	Add:             {"+", "add"},
+	Sub:             {"-", "sub"},
+	Mul:             {"*", "mul"},
+	Div:             {"/", "div"},
+	Mod:             {"%", "mod"},
+	BitAnd:          {"&", "bit_and"},
+	BitOr:           {"|", "bit_or"},
+	BitXor:          {"^", "bit_xor"},
+	LeftShift:       {"<<", "left_shift"},
+	RightShift:      {">>", "right_shift"},
+}
+
+func (op UnaryOp) String() string  { return unaryOpTable[op].desc }
+func (op BinaryOp) String() string { return binaryOpTable[op].desc }
+
+// ToUnaryOp converts s into a UnaryOp, or returns InvalidUnaryOp if it couldn't
+// be converted.
+func ToUnaryOp(s string) UnaryOp {
+	for op, item := range unaryOpTable {
+		if s == item.symbol || s == item.desc {
+			return UnaryOp(op)
+		}
+	}
+	return InvalidUnaryOp
+}
+
+// ToBinaryOp converts s into a BinaryOp, or returns InvalidBinaryOp if it
+// couldn't be converted.
+func ToBinaryOp(s string) BinaryOp {
+	for op, item := range binaryOpTable {
+		if s == item.symbol || s == item.desc {
+			return BinaryOp(op)
+		}
+	}
+	return InvalidBinaryOp
+}
diff --git a/lib/vdl/opconst/testutil_test.go b/lib/vdl/opconst/testutil_test.go
new file mode 100644
index 0000000..886e83e
--- /dev/null
+++ b/lib/vdl/opconst/testutil_test.go
@@ -0,0 +1,486 @@
+// 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 opconst
+
+// TODO(toddw): Merge with vdl/testutil_test.go.
+
+import (
+	"fmt"
+	"strings"
+	"testing"
+
+	"v.io/v23/vdl"
+)
+
+// CallAndRecover calls the function f and returns the result of recover().
+// This minimizes the scope of the deferred recover, to ensure f is actually the
+// function that paniced.
+func CallAndRecover(f func()) (result interface{}) {
+	defer func() {
+		result = recover()
+	}()
+	f()
+	return
+}
+
+func expectErr(t *testing.T, err error, wantstr string, format string, args ...interface{}) bool {
+	gotstr := fmt.Sprint(err)
+	msg := fmt.Sprintf(format, args...)
+	if wantstr != "" && !strings.Contains(gotstr, wantstr) {
+		t.Errorf(`%s got error %q, want substr %q`, msg, gotstr, wantstr)
+		return false
+	}
+	if wantstr == "" && err != nil {
+		t.Errorf(`%s got error %q, want nil`, msg, gotstr)
+		return false
+	}
+	return true
+}
+
+func expectPanic(t *testing.T, f func(), wantstr string, format string, args ...interface{}) {
+	got := CallAndRecover(f)
+	gotstr := fmt.Sprint(got)
+	msg := fmt.Sprintf(format, args...)
+	if wantstr != "" && !strings.Contains(gotstr, wantstr) {
+		t.Errorf(`%s got panic %q, want substr %q`, msg, gotstr, wantstr)
+	}
+	if wantstr == "" && got != nil {
+		t.Errorf(`%s got panic %q, want nil`, msg, gotstr)
+	}
+}
+
+func expectMismatchedKind(t *testing.T, f func()) {
+	expectPanic(t, f, "mismatched kind", "")
+}
+
+// Define a bunch of regular Go types used in tests.
+type (
+	// Scalars
+	nInterface  interface{}
+	nType       *vdl.Type
+	nBool       bool
+	nUint8      uint8
+	nUint16     uint16
+	nUint32     uint32
+	nUint64     uint64
+	nUint       uint
+	nUintptr    uintptr
+	nInt8       int8
+	nInt16      int16
+	nInt32      int32
+	nInt64      int64
+	nInt        int
+	nFloat32    float32
+	nFloat64    float64
+	nComplex64  complex64
+	nComplex128 complex128
+	nString     string
+	// Arrays
+	nArray3Interface  [3]nInterface
+	nArray3TypeObject [3]*vdl.Type
+	nArray3Bool       [3]bool
+	nArray3Uint8      [3]uint8
+	nArray3Uint16     [3]uint16
+	nArray3Uint32     [3]uint32
+	nArray3Uint64     [3]uint64
+	nArray3Uint       [3]uint
+	nArray3Uintptr    [3]uintptr
+	nArray3Int8       [3]int8
+	nArray3Int16      [3]int16
+	nArray3Int32      [3]int32
+	nArray3Int64      [3]int64
+	nArray3Int        [3]int
+	nArray3Float32    [3]float32
+	nArray3Float64    [3]float64
+	nArray3Complex64  [3]complex64
+	nArray3Complex128 [3]complex128
+	nArray3String     [3]string
+	// Structs
+	nStructInterface  struct{ X nInterface }
+	nStructTypeObject struct{ X *vdl.Type }
+	nStructBool       struct{ X bool }
+	nStructUint8      struct{ X uint8 }
+	nStructUint16     struct{ X uint16 }
+	nStructUint32     struct{ X uint32 }
+	nStructUint64     struct{ X uint64 }
+	nStructUint       struct{ X uint }
+	nStructUintptr    struct{ X uintptr }
+	nStructInt8       struct{ X int8 }
+	nStructInt16      struct{ X int16 }
+	nStructInt32      struct{ X int32 }
+	nStructInt64      struct{ X int64 }
+	nStructInt        struct{ X int }
+	nStructFloat32    struct{ X float32 }
+	nStructFloat64    struct{ X float64 }
+	nStructComplex64  struct{ X complex64 }
+	nStructComplex128 struct{ X complex128 }
+	nStructString     struct{ X string }
+	// Slices
+	nSliceInterface  []nInterface
+	nSliceTypeObject []*vdl.Type
+	nSliceBool       []bool
+	nSliceUint8      []uint8
+	nSliceUint16     []uint16
+	nSliceUint32     []uint32
+	nSliceUint64     []uint64
+	nSliceUint       []uint
+	nSliceUintptr    []uintptr
+	nSliceInt8       []int8
+	nSliceInt16      []int16
+	nSliceInt32      []int32
+	nSliceInt64      []int64
+	nSliceInt        []int
+	nSliceFloat32    []float32
+	nSliceFloat64    []float64
+	nSliceComplex64  []complex64
+	nSliceComplex128 []complex128
+	nSliceString     []string
+	// Sets
+	nSetInterface  map[nInterface]struct{}
+	nSetTypeObject map[*vdl.Type]struct{}
+	nSetBool       map[bool]struct{}
+	nSetUint8      map[uint8]struct{}
+	nSetUint16     map[uint16]struct{}
+	nSetUint32     map[uint32]struct{}
+	nSetUint64     map[uint64]struct{}
+	nSetUint       map[uint]struct{}
+	nSetUintptr    map[uintptr]struct{}
+	nSetInt8       map[int8]struct{}
+	nSetInt16      map[int16]struct{}
+	nSetInt32      map[int32]struct{}
+	nSetInt64      map[int64]struct{}
+	nSetInt        map[int]struct{}
+	nSetFloat32    map[float32]struct{}
+	nSetFloat64    map[float64]struct{}
+	nSetComplex64  map[complex64]struct{}
+	nSetComplex128 map[complex128]struct{}
+	nSetString     map[string]struct{}
+	// Maps
+	nMapInterface  map[nInterface]nInterface
+	nMapTypeObject map[*vdl.Type]*vdl.Type
+	nMapBool       map[bool]bool
+	nMapUint8      map[uint8]uint8
+	nMapUint16     map[uint16]uint16
+	nMapUint32     map[uint32]uint32
+	nMapUint64     map[uint64]uint64
+	nMapUint       map[uint]uint
+	nMapUintptr    map[uintptr]uintptr
+	nMapInt8       map[int8]int8
+	nMapInt16      map[int16]int16
+	nMapInt32      map[int32]int32
+	nMapInt64      map[int64]int64
+	nMapInt        map[int]int
+	nMapFloat32    map[float32]float32
+	nMapFloat64    map[float64]float64
+	nMapComplex64  map[complex64]complex64
+	nMapComplex128 map[complex128]complex128
+	nMapString     map[string]string
+	// Recursive types
+	nRecurseSelf struct{ X []nRecurseSelf }
+	nRecurseA    struct{ B []nRecurseB }
+	nRecurseB    struct{ A []nRecurseA }
+
+	// Composite types representing sets of numbers.
+	nMapUint64Empty    map[nUint64]struct{}
+	nMapInt64Empty     map[nUint64]struct{}
+	nMapFloat64Empty   map[nUint64]struct{}
+	nMapComplex64Empty map[nUint64]struct{}
+	nMapUint64Bool     map[nUint64]nBool
+	nMapInt64Bool      map[nInt64]nBool
+	nMapFloat64Bool    map[nFloat64]nBool
+	nMapComplex64Bool  map[nComplex64]nBool
+	// Composite types representing sets of strings.
+	nMapStringEmpty map[nString]struct{}
+	nMapStringBool  map[nString]nBool
+	nStructXYZBool  struct{ X, Y, Z nBool }
+	nStructWXBool   struct{ W, X nBool }
+	// Composite types representing maps of strings to numbers.
+	nMapStringUint64    map[nString]nUint64
+	nMapStringInt64     map[nString]nInt64
+	nMapStringFloat64   map[nString]nFloat64
+	nMapStringComplex64 map[nString]nComplex64
+	nStructVWXUint64    struct{ V, W, X nUint64 }
+	nStructVWXInt64     struct{ V, W, X nInt64 }
+	nStructVWXFloat64   struct{ V, W, X nFloat64 }
+	nStructVWXComplex64 struct{ V, W, X nComplex64 }
+	nStructUVUint64     struct{ U, V nUint64 }
+	nStructUVInt64      struct{ U, V nInt64 }
+	nStructUVFloat64    struct{ U, V nFloat64 }
+	nStructUVComplex64  struct{ U, V nComplex64 }
+	// Types that cannot be converted to sets.  We represent sets as
+	// map[key]struct{} on the Go side, but don't allow map[key]nEmpty.
+	nEmpty           struct{}
+	nMapStringnEmpty map[nString]nEmpty
+	nStructXYZEmpty  struct{ X, Y, Z struct{} }
+	nStructXYZnEmpty struct{ X, Y, Z nEmpty }
+)
+
+func recurseSelfType() *vdl.Type {
+	var builder vdl.TypeBuilder
+	n := builder.Named("v.io/v23/vdl.nRecurseSelf")
+	n.AssignBase(builder.Struct().AppendField("X", builder.List().AssignElem(n)))
+	builder.Build()
+	t, err := n.Built()
+	if err != nil {
+		panic(err)
+	}
+	return t
+}
+
+func recurseABTypes() [2]*vdl.Type {
+	var builder vdl.TypeBuilder
+	a := builder.Named("v.io/v23/vdl.nRecurseA")
+	b := builder.Named("v.io/v23/vdl.nRecurseB")
+	a.AssignBase(builder.Struct().AppendField("B", builder.List().AssignElem(b)))
+	b.AssignBase(builder.Struct().AppendField("A", builder.List().AssignElem(a)))
+	builder.Build()
+	aT, err := a.Built()
+	if err != nil {
+		panic(err)
+	}
+	bT, err := b.Built()
+	if err != nil {
+		panic(err)
+	}
+	return [2]*vdl.Type{aT, bT}
+}
+
+func recurseAType() *vdl.Type { return recurseABTypes()[0] }
+func recurseBType() *vdl.Type { return recurseABTypes()[1] }
+
+// Define a bunch of *Type types used in tests.
+var (
+	// Named scalar types
+	boolTypeN       = vdl.NamedType("nBool", vdl.BoolType)
+	nByteType       = vdl.NamedType("nByte", vdl.ByteType)
+	uint16TypeN     = vdl.NamedType("nUint16", vdl.Uint16Type)
+	uint32TypeN     = vdl.NamedType("nUint32", vdl.Uint32Type)
+	uint64TypeN     = vdl.NamedType("nUint64", vdl.Uint64Type)
+	int16TypeN      = vdl.NamedType("nInt16", vdl.Int16Type)
+	int32TypeN      = vdl.NamedType("nInt32", vdl.Int32Type)
+	int64TypeN      = vdl.NamedType("nInt64", vdl.Int64Type)
+	float32TypeN    = vdl.NamedType("nFloat32", vdl.Float32Type)
+	float64TypeN    = vdl.NamedType("nFloat64", vdl.Float64Type)
+	complex64TypeN  = vdl.NamedType("nComplex64", vdl.Complex64Type)
+	complex128TypeN = vdl.NamedType("nComplex128", vdl.Complex128Type)
+	stringTypeN     = vdl.NamedType("nString", vdl.StringType)
+
+	// Composite types representing strings and bytes.
+	bytesType   = vdl.ListType(vdl.ByteType)
+	bytesTypeN  = vdl.NamedType("nBytes", bytesType)
+	bytes3Type  = vdl.ArrayType(3, vdl.ByteType)
+	bytes3TypeN = vdl.NamedType("nBytes3", bytes3Type)
+	// Composite types representing sequences of numbers.
+	array3Uint64Type     = vdl.ArrayType(3, vdl.Uint64Type)
+	array3Uint64TypeN    = vdl.NamedType("nArray3Uint64", vdl.ArrayType(3, uint64TypeN))
+	array3Int64Type      = vdl.ArrayType(3, vdl.Int64Type)
+	array3Int64TypeN     = vdl.NamedType("nArray3Int64", vdl.ArrayType(3, int64TypeN))
+	array3Float64Type    = vdl.ArrayType(3, vdl.Float64Type)
+	array3Float64TypeN   = vdl.NamedType("nArray3Float64", vdl.ArrayType(3, float64TypeN))
+	array3Complex64Type  = vdl.ArrayType(3, vdl.Complex64Type)
+	array3Complex64TypeN = vdl.NamedType("nArray3Complex64", vdl.ArrayType(3, complex64TypeN))
+	listUint64Type       = vdl.ListType(vdl.Uint64Type)
+	listUint64TypeN      = vdl.NamedType("nListUint64", vdl.ListType(uint64TypeN))
+	listInt64Type        = vdl.ListType(vdl.Int64Type)
+	listInt64TypeN       = vdl.NamedType("nListInt64", vdl.ListType(int64TypeN))
+	listFloat64Type      = vdl.ListType(vdl.Float64Type)
+	listFloat64TypeN     = vdl.NamedType("nListFloat64", vdl.ListType(float64TypeN))
+	listComplex64Type    = vdl.ListType(vdl.Complex64Type)
+	listComplex64TypeN   = vdl.NamedType("nListComplex64", vdl.ListType(complex64TypeN))
+	// Composite types representing sets of numbers.
+	setUint64Type         = vdl.SetType(vdl.Uint64Type)
+	setUint64TypeN        = vdl.NamedType("nSetUint64", vdl.SetType(uint64TypeN))
+	setInt64Type          = vdl.SetType(vdl.Int64Type)
+	setInt64TypeN         = vdl.NamedType("nSetInt64", vdl.SetType(int64TypeN))
+	setFloat64Type        = vdl.SetType(vdl.Float64Type)
+	setFloat64TypeN       = vdl.NamedType("nSetFloat64", vdl.SetType(float64TypeN))
+	setComplex64Type      = vdl.SetType(vdl.Complex64Type)
+	setComplex64TypeN     = vdl.NamedType("nSetComplex64", vdl.SetType(complex64TypeN))
+	mapUint64BoolType     = vdl.MapType(vdl.Uint64Type, vdl.BoolType)
+	mapUint64BoolTypeN    = vdl.NamedType("nMapUint64Bool", vdl.MapType(uint64TypeN, boolTypeN))
+	mapInt64BoolType      = vdl.MapType(vdl.Int64Type, vdl.BoolType)
+	mapInt64BoolTypeN     = vdl.NamedType("nMapInt64Bool", vdl.MapType(int64TypeN, boolTypeN))
+	mapFloat64BoolType    = vdl.MapType(vdl.Float64Type, vdl.BoolType)
+	mapFloat64BoolTypeN   = vdl.NamedType("nMapFloat64Bool", vdl.MapType(float64TypeN, boolTypeN))
+	mapComplex64BoolType  = vdl.MapType(vdl.Complex64Type, vdl.BoolType)
+	mapComplex64BoolTypeN = vdl.NamedType("nMapComplex64Bool", vdl.MapType(complex64TypeN, boolTypeN))
+	// Composite types representing sets of strings.
+	setStringType      = vdl.SetType(vdl.StringType)
+	setStringTypeN     = vdl.NamedType("nSetString", vdl.SetType(stringTypeN))
+	mapStringBoolType  = vdl.MapType(vdl.StringType, vdl.BoolType)
+	mapStringBoolTypeN = vdl.NamedType("nMapStringBool", vdl.MapType(stringTypeN, boolTypeN))
+	structXYZBoolType  = vdl.StructType(vdl.Field{Name: "X", Type: vdl.BoolType}, vdl.Field{Name: "Y", Type: vdl.BoolType}, vdl.Field{Name: "Z", Type: vdl.BoolType})
+	structXYZBoolTypeN = vdl.NamedType("nStructXYZBool", vdl.StructType(vdl.Field{Name: "X", Type: boolTypeN}, vdl.Field{Name: "Y", Type: boolTypeN}, vdl.Field{Name: "Z", Type: boolTypeN}))
+	structWXBoolType   = vdl.StructType(vdl.Field{Name: "W", Type: vdl.BoolType}, vdl.Field{Name: "X", Type: vdl.BoolType})
+	structWXBoolTypeN  = vdl.NamedType("nStructWXBool", vdl.StructType(vdl.Field{Name: "W", Type: boolTypeN}, vdl.Field{Name: "X", Type: boolTypeN}))
+	// Composite types representing maps of strings to numbers.
+	mapStringUint64Type     = vdl.MapType(vdl.StringType, vdl.Uint64Type)
+	mapStringUint64TypeN    = vdl.NamedType("nMapStringUint64", vdl.MapType(stringTypeN, uint64TypeN))
+	mapStringInt64Type      = vdl.MapType(vdl.StringType, vdl.Int64Type)
+	mapStringInt64TypeN     = vdl.NamedType("nMapStringInt64", vdl.MapType(stringTypeN, int64TypeN))
+	mapStringFloat64Type    = vdl.MapType(vdl.StringType, vdl.Float64Type)
+	mapStringFloat64TypeN   = vdl.NamedType("nMapStringFloat64", vdl.MapType(stringTypeN, float64TypeN))
+	mapStringComplex64Type  = vdl.MapType(vdl.StringType, vdl.Complex64Type)
+	mapStringComplex64TypeN = vdl.NamedType("nMapStringComplex64", vdl.MapType(stringTypeN, complex64TypeN))
+	structVWXUint64Type     = vdl.StructType(vdl.Field{Name: "V", Type: vdl.Uint64Type}, vdl.Field{Name: "W", Type: vdl.Uint64Type}, vdl.Field{Name: "X", Type: vdl.Uint64Type})
+	structVWXUint64TypeN    = vdl.NamedType("nStructVWXUint64", vdl.StructType(vdl.Field{Name: "V", Type: uint64TypeN}, vdl.Field{Name: "W", Type: uint64TypeN}, vdl.Field{Name: "X", Type: uint64TypeN}))
+	structVWXInt64Type      = vdl.StructType(vdl.Field{Name: "V", Type: vdl.Int64Type}, vdl.Field{Name: "W", Type: vdl.Int64Type}, vdl.Field{Name: "X", Type: vdl.Int64Type})
+	structVWXInt64TypeN     = vdl.NamedType("nStructVWXInt64", vdl.StructType(vdl.Field{Name: "V", Type: int64TypeN}, vdl.Field{Name: "W", Type: int64TypeN}, vdl.Field{Name: "X", Type: int64TypeN}))
+	structVWXFloat64Type    = vdl.StructType(vdl.Field{Name: "V", Type: vdl.Float64Type}, vdl.Field{Name: "W", Type: vdl.Float64Type}, vdl.Field{Name: "X", Type: vdl.Float64Type})
+	structVWXFloat64TypeN   = vdl.NamedType("nStructVWXFloat64", vdl.StructType(vdl.Field{Name: "V", Type: float64TypeN}, vdl.Field{Name: "W", Type: float64TypeN}, vdl.Field{Name: "X", Type: float64TypeN}))
+	structVWXComplex64Type  = vdl.StructType(vdl.Field{Name: "V", Type: vdl.Complex64Type}, vdl.Field{Name: "W", Type: vdl.Complex64Type}, vdl.Field{Name: "X", Type: vdl.Complex64Type})
+	structVWXComplex64TypeN = vdl.NamedType("nStructVWXComplex64", vdl.StructType(vdl.Field{Name: "V", Type: complex64TypeN}, vdl.Field{Name: "W", Type: complex64TypeN}, vdl.Field{Name: "X", Type: complex64TypeN}))
+	structUVUint64Type      = vdl.StructType(vdl.Field{Name: "U", Type: vdl.Uint64Type}, vdl.Field{Name: "V", Type: vdl.Uint64Type})
+	structUVUint64TypeN     = vdl.NamedType("nStructUVUint64", vdl.StructType(vdl.Field{Name: "U", Type: uint64TypeN}, vdl.Field{Name: "V", Type: uint64TypeN}))
+	structUVInt64Type       = vdl.StructType(vdl.Field{Name: "U", Type: vdl.Int64Type}, vdl.Field{Name: "V", Type: vdl.Int64Type})
+	structUVInt64TypeN      = vdl.NamedType("nStructUVInt64", vdl.StructType(vdl.Field{Name: "U", Type: int64TypeN}, vdl.Field{Name: "V", Type: int64TypeN}))
+	structUVFloat64Type     = vdl.StructType(vdl.Field{Name: "U", Type: vdl.Float64Type}, vdl.Field{Name: "V", Type: vdl.Float64Type})
+	structUVFloat64TypeN    = vdl.NamedType("nStructUVFloat64", vdl.StructType(vdl.Field{Name: "U", Type: float64TypeN}, vdl.Field{Name: "V", Type: float64TypeN}))
+	structUVComplex64Type   = vdl.StructType(vdl.Field{Name: "U", Type: vdl.Complex64Type}, vdl.Field{Name: "V", Type: vdl.Complex64Type})
+	structUVComplex64TypeN  = vdl.NamedType("nStructUVComplex64", vdl.StructType(vdl.Field{Name: "U", Type: complex64TypeN}, vdl.Field{Name: "V", Type: complex64TypeN}))
+
+	structAIntType  = vdl.StructType(vdl.Field{Name: "A", Type: vdl.Int64Type})
+	structAIntTypeN = vdl.NamedType("nStructA", structAIntType)
+
+	// Types that cannot be converted to sets.  Although we represent sets as
+	// map[key]struct{} on the Go side, we don't allow these as general
+	// conversions for val.Value.
+	emptyType           = vdl.StructType()
+	emptyTypeN          = vdl.NamedType("nEmpty", vdl.StructType())
+	mapStringEmptyType  = vdl.MapType(vdl.StringType, emptyType)
+	mapStringEmptyTypeN = vdl.NamedType("nMapStringEmpty", vdl.MapType(stringTypeN, emptyTypeN))
+	structXYZEmptyType  = vdl.StructType(vdl.Field{Name: "X", Type: emptyType}, vdl.Field{Name: "Y", Type: emptyType}, vdl.Field{Name: "Z", Type: emptyType})
+	structXYZEmptyTypeN = vdl.NamedType("nStructXYZEmpty", vdl.StructType(vdl.Field{Name: "X", Type: emptyTypeN}, vdl.Field{Name: "Y", Type: emptyTypeN}, vdl.Field{Name: "Z", Type: emptyTypeN}))
+)
+
+func anyValue(x *vdl.Value) *vdl.Value                  { return vdl.ZeroValue(vdl.AnyType).Assign(x) }
+func boolValue(t *vdl.Type, x bool) *vdl.Value          { return vdl.ZeroValue(t).AssignBool(x) }
+func byteValue(t *vdl.Type, x byte) *vdl.Value          { return vdl.ZeroValue(t).AssignByte(x) }
+func uintValue(t *vdl.Type, x uint64) *vdl.Value        { return vdl.ZeroValue(t).AssignUint(x) }
+func intValue(t *vdl.Type, x int64) *vdl.Value          { return vdl.ZeroValue(t).AssignInt(x) }
+func floatValue(t *vdl.Type, x float64) *vdl.Value      { return vdl.ZeroValue(t).AssignFloat(x) }
+func complexValue(t *vdl.Type, x complex128) *vdl.Value { return vdl.ZeroValue(t).AssignComplex(x) }
+func stringValue(t *vdl.Type, x string) *vdl.Value      { return vdl.ZeroValue(t).AssignString(x) }
+func bytesValue(t *vdl.Type, x string) *vdl.Value       { return vdl.ZeroValue(t).AssignBytes([]byte(x)) }
+func bytes3Value(t *vdl.Type, x string) *vdl.Value      { return vdl.ZeroValue(t).CopyBytes([]byte(x)) }
+
+func setStringValue(t *vdl.Type, x ...string) *vdl.Value {
+	res := vdl.ZeroValue(t)
+	for _, vx := range x {
+		key := vdl.ZeroValue(t.Key()).AssignString(vx)
+		res.AssignSetKey(key)
+	}
+	return res
+}
+
+type sb struct {
+	s string
+	b bool
+}
+
+func mapStringBoolValue(t *vdl.Type, x ...sb) *vdl.Value {
+	res := vdl.ZeroValue(t)
+	for _, sb := range x {
+		key := vdl.ZeroValue(t.Key()).AssignString(sb.s)
+		val := vdl.ZeroValue(t.Elem()).AssignBool(sb.b)
+		res.AssignMapIndex(key, val)
+	}
+	return res
+}
+
+func mapStringEmptyValue(t *vdl.Type, x ...string) *vdl.Value {
+	res := vdl.ZeroValue(t)
+	for _, vx := range x {
+		key := vdl.ZeroValue(t.Key()).AssignString(vx)
+		val := vdl.ZeroValue(t.Elem())
+		res.AssignMapIndex(key, val)
+	}
+	return res
+}
+
+func structBoolValue(t *vdl.Type, x ...sb) *vdl.Value {
+	res := vdl.ZeroValue(t)
+	for _, sb := range x {
+		_, index := t.FieldByName(sb.s)
+		res.StructField(index).AssignBool(sb.b)
+	}
+	return res
+}
+
+func assignNum(v *vdl.Value, num float64) *vdl.Value {
+	switch v.Kind() {
+	case vdl.Byte:
+		v.AssignByte(byte(num))
+	case vdl.Uint16, vdl.Uint32, vdl.Uint64:
+		v.AssignUint(uint64(num))
+	case vdl.Int16, vdl.Int32, vdl.Int64:
+		v.AssignInt(int64(num))
+	case vdl.Float32, vdl.Float64:
+		v.AssignFloat(num)
+	case vdl.Complex64, vdl.Complex128:
+		v.AssignComplex(complex(num, 0))
+	default:
+		panic(fmt.Errorf("val: assignNum unhandled %v", v.Type()))
+	}
+	return v
+}
+
+func seqNumValue(t *vdl.Type, x ...float64) *vdl.Value {
+	res := vdl.ZeroValue(t)
+	if t.Kind() == vdl.List {
+		res.AssignLen(len(x))
+	}
+	for index, n := range x {
+		assignNum(res.Index(index), n)
+	}
+	return res
+}
+
+func setNumValue(t *vdl.Type, x ...float64) *vdl.Value {
+	res := vdl.ZeroValue(t)
+	for _, n := range x {
+		res.AssignSetKey(assignNum(vdl.ZeroValue(t.Key()), n))
+	}
+	return res
+}
+
+type nb struct {
+	n float64
+	b bool
+}
+
+func mapNumBoolValue(t *vdl.Type, x ...nb) *vdl.Value {
+	res := vdl.ZeroValue(t)
+	for _, nb := range x {
+		key := assignNum(vdl.ZeroValue(t.Key()), nb.n)
+		val := vdl.ZeroValue(t.Elem()).AssignBool(nb.b)
+		res.AssignMapIndex(key, val)
+	}
+	return res
+}
+
+type sn struct {
+	s string
+	n float64
+}
+
+func mapStringNumValue(t *vdl.Type, x ...sn) *vdl.Value {
+	res := vdl.ZeroValue(t)
+	for _, sn := range x {
+		key := vdl.ZeroValue(t.Key()).AssignString(sn.s)
+		val := assignNum(vdl.ZeroValue(t.Elem()), sn.n)
+		res.AssignMapIndex(key, val)
+	}
+	return res
+}
+
+func structNumValue(t *vdl.Type, x ...sn) *vdl.Value {
+	res := vdl.ZeroValue(t)
+	for _, sn := range x {
+		_, index := t.FieldByName(sn.s)
+		assignNum(res.StructField(index), sn.n)
+	}
+	return res
+}
diff --git a/lib/vdl/parse/const.go b/lib/vdl/parse/const.go
new file mode 100644
index 0000000..6c8f887
--- /dev/null
+++ b/lib/vdl/parse/const.go
@@ -0,0 +1,160 @@
+// 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 parse
+
+import (
+	"fmt"
+	"math/big"
+	"strconv"
+)
+
+// ConstDef represents a user-defined named const.
+type ConstDef struct {
+	NamePos
+	Expr ConstExpr
+}
+
+// ConstExpr is the interface for all nodes in an expression.
+type ConstExpr interface {
+	String() string
+	Pos() Pos
+}
+
+// ConstLit represents scalar literals in const expressions.  The supported
+// types for Lit are:
+//   string     - Represents all string constants.
+//   *big.Int   - Represents all integer constants.
+//   *big.Rat   - Represents all rational constants.
+//   *BigImag   - Represents all imaginary constants.
+type ConstLit struct {
+	Lit interface{}
+	P   Pos
+}
+
+// BigImag represents a literal imaginary number.
+type BigImag big.Rat
+
+// ConstCompositeLit represents composite literals in const expressions.
+type ConstCompositeLit struct {
+	Type   Type
+	KVList []KVLit
+	P      Pos
+}
+
+// KVLit represents a key/value literal in composite literals.
+type KVLit struct {
+	Key   ConstExpr
+	Value ConstExpr
+}
+
+// ConstNamed represents named references to other consts.
+type ConstNamed struct {
+	Name string
+	P    Pos
+}
+
+// ConstIndexed represents an index operation on a composite type.
+type ConstIndexed struct {
+	Expr      *ConstNamed
+	IndexExpr ConstExpr
+	P         Pos
+}
+
+// ConstTypeConv represents explicit type conversions.
+type ConstTypeConv struct {
+	Type Type
+	Expr ConstExpr
+	P    Pos
+}
+
+// ConstTypeObject represents typeobject; a type used as a value.
+type ConstTypeObject struct {
+	Type Type
+	P    Pos
+}
+
+// ConstUnaryOp represents all unary operations.
+type ConstUnaryOp struct {
+	Op   string
+	Expr ConstExpr
+	P    Pos
+}
+
+// ConstBinaryOp represents all binary operations.
+type ConstBinaryOp struct {
+	Op    string
+	Lexpr ConstExpr
+	Rexpr ConstExpr
+	P     Pos
+}
+
+// cvString returns a human-readable string representing the const value.
+func cvString(val interface{}) string {
+	switch tv := val.(type) {
+	case string:
+		return strconv.Quote(tv)
+	case *big.Int:
+		return tv.String()
+	case *big.Rat:
+		if tv.IsInt() {
+			return tv.Num().String() + ".0"
+		}
+		fv, _ := tv.Float64()
+		return strconv.FormatFloat(fv, 'g', -1, 64)
+	case *BigImag:
+		return cvString((*big.Rat)(tv)) + "i"
+	default:
+		panic(fmt.Errorf("vdl: unhandled const type %T value %v", val, val))
+	}
+}
+
+func (c *ConstLit) String() string {
+	return cvString(c.Lit)
+}
+func (c *ConstCompositeLit) String() string {
+	var s string
+	if c.Type != nil {
+		s += c.Type.String()
+	}
+	s += "{"
+	for index, kv := range c.KVList {
+		if index > 0 {
+			s += ", "
+		}
+		if kv.Key != nil {
+			s += kv.Key.String() + ": "
+		}
+		s += kv.Value.String()
+	}
+	return s + "}"
+}
+func (c *ConstNamed) String() string {
+	return c.Name
+}
+func (c *ConstIndexed) String() string {
+	return c.Expr.String() + "[" + c.IndexExpr.String() + "]"
+}
+func (c *ConstTypeConv) String() string {
+	return c.Type.String() + "(" + c.Expr.String() + ")"
+}
+func (c *ConstTypeObject) String() string {
+	return c.Type.String()
+}
+func (c *ConstUnaryOp) String() string {
+	return c.Op + c.Expr.String()
+}
+func (c *ConstBinaryOp) String() string {
+	return "(" + c.Lexpr.String() + c.Op + c.Rexpr.String() + ")"
+}
+func (c *ConstDef) String() string { return fmt.Sprintf("%+v", *c) }
+
+func (c *ConstLit) Pos() Pos          { return c.P }
+func (c *ConstCompositeLit) Pos() Pos { return c.P }
+func (c *ConstNamed) Pos() Pos        { return c.P }
+func (c *ConstIndexed) Pos() Pos      { return c.P }
+func (c *ConstTypeConv) Pos() Pos     { return c.P }
+func (c *ConstTypeObject) Pos() Pos   { return c.P }
+func (c *ConstUnaryOp) Pos() Pos      { return c.P }
+func (c *ConstBinaryOp) Pos() Pos     { return c.P }
diff --git a/lib/vdl/parse/grammar.y b/lib/vdl/parse/grammar.y
new file mode 100644
index 0000000..745185b
--- /dev/null
+++ b/lib/vdl/parse/grammar.y
@@ -0,0 +1,646 @@
+// Yacc grammar file for the vanadium VDL langage.
+// http://goto/veyron:vdl
+//
+// Similar to Go, the formal grammar uses semicolons ';' as terminators, but
+// idiomatic usage may omit most semicolons using the following rules:
+//   1) During the tokenization phase, semicolons are always auto-inserted at
+//      the end of each line after certain tokens.  This is implemented in
+//      the lexer via the autoSemi function.
+//   2) Semicolons may be omitted before a closing ')' or '}'.  This is
+//      implemented via the osemi rule below.
+//
+// To generate the grammar.go source file containing the parser, run
+// grammar_gen.sh in this same directory, or run go generate on this package.
+
+////////////////////////////////////////////////////////////////////////
+// Declarations section.
+%{
+// This grammar.y.go file was auto-generated by yacc from grammar.y.
+
+package parse
+
+import (
+  "math/big"
+  "strings"
+)
+
+type intPos struct {
+  int *big.Int
+  pos Pos
+}
+
+type ratPos struct {
+  rat *big.Rat
+  pos Pos
+}
+
+type imagPos struct {
+  imag *BigImag
+  pos  Pos
+}
+
+// typeListToStrList converts a slice of Type to a slice of StringPos.  Each
+// type must be a TypeNamed with an empty PackageName, otherwise errors are
+// reported, and ok=false is returned.
+func typeListToStrList(yylex yyLexer, typeList []Type) (strList []StringPos, ok bool) {
+  ok = true
+  for _, t := range typeList {
+    var tn *TypeNamed
+    if tn, ok = t.(*TypeNamed); !ok {
+      lexPosErrorf(yylex, t.Pos(), "%s invalid (expected one or more variable names)", t.String())
+      return
+    }
+    if strings.ContainsRune(tn.Name, '.') {
+      ok = false
+      lexPosErrorf(yylex, t.Pos(), "%s invalid (expected one or more variable names).", tn.Name)
+      return
+    }
+    strList = append(strList, StringPos{tn.Name, tn.P})
+  }
+  return
+}
+%}
+
+// This union is turned into the struct type yySymType.  Most symbols include
+// positional information; this is necessary since Go yacc doesn't support
+// passing positional information, so we need to track it ourselves.
+%union {
+  pos        Pos
+  strpos     StringPos
+  intpos     intPos
+  ratpos     ratPos
+  imagpos    imagPos
+  namepos    NamePos
+  nameposes  []NamePos
+  typeexpr   Type
+  typeexprs  []Type
+  fields     []*Field
+  iface      *Interface
+  constexpr  ConstExpr
+  constexprs []ConstExpr
+  complit    *ConstCompositeLit
+  kvlit      KVLit
+  kvlits     []KVLit
+  errordef   ErrorDef
+}
+
+// Terminal tokens.  We leave single-char tokens as-is using their ascii code as
+// their id, to make the grammar more readable; multi-char tokens get their own
+// id.  The start* tokens are dummy tokens to kick off the parse.
+%token            startFileImports startFile startConfigImports startConfig
+%token            startExprs
+%token <pos>      ';' ':' ',' '.' '(' ')' '[' ']' '{' '}' '<' '>' '='
+%token <pos>      '!' '+' '-' '*' '/' '%' '|' '&' '^' '?'
+%token <pos>      tOROR tANDAND tLE tGE tNE tEQEQ tLSH tRSH
+%token <pos>      tCONST tENUM tERROR tIMPORT tINTERFACE tMAP tPACKAGE
+%token <pos>      tSET tSTREAM tSTRUCT tTYPE tTYPEOBJECT tUNION
+%token <strpos>   tIDENT tSTRLIT
+%token <intpos>   tINTLIT
+%token <ratpos>   tRATLIT
+%token <imagpos>  tIMAGLIT
+
+// Labeled rules holding typed values.
+%type <strpos>     nameref dotnameref
+%type <namepos>    label_spec
+%type <nameposes>  label_spec_list
+%type <typeexpr>   type type_no_typeobject otype
+%type <typeexprs>  type_comma_list streamargs
+%type <fields>     field_spec_list field_spec named_arg_list inargs outargs
+%type <iface>      iface_item_list iface_item
+%type <constexpr>  expr unary_expr operand
+%type <constexprs> tags expr_comma_list
+%type <complit>    comp_lit
+%type <kvlit>      kv_lit
+%type <kvlits>     kv_lit_list
+%type <errordef>   error_details error_detail_list error_detail
+
+// There are 5 precedence levels for operators, all left-associative, just like
+// Go.  Lines are listed in order of increasing precedence.
+%left tOROR
+%left tANDAND
+%left '<' '>' tLE tGE tNE tEQEQ
+%left '+' '-' '|' '^'
+%left '*' '/' '%' '&' tLSH tRSH
+
+%left notPackage notConfig
+
+%start start
+
+%%
+////////////////////////////////////////////////////////////////////////
+// Rules section.
+
+// Note that vdl files and config files use an identical grammar, other than the
+// initial package or config clause respectively.  Error checking for config
+// files that include error, type or interface definitions occurs afterwards, to
+// improve error reporting.
+start:
+  startFileImports   package imports gen_imports_eof
+| startFile          package imports defs
+| startConfigImports config imports gen_imports_eof
+| startConfig        config imports defs
+| startExprs         expr_comma_list ';'
+  { lexStoreExprs(yylex, $2) }
+
+// Dummy rule to terminate the parse after the imports, regardless of whether
+// there are any defs.  Defs always start with either the tTYPE, tCONST or
+// tERROR tokens, and the rule handles all cases - either there's no trailing
+// text (the empty case, which would have resulted in EOF anyways), or there's
+// one or more defs, where we need to force an EOF.
+gen_imports_eof:
+  // Empty.
+  { lexGenEOF(yylex) }
+| tTYPE
+  { lexGenEOF(yylex) }
+| tCONST
+  { lexGenEOF(yylex) }
+| tERROR
+  { lexGenEOF(yylex) }
+
+// PACKAGE
+package:
+  %prec notPackage
+  { lexPosErrorf(yylex, Pos{}, "vdl file must start with package clause") }
+| tPACKAGE tIDENT ';'
+  { lexVDLFile(yylex).PackageDef = NamePos{Name:$2.String, Pos:$2.Pos} }
+
+// CONFIG
+config:
+  %prec notConfig
+  { lexPosErrorf(yylex, Pos{}, "config file must start with config clause") }
+| tIDENT '=' expr ';'
+  {
+    // We allow "config" as an identifier; it is not a keyword.  So we check
+    // manually to make sure the syntax is correct.
+    if $1.String != "config" {
+      lexPosErrorf(yylex, $1.Pos, "config file must start with config clause")
+      return 1 // Any non-zero code indicates an error
+    }
+    file := lexVDLFile(yylex)
+    file.PackageDef = NamePos{Name:"config", Pos:$1.Pos}
+    file.ConstDefs = []*ConstDef{{Expr:$3}}
+  }
+
+// IMPORTS
+imports:
+  // Empty.
+| imports import ';'
+
+import:
+  tIMPORT '(' ')'
+| tIMPORT '(' import_spec_list osemi ')'
+| tIMPORT import_spec
+
+import_spec_list:
+  import_spec
+| import_spec_list ';' import_spec
+
+import_spec:
+  tSTRLIT
+  {
+    imps := &lexVDLFile(yylex).Imports
+    *imps = append(*imps, &Import{Path:$1.String, NamePos:NamePos{Pos:$1.Pos}})
+  }
+| tIDENT tSTRLIT
+  {
+    imps := &lexVDLFile(yylex).Imports
+    *imps = append(*imps, &Import{Path:$2.String, NamePos:NamePos{Name:$1.String, Pos:$1.Pos}})
+  }
+
+// DEFINITIONS
+defs:
+  // Empty.
+| defs type_def ';'
+| defs const_def ';'
+| defs error_def ';'
+
+type_def:
+  tTYPE '(' ')'
+| tTYPE '(' type_spec_list osemi ')'
+| tTYPE type_spec
+| tTYPE interface_spec
+
+const_def:
+  tCONST '(' ')'
+| tCONST '(' const_spec_list osemi ')'
+| tCONST const_spec
+
+error_def:
+  tERROR '(' ')'
+| tERROR '(' error_spec_list osemi ')'
+| tERROR error_spec
+
+// TYPE DEFINITIONS
+type_spec_list:
+  type_spec
+| type_spec_list ';' type_spec
+
+type_spec:
+  tIDENT type
+  {
+    tds := &lexVDLFile(yylex).TypeDefs
+    *tds = append(*tds, &TypeDef{Type:$2, NamePos:NamePos{Name:$1.String, Pos:$1.Pos}})
+  }
+
+// The type_no_typeobject rule is necessary to avoid a shift/reduce conflict
+// between type conversions and typeobject const expressions.  E.g.
+//   type(expr)       // type conversion
+//   typeobject(type) // typeobject const expression
+//
+// We've chosen similar syntax to make it easier for the user to remember how to
+// use the feature, but since "typeobject" is itself a type, there is a problem.
+// We resolve the conflict by restricting the type conversion to the rule:
+//   type_no_typeobject '(' expr ')'
+//
+// Note that if we wanted to add general-purpose functions with the func(expr)
+// syntax, we'll need to pull nameref out of type_no_typeobject, and parse both
+// func(expr) and nameref(expr) into a generic structure.  We can't use that
+// same mechanism for typeobject, since the thing inside the parens is a value
+// expression for type conversions, but a type expression for typeobject.
+type_no_typeobject:
+  nameref
+  { $$ = &TypeNamed{Name:$1.String, P:$1.Pos} }
+| tERROR // Special-case to allow the "error" keyword as a named type.
+  { $$ = &TypeNamed{Name:"error", P:$1} }
+| '[' tINTLIT ']' type
+  { $$ = &TypeArray{Len:int($2.int.Int64()), Elem:$4, P:$1} }
+| '[' ']' type
+  { $$ = &TypeList{Elem:$3, P:$1} }
+| tENUM '{' label_spec_list osemi '}'
+  { $$ = &TypeEnum{Labels:$3, P:$1} }
+| tSET '[' type ']'
+  { $$ = &TypeSet{Key:$3, P:$1} }
+| tMAP '[' type ']' type
+  { $$ = &TypeMap{Key:$3, Elem:$5, P:$1} }
+| tSTRUCT '{' field_spec_list osemi '}'
+  { $$ = &TypeStruct{Fields:$3, P:$1} }
+| tSTRUCT '{' '}'
+  { $$ = &TypeStruct{P:$1} }
+| tUNION '{' field_spec_list osemi '}'
+  { $$ = &TypeUnion{Fields:$3, P:$1} }
+| tUNION '{' '}'
+  { $$ = &TypeUnion{P:$1} }
+| '?' type
+  { $$ = &TypeOptional{Base:$2, P:$1} }
+
+// The type rule expands to all the actual types, including typeobject.
+type:
+  type_no_typeobject
+  { $$ = $1}
+| tTYPEOBJECT
+  { $$ = &TypeNamed{Name:"typeobject", P:$1} }
+
+label_spec_list:
+  label_spec
+  { $$ = []NamePos{$1} }
+| label_spec_list ';' label_spec
+  { $$ = append($1, $3) }
+
+label_spec:
+  tIDENT
+  { $$ = NamePos{Name:$1.String, Pos:$1.Pos} }
+
+field_spec_list:
+  field_spec
+  { $$ = $1 }
+| field_spec_list ';' field_spec
+  { $$ = append($1, $3...) }
+
+// The field_spec rule is intended to capture the following patterns:
+//    var type
+//    var0, var1, var2 type
+// where var* refers to a variable name, and type refers to a type.  Each var
+// is expressed as an identifier.  An oddity here is that we use a type_list to
+// capture the list of variables rather than using a list of IDENTS.  This means
+// the grammar accepts invalid constructions, and we must validate afterwards.
+//
+// We do this to avoid a LALR reduce/reduce conflict with function arguments.
+// The problem is exhibited by the in-args of these two functions, where func1
+// has three args respectively named A, B, C all of type t1, and func2 has three
+// args with name and type t2, t3 and t4 respectively.  The func1 style is
+// captured by field_spec in named_arg_list, while the func2 style is captured
+// by type_list in args.
+//   func1(A, B, C t1)
+//   func2(t2, t3, t4)
+//
+// If we used an ident_list to capture "A, B, C" in func1, but used a type_list
+// to capture "t2, t3, t4" in func2, we'd have a reduce/reduce conflict since
+// yacc cannot determine whether to reduce as an ident_list or as a type_list;
+// we don't know until we've reached token t1 in func1, or token ')' in func2.
+//
+// The fix can be considered both beautiful and a huge hack.  To avoid the
+// conflict we force both forms to use type_list to capture both "A, B, C" and
+// "t2, t3, t4".  This avoids the conflict since we're now always reducing via
+// type_list, but allows invalid constructions like "[]int, []int []int".  So we
+// validate in the action and throw errors.
+//
+// An alternate fix would have been to remove the IDENT case from the type rule,
+// use ident_list to capture both cases, and manually "expand" the grammar to
+// distinguish the cases appropriately.  That would ensure we don't allow
+// constructions like "int, int int" in the grammar itself, but would lead to a
+// much more complicated grammar.  As a bonus, with the type_list solution we
+// can give better error messages.
+field_spec:
+  type_comma_list type
+  {
+    if names, ok := typeListToStrList(yylex, $1); ok {
+      for _, n := range names {
+        $$ = append($$, &Field{Type:$2, NamePos:NamePos{Name:n.String, Pos:n.Pos}})
+      }
+    } else {
+      lexPosErrorf(yylex, $2.Pos(), "perhaps you forgot a comma before %q?.", $2.String())
+    }
+  }
+
+type_comma_list:
+  type
+  { $$ = []Type{$1} }
+| type_comma_list ',' type
+  { $$ = append($1, $3) }
+
+// INTERFACE DEFINITIONS
+interface_spec:
+  tIDENT tINTERFACE '{' '}'
+  {
+    ifs := &lexVDLFile(yylex).Interfaces
+    *ifs = append(*ifs, &Interface{NamePos:NamePos{Name:$1.String, Pos:$1.Pos}})
+  }
+| tIDENT tINTERFACE '{' iface_item_list osemi '}'
+  {
+    $4.Name, $4.Pos = $1.String, $1.Pos
+    ifs := &lexVDLFile(yylex).Interfaces
+    *ifs = append(*ifs, $4)
+  }
+
+iface_item_list:
+  iface_item
+  { $$ = $1 }
+| iface_item_list ';' iface_item
+  {
+    $1.Embeds = append($1.Embeds, $3.Embeds...)
+    $1.Methods = append($1.Methods, $3.Methods...)
+    $$ = $1
+  }
+
+iface_item:
+  tIDENT inargs streamargs outargs tags
+  { $$ = &Interface{Methods: []*Method{{InArgs:$2, InStream:$3[0], OutStream:$3[1], OutArgs:$4, Tags:$5, NamePos:NamePos{Name:$1.String, Pos:$1.Pos}}}} }
+| nameref
+  { $$ = &Interface{Embeds: []*NamePos{{Name:$1.String, Pos:$1.Pos}}} }
+
+inargs:
+  '(' ')'
+  { $$ = nil }
+| '(' named_arg_list ocomma ')'
+  { $$ = $2 }
+| '(' type_comma_list ocomma ')'
+  // Just like Go, we allow a list of types without variable names.  See the
+  // field_spec rule for a workaround to avoid a reduce/reduce conflict.
+  {
+    for _, t := range $2 {
+      $$ = append($$, &Field{Type:t, NamePos:NamePos{Pos:t.Pos()}})
+    }
+  }
+
+// The named_arg_list rule is just like the field_spec_list, but uses comma ','
+// as a delimiter rather than semicolon ';'.
+named_arg_list:
+  field_spec
+  { $$ = $1 }
+| named_arg_list ',' field_spec
+  { $$ = append($1, $3...) }
+
+// The outargs use special syntax to denote the error associated with each
+// method.  For parsing we accept these forms:
+//  error
+//  (string | error)
+//  (a, b string, c bool | error)
+//
+// TODO(toddw): Improve parser syntax errors.
+outargs:
+  tERROR
+  { $$ = nil }
+| '(' named_arg_list ocomma '|' tERROR ')'
+  { $$ = $2 }
+| '(' type_comma_list ocomma '|' tERROR ')'
+  // Just like Go, we allow a list of types without variable names.  See the
+  // field_spec rule for a workaround to avoid a reduce/reduce conflict.
+  {
+    for _, t := range $2 {
+      $$ = append($$, &Field{Type:t, NamePos:NamePos{Pos:t.Pos()}})
+    }
+  }
+
+streamargs:
+  // Empty.
+  { $$ = []Type{nil, nil} }
+| tSTREAM '<' '>'
+  { $$ = []Type{nil, nil} }
+| tSTREAM '<' type '>'
+  { $$ = []Type{$3, nil} }
+| tSTREAM '<' type ',' type '>'
+  { $$ = []Type{$3, $5} }
+
+tags:
+  // Empty.
+  { $$ = nil }
+| '{' '}'
+  { $$ = nil }
+| '{' expr_comma_list ocomma '}'
+  { $$ = $2 }
+
+expr_comma_list:
+  expr
+  { $$ = []ConstExpr{$1} }
+| expr_comma_list ',' expr
+  { $$ = append($1, $3) }
+
+// CONST DEFINITIONS
+const_spec_list:
+  const_spec
+| const_spec_list ';' const_spec
+
+const_spec:
+  tIDENT '=' expr
+  {
+    cds := &lexVDLFile(yylex).ConstDefs
+    *cds = append(*cds, &ConstDef{Expr:$3, NamePos:NamePos{Name:$1.String, Pos:$1.Pos}})
+  }
+
+expr:
+  unary_expr
+  { $$ = $1 }
+| expr tOROR expr
+  { $$ = &ConstBinaryOp{"||", $1, $3, $2} }
+| expr tANDAND expr
+  { $$ = &ConstBinaryOp{"&&", $1, $3, $2} }
+| expr '<' expr
+  { $$ = &ConstBinaryOp{"<", $1, $3, $2} }
+| expr '>' expr
+  { $$ = &ConstBinaryOp{">", $1, $3, $2} }
+| expr tLE expr
+  { $$ = &ConstBinaryOp{"<=", $1, $3, $2} }
+| expr tGE expr
+  { $$ = &ConstBinaryOp{">=", $1, $3, $2} }
+| expr tNE expr
+  { $$ = &ConstBinaryOp{"!=", $1, $3, $2} }
+| expr tEQEQ expr
+  { $$ = &ConstBinaryOp{"==", $1, $3, $2} }
+| expr '+' expr
+  { $$ = &ConstBinaryOp{"+", $1, $3, $2} }
+| expr '-' expr
+  { $$ = &ConstBinaryOp{"-", $1, $3, $2} }
+| expr '*' expr
+  { $$ = &ConstBinaryOp{"*", $1, $3, $2} }
+| expr '/' expr
+  { $$ = &ConstBinaryOp{"/", $1, $3, $2} }
+| expr '%' expr
+  { $$ = &ConstBinaryOp{"%", $1, $3, $2} }
+| expr '|' expr
+  { $$ = &ConstBinaryOp{"|", $1, $3, $2} }
+| expr '&' expr
+  { $$ = &ConstBinaryOp{"&", $1, $3, $2} }
+| expr '^' expr
+  { $$ = &ConstBinaryOp{"^", $1, $3, $2} }
+| expr tLSH expr
+  { $$ = &ConstBinaryOp{"<<", $1, $3, $2} }
+| expr tRSH expr
+  { $$ = &ConstBinaryOp{">>", $1, $3, $2} }
+
+unary_expr:
+  operand
+  { $$ = $1 }
+| '!' unary_expr
+  { $$ = &ConstUnaryOp{"!", $2, $1} }
+| '+' unary_expr
+  { $$ = &ConstUnaryOp{"+", $2, $1} }
+| '-' unary_expr
+  { $$ = &ConstUnaryOp{"-", $2, $1} }
+| '^' unary_expr
+  { $$ = &ConstUnaryOp{"^", $2, $1} }
+| type_no_typeobject '(' expr ')'
+  { $$ = &ConstTypeConv{$1, $3, $1.Pos()} }
+| tTYPEOBJECT '(' type ')'
+  { $$ = &ConstTypeObject{$3, $1} }
+// TODO(bprosnitz) Add .real() and .imag() for complex.
+
+operand:
+  tSTRLIT
+  { $$ = &ConstLit{$1.String, $1.Pos} }
+| tINTLIT
+  { $$ = &ConstLit{$1.int, $1.pos} }
+| tRATLIT
+  { $$ = &ConstLit{$1.rat, $1.pos} }
+| tIMAGLIT
+  { $$ = &ConstLit{$1.imag, $1.pos} }
+| nameref
+  { $$ = &ConstNamed{$1.String, $1.Pos} }
+| comp_lit
+  { $$ = $1 }
+| comp_lit '.' tIDENT
+  { lexPosErrorf(yylex, $2, "cannot apply selector operator to unnamed constant")}
+| comp_lit '[' expr ']'
+  { lexPosErrorf(yylex, $2, "cannot apply index operator to unnamed constant")}
+| nameref '[' expr ']'
+  { $$ = &ConstIndexed{&ConstNamed{$1.String, $1.Pos}, $3, $1.Pos} }
+| '(' expr ')'
+  { $$ = $2 }
+
+comp_lit:
+  otype '{' '}'
+  { $$ = &ConstCompositeLit{$1, nil, $2} }
+| otype '{' kv_lit_list ocomma '}'
+  { $$ = &ConstCompositeLit{$1, $3, $2} }
+
+kv_lit_list:
+  kv_lit
+  { $$ = []KVLit{$1} }
+| kv_lit_list ',' kv_lit
+  { $$ = append($1, $3) }
+
+kv_lit:
+  expr
+  { $$ = KVLit{Value:$1} }
+| expr ':' expr
+  { $$ = KVLit{Key:$1, Value:$3} }
+
+// ERROR DEFINITIONS
+error_spec_list:
+  error_spec
+| error_spec_list ';' error_spec
+
+error_spec:
+  tIDENT inargs error_details
+  {
+    // Create *ErrorDef starting with a copy of error_details, filling in the
+    // name and params
+    ed := $3
+    ed.NamePos = NamePos{Name:$1.String, Pos:$1.Pos}
+    ed.Params = $2
+    eds := &lexVDLFile(yylex).ErrorDefs
+    *eds = append(*eds, &ed)
+  }
+
+error_details:
+  // Empty.
+  { $$ = ErrorDef{} }
+| '{' '}'
+  { $$ = ErrorDef{} }
+| '{' error_detail_list ocomma '}'
+  { $$ = $2 }
+
+error_detail_list:
+  error_detail
+  { $$ = $1 }
+| error_detail_list ',' error_detail
+  {
+    // Merge each ErrorDef in-order to build the final ErrorDef.
+    $$ = $1
+    switch {
+    case len($3.Actions) > 0:
+      $$.Actions = append($$.Actions, $3.Actions...)
+    case len($3.Formats) > 0:
+      $$.Formats = append($$.Formats, $3.Formats...)
+    }
+  }
+
+error_detail:
+  tIDENT
+  { $$ = ErrorDef{Actions: []StringPos{$1}} }
+| tSTRLIT ':' tSTRLIT
+  { $$ = ErrorDef{Formats: []LangFmt{{Lang: $1, Fmt: $3}}} }
+
+// MISC TOKENS
+
+// nameref describes a named reference to another type, interface or const.  We
+// allow the following forms:
+//   foo
+//   foo.bar            (and multi-dot variants)
+//   "pkg/path".foo
+//   "pkg/path".foo.bar (and multi-dot variants)
+nameref:
+  dotnameref
+  { $$ = $1 }
+| tSTRLIT '.' dotnameref
+  { $$ = StringPos{"\""+$1.String+"\"."+$3.String, $1.Pos} }
+
+// dotnameref describes just the dotted portion of nameref.
+dotnameref:
+  tIDENT
+  { $$ = $1 }
+| dotnameref '.' tIDENT
+  { $$ = StringPos{$1.String+"."+$3.String, $1.Pos} }
+
+otype:
+  // Empty.
+  { $$ = nil }
+| type
+  { $$ = $1 }
+
+osemi:
+  // Empty.
+| ';'
+
+ocomma:
+  // Empty.
+| ','
diff --git a/lib/vdl/parse/grammar.y.debug b/lib/vdl/parse/grammar.y.debug
new file mode 100644
index 0000000..a3fb1fc
--- /dev/null
+++ b/lib/vdl/parse/grammar.y.debug
@@ -0,0 +1,4347 @@
+***** PLEASE READ THIS! DO NOT DELETE THIS BLOCK! *****
+* The main reason this file has been generated and submitted is to try to ensure
+* we never submit changes that cause shift/reduce or reduce/reduce conflicts.
+* The Go yacc tool doesn't support the %expect directive, and will happily
+* generate a parser even if such conflicts exist; it's up to the developer
+* running the tool to notice that an error message is reported.  The bottom of
+* this file contains stats, including the number of conflicts.  If you're
+* reviewing a change make sure it says 0 conflicts.
+*
+* If you're updating the grammar, just cut-and-paste this message from the old
+* file to the new one, so that this comment block persists.
+***** PLEASE READ THIS! DO NOT DELETE THIS BLOCK! *****
+
+state 0
+	$accept: .start $end 
+
+	startFileImports  shift 2
+	startFile  shift 3
+	startConfigImports  shift 4
+	startConfig  shift 5
+	startExprs  shift 6
+	.  error
+
+	start  goto 1
+
+state 1
+	$accept:  start.$end 
+
+	$end  accept
+	.  error
+
+
+state 2
+	start:  startFileImports.package imports gen_imports_eof 
+	package: .    (10)
+
+	tPACKAGE  shift 8
+	.  reduce 10 (src line 161)
+
+	package  goto 7
+
+state 3
+	start:  startFile.package imports defs 
+	package: .    (10)
+
+	tPACKAGE  shift 8
+	.  reduce 10 (src line 161)
+
+	package  goto 9
+
+state 4
+	start:  startConfigImports.config imports gen_imports_eof 
+	config: .    (12)
+
+	tIDENT  shift 11
+	.  reduce 12 (src line 168)
+
+	config  goto 10
+
+state 5
+	start:  startConfig.config imports defs 
+	config: .    (12)
+
+	tIDENT  shift 11
+	.  reduce 12 (src line 168)
+
+	config  goto 12
+
+state 6
+	start:  startExprs.expr_comma_list ';' 
+	otype: .    (144)
+
+	'('  shift 29
+	'['  shift 31
+	'!'  shift 17
+	'+'  shift 18
+	'-'  shift 19
+	'^'  shift 20
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 22
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 23
+	tINTLIT  shift 24
+	tRATLIT  shift 25
+	tIMAGLIT  shift 26
+	.  reduce 144 (src line 634)
+
+	nameref  goto 27
+	dotnameref  goto 38
+	type  goto 41
+	type_no_typeobject  goto 21
+	otype  goto 39
+	expr  goto 14
+	unary_expr  goto 15
+	operand  goto 16
+	expr_comma_list  goto 13
+	comp_lit  goto 28
+
+state 7
+	start:  startFileImports package.imports gen_imports_eof 
+	imports: .    (14)
+
+	.  reduce 14 (src line 185)
+
+	imports  goto 42
+
+state 8
+	package:  tPACKAGE.tIDENT ';' 
+
+	tIDENT  shift 43
+	.  error
+
+
+state 9
+	start:  startFile package.imports defs 
+	imports: .    (14)
+
+	.  reduce 14 (src line 185)
+
+	imports  goto 44
+
+state 10
+	start:  startConfigImports config.imports gen_imports_eof 
+	imports: .    (14)
+
+	.  reduce 14 (src line 185)
+
+	imports  goto 45
+
+state 11
+	config:  tIDENT.'=' expr ';' 
+
+	'='  shift 46
+	.  error
+
+
+state 12
+	start:  startConfig config.imports defs 
+	imports: .    (14)
+
+	.  reduce 14 (src line 185)
+
+	imports  goto 47
+
+state 13
+	start:  startExprs expr_comma_list.';' 
+	expr_comma_list:  expr_comma_list.',' expr 
+
+	';'  shift 48
+	','  shift 49
+	.  error
+
+
+state 14
+	expr_comma_list:  expr.    (83)
+	expr:  expr.tOROR expr 
+	expr:  expr.tANDAND expr 
+	expr:  expr.'<' expr 
+	expr:  expr.'>' expr 
+	expr:  expr.tLE expr 
+	expr:  expr.tGE expr 
+	expr:  expr.tNE expr 
+	expr:  expr.tEQEQ expr 
+	expr:  expr.'+' expr 
+	expr:  expr.'-' expr 
+	expr:  expr.'*' expr 
+	expr:  expr.'/' expr 
+	expr:  expr.'%' expr 
+	expr:  expr.'|' expr 
+	expr:  expr.'&' expr 
+	expr:  expr.'^' expr 
+	expr:  expr.tLSH expr 
+	expr:  expr.tRSH expr 
+
+	'<'  shift 52
+	'>'  shift 53
+	'+'  shift 58
+	'-'  shift 59
+	'*'  shift 60
+	'/'  shift 61
+	'%'  shift 62
+	'|'  shift 63
+	'&'  shift 64
+	'^'  shift 65
+	tOROR  shift 50
+	tANDAND  shift 51
+	tLE  shift 54
+	tGE  shift 55
+	tNE  shift 56
+	tEQEQ  shift 57
+	tLSH  shift 66
+	tRSH  shift 67
+	.  reduce 83 (src line 452)
+
+
+state 15
+	expr:  unary_expr.    (88)
+
+	.  reduce 88 (src line 470)
+
+
+state 16
+	unary_expr:  operand.    (107)
+
+	.  reduce 107 (src line 510)
+
+
+state 17
+	unary_expr:  '!'.unary_expr 
+	otype: .    (144)
+
+	'('  shift 29
+	'['  shift 31
+	'!'  shift 17
+	'+'  shift 18
+	'-'  shift 19
+	'^'  shift 20
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 22
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 23
+	tINTLIT  shift 24
+	tRATLIT  shift 25
+	tIMAGLIT  shift 26
+	.  reduce 144 (src line 634)
+
+	nameref  goto 27
+	dotnameref  goto 38
+	type  goto 41
+	type_no_typeobject  goto 21
+	otype  goto 39
+	unary_expr  goto 68
+	operand  goto 16
+	comp_lit  goto 28
+
+state 18
+	unary_expr:  '+'.unary_expr 
+	otype: .    (144)
+
+	'('  shift 29
+	'['  shift 31
+	'!'  shift 17
+	'+'  shift 18
+	'-'  shift 19
+	'^'  shift 20
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 22
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 23
+	tINTLIT  shift 24
+	tRATLIT  shift 25
+	tIMAGLIT  shift 26
+	.  reduce 144 (src line 634)
+
+	nameref  goto 27
+	dotnameref  goto 38
+	type  goto 41
+	type_no_typeobject  goto 21
+	otype  goto 39
+	unary_expr  goto 69
+	operand  goto 16
+	comp_lit  goto 28
+
+state 19
+	unary_expr:  '-'.unary_expr 
+	otype: .    (144)
+
+	'('  shift 29
+	'['  shift 31
+	'!'  shift 17
+	'+'  shift 18
+	'-'  shift 19
+	'^'  shift 20
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 22
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 23
+	tINTLIT  shift 24
+	tRATLIT  shift 25
+	tIMAGLIT  shift 26
+	.  reduce 144 (src line 634)
+
+	nameref  goto 27
+	dotnameref  goto 38
+	type  goto 41
+	type_no_typeobject  goto 21
+	otype  goto 39
+	unary_expr  goto 70
+	operand  goto 16
+	comp_lit  goto 28
+
+state 20
+	unary_expr:  '^'.unary_expr 
+	otype: .    (144)
+
+	'('  shift 29
+	'['  shift 31
+	'!'  shift 17
+	'+'  shift 18
+	'-'  shift 19
+	'^'  shift 20
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 22
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 23
+	tINTLIT  shift 24
+	tRATLIT  shift 25
+	tIMAGLIT  shift 26
+	.  reduce 144 (src line 634)
+
+	nameref  goto 27
+	dotnameref  goto 38
+	type  goto 41
+	type_no_typeobject  goto 21
+	otype  goto 39
+	unary_expr  goto 71
+	operand  goto 16
+	comp_lit  goto 28
+
+state 21
+	type:  type_no_typeobject.    (52)
+	unary_expr:  type_no_typeobject.'(' expr ')' 
+
+	'('  shift 72
+	.  reduce 52 (src line 287)
+
+
+state 22
+	type:  tTYPEOBJECT.    (53)
+	unary_expr:  tTYPEOBJECT.'(' type ')' 
+
+	'('  shift 73
+	.  reduce 53 (src line 290)
+
+
+state 23
+	operand:  tSTRLIT.    (114)
+	nameref:  tSTRLIT.'.' dotnameref 
+
+	'.'  shift 74
+	.  reduce 114 (src line 527)
+
+
+state 24
+	operand:  tINTLIT.    (115)
+
+	.  reduce 115 (src line 530)
+
+
+state 25
+	operand:  tRATLIT.    (116)
+
+	.  reduce 116 (src line 532)
+
+
+state 26
+	operand:  tIMAGLIT.    (117)
+
+	.  reduce 117 (src line 534)
+
+
+state 27
+	type_no_typeobject:  nameref.    (40)
+	operand:  nameref.    (118)
+	operand:  nameref.'[' expr ']' 
+
+	'('  reduce 40 (src line 260)
+	'['  shift 75
+	'{'  reduce 40 (src line 260)
+	.  reduce 118 (src line 536)
+
+
+state 28
+	operand:  comp_lit.    (119)
+	operand:  comp_lit.'.' tIDENT 
+	operand:  comp_lit.'[' expr ']' 
+
+	'.'  shift 76
+	'['  shift 77
+	.  reduce 119 (src line 538)
+
+
+state 29
+	operand:  '('.expr ')' 
+	otype: .    (144)
+
+	'('  shift 29
+	'['  shift 31
+	'!'  shift 17
+	'+'  shift 18
+	'-'  shift 19
+	'^'  shift 20
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 22
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 23
+	tINTLIT  shift 24
+	tRATLIT  shift 25
+	tIMAGLIT  shift 26
+	.  reduce 144 (src line 634)
+
+	nameref  goto 27
+	dotnameref  goto 38
+	type  goto 41
+	type_no_typeobject  goto 21
+	otype  goto 39
+	expr  goto 78
+	unary_expr  goto 15
+	operand  goto 16
+	comp_lit  goto 28
+
+state 30
+	type_no_typeobject:  tERROR.    (41)
+
+	.  reduce 41 (src line 263)
+
+
+state 31
+	type_no_typeobject:  '['.tINTLIT ']' type 
+	type_no_typeobject:  '['.']' type 
+
+	']'  shift 80
+	tINTLIT  shift 79
+	.  error
+
+
+state 32
+	type_no_typeobject:  tENUM.'{' label_spec_list osemi '}' 
+
+	'{'  shift 81
+	.  error
+
+
+state 33
+	type_no_typeobject:  tSET.'[' type ']' 
+
+	'['  shift 82
+	.  error
+
+
+state 34
+	type_no_typeobject:  tMAP.'[' type ']' type 
+
+	'['  shift 83
+	.  error
+
+
+state 35
+	type_no_typeobject:  tSTRUCT.'{' field_spec_list osemi '}' 
+	type_no_typeobject:  tSTRUCT.'{' '}' 
+
+	'{'  shift 84
+	.  error
+
+
+state 36
+	type_no_typeobject:  tUNION.'{' field_spec_list osemi '}' 
+	type_no_typeobject:  tUNION.'{' '}' 
+
+	'{'  shift 85
+	.  error
+
+
+state 37
+	type_no_typeobject:  '?'.type 
+
+	'['  shift 31
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 88
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 90
+	.  error
+
+	nameref  goto 89
+	dotnameref  goto 38
+	type  goto 86
+	type_no_typeobject  goto 87
+
+state 38
+	nameref:  dotnameref.    (140)
+	dotnameref:  dotnameref.'.' tIDENT 
+
+	'.'  shift 91
+	.  reduce 140 (src line 621)
+
+
+state 39
+	comp_lit:  otype.'{' '}' 
+	comp_lit:  otype.'{' kv_lit_list ocomma '}' 
+
+	'{'  shift 92
+	.  error
+
+
+state 40
+	dotnameref:  tIDENT.    (142)
+
+	.  reduce 142 (src line 628)
+
+
+state 41
+	otype:  type.    (145)
+
+	.  reduce 145 (src line 637)
+
+
+state 42
+	start:  startFileImports package imports.gen_imports_eof 
+	imports:  imports.import ';' 
+	gen_imports_eof: .    (6)
+
+	tCONST  shift 96
+	tERROR  shift 97
+	tIMPORT  shift 98
+	tTYPE  shift 95
+	.  reduce 6 (src line 150)
+
+	gen_imports_eof  goto 93
+	import  goto 94
+
+state 43
+	package:  tPACKAGE tIDENT.';' 
+
+	';'  shift 99
+	.  error
+
+
+state 44
+	start:  startFile package imports.defs 
+	imports:  imports.import ';' 
+	defs: .    (23)
+
+	tIMPORT  shift 98
+	.  reduce 23 (src line 211)
+
+	defs  goto 100
+	import  goto 94
+
+state 45
+	start:  startConfigImports config imports.gen_imports_eof 
+	imports:  imports.import ';' 
+	gen_imports_eof: .    (6)
+
+	tCONST  shift 96
+	tERROR  shift 97
+	tIMPORT  shift 98
+	tTYPE  shift 95
+	.  reduce 6 (src line 150)
+
+	gen_imports_eof  goto 101
+	import  goto 94
+
+state 46
+	config:  tIDENT '='.expr ';' 
+	otype: .    (144)
+
+	'('  shift 29
+	'['  shift 31
+	'!'  shift 17
+	'+'  shift 18
+	'-'  shift 19
+	'^'  shift 20
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 22
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 23
+	tINTLIT  shift 24
+	tRATLIT  shift 25
+	tIMAGLIT  shift 26
+	.  reduce 144 (src line 634)
+
+	nameref  goto 27
+	dotnameref  goto 38
+	type  goto 41
+	type_no_typeobject  goto 21
+	otype  goto 39
+	expr  goto 102
+	unary_expr  goto 15
+	operand  goto 16
+	comp_lit  goto 28
+
+state 47
+	start:  startConfig config imports.defs 
+	imports:  imports.import ';' 
+	defs: .    (23)
+
+	tIMPORT  shift 98
+	.  reduce 23 (src line 211)
+
+	defs  goto 103
+	import  goto 94
+
+state 48
+	start:  startExprs expr_comma_list ';'.    (5)
+
+	.  reduce 5 (src line 142)
+
+
+state 49
+	expr_comma_list:  expr_comma_list ','.expr 
+	otype: .    (144)
+
+	'('  shift 29
+	'['  shift 31
+	'!'  shift 17
+	'+'  shift 18
+	'-'  shift 19
+	'^'  shift 20
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 22
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 23
+	tINTLIT  shift 24
+	tRATLIT  shift 25
+	tIMAGLIT  shift 26
+	.  reduce 144 (src line 634)
+
+	nameref  goto 27
+	dotnameref  goto 38
+	type  goto 41
+	type_no_typeobject  goto 21
+	otype  goto 39
+	expr  goto 104
+	unary_expr  goto 15
+	operand  goto 16
+	comp_lit  goto 28
+
+state 50
+	expr:  expr tOROR.expr 
+	otype: .    (144)
+
+	'('  shift 29
+	'['  shift 31
+	'!'  shift 17
+	'+'  shift 18
+	'-'  shift 19
+	'^'  shift 20
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 22
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 23
+	tINTLIT  shift 24
+	tRATLIT  shift 25
+	tIMAGLIT  shift 26
+	.  reduce 144 (src line 634)
+
+	nameref  goto 27
+	dotnameref  goto 38
+	type  goto 41
+	type_no_typeobject  goto 21
+	otype  goto 39
+	expr  goto 105
+	unary_expr  goto 15
+	operand  goto 16
+	comp_lit  goto 28
+
+state 51
+	expr:  expr tANDAND.expr 
+	otype: .    (144)
+
+	'('  shift 29
+	'['  shift 31
+	'!'  shift 17
+	'+'  shift 18
+	'-'  shift 19
+	'^'  shift 20
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 22
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 23
+	tINTLIT  shift 24
+	tRATLIT  shift 25
+	tIMAGLIT  shift 26
+	.  reduce 144 (src line 634)
+
+	nameref  goto 27
+	dotnameref  goto 38
+	type  goto 41
+	type_no_typeobject  goto 21
+	otype  goto 39
+	expr  goto 106
+	unary_expr  goto 15
+	operand  goto 16
+	comp_lit  goto 28
+
+state 52
+	expr:  expr '<'.expr 
+	otype: .    (144)
+
+	'('  shift 29
+	'['  shift 31
+	'!'  shift 17
+	'+'  shift 18
+	'-'  shift 19
+	'^'  shift 20
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 22
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 23
+	tINTLIT  shift 24
+	tRATLIT  shift 25
+	tIMAGLIT  shift 26
+	.  reduce 144 (src line 634)
+
+	nameref  goto 27
+	dotnameref  goto 38
+	type  goto 41
+	type_no_typeobject  goto 21
+	otype  goto 39
+	expr  goto 107
+	unary_expr  goto 15
+	operand  goto 16
+	comp_lit  goto 28
+
+state 53
+	expr:  expr '>'.expr 
+	otype: .    (144)
+
+	'('  shift 29
+	'['  shift 31
+	'!'  shift 17
+	'+'  shift 18
+	'-'  shift 19
+	'^'  shift 20
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 22
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 23
+	tINTLIT  shift 24
+	tRATLIT  shift 25
+	tIMAGLIT  shift 26
+	.  reduce 144 (src line 634)
+
+	nameref  goto 27
+	dotnameref  goto 38
+	type  goto 41
+	type_no_typeobject  goto 21
+	otype  goto 39
+	expr  goto 108
+	unary_expr  goto 15
+	operand  goto 16
+	comp_lit  goto 28
+
+state 54
+	expr:  expr tLE.expr 
+	otype: .    (144)
+
+	'('  shift 29
+	'['  shift 31
+	'!'  shift 17
+	'+'  shift 18
+	'-'  shift 19
+	'^'  shift 20
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 22
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 23
+	tINTLIT  shift 24
+	tRATLIT  shift 25
+	tIMAGLIT  shift 26
+	.  reduce 144 (src line 634)
+
+	nameref  goto 27
+	dotnameref  goto 38
+	type  goto 41
+	type_no_typeobject  goto 21
+	otype  goto 39
+	expr  goto 109
+	unary_expr  goto 15
+	operand  goto 16
+	comp_lit  goto 28
+
+state 55
+	expr:  expr tGE.expr 
+	otype: .    (144)
+
+	'('  shift 29
+	'['  shift 31
+	'!'  shift 17
+	'+'  shift 18
+	'-'  shift 19
+	'^'  shift 20
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 22
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 23
+	tINTLIT  shift 24
+	tRATLIT  shift 25
+	tIMAGLIT  shift 26
+	.  reduce 144 (src line 634)
+
+	nameref  goto 27
+	dotnameref  goto 38
+	type  goto 41
+	type_no_typeobject  goto 21
+	otype  goto 39
+	expr  goto 110
+	unary_expr  goto 15
+	operand  goto 16
+	comp_lit  goto 28
+
+state 56
+	expr:  expr tNE.expr 
+	otype: .    (144)
+
+	'('  shift 29
+	'['  shift 31
+	'!'  shift 17
+	'+'  shift 18
+	'-'  shift 19
+	'^'  shift 20
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 22
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 23
+	tINTLIT  shift 24
+	tRATLIT  shift 25
+	tIMAGLIT  shift 26
+	.  reduce 144 (src line 634)
+
+	nameref  goto 27
+	dotnameref  goto 38
+	type  goto 41
+	type_no_typeobject  goto 21
+	otype  goto 39
+	expr  goto 111
+	unary_expr  goto 15
+	operand  goto 16
+	comp_lit  goto 28
+
+state 57
+	expr:  expr tEQEQ.expr 
+	otype: .    (144)
+
+	'('  shift 29
+	'['  shift 31
+	'!'  shift 17
+	'+'  shift 18
+	'-'  shift 19
+	'^'  shift 20
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 22
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 23
+	tINTLIT  shift 24
+	tRATLIT  shift 25
+	tIMAGLIT  shift 26
+	.  reduce 144 (src line 634)
+
+	nameref  goto 27
+	dotnameref  goto 38
+	type  goto 41
+	type_no_typeobject  goto 21
+	otype  goto 39
+	expr  goto 112
+	unary_expr  goto 15
+	operand  goto 16
+	comp_lit  goto 28
+
+state 58
+	expr:  expr '+'.expr 
+	otype: .    (144)
+
+	'('  shift 29
+	'['  shift 31
+	'!'  shift 17
+	'+'  shift 18
+	'-'  shift 19
+	'^'  shift 20
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 22
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 23
+	tINTLIT  shift 24
+	tRATLIT  shift 25
+	tIMAGLIT  shift 26
+	.  reduce 144 (src line 634)
+
+	nameref  goto 27
+	dotnameref  goto 38
+	type  goto 41
+	type_no_typeobject  goto 21
+	otype  goto 39
+	expr  goto 113
+	unary_expr  goto 15
+	operand  goto 16
+	comp_lit  goto 28
+
+state 59
+	expr:  expr '-'.expr 
+	otype: .    (144)
+
+	'('  shift 29
+	'['  shift 31
+	'!'  shift 17
+	'+'  shift 18
+	'-'  shift 19
+	'^'  shift 20
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 22
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 23
+	tINTLIT  shift 24
+	tRATLIT  shift 25
+	tIMAGLIT  shift 26
+	.  reduce 144 (src line 634)
+
+	nameref  goto 27
+	dotnameref  goto 38
+	type  goto 41
+	type_no_typeobject  goto 21
+	otype  goto 39
+	expr  goto 114
+	unary_expr  goto 15
+	operand  goto 16
+	comp_lit  goto 28
+
+state 60
+	expr:  expr '*'.expr 
+	otype: .    (144)
+
+	'('  shift 29
+	'['  shift 31
+	'!'  shift 17
+	'+'  shift 18
+	'-'  shift 19
+	'^'  shift 20
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 22
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 23
+	tINTLIT  shift 24
+	tRATLIT  shift 25
+	tIMAGLIT  shift 26
+	.  reduce 144 (src line 634)
+
+	nameref  goto 27
+	dotnameref  goto 38
+	type  goto 41
+	type_no_typeobject  goto 21
+	otype  goto 39
+	expr  goto 115
+	unary_expr  goto 15
+	operand  goto 16
+	comp_lit  goto 28
+
+state 61
+	expr:  expr '/'.expr 
+	otype: .    (144)
+
+	'('  shift 29
+	'['  shift 31
+	'!'  shift 17
+	'+'  shift 18
+	'-'  shift 19
+	'^'  shift 20
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 22
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 23
+	tINTLIT  shift 24
+	tRATLIT  shift 25
+	tIMAGLIT  shift 26
+	.  reduce 144 (src line 634)
+
+	nameref  goto 27
+	dotnameref  goto 38
+	type  goto 41
+	type_no_typeobject  goto 21
+	otype  goto 39
+	expr  goto 116
+	unary_expr  goto 15
+	operand  goto 16
+	comp_lit  goto 28
+
+state 62
+	expr:  expr '%'.expr 
+	otype: .    (144)
+
+	'('  shift 29
+	'['  shift 31
+	'!'  shift 17
+	'+'  shift 18
+	'-'  shift 19
+	'^'  shift 20
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 22
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 23
+	tINTLIT  shift 24
+	tRATLIT  shift 25
+	tIMAGLIT  shift 26
+	.  reduce 144 (src line 634)
+
+	nameref  goto 27
+	dotnameref  goto 38
+	type  goto 41
+	type_no_typeobject  goto 21
+	otype  goto 39
+	expr  goto 117
+	unary_expr  goto 15
+	operand  goto 16
+	comp_lit  goto 28
+
+state 63
+	expr:  expr '|'.expr 
+	otype: .    (144)
+
+	'('  shift 29
+	'['  shift 31
+	'!'  shift 17
+	'+'  shift 18
+	'-'  shift 19
+	'^'  shift 20
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 22
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 23
+	tINTLIT  shift 24
+	tRATLIT  shift 25
+	tIMAGLIT  shift 26
+	.  reduce 144 (src line 634)
+
+	nameref  goto 27
+	dotnameref  goto 38
+	type  goto 41
+	type_no_typeobject  goto 21
+	otype  goto 39
+	expr  goto 118
+	unary_expr  goto 15
+	operand  goto 16
+	comp_lit  goto 28
+
+state 64
+	expr:  expr '&'.expr 
+	otype: .    (144)
+
+	'('  shift 29
+	'['  shift 31
+	'!'  shift 17
+	'+'  shift 18
+	'-'  shift 19
+	'^'  shift 20
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 22
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 23
+	tINTLIT  shift 24
+	tRATLIT  shift 25
+	tIMAGLIT  shift 26
+	.  reduce 144 (src line 634)
+
+	nameref  goto 27
+	dotnameref  goto 38
+	type  goto 41
+	type_no_typeobject  goto 21
+	otype  goto 39
+	expr  goto 119
+	unary_expr  goto 15
+	operand  goto 16
+	comp_lit  goto 28
+
+state 65
+	expr:  expr '^'.expr 
+	otype: .    (144)
+
+	'('  shift 29
+	'['  shift 31
+	'!'  shift 17
+	'+'  shift 18
+	'-'  shift 19
+	'^'  shift 20
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 22
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 23
+	tINTLIT  shift 24
+	tRATLIT  shift 25
+	tIMAGLIT  shift 26
+	.  reduce 144 (src line 634)
+
+	nameref  goto 27
+	dotnameref  goto 38
+	type  goto 41
+	type_no_typeobject  goto 21
+	otype  goto 39
+	expr  goto 120
+	unary_expr  goto 15
+	operand  goto 16
+	comp_lit  goto 28
+
+state 66
+	expr:  expr tLSH.expr 
+	otype: .    (144)
+
+	'('  shift 29
+	'['  shift 31
+	'!'  shift 17
+	'+'  shift 18
+	'-'  shift 19
+	'^'  shift 20
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 22
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 23
+	tINTLIT  shift 24
+	tRATLIT  shift 25
+	tIMAGLIT  shift 26
+	.  reduce 144 (src line 634)
+
+	nameref  goto 27
+	dotnameref  goto 38
+	type  goto 41
+	type_no_typeobject  goto 21
+	otype  goto 39
+	expr  goto 121
+	unary_expr  goto 15
+	operand  goto 16
+	comp_lit  goto 28
+
+state 67
+	expr:  expr tRSH.expr 
+	otype: .    (144)
+
+	'('  shift 29
+	'['  shift 31
+	'!'  shift 17
+	'+'  shift 18
+	'-'  shift 19
+	'^'  shift 20
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 22
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 23
+	tINTLIT  shift 24
+	tRATLIT  shift 25
+	tIMAGLIT  shift 26
+	.  reduce 144 (src line 634)
+
+	nameref  goto 27
+	dotnameref  goto 38
+	type  goto 41
+	type_no_typeobject  goto 21
+	otype  goto 39
+	expr  goto 122
+	unary_expr  goto 15
+	operand  goto 16
+	comp_lit  goto 28
+
+state 68
+	unary_expr:  '!' unary_expr.    (108)
+
+	.  reduce 108 (src line 513)
+
+
+state 69
+	unary_expr:  '+' unary_expr.    (109)
+
+	.  reduce 109 (src line 515)
+
+
+state 70
+	unary_expr:  '-' unary_expr.    (110)
+
+	.  reduce 110 (src line 517)
+
+
+state 71
+	unary_expr:  '^' unary_expr.    (111)
+
+	.  reduce 111 (src line 519)
+
+
+state 72
+	unary_expr:  type_no_typeobject '('.expr ')' 
+	otype: .    (144)
+
+	'('  shift 29
+	'['  shift 31
+	'!'  shift 17
+	'+'  shift 18
+	'-'  shift 19
+	'^'  shift 20
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 22
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 23
+	tINTLIT  shift 24
+	tRATLIT  shift 25
+	tIMAGLIT  shift 26
+	.  reduce 144 (src line 634)
+
+	nameref  goto 27
+	dotnameref  goto 38
+	type  goto 41
+	type_no_typeobject  goto 21
+	otype  goto 39
+	expr  goto 123
+	unary_expr  goto 15
+	operand  goto 16
+	comp_lit  goto 28
+
+state 73
+	unary_expr:  tTYPEOBJECT '('.type ')' 
+
+	'['  shift 31
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 88
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 90
+	.  error
+
+	nameref  goto 89
+	dotnameref  goto 38
+	type  goto 124
+	type_no_typeobject  goto 87
+
+state 74
+	nameref:  tSTRLIT '.'.dotnameref 
+
+	tIDENT  shift 40
+	.  error
+
+	dotnameref  goto 125
+
+state 75
+	operand:  nameref '['.expr ']' 
+	otype: .    (144)
+
+	'('  shift 29
+	'['  shift 31
+	'!'  shift 17
+	'+'  shift 18
+	'-'  shift 19
+	'^'  shift 20
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 22
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 23
+	tINTLIT  shift 24
+	tRATLIT  shift 25
+	tIMAGLIT  shift 26
+	.  reduce 144 (src line 634)
+
+	nameref  goto 27
+	dotnameref  goto 38
+	type  goto 41
+	type_no_typeobject  goto 21
+	otype  goto 39
+	expr  goto 126
+	unary_expr  goto 15
+	operand  goto 16
+	comp_lit  goto 28
+
+state 76
+	operand:  comp_lit '.'.tIDENT 
+
+	tIDENT  shift 127
+	.  error
+
+
+state 77
+	operand:  comp_lit '['.expr ']' 
+	otype: .    (144)
+
+	'('  shift 29
+	'['  shift 31
+	'!'  shift 17
+	'+'  shift 18
+	'-'  shift 19
+	'^'  shift 20
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 22
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 23
+	tINTLIT  shift 24
+	tRATLIT  shift 25
+	tIMAGLIT  shift 26
+	.  reduce 144 (src line 634)
+
+	nameref  goto 27
+	dotnameref  goto 38
+	type  goto 41
+	type_no_typeobject  goto 21
+	otype  goto 39
+	expr  goto 128
+	unary_expr  goto 15
+	operand  goto 16
+	comp_lit  goto 28
+
+state 78
+	expr:  expr.tOROR expr 
+	expr:  expr.tANDAND expr 
+	expr:  expr.'<' expr 
+	expr:  expr.'>' expr 
+	expr:  expr.tLE expr 
+	expr:  expr.tGE expr 
+	expr:  expr.tNE expr 
+	expr:  expr.tEQEQ expr 
+	expr:  expr.'+' expr 
+	expr:  expr.'-' expr 
+	expr:  expr.'*' expr 
+	expr:  expr.'/' expr 
+	expr:  expr.'%' expr 
+	expr:  expr.'|' expr 
+	expr:  expr.'&' expr 
+	expr:  expr.'^' expr 
+	expr:  expr.tLSH expr 
+	expr:  expr.tRSH expr 
+	operand:  '(' expr.')' 
+
+	')'  shift 129
+	'<'  shift 52
+	'>'  shift 53
+	'+'  shift 58
+	'-'  shift 59
+	'*'  shift 60
+	'/'  shift 61
+	'%'  shift 62
+	'|'  shift 63
+	'&'  shift 64
+	'^'  shift 65
+	tOROR  shift 50
+	tANDAND  shift 51
+	tLE  shift 54
+	tGE  shift 55
+	tNE  shift 56
+	tEQEQ  shift 57
+	tLSH  shift 66
+	tRSH  shift 67
+	.  error
+
+
+state 79
+	type_no_typeobject:  '[' tINTLIT.']' type 
+
+	']'  shift 130
+	.  error
+
+
+state 80
+	type_no_typeobject:  '[' ']'.type 
+
+	'['  shift 31
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 88
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 90
+	.  error
+
+	nameref  goto 89
+	dotnameref  goto 38
+	type  goto 131
+	type_no_typeobject  goto 87
+
+state 81
+	type_no_typeobject:  tENUM '{'.label_spec_list osemi '}' 
+
+	tIDENT  shift 134
+	.  error
+
+	label_spec  goto 133
+	label_spec_list  goto 132
+
+state 82
+	type_no_typeobject:  tSET '['.type ']' 
+
+	'['  shift 31
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 88
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 90
+	.  error
+
+	nameref  goto 89
+	dotnameref  goto 38
+	type  goto 135
+	type_no_typeobject  goto 87
+
+state 83
+	type_no_typeobject:  tMAP '['.type ']' type 
+
+	'['  shift 31
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 88
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 90
+	.  error
+
+	nameref  goto 89
+	dotnameref  goto 38
+	type  goto 136
+	type_no_typeobject  goto 87
+
+state 84
+	type_no_typeobject:  tSTRUCT '{'.field_spec_list osemi '}' 
+	type_no_typeobject:  tSTRUCT '{'.'}' 
+
+	'['  shift 31
+	'}'  shift 138
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 88
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 90
+	.  error
+
+	nameref  goto 89
+	dotnameref  goto 38
+	type  goto 141
+	type_no_typeobject  goto 87
+	type_comma_list  goto 140
+	field_spec_list  goto 137
+	field_spec  goto 139
+
+state 85
+	type_no_typeobject:  tUNION '{'.field_spec_list osemi '}' 
+	type_no_typeobject:  tUNION '{'.'}' 
+
+	'['  shift 31
+	'}'  shift 143
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 88
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 90
+	.  error
+
+	nameref  goto 89
+	dotnameref  goto 38
+	type  goto 141
+	type_no_typeobject  goto 87
+	type_comma_list  goto 140
+	field_spec_list  goto 142
+	field_spec  goto 139
+
+state 86
+	type_no_typeobject:  '?' type.    (51)
+
+	.  reduce 51 (src line 283)
+
+
+state 87
+	type:  type_no_typeobject.    (52)
+
+	.  reduce 52 (src line 287)
+
+
+state 88
+	type:  tTYPEOBJECT.    (53)
+
+	.  reduce 53 (src line 290)
+
+
+state 89
+	type_no_typeobject:  nameref.    (40)
+
+	.  reduce 40 (src line 260)
+
+
+state 90
+	nameref:  tSTRLIT.'.' dotnameref 
+
+	'.'  shift 74
+	.  error
+
+
+state 91
+	dotnameref:  dotnameref '.'.tIDENT 
+
+	tIDENT  shift 144
+	.  error
+
+
+state 92
+	comp_lit:  otype '{'.'}' 
+	comp_lit:  otype '{'.kv_lit_list ocomma '}' 
+	otype: .    (144)
+
+	'('  shift 29
+	'['  shift 31
+	'}'  shift 145
+	'!'  shift 17
+	'+'  shift 18
+	'-'  shift 19
+	'^'  shift 20
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 22
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 23
+	tINTLIT  shift 24
+	tRATLIT  shift 25
+	tIMAGLIT  shift 26
+	.  reduce 144 (src line 634)
+
+	nameref  goto 27
+	dotnameref  goto 38
+	type  goto 41
+	type_no_typeobject  goto 21
+	otype  goto 39
+	expr  goto 148
+	unary_expr  goto 15
+	operand  goto 16
+	comp_lit  goto 28
+	kv_lit  goto 147
+	kv_lit_list  goto 146
+
+state 93
+	start:  startFileImports package imports gen_imports_eof.    (1)
+
+	.  reduce 1 (src line 137)
+
+
+state 94
+	imports:  imports import.';' 
+
+	';'  shift 149
+	.  error
+
+
+state 95
+	gen_imports_eof:  tTYPE.    (7)
+
+	.  reduce 7 (src line 153)
+
+
+state 96
+	gen_imports_eof:  tCONST.    (8)
+
+	.  reduce 8 (src line 155)
+
+
+state 97
+	gen_imports_eof:  tERROR.    (9)
+
+	.  reduce 9 (src line 157)
+
+
+state 98
+	import:  tIMPORT.'(' ')' 
+	import:  tIMPORT.'(' import_spec_list osemi ')' 
+	import:  tIMPORT.import_spec 
+
+	'('  shift 150
+	tIDENT  shift 153
+	tSTRLIT  shift 152
+	.  error
+
+	import_spec  goto 151
+
+state 99
+	package:  tPACKAGE tIDENT ';'.    (11)
+
+	.  reduce 11 (src line 164)
+
+
+state 100
+	start:  startFile package imports defs.    (2)
+	defs:  defs.type_def ';' 
+	defs:  defs.const_def ';' 
+	defs:  defs.error_def ';' 
+
+	tCONST  shift 158
+	tERROR  shift 159
+	tTYPE  shift 157
+	.  reduce 2 (src line 139)
+
+	type_def  goto 154
+	const_def  goto 155
+	error_def  goto 156
+
+state 101
+	start:  startConfigImports config imports gen_imports_eof.    (3)
+
+	.  reduce 3 (src line 140)
+
+
+state 102
+	config:  tIDENT '=' expr.';' 
+	expr:  expr.tOROR expr 
+	expr:  expr.tANDAND expr 
+	expr:  expr.'<' expr 
+	expr:  expr.'>' expr 
+	expr:  expr.tLE expr 
+	expr:  expr.tGE expr 
+	expr:  expr.tNE expr 
+	expr:  expr.tEQEQ expr 
+	expr:  expr.'+' expr 
+	expr:  expr.'-' expr 
+	expr:  expr.'*' expr 
+	expr:  expr.'/' expr 
+	expr:  expr.'%' expr 
+	expr:  expr.'|' expr 
+	expr:  expr.'&' expr 
+	expr:  expr.'^' expr 
+	expr:  expr.tLSH expr 
+	expr:  expr.tRSH expr 
+
+	';'  shift 160
+	'<'  shift 52
+	'>'  shift 53
+	'+'  shift 58
+	'-'  shift 59
+	'*'  shift 60
+	'/'  shift 61
+	'%'  shift 62
+	'|'  shift 63
+	'&'  shift 64
+	'^'  shift 65
+	tOROR  shift 50
+	tANDAND  shift 51
+	tLE  shift 54
+	tGE  shift 55
+	tNE  shift 56
+	tEQEQ  shift 57
+	tLSH  shift 66
+	tRSH  shift 67
+	.  error
+
+
+state 103
+	start:  startConfig config imports defs.    (4)
+	defs:  defs.type_def ';' 
+	defs:  defs.const_def ';' 
+	defs:  defs.error_def ';' 
+
+	tCONST  shift 158
+	tERROR  shift 159
+	tTYPE  shift 157
+	.  reduce 4 (src line 141)
+
+	type_def  goto 154
+	const_def  goto 155
+	error_def  goto 156
+
+state 104
+	expr_comma_list:  expr_comma_list ',' expr.    (84)
+	expr:  expr.tOROR expr 
+	expr:  expr.tANDAND expr 
+	expr:  expr.'<' expr 
+	expr:  expr.'>' expr 
+	expr:  expr.tLE expr 
+	expr:  expr.tGE expr 
+	expr:  expr.tNE expr 
+	expr:  expr.tEQEQ expr 
+	expr:  expr.'+' expr 
+	expr:  expr.'-' expr 
+	expr:  expr.'*' expr 
+	expr:  expr.'/' expr 
+	expr:  expr.'%' expr 
+	expr:  expr.'|' expr 
+	expr:  expr.'&' expr 
+	expr:  expr.'^' expr 
+	expr:  expr.tLSH expr 
+	expr:  expr.tRSH expr 
+
+	'<'  shift 52
+	'>'  shift 53
+	'+'  shift 58
+	'-'  shift 59
+	'*'  shift 60
+	'/'  shift 61
+	'%'  shift 62
+	'|'  shift 63
+	'&'  shift 64
+	'^'  shift 65
+	tOROR  shift 50
+	tANDAND  shift 51
+	tLE  shift 54
+	tGE  shift 55
+	tNE  shift 56
+	tEQEQ  shift 57
+	tLSH  shift 66
+	tRSH  shift 67
+	.  reduce 84 (src line 455)
+
+
+state 105
+	expr:  expr.tOROR expr 
+	expr:  expr tOROR expr.    (89)
+	expr:  expr.tANDAND expr 
+	expr:  expr.'<' expr 
+	expr:  expr.'>' expr 
+	expr:  expr.tLE expr 
+	expr:  expr.tGE expr 
+	expr:  expr.tNE expr 
+	expr:  expr.tEQEQ expr 
+	expr:  expr.'+' expr 
+	expr:  expr.'-' expr 
+	expr:  expr.'*' expr 
+	expr:  expr.'/' expr 
+	expr:  expr.'%' expr 
+	expr:  expr.'|' expr 
+	expr:  expr.'&' expr 
+	expr:  expr.'^' expr 
+	expr:  expr.tLSH expr 
+	expr:  expr.tRSH expr 
+
+	'<'  shift 52
+	'>'  shift 53
+	'+'  shift 58
+	'-'  shift 59
+	'*'  shift 60
+	'/'  shift 61
+	'%'  shift 62
+	'|'  shift 63
+	'&'  shift 64
+	'^'  shift 65
+	tANDAND  shift 51
+	tLE  shift 54
+	tGE  shift 55
+	tNE  shift 56
+	tEQEQ  shift 57
+	tLSH  shift 66
+	tRSH  shift 67
+	.  reduce 89 (src line 473)
+
+
+state 106
+	expr:  expr.tOROR expr 
+	expr:  expr.tANDAND expr 
+	expr:  expr tANDAND expr.    (90)
+	expr:  expr.'<' expr 
+	expr:  expr.'>' expr 
+	expr:  expr.tLE expr 
+	expr:  expr.tGE expr 
+	expr:  expr.tNE expr 
+	expr:  expr.tEQEQ expr 
+	expr:  expr.'+' expr 
+	expr:  expr.'-' expr 
+	expr:  expr.'*' expr 
+	expr:  expr.'/' expr 
+	expr:  expr.'%' expr 
+	expr:  expr.'|' expr 
+	expr:  expr.'&' expr 
+	expr:  expr.'^' expr 
+	expr:  expr.tLSH expr 
+	expr:  expr.tRSH expr 
+
+	'<'  shift 52
+	'>'  shift 53
+	'+'  shift 58
+	'-'  shift 59
+	'*'  shift 60
+	'/'  shift 61
+	'%'  shift 62
+	'|'  shift 63
+	'&'  shift 64
+	'^'  shift 65
+	tLE  shift 54
+	tGE  shift 55
+	tNE  shift 56
+	tEQEQ  shift 57
+	tLSH  shift 66
+	tRSH  shift 67
+	.  reduce 90 (src line 475)
+
+
+state 107
+	expr:  expr.tOROR expr 
+	expr:  expr.tANDAND expr 
+	expr:  expr.'<' expr 
+	expr:  expr '<' expr.    (91)
+	expr:  expr.'>' expr 
+	expr:  expr.tLE expr 
+	expr:  expr.tGE expr 
+	expr:  expr.tNE expr 
+	expr:  expr.tEQEQ expr 
+	expr:  expr.'+' expr 
+	expr:  expr.'-' expr 
+	expr:  expr.'*' expr 
+	expr:  expr.'/' expr 
+	expr:  expr.'%' expr 
+	expr:  expr.'|' expr 
+	expr:  expr.'&' expr 
+	expr:  expr.'^' expr 
+	expr:  expr.tLSH expr 
+	expr:  expr.tRSH expr 
+
+	'+'  shift 58
+	'-'  shift 59
+	'*'  shift 60
+	'/'  shift 61
+	'%'  shift 62
+	'|'  shift 63
+	'&'  shift 64
+	'^'  shift 65
+	tLSH  shift 66
+	tRSH  shift 67
+	.  reduce 91 (src line 477)
+
+
+state 108
+	expr:  expr.tOROR expr 
+	expr:  expr.tANDAND expr 
+	expr:  expr.'<' expr 
+	expr:  expr.'>' expr 
+	expr:  expr '>' expr.    (92)
+	expr:  expr.tLE expr 
+	expr:  expr.tGE expr 
+	expr:  expr.tNE expr 
+	expr:  expr.tEQEQ expr 
+	expr:  expr.'+' expr 
+	expr:  expr.'-' expr 
+	expr:  expr.'*' expr 
+	expr:  expr.'/' expr 
+	expr:  expr.'%' expr 
+	expr:  expr.'|' expr 
+	expr:  expr.'&' expr 
+	expr:  expr.'^' expr 
+	expr:  expr.tLSH expr 
+	expr:  expr.tRSH expr 
+
+	'+'  shift 58
+	'-'  shift 59
+	'*'  shift 60
+	'/'  shift 61
+	'%'  shift 62
+	'|'  shift 63
+	'&'  shift 64
+	'^'  shift 65
+	tLSH  shift 66
+	tRSH  shift 67
+	.  reduce 92 (src line 479)
+
+
+state 109
+	expr:  expr.tOROR expr 
+	expr:  expr.tANDAND expr 
+	expr:  expr.'<' expr 
+	expr:  expr.'>' expr 
+	expr:  expr.tLE expr 
+	expr:  expr tLE expr.    (93)
+	expr:  expr.tGE expr 
+	expr:  expr.tNE expr 
+	expr:  expr.tEQEQ expr 
+	expr:  expr.'+' expr 
+	expr:  expr.'-' expr 
+	expr:  expr.'*' expr 
+	expr:  expr.'/' expr 
+	expr:  expr.'%' expr 
+	expr:  expr.'|' expr 
+	expr:  expr.'&' expr 
+	expr:  expr.'^' expr 
+	expr:  expr.tLSH expr 
+	expr:  expr.tRSH expr 
+
+	'+'  shift 58
+	'-'  shift 59
+	'*'  shift 60
+	'/'  shift 61
+	'%'  shift 62
+	'|'  shift 63
+	'&'  shift 64
+	'^'  shift 65
+	tLSH  shift 66
+	tRSH  shift 67
+	.  reduce 93 (src line 481)
+
+
+state 110
+	expr:  expr.tOROR expr 
+	expr:  expr.tANDAND expr 
+	expr:  expr.'<' expr 
+	expr:  expr.'>' expr 
+	expr:  expr.tLE expr 
+	expr:  expr.tGE expr 
+	expr:  expr tGE expr.    (94)
+	expr:  expr.tNE expr 
+	expr:  expr.tEQEQ expr 
+	expr:  expr.'+' expr 
+	expr:  expr.'-' expr 
+	expr:  expr.'*' expr 
+	expr:  expr.'/' expr 
+	expr:  expr.'%' expr 
+	expr:  expr.'|' expr 
+	expr:  expr.'&' expr 
+	expr:  expr.'^' expr 
+	expr:  expr.tLSH expr 
+	expr:  expr.tRSH expr 
+
+	'+'  shift 58
+	'-'  shift 59
+	'*'  shift 60
+	'/'  shift 61
+	'%'  shift 62
+	'|'  shift 63
+	'&'  shift 64
+	'^'  shift 65
+	tLSH  shift 66
+	tRSH  shift 67
+	.  reduce 94 (src line 483)
+
+
+state 111
+	expr:  expr.tOROR expr 
+	expr:  expr.tANDAND expr 
+	expr:  expr.'<' expr 
+	expr:  expr.'>' expr 
+	expr:  expr.tLE expr 
+	expr:  expr.tGE expr 
+	expr:  expr.tNE expr 
+	expr:  expr tNE expr.    (95)
+	expr:  expr.tEQEQ expr 
+	expr:  expr.'+' expr 
+	expr:  expr.'-' expr 
+	expr:  expr.'*' expr 
+	expr:  expr.'/' expr 
+	expr:  expr.'%' expr 
+	expr:  expr.'|' expr 
+	expr:  expr.'&' expr 
+	expr:  expr.'^' expr 
+	expr:  expr.tLSH expr 
+	expr:  expr.tRSH expr 
+
+	'+'  shift 58
+	'-'  shift 59
+	'*'  shift 60
+	'/'  shift 61
+	'%'  shift 62
+	'|'  shift 63
+	'&'  shift 64
+	'^'  shift 65
+	tLSH  shift 66
+	tRSH  shift 67
+	.  reduce 95 (src line 485)
+
+
+state 112
+	expr:  expr.tOROR expr 
+	expr:  expr.tANDAND expr 
+	expr:  expr.'<' expr 
+	expr:  expr.'>' expr 
+	expr:  expr.tLE expr 
+	expr:  expr.tGE expr 
+	expr:  expr.tNE expr 
+	expr:  expr.tEQEQ expr 
+	expr:  expr tEQEQ expr.    (96)
+	expr:  expr.'+' expr 
+	expr:  expr.'-' expr 
+	expr:  expr.'*' expr 
+	expr:  expr.'/' expr 
+	expr:  expr.'%' expr 
+	expr:  expr.'|' expr 
+	expr:  expr.'&' expr 
+	expr:  expr.'^' expr 
+	expr:  expr.tLSH expr 
+	expr:  expr.tRSH expr 
+
+	'+'  shift 58
+	'-'  shift 59
+	'*'  shift 60
+	'/'  shift 61
+	'%'  shift 62
+	'|'  shift 63
+	'&'  shift 64
+	'^'  shift 65
+	tLSH  shift 66
+	tRSH  shift 67
+	.  reduce 96 (src line 487)
+
+
+state 113
+	expr:  expr.tOROR expr 
+	expr:  expr.tANDAND expr 
+	expr:  expr.'<' expr 
+	expr:  expr.'>' expr 
+	expr:  expr.tLE expr 
+	expr:  expr.tGE expr 
+	expr:  expr.tNE expr 
+	expr:  expr.tEQEQ expr 
+	expr:  expr.'+' expr 
+	expr:  expr '+' expr.    (97)
+	expr:  expr.'-' expr 
+	expr:  expr.'*' expr 
+	expr:  expr.'/' expr 
+	expr:  expr.'%' expr 
+	expr:  expr.'|' expr 
+	expr:  expr.'&' expr 
+	expr:  expr.'^' expr 
+	expr:  expr.tLSH expr 
+	expr:  expr.tRSH expr 
+
+	'*'  shift 60
+	'/'  shift 61
+	'%'  shift 62
+	'&'  shift 64
+	tLSH  shift 66
+	tRSH  shift 67
+	.  reduce 97 (src line 489)
+
+
+state 114
+	expr:  expr.tOROR expr 
+	expr:  expr.tANDAND expr 
+	expr:  expr.'<' expr 
+	expr:  expr.'>' expr 
+	expr:  expr.tLE expr 
+	expr:  expr.tGE expr 
+	expr:  expr.tNE expr 
+	expr:  expr.tEQEQ expr 
+	expr:  expr.'+' expr 
+	expr:  expr.'-' expr 
+	expr:  expr '-' expr.    (98)
+	expr:  expr.'*' expr 
+	expr:  expr.'/' expr 
+	expr:  expr.'%' expr 
+	expr:  expr.'|' expr 
+	expr:  expr.'&' expr 
+	expr:  expr.'^' expr 
+	expr:  expr.tLSH expr 
+	expr:  expr.tRSH expr 
+
+	'*'  shift 60
+	'/'  shift 61
+	'%'  shift 62
+	'&'  shift 64
+	tLSH  shift 66
+	tRSH  shift 67
+	.  reduce 98 (src line 491)
+
+
+state 115
+	expr:  expr.tOROR expr 
+	expr:  expr.tANDAND expr 
+	expr:  expr.'<' expr 
+	expr:  expr.'>' expr 
+	expr:  expr.tLE expr 
+	expr:  expr.tGE expr 
+	expr:  expr.tNE expr 
+	expr:  expr.tEQEQ expr 
+	expr:  expr.'+' expr 
+	expr:  expr.'-' expr 
+	expr:  expr.'*' expr 
+	expr:  expr '*' expr.    (99)
+	expr:  expr.'/' expr 
+	expr:  expr.'%' expr 
+	expr:  expr.'|' expr 
+	expr:  expr.'&' expr 
+	expr:  expr.'^' expr 
+	expr:  expr.tLSH expr 
+	expr:  expr.tRSH expr 
+
+	.  reduce 99 (src line 493)
+
+
+state 116
+	expr:  expr.tOROR expr 
+	expr:  expr.tANDAND expr 
+	expr:  expr.'<' expr 
+	expr:  expr.'>' expr 
+	expr:  expr.tLE expr 
+	expr:  expr.tGE expr 
+	expr:  expr.tNE expr 
+	expr:  expr.tEQEQ expr 
+	expr:  expr.'+' expr 
+	expr:  expr.'-' expr 
+	expr:  expr.'*' expr 
+	expr:  expr.'/' expr 
+	expr:  expr '/' expr.    (100)
+	expr:  expr.'%' expr 
+	expr:  expr.'|' expr 
+	expr:  expr.'&' expr 
+	expr:  expr.'^' expr 
+	expr:  expr.tLSH expr 
+	expr:  expr.tRSH expr 
+
+	.  reduce 100 (src line 495)
+
+
+state 117
+	expr:  expr.tOROR expr 
+	expr:  expr.tANDAND expr 
+	expr:  expr.'<' expr 
+	expr:  expr.'>' expr 
+	expr:  expr.tLE expr 
+	expr:  expr.tGE expr 
+	expr:  expr.tNE expr 
+	expr:  expr.tEQEQ expr 
+	expr:  expr.'+' expr 
+	expr:  expr.'-' expr 
+	expr:  expr.'*' expr 
+	expr:  expr.'/' expr 
+	expr:  expr.'%' expr 
+	expr:  expr '%' expr.    (101)
+	expr:  expr.'|' expr 
+	expr:  expr.'&' expr 
+	expr:  expr.'^' expr 
+	expr:  expr.tLSH expr 
+	expr:  expr.tRSH expr 
+
+	.  reduce 101 (src line 497)
+
+
+state 118
+	expr:  expr.tOROR expr 
+	expr:  expr.tANDAND expr 
+	expr:  expr.'<' expr 
+	expr:  expr.'>' expr 
+	expr:  expr.tLE expr 
+	expr:  expr.tGE expr 
+	expr:  expr.tNE expr 
+	expr:  expr.tEQEQ expr 
+	expr:  expr.'+' expr 
+	expr:  expr.'-' expr 
+	expr:  expr.'*' expr 
+	expr:  expr.'/' expr 
+	expr:  expr.'%' expr 
+	expr:  expr.'|' expr 
+	expr:  expr '|' expr.    (102)
+	expr:  expr.'&' expr 
+	expr:  expr.'^' expr 
+	expr:  expr.tLSH expr 
+	expr:  expr.tRSH expr 
+
+	'*'  shift 60
+	'/'  shift 61
+	'%'  shift 62
+	'&'  shift 64
+	tLSH  shift 66
+	tRSH  shift 67
+	.  reduce 102 (src line 499)
+
+
+state 119
+	expr:  expr.tOROR expr 
+	expr:  expr.tANDAND expr 
+	expr:  expr.'<' expr 
+	expr:  expr.'>' expr 
+	expr:  expr.tLE expr 
+	expr:  expr.tGE expr 
+	expr:  expr.tNE expr 
+	expr:  expr.tEQEQ expr 
+	expr:  expr.'+' expr 
+	expr:  expr.'-' expr 
+	expr:  expr.'*' expr 
+	expr:  expr.'/' expr 
+	expr:  expr.'%' expr 
+	expr:  expr.'|' expr 
+	expr:  expr.'&' expr 
+	expr:  expr '&' expr.    (103)
+	expr:  expr.'^' expr 
+	expr:  expr.tLSH expr 
+	expr:  expr.tRSH expr 
+
+	.  reduce 103 (src line 501)
+
+
+state 120
+	expr:  expr.tOROR expr 
+	expr:  expr.tANDAND expr 
+	expr:  expr.'<' expr 
+	expr:  expr.'>' expr 
+	expr:  expr.tLE expr 
+	expr:  expr.tGE expr 
+	expr:  expr.tNE expr 
+	expr:  expr.tEQEQ expr 
+	expr:  expr.'+' expr 
+	expr:  expr.'-' expr 
+	expr:  expr.'*' expr 
+	expr:  expr.'/' expr 
+	expr:  expr.'%' expr 
+	expr:  expr.'|' expr 
+	expr:  expr.'&' expr 
+	expr:  expr.'^' expr 
+	expr:  expr '^' expr.    (104)
+	expr:  expr.tLSH expr 
+	expr:  expr.tRSH expr 
+
+	'*'  shift 60
+	'/'  shift 61
+	'%'  shift 62
+	'&'  shift 64
+	tLSH  shift 66
+	tRSH  shift 67
+	.  reduce 104 (src line 503)
+
+
+state 121
+	expr:  expr.tOROR expr 
+	expr:  expr.tANDAND expr 
+	expr:  expr.'<' expr 
+	expr:  expr.'>' expr 
+	expr:  expr.tLE expr 
+	expr:  expr.tGE expr 
+	expr:  expr.tNE expr 
+	expr:  expr.tEQEQ expr 
+	expr:  expr.'+' expr 
+	expr:  expr.'-' expr 
+	expr:  expr.'*' expr 
+	expr:  expr.'/' expr 
+	expr:  expr.'%' expr 
+	expr:  expr.'|' expr 
+	expr:  expr.'&' expr 
+	expr:  expr.'^' expr 
+	expr:  expr.tLSH expr 
+	expr:  expr tLSH expr.    (105)
+	expr:  expr.tRSH expr 
+
+	.  reduce 105 (src line 505)
+
+
+state 122
+	expr:  expr.tOROR expr 
+	expr:  expr.tANDAND expr 
+	expr:  expr.'<' expr 
+	expr:  expr.'>' expr 
+	expr:  expr.tLE expr 
+	expr:  expr.tGE expr 
+	expr:  expr.tNE expr 
+	expr:  expr.tEQEQ expr 
+	expr:  expr.'+' expr 
+	expr:  expr.'-' expr 
+	expr:  expr.'*' expr 
+	expr:  expr.'/' expr 
+	expr:  expr.'%' expr 
+	expr:  expr.'|' expr 
+	expr:  expr.'&' expr 
+	expr:  expr.'^' expr 
+	expr:  expr.tLSH expr 
+	expr:  expr.tRSH expr 
+	expr:  expr tRSH expr.    (106)
+
+	.  reduce 106 (src line 507)
+
+
+state 123
+	expr:  expr.tOROR expr 
+	expr:  expr.tANDAND expr 
+	expr:  expr.'<' expr 
+	expr:  expr.'>' expr 
+	expr:  expr.tLE expr 
+	expr:  expr.tGE expr 
+	expr:  expr.tNE expr 
+	expr:  expr.tEQEQ expr 
+	expr:  expr.'+' expr 
+	expr:  expr.'-' expr 
+	expr:  expr.'*' expr 
+	expr:  expr.'/' expr 
+	expr:  expr.'%' expr 
+	expr:  expr.'|' expr 
+	expr:  expr.'&' expr 
+	expr:  expr.'^' expr 
+	expr:  expr.tLSH expr 
+	expr:  expr.tRSH expr 
+	unary_expr:  type_no_typeobject '(' expr.')' 
+
+	')'  shift 161
+	'<'  shift 52
+	'>'  shift 53
+	'+'  shift 58
+	'-'  shift 59
+	'*'  shift 60
+	'/'  shift 61
+	'%'  shift 62
+	'|'  shift 63
+	'&'  shift 64
+	'^'  shift 65
+	tOROR  shift 50
+	tANDAND  shift 51
+	tLE  shift 54
+	tGE  shift 55
+	tNE  shift 56
+	tEQEQ  shift 57
+	tLSH  shift 66
+	tRSH  shift 67
+	.  error
+
+
+state 124
+	unary_expr:  tTYPEOBJECT '(' type.')' 
+
+	')'  shift 162
+	.  error
+
+
+state 125
+	nameref:  tSTRLIT '.' dotnameref.    (141)
+	dotnameref:  dotnameref.'.' tIDENT 
+
+	'.'  shift 91
+	.  reduce 141 (src line 624)
+
+
+state 126
+	expr:  expr.tOROR expr 
+	expr:  expr.tANDAND expr 
+	expr:  expr.'<' expr 
+	expr:  expr.'>' expr 
+	expr:  expr.tLE expr 
+	expr:  expr.tGE expr 
+	expr:  expr.tNE expr 
+	expr:  expr.tEQEQ expr 
+	expr:  expr.'+' expr 
+	expr:  expr.'-' expr 
+	expr:  expr.'*' expr 
+	expr:  expr.'/' expr 
+	expr:  expr.'%' expr 
+	expr:  expr.'|' expr 
+	expr:  expr.'&' expr 
+	expr:  expr.'^' expr 
+	expr:  expr.tLSH expr 
+	expr:  expr.tRSH expr 
+	operand:  nameref '[' expr.']' 
+
+	']'  shift 163
+	'<'  shift 52
+	'>'  shift 53
+	'+'  shift 58
+	'-'  shift 59
+	'*'  shift 60
+	'/'  shift 61
+	'%'  shift 62
+	'|'  shift 63
+	'&'  shift 64
+	'^'  shift 65
+	tOROR  shift 50
+	tANDAND  shift 51
+	tLE  shift 54
+	tGE  shift 55
+	tNE  shift 56
+	tEQEQ  shift 57
+	tLSH  shift 66
+	tRSH  shift 67
+	.  error
+
+
+state 127
+	operand:  comp_lit '.' tIDENT.    (120)
+
+	.  reduce 120 (src line 540)
+
+
+state 128
+	expr:  expr.tOROR expr 
+	expr:  expr.tANDAND expr 
+	expr:  expr.'<' expr 
+	expr:  expr.'>' expr 
+	expr:  expr.tLE expr 
+	expr:  expr.tGE expr 
+	expr:  expr.tNE expr 
+	expr:  expr.tEQEQ expr 
+	expr:  expr.'+' expr 
+	expr:  expr.'-' expr 
+	expr:  expr.'*' expr 
+	expr:  expr.'/' expr 
+	expr:  expr.'%' expr 
+	expr:  expr.'|' expr 
+	expr:  expr.'&' expr 
+	expr:  expr.'^' expr 
+	expr:  expr.tLSH expr 
+	expr:  expr.tRSH expr 
+	operand:  comp_lit '[' expr.']' 
+
+	']'  shift 164
+	'<'  shift 52
+	'>'  shift 53
+	'+'  shift 58
+	'-'  shift 59
+	'*'  shift 60
+	'/'  shift 61
+	'%'  shift 62
+	'|'  shift 63
+	'&'  shift 64
+	'^'  shift 65
+	tOROR  shift 50
+	tANDAND  shift 51
+	tLE  shift 54
+	tGE  shift 55
+	tNE  shift 56
+	tEQEQ  shift 57
+	tLSH  shift 66
+	tRSH  shift 67
+	.  error
+
+
+state 129
+	operand:  '(' expr ')'.    (123)
+
+	.  reduce 123 (src line 546)
+
+
+state 130
+	type_no_typeobject:  '[' tINTLIT ']'.type 
+
+	'['  shift 31
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 88
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 90
+	.  error
+
+	nameref  goto 89
+	dotnameref  goto 38
+	type  goto 165
+	type_no_typeobject  goto 87
+
+state 131
+	type_no_typeobject:  '[' ']' type.    (43)
+
+	.  reduce 43 (src line 267)
+
+
+state 132
+	type_no_typeobject:  tENUM '{' label_spec_list.osemi '}' 
+	label_spec_list:  label_spec_list.';' label_spec 
+	osemi: .    (146)
+
+	';'  shift 167
+	.  reduce 146 (src line 640)
+
+	osemi  goto 166
+
+state 133
+	label_spec_list:  label_spec.    (54)
+
+	.  reduce 54 (src line 293)
+
+
+state 134
+	label_spec:  tIDENT.    (56)
+
+	.  reduce 56 (src line 299)
+
+
+state 135
+	type_no_typeobject:  tSET '[' type.']' 
+
+	']'  shift 168
+	.  error
+
+
+state 136
+	type_no_typeobject:  tMAP '[' type.']' type 
+
+	']'  shift 169
+	.  error
+
+
+state 137
+	type_no_typeobject:  tSTRUCT '{' field_spec_list.osemi '}' 
+	field_spec_list:  field_spec_list.';' field_spec 
+	osemi: .    (146)
+
+	';'  shift 171
+	.  reduce 146 (src line 640)
+
+	osemi  goto 170
+
+state 138
+	type_no_typeobject:  tSTRUCT '{' '}'.    (48)
+
+	.  reduce 48 (src line 277)
+
+
+state 139
+	field_spec_list:  field_spec.    (57)
+
+	.  reduce 57 (src line 303)
+
+
+state 140
+	field_spec:  type_comma_list.type 
+	type_comma_list:  type_comma_list.',' type 
+
+	','  shift 173
+	'['  shift 31
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 88
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 90
+	.  error
+
+	nameref  goto 89
+	dotnameref  goto 38
+	type  goto 172
+	type_no_typeobject  goto 87
+
+state 141
+	type_comma_list:  type.    (60)
+
+	.  reduce 60 (src line 355)
+
+
+state 142
+	type_no_typeobject:  tUNION '{' field_spec_list.osemi '}' 
+	field_spec_list:  field_spec_list.';' field_spec 
+	osemi: .    (146)
+
+	';'  shift 171
+	.  reduce 146 (src line 640)
+
+	osemi  goto 174
+
+state 143
+	type_no_typeobject:  tUNION '{' '}'.    (50)
+
+	.  reduce 50 (src line 281)
+
+
+state 144
+	dotnameref:  dotnameref '.' tIDENT.    (143)
+
+	.  reduce 143 (src line 631)
+
+
+state 145
+	comp_lit:  otype '{' '}'.    (124)
+
+	.  reduce 124 (src line 549)
+
+
+state 146
+	comp_lit:  otype '{' kv_lit_list.ocomma '}' 
+	kv_lit_list:  kv_lit_list.',' kv_lit 
+	ocomma: .    (148)
+
+	','  shift 176
+	.  reduce 148 (src line 644)
+
+	ocomma  goto 175
+
+state 147
+	kv_lit_list:  kv_lit.    (126)
+
+	.  reduce 126 (src line 555)
+
+
+state 148
+	expr:  expr.tOROR expr 
+	expr:  expr.tANDAND expr 
+	expr:  expr.'<' expr 
+	expr:  expr.'>' expr 
+	expr:  expr.tLE expr 
+	expr:  expr.tGE expr 
+	expr:  expr.tNE expr 
+	expr:  expr.tEQEQ expr 
+	expr:  expr.'+' expr 
+	expr:  expr.'-' expr 
+	expr:  expr.'*' expr 
+	expr:  expr.'/' expr 
+	expr:  expr.'%' expr 
+	expr:  expr.'|' expr 
+	expr:  expr.'&' expr 
+	expr:  expr.'^' expr 
+	expr:  expr.tLSH expr 
+	expr:  expr.tRSH expr 
+	kv_lit:  expr.    (128)
+	kv_lit:  expr.':' expr 
+
+	':'  shift 177
+	'<'  shift 52
+	'>'  shift 53
+	'+'  shift 58
+	'-'  shift 59
+	'*'  shift 60
+	'/'  shift 61
+	'%'  shift 62
+	'|'  shift 63
+	'&'  shift 64
+	'^'  shift 65
+	tOROR  shift 50
+	tANDAND  shift 51
+	tLE  shift 54
+	tGE  shift 55
+	tNE  shift 56
+	tEQEQ  shift 57
+	tLSH  shift 66
+	tRSH  shift 67
+	.  reduce 128 (src line 561)
+
+
+state 149
+	imports:  imports import ';'.    (15)
+
+	.  reduce 15 (src line 187)
+
+
+state 150
+	import:  tIMPORT '('.')' 
+	import:  tIMPORT '('.import_spec_list osemi ')' 
+
+	')'  shift 178
+	tIDENT  shift 153
+	tSTRLIT  shift 152
+	.  error
+
+	import_spec_list  goto 179
+	import_spec  goto 180
+
+state 151
+	import:  tIMPORT import_spec.    (18)
+
+	.  reduce 18 (src line 192)
+
+
+state 152
+	import_spec:  tSTRLIT.    (21)
+
+	.  reduce 21 (src line 198)
+
+
+state 153
+	import_spec:  tIDENT.tSTRLIT 
+
+	tSTRLIT  shift 181
+	.  error
+
+
+state 154
+	defs:  defs type_def.';' 
+
+	';'  shift 182
+	.  error
+
+
+state 155
+	defs:  defs const_def.';' 
+
+	';'  shift 183
+	.  error
+
+
+state 156
+	defs:  defs error_def.';' 
+
+	';'  shift 184
+	.  error
+
+
+state 157
+	type_def:  tTYPE.'(' ')' 
+	type_def:  tTYPE.'(' type_spec_list osemi ')' 
+	type_def:  tTYPE.type_spec 
+	type_def:  tTYPE.interface_spec 
+
+	'('  shift 185
+	tIDENT  shift 188
+	.  error
+
+	type_spec  goto 186
+	interface_spec  goto 187
+
+state 158
+	const_def:  tCONST.'(' ')' 
+	const_def:  tCONST.'(' const_spec_list osemi ')' 
+	const_def:  tCONST.const_spec 
+
+	'('  shift 189
+	tIDENT  shift 191
+	.  error
+
+	const_spec  goto 190
+
+state 159
+	error_def:  tERROR.'(' ')' 
+	error_def:  tERROR.'(' error_spec_list osemi ')' 
+	error_def:  tERROR.error_spec 
+
+	'('  shift 192
+	tIDENT  shift 194
+	.  error
+
+	error_spec  goto 193
+
+state 160
+	config:  tIDENT '=' expr ';'.    (13)
+
+	.  reduce 13 (src line 171)
+
+
+state 161
+	unary_expr:  type_no_typeobject '(' expr ')'.    (112)
+
+	.  reduce 112 (src line 521)
+
+
+state 162
+	unary_expr:  tTYPEOBJECT '(' type ')'.    (113)
+
+	.  reduce 113 (src line 523)
+
+
+state 163
+	operand:  nameref '[' expr ']'.    (122)
+
+	.  reduce 122 (src line 544)
+
+
+state 164
+	operand:  comp_lit '[' expr ']'.    (121)
+
+	.  reduce 121 (src line 542)
+
+
+state 165
+	type_no_typeobject:  '[' tINTLIT ']' type.    (42)
+
+	.  reduce 42 (src line 265)
+
+
+state 166
+	type_no_typeobject:  tENUM '{' label_spec_list osemi.'}' 
+
+	'}'  shift 195
+	.  error
+
+
+state 167
+	label_spec_list:  label_spec_list ';'.label_spec 
+	osemi:  ';'.    (147)
+
+	tIDENT  shift 134
+	.  reduce 147 (src line 642)
+
+	label_spec  goto 196
+
+state 168
+	type_no_typeobject:  tSET '[' type ']'.    (45)
+
+	.  reduce 45 (src line 271)
+
+
+state 169
+	type_no_typeobject:  tMAP '[' type ']'.type 
+
+	'['  shift 31
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 88
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 90
+	.  error
+
+	nameref  goto 89
+	dotnameref  goto 38
+	type  goto 197
+	type_no_typeobject  goto 87
+
+state 170
+	type_no_typeobject:  tSTRUCT '{' field_spec_list osemi.'}' 
+
+	'}'  shift 198
+	.  error
+
+
+state 171
+	field_spec_list:  field_spec_list ';'.field_spec 
+	osemi:  ';'.    (147)
+
+	'['  shift 31
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 88
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 90
+	.  reduce 147 (src line 642)
+
+	nameref  goto 89
+	dotnameref  goto 38
+	type  goto 141
+	type_no_typeobject  goto 87
+	type_comma_list  goto 140
+	field_spec  goto 199
+
+state 172
+	field_spec:  type_comma_list type.    (59)
+
+	.  reduce 59 (src line 343)
+
+
+state 173
+	type_comma_list:  type_comma_list ','.type 
+
+	'['  shift 31
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 88
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 90
+	.  error
+
+	nameref  goto 89
+	dotnameref  goto 38
+	type  goto 200
+	type_no_typeobject  goto 87
+
+state 174
+	type_no_typeobject:  tUNION '{' field_spec_list osemi.'}' 
+
+	'}'  shift 201
+	.  error
+
+
+state 175
+	comp_lit:  otype '{' kv_lit_list ocomma.'}' 
+
+	'}'  shift 202
+	.  error
+
+
+state 176
+	kv_lit_list:  kv_lit_list ','.kv_lit 
+	ocomma:  ','.    (149)
+	otype: .    (144)
+
+	'('  shift 29
+	'['  shift 31
+	'}'  reduce 149 (src line 646)
+	'!'  shift 17
+	'+'  shift 18
+	'-'  shift 19
+	'^'  shift 20
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 22
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 23
+	tINTLIT  shift 24
+	tRATLIT  shift 25
+	tIMAGLIT  shift 26
+	.  reduce 144 (src line 634)
+
+	nameref  goto 27
+	dotnameref  goto 38
+	type  goto 41
+	type_no_typeobject  goto 21
+	otype  goto 39
+	expr  goto 148
+	unary_expr  goto 15
+	operand  goto 16
+	comp_lit  goto 28
+	kv_lit  goto 203
+
+state 177
+	kv_lit:  expr ':'.expr 
+	otype: .    (144)
+
+	'('  shift 29
+	'['  shift 31
+	'!'  shift 17
+	'+'  shift 18
+	'-'  shift 19
+	'^'  shift 20
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 22
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 23
+	tINTLIT  shift 24
+	tRATLIT  shift 25
+	tIMAGLIT  shift 26
+	.  reduce 144 (src line 634)
+
+	nameref  goto 27
+	dotnameref  goto 38
+	type  goto 41
+	type_no_typeobject  goto 21
+	otype  goto 39
+	expr  goto 204
+	unary_expr  goto 15
+	operand  goto 16
+	comp_lit  goto 28
+
+state 178
+	import:  tIMPORT '(' ')'.    (16)
+
+	.  reduce 16 (src line 189)
+
+
+state 179
+	import:  tIMPORT '(' import_spec_list.osemi ')' 
+	import_spec_list:  import_spec_list.';' import_spec 
+	osemi: .    (146)
+
+	';'  shift 206
+	.  reduce 146 (src line 640)
+
+	osemi  goto 205
+
+state 180
+	import_spec_list:  import_spec.    (19)
+
+	.  reduce 19 (src line 194)
+
+
+state 181
+	import_spec:  tIDENT tSTRLIT.    (22)
+
+	.  reduce 22 (src line 204)
+
+
+state 182
+	defs:  defs type_def ';'.    (24)
+
+	.  reduce 24 (src line 213)
+
+
+state 183
+	defs:  defs const_def ';'.    (25)
+
+	.  reduce 25 (src line 214)
+
+
+state 184
+	defs:  defs error_def ';'.    (26)
+
+	.  reduce 26 (src line 215)
+
+
+state 185
+	type_def:  tTYPE '('.')' 
+	type_def:  tTYPE '('.type_spec_list osemi ')' 
+
+	')'  shift 207
+	tIDENT  shift 210
+	.  error
+
+	type_spec_list  goto 208
+	type_spec  goto 209
+
+state 186
+	type_def:  tTYPE type_spec.    (29)
+
+	.  reduce 29 (src line 220)
+
+
+state 187
+	type_def:  tTYPE interface_spec.    (30)
+
+	.  reduce 30 (src line 221)
+
+
+state 188
+	type_spec:  tIDENT.type 
+	interface_spec:  tIDENT.tINTERFACE '{' '}' 
+	interface_spec:  tIDENT.tINTERFACE '{' iface_item_list osemi '}' 
+
+	'['  shift 31
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tINTERFACE  shift 212
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 88
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 90
+	.  error
+
+	nameref  goto 89
+	dotnameref  goto 38
+	type  goto 211
+	type_no_typeobject  goto 87
+
+state 189
+	const_def:  tCONST '('.')' 
+	const_def:  tCONST '('.const_spec_list osemi ')' 
+
+	')'  shift 213
+	tIDENT  shift 191
+	.  error
+
+	const_spec_list  goto 214
+	const_spec  goto 215
+
+state 190
+	const_def:  tCONST const_spec.    (33)
+
+	.  reduce 33 (src line 226)
+
+
+state 191
+	const_spec:  tIDENT.'=' expr 
+
+	'='  shift 216
+	.  error
+
+
+state 192
+	error_def:  tERROR '('.')' 
+	error_def:  tERROR '('.error_spec_list osemi ')' 
+
+	')'  shift 217
+	tIDENT  shift 194
+	.  error
+
+	error_spec_list  goto 218
+	error_spec  goto 219
+
+state 193
+	error_def:  tERROR error_spec.    (36)
+
+	.  reduce 36 (src line 231)
+
+
+state 194
+	error_spec:  tIDENT.inargs error_details 
+
+	'('  shift 221
+	.  error
+
+	inargs  goto 220
+
+state 195
+	type_no_typeobject:  tENUM '{' label_spec_list osemi '}'.    (44)
+
+	.  reduce 44 (src line 269)
+
+
+state 196
+	label_spec_list:  label_spec_list ';' label_spec.    (55)
+
+	.  reduce 55 (src line 296)
+
+
+state 197
+	type_no_typeobject:  tMAP '[' type ']' type.    (46)
+
+	.  reduce 46 (src line 273)
+
+
+state 198
+	type_no_typeobject:  tSTRUCT '{' field_spec_list osemi '}'.    (47)
+
+	.  reduce 47 (src line 275)
+
+
+state 199
+	field_spec_list:  field_spec_list ';' field_spec.    (58)
+
+	.  reduce 58 (src line 306)
+
+
+state 200
+	type_comma_list:  type_comma_list ',' type.    (61)
+
+	.  reduce 61 (src line 358)
+
+
+state 201
+	type_no_typeobject:  tUNION '{' field_spec_list osemi '}'.    (49)
+
+	.  reduce 49 (src line 279)
+
+
+state 202
+	comp_lit:  otype '{' kv_lit_list ocomma '}'.    (125)
+
+	.  reduce 125 (src line 552)
+
+
+state 203
+	kv_lit_list:  kv_lit_list ',' kv_lit.    (127)
+
+	.  reduce 127 (src line 558)
+
+
+state 204
+	expr:  expr.tOROR expr 
+	expr:  expr.tANDAND expr 
+	expr:  expr.'<' expr 
+	expr:  expr.'>' expr 
+	expr:  expr.tLE expr 
+	expr:  expr.tGE expr 
+	expr:  expr.tNE expr 
+	expr:  expr.tEQEQ expr 
+	expr:  expr.'+' expr 
+	expr:  expr.'-' expr 
+	expr:  expr.'*' expr 
+	expr:  expr.'/' expr 
+	expr:  expr.'%' expr 
+	expr:  expr.'|' expr 
+	expr:  expr.'&' expr 
+	expr:  expr.'^' expr 
+	expr:  expr.tLSH expr 
+	expr:  expr.tRSH expr 
+	kv_lit:  expr ':' expr.    (129)
+
+	'<'  shift 52
+	'>'  shift 53
+	'+'  shift 58
+	'-'  shift 59
+	'*'  shift 60
+	'/'  shift 61
+	'%'  shift 62
+	'|'  shift 63
+	'&'  shift 64
+	'^'  shift 65
+	tOROR  shift 50
+	tANDAND  shift 51
+	tLE  shift 54
+	tGE  shift 55
+	tNE  shift 56
+	tEQEQ  shift 57
+	tLSH  shift 66
+	tRSH  shift 67
+	.  reduce 129 (src line 564)
+
+
+state 205
+	import:  tIMPORT '(' import_spec_list osemi.')' 
+
+	')'  shift 222
+	.  error
+
+
+state 206
+	import_spec_list:  import_spec_list ';'.import_spec 
+	osemi:  ';'.    (147)
+
+	tIDENT  shift 153
+	tSTRLIT  shift 152
+	.  reduce 147 (src line 642)
+
+	import_spec  goto 223
+
+state 207
+	type_def:  tTYPE '(' ')'.    (27)
+
+	.  reduce 27 (src line 217)
+
+
+state 208
+	type_def:  tTYPE '(' type_spec_list.osemi ')' 
+	type_spec_list:  type_spec_list.';' type_spec 
+	osemi: .    (146)
+
+	';'  shift 225
+	.  reduce 146 (src line 640)
+
+	osemi  goto 224
+
+state 209
+	type_spec_list:  type_spec.    (37)
+
+	.  reduce 37 (src line 234)
+
+
+state 210
+	type_spec:  tIDENT.type 
+
+	'['  shift 31
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 88
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 90
+	.  error
+
+	nameref  goto 89
+	dotnameref  goto 38
+	type  goto 211
+	type_no_typeobject  goto 87
+
+state 211
+	type_spec:  tIDENT type.    (39)
+
+	.  reduce 39 (src line 238)
+
+
+state 212
+	interface_spec:  tIDENT tINTERFACE.'{' '}' 
+	interface_spec:  tIDENT tINTERFACE.'{' iface_item_list osemi '}' 
+
+	'{'  shift 226
+	.  error
+
+
+state 213
+	const_def:  tCONST '(' ')'.    (31)
+
+	.  reduce 31 (src line 223)
+
+
+state 214
+	const_def:  tCONST '(' const_spec_list.osemi ')' 
+	const_spec_list:  const_spec_list.';' const_spec 
+	osemi: .    (146)
+
+	';'  shift 228
+	.  reduce 146 (src line 640)
+
+	osemi  goto 227
+
+state 215
+	const_spec_list:  const_spec.    (85)
+
+	.  reduce 85 (src line 459)
+
+
+state 216
+	const_spec:  tIDENT '='.expr 
+	otype: .    (144)
+
+	'('  shift 29
+	'['  shift 31
+	'!'  shift 17
+	'+'  shift 18
+	'-'  shift 19
+	'^'  shift 20
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 22
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 23
+	tINTLIT  shift 24
+	tRATLIT  shift 25
+	tIMAGLIT  shift 26
+	.  reduce 144 (src line 634)
+
+	nameref  goto 27
+	dotnameref  goto 38
+	type  goto 41
+	type_no_typeobject  goto 21
+	otype  goto 39
+	expr  goto 229
+	unary_expr  goto 15
+	operand  goto 16
+	comp_lit  goto 28
+
+state 217
+	error_def:  tERROR '(' ')'.    (34)
+
+	.  reduce 34 (src line 228)
+
+
+state 218
+	error_def:  tERROR '(' error_spec_list.osemi ')' 
+	error_spec_list:  error_spec_list.';' error_spec 
+	osemi: .    (146)
+
+	';'  shift 231
+	.  reduce 146 (src line 640)
+
+	osemi  goto 230
+
+state 219
+	error_spec_list:  error_spec.    (130)
+
+	.  reduce 130 (src line 568)
+
+
+state 220
+	error_spec:  tIDENT inargs.error_details 
+	error_details: .    (133)
+
+	'{'  shift 233
+	.  reduce 133 (src line 584)
+
+	error_details  goto 232
+
+state 221
+	inargs:  '('.')' 
+	inargs:  '('.named_arg_list ocomma ')' 
+	inargs:  '('.type_comma_list ocomma ')' 
+
+	')'  shift 234
+	'['  shift 31
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 88
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 90
+	.  error
+
+	nameref  goto 89
+	dotnameref  goto 38
+	type  goto 141
+	type_no_typeobject  goto 87
+	type_comma_list  goto 236
+	field_spec  goto 237
+	named_arg_list  goto 235
+
+state 222
+	import:  tIMPORT '(' import_spec_list osemi ')'.    (17)
+
+	.  reduce 17 (src line 191)
+
+
+state 223
+	import_spec_list:  import_spec_list ';' import_spec.    (20)
+
+	.  reduce 20 (src line 196)
+
+
+state 224
+	type_def:  tTYPE '(' type_spec_list osemi.')' 
+
+	')'  shift 238
+	.  error
+
+
+state 225
+	type_spec_list:  type_spec_list ';'.type_spec 
+	osemi:  ';'.    (147)
+
+	tIDENT  shift 210
+	.  reduce 147 (src line 642)
+
+	type_spec  goto 239
+
+state 226
+	interface_spec:  tIDENT tINTERFACE '{'.'}' 
+	interface_spec:  tIDENT tINTERFACE '{'.iface_item_list osemi '}' 
+
+	'}'  shift 240
+	tIDENT  shift 243
+	tSTRLIT  shift 90
+	.  error
+
+	nameref  goto 244
+	dotnameref  goto 38
+	iface_item_list  goto 241
+	iface_item  goto 242
+
+state 227
+	const_def:  tCONST '(' const_spec_list osemi.')' 
+
+	')'  shift 245
+	.  error
+
+
+state 228
+	const_spec_list:  const_spec_list ';'.const_spec 
+	osemi:  ';'.    (147)
+
+	tIDENT  shift 191
+	.  reduce 147 (src line 642)
+
+	const_spec  goto 246
+
+state 229
+	const_spec:  tIDENT '=' expr.    (87)
+	expr:  expr.tOROR expr 
+	expr:  expr.tANDAND expr 
+	expr:  expr.'<' expr 
+	expr:  expr.'>' expr 
+	expr:  expr.tLE expr 
+	expr:  expr.tGE expr 
+	expr:  expr.tNE expr 
+	expr:  expr.tEQEQ expr 
+	expr:  expr.'+' expr 
+	expr:  expr.'-' expr 
+	expr:  expr.'*' expr 
+	expr:  expr.'/' expr 
+	expr:  expr.'%' expr 
+	expr:  expr.'|' expr 
+	expr:  expr.'&' expr 
+	expr:  expr.'^' expr 
+	expr:  expr.tLSH expr 
+	expr:  expr.tRSH expr 
+
+	'<'  shift 52
+	'>'  shift 53
+	'+'  shift 58
+	'-'  shift 59
+	'*'  shift 60
+	'/'  shift 61
+	'%'  shift 62
+	'|'  shift 63
+	'&'  shift 64
+	'^'  shift 65
+	tOROR  shift 50
+	tANDAND  shift 51
+	tLE  shift 54
+	tGE  shift 55
+	tNE  shift 56
+	tEQEQ  shift 57
+	tLSH  shift 66
+	tRSH  shift 67
+	.  reduce 87 (src line 463)
+
+
+state 230
+	error_def:  tERROR '(' error_spec_list osemi.')' 
+
+	')'  shift 247
+	.  error
+
+
+state 231
+	error_spec_list:  error_spec_list ';'.error_spec 
+	osemi:  ';'.    (147)
+
+	tIDENT  shift 194
+	.  reduce 147 (src line 642)
+
+	error_spec  goto 248
+
+state 232
+	error_spec:  tIDENT inargs error_details.    (132)
+
+	.  reduce 132 (src line 572)
+
+
+state 233
+	error_details:  '{'.'}' 
+	error_details:  '{'.error_detail_list ocomma '}' 
+
+	'}'  shift 249
+	tIDENT  shift 252
+	tSTRLIT  shift 253
+	.  error
+
+	error_detail_list  goto 250
+	error_detail  goto 251
+
+state 234
+	inargs:  '(' ')'.    (68)
+
+	.  reduce 68 (src line 391)
+
+
+state 235
+	inargs:  '(' named_arg_list.ocomma ')' 
+	named_arg_list:  named_arg_list.',' field_spec 
+	ocomma: .    (148)
+
+	','  shift 255
+	.  reduce 148 (src line 644)
+
+	ocomma  goto 254
+
+state 236
+	field_spec:  type_comma_list.type 
+	type_comma_list:  type_comma_list.',' type 
+	inargs:  '(' type_comma_list.ocomma ')' 
+	ocomma: .    (148)
+
+	','  shift 256
+	'['  shift 31
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 88
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 90
+	.  reduce 148 (src line 644)
+
+	nameref  goto 89
+	dotnameref  goto 38
+	type  goto 172
+	type_no_typeobject  goto 87
+	ocomma  goto 257
+
+state 237
+	named_arg_list:  field_spec.    (71)
+
+	.  reduce 71 (src line 407)
+
+
+state 238
+	type_def:  tTYPE '(' type_spec_list osemi ')'.    (28)
+
+	.  reduce 28 (src line 219)
+
+
+state 239
+	type_spec_list:  type_spec_list ';' type_spec.    (38)
+
+	.  reduce 38 (src line 236)
+
+
+state 240
+	interface_spec:  tIDENT tINTERFACE '{' '}'.    (62)
+
+	.  reduce 62 (src line 362)
+
+
+state 241
+	interface_spec:  tIDENT tINTERFACE '{' iface_item_list.osemi '}' 
+	iface_item_list:  iface_item_list.';' iface_item 
+	osemi: .    (146)
+
+	';'  shift 259
+	.  reduce 146 (src line 640)
+
+	osemi  goto 258
+
+state 242
+	iface_item_list:  iface_item.    (64)
+
+	.  reduce 64 (src line 375)
+
+
+state 243
+	iface_item:  tIDENT.inargs streamargs outargs tags 
+	dotnameref:  tIDENT.    (142)
+
+	'('  shift 221
+	.  reduce 142 (src line 628)
+
+	inargs  goto 260
+
+state 244
+	iface_item:  nameref.    (67)
+
+	.  reduce 67 (src line 388)
+
+
+state 245
+	const_def:  tCONST '(' const_spec_list osemi ')'.    (32)
+
+	.  reduce 32 (src line 225)
+
+
+state 246
+	const_spec_list:  const_spec_list ';' const_spec.    (86)
+
+	.  reduce 86 (src line 461)
+
+
+state 247
+	error_def:  tERROR '(' error_spec_list osemi ')'.    (35)
+
+	.  reduce 35 (src line 230)
+
+
+state 248
+	error_spec_list:  error_spec_list ';' error_spec.    (131)
+
+	.  reduce 131 (src line 570)
+
+
+state 249
+	error_details:  '{' '}'.    (134)
+
+	.  reduce 134 (src line 587)
+
+
+state 250
+	error_details:  '{' error_detail_list.ocomma '}' 
+	error_detail_list:  error_detail_list.',' error_detail 
+	ocomma: .    (148)
+
+	','  shift 262
+	.  reduce 148 (src line 644)
+
+	ocomma  goto 261
+
+state 251
+	error_detail_list:  error_detail.    (136)
+
+	.  reduce 136 (src line 592)
+
+
+state 252
+	error_detail:  tIDENT.    (138)
+
+	.  reduce 138 (src line 607)
+
+
+state 253
+	error_detail:  tSTRLIT.':' tSTRLIT 
+
+	':'  shift 263
+	.  error
+
+
+state 254
+	inargs:  '(' named_arg_list ocomma.')' 
+
+	')'  shift 264
+	.  error
+
+
+state 255
+	named_arg_list:  named_arg_list ','.field_spec 
+	ocomma:  ','.    (149)
+
+	'['  shift 31
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 88
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 90
+	.  reduce 149 (src line 646)
+
+	nameref  goto 89
+	dotnameref  goto 38
+	type  goto 141
+	type_no_typeobject  goto 87
+	type_comma_list  goto 140
+	field_spec  goto 265
+
+state 256
+	type_comma_list:  type_comma_list ','.type 
+	ocomma:  ','.    (149)
+
+	'['  shift 31
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 88
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 90
+	.  reduce 149 (src line 646)
+
+	nameref  goto 89
+	dotnameref  goto 38
+	type  goto 200
+	type_no_typeobject  goto 87
+
+state 257
+	inargs:  '(' type_comma_list ocomma.')' 
+
+	')'  shift 266
+	.  error
+
+
+state 258
+	interface_spec:  tIDENT tINTERFACE '{' iface_item_list osemi.'}' 
+
+	'}'  shift 267
+	.  error
+
+
+state 259
+	iface_item_list:  iface_item_list ';'.iface_item 
+	osemi:  ';'.    (147)
+
+	tIDENT  shift 243
+	tSTRLIT  shift 90
+	.  reduce 147 (src line 642)
+
+	nameref  goto 244
+	dotnameref  goto 38
+	iface_item  goto 268
+
+state 260
+	iface_item:  tIDENT inargs.streamargs outargs tags 
+	streamargs: .    (76)
+
+	tSTREAM  shift 270
+	.  reduce 76 (src line 434)
+
+	streamargs  goto 269
+
+state 261
+	error_details:  '{' error_detail_list ocomma.'}' 
+
+	'}'  shift 271
+	.  error
+
+
+state 262
+	error_detail_list:  error_detail_list ','.error_detail 
+	ocomma:  ','.    (149)
+
+	tIDENT  shift 252
+	tSTRLIT  shift 253
+	.  reduce 149 (src line 646)
+
+	error_detail  goto 272
+
+state 263
+	error_detail:  tSTRLIT ':'.tSTRLIT 
+
+	tSTRLIT  shift 273
+	.  error
+
+
+state 264
+	inargs:  '(' named_arg_list ocomma ')'.    (69)
+
+	.  reduce 69 (src line 394)
+
+
+state 265
+	named_arg_list:  named_arg_list ',' field_spec.    (72)
+
+	.  reduce 72 (src line 410)
+
+
+state 266
+	inargs:  '(' type_comma_list ocomma ')'.    (70)
+
+	.  reduce 70 (src line 396)
+
+
+state 267
+	interface_spec:  tIDENT tINTERFACE '{' iface_item_list osemi '}'.    (63)
+
+	.  reduce 63 (src line 368)
+
+
+state 268
+	iface_item_list:  iface_item_list ';' iface_item.    (65)
+
+	.  reduce 65 (src line 378)
+
+
+state 269
+	iface_item:  tIDENT inargs streamargs.outargs tags 
+
+	'('  shift 276
+	tERROR  shift 275
+	.  error
+
+	outargs  goto 274
+
+state 270
+	streamargs:  tSTREAM.'<' '>' 
+	streamargs:  tSTREAM.'<' type '>' 
+	streamargs:  tSTREAM.'<' type ',' type '>' 
+
+	'<'  shift 277
+	.  error
+
+
+state 271
+	error_details:  '{' error_detail_list ocomma '}'.    (135)
+
+	.  reduce 135 (src line 589)
+
+
+state 272
+	error_detail_list:  error_detail_list ',' error_detail.    (137)
+
+	.  reduce 137 (src line 595)
+
+
+state 273
+	error_detail:  tSTRLIT ':' tSTRLIT.    (139)
+
+	.  reduce 139 (src line 610)
+
+
+state 274
+	iface_item:  tIDENT inargs streamargs outargs.tags 
+	tags: .    (80)
+
+	'{'  shift 279
+	.  reduce 80 (src line 444)
+
+	tags  goto 278
+
+state 275
+	outargs:  tERROR.    (73)
+
+	.  reduce 73 (src line 420)
+
+
+state 276
+	outargs:  '('.named_arg_list ocomma '|' tERROR ')' 
+	outargs:  '('.type_comma_list ocomma '|' tERROR ')' 
+
+	'['  shift 31
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 88
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 90
+	.  error
+
+	nameref  goto 89
+	dotnameref  goto 38
+	type  goto 141
+	type_no_typeobject  goto 87
+	type_comma_list  goto 281
+	field_spec  goto 237
+	named_arg_list  goto 280
+
+state 277
+	streamargs:  tSTREAM '<'.'>' 
+	streamargs:  tSTREAM '<'.type '>' 
+	streamargs:  tSTREAM '<'.type ',' type '>' 
+
+	'['  shift 31
+	'>'  shift 282
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 88
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 90
+	.  error
+
+	nameref  goto 89
+	dotnameref  goto 38
+	type  goto 283
+	type_no_typeobject  goto 87
+
+state 278
+	iface_item:  tIDENT inargs streamargs outargs tags.    (66)
+
+	.  reduce 66 (src line 385)
+
+
+state 279
+	tags:  '{'.'}' 
+	tags:  '{'.expr_comma_list ocomma '}' 
+	otype: .    (144)
+
+	'('  shift 29
+	'['  shift 31
+	'}'  shift 284
+	'!'  shift 17
+	'+'  shift 18
+	'-'  shift 19
+	'^'  shift 20
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 22
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 23
+	tINTLIT  shift 24
+	tRATLIT  shift 25
+	tIMAGLIT  shift 26
+	.  reduce 144 (src line 634)
+
+	nameref  goto 27
+	dotnameref  goto 38
+	type  goto 41
+	type_no_typeobject  goto 21
+	otype  goto 39
+	expr  goto 14
+	unary_expr  goto 15
+	operand  goto 16
+	expr_comma_list  goto 285
+	comp_lit  goto 28
+
+state 280
+	named_arg_list:  named_arg_list.',' field_spec 
+	outargs:  '(' named_arg_list.ocomma '|' tERROR ')' 
+	ocomma: .    (148)
+
+	','  shift 255
+	.  reduce 148 (src line 644)
+
+	ocomma  goto 286
+
+state 281
+	field_spec:  type_comma_list.type 
+	type_comma_list:  type_comma_list.',' type 
+	outargs:  '(' type_comma_list.ocomma '|' tERROR ')' 
+	ocomma: .    (148)
+
+	','  shift 256
+	'['  shift 31
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 88
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 90
+	.  reduce 148 (src line 644)
+
+	nameref  goto 89
+	dotnameref  goto 38
+	type  goto 172
+	type_no_typeobject  goto 87
+	ocomma  goto 287
+
+state 282
+	streamargs:  tSTREAM '<' '>'.    (77)
+
+	.  reduce 77 (src line 437)
+
+
+state 283
+	streamargs:  tSTREAM '<' type.'>' 
+	streamargs:  tSTREAM '<' type.',' type '>' 
+
+	','  shift 289
+	'>'  shift 288
+	.  error
+
+
+state 284
+	tags:  '{' '}'.    (81)
+
+	.  reduce 81 (src line 447)
+
+
+state 285
+	tags:  '{' expr_comma_list.ocomma '}' 
+	expr_comma_list:  expr_comma_list.',' expr 
+	ocomma: .    (148)
+
+	','  shift 291
+	.  reduce 148 (src line 644)
+
+	ocomma  goto 290
+
+state 286
+	outargs:  '(' named_arg_list ocomma.'|' tERROR ')' 
+
+	'|'  shift 292
+	.  error
+
+
+state 287
+	outargs:  '(' type_comma_list ocomma.'|' tERROR ')' 
+
+	'|'  shift 293
+	.  error
+
+
+state 288
+	streamargs:  tSTREAM '<' type '>'.    (78)
+
+	.  reduce 78 (src line 439)
+
+
+state 289
+	streamargs:  tSTREAM '<' type ','.type '>' 
+
+	'['  shift 31
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 88
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 90
+	.  error
+
+	nameref  goto 89
+	dotnameref  goto 38
+	type  goto 294
+	type_no_typeobject  goto 87
+
+state 290
+	tags:  '{' expr_comma_list ocomma.'}' 
+
+	'}'  shift 295
+	.  error
+
+
+state 291
+	expr_comma_list:  expr_comma_list ','.expr 
+	ocomma:  ','.    (149)
+	otype: .    (144)
+
+	'('  shift 29
+	'['  shift 31
+	'}'  reduce 149 (src line 646)
+	'!'  shift 17
+	'+'  shift 18
+	'-'  shift 19
+	'^'  shift 20
+	'?'  shift 37
+	tENUM  shift 32
+	tERROR  shift 30
+	tMAP  shift 34
+	tSET  shift 33
+	tSTRUCT  shift 35
+	tTYPEOBJECT  shift 22
+	tUNION  shift 36
+	tIDENT  shift 40
+	tSTRLIT  shift 23
+	tINTLIT  shift 24
+	tRATLIT  shift 25
+	tIMAGLIT  shift 26
+	.  reduce 144 (src line 634)
+
+	nameref  goto 27
+	dotnameref  goto 38
+	type  goto 41
+	type_no_typeobject  goto 21
+	otype  goto 39
+	expr  goto 104
+	unary_expr  goto 15
+	operand  goto 16
+	comp_lit  goto 28
+
+state 292
+	outargs:  '(' named_arg_list ocomma '|'.tERROR ')' 
+
+	tERROR  shift 296
+	.  error
+
+
+state 293
+	outargs:  '(' type_comma_list ocomma '|'.tERROR ')' 
+
+	tERROR  shift 297
+	.  error
+
+
+state 294
+	streamargs:  tSTREAM '<' type ',' type.'>' 
+
+	'>'  shift 298
+	.  error
+
+
+state 295
+	tags:  '{' expr_comma_list ocomma '}'.    (82)
+
+	.  reduce 82 (src line 449)
+
+
+state 296
+	outargs:  '(' named_arg_list ocomma '|' tERROR.')' 
+
+	')'  shift 299
+	.  error
+
+
+state 297
+	outargs:  '(' type_comma_list ocomma '|' tERROR.')' 
+
+	')'  shift 300
+	.  error
+
+
+state 298
+	streamargs:  tSTREAM '<' type ',' type '>'.    (79)
+
+	.  reduce 79 (src line 441)
+
+
+state 299
+	outargs:  '(' named_arg_list ocomma '|' tERROR ')'.    (74)
+
+	.  reduce 74 (src line 423)
+
+
+state 300
+	outargs:  '(' type_comma_list ocomma '|' tERROR ')'.    (75)
+
+	.  reduce 75 (src line 425)
+
+
+59 terminals, 49 nonterminals
+150 grammar rules, 301/2000 states
+0 shift/reduce, 0 reduce/reduce conflicts reported
+98 working sets used
+memory: parser 590/30000
+142 extra closures
+1361 shift entries, 5 exceptions
+192 goto entries
+303 entries saved by goto default
+Optimizer space used: output 800/30000
+800 table entries, 148 zero
+maximum spread: 57, maximum offset: 291
diff --git a/lib/vdl/parse/grammar.y.go b/lib/vdl/parse/grammar.y.go
new file mode 100644
index 0000000..d482b8f
--- /dev/null
+++ b/lib/vdl/parse/grammar.y.go
@@ -0,0 +1,1525 @@
+// 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.
+
+//line grammar.y:18
+
+// This grammar.y.go file was auto-generated by yacc from grammar.y.
+
+package parse
+
+import __yyfmt__ "fmt"
+
+//line grammar.y:20
+import (
+	"math/big"
+	"strings"
+)
+
+type intPos struct {
+	int *big.Int
+	pos Pos
+}
+
+type ratPos struct {
+	rat *big.Rat
+	pos Pos
+}
+
+type imagPos struct {
+	imag *BigImag
+	pos  Pos
+}
+
+// typeListToStrList converts a slice of Type to a slice of StringPos.  Each
+// type must be a TypeNamed with an empty PackageName, otherwise errors are
+// reported, and ok=false is returned.
+func typeListToStrList(yylex yyLexer, typeList []Type) (strList []StringPos, ok bool) {
+	ok = true
+	for _, t := range typeList {
+		var tn *TypeNamed
+		if tn, ok = t.(*TypeNamed); !ok {
+			lexPosErrorf(yylex, t.Pos(), "%s invalid (expected one or more variable names)", t.String())
+			return
+		}
+		if strings.ContainsRune(tn.Name, '.') {
+			ok = false
+			lexPosErrorf(yylex, t.Pos(), "%s invalid (expected one or more variable names).", tn.Name)
+			return
+		}
+		strList = append(strList, StringPos{tn.Name, tn.P})
+	}
+	return
+}
+
+//line grammar.y:67
+type yySymType struct {
+	yys        int
+	pos        Pos
+	strpos     StringPos
+	intpos     intPos
+	ratpos     ratPos
+	imagpos    imagPos
+	namepos    NamePos
+	nameposes  []NamePos
+	typeexpr   Type
+	typeexprs  []Type
+	fields     []*Field
+	iface      *Interface
+	constexpr  ConstExpr
+	constexprs []ConstExpr
+	complit    *ConstCompositeLit
+	kvlit      KVLit
+	kvlits     []KVLit
+	errordef   ErrorDef
+}
+
+const startFileImports = 57346
+const startFile = 57347
+const startConfigImports = 57348
+const startConfig = 57349
+const startExprs = 57350
+const tOROR = 57351
+const tANDAND = 57352
+const tLE = 57353
+const tGE = 57354
+const tNE = 57355
+const tEQEQ = 57356
+const tLSH = 57357
+const tRSH = 57358
+const tCONST = 57359
+const tENUM = 57360
+const tERROR = 57361
+const tIMPORT = 57362
+const tINTERFACE = 57363
+const tMAP = 57364
+const tPACKAGE = 57365
+const tSET = 57366
+const tSTREAM = 57367
+const tSTRUCT = 57368
+const tTYPE = 57369
+const tTYPEOBJECT = 57370
+const tUNION = 57371
+const tIDENT = 57372
+const tSTRLIT = 57373
+const tINTLIT = 57374
+const tRATLIT = 57375
+const tIMAGLIT = 57376
+const notPackage = 57377
+const notConfig = 57378
+
+var yyToknames = [...]string{
+	"$end",
+	"error",
+	"$unk",
+	"startFileImports",
+	"startFile",
+	"startConfigImports",
+	"startConfig",
+	"startExprs",
+	"';'",
+	"':'",
+	"','",
+	"'.'",
+	"'('",
+	"')'",
+	"'['",
+	"']'",
+	"'{'",
+	"'}'",
+	"'<'",
+	"'>'",
+	"'='",
+	"'!'",
+	"'+'",
+	"'-'",
+	"'*'",
+	"'/'",
+	"'%'",
+	"'|'",
+	"'&'",
+	"'^'",
+	"'?'",
+	"tOROR",
+	"tANDAND",
+	"tLE",
+	"tGE",
+	"tNE",
+	"tEQEQ",
+	"tLSH",
+	"tRSH",
+	"tCONST",
+	"tENUM",
+	"tERROR",
+	"tIMPORT",
+	"tINTERFACE",
+	"tMAP",
+	"tPACKAGE",
+	"tSET",
+	"tSTREAM",
+	"tSTRUCT",
+	"tTYPE",
+	"tTYPEOBJECT",
+	"tUNION",
+	"tIDENT",
+	"tSTRLIT",
+	"tINTLIT",
+	"tRATLIT",
+	"tIMAGLIT",
+	"notPackage",
+	"notConfig",
+}
+var yyStatenames = [...]string{}
+
+const yyEofCode = 1
+const yyErrCode = 2
+const yyMaxDepth = 200
+
+//line yacctab:1
+var yyExca = [...]int{
+	-1, 1,
+	1, -1,
+	-2, 0,
+	-1, 27,
+	13, 40,
+	17, 40,
+	-2, 118,
+	-1, 176,
+	18, 149,
+	-2, 144,
+	-1, 291,
+	18, 149,
+	-2, 144,
+}
+
+const yyNprod = 150
+const yyPrivate = 57344
+
+var yyTokenNames []string
+var yyStates []string
+
+const yyLast = 800
+
+var yyAct = [...]int{
+
+	14, 27, 13, 140, 139, 242, 251, 235, 220, 151,
+	186, 193, 175, 137, 147, 38, 190, 252, 253, 133,
+	166, 243, 90, 153, 152, 273, 181, 194, 191, 210,
+	78, 21, 134, 217, 192, 249, 144, 127, 80, 89,
+	40, 43, 213, 11, 240, 270, 8, 102, 98, 178,
+	104, 105, 106, 107, 108, 109, 110, 111, 112, 113,
+	114, 115, 116, 117, 118, 119, 120, 121, 122, 87,
+	252, 253, 194, 123, 194, 89, 126, 79, 128, 243,
+	90, 191, 89, 150, 89, 89, 89, 89, 153, 152,
+	125, 207, 297, 148, 189, 185, 158, 276, 159, 142,
+	41, 296, 93, 293, 292, 87, 157, 216, 100, 46,
+	289, 29, 87, 31, 87, 87, 87, 87, 295, 288,
+	17, 18, 19, 153, 152, 298, 275, 277, 20, 37,
+	210, 271, 89, 267, 191, 188, 202, 201, 86, 32,
+	30, 198, 89, 34, 195, 33, 279, 35, 101, 22,
+	36, 40, 23, 24, 25, 26, 103, 233, 170, 226,
+	180, 92, 87, 174, 85, 84, 60, 61, 62, 169,
+	64, 89, 87, 89, 124, 89, 199, 148, 204, 66,
+	67, 131, 81, 135, 136, 141, 141, 196, 168, 15,
+	89, 203, 96, 130, 97, 98, 209, 83, 82, 75,
+	205, 87, 95, 87, 219, 87, 215, 68, 69, 70,
+	71, 76, 89, 221, 77, 300, 223, 229, 299, 266,
+	87, 264, 247, 89, 245, 236, 237, 238, 244, 224,
+	222, 165, 162, 73, 72, 227, 239, 91, 89, 230,
+	74, 172, 87, 248, 48, 246, 49, 291, 254, 257,
+	255, 262, 260, 87, 176, 263, 259, 89, 89, 231,
+	265, 244, 258, 261, 228, 268, 225, 206, 87, 272,
+	197, 184, 141, 183, 200, 182, 171, 167, 89, 89,
+	281, 237, 285, 89, 280, 218, 149, 87, 87, 211,
+	99, 89, 104, 286, 287, 214, 187, 208, 290, 58,
+	59, 60, 61, 62, 63, 64, 65, 42, 87, 87,
+	10, 211, 156, 87, 66, 67, 12, 44, 45, 155,
+	47, 87, 141, 2, 3, 4, 5, 6, 154, 7,
+	29, 179, 31, 9, 94, 284, 1, 172, 250, 17,
+	18, 19, 232, 146, 28, 278, 16, 20, 37, 241,
+	274, 269, 39, 132, 0, 0, 141, 200, 32, 30,
+	0, 0, 34, 0, 33, 0, 35, 0, 22, 36,
+	40, 23, 24, 25, 26, 0, 0, 141, 283, 0,
+	0, 0, 172, 29, 0, 31, 0, 0, 145, 0,
+	294, 0, 17, 18, 19, 0, 0, 0, 256, 0,
+	20, 37, 31, 0, 0, 0, 0, 0, 0, 0,
+	0, 32, 30, 0, 0, 34, 0, 33, 37, 35,
+	0, 22, 36, 40, 23, 24, 25, 26, 32, 30,
+	31, 0, 34, 0, 33, 282, 35, 0, 88, 36,
+	40, 90, 0, 0, 234, 31, 37, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 32, 30, 0, 0,
+	34, 37, 33, 0, 35, 0, 88, 36, 40, 90,
+	31, 32, 30, 0, 0, 34, 0, 33, 0, 35,
+	0, 88, 36, 40, 90, 173, 37, 0, 0, 31,
+	0, 0, 0, 0, 0, 0, 32, 30, 0, 212,
+	34, 0, 33, 0, 35, 37, 88, 36, 40, 90,
+	0, 0, 0, 0, 31, 32, 30, 143, 0, 34,
+	0, 33, 0, 35, 0, 88, 36, 40, 90, 31,
+	37, 0, 138, 0, 0, 0, 0, 0, 0, 0,
+	32, 30, 0, 0, 34, 37, 33, 0, 35, 0,
+	88, 36, 40, 90, 31, 32, 30, 0, 0, 34,
+	0, 33, 0, 35, 0, 88, 36, 40, 90, 0,
+	37, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	32, 30, 0, 0, 34, 177, 33, 0, 35, 0,
+	88, 36, 40, 90, 52, 53, 0, 0, 58, 59,
+	60, 61, 62, 63, 64, 65, 0, 50, 51, 54,
+	55, 56, 57, 66, 67, 164, 0, 0, 52, 53,
+	0, 0, 58, 59, 60, 61, 62, 63, 64, 65,
+	0, 50, 51, 54, 55, 56, 57, 66, 67, 163,
+	0, 0, 52, 53, 0, 0, 58, 59, 60, 61,
+	62, 63, 64, 65, 0, 50, 51, 54, 55, 56,
+	57, 66, 67, 161, 0, 0, 0, 0, 52, 53,
+	0, 0, 58, 59, 60, 61, 62, 63, 64, 65,
+	160, 50, 51, 54, 55, 56, 57, 66, 67, 0,
+	52, 53, 0, 0, 58, 59, 60, 61, 62, 63,
+	64, 65, 0, 50, 51, 54, 55, 56, 57, 66,
+	67, 129, 0, 0, 0, 0, 52, 53, 0, 0,
+	58, 59, 60, 61, 62, 63, 64, 65, 0, 50,
+	51, 54, 55, 56, 57, 66, 67, 52, 53, 0,
+	0, 58, 59, 60, 61, 62, 63, 64, 65, 0,
+	50, 51, 54, 55, 56, 57, 66, 67, 52, 53,
+	0, 0, 58, 59, 60, 61, 62, 63, 64, 65,
+	0, 0, 51, 54, 55, 56, 57, 66, 67, 52,
+	53, 0, 0, 58, 59, 60, 61, 62, 63, 64,
+	65, 0, 0, 0, 54, 55, 56, 57, 66, 67,
+}
+var yyPact = [...]int{
+
+	319, -1000, 0, 0, -10, -10, 98, -1000, -12, -1000,
+	-1000, 88, -1000, 235, 718, -1000, -1000, 98, 98, 98,
+	98, 221, 220, 228, -1000, -1000, -1000, 184, 199, 98,
+	-1000, 22, 165, 183, 182, 148, 147, 539, 225, 144,
+	-1000, -1000, 152, 281, 5, 152, 98, 5, -1000, 98,
+	98, 98, 98, 98, 98, 98, 98, 98, 98, 98,
+	98, 98, 98, 98, 98, 98, 98, 98, -1000, -1000,
+	-1000, -1000, 98, 539, -13, 98, -16, 98, 697, 177,
+	539, -21, 539, 539, 514, 499, -1000, -1000, -1000, -1000,
+	228, -17, 370, -1000, 277, -1000, -1000, -1000, 70, -1000,
+	56, -1000, 671, 56, 718, 739, 760, 276, 276, 276,
+	276, 276, 276, 141, 141, -1000, -1000, -1000, 141, -1000,
+	141, -1000, -1000, 649, 218, 225, 623, -1000, 599, -1000,
+	539, -1000, 268, -1000, -1000, 172, 153, 267, -1000, -1000,
+	474, -1000, 267, -1000, -1000, -1000, 243, -1000, 575, -1000,
+	35, -1000, -1000, -28, 266, 264, 262, 82, 81, 21,
+	-1000, -1000, -1000, -1000, -1000, -1000, 126, -21, -1000, 539,
+	123, 539, -1000, 539, 119, 118, 98, 98, -1000, 258,
+	-1000, -1000, -1000, -1000, -1000, 77, -1000, -1000, 455, 28,
+	-1000, 86, 19, -1000, 200, -1000, -1000, -1000, -1000, -1000,
+	-1000, -1000, -1000, -1000, 718, 216, -30, -1000, 257, -1000,
+	539, -1000, 142, -1000, 255, -1000, 98, -1000, 250, -1000,
+	140, 430, -1000, -1000, 213, -24, 26, 210, -25, 718,
+	208, -26, -1000, 17, -1000, 239, 387, -1000, -1000, -1000,
+	-1000, 247, -1000, 200, -1000, -1000, -1000, -1000, -1000, -1000,
+	240, -1000, -1000, 245, 207, 539, 539, 205, 115, -32,
+	-3, 113, -36, -29, -1000, -1000, -1000, -1000, -1000, 84,
+	108, -1000, -1000, -1000, 129, -1000, 539, 415, -1000, 317,
+	239, 387, -1000, 99, -1000, 236, 76, 75, -1000, 539,
+	100, 98, 59, 50, 105, -1000, 204, 201, -1000, -1000,
+	-1000,
+}
+var yyPgo = [...]int{
+
+	0, 1, 15, 19, 353, 100, 31, 352, 3, 351,
+	13, 4, 7, 8, 350, 349, 5, 0, 189, 346,
+	345, 2, 344, 14, 343, 342, 338, 6, 336, 329,
+	307, 102, 108, 310, 334, 331, 20, 9, 328, 319,
+	312, 297, 10, 296, 295, 16, 285, 11, 12,
+}
+var yyR1 = [...]int{
+
+	0, 28, 28, 28, 28, 28, 31, 31, 31, 31,
+	29, 29, 33, 33, 30, 30, 34, 34, 34, 35,
+	35, 37, 37, 32, 32, 32, 32, 38, 38, 38,
+	38, 39, 39, 39, 40, 40, 40, 41, 41, 42,
+	6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+	6, 6, 5, 5, 4, 4, 3, 10, 10, 11,
+	8, 8, 43, 43, 15, 15, 16, 16, 13, 13,
+	13, 12, 12, 14, 14, 14, 9, 9, 9, 9,
+	20, 20, 20, 21, 21, 44, 44, 45, 17, 17,
+	17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
+	17, 17, 17, 17, 17, 17, 17, 18, 18, 18,
+	18, 18, 18, 18, 19, 19, 19, 19, 19, 19,
+	19, 19, 19, 19, 22, 22, 24, 24, 23, 23,
+	46, 46, 47, 25, 25, 25, 26, 26, 27, 27,
+	1, 1, 2, 2, 7, 7, 36, 36, 48, 48,
+}
+var yyR2 = [...]int{
+
+	0, 4, 4, 4, 4, 3, 0, 1, 1, 1,
+	0, 3, 0, 4, 0, 3, 3, 5, 2, 1,
+	3, 1, 2, 0, 3, 3, 3, 3, 5, 2,
+	2, 3, 5, 2, 3, 5, 2, 1, 3, 2,
+	1, 1, 4, 3, 5, 4, 5, 5, 3, 5,
+	3, 2, 1, 1, 1, 3, 1, 1, 3, 2,
+	1, 3, 4, 6, 1, 3, 5, 1, 2, 4,
+	4, 1, 3, 1, 6, 6, 0, 3, 4, 6,
+	0, 2, 4, 1, 3, 1, 3, 3, 1, 3,
+	3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+	3, 3, 3, 3, 3, 3, 3, 1, 2, 2,
+	2, 2, 4, 4, 1, 1, 1, 1, 1, 1,
+	3, 4, 4, 3, 3, 5, 1, 3, 1, 3,
+	1, 3, 3, 0, 2, 4, 1, 3, 1, 3,
+	1, 3, 1, 3, 0, 1, 0, 1, 0, 1,
+}
+var yyChk = [...]int{
+
+	-1000, -28, 4, 5, 6, 7, 8, -29, 46, -29,
+	-33, 53, -33, -21, -17, -18, -19, 22, 23, 24,
+	30, -6, 51, 54, 55, 56, 57, -1, -22, 13,
+	42, 15, 41, 47, 45, 49, 52, 31, -2, -7,
+	53, -5, -30, 53, -30, -30, 21, -30, 9, 11,
+	32, 33, 19, 20, 34, 35, 36, 37, 23, 24,
+	25, 26, 27, 28, 29, 30, 38, 39, -18, -18,
+	-18, -18, 13, 13, 12, 15, 12, 15, -17, 55,
+	16, 17, 15, 15, 17, 17, -5, -6, 51, -1,
+	54, 12, 17, -31, -34, 50, 40, 42, 43, 9,
+	-32, -31, -17, -32, -17, -17, -17, -17, -17, -17,
+	-17, -17, -17, -17, -17, -17, -17, -17, -17, -17,
+	-17, -17, -17, -17, -5, -2, -17, 53, -17, 14,
+	16, -5, -4, -3, 53, -5, -5, -10, 18, -11,
+	-8, -5, -10, 18, 53, 18, -24, -23, -17, 9,
+	13, -37, 54, 53, -38, -39, -40, 50, 40, 42,
+	9, 14, 14, 16, 16, -5, -36, 9, 16, 16,
+	-36, 9, -5, 11, -36, -48, 11, 10, 14, -35,
+	-37, 54, 9, 9, 9, 13, -42, -43, 53, 13,
+	-45, 53, 13, -47, 53, 18, -3, -5, 18, -11,
+	-5, 18, 18, -23, -17, -36, 9, 14, -41, -42,
+	53, -5, 44, 14, -44, -45, 21, 14, -46, -47,
+	-13, 13, 14, -37, -36, 9, 17, -36, 9, -17,
+	-36, 9, -25, 17, 14, -12, -8, -11, 14, -42,
+	18, -15, -16, 53, -1, 14, -45, 14, -47, 18,
+	-26, -27, 53, 54, -48, 11, 11, -48, -36, 9,
+	-13, -48, 11, 10, 14, -11, 14, 18, -16, -9,
+	48, 18, -27, 54, -14, 42, 13, 19, -20, 17,
+	-12, -8, 20, -5, 18, -21, -48, -48, 20, 11,
+	-48, 11, 28, 28, -5, 18, 42, 42, 20, 14,
+	14,
+}
+var yyDef = [...]int{
+
+	0, -2, 10, 10, 12, 12, 144, 14, 0, 14,
+	14, 0, 14, 0, 83, 88, 107, 144, 144, 144,
+	144, 52, 53, 114, 115, 116, 117, -2, 119, 144,
+	41, 0, 0, 0, 0, 0, 0, 0, 140, 0,
+	142, 145, 6, 0, 23, 6, 144, 23, 5, 144,
+	144, 144, 144, 144, 144, 144, 144, 144, 144, 144,
+	144, 144, 144, 144, 144, 144, 144, 144, 108, 109,
+	110, 111, 144, 0, 0, 144, 0, 144, 0, 0,
+	0, 0, 0, 0, 0, 0, 51, 52, 53, 40,
+	0, 0, 144, 1, 0, 7, 8, 9, 0, 11,
+	2, 3, 0, 4, 84, 89, 90, 91, 92, 93,
+	94, 95, 96, 97, 98, 99, 100, 101, 102, 103,
+	104, 105, 106, 0, 0, 141, 0, 120, 0, 123,
+	0, 43, 146, 54, 56, 0, 0, 146, 48, 57,
+	0, 60, 146, 50, 143, 124, 148, 126, 128, 15,
+	0, 18, 21, 0, 0, 0, 0, 0, 0, 0,
+	13, 112, 113, 122, 121, 42, 0, 147, 45, 0,
+	0, 147, 59, 0, 0, 0, -2, 144, 16, 146,
+	19, 22, 24, 25, 26, 0, 29, 30, 0, 0,
+	33, 0, 0, 36, 0, 44, 55, 46, 47, 58,
+	61, 49, 125, 127, 129, 0, 147, 27, 146, 37,
+	0, 39, 0, 31, 146, 85, 144, 34, 146, 130,
+	133, 0, 17, 20, 0, 147, 0, 0, 147, 87,
+	0, 147, 132, 0, 68, 148, 148, 71, 28, 38,
+	62, 146, 64, 142, 67, 32, 86, 35, 131, 134,
+	148, 136, 138, 0, 0, 149, 149, 0, 0, 147,
+	76, 0, 149, 0, 69, 72, 70, 63, 65, 0,
+	0, 135, 137, 139, 80, 73, 0, 0, 66, 144,
+	148, 148, 77, 0, 81, 148, 0, 0, 78, 0,
+	0, -2, 0, 0, 0, 82, 0, 0, 79, 74,
+	75,
+}
+var yyTok1 = [...]int{
+
+	1, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+	3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+	3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+	3, 3, 3, 22, 3, 3, 3, 27, 29, 3,
+	13, 14, 25, 23, 11, 24, 12, 26, 3, 3,
+	3, 3, 3, 3, 3, 3, 3, 3, 10, 9,
+	19, 21, 20, 31, 3, 3, 3, 3, 3, 3,
+	3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+	3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+	3, 15, 3, 16, 30, 3, 3, 3, 3, 3,
+	3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+	3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+	3, 3, 3, 17, 28, 18,
+}
+var yyTok2 = [...]int{
+
+	2, 3, 4, 5, 6, 7, 8, 32, 33, 34,
+	35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
+	45, 46, 47, 48, 49, 50, 51, 52, 53, 54,
+	55, 56, 57, 58, 59,
+}
+var yyTok3 = [...]int{
+	0,
+}
+
+var yyErrorMessages = [...]struct {
+	state int
+	token int
+	msg   string
+}{}
+
+//line yaccpar:1
+
+/*	parser for yacc output	*/
+
+var (
+	yyDebug        = 0
+	yyErrorVerbose = false
+)
+
+type yyLexer interface {
+	Lex(lval *yySymType) int
+	Error(s string)
+}
+
+type yyParser interface {
+	Parse(yyLexer) int
+	Lookahead() int
+}
+
+type yyParserImpl struct {
+	lookahead func() int
+}
+
+func (p *yyParserImpl) Lookahead() int {
+	return p.lookahead()
+}
+
+func yyNewParser() yyParser {
+	p := &yyParserImpl{
+		lookahead: func() int { return -1 },
+	}
+	return p
+}
+
+const yyFlag = -1000
+
+func yyTokname(c int) string {
+	if c >= 1 && c-1 < len(yyToknames) {
+		if yyToknames[c-1] != "" {
+			return yyToknames[c-1]
+		}
+	}
+	return __yyfmt__.Sprintf("tok-%v", c)
+}
+
+func yyStatname(s int) string {
+	if s >= 0 && s < len(yyStatenames) {
+		if yyStatenames[s] != "" {
+			return yyStatenames[s]
+		}
+	}
+	return __yyfmt__.Sprintf("state-%v", s)
+}
+
+func yyErrorMessage(state, lookAhead int) string {
+	const TOKSTART = 4
+
+	if !yyErrorVerbose {
+		return "syntax error"
+	}
+
+	for _, e := range yyErrorMessages {
+		if e.state == state && e.token == lookAhead {
+			return "syntax error: " + e.msg
+		}
+	}
+
+	res := "syntax error: unexpected " + yyTokname(lookAhead)
+
+	// To match Bison, suggest at most four expected tokens.
+	expected := make([]int, 0, 4)
+
+	// Look for shiftable tokens.
+	base := yyPact[state]
+	for tok := TOKSTART; tok-1 < len(yyToknames); tok++ {
+		if n := base + tok; n >= 0 && n < yyLast && yyChk[yyAct[n]] == tok {
+			if len(expected) == cap(expected) {
+				return res
+			}
+			expected = append(expected, tok)
+		}
+	}
+
+	if yyDef[state] == -2 {
+		i := 0
+		for yyExca[i] != -1 || yyExca[i+1] != state {
+			i += 2
+		}
+
+		// Look for tokens that we accept or reduce.
+		for i += 2; yyExca[i] >= 0; i += 2 {
+			tok := yyExca[i]
+			if tok < TOKSTART || yyExca[i+1] == 0 {
+				continue
+			}
+			if len(expected) == cap(expected) {
+				return res
+			}
+			expected = append(expected, tok)
+		}
+
+		// If the default action is to accept or reduce, give up.
+		if yyExca[i+1] != 0 {
+			return res
+		}
+	}
+
+	for i, tok := range expected {
+		if i == 0 {
+			res += ", expecting "
+		} else {
+			res += " or "
+		}
+		res += yyTokname(tok)
+	}
+	return res
+}
+
+func yylex1(lex yyLexer, lval *yySymType) (char, token int) {
+	token = 0
+	char = lex.Lex(lval)
+	if char <= 0 {
+		token = yyTok1[0]
+		goto out
+	}
+	if char < len(yyTok1) {
+		token = yyTok1[char]
+		goto out
+	}
+	if char >= yyPrivate {
+		if char < yyPrivate+len(yyTok2) {
+			token = yyTok2[char-yyPrivate]
+			goto out
+		}
+	}
+	for i := 0; i < len(yyTok3); i += 2 {
+		token = yyTok3[i+0]
+		if token == char {
+			token = yyTok3[i+1]
+			goto out
+		}
+	}
+
+out:
+	if token == 0 {
+		token = yyTok2[1] /* unknown char */
+	}
+	if yyDebug >= 3 {
+		__yyfmt__.Printf("lex %s(%d)\n", yyTokname(token), uint(char))
+	}
+	return char, token
+}
+
+func yyParse(yylex yyLexer) int {
+	return yyNewParser().Parse(yylex)
+}
+
+func (yyrcvr *yyParserImpl) Parse(yylex yyLexer) int {
+	var yyn int
+	var yylval yySymType
+	var yyVAL yySymType
+	var yyDollar []yySymType
+	_ = yyDollar // silence set and not used
+	yyS := make([]yySymType, yyMaxDepth)
+
+	Nerrs := 0   /* number of errors */
+	Errflag := 0 /* error recovery flag */
+	yystate := 0
+	yychar := -1
+	yytoken := -1 // yychar translated into internal numbering
+	yyrcvr.lookahead = func() int { return yychar }
+	defer func() {
+		// Make sure we report no lookahead when not parsing.
+		yystate = -1
+		yychar = -1
+		yytoken = -1
+	}()
+	yyp := -1
+	goto yystack
+
+ret0:
+	return 0
+
+ret1:
+	return 1
+
+yystack:
+	/* put a state and value onto the stack */
+	if yyDebug >= 4 {
+		__yyfmt__.Printf("char %v in %v\n", yyTokname(yytoken), yyStatname(yystate))
+	}
+
+	yyp++
+	if yyp >= len(yyS) {
+		nyys := make([]yySymType, len(yyS)*2)
+		copy(nyys, yyS)
+		yyS = nyys
+	}
+	yyS[yyp] = yyVAL
+	yyS[yyp].yys = yystate
+
+yynewstate:
+	yyn = yyPact[yystate]
+	if yyn <= yyFlag {
+		goto yydefault /* simple state */
+	}
+	if yychar < 0 {
+		yychar, yytoken = yylex1(yylex, &yylval)
+	}
+	yyn += yytoken
+	if yyn < 0 || yyn >= yyLast {
+		goto yydefault
+	}
+	yyn = yyAct[yyn]
+	if yyChk[yyn] == yytoken { /* valid shift */
+		yychar = -1
+		yytoken = -1
+		yyVAL = yylval
+		yystate = yyn
+		if Errflag > 0 {
+			Errflag--
+		}
+		goto yystack
+	}
+
+yydefault:
+	/* default state action */
+	yyn = yyDef[yystate]
+	if yyn == -2 {
+		if yychar < 0 {
+			yychar, yytoken = yylex1(yylex, &yylval)
+		}
+
+		/* look through exception table */
+		xi := 0
+		for {
+			if yyExca[xi+0] == -1 && yyExca[xi+1] == yystate {
+				break
+			}
+			xi += 2
+		}
+		for xi += 2; ; xi += 2 {
+			yyn = yyExca[xi+0]
+			if yyn < 0 || yyn == yytoken {
+				break
+			}
+		}
+		yyn = yyExca[xi+1]
+		if yyn < 0 {
+			goto ret0
+		}
+	}
+	if yyn == 0 {
+		/* error ... attempt to resume parsing */
+		switch Errflag {
+		case 0: /* brand new error */
+			yylex.Error(yyErrorMessage(yystate, yytoken))
+			Nerrs++
+			if yyDebug >= 1 {
+				__yyfmt__.Printf("%s", yyStatname(yystate))
+				__yyfmt__.Printf(" saw %s\n", yyTokname(yytoken))
+			}
+			fallthrough
+
+		case 1, 2: /* incompletely recovered error ... try again */
+			Errflag = 3
+
+			/* find a state where "error" is a legal shift action */
+			for yyp >= 0 {
+				yyn = yyPact[yyS[yyp].yys] + yyErrCode
+				if yyn >= 0 && yyn < yyLast {
+					yystate = yyAct[yyn] /* simulate a shift of "error" */
+					if yyChk[yystate] == yyErrCode {
+						goto yystack
+					}
+				}
+
+				/* the current p has no shift on "error", pop stack */
+				if yyDebug >= 2 {
+					__yyfmt__.Printf("error recovery pops state %d\n", yyS[yyp].yys)
+				}
+				yyp--
+			}
+			/* there is no state on the stack with an error shift ... abort */
+			goto ret1
+
+		case 3: /* no shift yet; clobber input char */
+			if yyDebug >= 2 {
+				__yyfmt__.Printf("error recovery discards %s\n", yyTokname(yytoken))
+			}
+			if yytoken == yyEofCode {
+				goto ret1
+			}
+			yychar = -1
+			yytoken = -1
+			goto yynewstate /* try again in the same state */
+		}
+	}
+
+	/* reduction by production yyn */
+	if yyDebug >= 2 {
+		__yyfmt__.Printf("reduce %v in:\n\t%v\n", yyn, yyStatname(yystate))
+	}
+
+	yynt := yyn
+	yypt := yyp
+	_ = yypt // guard against "declared and not used"
+
+	yyp -= yyR2[yyn]
+	// yyp is now the index of $0. Perform the default action. Iff the
+	// reduced production is ε, $1 is possibly out of range.
+	if yyp+1 >= len(yyS) {
+		nyys := make([]yySymType, len(yyS)*2)
+		copy(nyys, yyS)
+		yyS = nyys
+	}
+	yyVAL = yyS[yyp+1]
+
+	/* consult goto table to find next state */
+	yyn = yyR1[yyn]
+	yyg := yyPgo[yyn]
+	yyj := yyg + yyS[yyp].yys + 1
+
+	if yyj >= yyLast {
+		yystate = yyAct[yyg]
+	} else {
+		yystate = yyAct[yyj]
+		if yyChk[yystate] != -yyn {
+			yystate = yyAct[yyg]
+		}
+	}
+	// dummy call; replaced with literal code
+	switch yynt {
+
+	case 5:
+		yyDollar = yyS[yypt-3 : yypt+1]
+		//line grammar.y:143
+		{
+			lexStoreExprs(yylex, yyDollar[2].constexprs)
+		}
+	case 6:
+		yyDollar = yyS[yypt-0 : yypt+1]
+		//line grammar.y:152
+		{
+			lexGenEOF(yylex)
+		}
+	case 7:
+		yyDollar = yyS[yypt-1 : yypt+1]
+		//line grammar.y:154
+		{
+			lexGenEOF(yylex)
+		}
+	case 8:
+		yyDollar = yyS[yypt-1 : yypt+1]
+		//line grammar.y:156
+		{
+			lexGenEOF(yylex)
+		}
+	case 9:
+		yyDollar = yyS[yypt-1 : yypt+1]
+		//line grammar.y:158
+		{
+			lexGenEOF(yylex)
+		}
+	case 10:
+		yyDollar = yyS[yypt-0 : yypt+1]
+		//line grammar.y:163
+		{
+			lexPosErrorf(yylex, Pos{}, "vdl file must start with package clause")
+		}
+	case 11:
+		yyDollar = yyS[yypt-3 : yypt+1]
+		//line grammar.y:165
+		{
+			lexVDLFile(yylex).PackageDef = NamePos{Name: yyDollar[2].strpos.String, Pos: yyDollar[2].strpos.Pos}
+		}
+	case 12:
+		yyDollar = yyS[yypt-0 : yypt+1]
+		//line grammar.y:170
+		{
+			lexPosErrorf(yylex, Pos{}, "config file must start with config clause")
+		}
+	case 13:
+		yyDollar = yyS[yypt-4 : yypt+1]
+		//line grammar.y:172
+		{
+			// We allow "config" as an identifier; it is not a keyword.  So we check
+			// manually to make sure the syntax is correct.
+			if yyDollar[1].strpos.String != "config" {
+				lexPosErrorf(yylex, yyDollar[1].strpos.Pos, "config file must start with config clause")
+				return 1 // Any non-zero code indicates an error
+			}
+			file := lexVDLFile(yylex)
+			file.PackageDef = NamePos{Name: "config", Pos: yyDollar[1].strpos.Pos}
+			file.ConstDefs = []*ConstDef{{Expr: yyDollar[3].constexpr}}
+		}
+	case 21:
+		yyDollar = yyS[yypt-1 : yypt+1]
+		//line grammar.y:200
+		{
+			imps := &lexVDLFile(yylex).Imports
+			*imps = append(*imps, &Import{Path: yyDollar[1].strpos.String, NamePos: NamePos{Pos: yyDollar[1].strpos.Pos}})
+		}
+	case 22:
+		yyDollar = yyS[yypt-2 : yypt+1]
+		//line grammar.y:205
+		{
+			imps := &lexVDLFile(yylex).Imports
+			*imps = append(*imps, &Import{Path: yyDollar[2].strpos.String, NamePos: NamePos{Name: yyDollar[1].strpos.String, Pos: yyDollar[1].strpos.Pos}})
+		}
+	case 39:
+		yyDollar = yyS[yypt-2 : yypt+1]
+		//line grammar.y:240
+		{
+			tds := &lexVDLFile(yylex).TypeDefs
+			*tds = append(*tds, &TypeDef{Type: yyDollar[2].typeexpr, NamePos: NamePos{Name: yyDollar[1].strpos.String, Pos: yyDollar[1].strpos.Pos}})
+		}
+	case 40:
+		yyDollar = yyS[yypt-1 : yypt+1]
+		//line grammar.y:262
+		{
+			yyVAL.typeexpr = &TypeNamed{Name: yyDollar[1].strpos.String, P: yyDollar[1].strpos.Pos}
+		}
+	case 41:
+		yyDollar = yyS[yypt-1 : yypt+1]
+		//line grammar.y:264
+		{
+			yyVAL.typeexpr = &TypeNamed{Name: "error", P: yyDollar[1].pos}
+		}
+	case 42:
+		yyDollar = yyS[yypt-4 : yypt+1]
+		//line grammar.y:266
+		{
+			yyVAL.typeexpr = &TypeArray{Len: int(yyDollar[2].intpos.int.Int64()), Elem: yyDollar[4].typeexpr, P: yyDollar[1].pos}
+		}
+	case 43:
+		yyDollar = yyS[yypt-3 : yypt+1]
+		//line grammar.y:268
+		{
+			yyVAL.typeexpr = &TypeList{Elem: yyDollar[3].typeexpr, P: yyDollar[1].pos}
+		}
+	case 44:
+		yyDollar = yyS[yypt-5 : yypt+1]
+		//line grammar.y:270
+		{
+			yyVAL.typeexpr = &TypeEnum{Labels: yyDollar[3].nameposes, P: yyDollar[1].pos}
+		}
+	case 45:
+		yyDollar = yyS[yypt-4 : yypt+1]
+		//line grammar.y:272
+		{
+			yyVAL.typeexpr = &TypeSet{Key: yyDollar[3].typeexpr, P: yyDollar[1].pos}
+		}
+	case 46:
+		yyDollar = yyS[yypt-5 : yypt+1]
+		//line grammar.y:274
+		{
+			yyVAL.typeexpr = &TypeMap{Key: yyDollar[3].typeexpr, Elem: yyDollar[5].typeexpr, P: yyDollar[1].pos}
+		}
+	case 47:
+		yyDollar = yyS[yypt-5 : yypt+1]
+		//line grammar.y:276
+		{
+			yyVAL.typeexpr = &TypeStruct{Fields: yyDollar[3].fields, P: yyDollar[1].pos}
+		}
+	case 48:
+		yyDollar = yyS[yypt-3 : yypt+1]
+		//line grammar.y:278
+		{
+			yyVAL.typeexpr = &TypeStruct{P: yyDollar[1].pos}
+		}
+	case 49:
+		yyDollar = yyS[yypt-5 : yypt+1]
+		//line grammar.y:280
+		{
+			yyVAL.typeexpr = &TypeUnion{Fields: yyDollar[3].fields, P: yyDollar[1].pos}
+		}
+	case 50:
+		yyDollar = yyS[yypt-3 : yypt+1]
+		//line grammar.y:282
+		{
+			yyVAL.typeexpr = &TypeUnion{P: yyDollar[1].pos}
+		}
+	case 51:
+		yyDollar = yyS[yypt-2 : yypt+1]
+		//line grammar.y:284
+		{
+			yyVAL.typeexpr = &TypeOptional{Base: yyDollar[2].typeexpr, P: yyDollar[1].pos}
+		}
+	case 52:
+		yyDollar = yyS[yypt-1 : yypt+1]
+		//line grammar.y:289
+		{
+			yyVAL.typeexpr = yyDollar[1].typeexpr
+		}
+	case 53:
+		yyDollar = yyS[yypt-1 : yypt+1]
+		//line grammar.y:291
+		{
+			yyVAL.typeexpr = &TypeNamed{Name: "typeobject", P: yyDollar[1].pos}
+		}
+	case 54:
+		yyDollar = yyS[yypt-1 : yypt+1]
+		//line grammar.y:295
+		{
+			yyVAL.nameposes = []NamePos{yyDollar[1].namepos}
+		}
+	case 55:
+		yyDollar = yyS[yypt-3 : yypt+1]
+		//line grammar.y:297
+		{
+			yyVAL.nameposes = append(yyDollar[1].nameposes, yyDollar[3].namepos)
+		}
+	case 56:
+		yyDollar = yyS[yypt-1 : yypt+1]
+		//line grammar.y:301
+		{
+			yyVAL.namepos = NamePos{Name: yyDollar[1].strpos.String, Pos: yyDollar[1].strpos.Pos}
+		}
+	case 57:
+		yyDollar = yyS[yypt-1 : yypt+1]
+		//line grammar.y:305
+		{
+			yyVAL.fields = yyDollar[1].fields
+		}
+	case 58:
+		yyDollar = yyS[yypt-3 : yypt+1]
+		//line grammar.y:307
+		{
+			yyVAL.fields = append(yyDollar[1].fields, yyDollar[3].fields...)
+		}
+	case 59:
+		yyDollar = yyS[yypt-2 : yypt+1]
+		//line grammar.y:345
+		{
+			if names, ok := typeListToStrList(yylex, yyDollar[1].typeexprs); ok {
+				for _, n := range names {
+					yyVAL.fields = append(yyVAL.fields, &Field{Type: yyDollar[2].typeexpr, NamePos: NamePos{Name: n.String, Pos: n.Pos}})
+				}
+			} else {
+				lexPosErrorf(yylex, yyDollar[2].typeexpr.Pos(), "perhaps you forgot a comma before %q?.", yyDollar[2].typeexpr.String())
+			}
+		}
+	case 60:
+		yyDollar = yyS[yypt-1 : yypt+1]
+		//line grammar.y:357
+		{
+			yyVAL.typeexprs = []Type{yyDollar[1].typeexpr}
+		}
+	case 61:
+		yyDollar = yyS[yypt-3 : yypt+1]
+		//line grammar.y:359
+		{
+			yyVAL.typeexprs = append(yyDollar[1].typeexprs, yyDollar[3].typeexpr)
+		}
+	case 62:
+		yyDollar = yyS[yypt-4 : yypt+1]
+		//line grammar.y:364
+		{
+			ifs := &lexVDLFile(yylex).Interfaces
+			*ifs = append(*ifs, &Interface{NamePos: NamePos{Name: yyDollar[1].strpos.String, Pos: yyDollar[1].strpos.Pos}})
+		}
+	case 63:
+		yyDollar = yyS[yypt-6 : yypt+1]
+		//line grammar.y:369
+		{
+			yyDollar[4].iface.Name, yyDollar[4].iface.Pos = yyDollar[1].strpos.String, yyDollar[1].strpos.Pos
+			ifs := &lexVDLFile(yylex).Interfaces
+			*ifs = append(*ifs, yyDollar[4].iface)
+		}
+	case 64:
+		yyDollar = yyS[yypt-1 : yypt+1]
+		//line grammar.y:377
+		{
+			yyVAL.iface = yyDollar[1].iface
+		}
+	case 65:
+		yyDollar = yyS[yypt-3 : yypt+1]
+		//line grammar.y:379
+		{
+			yyDollar[1].iface.Embeds = append(yyDollar[1].iface.Embeds, yyDollar[3].iface.Embeds...)
+			yyDollar[1].iface.Methods = append(yyDollar[1].iface.Methods, yyDollar[3].iface.Methods...)
+			yyVAL.iface = yyDollar[1].iface
+		}
+	case 66:
+		yyDollar = yyS[yypt-5 : yypt+1]
+		//line grammar.y:387
+		{
+			yyVAL.iface = &Interface{Methods: []*Method{{InArgs: yyDollar[2].fields, InStream: yyDollar[3].typeexprs[0], OutStream: yyDollar[3].typeexprs[1], OutArgs: yyDollar[4].fields, Tags: yyDollar[5].constexprs, NamePos: NamePos{Name: yyDollar[1].strpos.String, Pos: yyDollar[1].strpos.Pos}}}}
+		}
+	case 67:
+		yyDollar = yyS[yypt-1 : yypt+1]
+		//line grammar.y:389
+		{
+			yyVAL.iface = &Interface{Embeds: []*NamePos{{Name: yyDollar[1].strpos.String, Pos: yyDollar[1].strpos.Pos}}}
+		}
+	case 68:
+		yyDollar = yyS[yypt-2 : yypt+1]
+		//line grammar.y:393
+		{
+			yyVAL.fields = nil
+		}
+	case 69:
+		yyDollar = yyS[yypt-4 : yypt+1]
+		//line grammar.y:395
+		{
+			yyVAL.fields = yyDollar[2].fields
+		}
+	case 70:
+		yyDollar = yyS[yypt-4 : yypt+1]
+		//line grammar.y:399
+		{
+			for _, t := range yyDollar[2].typeexprs {
+				yyVAL.fields = append(yyVAL.fields, &Field{Type: t, NamePos: NamePos{Pos: t.Pos()}})
+			}
+		}
+	case 71:
+		yyDollar = yyS[yypt-1 : yypt+1]
+		//line grammar.y:409
+		{
+			yyVAL.fields = yyDollar[1].fields
+		}
+	case 72:
+		yyDollar = yyS[yypt-3 : yypt+1]
+		//line grammar.y:411
+		{
+			yyVAL.fields = append(yyDollar[1].fields, yyDollar[3].fields...)
+		}
+	case 73:
+		yyDollar = yyS[yypt-1 : yypt+1]
+		//line grammar.y:422
+		{
+			yyVAL.fields = nil
+		}
+	case 74:
+		yyDollar = yyS[yypt-6 : yypt+1]
+		//line grammar.y:424
+		{
+			yyVAL.fields = yyDollar[2].fields
+		}
+	case 75:
+		yyDollar = yyS[yypt-6 : yypt+1]
+		//line grammar.y:428
+		{
+			for _, t := range yyDollar[2].typeexprs {
+				yyVAL.fields = append(yyVAL.fields, &Field{Type: t, NamePos: NamePos{Pos: t.Pos()}})
+			}
+		}
+	case 76:
+		yyDollar = yyS[yypt-0 : yypt+1]
+		//line grammar.y:436
+		{
+			yyVAL.typeexprs = []Type{nil, nil}
+		}
+	case 77:
+		yyDollar = yyS[yypt-3 : yypt+1]
+		//line grammar.y:438
+		{
+			yyVAL.typeexprs = []Type{nil, nil}
+		}
+	case 78:
+		yyDollar = yyS[yypt-4 : yypt+1]
+		//line grammar.y:440
+		{
+			yyVAL.typeexprs = []Type{yyDollar[3].typeexpr, nil}
+		}
+	case 79:
+		yyDollar = yyS[yypt-6 : yypt+1]
+		//line grammar.y:442
+		{
+			yyVAL.typeexprs = []Type{yyDollar[3].typeexpr, yyDollar[5].typeexpr}
+		}
+	case 80:
+		yyDollar = yyS[yypt-0 : yypt+1]
+		//line grammar.y:446
+		{
+			yyVAL.constexprs = nil
+		}
+	case 81:
+		yyDollar = yyS[yypt-2 : yypt+1]
+		//line grammar.y:448
+		{
+			yyVAL.constexprs = nil
+		}
+	case 82:
+		yyDollar = yyS[yypt-4 : yypt+1]
+		//line grammar.y:450
+		{
+			yyVAL.constexprs = yyDollar[2].constexprs
+		}
+	case 83:
+		yyDollar = yyS[yypt-1 : yypt+1]
+		//line grammar.y:454
+		{
+			yyVAL.constexprs = []ConstExpr{yyDollar[1].constexpr}
+		}
+	case 84:
+		yyDollar = yyS[yypt-3 : yypt+1]
+		//line grammar.y:456
+		{
+			yyVAL.constexprs = append(yyDollar[1].constexprs, yyDollar[3].constexpr)
+		}
+	case 87:
+		yyDollar = yyS[yypt-3 : yypt+1]
+		//line grammar.y:465
+		{
+			cds := &lexVDLFile(yylex).ConstDefs
+			*cds = append(*cds, &ConstDef{Expr: yyDollar[3].constexpr, NamePos: NamePos{Name: yyDollar[1].strpos.String, Pos: yyDollar[1].strpos.Pos}})
+		}
+	case 88:
+		yyDollar = yyS[yypt-1 : yypt+1]
+		//line grammar.y:472
+		{
+			yyVAL.constexpr = yyDollar[1].constexpr
+		}
+	case 89:
+		yyDollar = yyS[yypt-3 : yypt+1]
+		//line grammar.y:474
+		{
+			yyVAL.constexpr = &ConstBinaryOp{"||", yyDollar[1].constexpr, yyDollar[3].constexpr, yyDollar[2].pos}
+		}
+	case 90:
+		yyDollar = yyS[yypt-3 : yypt+1]
+		//line grammar.y:476
+		{
+			yyVAL.constexpr = &ConstBinaryOp{"&&", yyDollar[1].constexpr, yyDollar[3].constexpr, yyDollar[2].pos}
+		}
+	case 91:
+		yyDollar = yyS[yypt-3 : yypt+1]
+		//line grammar.y:478
+		{
+			yyVAL.constexpr = &ConstBinaryOp{"<", yyDollar[1].constexpr, yyDollar[3].constexpr, yyDollar[2].pos}
+		}
+	case 92:
+		yyDollar = yyS[yypt-3 : yypt+1]
+		//line grammar.y:480
+		{
+			yyVAL.constexpr = &ConstBinaryOp{">", yyDollar[1].constexpr, yyDollar[3].constexpr, yyDollar[2].pos}
+		}
+	case 93:
+		yyDollar = yyS[yypt-3 : yypt+1]
+		//line grammar.y:482
+		{
+			yyVAL.constexpr = &ConstBinaryOp{"<=", yyDollar[1].constexpr, yyDollar[3].constexpr, yyDollar[2].pos}
+		}
+	case 94:
+		yyDollar = yyS[yypt-3 : yypt+1]
+		//line grammar.y:484
+		{
+			yyVAL.constexpr = &ConstBinaryOp{">=", yyDollar[1].constexpr, yyDollar[3].constexpr, yyDollar[2].pos}
+		}
+	case 95:
+		yyDollar = yyS[yypt-3 : yypt+1]
+		//line grammar.y:486
+		{
+			yyVAL.constexpr = &ConstBinaryOp{"!=", yyDollar[1].constexpr, yyDollar[3].constexpr, yyDollar[2].pos}
+		}
+	case 96:
+		yyDollar = yyS[yypt-3 : yypt+1]
+		//line grammar.y:488
+		{
+			yyVAL.constexpr = &ConstBinaryOp{"==", yyDollar[1].constexpr, yyDollar[3].constexpr, yyDollar[2].pos}
+		}
+	case 97:
+		yyDollar = yyS[yypt-3 : yypt+1]
+		//line grammar.y:490
+		{
+			yyVAL.constexpr = &ConstBinaryOp{"+", yyDollar[1].constexpr, yyDollar[3].constexpr, yyDollar[2].pos}
+		}
+	case 98:
+		yyDollar = yyS[yypt-3 : yypt+1]
+		//line grammar.y:492
+		{
+			yyVAL.constexpr = &ConstBinaryOp{"-", yyDollar[1].constexpr, yyDollar[3].constexpr, yyDollar[2].pos}
+		}
+	case 99:
+		yyDollar = yyS[yypt-3 : yypt+1]
+		//line grammar.y:494
+		{
+			yyVAL.constexpr = &ConstBinaryOp{"*", yyDollar[1].constexpr, yyDollar[3].constexpr, yyDollar[2].pos}
+		}
+	case 100:
+		yyDollar = yyS[yypt-3 : yypt+1]
+		//line grammar.y:496
+		{
+			yyVAL.constexpr = &ConstBinaryOp{"/", yyDollar[1].constexpr, yyDollar[3].constexpr, yyDollar[2].pos}
+		}
+	case 101:
+		yyDollar = yyS[yypt-3 : yypt+1]
+		//line grammar.y:498
+		{
+			yyVAL.constexpr = &ConstBinaryOp{"%", yyDollar[1].constexpr, yyDollar[3].constexpr, yyDollar[2].pos}
+		}
+	case 102:
+		yyDollar = yyS[yypt-3 : yypt+1]
+		//line grammar.y:500
+		{
+			yyVAL.constexpr = &ConstBinaryOp{"|", yyDollar[1].constexpr, yyDollar[3].constexpr, yyDollar[2].pos}
+		}
+	case 103:
+		yyDollar = yyS[yypt-3 : yypt+1]
+		//line grammar.y:502
+		{
+			yyVAL.constexpr = &ConstBinaryOp{"&", yyDollar[1].constexpr, yyDollar[3].constexpr, yyDollar[2].pos}
+		}
+	case 104:
+		yyDollar = yyS[yypt-3 : yypt+1]
+		//line grammar.y:504
+		{
+			yyVAL.constexpr = &ConstBinaryOp{"^", yyDollar[1].constexpr, yyDollar[3].constexpr, yyDollar[2].pos}
+		}
+	case 105:
+		yyDollar = yyS[yypt-3 : yypt+1]
+		//line grammar.y:506
+		{
+			yyVAL.constexpr = &ConstBinaryOp{"<<", yyDollar[1].constexpr, yyDollar[3].constexpr, yyDollar[2].pos}
+		}
+	case 106:
+		yyDollar = yyS[yypt-3 : yypt+1]
+		//line grammar.y:508
+		{
+			yyVAL.constexpr = &ConstBinaryOp{">>", yyDollar[1].constexpr, yyDollar[3].constexpr, yyDollar[2].pos}
+		}
+	case 107:
+		yyDollar = yyS[yypt-1 : yypt+1]
+		//line grammar.y:512
+		{
+			yyVAL.constexpr = yyDollar[1].constexpr
+		}
+	case 108:
+		yyDollar = yyS[yypt-2 : yypt+1]
+		//line grammar.y:514
+		{
+			yyVAL.constexpr = &ConstUnaryOp{"!", yyDollar[2].constexpr, yyDollar[1].pos}
+		}
+	case 109:
+		yyDollar = yyS[yypt-2 : yypt+1]
+		//line grammar.y:516
+		{
+			yyVAL.constexpr = &ConstUnaryOp{"+", yyDollar[2].constexpr, yyDollar[1].pos}
+		}
+	case 110:
+		yyDollar = yyS[yypt-2 : yypt+1]
+		//line grammar.y:518
+		{
+			yyVAL.constexpr = &ConstUnaryOp{"-", yyDollar[2].constexpr, yyDollar[1].pos}
+		}
+	case 111:
+		yyDollar = yyS[yypt-2 : yypt+1]
+		//line grammar.y:520
+		{
+			yyVAL.constexpr = &ConstUnaryOp{"^", yyDollar[2].constexpr, yyDollar[1].pos}
+		}
+	case 112:
+		yyDollar = yyS[yypt-4 : yypt+1]
+		//line grammar.y:522
+		{
+			yyVAL.constexpr = &ConstTypeConv{yyDollar[1].typeexpr, yyDollar[3].constexpr, yyDollar[1].typeexpr.Pos()}
+		}
+	case 113:
+		yyDollar = yyS[yypt-4 : yypt+1]
+		//line grammar.y:524
+		{
+			yyVAL.constexpr = &ConstTypeObject{yyDollar[3].typeexpr, yyDollar[1].pos}
+		}
+	case 114:
+		yyDollar = yyS[yypt-1 : yypt+1]
+		//line grammar.y:529
+		{
+			yyVAL.constexpr = &ConstLit{yyDollar[1].strpos.String, yyDollar[1].strpos.Pos}
+		}
+	case 115:
+		yyDollar = yyS[yypt-1 : yypt+1]
+		//line grammar.y:531
+		{
+			yyVAL.constexpr = &ConstLit{yyDollar[1].intpos.int, yyDollar[1].intpos.pos}
+		}
+	case 116:
+		yyDollar = yyS[yypt-1 : yypt+1]
+		//line grammar.y:533
+		{
+			yyVAL.constexpr = &ConstLit{yyDollar[1].ratpos.rat, yyDollar[1].ratpos.pos}
+		}
+	case 117:
+		yyDollar = yyS[yypt-1 : yypt+1]
+		//line grammar.y:535
+		{
+			yyVAL.constexpr = &ConstLit{yyDollar[1].imagpos.imag, yyDollar[1].imagpos.pos}
+		}
+	case 118:
+		yyDollar = yyS[yypt-1 : yypt+1]
+		//line grammar.y:537
+		{
+			yyVAL.constexpr = &ConstNamed{yyDollar[1].strpos.String, yyDollar[1].strpos.Pos}
+		}
+	case 119:
+		yyDollar = yyS[yypt-1 : yypt+1]
+		//line grammar.y:539
+		{
+			yyVAL.constexpr = yyDollar[1].complit
+		}
+	case 120:
+		yyDollar = yyS[yypt-3 : yypt+1]
+		//line grammar.y:541
+		{
+			lexPosErrorf(yylex, yyDollar[2].pos, "cannot apply selector operator to unnamed constant")
+		}
+	case 121:
+		yyDollar = yyS[yypt-4 : yypt+1]
+		//line grammar.y:543
+		{
+			lexPosErrorf(yylex, yyDollar[2].pos, "cannot apply index operator to unnamed constant")
+		}
+	case 122:
+		yyDollar = yyS[yypt-4 : yypt+1]
+		//line grammar.y:545
+		{
+			yyVAL.constexpr = &ConstIndexed{&ConstNamed{yyDollar[1].strpos.String, yyDollar[1].strpos.Pos}, yyDollar[3].constexpr, yyDollar[1].strpos.Pos}
+		}
+	case 123:
+		yyDollar = yyS[yypt-3 : yypt+1]
+		//line grammar.y:547
+		{
+			yyVAL.constexpr = yyDollar[2].constexpr
+		}
+	case 124:
+		yyDollar = yyS[yypt-3 : yypt+1]
+		//line grammar.y:551
+		{
+			yyVAL.complit = &ConstCompositeLit{yyDollar[1].typeexpr, nil, yyDollar[2].pos}
+		}
+	case 125:
+		yyDollar = yyS[yypt-5 : yypt+1]
+		//line grammar.y:553
+		{
+			yyVAL.complit = &ConstCompositeLit{yyDollar[1].typeexpr, yyDollar[3].kvlits, yyDollar[2].pos}
+		}
+	case 126:
+		yyDollar = yyS[yypt-1 : yypt+1]
+		//line grammar.y:557
+		{
+			yyVAL.kvlits = []KVLit{yyDollar[1].kvlit}
+		}
+	case 127:
+		yyDollar = yyS[yypt-3 : yypt+1]
+		//line grammar.y:559
+		{
+			yyVAL.kvlits = append(yyDollar[1].kvlits, yyDollar[3].kvlit)
+		}
+	case 128:
+		yyDollar = yyS[yypt-1 : yypt+1]
+		//line grammar.y:563
+		{
+			yyVAL.kvlit = KVLit{Value: yyDollar[1].constexpr}
+		}
+	case 129:
+		yyDollar = yyS[yypt-3 : yypt+1]
+		//line grammar.y:565
+		{
+			yyVAL.kvlit = KVLit{Key: yyDollar[1].constexpr, Value: yyDollar[3].constexpr}
+		}
+	case 132:
+		yyDollar = yyS[yypt-3 : yypt+1]
+		//line grammar.y:574
+		{
+			// Create *ErrorDef starting with a copy of error_details, filling in the
+			// name and params
+			ed := yyDollar[3].errordef
+			ed.NamePos = NamePos{Name: yyDollar[1].strpos.String, Pos: yyDollar[1].strpos.Pos}
+			ed.Params = yyDollar[2].fields
+			eds := &lexVDLFile(yylex).ErrorDefs
+			*eds = append(*eds, &ed)
+		}
+	case 133:
+		yyDollar = yyS[yypt-0 : yypt+1]
+		//line grammar.y:586
+		{
+			yyVAL.errordef = ErrorDef{}
+		}
+	case 134:
+		yyDollar = yyS[yypt-2 : yypt+1]
+		//line grammar.y:588
+		{
+			yyVAL.errordef = ErrorDef{}
+		}
+	case 135:
+		yyDollar = yyS[yypt-4 : yypt+1]
+		//line grammar.y:590
+		{
+			yyVAL.errordef = yyDollar[2].errordef
+		}
+	case 136:
+		yyDollar = yyS[yypt-1 : yypt+1]
+		//line grammar.y:594
+		{
+			yyVAL.errordef = yyDollar[1].errordef
+		}
+	case 137:
+		yyDollar = yyS[yypt-3 : yypt+1]
+		//line grammar.y:596
+		{
+			// Merge each ErrorDef in-order to build the final ErrorDef.
+			yyVAL.errordef = yyDollar[1].errordef
+			switch {
+			case len(yyDollar[3].errordef.Actions) > 0:
+				yyVAL.errordef.Actions = append(yyVAL.errordef.Actions, yyDollar[3].errordef.Actions...)
+			case len(yyDollar[3].errordef.Formats) > 0:
+				yyVAL.errordef.Formats = append(yyVAL.errordef.Formats, yyDollar[3].errordef.Formats...)
+			}
+		}
+	case 138:
+		yyDollar = yyS[yypt-1 : yypt+1]
+		//line grammar.y:609
+		{
+			yyVAL.errordef = ErrorDef{Actions: []StringPos{yyDollar[1].strpos}}
+		}
+	case 139:
+		yyDollar = yyS[yypt-3 : yypt+1]
+		//line grammar.y:611
+		{
+			yyVAL.errordef = ErrorDef{Formats: []LangFmt{{Lang: yyDollar[1].strpos, Fmt: yyDollar[3].strpos}}}
+		}
+	case 140:
+		yyDollar = yyS[yypt-1 : yypt+1]
+		//line grammar.y:623
+		{
+			yyVAL.strpos = yyDollar[1].strpos
+		}
+	case 141:
+		yyDollar = yyS[yypt-3 : yypt+1]
+		//line grammar.y:625
+		{
+			yyVAL.strpos = StringPos{"\"" + yyDollar[1].strpos.String + "\"." + yyDollar[3].strpos.String, yyDollar[1].strpos.Pos}
+		}
+	case 142:
+		yyDollar = yyS[yypt-1 : yypt+1]
+		//line grammar.y:630
+		{
+			yyVAL.strpos = yyDollar[1].strpos
+		}
+	case 143:
+		yyDollar = yyS[yypt-3 : yypt+1]
+		//line grammar.y:632
+		{
+			yyVAL.strpos = StringPos{yyDollar[1].strpos.String + "." + yyDollar[3].strpos.String, yyDollar[1].strpos.Pos}
+		}
+	case 144:
+		yyDollar = yyS[yypt-0 : yypt+1]
+		//line grammar.y:636
+		{
+			yyVAL.typeexpr = nil
+		}
+	case 145:
+		yyDollar = yyS[yypt-1 : yypt+1]
+		//line grammar.y:638
+		{
+			yyVAL.typeexpr = yyDollar[1].typeexpr
+		}
+	}
+	goto yystack /* stack new state and value */
+}
diff --git a/lib/vdl/parse/grammar_gen.sh b/lib/vdl/parse/grammar_gen.sh
new file mode 100755
index 0000000..dad7b5f
--- /dev/null
+++ b/lib/vdl/parse/grammar_gen.sh
@@ -0,0 +1,36 @@
+#!/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.
+
+# Generate the grammar.go source file, which contains the parser, by running
+# this shell script in the same directory, or by running go generate.  This also
+# generates grammar.y.debug, which contains a list of all states produced for
+# the parser, and some stats.
+
+set -e
+
+go tool yacc -o grammar.y.tmp.go -v grammar.y.debug.tmp grammar.y
+gofmt -l -w grammar.y.tmp.go
+cat - grammar.y.tmp.go > grammar.y.go <<EOF
+// 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.
+
+EOF
+cat - grammar.y.debug.tmp > grammar.y.debug <<EOF
+***** PLEASE READ THIS! DO NOT DELETE THIS BLOCK! *****
+* The main reason this file has been generated and submitted is to try to ensure
+* we never submit changes that cause shift/reduce or reduce/reduce conflicts.
+* The Go yacc tool doesn't support the %expect directive, and will happily
+* generate a parser even if such conflicts exist; it's up to the developer
+* running the tool to notice that an error message is reported.  The bottom of
+* this file contains stats, including the number of conflicts.  If you're
+* reviewing a change make sure it says 0 conflicts.
+*
+* If you're updating the grammar, just cut-and-paste this message from the old
+* file to the new one, so that this comment block persists.
+***** PLEASE READ THIS! DO NOT DELETE THIS BLOCK! *****
+EOF
+
+rm grammar.y.debug.tmp grammar.y.tmp.go
diff --git a/lib/vdl/parse/parse.go b/lib/vdl/parse/parse.go
new file mode 100644
index 0000000..75fa6c7
--- /dev/null
+++ b/lib/vdl/parse/parse.go
@@ -0,0 +1,779 @@
+// 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 parse implements the VDL parser, converting source files into a parse
+// tree.  The ParseFile function is the main entry point.
+package parse
+
+//go:generate ./grammar_gen.sh
+
+// This is the only file in this package that uses the yacc-generated parser
+// with entrypoint yyParse.  The result of the parse is the simple parse.File
+// representation, which is used by the compilation stage.
+//
+// TODO(toddw): The yacc-generated parser returns pretty lousy error messages;
+// basically "syntax error" is the only string returned.  Improve them.
+import (
+	"fmt"
+	"io"
+	"log"
+	"math/big"
+	"path"
+	"strconv"
+	"strings"
+	"text/scanner"
+
+	"v.io/x/ref/lib/vdl/vdlutil"
+)
+
+// Opts specifies vdl parsing options.
+type Opts struct {
+	ImportsOnly bool // Only parse imports; skip everything else.
+}
+
+// ParseFile takes a file name, the contents of the vdl file src, and the
+// accumulated errors, and parses the vdl into a parse.File containing the parse
+// tree.  Returns nil if any errors are encountered, with errs containing more
+// information.  Otherwise returns the parsed File.
+func ParseFile(fileName string, src io.Reader, opts Opts, errs *vdlutil.Errors) *File {
+	start := startFile
+	if opts.ImportsOnly {
+		start = startFileImports
+	}
+	return parse(fileName, src, start, errs)
+}
+
+// ParseConfig takes a file name, the contents of the config file src, and the
+// accumulated errors, and parses the config into a parse.Config containing the
+// parse tree.  Returns nil if any errors are encountered, with errs containing
+// more information.  Otherwise returns the parsed Config.
+func ParseConfig(fileName string, src io.Reader, opts Opts, errs *vdlutil.Errors) *Config {
+	start := startConfig
+	if opts.ImportsOnly {
+		start = startConfigImports
+	}
+	// Since the syntax is so similar between config files and vdl files, we just
+	// parse it as a vdl file and populate Config afterwards.
+	file := parse(fileName, src, start, errs)
+	if file == nil {
+		return nil
+	}
+	if len(file.ErrorDefs) > 0 || len(file.TypeDefs) > 0 || len(file.Interfaces) > 0 {
+		errs.Errorf("%s: config files may not contain error, type or interface definitions", fileName)
+		return nil
+	}
+	config := &Config{
+		FileName:  fileName,
+		Doc:       file.Doc,
+		ConfigDef: file.PackageDef,
+		Imports:   file.Imports,
+		Config:    file.ConstDefs[0].Expr,
+		ConstDefs: file.ConstDefs[1:],
+	}
+	if len(config.ConstDefs) == 0 {
+		config.ConstDefs = nil
+	}
+	if opts.ImportsOnly {
+		// Clear out the const expression from the config clause.
+		config.Config = nil
+		config.ConstDefs = nil
+	}
+	return config
+}
+
+func parse(fileName string, src io.Reader, startTok int, errs *vdlutil.Errors) *File {
+	if errs == nil {
+		log.Fatal("Nil errors specified for Parse")
+	}
+	origErrs := errs.NumErrors()
+	lex := newLexer(fileName, src, startTok, errs)
+	if errCode := yyParse(lex); errCode != 0 {
+		errs.Errorf("%s: yyParse returned error code %v", fileName, errCode)
+	}
+	lex.attachComments()
+	if startTok == startFile || startTok == startConfig {
+		vdlutil.Vlog.Printf("PARSE RESULTS\n\n%v\n\n", lex.vdlFile)
+	}
+	if origErrs != errs.NumErrors() {
+		return nil
+	}
+	return lex.vdlFile
+}
+
+// ParseExprs parses data into a slice of parsed const expressions.  The input
+// data is specified in VDL syntax, with commas separating multiple expressions.
+// There must be at least one expression specified in data.  Errors are returned
+// in errs.
+func ParseExprs(data string, errs *vdlutil.Errors) []ConstExpr {
+	const name = "exprs"
+	lex := newLexer(name, strings.NewReader(data), startExprs, errs)
+	if errCode := yyParse(lex); errCode != 0 {
+		errs.Errorf("vdl: yyParse returned error code %d", errCode)
+	}
+	return lex.exprs
+}
+
+// ExtractExprPackagePaths returns any package paths that appear in named constants
+// in expr. i.e. "a/b/c".Foo => "a/b/c".
+func ExtractExprPackagePaths(expr ConstExpr) []string {
+	var paths []string
+	switch e := expr.(type) {
+	case *ConstNamed:
+		if path := packageFromName(e.Name); len(path) > 0 {
+			paths = append(paths, path)
+		}
+	case *ConstCompositeLit:
+		for _, kv := range e.KVList {
+			paths = append(paths, ExtractExprPackagePaths(kv.Key)...)
+			paths = append(paths, ExtractExprPackagePaths(kv.Value)...)
+		}
+		paths = append(paths, ExtractTypePackagePaths(e.Type)...)
+	case *ConstIndexed:
+		paths = append(paths, ExtractExprPackagePaths(e.Expr)...)
+		paths = append(paths, ExtractExprPackagePaths(e.IndexExpr)...)
+	case *ConstTypeConv:
+		paths = append(paths, ExtractTypePackagePaths(e.Type)...)
+		paths = append(paths, ExtractExprPackagePaths(e.Expr)...)
+	case *ConstTypeObject:
+		paths = append(paths, ExtractTypePackagePaths(e.Type)...)
+	case *ConstBinaryOp:
+		paths = append(paths, ExtractExprPackagePaths(e.Lexpr)...)
+		paths = append(paths, ExtractExprPackagePaths(e.Rexpr)...)
+	case *ConstUnaryOp:
+		paths = append(paths, ExtractExprPackagePaths(e.Expr)...)
+	default:
+		// leaf expression with no embedded expressions or types.
+	}
+	return paths
+}
+
+func ExtractTypePackagePaths(typ Type) []string {
+	var paths []string
+	switch t := typ.(type) {
+	case *TypeNamed:
+		if path := packageFromName(t.Name); len(path) > 0 {
+			paths = append(paths, path)
+		}
+	case *TypeArray:
+		paths = append(paths, ExtractTypePackagePaths(t.Elem)...)
+	case *TypeList:
+		paths = append(paths, ExtractTypePackagePaths(t.Elem)...)
+	case *TypeSet:
+		paths = append(paths, ExtractTypePackagePaths(t.Key)...)
+	case *TypeMap:
+		paths = append(paths, ExtractTypePackagePaths(t.Key)...)
+		paths = append(paths, ExtractTypePackagePaths(t.Elem)...)
+	case *TypeStruct:
+		for _, f := range t.Fields {
+			paths = append(paths, ExtractTypePackagePaths(f.Type)...)
+		}
+	case *TypeUnion:
+		for _, f := range t.Fields {
+			paths = append(paths, ExtractTypePackagePaths(f.Type)...)
+		}
+	case *TypeOptional:
+		paths = append(paths, ExtractTypePackagePaths(t.Base)...)
+	default:
+		// leaf type with no embedded types.
+	}
+	return paths
+}
+
+func packageFromName(name string) string {
+	if strings.HasPrefix(name, `"`) {
+		if parts := strings.SplitN(name[1:], `".`, 2); len(parts) == 2 {
+			return parts[0]
+		}
+	}
+	return ""
+}
+
+// lexer implements the yyLexer interface for the yacc-generated parser.
+//
+// An oddity: lexer also holds the result of the parse.  Most yacc examples hold
+// parse results in package-scoped (global) variables, but doing that would mean
+// we wouldn't be able to run separate parses concurrently.  To enable that we'd
+// need each invocation of yyParse to mutate its own result, but unfortunately
+// the Go yacc tool doesn't provide any way to pass extra arguments to yyParse.
+//
+// So we cheat and hold the parse result in the lexer, and in the yacc rules we
+// call lexVDLFile(yylex) to convert from the yyLexer interface back to the
+// concrete lexer type, and retrieve a pointer to the parse result.
+type lexer struct {
+	// Fields for lexing / scanning the input source file.
+	name     string
+	scanner  scanner.Scanner
+	errs     *vdlutil.Errors
+	startTok int   // One of our dummy start tokens.
+	started  bool  // Has the dummy start token already been emitted?
+	sawEOF   bool  // Have we already seen the end-of-file?
+	prevTok  token // Previous token, used for auto-semicolons and errors.
+
+	// Fields holding the result of file and config parsing.
+	comments commentMap
+	vdlFile  *File
+
+	// Field holding the result of expr parsing.
+	exprs []ConstExpr
+}
+
+func newLexer(fileName string, src io.Reader, startTok int, errs *vdlutil.Errors) *lexer {
+	l := &lexer{name: fileName, errs: errs, startTok: startTok, vdlFile: &File{BaseName: path.Base(fileName)}}
+	l.comments.init()
+	l.scanner.Init(src)
+	// Don't produce character literal tokens, but do scan comments.
+	l.scanner.Mode = scanner.ScanIdents | scanner.ScanFloats | scanner.ScanStrings | scanner.ScanRawStrings | scanner.ScanComments
+	// Don't treat '\n' as whitespace, so we can auto-insert semicolons.
+	l.scanner.Whitespace = 1<<'\t' | 1<<'\r' | 1<<' '
+	l.scanner.Error = func(s *scanner.Scanner, msg string) {
+		l.Error(msg)
+	}
+	return l
+}
+
+type token struct {
+	t    rune
+	text string
+	pos  Pos
+}
+
+func (t token) String() string {
+	return fmt.Sprintf("%v %U %s", t.pos, t.t, t.text)
+}
+
+// The lex* functions below all convert the yyLexer input arg into a concrete
+// lexer as their first step.  The type conversion is always safe since we're
+// the ones who called yyParse, and thus know the concrete type is always lexer.
+
+// lexVDLFile retrieves the File parse result from the yyLexer interface.  This
+// is called in the yacc rules to fill in the parse result.
+func lexVDLFile(yylex yyLexer) *File {
+	return yylex.(*lexer).vdlFile
+}
+
+// lexPosErrorf adds an error with positional information, on a type
+// implementing the yyLexer interface.  This is called in the yacc rules to
+// throw errors.
+func lexPosErrorf(yylex yyLexer, pos Pos, format string, v ...interface{}) {
+	yylex.(*lexer).posErrorf(pos, format, v...)
+}
+
+// lexGenEOF tells the lexer to generate EOF tokens from now on, as if the end
+// of file had been seen.  This is called in the yacc rules to terminate the
+// parse even if the file still has tokens.
+func lexGenEOF(yylex yyLexer) {
+	yylex.(*lexer).sawEOF = true
+}
+
+// lexStoreExprs stores the parsed exprs in the lexer.
+func lexStoreExprs(yylex yyLexer, exprs []ConstExpr) {
+	yylex.(*lexer).exprs = exprs
+}
+
+var keywords = map[string]int{
+	"const":      tCONST,
+	"enum":       tENUM,
+	"error":      tERROR,
+	"import":     tIMPORT,
+	"interface":  tINTERFACE,
+	"map":        tMAP,
+	"package":    tPACKAGE,
+	"set":        tSET,
+	"stream":     tSTREAM,
+	"struct":     tSTRUCT,
+	"type":       tTYPE,
+	"typeobject": tTYPEOBJECT,
+	"union":      tUNION,
+}
+
+type nextRune struct {
+	t  rune
+	id int
+}
+
+// knownPunct is a map of our known punctuation.  We support 1 and 2 rune
+// combinations, where 2 rune combos must be immediately adjacent with no
+// intervening whitespace.  The 2-rune combos always take precedence over the
+// 1-rune combos.  Every entry is a valid 1-rune combo, which is returned as-is
+// without a special token id; the ascii value represents itself.
+var knownPunct = map[rune][]nextRune{
+	';': nil,
+	':': nil,
+	',': nil,
+	'.': nil,
+	'*': nil,
+	'(': nil,
+	')': nil,
+	'[': nil,
+	']': nil,
+	'{': nil,
+	'}': nil,
+	'+': nil,
+	'-': nil,
+	'/': nil,
+	'%': nil,
+	'^': nil,
+	'?': nil,
+	'!': {{'=', tNE}},
+	'=': {{'=', tEQEQ}},
+	'<': {{'=', tLE}, {'<', tLSH}},
+	'>': {{'=', tGE}, {'>', tRSH}},
+	'|': {{'|', tOROR}},
+	'&': {{'&', tANDAND}},
+}
+
+// autoSemi determines whether to automatically add a semicolon, based on the
+// rule that semicolons are always added at the end of each line after certain
+// tokens.  The Go auto-semicolon rule is described here:
+//   http://golang.org/ref/spec#Semicolons
+func autoSemi(prevTok token) bool {
+	return prevAutoSemi[prevTok.t] && prevTok.pos.IsValid()
+}
+
+var prevAutoSemi = map[rune]bool{
+	scanner.Ident:     true,
+	scanner.Int:       true,
+	scanner.Float:     true,
+	scanner.String:    true,
+	scanner.RawString: true,
+	')':               true,
+	']':               true,
+	'}':               true,
+	'>':               true,
+}
+
+const yaccEOF int = 0 // yacc interprets 0 as the end-of-file marker
+
+func init() {
+	// yyDebug is defined in the yacc-generated grammar.go file.  Setting it to 1
+	// only produces output on syntax errors; set it to 4 to generate full debug
+	// output.  Sadly yacc doesn't give position information describing the error.
+	yyDebug = 1
+}
+
+// A note on the comment-tracking strategy.  During lexing we generate
+// commentBlocks, defined as a sequence of adjacent or abutting comments (either
+// // or /**/) with no intervening tokens.  Adjacent means that the previous
+// comment ends on the line immediately before the next one starts, and abutting
+// means that the previous comment ends on the same line as the next one starts.
+//
+// At the end of the parse we try to attach comment blocks to parse tree items.
+// We use a heuristic that works for common cases, but isn't perfect - it
+// mis-associates some styles of comments, and we don't ensure all comment
+// blocks will be associated to an item.
+
+type commentBlock struct {
+	text      string
+	firstLine int
+	lastLine  int
+}
+
+// update returns true and adds tok to this block if tok is adjacent or
+// abutting, otherwise it returns false without mutating the block.  Since we're
+// handling newlines explicitly in the lexer, we never get comment tokens with
+// trailing newlines.  We can get embedded newlines via /**/ style comments.
+func (cb *commentBlock) update(tok token) bool {
+	if cb.text == "" {
+		// First update in this block.
+		cb.text = tok.text
+		cb.firstLine = tok.pos.Line
+		cb.lastLine = tok.pos.Line + strings.Count(tok.text, "\n")
+		return true
+	}
+	if cb.lastLine >= tok.pos.Line-1 {
+		// The tok is adjacent or abutting.
+		if cb.lastLine == tok.pos.Line-1 {
+			// The tok is adjacent - need a newline.
+			cb.text += "\n"
+			cb.lastLine++
+		}
+		cb.text += tok.text
+		cb.lastLine += strings.Count(tok.text, "\n")
+		return true
+	}
+	return false
+}
+
+// commentMap keeps track of blocks of comments in a file.  We store comment
+// blocks in maps by first line, and by last line.  Note that technically there
+// could be more than one commentBlock ending on the same line, due to /**/
+// style comments.  We ignore this rare case and just keep the first one.
+type commentMap struct {
+	byFirst      map[int]commentBlock
+	byLast       map[int]commentBlock
+	cur          commentBlock
+	prevTokenPos Pos
+}
+
+func (cm *commentMap) init() {
+	cm.byFirst = make(map[int]commentBlock)
+	cm.byLast = make(map[int]commentBlock)
+}
+
+// addComment adds a comment token to the map, either appending to the current
+// block or ending the current block and starting a new one.
+func (cm *commentMap) addComment(tok token) {
+	if !cm.cur.update(tok) {
+		cm.endBlock()
+		if !cm.cur.update(tok) {
+			panic(fmt.Errorf("vdl: couldn't update current comment block with token %v", tok))
+		}
+	}
+	// Here's an example of why we need the special case endBlock logic.
+	//
+	//   type Foo struct {
+	//     // doc1
+	//     A int // doc2
+	//     // doc3
+	//     B int
+	//   }
+	//
+	// The problem is that without the special-case, we'd group doc2 and doc3
+	// together into the same block.  That may actually be correct some times, but
+	// it's more common for doc3 to be semantically associated with field B.  Thus
+	// if we've already seen any token on the same line as this comment block, we
+	// end the block immediately.  This means that comments appearing on the same
+	// line as any other token are forced to be a single comment block.
+	if cm.prevTokenPos.Line == tok.pos.Line {
+		cm.endBlock()
+	}
+}
+
+func (cm *commentMap) handleToken(tok token) {
+	cm.endBlock()
+	cm.prevTokenPos = tok.pos
+}
+
+// endBlock adds the the current comment block to the map, and resets it in
+// preparation for new comments to be added.  In the rare case where we see
+// comment blocks that either start or end on the same line, we just keep the
+// first comment block that was inserted.
+func (cm *commentMap) endBlock() {
+	_, inFirst := cm.byFirst[cm.cur.firstLine]
+	_, inLast := cm.byLast[cm.cur.lastLine]
+	if cm.cur.text != "" && !inFirst && !inLast {
+		cm.byFirst[cm.cur.firstLine] = cm.cur
+		cm.byLast[cm.cur.lastLine] = cm.cur
+	}
+	cm.cur.text = ""
+	cm.cur.firstLine = 0
+	cm.cur.lastLine = 0
+}
+
+// getDoc returns the documentation string associated with pos.  Our rule is the
+// last line of the documentation must end on the line immediately before pos.
+// Once a comment block has been returned it isn't eligible to be attached to
+// any other item, and is deleted from the map.
+//
+// The returned string is either empty, or is newline terminated.
+func (cm *commentMap) getDoc(pos Pos) string {
+	block := cm.byLast[pos.Line-1]
+	if block.text == "" {
+		return ""
+	}
+	doc := block.text + "\n"
+	delete(cm.byFirst, block.firstLine)
+	delete(cm.byLast, block.lastLine)
+	return doc
+}
+
+// getDocSuffix returns the suffix documentation associated with pos.  Our rule
+// is the first line of the documentation must be on the same line as pos.  Once
+// a comment block has been returned it isn't eligible to be attached to any
+// other item, and is deleted from the map.
+//
+// The returned string is either empty, or has a leading space.
+func (cm *commentMap) getDocSuffix(pos Pos) string {
+	block := cm.byFirst[pos.Line]
+	if block.text == "" {
+		return ""
+	}
+	doc := " " + block.text
+	delete(cm.byFirst, block.firstLine)
+	delete(cm.byLast, block.lastLine)
+	return doc
+}
+
+// getFileDoc returns the file documentation.  Our rule is that the first line
+// of the documentation must occur on the first line of the file, and all other
+// comments must have already been attached.  Once a comment block has been
+// returned it isn't eligible to be attached to any other item, and is deleted
+// from the map.
+//
+// The returned string is either empty, or is newline terminated.
+func (cm *commentMap) getFileDoc() string {
+	block := cm.byFirst[1]
+	if block.text == "" {
+		return ""
+	}
+	doc := block.text + "\n"
+	delete(cm.byFirst, block.firstLine)
+	delete(cm.byLast, block.lastLine)
+	return doc
+}
+
+func attachTypeComments(t Type, cm *commentMap, suffix bool) {
+	switch tu := t.(type) {
+	case *TypeEnum:
+		for _, label := range tu.Labels {
+			if suffix {
+				label.DocSuffix = cm.getDocSuffix(label.Pos)
+			} else {
+				label.Doc = cm.getDoc(label.Pos)
+			}
+		}
+	case *TypeArray:
+		attachTypeComments(tu.Elem, cm, suffix)
+	case *TypeList:
+		attachTypeComments(tu.Elem, cm, suffix)
+	case *TypeSet:
+		attachTypeComments(tu.Key, cm, suffix)
+	case *TypeMap:
+		attachTypeComments(tu.Key, cm, suffix)
+		attachTypeComments(tu.Elem, cm, suffix)
+	case *TypeStruct:
+		for _, field := range tu.Fields {
+			if suffix {
+				field.DocSuffix = cm.getDocSuffix(field.Pos)
+			} else {
+				field.Doc = cm.getDoc(field.Pos)
+			}
+			attachTypeComments(field.Type, cm, suffix)
+		}
+	case *TypeUnion:
+		for _, field := range tu.Fields {
+			if suffix {
+				field.DocSuffix = cm.getDocSuffix(field.Pos)
+			} else {
+				field.Doc = cm.getDoc(field.Pos)
+			}
+			attachTypeComments(field.Type, cm, suffix)
+		}
+	case *TypeOptional:
+		attachTypeComments(tu.Base, cm, suffix)
+	case *TypeNamed:
+		// Terminate the recursion at named types.
+	default:
+		panic(fmt.Errorf("vdl: unhandled type %#v", t))
+	}
+}
+
+// attachComments causes all comments collected during the parse to be attached
+// to the appropriate parse tree items.  This should only be called after the
+// parse has completed.
+func (l *lexer) attachComments() {
+	f := l.vdlFile
+	// First attach all suffix docs - these occur on the same line.
+	f.PackageDef.DocSuffix = l.comments.getDocSuffix(f.PackageDef.Pos)
+	for _, x := range f.Imports {
+		x.DocSuffix = l.comments.getDocSuffix(x.Pos)
+	}
+	for _, x := range f.ErrorDefs {
+		x.DocSuffix = l.comments.getDocSuffix(x.Pos)
+	}
+	for _, x := range f.TypeDefs {
+		x.DocSuffix = l.comments.getDocSuffix(x.Pos)
+		attachTypeComments(x.Type, &l.comments, true)
+	}
+	for _, x := range f.ConstDefs {
+		x.DocSuffix = l.comments.getDocSuffix(x.Pos)
+	}
+	for _, x := range f.Interfaces {
+		x.DocSuffix = l.comments.getDocSuffix(x.Pos)
+		for _, y := range x.Embeds {
+			y.DocSuffix = l.comments.getDocSuffix(y.Pos)
+		}
+		for _, y := range x.Methods {
+			y.DocSuffix = l.comments.getDocSuffix(y.Pos)
+		}
+	}
+	// Now attach the docs - these occur on the line immediately before.
+	f.PackageDef.Doc = l.comments.getDoc(f.PackageDef.Pos)
+	for _, x := range f.Imports {
+		x.Doc = l.comments.getDoc(x.Pos)
+	}
+	for _, x := range f.ErrorDefs {
+		x.Doc = l.comments.getDoc(x.Pos)
+	}
+	for _, x := range f.TypeDefs {
+		x.Doc = l.comments.getDoc(x.Pos)
+		attachTypeComments(x.Type, &l.comments, false)
+	}
+	for _, x := range f.ConstDefs {
+		x.Doc = l.comments.getDoc(x.Pos)
+	}
+	for _, x := range f.Interfaces {
+		x.Doc = l.comments.getDoc(x.Pos)
+		for _, y := range x.Embeds {
+			y.Doc = l.comments.getDoc(y.Pos)
+		}
+		for _, y := range x.Methods {
+			y.Doc = l.comments.getDoc(y.Pos)
+		}
+	}
+	// Finally attach the top-level file doc - this occurs on the first line.
+	f.Doc = l.comments.getFileDoc()
+}
+
+// nextToken uses the text/scanner package to scan the input for the next token.
+func (l *lexer) nextToken() (tok token) {
+	tok.t = l.scanner.Scan()
+	tok.text = l.scanner.TokenText()
+	// Both Pos and scanner.Position start line and column numbering at 1.
+	tok.pos = Pos{Line: l.scanner.Position.Line, Col: l.scanner.Position.Column}
+	return
+}
+
+// handleImag handles imaginary literals "[number]i" by peeking ahead.
+func (l *lexer) handleImag(tok token, lval *yySymType) bool {
+	if l.scanner.Peek() != 'i' {
+		return false
+	}
+	l.scanner.Next()
+
+	rat := new(big.Rat)
+	if _, ok := rat.SetString(tok.text); !ok {
+		l.posErrorf(tok.pos, "can't convert token [%v] to imaginary literal", tok)
+	}
+	lval.imagpos.pos = tok.pos
+	lval.imagpos.imag = (*BigImag)(rat)
+	return true
+}
+
+// translateToken takes the token we just scanned, and translates it into a
+// token usable by yacc (lval and id).  The done return arg is true when a real
+// yacc token was generated, or false if we need another next/translate pass.
+func (l *lexer) translateToken(tok token, lval *yySymType) (id int, done bool) {
+	switch tok.t {
+	case scanner.EOF:
+		l.sawEOF = true
+		if autoSemi(l.prevTok) {
+			return ';', true
+		}
+		return yaccEOF, true
+
+	case '\n':
+		if autoSemi(l.prevTok) {
+			return ';', true
+		}
+		// Returning done=false ensures next/translate will be called again so that
+		// this newline is skipped; id=yaccEOF is a dummy value that's ignored.
+		return yaccEOF, false
+
+	case scanner.String, scanner.RawString:
+		var err error
+		lval.strpos.Pos = tok.pos
+		lval.strpos.String, err = strconv.Unquote(tok.text)
+		if err != nil {
+			l.posErrorf(tok.pos, "can't convert token [%v] to string literal", tok)
+		}
+		return tSTRLIT, true
+
+	case scanner.Int:
+		if l.handleImag(tok, lval) {
+			return tIMAGLIT, true
+		}
+		lval.intpos.pos = tok.pos
+		lval.intpos.int = new(big.Int)
+		if _, ok := lval.intpos.int.SetString(tok.text, 0); !ok {
+			l.posErrorf(tok.pos, "can't convert token [%v] to integer literal", tok)
+		}
+		return tINTLIT, true
+
+	case scanner.Float:
+		if l.handleImag(tok, lval) {
+			return tIMAGLIT, true
+		}
+		lval.ratpos.pos = tok.pos
+		lval.ratpos.rat = new(big.Rat)
+		if _, ok := lval.ratpos.rat.SetString(tok.text); !ok {
+			l.posErrorf(tok.pos, "can't convert token [%v] to float literal", tok)
+		}
+		return tRATLIT, true
+
+	case scanner.Ident:
+		// Either the identifier is a known keyword, or we pass it through as IDENT.
+		if keytok, ok := keywords[tok.text]; ok {
+			lval.pos = tok.pos
+			return keytok, true
+		}
+		lval.strpos.Pos = tok.pos
+		lval.strpos.String = tok.text
+		return tIDENT, true
+
+	case scanner.Comment:
+		l.comments.addComment(tok)
+		// Comments aren't considered tokens, just like the '\n' case.
+		return yaccEOF, false
+
+	default:
+		// Either the rune is in our known punctuation whitelist, or we've hit a
+		// syntax error.
+		if nextRunes, ok := knownPunct[tok.t]; ok {
+			// Peek at the next rune and compare against our list of next runes.  If
+			// we find a match we return the id in next, otherwise just return the
+			// original rune.  This means that 2-rune tokens always take precedence
+			// over 1-rune tokens.  Either way the pos is set to the original rune.
+			lval.pos = tok.pos
+			peek := l.scanner.Peek()
+			for _, next := range nextRunes {
+				if peek == next.t {
+					l.scanner.Next()
+					return next.id, true
+				}
+			}
+			return int(tok.t), true
+		}
+		l.posErrorf(tok.pos, "unexpected token [%v]", tok)
+		l.sawEOF = true
+		return yaccEOF, true
+	}
+}
+
+// Lex is part of the yyLexer interface, called by the yacc-generated parser.
+func (l *lexer) Lex(lval *yySymType) int {
+	// Emit a dummy start token indicating what type of parse we're performing.
+	if !l.started {
+		l.started = true
+		switch l.startTok {
+		case startFileImports, startFile, startConfigImports, startConfig, startExprs:
+			return l.startTok
+		default:
+			panic(fmt.Errorf("vdl: unhandled parse start token %d", l.startTok))
+		}
+	}
+	// Always return EOF after we've scanned it.  This ensures we emit EOF on the
+	// next Lex call after scanning EOF and adding an auto-semicolon.
+	if l.sawEOF {
+		return yaccEOF
+	}
+	// Run next/translate in a loop to handle newline-triggered auto-semicolons;
+	// nextToken needs to generate newline tokens so that we can trigger the
+	// auto-semicolon logic, but if the newline doesn't generate an auto-semicolon
+	// we should skip the token and move on to the next one.
+	for {
+		tok := l.nextToken()
+		if id, done := l.translateToken(tok, lval); done {
+			l.prevTok = tok
+			l.comments.handleToken(tok)
+			return id
+		}
+	}
+}
+
+// Error is part of the yyLexer interface, called by the yacc-generated parser.
+// Unfortunately yacc doesn't give good error information - we dump the position
+// of the previous scanned token as an approximation of where the error is.
+func (l *lexer) Error(s string) {
+	l.posErrorf(l.prevTok.pos, "%s", s)
+}
+
+// posErrorf generates an error with file and pos info.
+func (l *lexer) posErrorf(pos Pos, format string, v ...interface{}) {
+	var posstr string
+	if pos.IsValid() {
+		posstr = pos.String()
+	}
+	l.errs.Errorf(l.name+":"+posstr+" "+format, v...)
+}
diff --git a/lib/vdl/parse/parse_test.go b/lib/vdl/parse/parse_test.go
new file mode 100644
index 0000000..7cbe7f1
--- /dev/null
+++ b/lib/vdl/parse/parse_test.go
@@ -0,0 +1,1507 @@
+// 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 parse_test
+
+// TODO(toddw): Add tests for imaginary literals.
+
+import (
+	"math/big"
+	"reflect"
+	"strings"
+	"testing"
+
+	"v.io/x/ref/lib/vdl/internal/vdltest"
+	"v.io/x/ref/lib/vdl/parse"
+	"v.io/x/ref/lib/vdl/vdlutil"
+)
+
+func pos(line, col int) parse.Pos {
+	return parse.Pos{line, col}
+}
+
+func sp(str string, line, col int) parse.StringPos {
+	return parse.StringPos{String: str, Pos: pos(line, col)}
+}
+
+func lf(l, f parse.StringPos) parse.LangFmt {
+	return parse.LangFmt{Lang: l, Fmt: f}
+}
+
+func np(name string, line, col int) parse.NamePos {
+	return parse.NamePos{Name: name, Pos: pos(line, col)}
+}
+
+func npptr(name string, line, col int) *parse.NamePos {
+	ret := np(name, line, col)
+	return &ret
+}
+
+func tn(name string, line, col int) *parse.TypeNamed {
+	return &parse.TypeNamed{Name: name, P: pos(line, col)}
+}
+
+func cn(name string, line, col int) *parse.ConstNamed {
+	return &parse.ConstNamed{Name: name, P: pos(line, col)}
+}
+
+func cl(lit interface{}, line, col int) *parse.ConstLit {
+	return &parse.ConstLit{Lit: lit, P: pos(line, col)}
+}
+
+// Tests of vdl imports and file parsing.
+type vdlTest struct {
+	name   string
+	src    string
+	expect *parse.File
+	errors []string
+}
+
+func testParseVDL(t *testing.T, test vdlTest, opts parse.Opts) {
+	errs := vdlutil.NewErrors(-1)
+	actual := parse.ParseFile("testfile", strings.NewReader(test.src), opts, errs)
+	vdltest.ExpectResult(t, errs, test.name, test.errors...)
+	if !reflect.DeepEqual(test.expect, actual) {
+		t.Errorf("%v\nEXPECT %+v\nACTUAL %+v", test.name, test.expect, actual)
+	}
+}
+
+func TestParseVDLImports(t *testing.T) {
+	for _, test := range vdlImportsTests {
+		testParseVDL(t, test, parse.Opts{ImportsOnly: true})
+	}
+	for _, test := range vdlFileTests {
+		// We only run the success tests from vdlFileTests on the imports only
+		// parser, since the failure tests are testing failures in stuff after the
+		// imports, which won't cause failures in the imports only parser.
+		//
+		// The imports-only parser isn't supposed to fill in fields after the
+		// imports, so we clear them from the expected result.  We must copy the
+		// file to ensure the actual vdlTests isn't overwritten since the
+		// full-parser tests needs the full expectations.  The test itself doesn't
+		// need to be copied, since it's already copied in the range-for.
+		if test.expect != nil {
+			copyFile := *test.expect
+			test.expect = &copyFile
+			test.expect.TypeDefs = nil
+			test.expect.ConstDefs = nil
+			test.expect.ErrorDefs = nil
+			test.expect.Interfaces = nil
+			testParseVDL(t, test, parse.Opts{ImportsOnly: true})
+		}
+	}
+}
+
+func TestParseVDLFile(t *testing.T) {
+	for _, test := range append(vdlImportsTests, vdlFileTests...) {
+		testParseVDL(t, test, parse.Opts{ImportsOnly: false})
+	}
+}
+
+// Tests of config imports and file parsing.
+type configTest struct {
+	name   string
+	src    string
+	expect *parse.Config
+	errors []string
+}
+
+func testParseConfig(t *testing.T, test configTest, opts parse.Opts) {
+	errs := vdlutil.NewErrors(-1)
+	actual := parse.ParseConfig("testfile", strings.NewReader(test.src), opts, errs)
+	vdltest.ExpectResult(t, errs, test.name, test.errors...)
+	if !reflect.DeepEqual(test.expect, actual) {
+		t.Errorf("%v\nEXPECT %+v\nACTUAL %+v", test.name, test.expect, actual)
+	}
+}
+
+func TestParseConfigImports(t *testing.T) {
+	for _, test := range configTests {
+		// We only run the success tests from configTests on the imports only
+		// parser, since the failure tests are testing failures in stuff after the
+		// imports, which won't cause failures in the imports only parser.
+		//
+		// The imports-only parser isn't supposed to fill in fields after the
+		// imports, so we clear them from the expected result.  We must copy the
+		// file to ensure the actual configTests isn't overwritten since the
+		// full-parser tests needs the full expectations.  The test itself doesn't
+		// need to be copied, since it's already copied in the range-for.
+		if test.expect != nil {
+			copyConfig := *test.expect
+			test.expect = &copyConfig
+			test.expect.Config = nil
+			test.expect.ConstDefs = nil
+			testParseConfig(t, test, parse.Opts{ImportsOnly: true})
+		}
+	}
+}
+
+func TestParseConfig(t *testing.T) {
+	for _, test := range configTests {
+		testParseConfig(t, test, parse.Opts{ImportsOnly: false})
+	}
+}
+
+// vdlImportsTests contains tests of stuff up to and including the imports.
+var vdlImportsTests = []vdlTest{
+	// Empty file isn't allowed (need at least a package clause).
+	{
+		"FAILEmptyFile",
+		"",
+		nil,
+		[]string{"vdl file must start with package clause"}},
+
+	// Comment tests.
+	{
+		"PackageDocOneLiner",
+		`// One liner
+// Another line
+package testpkg`,
+		&parse.File{BaseName: "testfile", PackageDef: parse.NamePos{Name: "testpkg", Pos: pos(3, 9), Doc: `// One liner
+// Another line
+`}},
+		nil},
+	{
+		"PackageDocMultiLiner",
+		`/* Multi liner
+Another line
+*/
+package testpkg`,
+		&parse.File{BaseName: "testfile", PackageDef: parse.NamePos{Name: "testpkg", Pos: pos(4, 9), Doc: `/* Multi liner
+Another line
+*/
+`}},
+		nil},
+	{
+		"FileDocNoPackageDoc",
+		`// File doc, has extra newline so not package doc
+
+package testpkg`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 3, 9), Doc: "// File doc, has extra newline so not package doc\n"},
+		nil},
+	{
+		"FileDocAndPackageDoc",
+		`// File doc
+
+// Package doc
+package testpkg`,
+		&parse.File{BaseName: "testfile", PackageDef: parse.NamePos{Name: "testpkg", Pos: pos(4, 9), Doc: "// Package doc\n"}, Doc: "// File doc\n"},
+		nil},
+	{
+		"FAILUnterminatedComment",
+		`/* Unterminated
+Another line
+package testpkg`,
+		nil,
+		[]string{"comment not terminated"}},
+
+	// Package tests.
+	{
+		"Package",
+		"package testpkg;",
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9)},
+		nil},
+	{
+		"PackageNoSemi",
+		"package testpkg",
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9)},
+		nil},
+	{
+		"FAILBadPackageName",
+		"package foo.bar",
+		nil,
+		[]string{"testfile:1:12 syntax error"}},
+
+	// Import tests.
+	{
+		"EmptyImport",
+		`package testpkg;
+import (
+)`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9)},
+		nil},
+	{
+		"OneImport",
+		`package testpkg;
+import "foo/bar";`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			Imports: []*parse.Import{{Path: "foo/bar", NamePos: np("", 2, 8)}}},
+		nil},
+	{
+		"OneImportLocalNameNoSemi",
+		`package testpkg
+import baz "foo/bar"`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			Imports: []*parse.Import{{Path: "foo/bar", NamePos: np("baz", 2, 8)}}},
+		nil},
+	{
+		"OneImportParens",
+		`package testpkg
+import (
+  "foo/bar";
+)`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			Imports: []*parse.Import{{Path: "foo/bar", NamePos: np("", 3, 3)}}},
+		nil},
+	{
+		"OneImportParensNoSemi",
+		`package testpkg
+import (
+  "foo/bar"
+)`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			Imports: []*parse.Import{{Path: "foo/bar", NamePos: np("", 3, 3)}}},
+		nil},
+	{
+		"MixedImports",
+		`package testpkg
+import "foo/bar"
+import (
+  "baz";"a/b"
+  "c/d"
+)
+import "z"`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			Imports: []*parse.Import{
+				{Path: "foo/bar", NamePos: np("", 2, 8)},
+				{Path: "baz", NamePos: np("", 4, 3)},
+				{Path: "a/b", NamePos: np("", 4, 9)},
+				{Path: "c/d", NamePos: np("", 5, 3)},
+				{Path: "z", NamePos: np("", 7, 8)}}},
+		nil},
+	{
+		"FAILImportParensNotClosed",
+		`package testpkg
+import (
+  "foo/bar"`,
+		nil,
+		[]string{"testfile:3:12 syntax error"}},
+}
+
+// vdlFileTests contains tests of stuff after the imports.
+var vdlFileTests = []vdlTest{
+	// Data type tests.
+	{
+		"TypeNamed",
+		`package testpkg
+type foo bar`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			TypeDefs: []*parse.TypeDef{
+				{NamePos: np("foo", 2, 6), Type: tn("bar", 2, 10)}}},
+		nil},
+	{
+		"TypeNamedQualified",
+		`package testpkg
+type foo bar.baz`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			TypeDefs: []*parse.TypeDef{
+				{NamePos: np("foo", 2, 6), Type: tn("bar.baz", 2, 10)}}},
+		nil},
+	{
+		"TypeNamedQualifiedPath",
+		`package testpkg
+type foo "a/b/c/bar".baz`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			TypeDefs: []*parse.TypeDef{
+				{NamePos: np("foo", 2, 6), Type: tn(`"a/b/c/bar".baz`, 2, 10)}}},
+		nil},
+	{
+		"TypeEnum",
+		`package testpkg
+type foo enum{A;B;C}`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			TypeDefs: []*parse.TypeDef{
+				{NamePos: np("foo", 2, 6), Type: &parse.TypeEnum{
+					Labels: []parse.NamePos{np("A", 2, 15), np("B", 2, 17), np("C", 2, 19)},
+					P:      pos(2, 10)}}}},
+		nil},
+	{
+		"TypeEnumNewlines",
+		`package testpkg
+type foo enum {
+  A
+  B
+  C
+}`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			TypeDefs: []*parse.TypeDef{
+				{NamePos: np("foo", 2, 6), Type: &parse.TypeEnum{
+					Labels: []parse.NamePos{np("A", 3, 3), np("B", 4, 3), np("C", 5, 3)},
+					P:      pos(2, 10)}}}},
+		nil},
+	{
+		"TypeArray",
+		`package testpkg
+type foo [2]bar`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			TypeDefs: []*parse.TypeDef{
+				{NamePos: np("foo", 2, 6), Type: &parse.TypeArray{
+					Len: 2, Elem: tn("bar", 2, 13), P: pos(2, 10)}}}},
+		nil},
+	{
+		"TypeList",
+		`package testpkg
+type foo []bar`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			TypeDefs: []*parse.TypeDef{
+				{NamePos: np("foo", 2, 6), Type: &parse.TypeList{
+					Elem: tn("bar", 2, 12), P: pos(2, 10)}}}},
+		nil},
+	{
+		"TypeSet",
+		`package testpkg
+type foo set[bar]`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			TypeDefs: []*parse.TypeDef{
+				{NamePos: np("foo", 2, 6), Type: &parse.TypeSet{
+					Key: tn("bar", 2, 14), P: pos(2, 10)}}}},
+		nil},
+	{
+		"TypeMap",
+		`package testpkg
+type foo map[bar]baz`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			TypeDefs: []*parse.TypeDef{
+				{NamePos: np("foo", 2, 6), Type: &parse.TypeMap{
+					Key: tn("bar", 2, 14), Elem: tn("baz", 2, 18), P: pos(2, 10)}}}},
+		nil},
+	{
+		"TypeStructOneField",
+		`package testpkg
+type foo struct{a b;}`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			TypeDefs: []*parse.TypeDef{
+				{NamePos: np("foo", 2, 6), Type: &parse.TypeStruct{
+					Fields: []*parse.Field{{NamePos: np("a", 2, 17), Type: tn("b", 2, 19)}},
+					P:      pos(2, 10)}}}},
+		nil},
+	{
+		"TypeStructOneFieldNoSemi",
+		`package testpkg
+type foo struct{a b}`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			TypeDefs: []*parse.TypeDef{
+				{NamePos: np("foo", 2, 6), Type: &parse.TypeStruct{
+					Fields: []*parse.Field{{NamePos: np("a", 2, 17), Type: tn("b", 2, 19)}},
+					P:      pos(2, 10)}}}},
+		nil},
+	{
+		"TypeStructOneFieldNewline",
+		`package testpkg
+type foo struct{
+  a b;
+}`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			TypeDefs: []*parse.TypeDef{
+				{NamePos: np("foo", 2, 6), Type: &parse.TypeStruct{
+					Fields: []*parse.Field{{NamePos: np("a", 3, 3), Type: tn("b", 3, 5)}},
+					P:      pos(2, 10)}}}},
+		nil},
+	{
+		"TypeStructOneFieldNewlineNoSemi",
+		`package testpkg
+type foo struct{
+  a b
+}`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			TypeDefs: []*parse.TypeDef{
+				{NamePos: np("foo", 2, 6), Type: &parse.TypeStruct{
+					Fields: []*parse.Field{{NamePos: np("a", 3, 3), Type: tn("b", 3, 5)}},
+					P:      pos(2, 10)}}}},
+		nil},
+	{
+		"TypeStructOneFieldList",
+		`package testpkg
+type foo struct{a,b,c d}`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			TypeDefs: []*parse.TypeDef{
+				{NamePos: np("foo", 2, 6), Type: &parse.TypeStruct{
+					Fields: []*parse.Field{
+						{NamePos: np("a", 2, 17), Type: tn("d", 2, 23)},
+						{NamePos: np("b", 2, 19), Type: tn("d", 2, 23)},
+						{NamePos: np("c", 2, 21), Type: tn("d", 2, 23)}},
+					P: pos(2, 10)}}}},
+		nil},
+	{
+		"TypeStructMixed",
+		`package testpkg
+type foo struct{
+  a b;c,d e
+  f,g h
+}`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			TypeDefs: []*parse.TypeDef{
+				{NamePos: np("foo", 2, 6), Type: &parse.TypeStruct{
+					Fields: []*parse.Field{
+						{NamePos: np("a", 3, 3), Type: tn("b", 3, 5)},
+						{NamePos: np("c", 3, 7), Type: tn("e", 3, 11)},
+						{NamePos: np("d", 3, 9), Type: tn("e", 3, 11)},
+						{NamePos: np("f", 4, 3), Type: tn("h", 4, 7)},
+						{NamePos: np("g", 4, 5), Type: tn("h", 4, 7)}},
+					P: pos(2, 10)}}}},
+		nil},
+	{
+		"TypeUnion",
+		`package testpkg
+type foo union{A a;B b;C c}`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			TypeDefs: []*parse.TypeDef{
+				{NamePos: np("foo", 2, 6), Type: &parse.TypeUnion{
+					Fields: []*parse.Field{
+						{NamePos: np("A", 2, 16), Type: tn("a", 2, 18)},
+						{NamePos: np("B", 2, 20), Type: tn("b", 2, 22)},
+						{NamePos: np("C", 2, 24), Type: tn("c", 2, 26)}},
+					P: pos(2, 10)}}}},
+		nil},
+	{
+		"TypeUnionNewlines",
+		`package testpkg
+type foo union{
+  A a
+  B b
+  C c
+}`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			TypeDefs: []*parse.TypeDef{
+				{NamePos: np("foo", 2, 6), Type: &parse.TypeUnion{
+					Fields: []*parse.Field{
+						{NamePos: np("A", 3, 3), Type: tn("a", 3, 5)},
+						{NamePos: np("B", 4, 3), Type: tn("b", 4, 5)},
+						{NamePos: np("C", 5, 3), Type: tn("c", 5, 5)}},
+					P: pos(2, 10)}}}},
+		nil},
+	{
+		"TypeOptional",
+		`package testpkg
+type foo union{A a;B ?b;C ?c}`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			TypeDefs: []*parse.TypeDef{
+				{NamePos: np("foo", 2, 6), Type: &parse.TypeUnion{
+					Fields: []*parse.Field{
+						{NamePos: np("A", 2, 16), Type: tn("a", 2, 18)},
+						{NamePos: np("B", 2, 20),
+							Type: &parse.TypeOptional{Base: tn("b", 2, 23), P: pos(2, 22)}},
+						{NamePos: np("C", 2, 25),
+							Type: &parse.TypeOptional{Base: tn("c", 2, 28), P: pos(2, 27)}}},
+					P: pos(2, 10)}}}},
+		nil},
+	{
+		"TypeOptionalNewlines",
+		`package testpkg
+type foo union{
+  A a
+  B ?b
+  C ?c
+}`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			TypeDefs: []*parse.TypeDef{
+				{NamePos: np("foo", 2, 6), Type: &parse.TypeUnion{
+					Fields: []*parse.Field{
+						{NamePos: np("A", 3, 3), Type: tn("a", 3, 5)},
+						{NamePos: np("B", 4, 3),
+							Type: &parse.TypeOptional{Base: tn("b", 4, 6), P: pos(4, 5)}},
+						{NamePos: np("C", 5, 3),
+							Type: &parse.TypeOptional{Base: tn("c", 5, 6), P: pos(5, 5)}}},
+					P: pos(2, 10)}}}},
+		nil},
+	{
+		"FAILTypeStructNotClosed",
+		`package testpkg
+type foo struct{
+  a b`,
+		nil,
+		[]string{"testfile:3:6 syntax error"}},
+	{
+		"FAILTypeStructUnnamedField",
+		`package testpkg
+type foo struct{a}`,
+		nil,
+		[]string{"testfile:2:18 syntax error"}},
+	{
+		"FAILTypeStructUnnamedFieldList",
+		`package testpkg
+type foo struct{a, b}`,
+		nil,
+		[]string{"testfile:2:21 syntax error"}},
+
+	// Const definition tests.
+	{
+		"BoolConst",
+		`package testpkg
+const foo = true
+const bar = false`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			ConstDefs: []*parse.ConstDef{
+				{NamePos: np("foo", 2, 7), Expr: cn("true", 2, 13)},
+				{NamePos: np("bar", 3, 7), Expr: cn("false", 3, 13)}}},
+		nil},
+	{
+		"StringConst",
+		"package testpkg\nconst foo = \"abc\"\nconst bar = `def`",
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			ConstDefs: []*parse.ConstDef{
+				{NamePos: np("foo", 2, 7), Expr: cl("abc", 2, 13)},
+				{NamePos: np("bar", 3, 7), Expr: cl("def", 3, 13)}}},
+		nil},
+	{
+		"IntegerConst",
+		`package testpkg
+const foo = 123`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			ConstDefs: []*parse.ConstDef{
+				{NamePos: np("foo", 2, 7), Expr: cl(big.NewInt(123), 2, 13)}}},
+		nil},
+	{
+		"FloatConst",
+		`package testpkg
+const foo = 1.5`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			ConstDefs: []*parse.ConstDef{
+				{NamePos: np("foo", 2, 7), Expr: cl(big.NewRat(3, 2), 2, 13)}}},
+		nil},
+	{
+		"NamedConst",
+		`package testpkg
+const foo = baz
+const bar = pkg.box`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			ConstDefs: []*parse.ConstDef{
+				{NamePos: np("foo", 2, 7), Expr: cn("baz", 2, 13)},
+				{NamePos: np("bar", 3, 7), Expr: cn("pkg.box", 3, 13)}}},
+		nil},
+	{
+		"NamedConstQualified",
+		`package testpkg
+const foo = bar.baz`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			ConstDefs: []*parse.ConstDef{
+				{NamePos: np("foo", 2, 7), Expr: cn("bar.baz", 2, 13)}}},
+		nil},
+	{
+		"NamedConstQualifiedPath",
+		`package testpkg
+const foo = "a/b/c/bar".baz`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			ConstDefs: []*parse.ConstDef{
+				{NamePos: np("foo", 2, 7), Expr: cn(`"a/b/c/bar".baz`, 2, 13)}}},
+		nil},
+	{
+		"CompLitConst",
+		`package testpkg
+const foo = {"a","b"}`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			ConstDefs: []*parse.ConstDef{
+				{NamePos: np("foo", 2, 7), Expr: &parse.ConstCompositeLit{
+					KVList: []parse.KVLit{
+						{Value: cl("a", 2, 14)},
+						{Value: cl("b", 2, 18)}},
+					P: pos(2, 13)}}}},
+		nil},
+	{
+		"CompLitKVConst",
+		`package testpkg
+const foo = {"a":1,"b":2}`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			ConstDefs: []*parse.ConstDef{
+				{NamePos: np("foo", 2, 7), Expr: &parse.ConstCompositeLit{
+					KVList: []parse.KVLit{
+						{cl("a", 2, 14), cl(big.NewInt(1), 2, 18)},
+						{cl("b", 2, 20), cl(big.NewInt(2), 2, 24)}},
+					P: pos(2, 13)}}}},
+		nil},
+	{
+		"CompLitTypedConst",
+		`package testpkg
+const foo = bar{"a","b"}`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			ConstDefs: []*parse.ConstDef{
+				{NamePos: np("foo", 2, 7), Expr: &parse.ConstCompositeLit{
+					Type: tn("bar", 2, 13),
+					KVList: []parse.KVLit{
+						{Value: cl("a", 2, 17)},
+						{Value: cl("b", 2, 21)}},
+					P: pos(2, 16)}}}},
+		nil},
+	{
+		"CompLitKVTypedConst",
+		`package testpkg
+const foo = bar{"a":1,"b":2}`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			ConstDefs: []*parse.ConstDef{
+				{NamePos: np("foo", 2, 7), Expr: &parse.ConstCompositeLit{
+					Type: tn("bar", 2, 13),
+					KVList: []parse.KVLit{
+						{cl("a", 2, 17), cl(big.NewInt(1), 2, 21)},
+						{cl("b", 2, 23), cl(big.NewInt(2), 2, 27)}},
+					P: pos(2, 16)}}}},
+		nil},
+	{
+		"UnaryOpConst",
+		`package testpkg
+const foo = !false
+const bar = +1
+const baz = -2
+const box = ^3`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			ConstDefs: []*parse.ConstDef{
+				{NamePos: np("foo", 2, 7), Expr: &parse.ConstUnaryOp{
+					Op:   "!",
+					Expr: cn("false", 2, 14),
+					P:    pos(2, 13)},
+				},
+				{NamePos: np("bar", 3, 7), Expr: &parse.ConstUnaryOp{
+					Op:   "+",
+					Expr: cl(big.NewInt(1), 3, 14),
+					P:    pos(3, 13)},
+				},
+				{NamePos: np("baz", 4, 7), Expr: &parse.ConstUnaryOp{
+					Op:   "-",
+					Expr: cl(big.NewInt(2), 4, 14),
+					P:    pos(4, 13)},
+				},
+				{NamePos: np("box", 5, 7), Expr: &parse.ConstUnaryOp{
+					Op:   "^",
+					Expr: cl(big.NewInt(3), 5, 14),
+					P:    pos(5, 13),
+				}}}},
+		nil},
+	{
+		"TypeConvConst",
+		`package testpkg
+const foo = baz(true)
+const bar = pkg.box(false)`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			ConstDefs: []*parse.ConstDef{
+				{NamePos: np("foo", 2, 7), Expr: &parse.ConstTypeConv{
+					Type: tn("baz", 2, 13),
+					Expr: cn("true", 2, 17),
+					P:    pos(2, 13),
+				}},
+				{NamePos: np("bar", 3, 7), Expr: &parse.ConstTypeConv{
+					Type: tn("pkg.box", 3, 13),
+					Expr: cn("false", 3, 21),
+					P:    pos(3, 13),
+				}}}},
+		nil},
+	{
+		"TypeObjectConst",
+		`package testpkg
+const foo = typeobject(bool)
+const bar = typeobject(pkg.box)`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			ConstDefs: []*parse.ConstDef{
+				{NamePos: np("foo", 2, 7), Expr: &parse.ConstTypeObject{
+					Type: tn("bool", 2, 24),
+					P:    pos(2, 13),
+				}},
+				{NamePos: np("bar", 3, 7), Expr: &parse.ConstTypeObject{
+					Type: tn("pkg.box", 3, 24),
+					P:    pos(3, 13),
+				}}}},
+		nil},
+	{
+		"BinaryOpConst",
+		`package testpkg
+const a = true || false
+const b = true && false
+const c = 1 < 2
+const d = 3 > 4
+const e = 5 <= 6
+const f = 7 >= 8
+const g = 9 != 8
+const h = 7 == 6
+const i = 5 + 4
+const j = 3 - 2
+const k = 1 * 2
+const l = 3 / 4
+const m = 5 % 6
+const n = 7 | 8
+const o = 9 & 8
+const p = 7 ^ 6
+const q = 5 << 4
+const r = 3 >> 2`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			ConstDefs: []*parse.ConstDef{
+				{NamePos: np("a", 2, 7),
+					Expr: &parse.ConstBinaryOp{
+						Op:    "||",
+						Lexpr: cn("true", 2, 11),
+						Rexpr: cn("false", 2, 19),
+						P:     pos(2, 16),
+					}},
+				{NamePos: np("b", 3, 7),
+					Expr: &parse.ConstBinaryOp{
+						Op:    "&&",
+						Lexpr: cn("true", 3, 11),
+						Rexpr: cn("false", 3, 19),
+						P:     pos(3, 16),
+					}},
+				{NamePos: np("c", 4, 7),
+					Expr: &parse.ConstBinaryOp{
+						Op:    "<",
+						Lexpr: cl(big.NewInt(1), 4, 11),
+						Rexpr: cl(big.NewInt(2), 4, 15),
+						P:     pos(4, 13),
+					}},
+				{NamePos: np("d", 5, 7),
+					Expr: &parse.ConstBinaryOp{
+						Op:    ">",
+						Lexpr: cl(big.NewInt(3), 5, 11),
+						Rexpr: cl(big.NewInt(4), 5, 15),
+						P:     pos(5, 13),
+					}},
+				{NamePos: np("e", 6, 7),
+					Expr: &parse.ConstBinaryOp{
+						Op:    "<=",
+						Lexpr: cl(big.NewInt(5), 6, 11),
+						Rexpr: cl(big.NewInt(6), 6, 16),
+						P:     pos(6, 13),
+					}},
+				{NamePos: np("f", 7, 7),
+					Expr: &parse.ConstBinaryOp{
+						Op:    ">=",
+						Lexpr: cl(big.NewInt(7), 7, 11),
+						Rexpr: cl(big.NewInt(8), 7, 16),
+						P:     pos(7, 13),
+					}},
+				{NamePos: np("g", 8, 7),
+					Expr: &parse.ConstBinaryOp{
+						Op:    "!=",
+						Lexpr: cl(big.NewInt(9), 8, 11),
+						Rexpr: cl(big.NewInt(8), 8, 16),
+						P:     pos(8, 13),
+					}},
+				{NamePos: np("h", 9, 7),
+					Expr: &parse.ConstBinaryOp{
+						Op:    "==",
+						Lexpr: cl(big.NewInt(7), 9, 11),
+						Rexpr: cl(big.NewInt(6), 9, 16),
+						P:     pos(9, 13),
+					}},
+				{NamePos: np("i", 10, 7),
+					Expr: &parse.ConstBinaryOp{
+						Op:    "+",
+						Lexpr: cl(big.NewInt(5), 10, 11),
+						Rexpr: cl(big.NewInt(4), 10, 15),
+						P:     pos(10, 13),
+					}},
+				{NamePos: np("j", 11, 7),
+					Expr: &parse.ConstBinaryOp{
+						Op:    "-",
+						Lexpr: cl(big.NewInt(3), 11, 11),
+						Rexpr: cl(big.NewInt(2), 11, 15),
+						P:     pos(11, 13),
+					}},
+				{NamePos: np("k", 12, 7),
+					Expr: &parse.ConstBinaryOp{
+						Op:    "*",
+						Lexpr: cl(big.NewInt(1), 12, 11),
+						Rexpr: cl(big.NewInt(2), 12, 15),
+						P:     pos(12, 13),
+					}},
+				{NamePos: np("l", 13, 7),
+					Expr: &parse.ConstBinaryOp{
+						Op:    "/",
+						Lexpr: cl(big.NewInt(3), 13, 11),
+						Rexpr: cl(big.NewInt(4), 13, 15),
+						P:     pos(13, 13),
+					}},
+				{NamePos: np("m", 14, 7),
+					Expr: &parse.ConstBinaryOp{
+						Op:    "%",
+						Lexpr: cl(big.NewInt(5), 14, 11),
+						Rexpr: cl(big.NewInt(6), 14, 15),
+						P:     pos(14, 13),
+					}},
+				{NamePos: np("n", 15, 7),
+					Expr: &parse.ConstBinaryOp{
+						Op:    "|",
+						Lexpr: cl(big.NewInt(7), 15, 11),
+						Rexpr: cl(big.NewInt(8), 15, 15),
+						P:     pos(15, 13),
+					}},
+				{NamePos: np("o", 16, 7),
+					Expr: &parse.ConstBinaryOp{
+						Op:    "&",
+						Lexpr: cl(big.NewInt(9), 16, 11),
+						Rexpr: cl(big.NewInt(8), 16, 15),
+						P:     pos(16, 13),
+					}},
+				{NamePos: np("p", 17, 7),
+					Expr: &parse.ConstBinaryOp{
+						Op:    "^",
+						Lexpr: cl(big.NewInt(7), 17, 11),
+						Rexpr: cl(big.NewInt(6), 17, 15),
+						P:     pos(17, 13),
+					}},
+				{NamePos: np("q", 18, 7),
+					Expr: &parse.ConstBinaryOp{
+						Op:    "<<",
+						Lexpr: cl(big.NewInt(5), 18, 11),
+						Rexpr: cl(big.NewInt(4), 18, 16),
+						P:     pos(18, 13),
+					}},
+				{NamePos: np("r", 19, 7),
+					Expr: &parse.ConstBinaryOp{
+						Op:    ">>",
+						Lexpr: cl(big.NewInt(3), 19, 11),
+						Rexpr: cl(big.NewInt(2), 19, 16),
+						P:     pos(19, 13),
+					}}}},
+		nil},
+	{
+		"FAILConstOnlyName",
+		`package testpkg
+const foo`,
+		nil,
+		[]string{"testfile:2:10 syntax error"}},
+	{
+		"FAILConstNoEquals",
+		`package testpkg
+const foo bar`,
+		nil,
+		[]string{"testfile:2:11 syntax error"}},
+	{
+		"FAILConstNoValue",
+		`package testpkg
+const foo =`,
+		nil,
+		[]string{"testfile:2:12 syntax error"}},
+
+	// Error definition tests.
+	{
+		"ErrorEmpty",
+		`package testpkg
+error()`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9)},
+		nil},
+	{
+		"ErrorDefNoParamsNoDetails1",
+		`package testpkg
+error ErrFoo()`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			ErrorDefs: []*parse.ErrorDef{{NamePos: np("ErrFoo", 2, 7)}}},
+		nil},
+	{
+		"ErrorDefNoParamsNoDetails2",
+		`package testpkg
+error ErrFoo() {}`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			ErrorDefs: []*parse.ErrorDef{{NamePos: np("ErrFoo", 2, 7)}}},
+		nil},
+	{
+		"ErrorDefNoParamsWithDetails1",
+		`package testpkg
+error ErrFoo() {NoRetry}`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			ErrorDefs: []*parse.ErrorDef{{
+				NamePos: np("ErrFoo", 2, 7),
+				Actions: []parse.StringPos{sp("NoRetry", 2, 17)}}}},
+		nil},
+	{
+		"ErrorDefNoParamsWithDetails2",
+		`package testpkg
+error ErrFoo() {"en":"a"}`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			ErrorDefs: []*parse.ErrorDef{{
+				NamePos: np("ErrFoo", 2, 7),
+				Formats: []parse.LangFmt{lf(sp("en", 2, 17), sp("a", 2, 22))}}}},
+		nil},
+	{
+		"ErrorDefNoParamsWithDetails3",
+		`package testpkg
+error ErrFoo() {NoRetry, "en":"a", "zh":"b"}`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			ErrorDefs: []*parse.ErrorDef{{
+				NamePos: np("ErrFoo", 2, 7),
+				Actions: []parse.StringPos{sp("NoRetry", 2, 17)},
+				Formats: []parse.LangFmt{
+					lf(sp("en", 2, 26), sp("a", 2, 31)),
+					lf(sp("zh", 2, 36), sp("b", 2, 41)),
+				}}}},
+		nil},
+	{
+		"ErrorDefWithParamsNoDetails1",
+		`package testpkg
+error ErrFoo(x int, y bool)`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			ErrorDefs: []*parse.ErrorDef{{
+				NamePos: np("ErrFoo", 2, 7),
+				Params: []*parse.Field{
+					{NamePos: np("x", 2, 14), Type: tn("int", 2, 16)},
+					{NamePos: np("y", 2, 21), Type: tn("bool", 2, 23)}}}}},
+		nil},
+	{
+		"ErrorDefWithParamsNoDetails2",
+		`package testpkg
+error ErrFoo(x int, y bool) {}`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			ErrorDefs: []*parse.ErrorDef{{
+				NamePos: np("ErrFoo", 2, 7),
+				Params: []*parse.Field{
+					{NamePos: np("x", 2, 14), Type: tn("int", 2, 16)},
+					{NamePos: np("y", 2, 21), Type: tn("bool", 2, 23)}}}}},
+		nil},
+	{
+		"ErrorDefWithParamsWithDetails1",
+		`package testpkg
+error ErrFoo(x int, y bool) {NoRetry}`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			ErrorDefs: []*parse.ErrorDef{{
+				NamePos: np("ErrFoo", 2, 7),
+				Params: []*parse.Field{
+					{NamePos: np("x", 2, 14), Type: tn("int", 2, 16)},
+					{NamePos: np("y", 2, 21), Type: tn("bool", 2, 23)}},
+				Actions: []parse.StringPos{sp("NoRetry", 2, 30)}}}},
+		nil},
+	{
+		"ErrorDefWithParamsWithDetails2",
+		`package testpkg
+error ErrFoo(x int, y bool) {"en":"a"}`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			ErrorDefs: []*parse.ErrorDef{{
+				NamePos: np("ErrFoo", 2, 7),
+				Params: []*parse.Field{
+					{NamePos: np("x", 2, 14), Type: tn("int", 2, 16)},
+					{NamePos: np("y", 2, 21), Type: tn("bool", 2, 23)}},
+				Formats: []parse.LangFmt{lf(sp("en", 2, 30), sp("a", 2, 35))}}}},
+		nil},
+	{
+		"ErrorDefWithParamsWithDetails3",
+		`package testpkg
+error ErrFoo(x int, y bool) {NoRetry, "en":"a", "zh":"b"}`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			ErrorDefs: []*parse.ErrorDef{{
+				NamePos: np("ErrFoo", 2, 7),
+				Params: []*parse.Field{
+					{NamePos: np("x", 2, 14), Type: tn("int", 2, 16)},
+					{NamePos: np("y", 2, 21), Type: tn("bool", 2, 23)}},
+				Actions: []parse.StringPos{sp("NoRetry", 2, 30)},
+				Formats: []parse.LangFmt{
+					lf(sp("en", 2, 39), sp("a", 2, 44)),
+					lf(sp("zh", 2, 49), sp("b", 2, 54)),
+				}}}},
+		nil},
+	{
+		"ErrorDefMulti",
+		`package testpkg
+error (
+  ErrFoo()
+  ErrBar() {NoRetry, "en":"a", "zh":"b"}
+  ErrBaz(x int, y bool) {NoRetry, "en":"a", "zh":"b"}
+)`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			ErrorDefs: []*parse.ErrorDef{
+				{
+					NamePos: np("ErrFoo", 3, 3),
+				},
+				{
+					NamePos: np("ErrBar", 4, 3),
+					Actions: []parse.StringPos{sp("NoRetry", 4, 13)},
+					Formats: []parse.LangFmt{
+						lf(sp("en", 4, 22), sp("a", 4, 27)),
+						lf(sp("zh", 4, 32), sp("b", 4, 37)),
+					},
+				},
+				{
+					NamePos: np("ErrBaz", 5, 3),
+					Params: []*parse.Field{
+						{NamePos: np("x", 5, 10), Type: tn("int", 5, 12)},
+						{NamePos: np("y", 5, 17), Type: tn("bool", 5, 19)}},
+					Actions: []parse.StringPos{sp("NoRetry", 5, 26)},
+					Formats: []parse.LangFmt{
+						lf(sp("en", 5, 35), sp("a", 5, 40)),
+						lf(sp("zh", 5, 45), sp("b", 5, 50)),
+					},
+				},
+			}},
+		nil},
+
+	// Interface tests.
+	{
+		"InterfaceEmpty",
+		`package testpkg
+type foo interface{}`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			Interfaces: []*parse.Interface{{NamePos: np("foo", 2, 6)}}},
+		nil},
+	{
+		"InterfaceOneMethodOneInUnnamedOut",
+		`package testpkg
+type foo interface{meth1(a b) (c | error)}`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			Interfaces: []*parse.Interface{{NamePos: np("foo", 2, 6),
+				Methods: []*parse.Method{{NamePos: np("meth1", 2, 20),
+					InArgs:  []*parse.Field{{NamePos: np("a", 2, 26), Type: tn("b", 2, 28)}},
+					OutArgs: []*parse.Field{{NamePos: np("", 2, 32), Type: tn("c", 2, 32)}}}}}}},
+		nil},
+	{
+		"InterfaceErrors",
+		`package testpkg
+type foo interface{meth1(err error) error}`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			Interfaces: []*parse.Interface{{NamePos: np("foo", 2, 6),
+				Methods: []*parse.Method{{NamePos: np("meth1", 2, 20),
+					InArgs: []*parse.Field{{NamePos: np("err", 2, 26), Type: tn("error", 2, 30)}}}}}}},
+		nil},
+	{
+		"InterfaceMixedMethods",
+		`package testpkg
+type foo interface{
+  meth1(a b) (c | error);meth2() error
+  meth3(e f, g, h i) (j k, l, m n | error)
+}`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			Interfaces: []*parse.Interface{{NamePos: np("foo", 2, 6),
+				Methods: []*parse.Method{
+					{NamePos: np("meth1", 3, 3),
+						InArgs:  []*parse.Field{{NamePos: np("a", 3, 9), Type: tn("b", 3, 11)}},
+						OutArgs: []*parse.Field{{NamePos: np("", 3, 15), Type: tn("c", 3, 15)}}},
+					{NamePos: np("meth2", 3, 26)},
+					{NamePos: np("meth3", 4, 3),
+						InArgs: []*parse.Field{
+							{NamePos: np("e", 4, 9), Type: tn("f", 4, 11)},
+							{NamePos: np("g", 4, 14), Type: tn("i", 4, 19)},
+							{NamePos: np("h", 4, 17), Type: tn("i", 4, 19)}},
+						OutArgs: []*parse.Field{
+							{NamePos: np("j", 4, 23), Type: tn("k", 4, 25)},
+							{NamePos: np("l", 4, 28), Type: tn("n", 4, 33)},
+							{NamePos: np("m", 4, 31), Type: tn("n", 4, 33)}}}}}}},
+		nil},
+	{
+		"InterfaceEmbed",
+		`package testpkg
+type foo interface{bar}`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			Interfaces: []*parse.Interface{{NamePos: np("foo", 2, 6),
+				Embeds: []*parse.NamePos{npptr("bar", 2, 20)}}}},
+		nil},
+	{
+		"InterfaceEmbedQualified",
+		`package testpkg
+type foo interface{bar.baz}`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			Interfaces: []*parse.Interface{{NamePos: np("foo", 2, 6),
+				Embeds: []*parse.NamePos{npptr("bar.baz", 2, 20)}}}},
+		nil},
+	{
+		"InterfaceEmbedQualifiedPath",
+		`package testpkg
+type foo interface{"a/b/c/bar".baz}`,
+		&parse.File{BaseName: "testfile", PackageDef: np("testpkg", 1, 9),
+			Interfaces: []*parse.Interface{{NamePos: np("foo", 2, 6),
+				Embeds: []*parse.NamePos{npptr(`"a/b/c/bar".baz`, 2, 20)}}}},
+		nil},
+	{
+		"FAILInterfaceUnclosedInterface",
+		`package testpkg
+type foo interface{
+  meth1()`,
+		nil,
+		[]string{"testfile:3:10 syntax error"}},
+	{
+		"FAILInterfaceUnclosedArgs",
+		`package testpkg
+type foo interface{
+  meth1(
+}`,
+		nil,
+		[]string{"testfile:4:1 syntax error"}},
+	{
+		"FAILInterfaceVariableNames",
+		`package testpkg
+type foo interface{
+  meth1([]a, []b []c)
+}`,
+		nil,
+		[]string{"expected one or more variable names",
+			"testfile:3:18 perhaps you forgot a comma"}},
+}
+
+// configTests contains tests of config files.
+var configTests = []configTest{
+	// Empty file isn't allowed (need at least a package clause).
+	{
+		"FAILEmptyFile",
+		"",
+		nil,
+		[]string{"config file must start with config clause"}},
+
+	// Comment tests.
+	{
+		"ConfigDocOneLiner",
+		`// One liner
+// Another line
+config = true`,
+		&parse.Config{FileName: "testfile", ConfigDef: parse.NamePos{Name: "config", Pos: pos(3, 1), Doc: `// One liner
+// Another line
+`},
+			Config: cn("true", 3, 10)},
+		nil},
+	{
+		"ConfigDocMultiLiner",
+		`/* Multi liner
+Another line
+*/
+config = true`,
+		&parse.Config{FileName: "testfile", ConfigDef: parse.NamePos{Name: "config", Pos: pos(4, 1), Doc: `/* Multi liner
+Another line
+*/
+`},
+			Config: cn("true", 4, 10)},
+		nil},
+	{
+		"FileDocNoConfigDoc",
+		`// File doc, has extra newline so not config doc
+
+config = true`,
+		&parse.Config{FileName: "testfile", ConfigDef: np("config", 3, 1),
+			Doc:    "// File doc, has extra newline so not config doc\n",
+			Config: cn("true", 3, 10)},
+		nil},
+	{
+		"FileDocAndConfigDoc",
+		`// File doc
+
+// Config doc
+config = true`,
+		&parse.Config{FileName: "testfile", ConfigDef: parse.NamePos{Name: "config", Pos: pos(4, 1), Doc: "// Config doc\n"},
+			Doc:    "// File doc\n",
+			Config: cn("true", 4, 10)},
+		nil},
+	{
+		"FAILUnterminatedComment",
+		`/* Unterminated
+Another line
+config = true`,
+		nil,
+		[]string{"comment not terminated"}},
+
+	// Config tests.
+	{
+		"Config",
+		"config = true;",
+		&parse.Config{FileName: "testfile", ConfigDef: np("config", 1, 1),
+			Config: cn("true", 1, 10)},
+		nil},
+	{
+		"ConfigNoSemi",
+		"config = true",
+		&parse.Config{FileName: "testfile", ConfigDef: np("config", 1, 1),
+			Config: cn("true", 1, 10)},
+		nil},
+	{
+		"ConfigNamedConfig",
+		"config = config",
+		&parse.Config{FileName: "testfile", ConfigDef: np("config", 1, 1),
+			Config: cn("config", 1, 10)},
+		nil},
+	{
+		"FAILConfigNoEqual",
+		"config true",
+		nil,
+		[]string{"testfile:1:8 syntax error"}},
+
+	// Import tests.
+	{
+		"EmptyImport",
+		`config = foo
+import (
+)`,
+		&parse.Config{FileName: "testfile", ConfigDef: np("config", 1, 1),
+			Config: cn("foo", 1, 10)},
+		nil},
+	{
+		"OneImport",
+		`config = foo
+import "foo/bar";`,
+		&parse.Config{FileName: "testfile", ConfigDef: np("config", 1, 1),
+			Imports: []*parse.Import{{Path: "foo/bar", NamePos: np("", 2, 8)}},
+			Config:  cn("foo", 1, 10)},
+		nil},
+	{
+		"OneImportLocalNameNoSemi",
+		`config = foo
+import baz "foo/bar"`,
+		&parse.Config{FileName: "testfile", ConfigDef: np("config", 1, 1),
+			Imports: []*parse.Import{{Path: "foo/bar", NamePos: np("baz", 2, 8)}},
+			Config:  cn("foo", 1, 10)},
+		nil},
+	{
+		"OneImportParens",
+		`config = foo
+import (
+  "foo/bar";
+)`,
+		&parse.Config{FileName: "testfile", ConfigDef: np("config", 1, 1),
+			Imports: []*parse.Import{{Path: "foo/bar", NamePos: np("", 3, 3)}},
+			Config:  cn("foo", 1, 10)},
+		nil},
+	{
+		"OneImportParensNoSemi",
+		`config = foo
+import (
+  "foo/bar"
+)`,
+		&parse.Config{FileName: "testfile", ConfigDef: np("config", 1, 1),
+			Imports: []*parse.Import{{Path: "foo/bar", NamePos: np("", 3, 3)}},
+			Config:  cn("foo", 1, 10)},
+		nil},
+	{
+		"OneImportParensNamed",
+		`config = foo
+import (
+  baz "foo/bar"
+)`,
+		&parse.Config{FileName: "testfile", ConfigDef: np("config", 1, 1),
+			Imports: []*parse.Import{{Path: "foo/bar", NamePos: np("baz", 3, 3)}},
+			Config:  cn("foo", 1, 10)},
+		nil},
+	{
+		"MixedImports",
+		`config = foo
+import "foo/bar"
+import (
+  "baz";"a/b"
+  "c/d"
+)
+import "z"`,
+		&parse.Config{FileName: "testfile", ConfigDef: np("config", 1, 1),
+			Imports: []*parse.Import{
+				{Path: "foo/bar", NamePos: np("", 2, 8)},
+				{Path: "baz", NamePos: np("", 4, 3)},
+				{Path: "a/b", NamePos: np("", 4, 9)},
+				{Path: "c/d", NamePos: np("", 5, 3)},
+				{Path: "z", NamePos: np("", 7, 8)}},
+			Config: cn("foo", 1, 10)},
+		nil},
+	{
+		"FAILImportParensNotClosed",
+		`config = foo
+import (
+  "foo/bar"`,
+		nil,
+		[]string{"testfile:3:12 syntax error"}},
+
+	// Inline config tests.
+	{
+		"BoolConst",
+		`config = true`,
+		&parse.Config{FileName: "testfile", ConfigDef: np("config", 1, 1),
+			Config: cn("true", 1, 10)},
+		nil},
+	{
+		"StringConst",
+		`config = "abc"`,
+		&parse.Config{FileName: "testfile", ConfigDef: np("config", 1, 1),
+			Config: cl("abc", 1, 10)},
+		nil},
+	{
+		"IntegerConst",
+		`config = 123`,
+		&parse.Config{FileName: "testfile", ConfigDef: np("config", 1, 1),
+			Config: cl(big.NewInt(123), 1, 10)},
+		nil},
+	{
+		"FloatConst",
+		`config = 1.5`,
+		&parse.Config{FileName: "testfile", ConfigDef: np("config", 1, 1),
+			Config: cl(big.NewRat(3, 2), 1, 10)},
+		nil},
+	{
+		"NamedConst",
+		`config = pkg.foo`,
+		&parse.Config{FileName: "testfile", ConfigDef: np("config", 1, 1),
+			Config: cn("pkg.foo", 1, 10)},
+		nil},
+	{
+		"CompLitConst",
+		`config = {"a","b"}`,
+		&parse.Config{FileName: "testfile", ConfigDef: np("config", 1, 1),
+			Config: &parse.ConstCompositeLit{
+				KVList: []parse.KVLit{
+					{Value: cl("a", 1, 11)},
+					{Value: cl("b", 1, 15)}},
+				P: pos(1, 10)}},
+		nil},
+	{
+		"CompLitKVConst",
+		`config = {"a":1,"b":2}`,
+		&parse.Config{FileName: "testfile", ConfigDef: np("config", 1, 1),
+			Config: &parse.ConstCompositeLit{
+				KVList: []parse.KVLit{
+					{cl("a", 1, 11), cl(big.NewInt(1), 1, 15)},
+					{cl("b", 1, 17), cl(big.NewInt(2), 1, 21)}},
+				P: pos(1, 10)}},
+		nil},
+	{
+		"CompLitTypedConst",
+		`config = foo{"a","b"}`,
+		&parse.Config{FileName: "testfile", ConfigDef: np("config", 1, 1),
+			Config: &parse.ConstCompositeLit{
+				Type: tn("foo", 1, 10),
+				KVList: []parse.KVLit{
+					{Value: cl("a", 1, 14)},
+					{Value: cl("b", 1, 18)}},
+				P: pos(1, 13)}},
+		nil},
+	{
+		"CompLitKVTypedConst",
+		`config = foo{"a":1,"b":2}`,
+		&parse.Config{FileName: "testfile", ConfigDef: np("config", 1, 1),
+			Config: &parse.ConstCompositeLit{
+				Type: tn("foo", 1, 10),
+				KVList: []parse.KVLit{
+					{cl("a", 1, 14), cl(big.NewInt(1), 1, 18)},
+					{cl("b", 1, 20), cl(big.NewInt(2), 1, 24)}},
+				P: pos(1, 13)}},
+		nil},
+	{
+		"FAILConstNoEquals",
+		`config 123`,
+		nil,
+		[]string{"testfile:1:8 syntax error"}},
+	{
+		"FAILConstNoValue",
+		`config =`,
+		nil,
+		[]string{"testfile:1:9 syntax error"}},
+
+	// Out-of-line config tests.
+	{
+		"BoolOutOfLineConfig",
+		`config = config
+import "foo"
+const config = true`,
+		&parse.Config{FileName: "testfile", ConfigDef: np("config", 1, 1),
+			Imports: []*parse.Import{{Path: "foo", NamePos: np("", 2, 8)}},
+			Config:  cn("config", 1, 10),
+			ConstDefs: []*parse.ConstDef{
+				{NamePos: np("config", 3, 7), Expr: cn("true", 3, 16)}}},
+		nil},
+	{
+		"BoolOutOfLineBar",
+		`config = bar
+import "foo"
+const bar = true`,
+		&parse.Config{FileName: "testfile", ConfigDef: np("config", 1, 1),
+			Imports: []*parse.Import{{Path: "foo", NamePos: np("", 2, 8)}},
+			Config:  cn("bar", 1, 10),
+			ConstDefs: []*parse.ConstDef{
+				{NamePos: np("bar", 3, 7), Expr: cn("true", 3, 13)}}},
+		nil},
+
+	// Errors, types and interfaces return error
+	{
+		"FAILError",
+		`config = true
+error foo()`,
+		nil,
+		[]string{"config files may not contain error, type or interface definitions"}},
+	{
+		"FAILType",
+		`config = true
+type foo bool`,
+		nil,
+		[]string{"config files may not contain error, type or interface definitions"}},
+	{
+		"FAILInterface",
+		`config = true
+type foo interface{}`,
+		nil,
+		[]string{"config files may not contain error, type or interface definitions"}},
+}
+
+func configImports(imports ...string) *parse.Config {
+	config := new(parse.Config)
+	for _, i := range imports {
+		config.Imports = append(config.Imports, &parse.Import{Path: i})
+	}
+	return config
+}
+
+func TestConfigHasImport(t *testing.T) {
+	config := configImports("a", "b/c")
+	tests := []struct {
+		Path string
+		Want bool
+	}{
+		{"a", true},
+		{"b/c", true},
+		{"b", false},
+		{"c", false},
+		{"d", false},
+	}
+	for _, test := range tests {
+		if got, want := config.HasImport(test.Path), test.Want; got != want {
+			t.Errorf("HasImport(%q) got %v, want %v", test.Path, got, want)
+		}
+	}
+}
+
+func TestConfigAddImports(t *testing.T) {
+	tests := []struct {
+		Base    *parse.Config
+		Imports []string
+		Want    *parse.Config
+	}{
+		{configImports(), []string{"a", "b/c"}, configImports("a", "b/c")},
+		{configImports("a"), []string{"a", "b/c"}, configImports("a", "b/c")},
+		{configImports("a", "b/c"), []string{"a", "b/c"}, configImports("a", "b/c")},
+		{configImports("a", "b/c"), []string{"a", "b/c", "d"}, configImports("a", "b/c", "d")},
+	}
+	for _, test := range tests {
+		test.Base.AddImports(test.Imports...)
+		if got, want := test.Base, test.Want; !reflect.DeepEqual(got, want) {
+			t.Errorf("AddImports(%q) got %v, want %v", test.Imports, got, want)
+		}
+	}
+}
+
+func TestParseExprs(t *testing.T) {
+	tests := []struct {
+		Data  string
+		Exprs []parse.ConstExpr
+		Err   string
+	}{
+		{``, nil, "syntax error"},
+		{`true`, []parse.ConstExpr{cn("true", 1, 1)}, ""},
+		{`false`, []parse.ConstExpr{cn("false", 1, 1)}, ""},
+		{`abc`, []parse.ConstExpr{cn("abc", 1, 1)}, ""},
+		{`"a/b/c".abc`, []parse.ConstExpr{cn(`"a/b/c".abc`, 1, 1)}, ""},
+		{`"abc"`, []parse.ConstExpr{cl("abc", 1, 1)}, ""},
+		{`1`, []parse.ConstExpr{cl(big.NewInt(1), 1, 1)}, ""},
+		{`123`, []parse.ConstExpr{cl(big.NewInt(123), 1, 1)}, ""},
+		{`1.0`, []parse.ConstExpr{cl(big.NewRat(1, 1), 1, 1)}, ""},
+		{`1.5`, []parse.ConstExpr{cl(big.NewRat(3, 2), 1, 1)}, ""},
+		{`{1,2}`, []parse.ConstExpr{
+			&parse.ConstCompositeLit{
+				KVList: []parse.KVLit{
+					{Value: cl(big.NewInt(1), 1, 2)},
+					{Value: cl(big.NewInt(2), 1, 4)},
+				},
+				P: pos(1, 1),
+			},
+		}, ""},
+		{`1+2`, []parse.ConstExpr{
+			&parse.ConstBinaryOp{"+",
+				cl(big.NewInt(1), 1, 1),
+				cl(big.NewInt(2), 1, 3),
+				pos(1, 2),
+			},
+		}, ""},
+		{`1,"abc"`, []parse.ConstExpr{
+			cl(big.NewInt(1), 1, 1),
+			cl("abc", 1, 3),
+		}, ""},
+	}
+	for _, test := range tests {
+		errs := vdlutil.NewErrors(-1)
+		exprs := parse.ParseExprs(test.Data, errs)
+		vdltest.ExpectResult(t, errs, test.Data, test.Err)
+		if got, want := exprs, test.Exprs; !reflect.DeepEqual(got, want) {
+			t.Errorf("%s got %v, want %v", test.Data, got, want)
+		}
+	}
+}
diff --git a/lib/vdl/parse/result.go b/lib/vdl/parse/result.go
new file mode 100644
index 0000000..bdb71bb
--- /dev/null
+++ b/lib/vdl/parse/result.go
@@ -0,0 +1,192 @@
+// 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 parse
+
+import (
+	"fmt"
+	"path"
+	"strconv"
+	"strings"
+
+	"v.io/x/ref/lib/vdl/vdlutil"
+)
+
+// Pos captures positional information during parsing.
+type Pos struct {
+	Line int // Line number, starting at 1
+	Col  int // Column number (character count), starting at 1
+}
+
+// StringPos holds a string and a Pos.
+type StringPos struct {
+	String string
+	Pos    Pos
+}
+
+// Returns true iff this Pos has been initialized.  The zero Pos is invalid.
+func (p Pos) IsValid() bool {
+	return p.Line > 0 && p.Col > 0
+}
+
+func (p Pos) String() string {
+	if !p.IsValid() {
+		return "[no pos]"
+	}
+	return fmt.Sprintf("%v:%v", p.Line, p.Col)
+}
+
+// InferPackageName returns the package name from a group of files.  Every file
+// must specify the same package name, otherwise an error is reported in errs.
+func InferPackageName(files []*File, errs *vdlutil.Errors) (pkgName string) {
+	var firstFile string
+	for _, f := range files {
+		switch {
+		case pkgName == "":
+			firstFile = f.BaseName
+			pkgName = f.PackageDef.Name
+		case pkgName != f.PackageDef.Name:
+			errs.Errorf("Files in the same directory must be in the same package; %v has package %v, but %v has package %v", firstFile, pkgName, f.BaseName, f.PackageDef.Name)
+		}
+	}
+	return
+}
+
+// Representation of the components of an vdl file.  These data types represent
+// the parse tree generated by the parse.
+
+// File represents a parsed vdl file.
+type File struct {
+	BaseName   string       // Base name of the vdl file, e.g. "foo.vdl"
+	Doc        string       // Top-level file documentation
+	PackageDef NamePos      // Name, position and docs of the "package" clause
+	Imports    []*Import    // Imports listed in this file
+	ErrorDefs  []*ErrorDef  // Errors defined in this file
+	TypeDefs   []*TypeDef   // Types defined in this file
+	ConstDefs  []*ConstDef  // Consts defined in this file
+	Interfaces []*Interface // Interfaces defined in this file
+}
+
+// Config represents a parsed config file.  Config files use a similar syntax as
+// vdl files, with similar concepts.
+type Config struct {
+	FileName  string      // Config file name, e.g. "a/b/foo.config"
+	Doc       string      // Top-level config file documentation
+	ConfigDef NamePos     // Name, position and docs of the "config" clause
+	Imports   []*Import   // Imports listed in this file.
+	Config    ConstExpr   // Const expression exported from this config.
+	ConstDefs []*ConstDef // Consts defined in this file.
+}
+
+// AddImports adds the path imports that don't already exist to c.
+func (c *Config) AddImports(path ...string) {
+	for _, p := range path {
+		if !c.HasImport(p) {
+			c.Imports = append(c.Imports, &Import{Path: p})
+		}
+	}
+}
+
+// HasImport returns true iff path exists in c.Imports.
+func (c *Config) HasImport(path string) bool {
+	for _, imp := range c.Imports {
+		if imp.Path == path {
+			return true
+		}
+	}
+	return false
+}
+
+// Import represents an import definition, which is used to import other
+// packages into an vdl file.  An example of the syntax in the vdl file:
+//   import foo "some/package/path"
+type Import struct {
+	NamePos        // e.g. foo (from above), or typically empty
+	Path    string // e.g. "some/package/path" (from above)
+}
+
+// LocalName returns the name used locally within the File to refer to the
+// imported package.
+func (i *Import) LocalName() string {
+	if i.Name != "" {
+		return i.Name
+	}
+	return path.Base(i.Path)
+}
+
+// ErrorDef represents an error definition.
+type ErrorDef struct {
+	NamePos             // error name, pos and doc
+	Params  []*Field    // list of positional parameters
+	Actions []StringPos // list of action code identifiers
+	Formats []LangFmt   // list of language / format pairs
+}
+
+// LangFmt represents a language / format string pair.
+type LangFmt struct {
+	Lang StringPos // IETF language tag
+	Fmt  StringPos // i18n format string in the given language
+}
+
+// Pos returns the position of the LangFmt.
+func (x LangFmt) Pos() Pos {
+	if x.Lang.Pos.IsValid() {
+		return x.Lang.Pos
+	}
+	return x.Fmt.Pos
+}
+
+// Interface represents a set of embedded interfaces and methods.
+type Interface struct {
+	NamePos            // interface name, pos and doc
+	Embeds  []*NamePos // names of embedded interfaces
+	Methods []*Method  // list of methods
+}
+
+// Method represents a method in an interface.
+type Method struct {
+	NamePos               // method name, pos and doc
+	InArgs    []*Field    // list of positional in-args
+	OutArgs   []*Field    // list of positional out-args
+	InStream  Type        // in-stream type, may be nil
+	OutStream Type        // out-stream type, may be nil
+	Tags      []ConstExpr // list of method tags
+}
+
+// Field represents fields in structs as well as method arguments.
+type Field struct {
+	NamePos      // field name, pos and doc
+	Type    Type // field type, never nil
+}
+
+// NamePos represents a name, its associated position and documentation.
+type NamePos struct {
+	Name      string
+	Pos       Pos    // position of first character in name
+	Doc       string // docs that occur before the item
+	DocSuffix string // docs that occur on the same line after the item
+}
+
+func (x *File) String() string      { return fmt.Sprintf("%+v", *x) }
+func (x *Import) String() string    { return fmt.Sprintf("%+v", *x) }
+func (x *ErrorDef) String() string  { return fmt.Sprintf("%+v", *x) }
+func (x *Interface) String() string { return fmt.Sprintf("%+v", *x) }
+func (x *Method) String() string    { return fmt.Sprintf("%+v", *x) }
+func (x *Field) String() string     { return fmt.Sprintf("%+v", *x) }
+func (x *NamePos) String() string   { return fmt.Sprintf("%+v", *x) }
+
+// QuoteStripDoc takes a Doc string, which includes comment markers /**/ and
+// double-slash, and returns a raw-quoted string.
+//
+// TODO(toddw): This should remove comment markers.  This is non-trivial, since
+// we should handle removing leading whitespace "rectangles", and might want to
+// retain inline /**/ or adjacent /**/ on the same line.  For now we just leave
+// them in the output.
+func QuoteStripDoc(doc string) string {
+	trimmed := strings.Trim(doc, "\n")
+	if strconv.CanBackquote(doc) {
+		return "`" + trimmed + "`"
+	}
+	return strconv.Quote(trimmed)
+}
diff --git a/lib/vdl/parse/type.go b/lib/vdl/parse/type.go
new file mode 100644
index 0000000..8493edf
--- /dev/null
+++ b/lib/vdl/parse/type.go
@@ -0,0 +1,143 @@
+// 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 parse
+
+import (
+	"fmt"
+)
+
+// TypeDef represents a user-defined named type.
+type TypeDef struct {
+	NamePos      // name assigned by the user, pos and doc
+	Type    Type // the underlying type of the type definition.
+}
+
+// Type is an interface representing symbolic occurrences of types in VDL files.
+type Type interface {
+	// String returns a human-readable description of the type.
+	String() string
+	// Kind returns a short human-readable string describing the kind of type.
+	Kind() string
+	// Pos returns the position of the first character in the type.
+	Pos() Pos
+}
+
+// TypeNamed captures named references to other types.  Both built-in primitives
+// and user-defined named types use this representation.
+type TypeNamed struct {
+	Name string
+	P    Pos
+}
+
+// TypeEnum represents enum types.
+type TypeEnum struct {
+	Labels []NamePos
+	P      Pos
+}
+
+// TypeArray represents array types.
+type TypeArray struct {
+	Len  int
+	Elem Type
+	P    Pos
+}
+
+// TypeList represents list types.
+type TypeList struct {
+	Elem Type
+	P    Pos
+}
+
+// TypeSet represents set types.
+type TypeSet struct {
+	Key Type
+	P   Pos
+}
+
+// TypeMap represents map types.
+type TypeMap struct {
+	Key  Type
+	Elem Type
+	P    Pos
+}
+
+// TypeStruct represents struct types.
+type TypeStruct struct {
+	Fields []*Field
+	P      Pos
+}
+
+// TypeUnion represents union types.
+type TypeUnion struct {
+	Fields []*Field
+	P      Pos
+}
+
+// TypeOptional represents optional types.
+type TypeOptional struct {
+	Base Type
+	P    Pos
+}
+
+func (t *TypeNamed) Pos() Pos    { return t.P }
+func (t *TypeEnum) Pos() Pos     { return t.P }
+func (t *TypeArray) Pos() Pos    { return t.P }
+func (t *TypeList) Pos() Pos     { return t.P }
+func (t *TypeSet) Pos() Pos      { return t.P }
+func (t *TypeMap) Pos() Pos      { return t.P }
+func (t *TypeStruct) Pos() Pos   { return t.P }
+func (t *TypeUnion) Pos() Pos    { return t.P }
+func (t *TypeOptional) Pos() Pos { return t.P }
+
+func (t *TypeNamed) Kind() string    { return "named" }
+func (t *TypeEnum) Kind() string     { return "enum" }
+func (t *TypeArray) Kind() string    { return "array" }
+func (t *TypeList) Kind() string     { return "list" }
+func (t *TypeSet) Kind() string      { return "set" }
+func (t *TypeMap) Kind() string      { return "map" }
+func (t *TypeStruct) Kind() string   { return "struct" }
+func (t *TypeUnion) Kind() string    { return "union" }
+func (t *TypeOptional) Kind() string { return "optional" }
+
+func (t *TypeNamed) String() string { return t.Name }
+func (t *TypeEnum) String() string {
+	result := "enum{"
+	for index, label := range t.Labels {
+		if index > 0 {
+			result += ";"
+		}
+		result += label.Name
+	}
+	return result + "}"
+}
+func (t *TypeArray) String() string { return fmt.Sprintf("[%v]%v", t.Len, t.Elem) }
+func (t *TypeList) String() string  { return fmt.Sprintf("[]%v", t.Elem) }
+func (t *TypeSet) String() string   { return fmt.Sprintf("set[%v]", t.Key) }
+func (t *TypeMap) String() string   { return fmt.Sprintf("map[%v]%v", t.Key, t.Elem) }
+func (t *TypeStruct) String() string {
+	result := "struct{"
+	for index, field := range t.Fields {
+		if index > 0 {
+			result += ";"
+		}
+		result += field.Name + " " + field.Type.String()
+	}
+	return result + "}"
+}
+func (t *TypeUnion) String() string {
+	result := "union{"
+	for index, field := range t.Fields {
+		if index > 0 {
+			result += ";"
+		}
+		result += field.Name + " " + field.Type.String()
+	}
+	return result + "}"
+}
+func (t *TypeOptional) String() string { return fmt.Sprintf("?%v", t.Base) }
+
+func (t *TypeDef) String() string {
+	return fmt.Sprintf("(%v %v %v)", t.Pos, t.Name, t.Type)
+}
diff --git a/lib/vdl/testdata/arith/advanced.vdl b/lib/vdl/testdata/arith/advanced.vdl
new file mode 100644
index 0000000..03e8e4b
--- /dev/null
+++ b/lib/vdl/testdata/arith/advanced.vdl
@@ -0,0 +1,24 @@
+// 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 arith
+
+import (
+	"v.io/x/ref/lib/vdl/testdata/arith/exp"
+)
+
+// Trigonometry is an interface that specifies a couple trigonometric functions.
+type Trigonometry interface {
+	Sine(angle float64) (float64 | error)
+	Cosine(angle float64) (float64 | error)
+}
+
+// AdvancedMath is an interface for more advanced math than arith.  It embeds
+// interfaces defined both in the same file and in an external package; and in
+// turn it is embedded by arith.Calculator (which is in the same package but
+// different file) to verify that embedding works in all these scenarios.
+type AdvancedMath interface {
+	Trigonometry
+	exp.Exp
+}
diff --git a/lib/vdl/testdata/arith/advanced.vdl.go b/lib/vdl/testdata/arith/advanced.vdl.go
new file mode 100644
index 0000000..f2a0d72
--- /dev/null
+++ b/lib/vdl/testdata/arith/advanced.vdl.go
@@ -0,0 +1,247 @@
+// 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 by the vanadium vdl tool.
+// Source: advanced.vdl
+
+package arith
+
+import (
+	// VDL system imports
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+
+	// VDL user imports
+	"v.io/x/ref/lib/vdl/testdata/arith/exp"
+)
+
+// TrigonometryClientMethods is the client interface
+// containing Trigonometry methods.
+//
+// Trigonometry is an interface that specifies a couple trigonometric functions.
+type TrigonometryClientMethods interface {
+	Sine(ctx *context.T, angle float64, opts ...rpc.CallOpt) (float64, error)
+	Cosine(ctx *context.T, angle float64, opts ...rpc.CallOpt) (float64, error)
+}
+
+// TrigonometryClientStub adds universal methods to TrigonometryClientMethods.
+type TrigonometryClientStub interface {
+	TrigonometryClientMethods
+	rpc.UniversalServiceMethods
+}
+
+// TrigonometryClient returns a client stub for Trigonometry.
+func TrigonometryClient(name string) TrigonometryClientStub {
+	return implTrigonometryClientStub{name}
+}
+
+type implTrigonometryClientStub struct {
+	name string
+}
+
+func (c implTrigonometryClientStub) Sine(ctx *context.T, i0 float64, opts ...rpc.CallOpt) (o0 float64, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "Sine", []interface{}{i0}, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implTrigonometryClientStub) Cosine(ctx *context.T, i0 float64, opts ...rpc.CallOpt) (o0 float64, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "Cosine", []interface{}{i0}, []interface{}{&o0}, opts...)
+	return
+}
+
+// TrigonometryServerMethods is the interface a server writer
+// implements for Trigonometry.
+//
+// Trigonometry is an interface that specifies a couple trigonometric functions.
+type TrigonometryServerMethods interface {
+	Sine(ctx *context.T, call rpc.ServerCall, angle float64) (float64, error)
+	Cosine(ctx *context.T, call rpc.ServerCall, angle float64) (float64, error)
+}
+
+// TrigonometryServerStubMethods is the server interface containing
+// Trigonometry methods, as expected by rpc.Server.
+// There is no difference between this interface and TrigonometryServerMethods
+// since there are no streaming methods.
+type TrigonometryServerStubMethods TrigonometryServerMethods
+
+// TrigonometryServerStub adds universal methods to TrigonometryServerStubMethods.
+type TrigonometryServerStub interface {
+	TrigonometryServerStubMethods
+	// Describe the Trigonometry interfaces.
+	Describe__() []rpc.InterfaceDesc
+}
+
+// TrigonometryServer returns a server stub for Trigonometry.
+// It converts an implementation of TrigonometryServerMethods into
+// an object that may be used by rpc.Server.
+func TrigonometryServer(impl TrigonometryServerMethods) TrigonometryServerStub {
+	stub := implTrigonometryServerStub{
+		impl: impl,
+	}
+	// Initialize GlobState; always check the stub itself first, to handle the
+	// case where the user has the Glob method defined in their VDL source.
+	if gs := rpc.NewGlobState(stub); gs != nil {
+		stub.gs = gs
+	} else if gs := rpc.NewGlobState(impl); gs != nil {
+		stub.gs = gs
+	}
+	return stub
+}
+
+type implTrigonometryServerStub struct {
+	impl TrigonometryServerMethods
+	gs   *rpc.GlobState
+}
+
+func (s implTrigonometryServerStub) Sine(ctx *context.T, call rpc.ServerCall, i0 float64) (float64, error) {
+	return s.impl.Sine(ctx, call, i0)
+}
+
+func (s implTrigonometryServerStub) Cosine(ctx *context.T, call rpc.ServerCall, i0 float64) (float64, error) {
+	return s.impl.Cosine(ctx, call, i0)
+}
+
+func (s implTrigonometryServerStub) Globber() *rpc.GlobState {
+	return s.gs
+}
+
+func (s implTrigonometryServerStub) Describe__() []rpc.InterfaceDesc {
+	return []rpc.InterfaceDesc{TrigonometryDesc}
+}
+
+// TrigonometryDesc describes the Trigonometry interface.
+var TrigonometryDesc rpc.InterfaceDesc = descTrigonometry
+
+// descTrigonometry hides the desc to keep godoc clean.
+var descTrigonometry = rpc.InterfaceDesc{
+	Name:    "Trigonometry",
+	PkgPath: "v.io/x/ref/lib/vdl/testdata/arith",
+	Doc:     "// Trigonometry is an interface that specifies a couple trigonometric functions.",
+	Methods: []rpc.MethodDesc{
+		{
+			Name: "Sine",
+			InArgs: []rpc.ArgDesc{
+				{"angle", ``}, // float64
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"", ``}, // float64
+			},
+		},
+		{
+			Name: "Cosine",
+			InArgs: []rpc.ArgDesc{
+				{"angle", ``}, // float64
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"", ``}, // float64
+			},
+		},
+	},
+}
+
+// AdvancedMathClientMethods is the client interface
+// containing AdvancedMath methods.
+//
+// AdvancedMath is an interface for more advanced math than arith.  It embeds
+// interfaces defined both in the same file and in an external package; and in
+// turn it is embedded by arith.Calculator (which is in the same package but
+// different file) to verify that embedding works in all these scenarios.
+type AdvancedMathClientMethods interface {
+	// Trigonometry is an interface that specifies a couple trigonometric functions.
+	TrigonometryClientMethods
+	exp.ExpClientMethods
+}
+
+// AdvancedMathClientStub adds universal methods to AdvancedMathClientMethods.
+type AdvancedMathClientStub interface {
+	AdvancedMathClientMethods
+	rpc.UniversalServiceMethods
+}
+
+// AdvancedMathClient returns a client stub for AdvancedMath.
+func AdvancedMathClient(name string) AdvancedMathClientStub {
+	return implAdvancedMathClientStub{name, TrigonometryClient(name), exp.ExpClient(name)}
+}
+
+type implAdvancedMathClientStub struct {
+	name string
+
+	TrigonometryClientStub
+	exp.ExpClientStub
+}
+
+// AdvancedMathServerMethods is the interface a server writer
+// implements for AdvancedMath.
+//
+// AdvancedMath is an interface for more advanced math than arith.  It embeds
+// interfaces defined both in the same file and in an external package; and in
+// turn it is embedded by arith.Calculator (which is in the same package but
+// different file) to verify that embedding works in all these scenarios.
+type AdvancedMathServerMethods interface {
+	// Trigonometry is an interface that specifies a couple trigonometric functions.
+	TrigonometryServerMethods
+	exp.ExpServerMethods
+}
+
+// AdvancedMathServerStubMethods is the server interface containing
+// AdvancedMath methods, as expected by rpc.Server.
+// There is no difference between this interface and AdvancedMathServerMethods
+// since there are no streaming methods.
+type AdvancedMathServerStubMethods AdvancedMathServerMethods
+
+// AdvancedMathServerStub adds universal methods to AdvancedMathServerStubMethods.
+type AdvancedMathServerStub interface {
+	AdvancedMathServerStubMethods
+	// Describe the AdvancedMath interfaces.
+	Describe__() []rpc.InterfaceDesc
+}
+
+// AdvancedMathServer returns a server stub for AdvancedMath.
+// It converts an implementation of AdvancedMathServerMethods into
+// an object that may be used by rpc.Server.
+func AdvancedMathServer(impl AdvancedMathServerMethods) AdvancedMathServerStub {
+	stub := implAdvancedMathServerStub{
+		impl: impl,
+		TrigonometryServerStub: TrigonometryServer(impl),
+		ExpServerStub:          exp.ExpServer(impl),
+	}
+	// Initialize GlobState; always check the stub itself first, to handle the
+	// case where the user has the Glob method defined in their VDL source.
+	if gs := rpc.NewGlobState(stub); gs != nil {
+		stub.gs = gs
+	} else if gs := rpc.NewGlobState(impl); gs != nil {
+		stub.gs = gs
+	}
+	return stub
+}
+
+type implAdvancedMathServerStub struct {
+	impl AdvancedMathServerMethods
+	TrigonometryServerStub
+	exp.ExpServerStub
+	gs *rpc.GlobState
+}
+
+func (s implAdvancedMathServerStub) Globber() *rpc.GlobState {
+	return s.gs
+}
+
+func (s implAdvancedMathServerStub) Describe__() []rpc.InterfaceDesc {
+	return []rpc.InterfaceDesc{AdvancedMathDesc, TrigonometryDesc, exp.ExpDesc}
+}
+
+// AdvancedMathDesc describes the AdvancedMath interface.
+var AdvancedMathDesc rpc.InterfaceDesc = descAdvancedMath
+
+// descAdvancedMath hides the desc to keep godoc clean.
+var descAdvancedMath = rpc.InterfaceDesc{
+	Name:    "AdvancedMath",
+	PkgPath: "v.io/x/ref/lib/vdl/testdata/arith",
+	Doc:     "// AdvancedMath is an interface for more advanced math than arith.  It embeds\n// interfaces defined both in the same file and in an external package; and in\n// turn it is embedded by arith.Calculator (which is in the same package but\n// different file) to verify that embedding works in all these scenarios.",
+	Embeds: []rpc.EmbedDesc{
+		{"Trigonometry", "v.io/x/ref/lib/vdl/testdata/arith", "// Trigonometry is an interface that specifies a couple trigonometric functions."},
+		{"Exp", "v.io/x/ref/lib/vdl/testdata/arith/exp", ``},
+	},
+}
diff --git a/lib/vdl/testdata/arith/arith.vdl b/lib/vdl/testdata/arith/arith.vdl
new file mode 100644
index 0000000..ef28a53
--- /dev/null
+++ b/lib/vdl/testdata/arith/arith.vdl
@@ -0,0 +1,71 @@
+// 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 arith is a vdl test package with imports.
+package arith
+
+// Test the import mechanism.
+import (
+	"v.io/x/ref/lib/vdl/testdata/base"
+)
+
+// Constants.
+const (
+	// Yes shows that bools may be untyped.
+	Yes      = true // yes trailing doc
+	// No shows explicit boolean typing.
+	No       = bool(false)
+	Hello    = "hello"
+	// Int32Const shows explicit integer typing.
+	Int32Const    = int32(123)
+	// Int64Const shows explicit integer conversion from another type, and referencing
+	// a constant from another package.
+	Int64Const    = int64(Int32Const + base.Five)
+	// FloatConst shows arithmetic expressions may be used.
+	FloatConst    = float64(3.0 / 2 + 0.5)
+	// Mask shows bitwise operations.
+	Mask     = uint64(0x1 << 8)
+)
+
+// Arith is an example of an interface definition for an arithmetic service.
+// Things to note:
+//   * There must be at least 1 out-arg, and the last out-arg must be error.
+type Arith interface {
+	// Add is a typical method with multiple input and output arguments.
+	Add(a int32, b int32) (int32 | error)
+
+	// DivMod shows that runs of args with the same type can use the short form,
+	// just like Go.
+	DivMod(a, b int32) (quot, rem int32 | error)
+
+	// Sub shows that you can use data types defined in other packages.
+	Sub(args base.Args) (int32 | error)
+
+	// Mul tries another data type defined in another package.
+	Mul(nested base.NestedArgs) (int32 | error)
+
+	// GenError shows that it's fine to have no in args, and no out args other
+	// than "error".  In addition GenError shows the usage of tags.  Tags are a
+	// sequence of constants.  There's no requirement on uniqueness of types or
+	// values, and regular const expressions may also be used.
+	GenError() error {"foo", "bar" + "z", Hello, int32(Int64Const + 1), base.SixSquared}
+
+	// Count shows using only an int32 out-stream type, with no in-stream type.
+	Count(start int32) stream<_, int32> error
+
+	// StreamingAdd shows a bidirectional stream.
+	StreamingAdd() stream<int32, int32> (total int32 | error)
+
+	// QuoteAny shows the any built-in type, representing a value of any type.
+	QuoteAny(a any) (any | error)
+}
+
+type Calculator interface {
+	// A calculator can do basic arithmetic.
+	Arith  // Arith provides the interface to basic arithmetic operations.
+	// A calculator has basic advanced function support.
+	AdvancedMath
+	On() error             // On turns the calculator on.
+	Off() error {"offtag"} // Off turns the calculator off.
+}
diff --git a/lib/vdl/testdata/arith/arith.vdl.go b/lib/vdl/testdata/arith/arith.vdl.go
new file mode 100644
index 0000000..c0cd823
--- /dev/null
+++ b/lib/vdl/testdata/arith/arith.vdl.go
@@ -0,0 +1,790 @@
+// 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 by the vanadium vdl tool.
+// Source: arith.vdl
+
+// Package arith is a vdl test package with imports.
+package arith
+
+import (
+	// VDL system imports
+	"io"
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/vdl"
+
+	// VDL user imports
+	"v.io/x/ref/lib/vdl/testdata/arith/exp"
+	"v.io/x/ref/lib/vdl/testdata/base"
+)
+
+// Yes shows that bools may be untyped.
+const Yes = true // yes trailing doc
+
+// No shows explicit boolean typing.
+const No = false
+
+const Hello = "hello"
+
+// Int32Const shows explicit integer typing.
+const Int32Const = int32(123)
+
+// Int64Const shows explicit integer conversion from another type, and referencing
+// a constant from another package.
+const Int64Const = int64(128)
+
+// FloatConst shows arithmetic expressions may be used.
+const FloatConst = float64(2)
+
+// Mask shows bitwise operations.
+const Mask = uint64(256)
+
+// ArithClientMethods is the client interface
+// containing Arith methods.
+//
+// Arith is an example of an interface definition for an arithmetic service.
+// Things to note:
+//   * There must be at least 1 out-arg, and the last out-arg must be error.
+type ArithClientMethods interface {
+	// Add is a typical method with multiple input and output arguments.
+	Add(ctx *context.T, a int32, b int32, opts ...rpc.CallOpt) (int32, error)
+	// DivMod shows that runs of args with the same type can use the short form,
+	// just like Go.
+	DivMod(ctx *context.T, a int32, b int32, opts ...rpc.CallOpt) (quot int32, rem int32, err error)
+	// Sub shows that you can use data types defined in other packages.
+	Sub(ctx *context.T, args base.Args, opts ...rpc.CallOpt) (int32, error)
+	// Mul tries another data type defined in another package.
+	Mul(ctx *context.T, nested base.NestedArgs, opts ...rpc.CallOpt) (int32, error)
+	// GenError shows that it's fine to have no in args, and no out args other
+	// than "error".  In addition GenError shows the usage of tags.  Tags are a
+	// sequence of constants.  There's no requirement on uniqueness of types or
+	// values, and regular const expressions may also be used.
+	GenError(*context.T, ...rpc.CallOpt) error
+	// Count shows using only an int32 out-stream type, with no in-stream type.
+	Count(ctx *context.T, start int32, opts ...rpc.CallOpt) (ArithCountClientCall, error)
+	// StreamingAdd shows a bidirectional stream.
+	StreamingAdd(*context.T, ...rpc.CallOpt) (ArithStreamingAddClientCall, error)
+	// QuoteAny shows the any built-in type, representing a value of any type.
+	QuoteAny(ctx *context.T, a *vdl.Value, opts ...rpc.CallOpt) (*vdl.Value, error)
+}
+
+// ArithClientStub adds universal methods to ArithClientMethods.
+type ArithClientStub interface {
+	ArithClientMethods
+	rpc.UniversalServiceMethods
+}
+
+// ArithClient returns a client stub for Arith.
+func ArithClient(name string) ArithClientStub {
+	return implArithClientStub{name}
+}
+
+type implArithClientStub struct {
+	name string
+}
+
+func (c implArithClientStub) Add(ctx *context.T, i0 int32, i1 int32, opts ...rpc.CallOpt) (o0 int32, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "Add", []interface{}{i0, i1}, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implArithClientStub) DivMod(ctx *context.T, i0 int32, i1 int32, opts ...rpc.CallOpt) (o0 int32, o1 int32, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "DivMod", []interface{}{i0, i1}, []interface{}{&o0, &o1}, opts...)
+	return
+}
+
+func (c implArithClientStub) Sub(ctx *context.T, i0 base.Args, opts ...rpc.CallOpt) (o0 int32, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "Sub", []interface{}{i0}, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implArithClientStub) Mul(ctx *context.T, i0 base.NestedArgs, opts ...rpc.CallOpt) (o0 int32, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "Mul", []interface{}{i0}, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implArithClientStub) GenError(ctx *context.T, opts ...rpc.CallOpt) (err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "GenError", nil, nil, opts...)
+	return
+}
+
+func (c implArithClientStub) Count(ctx *context.T, i0 int32, opts ...rpc.CallOpt) (ocall ArithCountClientCall, err error) {
+	var call rpc.ClientCall
+	if call, err = v23.GetClient(ctx).StartCall(ctx, c.name, "Count", []interface{}{i0}, opts...); err != nil {
+		return
+	}
+	ocall = &implArithCountClientCall{ClientCall: call}
+	return
+}
+
+func (c implArithClientStub) StreamingAdd(ctx *context.T, opts ...rpc.CallOpt) (ocall ArithStreamingAddClientCall, err error) {
+	var call rpc.ClientCall
+	if call, err = v23.GetClient(ctx).StartCall(ctx, c.name, "StreamingAdd", nil, opts...); err != nil {
+		return
+	}
+	ocall = &implArithStreamingAddClientCall{ClientCall: call}
+	return
+}
+
+func (c implArithClientStub) QuoteAny(ctx *context.T, i0 *vdl.Value, opts ...rpc.CallOpt) (o0 *vdl.Value, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "QuoteAny", []interface{}{i0}, []interface{}{&o0}, opts...)
+	return
+}
+
+// ArithCountClientStream is the client stream for Arith.Count.
+type ArithCountClientStream interface {
+	// RecvStream returns the receiver side of the Arith.Count client stream.
+	RecvStream() interface {
+		// Advance stages an item so that it may be retrieved via Value.  Returns
+		// true iff there is an item to retrieve.  Advance must be called before
+		// Value is called.  May block if an item is not available.
+		Advance() bool
+		// Value returns the item that was staged by Advance.  May panic if Advance
+		// returned false or was not called.  Never blocks.
+		Value() int32
+		// Err returns any error encountered by Advance.  Never blocks.
+		Err() error
+	}
+}
+
+// ArithCountClientCall represents the call returned from Arith.Count.
+type ArithCountClientCall interface {
+	ArithCountClientStream
+	// Finish blocks until the server is done, and returns the positional return
+	// values for call.
+	//
+	// Finish returns immediately if the call has been canceled; depending on the
+	// timing the output could either be an error signaling cancelation, or the
+	// valid positional return values from the server.
+	//
+	// Calling Finish is mandatory for releasing stream resources, unless the call
+	// has been canceled or any of the other methods return an error.  Finish should
+	// be called at most once.
+	Finish() error
+}
+
+type implArithCountClientCall struct {
+	rpc.ClientCall
+	valRecv int32
+	errRecv error
+}
+
+func (c *implArithCountClientCall) RecvStream() interface {
+	Advance() bool
+	Value() int32
+	Err() error
+} {
+	return implArithCountClientCallRecv{c}
+}
+
+type implArithCountClientCallRecv struct {
+	c *implArithCountClientCall
+}
+
+func (c implArithCountClientCallRecv) Advance() bool {
+	c.c.errRecv = c.c.Recv(&c.c.valRecv)
+	return c.c.errRecv == nil
+}
+func (c implArithCountClientCallRecv) Value() int32 {
+	return c.c.valRecv
+}
+func (c implArithCountClientCallRecv) Err() error {
+	if c.c.errRecv == io.EOF {
+		return nil
+	}
+	return c.c.errRecv
+}
+func (c *implArithCountClientCall) Finish() (err error) {
+	err = c.ClientCall.Finish()
+	return
+}
+
+// ArithStreamingAddClientStream is the client stream for Arith.StreamingAdd.
+type ArithStreamingAddClientStream interface {
+	// RecvStream returns the receiver side of the Arith.StreamingAdd client stream.
+	RecvStream() interface {
+		// Advance stages an item so that it may be retrieved via Value.  Returns
+		// true iff there is an item to retrieve.  Advance must be called before
+		// Value is called.  May block if an item is not available.
+		Advance() bool
+		// Value returns the item that was staged by Advance.  May panic if Advance
+		// returned false or was not called.  Never blocks.
+		Value() int32
+		// Err returns any error encountered by Advance.  Never blocks.
+		Err() error
+	}
+	// SendStream returns the send side of the Arith.StreamingAdd client stream.
+	SendStream() interface {
+		// Send places the item onto the output stream.  Returns errors
+		// encountered while sending, or if Send is called after Close or
+		// the stream has been canceled.  Blocks if there is no buffer
+		// space; will unblock when buffer space is available or after
+		// the stream has been canceled.
+		Send(item int32) error
+		// Close indicates to the server that no more items will be sent;
+		// server Recv calls will receive io.EOF after all sent items.
+		// This is an optional call - e.g. a client might call Close if it
+		// needs to continue receiving items from the server after it's
+		// done sending.  Returns errors encountered while closing, or if
+		// Close is called after the stream has been canceled.  Like Send,
+		// blocks if there is no buffer space available.
+		Close() error
+	}
+}
+
+// ArithStreamingAddClientCall represents the call returned from Arith.StreamingAdd.
+type ArithStreamingAddClientCall interface {
+	ArithStreamingAddClientStream
+	// Finish performs the equivalent of SendStream().Close, then blocks until
+	// the server is done, and returns the positional return values for the call.
+	//
+	// Finish returns immediately if the call has been canceled; depending on the
+	// timing the output could either be an error signaling cancelation, or the
+	// valid positional return values from the server.
+	//
+	// Calling Finish is mandatory for releasing stream resources, unless the call
+	// has been canceled or any of the other methods return an error.  Finish should
+	// be called at most once.
+	Finish() (total int32, err error)
+}
+
+type implArithStreamingAddClientCall struct {
+	rpc.ClientCall
+	valRecv int32
+	errRecv error
+}
+
+func (c *implArithStreamingAddClientCall) RecvStream() interface {
+	Advance() bool
+	Value() int32
+	Err() error
+} {
+	return implArithStreamingAddClientCallRecv{c}
+}
+
+type implArithStreamingAddClientCallRecv struct {
+	c *implArithStreamingAddClientCall
+}
+
+func (c implArithStreamingAddClientCallRecv) Advance() bool {
+	c.c.errRecv = c.c.Recv(&c.c.valRecv)
+	return c.c.errRecv == nil
+}
+func (c implArithStreamingAddClientCallRecv) Value() int32 {
+	return c.c.valRecv
+}
+func (c implArithStreamingAddClientCallRecv) Err() error {
+	if c.c.errRecv == io.EOF {
+		return nil
+	}
+	return c.c.errRecv
+}
+func (c *implArithStreamingAddClientCall) SendStream() interface {
+	Send(item int32) error
+	Close() error
+} {
+	return implArithStreamingAddClientCallSend{c}
+}
+
+type implArithStreamingAddClientCallSend struct {
+	c *implArithStreamingAddClientCall
+}
+
+func (c implArithStreamingAddClientCallSend) Send(item int32) error {
+	return c.c.Send(item)
+}
+func (c implArithStreamingAddClientCallSend) Close() error {
+	return c.c.CloseSend()
+}
+func (c *implArithStreamingAddClientCall) Finish() (o0 int32, err error) {
+	err = c.ClientCall.Finish(&o0)
+	return
+}
+
+// ArithServerMethods is the interface a server writer
+// implements for Arith.
+//
+// Arith is an example of an interface definition for an arithmetic service.
+// Things to note:
+//   * There must be at least 1 out-arg, and the last out-arg must be error.
+type ArithServerMethods interface {
+	// Add is a typical method with multiple input and output arguments.
+	Add(ctx *context.T, call rpc.ServerCall, a int32, b int32) (int32, error)
+	// DivMod shows that runs of args with the same type can use the short form,
+	// just like Go.
+	DivMod(ctx *context.T, call rpc.ServerCall, a int32, b int32) (quot int32, rem int32, err error)
+	// Sub shows that you can use data types defined in other packages.
+	Sub(ctx *context.T, call rpc.ServerCall, args base.Args) (int32, error)
+	// Mul tries another data type defined in another package.
+	Mul(ctx *context.T, call rpc.ServerCall, nested base.NestedArgs) (int32, error)
+	// GenError shows that it's fine to have no in args, and no out args other
+	// than "error".  In addition GenError shows the usage of tags.  Tags are a
+	// sequence of constants.  There's no requirement on uniqueness of types or
+	// values, and regular const expressions may also be used.
+	GenError(*context.T, rpc.ServerCall) error
+	// Count shows using only an int32 out-stream type, with no in-stream type.
+	Count(ctx *context.T, call ArithCountServerCall, start int32) error
+	// StreamingAdd shows a bidirectional stream.
+	StreamingAdd(*context.T, ArithStreamingAddServerCall) (total int32, err error)
+	// QuoteAny shows the any built-in type, representing a value of any type.
+	QuoteAny(ctx *context.T, call rpc.ServerCall, a *vdl.Value) (*vdl.Value, error)
+}
+
+// ArithServerStubMethods is the server interface containing
+// Arith methods, as expected by rpc.Server.
+// The only difference between this interface and ArithServerMethods
+// is the streaming methods.
+type ArithServerStubMethods interface {
+	// Add is a typical method with multiple input and output arguments.
+	Add(ctx *context.T, call rpc.ServerCall, a int32, b int32) (int32, error)
+	// DivMod shows that runs of args with the same type can use the short form,
+	// just like Go.
+	DivMod(ctx *context.T, call rpc.ServerCall, a int32, b int32) (quot int32, rem int32, err error)
+	// Sub shows that you can use data types defined in other packages.
+	Sub(ctx *context.T, call rpc.ServerCall, args base.Args) (int32, error)
+	// Mul tries another data type defined in another package.
+	Mul(ctx *context.T, call rpc.ServerCall, nested base.NestedArgs) (int32, error)
+	// GenError shows that it's fine to have no in args, and no out args other
+	// than "error".  In addition GenError shows the usage of tags.  Tags are a
+	// sequence of constants.  There's no requirement on uniqueness of types or
+	// values, and regular const expressions may also be used.
+	GenError(*context.T, rpc.ServerCall) error
+	// Count shows using only an int32 out-stream type, with no in-stream type.
+	Count(ctx *context.T, call *ArithCountServerCallStub, start int32) error
+	// StreamingAdd shows a bidirectional stream.
+	StreamingAdd(*context.T, *ArithStreamingAddServerCallStub) (total int32, err error)
+	// QuoteAny shows the any built-in type, representing a value of any type.
+	QuoteAny(ctx *context.T, call rpc.ServerCall, a *vdl.Value) (*vdl.Value, error)
+}
+
+// ArithServerStub adds universal methods to ArithServerStubMethods.
+type ArithServerStub interface {
+	ArithServerStubMethods
+	// Describe the Arith interfaces.
+	Describe__() []rpc.InterfaceDesc
+}
+
+// ArithServer returns a server stub for Arith.
+// It converts an implementation of ArithServerMethods into
+// an object that may be used by rpc.Server.
+func ArithServer(impl ArithServerMethods) ArithServerStub {
+	stub := implArithServerStub{
+		impl: impl,
+	}
+	// Initialize GlobState; always check the stub itself first, to handle the
+	// case where the user has the Glob method defined in their VDL source.
+	if gs := rpc.NewGlobState(stub); gs != nil {
+		stub.gs = gs
+	} else if gs := rpc.NewGlobState(impl); gs != nil {
+		stub.gs = gs
+	}
+	return stub
+}
+
+type implArithServerStub struct {
+	impl ArithServerMethods
+	gs   *rpc.GlobState
+}
+
+func (s implArithServerStub) Add(ctx *context.T, call rpc.ServerCall, i0 int32, i1 int32) (int32, error) {
+	return s.impl.Add(ctx, call, i0, i1)
+}
+
+func (s implArithServerStub) DivMod(ctx *context.T, call rpc.ServerCall, i0 int32, i1 int32) (int32, int32, error) {
+	return s.impl.DivMod(ctx, call, i0, i1)
+}
+
+func (s implArithServerStub) Sub(ctx *context.T, call rpc.ServerCall, i0 base.Args) (int32, error) {
+	return s.impl.Sub(ctx, call, i0)
+}
+
+func (s implArithServerStub) Mul(ctx *context.T, call rpc.ServerCall, i0 base.NestedArgs) (int32, error) {
+	return s.impl.Mul(ctx, call, i0)
+}
+
+func (s implArithServerStub) GenError(ctx *context.T, call rpc.ServerCall) error {
+	return s.impl.GenError(ctx, call)
+}
+
+func (s implArithServerStub) Count(ctx *context.T, call *ArithCountServerCallStub, i0 int32) error {
+	return s.impl.Count(ctx, call, i0)
+}
+
+func (s implArithServerStub) StreamingAdd(ctx *context.T, call *ArithStreamingAddServerCallStub) (int32, error) {
+	return s.impl.StreamingAdd(ctx, call)
+}
+
+func (s implArithServerStub) QuoteAny(ctx *context.T, call rpc.ServerCall, i0 *vdl.Value) (*vdl.Value, error) {
+	return s.impl.QuoteAny(ctx, call, i0)
+}
+
+func (s implArithServerStub) Globber() *rpc.GlobState {
+	return s.gs
+}
+
+func (s implArithServerStub) Describe__() []rpc.InterfaceDesc {
+	return []rpc.InterfaceDesc{ArithDesc}
+}
+
+// ArithDesc describes the Arith interface.
+var ArithDesc rpc.InterfaceDesc = descArith
+
+// descArith hides the desc to keep godoc clean.
+var descArith = rpc.InterfaceDesc{
+	Name:    "Arith",
+	PkgPath: "v.io/x/ref/lib/vdl/testdata/arith",
+	Doc:     "// Arith is an example of an interface definition for an arithmetic service.\n// Things to note:\n//   * There must be at least 1 out-arg, and the last out-arg must be error.",
+	Methods: []rpc.MethodDesc{
+		{
+			Name: "Add",
+			Doc:  "// Add is a typical method with multiple input and output arguments.",
+			InArgs: []rpc.ArgDesc{
+				{"a", ``}, // int32
+				{"b", ``}, // int32
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"", ``}, // int32
+			},
+		},
+		{
+			Name: "DivMod",
+			Doc:  "// DivMod shows that runs of args with the same type can use the short form,\n// just like Go.",
+			InArgs: []rpc.ArgDesc{
+				{"a", ``}, // int32
+				{"b", ``}, // int32
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"quot", ``}, // int32
+				{"rem", ``},  // int32
+			},
+		},
+		{
+			Name: "Sub",
+			Doc:  "// Sub shows that you can use data types defined in other packages.",
+			InArgs: []rpc.ArgDesc{
+				{"args", ``}, // base.Args
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"", ``}, // int32
+			},
+		},
+		{
+			Name: "Mul",
+			Doc:  "// Mul tries another data type defined in another package.",
+			InArgs: []rpc.ArgDesc{
+				{"nested", ``}, // base.NestedArgs
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"", ``}, // int32
+			},
+		},
+		{
+			Name: "GenError",
+			Doc:  "// GenError shows that it's fine to have no in args, and no out args other\n// than \"error\".  In addition GenError shows the usage of tags.  Tags are a\n// sequence of constants.  There's no requirement on uniqueness of types or\n// values, and regular const expressions may also be used.",
+			Tags: []*vdl.Value{vdl.ValueOf("foo"), vdl.ValueOf("barz"), vdl.ValueOf("hello"), vdl.ValueOf(int32(129)), vdl.ValueOf(uint64(36))},
+		},
+		{
+			Name: "Count",
+			Doc:  "// Count shows using only an int32 out-stream type, with no in-stream type.",
+			InArgs: []rpc.ArgDesc{
+				{"start", ``}, // int32
+			},
+		},
+		{
+			Name: "StreamingAdd",
+			Doc:  "// StreamingAdd shows a bidirectional stream.",
+			OutArgs: []rpc.ArgDesc{
+				{"total", ``}, // int32
+			},
+		},
+		{
+			Name: "QuoteAny",
+			Doc:  "// QuoteAny shows the any built-in type, representing a value of any type.",
+			InArgs: []rpc.ArgDesc{
+				{"a", ``}, // *vdl.Value
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"", ``}, // *vdl.Value
+			},
+		},
+	},
+}
+
+// ArithCountServerStream is the server stream for Arith.Count.
+type ArithCountServerStream interface {
+	// SendStream returns the send side of the Arith.Count server stream.
+	SendStream() interface {
+		// Send places the item onto the output stream.  Returns errors encountered
+		// while sending.  Blocks if there is no buffer space; will unblock when
+		// buffer space is available.
+		Send(item int32) error
+	}
+}
+
+// ArithCountServerCall represents the context passed to Arith.Count.
+type ArithCountServerCall interface {
+	rpc.ServerCall
+	ArithCountServerStream
+}
+
+// ArithCountServerCallStub is a wrapper that converts rpc.StreamServerCall into
+// a typesafe stub that implements ArithCountServerCall.
+type ArithCountServerCallStub struct {
+	rpc.StreamServerCall
+}
+
+// Init initializes ArithCountServerCallStub from rpc.StreamServerCall.
+func (s *ArithCountServerCallStub) Init(call rpc.StreamServerCall) {
+	s.StreamServerCall = call
+}
+
+// SendStream returns the send side of the Arith.Count server stream.
+func (s *ArithCountServerCallStub) SendStream() interface {
+	Send(item int32) error
+} {
+	return implArithCountServerCallSend{s}
+}
+
+type implArithCountServerCallSend struct {
+	s *ArithCountServerCallStub
+}
+
+func (s implArithCountServerCallSend) Send(item int32) error {
+	return s.s.Send(item)
+}
+
+// ArithStreamingAddServerStream is the server stream for Arith.StreamingAdd.
+type ArithStreamingAddServerStream interface {
+	// RecvStream returns the receiver side of the Arith.StreamingAdd server stream.
+	RecvStream() interface {
+		// Advance stages an item so that it may be retrieved via Value.  Returns
+		// true iff there is an item to retrieve.  Advance must be called before
+		// Value is called.  May block if an item is not available.
+		Advance() bool
+		// Value returns the item that was staged by Advance.  May panic if Advance
+		// returned false or was not called.  Never blocks.
+		Value() int32
+		// Err returns any error encountered by Advance.  Never blocks.
+		Err() error
+	}
+	// SendStream returns the send side of the Arith.StreamingAdd server stream.
+	SendStream() interface {
+		// Send places the item onto the output stream.  Returns errors encountered
+		// while sending.  Blocks if there is no buffer space; will unblock when
+		// buffer space is available.
+		Send(item int32) error
+	}
+}
+
+// ArithStreamingAddServerCall represents the context passed to Arith.StreamingAdd.
+type ArithStreamingAddServerCall interface {
+	rpc.ServerCall
+	ArithStreamingAddServerStream
+}
+
+// ArithStreamingAddServerCallStub is a wrapper that converts rpc.StreamServerCall into
+// a typesafe stub that implements ArithStreamingAddServerCall.
+type ArithStreamingAddServerCallStub struct {
+	rpc.StreamServerCall
+	valRecv int32
+	errRecv error
+}
+
+// Init initializes ArithStreamingAddServerCallStub from rpc.StreamServerCall.
+func (s *ArithStreamingAddServerCallStub) Init(call rpc.StreamServerCall) {
+	s.StreamServerCall = call
+}
+
+// RecvStream returns the receiver side of the Arith.StreamingAdd server stream.
+func (s *ArithStreamingAddServerCallStub) RecvStream() interface {
+	Advance() bool
+	Value() int32
+	Err() error
+} {
+	return implArithStreamingAddServerCallRecv{s}
+}
+
+type implArithStreamingAddServerCallRecv struct {
+	s *ArithStreamingAddServerCallStub
+}
+
+func (s implArithStreamingAddServerCallRecv) Advance() bool {
+	s.s.errRecv = s.s.Recv(&s.s.valRecv)
+	return s.s.errRecv == nil
+}
+func (s implArithStreamingAddServerCallRecv) Value() int32 {
+	return s.s.valRecv
+}
+func (s implArithStreamingAddServerCallRecv) Err() error {
+	if s.s.errRecv == io.EOF {
+		return nil
+	}
+	return s.s.errRecv
+}
+
+// SendStream returns the send side of the Arith.StreamingAdd server stream.
+func (s *ArithStreamingAddServerCallStub) SendStream() interface {
+	Send(item int32) error
+} {
+	return implArithStreamingAddServerCallSend{s}
+}
+
+type implArithStreamingAddServerCallSend struct {
+	s *ArithStreamingAddServerCallStub
+}
+
+func (s implArithStreamingAddServerCallSend) Send(item int32) error {
+	return s.s.Send(item)
+}
+
+// CalculatorClientMethods is the client interface
+// containing Calculator methods.
+type CalculatorClientMethods interface {
+	// Arith is an example of an interface definition for an arithmetic service.
+	// Things to note:
+	//   * There must be at least 1 out-arg, and the last out-arg must be error.
+	ArithClientMethods
+	// AdvancedMath is an interface for more advanced math than arith.  It embeds
+	// interfaces defined both in the same file and in an external package; and in
+	// turn it is embedded by arith.Calculator (which is in the same package but
+	// different file) to verify that embedding works in all these scenarios.
+	AdvancedMathClientMethods
+	On(*context.T, ...rpc.CallOpt) error  // On turns the calculator on.
+	Off(*context.T, ...rpc.CallOpt) error // Off turns the calculator off.
+}
+
+// CalculatorClientStub adds universal methods to CalculatorClientMethods.
+type CalculatorClientStub interface {
+	CalculatorClientMethods
+	rpc.UniversalServiceMethods
+}
+
+// CalculatorClient returns a client stub for Calculator.
+func CalculatorClient(name string) CalculatorClientStub {
+	return implCalculatorClientStub{name, ArithClient(name), AdvancedMathClient(name)}
+}
+
+type implCalculatorClientStub struct {
+	name string
+
+	ArithClientStub
+	AdvancedMathClientStub
+}
+
+func (c implCalculatorClientStub) On(ctx *context.T, opts ...rpc.CallOpt) (err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "On", nil, nil, opts...)
+	return
+}
+
+func (c implCalculatorClientStub) Off(ctx *context.T, opts ...rpc.CallOpt) (err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "Off", nil, nil, opts...)
+	return
+}
+
+// CalculatorServerMethods is the interface a server writer
+// implements for Calculator.
+type CalculatorServerMethods interface {
+	// Arith is an example of an interface definition for an arithmetic service.
+	// Things to note:
+	//   * There must be at least 1 out-arg, and the last out-arg must be error.
+	ArithServerMethods
+	// AdvancedMath is an interface for more advanced math than arith.  It embeds
+	// interfaces defined both in the same file and in an external package; and in
+	// turn it is embedded by arith.Calculator (which is in the same package but
+	// different file) to verify that embedding works in all these scenarios.
+	AdvancedMathServerMethods
+	On(*context.T, rpc.ServerCall) error  // On turns the calculator on.
+	Off(*context.T, rpc.ServerCall) error // Off turns the calculator off.
+}
+
+// CalculatorServerStubMethods is the server interface containing
+// Calculator methods, as expected by rpc.Server.
+// The only difference between this interface and CalculatorServerMethods
+// is the streaming methods.
+type CalculatorServerStubMethods interface {
+	// Arith is an example of an interface definition for an arithmetic service.
+	// Things to note:
+	//   * There must be at least 1 out-arg, and the last out-arg must be error.
+	ArithServerStubMethods
+	// AdvancedMath is an interface for more advanced math than arith.  It embeds
+	// interfaces defined both in the same file and in an external package; and in
+	// turn it is embedded by arith.Calculator (which is in the same package but
+	// different file) to verify that embedding works in all these scenarios.
+	AdvancedMathServerStubMethods
+	On(*context.T, rpc.ServerCall) error  // On turns the calculator on.
+	Off(*context.T, rpc.ServerCall) error // Off turns the calculator off.
+}
+
+// CalculatorServerStub adds universal methods to CalculatorServerStubMethods.
+type CalculatorServerStub interface {
+	CalculatorServerStubMethods
+	// Describe the Calculator interfaces.
+	Describe__() []rpc.InterfaceDesc
+}
+
+// CalculatorServer returns a server stub for Calculator.
+// It converts an implementation of CalculatorServerMethods into
+// an object that may be used by rpc.Server.
+func CalculatorServer(impl CalculatorServerMethods) CalculatorServerStub {
+	stub := implCalculatorServerStub{
+		impl:                   impl,
+		ArithServerStub:        ArithServer(impl),
+		AdvancedMathServerStub: AdvancedMathServer(impl),
+	}
+	// Initialize GlobState; always check the stub itself first, to handle the
+	// case where the user has the Glob method defined in their VDL source.
+	if gs := rpc.NewGlobState(stub); gs != nil {
+		stub.gs = gs
+	} else if gs := rpc.NewGlobState(impl); gs != nil {
+		stub.gs = gs
+	}
+	return stub
+}
+
+type implCalculatorServerStub struct {
+	impl CalculatorServerMethods
+	ArithServerStub
+	AdvancedMathServerStub
+	gs *rpc.GlobState
+}
+
+func (s implCalculatorServerStub) On(ctx *context.T, call rpc.ServerCall) error {
+	return s.impl.On(ctx, call)
+}
+
+func (s implCalculatorServerStub) Off(ctx *context.T, call rpc.ServerCall) error {
+	return s.impl.Off(ctx, call)
+}
+
+func (s implCalculatorServerStub) Globber() *rpc.GlobState {
+	return s.gs
+}
+
+func (s implCalculatorServerStub) Describe__() []rpc.InterfaceDesc {
+	return []rpc.InterfaceDesc{CalculatorDesc, ArithDesc, AdvancedMathDesc, TrigonometryDesc, exp.ExpDesc}
+}
+
+// CalculatorDesc describes the Calculator interface.
+var CalculatorDesc rpc.InterfaceDesc = descCalculator
+
+// descCalculator hides the desc to keep godoc clean.
+var descCalculator = rpc.InterfaceDesc{
+	Name:    "Calculator",
+	PkgPath: "v.io/x/ref/lib/vdl/testdata/arith",
+	Embeds: []rpc.EmbedDesc{
+		{"Arith", "v.io/x/ref/lib/vdl/testdata/arith", "// Arith is an example of an interface definition for an arithmetic service.\n// Things to note:\n//   * There must be at least 1 out-arg, and the last out-arg must be error."},
+		{"AdvancedMath", "v.io/x/ref/lib/vdl/testdata/arith", "// AdvancedMath is an interface for more advanced math than arith.  It embeds\n// interfaces defined both in the same file and in an external package; and in\n// turn it is embedded by arith.Calculator (which is in the same package but\n// different file) to verify that embedding works in all these scenarios."},
+	},
+	Methods: []rpc.MethodDesc{
+		{
+			Name: "On",
+		},
+		{
+			Name: "Off",
+			Tags: []*vdl.Value{vdl.ValueOf("offtag")},
+		},
+	},
+}
diff --git a/lib/vdl/testdata/arith/exp/exp.vdl b/lib/vdl/testdata/arith/exp/exp.vdl
new file mode 100644
index 0000000..1db1f1f
--- /dev/null
+++ b/lib/vdl/testdata/arith/exp/exp.vdl
@@ -0,0 +1,11 @@
+// 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 exp is used to test that embedding interfaces works across packages.
+// The arith.Calculator vdl interface embeds the Exp interface.
+package exp
+
+type Exp interface {
+	Exp(x float64) (float64 | error)
+}
diff --git a/lib/vdl/testdata/arith/exp/exp.vdl.go b/lib/vdl/testdata/arith/exp/exp.vdl.go
new file mode 100644
index 0000000..2229371
--- /dev/null
+++ b/lib/vdl/testdata/arith/exp/exp.vdl.go
@@ -0,0 +1,116 @@
+// 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 by the vanadium vdl tool.
+// Source: exp.vdl
+
+// Package exp is used to test that embedding interfaces works across packages.
+// The arith.Calculator vdl interface embeds the Exp interface.
+package exp
+
+import (
+	// VDL system imports
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+)
+
+// ExpClientMethods is the client interface
+// containing Exp methods.
+type ExpClientMethods interface {
+	Exp(ctx *context.T, x float64, opts ...rpc.CallOpt) (float64, error)
+}
+
+// ExpClientStub adds universal methods to ExpClientMethods.
+type ExpClientStub interface {
+	ExpClientMethods
+	rpc.UniversalServiceMethods
+}
+
+// ExpClient returns a client stub for Exp.
+func ExpClient(name string) ExpClientStub {
+	return implExpClientStub{name}
+}
+
+type implExpClientStub struct {
+	name string
+}
+
+func (c implExpClientStub) Exp(ctx *context.T, i0 float64, opts ...rpc.CallOpt) (o0 float64, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "Exp", []interface{}{i0}, []interface{}{&o0}, opts...)
+	return
+}
+
+// ExpServerMethods is the interface a server writer
+// implements for Exp.
+type ExpServerMethods interface {
+	Exp(ctx *context.T, call rpc.ServerCall, x float64) (float64, error)
+}
+
+// ExpServerStubMethods is the server interface containing
+// Exp methods, as expected by rpc.Server.
+// There is no difference between this interface and ExpServerMethods
+// since there are no streaming methods.
+type ExpServerStubMethods ExpServerMethods
+
+// ExpServerStub adds universal methods to ExpServerStubMethods.
+type ExpServerStub interface {
+	ExpServerStubMethods
+	// Describe the Exp interfaces.
+	Describe__() []rpc.InterfaceDesc
+}
+
+// ExpServer returns a server stub for Exp.
+// It converts an implementation of ExpServerMethods into
+// an object that may be used by rpc.Server.
+func ExpServer(impl ExpServerMethods) ExpServerStub {
+	stub := implExpServerStub{
+		impl: impl,
+	}
+	// Initialize GlobState; always check the stub itself first, to handle the
+	// case where the user has the Glob method defined in their VDL source.
+	if gs := rpc.NewGlobState(stub); gs != nil {
+		stub.gs = gs
+	} else if gs := rpc.NewGlobState(impl); gs != nil {
+		stub.gs = gs
+	}
+	return stub
+}
+
+type implExpServerStub struct {
+	impl ExpServerMethods
+	gs   *rpc.GlobState
+}
+
+func (s implExpServerStub) Exp(ctx *context.T, call rpc.ServerCall, i0 float64) (float64, error) {
+	return s.impl.Exp(ctx, call, i0)
+}
+
+func (s implExpServerStub) Globber() *rpc.GlobState {
+	return s.gs
+}
+
+func (s implExpServerStub) Describe__() []rpc.InterfaceDesc {
+	return []rpc.InterfaceDesc{ExpDesc}
+}
+
+// ExpDesc describes the Exp interface.
+var ExpDesc rpc.InterfaceDesc = descExp
+
+// descExp hides the desc to keep godoc clean.
+var descExp = rpc.InterfaceDesc{
+	Name:    "Exp",
+	PkgPath: "v.io/x/ref/lib/vdl/testdata/arith/exp",
+	Methods: []rpc.MethodDesc{
+		{
+			Name: "Exp",
+			InArgs: []rpc.ArgDesc{
+				{"x", ``}, // float64
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"", ``}, // float64
+			},
+		},
+	},
+}
diff --git a/lib/vdl/testdata/base/base.vdl b/lib/vdl/testdata/base/base.vdl
new file mode 100644
index 0000000..5b71d26
--- /dev/null
+++ b/lib/vdl/testdata/base/base.vdl
@@ -0,0 +1,239 @@
+// 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 base is a simple single-file test of vdl functionality.
+package base
+
+type (
+	NamedBool       bool
+	NamedByte       byte
+	NamedUint16     uint16
+	NamedUint32     uint32
+	NamedUint64     uint64
+	NamedInt16      int16
+	NamedInt32      int32
+	NamedInt64      int64
+	NamedFloat32    float32
+	NamedFloat64    float64
+	NamedComplex64  complex64
+	NamedComplex128 complex128
+	NamedString     string
+	NamedEnum       enum{A;B;C}
+	NamedArray      [2]bool
+	NamedList       []uint32
+	NamedSet        set[string]
+	NamedMap        map[string]float32
+	NamedStruct     struct{A bool;B string;C int32}
+	NamedUnion      union{A bool;B string;C int32}
+)
+
+type Scalars struct {
+	A0  bool
+	A1  byte
+	A2  uint16
+	A3  uint32
+	A4  uint64
+	A5  int16
+	A6  int32
+	A7  int64
+	A8  float32
+	A9  float64
+	A10 complex64
+	A11 complex128
+	A12 string
+	A13 error
+	A14 any
+	A15 typeobject
+
+	B0  NamedBool
+	B1  NamedByte
+	B2  NamedUint16
+	B3  NamedUint32
+	B4  NamedUint64
+	B5  NamedInt16
+	B6  NamedInt32
+	B7  NamedInt64
+	B8  NamedFloat32
+	B9  NamedFloat64
+	B10 NamedComplex64
+	B11 NamedComplex128
+	B12 NamedString
+	B13 NamedEnum
+	B14 NamedUnion
+}
+
+// These are all scalars that may be used as map or set keys.
+type KeyScalars struct {
+	A0  bool
+	A1  byte
+	A2  uint16
+	A3  uint32
+	A4  uint64
+	A5  int16
+	A6  int32
+	A7  int64
+	A8  float32
+	A9  float64
+	A10 complex64
+	A11 complex128
+	A12 string
+
+	B0  NamedBool
+	B1  NamedByte
+	B2  NamedUint16
+	B3  NamedUint32
+	B4  NamedUint64
+	B5  NamedInt16
+	B6  NamedInt32
+	B7  NamedInt64
+	B8  NamedFloat32
+	B9  NamedFloat64
+	B10 NamedComplex64
+	B11 NamedComplex128
+	B12 NamedString
+}
+
+type ScalarsArray [2]Scalars
+
+type Composites struct {
+	A0 Scalars
+	A1 ScalarsArray
+	A2 []Scalars
+	A3 set[KeyScalars]
+	A4 map[string]Scalars
+	A5 map[KeyScalars][]map[string]complex128
+}
+
+type CompositesArray [2]Composites
+
+type CompComp struct {
+	A0 Composites
+	A1 CompositesArray
+	A2 []Composites
+	A3 map[string]Composites
+	A4 map[KeyScalars][]map[string]Composites
+}
+
+// NestedArgs is defined before Args; that's allowed in regular Go, and also
+// allowed in our vdl files.  The compiler will re-order dependent types to ease
+// code generation in other languages.
+type NestedArgs struct {
+	Args Args
+}
+
+// Args will be reordered to show up before NestedArgs in the generated output.
+type Args struct {
+	A int32
+	B int32
+}
+
+const (
+	Cbool = true
+	Cbyte = byte(1)
+	Cint32 = int32(2)
+	Cint64 = int64(3)
+	Cuint32 = uint32(4)
+	Cuint64 = uint64(5)
+	Cfloat32 = float32(6)
+	Cfloat64 = float64(7)
+	CNamedBool = NamedBool(true)
+	CNamedStruct = NamedStruct{A:true, B: "test",}
+	Ccomplex64 = complex64(8+9i)
+	Ccomplex128 = complex128(10+11i)
+	Cstring = "foo"
+	Cenum  = NamedEnum.A
+	Cunion = NamedUnion{A: true}
+	Carray = NamedArray{true, false}
+	Clist  = []int32{1, 2, 3}
+	Cset   = set[int32]{1, 2, 3}
+	cmap   = map[int32]string{1: "A", 2: "B", 3: "C"}
+	Cargs  = Args{1, 2}
+
+	CScalars = Scalars{
+		A0: true,
+		A1: 1,
+		A2: 2,
+		A3: 3,
+		A4: 4,
+		A5: 5,
+		A6: 6,
+		A7: 7,
+		A8: 8,
+		A9: 9,
+		A10: 10,
+		A11: 11,
+		A12: "abc",
+		A14: false,
+		A15: typeobject(bool),
+
+		B0: true,
+		B1: 1,
+		B2: 2,
+		B3: 3,
+		B4: 4,
+		B5: 5,
+		B6: 6,
+		B7: 7,
+		B8: 8,
+		B9: 9,
+		B10: 10,
+		B11: 11,
+		B12: "abc",
+		B13: NamedEnum.B,
+		B14: NamedUnion{C: 123},
+	}
+
+	True = true
+	Foo = "foo"
+	Five = int32(5)
+	SixSquared = Six*Six
+	FiveSquared = Five*Five
+	Six = uint64(6)
+
+	CTypeObject_bool       = typeobject(bool)
+	CTypeObject_string     = typeobject(string)
+	CTypeObject_bytes      = typeobject([]byte)
+	CTypeObject_byte       = typeobject(byte)
+	CTypeObject_uint16     = typeobject(uint16)
+	CTypeObject_int16      = typeobject(int16)
+	CTypeObject_float32    = typeobject(float32)
+	CTypeObject_complex64  = typeobject(complex64)
+	CTypeObject_enum       = typeobject(NamedEnum)
+	CTypeObject_Array      = typeobject(NamedArray)
+	CTypeObject_List       = typeobject([]string)
+	CTypeObject_Set        = typeobject(set[string])
+	CTypeObject_Map        = typeobject(map[string]int64)
+	CTypeObject_Struct     = typeobject(Scalars)
+	CTypeObject_Union      = typeobject(NamedUnion)
+	CTypeObject_TypeObject = typeobject(typeobject)
+	CTypeObject_Any        = typeobject(any)
+)
+
+type ServiceA interface {
+	MethodA1() error
+	MethodA2(a int32, b string) (s string | error)
+	MethodA3(a int32) stream<_, Scalars> (s string | error) {"tag", Six}
+	MethodA4(a int32) stream<int32, string> error
+}
+
+type ServiceB interface {
+	ServiceA
+	MethodB1(a Scalars, b Composites) (c CompComp | error)
+}
+
+// Error definitions, which allow stable error-checking across different address
+// spaces.
+error (
+	NoParams1() {"en":"en msg"}
+	NoParams2() {RetryRefetch, "en":"en msg", "fr":"fr msg"}
+
+	WithParams1(x string, y int32) {"en":"en x={x} y={y}"}
+	WithParams2(x string, y int32) {
+		RetryRefetch,
+		"en":"en x={x} y={y}",
+		"fr":"fr y={y} x={x}",
+	}
+
+	notExported(x string, y int32) {"en":"en x={x} y={y}"}
+)
diff --git a/lib/vdl/testdata/base/base.vdl.go b/lib/vdl/testdata/base/base.vdl.go
new file mode 100644
index 0000000..c68af45
--- /dev/null
+++ b/lib/vdl/testdata/base/base.vdl.go
@@ -0,0 +1,1179 @@
+// 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 by the vanadium vdl tool.
+// Source: base.vdl
+
+// Package base is a simple single-file test of vdl functionality.
+package base
+
+import (
+	// VDL system imports
+	"fmt"
+	"io"
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/i18n"
+	"v.io/v23/rpc"
+	"v.io/v23/vdl"
+	"v.io/v23/verror"
+)
+
+type NamedBool bool
+
+func (NamedBool) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/lib/vdl/testdata/base.NamedBool"`
+}) {
+}
+
+type NamedByte byte
+
+func (NamedByte) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/lib/vdl/testdata/base.NamedByte"`
+}) {
+}
+
+type NamedUint16 uint16
+
+func (NamedUint16) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/lib/vdl/testdata/base.NamedUint16"`
+}) {
+}
+
+type NamedUint32 uint32
+
+func (NamedUint32) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/lib/vdl/testdata/base.NamedUint32"`
+}) {
+}
+
+type NamedUint64 uint64
+
+func (NamedUint64) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/lib/vdl/testdata/base.NamedUint64"`
+}) {
+}
+
+type NamedInt16 int16
+
+func (NamedInt16) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/lib/vdl/testdata/base.NamedInt16"`
+}) {
+}
+
+type NamedInt32 int32
+
+func (NamedInt32) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/lib/vdl/testdata/base.NamedInt32"`
+}) {
+}
+
+type NamedInt64 int64
+
+func (NamedInt64) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/lib/vdl/testdata/base.NamedInt64"`
+}) {
+}
+
+type NamedFloat32 float32
+
+func (NamedFloat32) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/lib/vdl/testdata/base.NamedFloat32"`
+}) {
+}
+
+type NamedFloat64 float64
+
+func (NamedFloat64) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/lib/vdl/testdata/base.NamedFloat64"`
+}) {
+}
+
+type NamedComplex64 complex64
+
+func (NamedComplex64) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/lib/vdl/testdata/base.NamedComplex64"`
+}) {
+}
+
+type NamedComplex128 complex128
+
+func (NamedComplex128) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/lib/vdl/testdata/base.NamedComplex128"`
+}) {
+}
+
+type NamedString string
+
+func (NamedString) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/lib/vdl/testdata/base.NamedString"`
+}) {
+}
+
+type NamedEnum int
+
+const (
+	NamedEnumA NamedEnum = iota
+	NamedEnumB
+	NamedEnumC
+)
+
+// NamedEnumAll holds all labels for NamedEnum.
+var NamedEnumAll = [...]NamedEnum{NamedEnumA, NamedEnumB, NamedEnumC}
+
+// NamedEnumFromString creates a NamedEnum from a string label.
+func NamedEnumFromString(label string) (x NamedEnum, err error) {
+	err = x.Set(label)
+	return
+}
+
+// Set assigns label to x.
+func (x *NamedEnum) Set(label string) error {
+	switch label {
+	case "A", "a":
+		*x = NamedEnumA
+		return nil
+	case "B", "b":
+		*x = NamedEnumB
+		return nil
+	case "C", "c":
+		*x = NamedEnumC
+		return nil
+	}
+	*x = -1
+	return fmt.Errorf("unknown label %q in base.NamedEnum", label)
+}
+
+// String returns the string label of x.
+func (x NamedEnum) String() string {
+	switch x {
+	case NamedEnumA:
+		return "A"
+	case NamedEnumB:
+		return "B"
+	case NamedEnumC:
+		return "C"
+	}
+	return ""
+}
+
+func (NamedEnum) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/lib/vdl/testdata/base.NamedEnum"`
+	Enum struct{ A, B, C string }
+}) {
+}
+
+type NamedArray [2]bool
+
+func (NamedArray) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/lib/vdl/testdata/base.NamedArray"`
+}) {
+}
+
+type NamedList []uint32
+
+func (NamedList) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/lib/vdl/testdata/base.NamedList"`
+}) {
+}
+
+type NamedSet map[string]struct{}
+
+func (NamedSet) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/lib/vdl/testdata/base.NamedSet"`
+}) {
+}
+
+type NamedMap map[string]float32
+
+func (NamedMap) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/lib/vdl/testdata/base.NamedMap"`
+}) {
+}
+
+type NamedStruct struct {
+	A bool
+	B string
+	C int32
+}
+
+func (NamedStruct) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/lib/vdl/testdata/base.NamedStruct"`
+}) {
+}
+
+type (
+	// NamedUnion represents any single field of the NamedUnion union type.
+	NamedUnion interface {
+		// Index returns the field index.
+		Index() int
+		// Interface returns the field value as an interface.
+		Interface() interface{}
+		// Name returns the field name.
+		Name() string
+		// __VDLReflect describes the NamedUnion union type.
+		__VDLReflect(__NamedUnionReflect)
+	}
+	// NamedUnionA represents field A of the NamedUnion union type.
+	NamedUnionA struct{ Value bool }
+	// NamedUnionB represents field B of the NamedUnion union type.
+	NamedUnionB struct{ Value string }
+	// NamedUnionC represents field C of the NamedUnion union type.
+	NamedUnionC struct{ Value int32 }
+	// __NamedUnionReflect describes the NamedUnion union type.
+	__NamedUnionReflect struct {
+		Name  string `vdl:"v.io/x/ref/lib/vdl/testdata/base.NamedUnion"`
+		Type  NamedUnion
+		Union struct {
+			A NamedUnionA
+			B NamedUnionB
+			C NamedUnionC
+		}
+	}
+)
+
+func (x NamedUnionA) Index() int                       { return 0 }
+func (x NamedUnionA) Interface() interface{}           { return x.Value }
+func (x NamedUnionA) Name() string                     { return "A" }
+func (x NamedUnionA) __VDLReflect(__NamedUnionReflect) {}
+
+func (x NamedUnionB) Index() int                       { return 1 }
+func (x NamedUnionB) Interface() interface{}           { return x.Value }
+func (x NamedUnionB) Name() string                     { return "B" }
+func (x NamedUnionB) __VDLReflect(__NamedUnionReflect) {}
+
+func (x NamedUnionC) Index() int                       { return 2 }
+func (x NamedUnionC) Interface() interface{}           { return x.Value }
+func (x NamedUnionC) Name() string                     { return "C" }
+func (x NamedUnionC) __VDLReflect(__NamedUnionReflect) {}
+
+type Scalars struct {
+	A0  bool
+	A1  byte
+	A2  uint16
+	A3  uint32
+	A4  uint64
+	A5  int16
+	A6  int32
+	A7  int64
+	A8  float32
+	A9  float64
+	A10 complex64
+	A11 complex128
+	A12 string
+	A13 error
+	A14 *vdl.Value
+	A15 *vdl.Type
+	B0  NamedBool
+	B1  NamedByte
+	B2  NamedUint16
+	B3  NamedUint32
+	B4  NamedUint64
+	B5  NamedInt16
+	B6  NamedInt32
+	B7  NamedInt64
+	B8  NamedFloat32
+	B9  NamedFloat64
+	B10 NamedComplex64
+	B11 NamedComplex128
+	B12 NamedString
+	B13 NamedEnum
+	B14 NamedUnion
+}
+
+func (Scalars) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/lib/vdl/testdata/base.Scalars"`
+}) {
+}
+
+// These are all scalars that may be used as map or set keys.
+type KeyScalars struct {
+	A0  bool
+	A1  byte
+	A2  uint16
+	A3  uint32
+	A4  uint64
+	A5  int16
+	A6  int32
+	A7  int64
+	A8  float32
+	A9  float64
+	A10 complex64
+	A11 complex128
+	A12 string
+	B0  NamedBool
+	B1  NamedByte
+	B2  NamedUint16
+	B3  NamedUint32
+	B4  NamedUint64
+	B5  NamedInt16
+	B6  NamedInt32
+	B7  NamedInt64
+	B8  NamedFloat32
+	B9  NamedFloat64
+	B10 NamedComplex64
+	B11 NamedComplex128
+	B12 NamedString
+}
+
+func (KeyScalars) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/lib/vdl/testdata/base.KeyScalars"`
+}) {
+}
+
+type ScalarsArray [2]Scalars
+
+func (ScalarsArray) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/lib/vdl/testdata/base.ScalarsArray"`
+}) {
+}
+
+type Composites struct {
+	A0 Scalars
+	A1 ScalarsArray
+	A2 []Scalars
+	A3 map[KeyScalars]struct{}
+	A4 map[string]Scalars
+	A5 map[KeyScalars][]map[string]complex128
+}
+
+func (Composites) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/lib/vdl/testdata/base.Composites"`
+}) {
+}
+
+type CompositesArray [2]Composites
+
+func (CompositesArray) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/lib/vdl/testdata/base.CompositesArray"`
+}) {
+}
+
+type CompComp struct {
+	A0 Composites
+	A1 CompositesArray
+	A2 []Composites
+	A3 map[string]Composites
+	A4 map[KeyScalars][]map[string]Composites
+}
+
+func (CompComp) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/lib/vdl/testdata/base.CompComp"`
+}) {
+}
+
+// NestedArgs is defined before Args; that's allowed in regular Go, and also
+// allowed in our vdl files.  The compiler will re-order dependent types to ease
+// code generation in other languages.
+type NestedArgs struct {
+	Args Args
+}
+
+func (NestedArgs) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/lib/vdl/testdata/base.NestedArgs"`
+}) {
+}
+
+// Args will be reordered to show up before NestedArgs in the generated output.
+type Args struct {
+	A int32
+	B int32
+}
+
+func (Args) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/lib/vdl/testdata/base.Args"`
+}) {
+}
+
+func init() {
+	vdl.Register((*NamedBool)(nil))
+	vdl.Register((*NamedByte)(nil))
+	vdl.Register((*NamedUint16)(nil))
+	vdl.Register((*NamedUint32)(nil))
+	vdl.Register((*NamedUint64)(nil))
+	vdl.Register((*NamedInt16)(nil))
+	vdl.Register((*NamedInt32)(nil))
+	vdl.Register((*NamedInt64)(nil))
+	vdl.Register((*NamedFloat32)(nil))
+	vdl.Register((*NamedFloat64)(nil))
+	vdl.Register((*NamedComplex64)(nil))
+	vdl.Register((*NamedComplex128)(nil))
+	vdl.Register((*NamedString)(nil))
+	vdl.Register((*NamedEnum)(nil))
+	vdl.Register((*NamedArray)(nil))
+	vdl.Register((*NamedList)(nil))
+	vdl.Register((*NamedSet)(nil))
+	vdl.Register((*NamedMap)(nil))
+	vdl.Register((*NamedStruct)(nil))
+	vdl.Register((*NamedUnion)(nil))
+	vdl.Register((*Scalars)(nil))
+	vdl.Register((*KeyScalars)(nil))
+	vdl.Register((*ScalarsArray)(nil))
+	vdl.Register((*Composites)(nil))
+	vdl.Register((*CompositesArray)(nil))
+	vdl.Register((*CompComp)(nil))
+	vdl.Register((*NestedArgs)(nil))
+	vdl.Register((*Args)(nil))
+}
+
+const Cbool = true
+
+const Cbyte = byte(1)
+
+const Cint32 = int32(2)
+
+const Cint64 = int64(3)
+
+const Cuint32 = uint32(4)
+
+const Cuint64 = uint64(5)
+
+const Cfloat32 = float32(6)
+
+const Cfloat64 = float64(7)
+
+const CNamedBool = NamedBool(true)
+
+var CNamedStruct = NamedStruct{
+	A: true,
+	B: "test",
+}
+
+const Ccomplex64 = complex64(8 + 9i)
+
+const Ccomplex128 = complex128(10 + 11i)
+
+const Cstring = "foo"
+
+const Cenum = NamedEnumA
+
+var Cunion = NamedUnion(NamedUnionA{true})
+
+var Carray = NamedArray{
+	true,
+	false,
+}
+
+var Clist = []int32{
+	1,
+	2,
+	3,
+}
+
+var Cset = map[int32]struct{}{
+	1: struct{}{},
+	2: struct{}{},
+	3: struct{}{},
+}
+
+var cmap = map[int32]string{
+	1: "A",
+	2: "B",
+	3: "C",
+}
+
+var Cargs = Args{
+	A: 1,
+	B: 2,
+}
+
+var CScalars = Scalars{
+	A0:  true,
+	A1:  1,
+	A2:  2,
+	A3:  3,
+	A4:  4,
+	A5:  5,
+	A6:  6,
+	A7:  7,
+	A8:  8,
+	A9:  9,
+	A10: 10,
+	A11: 11,
+	A12: "abc",
+	A14: vdl.ValueOf(false),
+	A15: vdl.TypeOf(false),
+	B0:  true,
+	B1:  1,
+	B2:  2,
+	B3:  3,
+	B4:  4,
+	B5:  5,
+	B6:  6,
+	B7:  7,
+	B8:  8,
+	B9:  9,
+	B10: 10,
+	B11: 11,
+	B12: "abc",
+	B13: NamedEnumB,
+	B14: NamedUnionC{int32(123)},
+}
+
+const True = true
+
+const Foo = "foo"
+
+const Five = int32(5)
+
+const Six = uint64(6)
+
+const SixSquared = uint64(36)
+
+const FiveSquared = int32(25)
+
+var CTypeObject_bool = vdl.TypeOf(false)
+
+var CTypeObject_string = vdl.TypeOf("")
+
+var CTypeObject_bytes = vdl.TypeOf([]byte(nil))
+
+var CTypeObject_byte = vdl.TypeOf(byte(0))
+
+var CTypeObject_uint16 = vdl.TypeOf(uint16(0))
+
+var CTypeObject_int16 = vdl.TypeOf(int16(0))
+
+var CTypeObject_float32 = vdl.TypeOf(float32(0))
+
+var CTypeObject_complex64 = vdl.TypeOf(complex64(0))
+
+var CTypeObject_enum = vdl.TypeOf(NamedEnumA)
+
+var CTypeObject_Array = vdl.TypeOf(NamedArray{})
+
+var CTypeObject_List = vdl.TypeOf([]string(nil))
+
+var CTypeObject_Set = vdl.TypeOf(map[string]struct{}(nil))
+
+var CTypeObject_Map = vdl.TypeOf(map[string]int64(nil))
+
+var CTypeObject_Struct = vdl.TypeOf(Scalars{
+	A15: vdl.AnyType,
+	B14: NamedUnionA{false},
+})
+
+var CTypeObject_Union = vdl.TypeOf(NamedUnion(NamedUnionA{false}))
+
+var CTypeObject_TypeObject = vdl.TypeObjectType
+
+var CTypeObject_Any = vdl.AnyType
+
+var (
+	ErrNoParams1   = verror.Register("v.io/x/ref/lib/vdl/testdata/base.NoParams1", verror.NoRetry, "{1:}{2:} en msg")
+	ErrNoParams2   = verror.Register("v.io/x/ref/lib/vdl/testdata/base.NoParams2", verror.RetryRefetch, "{1:}{2:} en msg")
+	ErrWithParams1 = verror.Register("v.io/x/ref/lib/vdl/testdata/base.WithParams1", verror.NoRetry, "{1:}{2:} en x={3} y={4}")
+	ErrWithParams2 = verror.Register("v.io/x/ref/lib/vdl/testdata/base.WithParams2", verror.RetryRefetch, "{1:}{2:} en x={3} y={4}")
+	errNotExported = verror.Register("v.io/x/ref/lib/vdl/testdata/base.notExported", verror.NoRetry, "{1:}{2:} en x={3} y={4}")
+)
+
+func init() {
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrNoParams1.ID), "{1:}{2:} en msg")
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrNoParams2.ID), "{1:}{2:} en msg")
+	i18n.Cat().SetWithBase(i18n.LangID("fr"), i18n.MsgID(ErrNoParams2.ID), "{1:}{2:} fr msg")
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrWithParams1.ID), "{1:}{2:} en x={3} y={4}")
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrWithParams2.ID), "{1:}{2:} en x={3} y={4}")
+	i18n.Cat().SetWithBase(i18n.LangID("fr"), i18n.MsgID(ErrWithParams2.ID), "{1:}{2:} fr y={4} x={3}")
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(errNotExported.ID), "{1:}{2:} en x={3} y={4}")
+}
+
+// NewErrNoParams1 returns an error with the ErrNoParams1 ID.
+func NewErrNoParams1(ctx *context.T) error {
+	return verror.New(ErrNoParams1, ctx)
+}
+
+// NewErrNoParams2 returns an error with the ErrNoParams2 ID.
+func NewErrNoParams2(ctx *context.T) error {
+	return verror.New(ErrNoParams2, ctx)
+}
+
+// NewErrWithParams1 returns an error with the ErrWithParams1 ID.
+func NewErrWithParams1(ctx *context.T, x string, y int32) error {
+	return verror.New(ErrWithParams1, ctx, x, y)
+}
+
+// NewErrWithParams2 returns an error with the ErrWithParams2 ID.
+func NewErrWithParams2(ctx *context.T, x string, y int32) error {
+	return verror.New(ErrWithParams2, ctx, x, y)
+}
+
+// newErrNotExported returns an error with the errNotExported ID.
+func newErrNotExported(ctx *context.T, x string, y int32) error {
+	return verror.New(errNotExported, ctx, x, y)
+}
+
+// ServiceAClientMethods is the client interface
+// containing ServiceA methods.
+type ServiceAClientMethods interface {
+	MethodA1(*context.T, ...rpc.CallOpt) error
+	MethodA2(ctx *context.T, a int32, b string, opts ...rpc.CallOpt) (s string, err error)
+	MethodA3(ctx *context.T, a int32, opts ...rpc.CallOpt) (ServiceAMethodA3ClientCall, error)
+	MethodA4(ctx *context.T, a int32, opts ...rpc.CallOpt) (ServiceAMethodA4ClientCall, error)
+}
+
+// ServiceAClientStub adds universal methods to ServiceAClientMethods.
+type ServiceAClientStub interface {
+	ServiceAClientMethods
+	rpc.UniversalServiceMethods
+}
+
+// ServiceAClient returns a client stub for ServiceA.
+func ServiceAClient(name string) ServiceAClientStub {
+	return implServiceAClientStub{name}
+}
+
+type implServiceAClientStub struct {
+	name string
+}
+
+func (c implServiceAClientStub) MethodA1(ctx *context.T, opts ...rpc.CallOpt) (err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "MethodA1", nil, nil, opts...)
+	return
+}
+
+func (c implServiceAClientStub) MethodA2(ctx *context.T, i0 int32, i1 string, opts ...rpc.CallOpt) (o0 string, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "MethodA2", []interface{}{i0, i1}, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implServiceAClientStub) MethodA3(ctx *context.T, i0 int32, opts ...rpc.CallOpt) (ocall ServiceAMethodA3ClientCall, err error) {
+	var call rpc.ClientCall
+	if call, err = v23.GetClient(ctx).StartCall(ctx, c.name, "MethodA3", []interface{}{i0}, opts...); err != nil {
+		return
+	}
+	ocall = &implServiceAMethodA3ClientCall{ClientCall: call}
+	return
+}
+
+func (c implServiceAClientStub) MethodA4(ctx *context.T, i0 int32, opts ...rpc.CallOpt) (ocall ServiceAMethodA4ClientCall, err error) {
+	var call rpc.ClientCall
+	if call, err = v23.GetClient(ctx).StartCall(ctx, c.name, "MethodA4", []interface{}{i0}, opts...); err != nil {
+		return
+	}
+	ocall = &implServiceAMethodA4ClientCall{ClientCall: call}
+	return
+}
+
+// ServiceAMethodA3ClientStream is the client stream for ServiceA.MethodA3.
+type ServiceAMethodA3ClientStream interface {
+	// RecvStream returns the receiver side of the ServiceA.MethodA3 client stream.
+	RecvStream() interface {
+		// Advance stages an item so that it may be retrieved via Value.  Returns
+		// true iff there is an item to retrieve.  Advance must be called before
+		// Value is called.  May block if an item is not available.
+		Advance() bool
+		// Value returns the item that was staged by Advance.  May panic if Advance
+		// returned false or was not called.  Never blocks.
+		Value() Scalars
+		// Err returns any error encountered by Advance.  Never blocks.
+		Err() error
+	}
+}
+
+// ServiceAMethodA3ClientCall represents the call returned from ServiceA.MethodA3.
+type ServiceAMethodA3ClientCall interface {
+	ServiceAMethodA3ClientStream
+	// Finish blocks until the server is done, and returns the positional return
+	// values for call.
+	//
+	// Finish returns immediately if the call has been canceled; depending on the
+	// timing the output could either be an error signaling cancelation, or the
+	// valid positional return values from the server.
+	//
+	// Calling Finish is mandatory for releasing stream resources, unless the call
+	// has been canceled or any of the other methods return an error.  Finish should
+	// be called at most once.
+	Finish() (s string, err error)
+}
+
+type implServiceAMethodA3ClientCall struct {
+	rpc.ClientCall
+	valRecv Scalars
+	errRecv error
+}
+
+func (c *implServiceAMethodA3ClientCall) RecvStream() interface {
+	Advance() bool
+	Value() Scalars
+	Err() error
+} {
+	return implServiceAMethodA3ClientCallRecv{c}
+}
+
+type implServiceAMethodA3ClientCallRecv struct {
+	c *implServiceAMethodA3ClientCall
+}
+
+func (c implServiceAMethodA3ClientCallRecv) Advance() bool {
+	c.c.valRecv = Scalars{}
+	c.c.errRecv = c.c.Recv(&c.c.valRecv)
+	return c.c.errRecv == nil
+}
+func (c implServiceAMethodA3ClientCallRecv) Value() Scalars {
+	return c.c.valRecv
+}
+func (c implServiceAMethodA3ClientCallRecv) Err() error {
+	if c.c.errRecv == io.EOF {
+		return nil
+	}
+	return c.c.errRecv
+}
+func (c *implServiceAMethodA3ClientCall) Finish() (o0 string, err error) {
+	err = c.ClientCall.Finish(&o0)
+	return
+}
+
+// ServiceAMethodA4ClientStream is the client stream for ServiceA.MethodA4.
+type ServiceAMethodA4ClientStream interface {
+	// RecvStream returns the receiver side of the ServiceA.MethodA4 client stream.
+	RecvStream() interface {
+		// Advance stages an item so that it may be retrieved via Value.  Returns
+		// true iff there is an item to retrieve.  Advance must be called before
+		// Value is called.  May block if an item is not available.
+		Advance() bool
+		// Value returns the item that was staged by Advance.  May panic if Advance
+		// returned false or was not called.  Never blocks.
+		Value() string
+		// Err returns any error encountered by Advance.  Never blocks.
+		Err() error
+	}
+	// SendStream returns the send side of the ServiceA.MethodA4 client stream.
+	SendStream() interface {
+		// Send places the item onto the output stream.  Returns errors
+		// encountered while sending, or if Send is called after Close or
+		// the stream has been canceled.  Blocks if there is no buffer
+		// space; will unblock when buffer space is available or after
+		// the stream has been canceled.
+		Send(item int32) error
+		// Close indicates to the server that no more items will be sent;
+		// server Recv calls will receive io.EOF after all sent items.
+		// This is an optional call - e.g. a client might call Close if it
+		// needs to continue receiving items from the server after it's
+		// done sending.  Returns errors encountered while closing, or if
+		// Close is called after the stream has been canceled.  Like Send,
+		// blocks if there is no buffer space available.
+		Close() error
+	}
+}
+
+// ServiceAMethodA4ClientCall represents the call returned from ServiceA.MethodA4.
+type ServiceAMethodA4ClientCall interface {
+	ServiceAMethodA4ClientStream
+	// Finish performs the equivalent of SendStream().Close, then blocks until
+	// the server is done, and returns the positional return values for the call.
+	//
+	// Finish returns immediately if the call has been canceled; depending on the
+	// timing the output could either be an error signaling cancelation, or the
+	// valid positional return values from the server.
+	//
+	// Calling Finish is mandatory for releasing stream resources, unless the call
+	// has been canceled or any of the other methods return an error.  Finish should
+	// be called at most once.
+	Finish() error
+}
+
+type implServiceAMethodA4ClientCall struct {
+	rpc.ClientCall
+	valRecv string
+	errRecv error
+}
+
+func (c *implServiceAMethodA4ClientCall) RecvStream() interface {
+	Advance() bool
+	Value() string
+	Err() error
+} {
+	return implServiceAMethodA4ClientCallRecv{c}
+}
+
+type implServiceAMethodA4ClientCallRecv struct {
+	c *implServiceAMethodA4ClientCall
+}
+
+func (c implServiceAMethodA4ClientCallRecv) Advance() bool {
+	c.c.errRecv = c.c.Recv(&c.c.valRecv)
+	return c.c.errRecv == nil
+}
+func (c implServiceAMethodA4ClientCallRecv) Value() string {
+	return c.c.valRecv
+}
+func (c implServiceAMethodA4ClientCallRecv) Err() error {
+	if c.c.errRecv == io.EOF {
+		return nil
+	}
+	return c.c.errRecv
+}
+func (c *implServiceAMethodA4ClientCall) SendStream() interface {
+	Send(item int32) error
+	Close() error
+} {
+	return implServiceAMethodA4ClientCallSend{c}
+}
+
+type implServiceAMethodA4ClientCallSend struct {
+	c *implServiceAMethodA4ClientCall
+}
+
+func (c implServiceAMethodA4ClientCallSend) Send(item int32) error {
+	return c.c.Send(item)
+}
+func (c implServiceAMethodA4ClientCallSend) Close() error {
+	return c.c.CloseSend()
+}
+func (c *implServiceAMethodA4ClientCall) Finish() (err error) {
+	err = c.ClientCall.Finish()
+	return
+}
+
+// ServiceAServerMethods is the interface a server writer
+// implements for ServiceA.
+type ServiceAServerMethods interface {
+	MethodA1(*context.T, rpc.ServerCall) error
+	MethodA2(ctx *context.T, call rpc.ServerCall, a int32, b string) (s string, err error)
+	MethodA3(ctx *context.T, call ServiceAMethodA3ServerCall, a int32) (s string, err error)
+	MethodA4(ctx *context.T, call ServiceAMethodA4ServerCall, a int32) error
+}
+
+// ServiceAServerStubMethods is the server interface containing
+// ServiceA methods, as expected by rpc.Server.
+// The only difference between this interface and ServiceAServerMethods
+// is the streaming methods.
+type ServiceAServerStubMethods interface {
+	MethodA1(*context.T, rpc.ServerCall) error
+	MethodA2(ctx *context.T, call rpc.ServerCall, a int32, b string) (s string, err error)
+	MethodA3(ctx *context.T, call *ServiceAMethodA3ServerCallStub, a int32) (s string, err error)
+	MethodA4(ctx *context.T, call *ServiceAMethodA4ServerCallStub, a int32) error
+}
+
+// ServiceAServerStub adds universal methods to ServiceAServerStubMethods.
+type ServiceAServerStub interface {
+	ServiceAServerStubMethods
+	// Describe the ServiceA interfaces.
+	Describe__() []rpc.InterfaceDesc
+}
+
+// ServiceAServer returns a server stub for ServiceA.
+// It converts an implementation of ServiceAServerMethods into
+// an object that may be used by rpc.Server.
+func ServiceAServer(impl ServiceAServerMethods) ServiceAServerStub {
+	stub := implServiceAServerStub{
+		impl: impl,
+	}
+	// Initialize GlobState; always check the stub itself first, to handle the
+	// case where the user has the Glob method defined in their VDL source.
+	if gs := rpc.NewGlobState(stub); gs != nil {
+		stub.gs = gs
+	} else if gs := rpc.NewGlobState(impl); gs != nil {
+		stub.gs = gs
+	}
+	return stub
+}
+
+type implServiceAServerStub struct {
+	impl ServiceAServerMethods
+	gs   *rpc.GlobState
+}
+
+func (s implServiceAServerStub) MethodA1(ctx *context.T, call rpc.ServerCall) error {
+	return s.impl.MethodA1(ctx, call)
+}
+
+func (s implServiceAServerStub) MethodA2(ctx *context.T, call rpc.ServerCall, i0 int32, i1 string) (string, error) {
+	return s.impl.MethodA2(ctx, call, i0, i1)
+}
+
+func (s implServiceAServerStub) MethodA3(ctx *context.T, call *ServiceAMethodA3ServerCallStub, i0 int32) (string, error) {
+	return s.impl.MethodA3(ctx, call, i0)
+}
+
+func (s implServiceAServerStub) MethodA4(ctx *context.T, call *ServiceAMethodA4ServerCallStub, i0 int32) error {
+	return s.impl.MethodA4(ctx, call, i0)
+}
+
+func (s implServiceAServerStub) Globber() *rpc.GlobState {
+	return s.gs
+}
+
+func (s implServiceAServerStub) Describe__() []rpc.InterfaceDesc {
+	return []rpc.InterfaceDesc{ServiceADesc}
+}
+
+// ServiceADesc describes the ServiceA interface.
+var ServiceADesc rpc.InterfaceDesc = descServiceA
+
+// descServiceA hides the desc to keep godoc clean.
+var descServiceA = rpc.InterfaceDesc{
+	Name:    "ServiceA",
+	PkgPath: "v.io/x/ref/lib/vdl/testdata/base",
+	Methods: []rpc.MethodDesc{
+		{
+			Name: "MethodA1",
+		},
+		{
+			Name: "MethodA2",
+			InArgs: []rpc.ArgDesc{
+				{"a", ``}, // int32
+				{"b", ``}, // string
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"s", ``}, // string
+			},
+		},
+		{
+			Name: "MethodA3",
+			InArgs: []rpc.ArgDesc{
+				{"a", ``}, // int32
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"s", ``}, // string
+			},
+			Tags: []*vdl.Value{vdl.ValueOf("tag"), vdl.ValueOf(uint64(6))},
+		},
+		{
+			Name: "MethodA4",
+			InArgs: []rpc.ArgDesc{
+				{"a", ``}, // int32
+			},
+		},
+	},
+}
+
+// ServiceAMethodA3ServerStream is the server stream for ServiceA.MethodA3.
+type ServiceAMethodA3ServerStream interface {
+	// SendStream returns the send side of the ServiceA.MethodA3 server stream.
+	SendStream() interface {
+		// Send places the item onto the output stream.  Returns errors encountered
+		// while sending.  Blocks if there is no buffer space; will unblock when
+		// buffer space is available.
+		Send(item Scalars) error
+	}
+}
+
+// ServiceAMethodA3ServerCall represents the context passed to ServiceA.MethodA3.
+type ServiceAMethodA3ServerCall interface {
+	rpc.ServerCall
+	ServiceAMethodA3ServerStream
+}
+
+// ServiceAMethodA3ServerCallStub is a wrapper that converts rpc.StreamServerCall into
+// a typesafe stub that implements ServiceAMethodA3ServerCall.
+type ServiceAMethodA3ServerCallStub struct {
+	rpc.StreamServerCall
+}
+
+// Init initializes ServiceAMethodA3ServerCallStub from rpc.StreamServerCall.
+func (s *ServiceAMethodA3ServerCallStub) Init(call rpc.StreamServerCall) {
+	s.StreamServerCall = call
+}
+
+// SendStream returns the send side of the ServiceA.MethodA3 server stream.
+func (s *ServiceAMethodA3ServerCallStub) SendStream() interface {
+	Send(item Scalars) error
+} {
+	return implServiceAMethodA3ServerCallSend{s}
+}
+
+type implServiceAMethodA3ServerCallSend struct {
+	s *ServiceAMethodA3ServerCallStub
+}
+
+func (s implServiceAMethodA3ServerCallSend) Send(item Scalars) error {
+	return s.s.Send(item)
+}
+
+// ServiceAMethodA4ServerStream is the server stream for ServiceA.MethodA4.
+type ServiceAMethodA4ServerStream interface {
+	// RecvStream returns the receiver side of the ServiceA.MethodA4 server stream.
+	RecvStream() interface {
+		// Advance stages an item so that it may be retrieved via Value.  Returns
+		// true iff there is an item to retrieve.  Advance must be called before
+		// Value is called.  May block if an item is not available.
+		Advance() bool
+		// Value returns the item that was staged by Advance.  May panic if Advance
+		// returned false or was not called.  Never blocks.
+		Value() int32
+		// Err returns any error encountered by Advance.  Never blocks.
+		Err() error
+	}
+	// SendStream returns the send side of the ServiceA.MethodA4 server stream.
+	SendStream() interface {
+		// Send places the item onto the output stream.  Returns errors encountered
+		// while sending.  Blocks if there is no buffer space; will unblock when
+		// buffer space is available.
+		Send(item string) error
+	}
+}
+
+// ServiceAMethodA4ServerCall represents the context passed to ServiceA.MethodA4.
+type ServiceAMethodA4ServerCall interface {
+	rpc.ServerCall
+	ServiceAMethodA4ServerStream
+}
+
+// ServiceAMethodA4ServerCallStub is a wrapper that converts rpc.StreamServerCall into
+// a typesafe stub that implements ServiceAMethodA4ServerCall.
+type ServiceAMethodA4ServerCallStub struct {
+	rpc.StreamServerCall
+	valRecv int32
+	errRecv error
+}
+
+// Init initializes ServiceAMethodA4ServerCallStub from rpc.StreamServerCall.
+func (s *ServiceAMethodA4ServerCallStub) Init(call rpc.StreamServerCall) {
+	s.StreamServerCall = call
+}
+
+// RecvStream returns the receiver side of the ServiceA.MethodA4 server stream.
+func (s *ServiceAMethodA4ServerCallStub) RecvStream() interface {
+	Advance() bool
+	Value() int32
+	Err() error
+} {
+	return implServiceAMethodA4ServerCallRecv{s}
+}
+
+type implServiceAMethodA4ServerCallRecv struct {
+	s *ServiceAMethodA4ServerCallStub
+}
+
+func (s implServiceAMethodA4ServerCallRecv) Advance() bool {
+	s.s.errRecv = s.s.Recv(&s.s.valRecv)
+	return s.s.errRecv == nil
+}
+func (s implServiceAMethodA4ServerCallRecv) Value() int32 {
+	return s.s.valRecv
+}
+func (s implServiceAMethodA4ServerCallRecv) Err() error {
+	if s.s.errRecv == io.EOF {
+		return nil
+	}
+	return s.s.errRecv
+}
+
+// SendStream returns the send side of the ServiceA.MethodA4 server stream.
+func (s *ServiceAMethodA4ServerCallStub) SendStream() interface {
+	Send(item string) error
+} {
+	return implServiceAMethodA4ServerCallSend{s}
+}
+
+type implServiceAMethodA4ServerCallSend struct {
+	s *ServiceAMethodA4ServerCallStub
+}
+
+func (s implServiceAMethodA4ServerCallSend) Send(item string) error {
+	return s.s.Send(item)
+}
+
+// ServiceBClientMethods is the client interface
+// containing ServiceB methods.
+type ServiceBClientMethods interface {
+	ServiceAClientMethods
+	MethodB1(ctx *context.T, a Scalars, b Composites, opts ...rpc.CallOpt) (c CompComp, err error)
+}
+
+// ServiceBClientStub adds universal methods to ServiceBClientMethods.
+type ServiceBClientStub interface {
+	ServiceBClientMethods
+	rpc.UniversalServiceMethods
+}
+
+// ServiceBClient returns a client stub for ServiceB.
+func ServiceBClient(name string) ServiceBClientStub {
+	return implServiceBClientStub{name, ServiceAClient(name)}
+}
+
+type implServiceBClientStub struct {
+	name string
+
+	ServiceAClientStub
+}
+
+func (c implServiceBClientStub) MethodB1(ctx *context.T, i0 Scalars, i1 Composites, opts ...rpc.CallOpt) (o0 CompComp, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "MethodB1", []interface{}{i0, i1}, []interface{}{&o0}, opts...)
+	return
+}
+
+// ServiceBServerMethods is the interface a server writer
+// implements for ServiceB.
+type ServiceBServerMethods interface {
+	ServiceAServerMethods
+	MethodB1(ctx *context.T, call rpc.ServerCall, a Scalars, b Composites) (c CompComp, err error)
+}
+
+// ServiceBServerStubMethods is the server interface containing
+// ServiceB methods, as expected by rpc.Server.
+// The only difference between this interface and ServiceBServerMethods
+// is the streaming methods.
+type ServiceBServerStubMethods interface {
+	ServiceAServerStubMethods
+	MethodB1(ctx *context.T, call rpc.ServerCall, a Scalars, b Composites) (c CompComp, err error)
+}
+
+// ServiceBServerStub adds universal methods to ServiceBServerStubMethods.
+type ServiceBServerStub interface {
+	ServiceBServerStubMethods
+	// Describe the ServiceB interfaces.
+	Describe__() []rpc.InterfaceDesc
+}
+
+// ServiceBServer returns a server stub for ServiceB.
+// It converts an implementation of ServiceBServerMethods into
+// an object that may be used by rpc.Server.
+func ServiceBServer(impl ServiceBServerMethods) ServiceBServerStub {
+	stub := implServiceBServerStub{
+		impl:               impl,
+		ServiceAServerStub: ServiceAServer(impl),
+	}
+	// Initialize GlobState; always check the stub itself first, to handle the
+	// case where the user has the Glob method defined in their VDL source.
+	if gs := rpc.NewGlobState(stub); gs != nil {
+		stub.gs = gs
+	} else if gs := rpc.NewGlobState(impl); gs != nil {
+		stub.gs = gs
+	}
+	return stub
+}
+
+type implServiceBServerStub struct {
+	impl ServiceBServerMethods
+	ServiceAServerStub
+	gs *rpc.GlobState
+}
+
+func (s implServiceBServerStub) MethodB1(ctx *context.T, call rpc.ServerCall, i0 Scalars, i1 Composites) (CompComp, error) {
+	return s.impl.MethodB1(ctx, call, i0, i1)
+}
+
+func (s implServiceBServerStub) Globber() *rpc.GlobState {
+	return s.gs
+}
+
+func (s implServiceBServerStub) Describe__() []rpc.InterfaceDesc {
+	return []rpc.InterfaceDesc{ServiceBDesc, ServiceADesc}
+}
+
+// ServiceBDesc describes the ServiceB interface.
+var ServiceBDesc rpc.InterfaceDesc = descServiceB
+
+// descServiceB hides the desc to keep godoc clean.
+var descServiceB = rpc.InterfaceDesc{
+	Name:    "ServiceB",
+	PkgPath: "v.io/x/ref/lib/vdl/testdata/base",
+	Embeds: []rpc.EmbedDesc{
+		{"ServiceA", "v.io/x/ref/lib/vdl/testdata/base", ``},
+	},
+	Methods: []rpc.MethodDesc{
+		{
+			Name: "MethodB1",
+			InArgs: []rpc.ArgDesc{
+				{"a", ``}, // Scalars
+				{"b", ``}, // Composites
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"c", ``}, // CompComp
+			},
+		},
+	},
+}
diff --git a/lib/vdl/testdata/nativedep/nativedep.vdl b/lib/vdl/testdata/nativedep/nativedep.vdl
new file mode 100644
index 0000000..3a4aa9c
--- /dev/null
+++ b/lib/vdl/testdata/nativedep/nativedep.vdl
@@ -0,0 +1,15 @@
+// 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 nativedep
+
+import "v.io/x/ref/lib/vdl/testdata/nativetest"
+
+type All struct {
+	A nativetest.WireString
+	B nativetest.WireMapStringInt
+	C nativetest.WireTime
+	D nativetest.WireSamePkg
+	E nativetest.WireMultiImport
+}
diff --git a/lib/vdl/testdata/nativedep/nativedep.vdl.go b/lib/vdl/testdata/nativedep/nativedep.vdl.go
new file mode 100644
index 0000000..b3b6a50
--- /dev/null
+++ b/lib/vdl/testdata/nativedep/nativedep.vdl.go
@@ -0,0 +1,35 @@
+// 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 by the vanadium vdl tool.
+// Source: nativedep.vdl
+
+package nativedep
+
+import (
+	// VDL system imports
+	"v.io/v23/vdl"
+
+	// VDL user imports
+	"time"
+	"v.io/v23/vdl/testdata/nativetest"
+	_ "v.io/x/ref/lib/vdl/testdata/nativetest"
+)
+
+type All struct {
+	A string
+	B map[string]int
+	C time.Time
+	D nativetest.NativeSamePkg
+	E map[nativetest.NativeSamePkg]time.Time
+}
+
+func (All) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/lib/vdl/testdata/nativedep.All"`
+}) {
+}
+
+func init() {
+	vdl.Register((*All)(nil))
+}
diff --git a/lib/vdl/testdata/nativedep2/nativedep2.vdl b/lib/vdl/testdata/nativedep2/nativedep2.vdl
new file mode 100644
index 0000000..95a7b92
--- /dev/null
+++ b/lib/vdl/testdata/nativedep2/nativedep2.vdl
@@ -0,0 +1,18 @@
+// 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 nativedep2
+
+// The purpose of this test is to ensure that the generated file gets the
+// imports right.  In particular, the generated file has no code dependencies on
+// nativetest, but should have two imports:
+//     "time"
+//   _ "v.io/x/ref/lib/vdl/testdata/nativetest"
+//
+// The underscore dependency is added to ensure that nativetest.WireTime is
+// registered whenever this package is used, so that the WireTime<->time.Time
+// mapping is known by the vdl package.
+import "v.io/x/ref/lib/vdl/testdata/nativetest"
+
+type MyTime nativetest.WireTime
diff --git a/lib/vdl/testdata/nativedep2/nativedep2.vdl.go b/lib/vdl/testdata/nativedep2/nativedep2.vdl.go
new file mode 100644
index 0000000..0fedb68
--- /dev/null
+++ b/lib/vdl/testdata/nativedep2/nativedep2.vdl.go
@@ -0,0 +1,28 @@
+// 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 by the vanadium vdl tool.
+// Source: nativedep2.vdl
+
+package nativedep2
+
+import (
+	// VDL system imports
+	"v.io/v23/vdl"
+
+	// VDL user imports
+	"time"
+	_ "v.io/x/ref/lib/vdl/testdata/nativetest"
+)
+
+type MyTime time.Time
+
+func (MyTime) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/lib/vdl/testdata/nativedep2.MyTime"`
+}) {
+}
+
+func init() {
+	vdl.Register((*MyTime)(nil))
+}
diff --git a/lib/vdl/testdata/nativetest/nativetest.go b/lib/vdl/testdata/nativetest/nativetest.go
new file mode 100644
index 0000000..594f46d
--- /dev/null
+++ b/lib/vdl/testdata/nativetest/nativetest.go
@@ -0,0 +1,34 @@
+// 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 nativetest
+
+import (
+	"strconv"
+	"time"
+)
+
+func wireStringToNative(x WireString, native *string) error {
+	*native = strconv.Itoa(int(x))
+	return nil
+}
+func wireStringFromNative(x *WireString, native string) error {
+	v, err := strconv.Atoi(native)
+	*x = WireString(v)
+	return err
+}
+
+func wireMapStringIntToNative(WireMapStringInt, *map[string]int) error   { return nil }
+func wireMapStringIntFromNative(*WireMapStringInt, map[string]int) error { return nil }
+
+func wireTimeToNative(WireTime, *time.Time) error   { return nil }
+func wireTimeFromNative(*WireTime, time.Time) error { return nil }
+
+func wireSamePkgToNative(WireSamePkg, native *NativeSamePkg) error { return nil }
+func wireSamePkgFromNative(*WireSamePkg, NativeSamePkg) error      { return nil }
+
+func wireMultiImportToNative(WireMultiImport, *map[NativeSamePkg]time.Time) error   { return nil }
+func wireMultiImportFromNative(*WireMultiImport, map[NativeSamePkg]time.Time) error { return nil }
+
+type NativeSamePkg string
diff --git a/lib/vdl/testdata/nativetest/nativetest.vdl b/lib/vdl/testdata/nativetest/nativetest.vdl
new file mode 100644
index 0000000..93bb309
--- /dev/null
+++ b/lib/vdl/testdata/nativetest/nativetest.vdl
@@ -0,0 +1,22 @@
+// 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 nativetest tests a package with native type conversions.
+package nativetest
+
+type WireString int32
+type WireMapStringInt int32
+type WireTime int32
+type WireSamePkg int32
+type WireMultiImport int32
+type WireRenameMe int32
+
+type WireAll struct {
+	A WireString
+	B WireMapStringInt
+	C WireTime
+	D WireSamePkg
+	E WireMultiImport
+	F WireRenameMe
+}
diff --git a/lib/vdl/testdata/nativetest/nativetest.vdl.go b/lib/vdl/testdata/nativetest/nativetest.vdl.go
new file mode 100644
index 0000000..983fbd0
--- /dev/null
+++ b/lib/vdl/testdata/nativetest/nativetest.vdl.go
@@ -0,0 +1,109 @@
+// 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 by the vanadium vdl tool.
+// Source: nativetest.vdl
+
+// Package nativetest tests a package with native type conversions.
+package nativetest
+
+import (
+	// VDL system imports
+	"v.io/v23/vdl"
+
+	// VDL user imports
+	"time"
+	"v.io/v23/vdl/testdata/nativetest"
+)
+
+type WireString int32
+
+func (WireString) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/lib/vdl/testdata/nativetest.WireString"`
+}) {
+}
+
+type WireMapStringInt int32
+
+func (WireMapStringInt) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/lib/vdl/testdata/nativetest.WireMapStringInt"`
+}) {
+}
+
+type WireTime int32
+
+func (WireTime) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/lib/vdl/testdata/nativetest.WireTime"`
+}) {
+}
+
+type WireSamePkg int32
+
+func (WireSamePkg) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/lib/vdl/testdata/nativetest.WireSamePkg"`
+}) {
+}
+
+type WireMultiImport int32
+
+func (WireMultiImport) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/lib/vdl/testdata/nativetest.WireMultiImport"`
+}) {
+}
+
+type WireRenameMe int32
+
+func (WireRenameMe) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/lib/vdl/testdata/nativetest.WireRenameMe"`
+}) {
+}
+
+type WireAll struct {
+	A string
+	B map[string]int
+	C time.Time
+	D nativetest.NativeSamePkg
+	E map[nativetest.NativeSamePkg]time.Time
+	F WireRenameMe
+}
+
+func (WireAll) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/lib/vdl/testdata/nativetest.WireAll"`
+}) {
+}
+
+func init() {
+	vdl.RegisterNative(wireMapStringIntToNative, wireMapStringIntFromNative)
+	vdl.RegisterNative(wireMultiImportToNative, wireMultiImportFromNative)
+	vdl.RegisterNative(wireSamePkgToNative, wireSamePkgFromNative)
+	vdl.RegisterNative(wireStringToNative, wireStringFromNative)
+	vdl.RegisterNative(wireTimeToNative, wireTimeFromNative)
+	vdl.Register((*WireString)(nil))
+	vdl.Register((*WireMapStringInt)(nil))
+	vdl.Register((*WireTime)(nil))
+	vdl.Register((*WireSamePkg)(nil))
+	vdl.Register((*WireMultiImport)(nil))
+	vdl.Register((*WireRenameMe)(nil))
+	vdl.Register((*WireAll)(nil))
+}
+
+// Type-check WireMapStringInt conversion functions.
+var _ func(WireMapStringInt, *map[string]int) error = wireMapStringIntToNative
+var _ func(*WireMapStringInt, map[string]int) error = wireMapStringIntFromNative
+
+// Type-check WireMultiImport conversion functions.
+var _ func(WireMultiImport, *map[nativetest.NativeSamePkg]time.Time) error = wireMultiImportToNative
+var _ func(*WireMultiImport, map[nativetest.NativeSamePkg]time.Time) error = wireMultiImportFromNative
+
+// Type-check WireSamePkg conversion functions.
+var _ func(WireSamePkg, *nativetest.NativeSamePkg) error = wireSamePkgToNative
+var _ func(*WireSamePkg, nativetest.NativeSamePkg) error = wireSamePkgFromNative
+
+// Type-check WireString conversion functions.
+var _ func(WireString, *string) error = wireStringToNative
+var _ func(*WireString, string) error = wireStringFromNative
+
+// Type-check WireTime conversion functions.
+var _ func(WireTime, *time.Time) error = wireTimeToNative
+var _ func(*WireTime, time.Time) error = wireTimeFromNative
diff --git a/lib/vdl/testdata/nativetest/otherfile.vdl b/lib/vdl/testdata/nativetest/otherfile.vdl
new file mode 100644
index 0000000..f85eff5
--- /dev/null
+++ b/lib/vdl/testdata/nativetest/otherfile.vdl
@@ -0,0 +1,10 @@
+// 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 nativetest
+
+// The only purpose of this file is to help ensure that the vdl tool works well
+// with multiple .vdl files in a package.
+
+type ignoreme string
diff --git a/lib/vdl/testdata/nativetest/otherfile.vdl.go b/lib/vdl/testdata/nativetest/otherfile.vdl.go
new file mode 100644
index 0000000..6e4a1bf
--- /dev/null
+++ b/lib/vdl/testdata/nativetest/otherfile.vdl.go
@@ -0,0 +1,28 @@
+// 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 by the vanadium vdl tool.
+// Source: otherfile.vdl
+
+package nativetest
+
+import (
+	// VDL system imports
+	"v.io/v23/vdl"
+
+	// VDL user imports
+	"time"
+	"v.io/v23/vdl/testdata/nativetest"
+)
+
+type ignoreme string
+
+func (ignoreme) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/lib/vdl/testdata/nativetest.ignoreme"`
+}) {
+}
+
+func init() {
+	vdl.Register((*ignoreme)(nil))
+}
diff --git a/lib/vdl/testdata/nativetest/vdl.config b/lib/vdl/testdata/nativetest/vdl.config
new file mode 100644
index 0000000..7695b14
--- /dev/null
+++ b/lib/vdl/testdata/nativetest/vdl.config
@@ -0,0 +1,37 @@
+config = vdltool.Config{
+	Go: {
+		WireToNativeTypes: {
+			"WireString":       {Type: "string"},
+			"WireMapStringInt": {Type: "map[string]int"},
+			"WireTime": {
+				Type:    "time.Time",
+				Imports: {{Path: "time", Name: "time"}},
+			},
+			"WireSamePkg": {
+				Type:    "nativetest.NativeSamePkg",
+				Imports: {{Path: "v.io/v23/vdl/testdata/nativetest", Name: "nativetest"}},
+			},
+			"WireMultiImport": {
+				Type: "map[nativetest.NativeSamePkg]time.Time",
+				Imports: {
+					{Path: "v.io/v23/vdl/testdata/nativetest", Name: "nativetest"},
+					{Path: "time", Name: "time"},
+				},
+			},
+		},
+	},
+	Java: {
+		WireTypeRenames: {
+			"WireRenameMe": "WireRenamed",
+		},
+
+		WireToNativeTypes: {
+			"WireString": "java.lang.String",
+			"WireMapStringInt": "java.util.Map<java.lang.String, java.lang.Integer>",
+			"WireTime": "org.joda.time.DateTime",
+			"WireSamePkg": "io.v.v23.vdl.testdata.nativetest.NativeSamePkg",
+			"WireMultiImport": "java.util.Map<io.v.v23.vdl.testdata.nativetest.NativeSamePkg, org.joda.time.DateTime>",
+			"WireRenamed": "java.lang.Long",
+		},
+	},
+}
diff --git a/lib/vdl/testdata/testconfig/testconfig.vdl b/lib/vdl/testdata/testconfig/testconfig.vdl
new file mode 100644
index 0000000..8d1e1a3
--- /dev/null
+++ b/lib/vdl/testdata/testconfig/testconfig.vdl
@@ -0,0 +1,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.
+
+// Package testconfig is a simple test of vdl.config files.  We don't care about
+// the actual VDL file contents; we really only want to make sure that the
+// vdl.config file in this package is read in successfully.
+package testconfig
diff --git a/lib/vdl/testdata/testconfig/testconfig.vdl.go b/lib/vdl/testdata/testconfig/testconfig.vdl.go
new file mode 100644
index 0000000..d77373c
--- /dev/null
+++ b/lib/vdl/testdata/testconfig/testconfig.vdl.go
@@ -0,0 +1,11 @@
+// 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 by the vanadium vdl tool.
+// Source: testconfig.vdl
+
+// Package testconfig is a simple test of vdl.config files.  We don't care about
+// the actual VDL file contents; we really only want to make sure that the
+// vdl.config file in this package is read in successfully.
+package testconfig
diff --git a/lib/vdl/testdata/testconfig/vdl.config b/lib/vdl/testdata/testconfig/vdl.config
new file mode 100644
index 0000000..fe2db9f
--- /dev/null
+++ b/lib/vdl/testdata/testconfig/vdl.config
@@ -0,0 +1,4 @@
+// Example of a vdl.config file for testing.
+config = vdltool.Config{
+	GenLanguages: {vdltool.GenLanguage.Go},
+}
diff --git a/lib/vdl/vdlutil/errors.go b/lib/vdl/vdlutil/errors.go
new file mode 100644
index 0000000..ceebf0a
--- /dev/null
+++ b/lib/vdl/vdlutil/errors.go
@@ -0,0 +1,86 @@
+// 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 vdlutil
+
+import (
+	"bytes"
+	"errors"
+	"fmt"
+	"strconv"
+)
+
+// Errors holds a buffer of encountered errors.  The point is to try displaying
+// all errors to the user rather than just the first.  We cutoff at MaxErrors to
+// ensure if something's really messed up we won't spew errors forever.  Set
+// MaxErrors=-1 to effectively continue despite any number of errors.  The zero
+// Errors struct stops at the first error encountered.
+type Errors struct {
+	MaxErrors int
+	buf       bytes.Buffer
+	num       int
+}
+
+// NewErrors returns a new Errors object, holding up to max errors.
+func NewErrors(max int) *Errors {
+	return &Errors{MaxErrors: max}
+}
+
+// Error adds the error described by msg to the buffer.  Returns true iff we're
+// still under the MaxErrors cutoff.
+func (e *Errors) Error(msg string) bool {
+	if e.num == e.MaxErrors {
+		return false
+	}
+	msg1 := "#" + strconv.Itoa(e.num) + " " + msg + "\n"
+	e.buf.WriteString(msg1)
+	if e.num++; e.num == e.MaxErrors {
+		msg2 := fmt.Sprintf("...stopping after %d error(s)...\n", e.num)
+		e.buf.WriteString(msg2)
+		return false
+	}
+	return true
+}
+
+// Errorf is like Error, and takes the same args as fmt.Printf.
+func (e *Errors) Errorf(format string, v ...interface{}) bool {
+	return e.Error(fmt.Sprintf(format, v...))
+}
+
+// String returns the buffered errors as a single human-readable string.
+func (e *Errors) String() string {
+	return e.buf.String()
+}
+
+// ToError returns the buffered errors as a single error, or nil if there
+// weren't any errors.
+func (e *Errors) ToError() error {
+	if e.num == 0 {
+		return nil
+	}
+	return errors.New(e.buf.String())
+}
+
+// IsEmpty returns true iff there weren't any errors.
+func (e *Errors) IsEmpty() bool {
+	return e.num == 0
+}
+
+// IsFull returns true iff we hit the MaxErrors cutoff.
+func (e *Errors) IsFull() bool {
+	return e.num == e.MaxErrors
+}
+
+// NumErrors returns the number of errors we've seen.
+func (e *Errors) NumErrors() int {
+	return e.num
+}
+
+// Reset clears the internal state so you start with no buffered errors.
+// MaxErrors remains the same; if you want to change it you should create a new
+// Errors struct.
+func (e *Errors) Reset() {
+	e.buf = bytes.Buffer{}
+	e.num = 0
+}
diff --git a/lib/vdl/vdlutil/util.go b/lib/vdl/vdlutil/util.go
new file mode 100644
index 0000000..1145857
--- /dev/null
+++ b/lib/vdl/vdlutil/util.go
@@ -0,0 +1,69 @@
+// 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 vdlutil implements utilities used by many VDL components.  It should
+// have a small set of dependencies.
+package vdlutil
+
+import (
+	"bytes"
+	"unicode"
+	"unicode/utf8"
+)
+
+// FirstRuneToLower returns s with its first rune in lowercase.
+func FirstRuneToLower(s string) string {
+	if s == "" {
+		return ""
+	}
+	r, n := utf8.DecodeRuneInString(s)
+	return string(unicode.ToLower(r)) + s[n:]
+}
+
+// FirstRuneToUpper returns s with its first rune in uppercase.
+func FirstRuneToUpper(s string) string {
+	if s == "" {
+		return ""
+	}
+	r, n := utf8.DecodeRuneInString(s)
+	return string(unicode.ToUpper(r)) + s[n:]
+}
+
+// FirstRuneToExportCase returns s with its first rune in uppercase if export is
+// true, otherwise in lowercase.
+func FirstRuneToExportCase(s string, export bool) string {
+	if export {
+		return FirstRuneToUpper(s)
+	}
+	return FirstRuneToLower(s)
+}
+
+// toConstCase converts ThisString to THIS_STRING. For adding '_', we follow the
+// following algorithm.  For any sequence of three characters, c[n-1], c[n],
+// c[n+1], we add an underscore before c[n] if:
+//     1) c[n-1] is a digit and c[n] is a letter, or
+//     2) c[n-1] is a letter and c[n] is a digit, or
+//     3) c[n-1] is lowercase, and c[n] is uppercase, or
+//     4) c[n-1] is uppercase, c[n] is uppercase, and c[n+1] is lowercase.
+func ToConstCase(s string) string {
+	var buf bytes.Buffer
+	var size int
+	var prev, cur, next rune
+	next, size = utf8.DecodeRuneInString(s)
+	for next != utf8.RuneError {
+		s = s[size:]
+		prev, cur = cur, next
+		next, size = utf8.DecodeRuneInString(s)
+		// We avoid checking boundary conditions because, for a rune r that is zero
+		// or utf8.RuneError: unicode.Is{Letter,Digit,Lower,Upper}(r) == false
+		if unicode.IsDigit(prev) && unicode.IsLetter(cur) || // Rule (1)
+			unicode.IsLetter(prev) && unicode.IsDigit(cur) || // Rule (2)
+			unicode.IsLower(prev) && unicode.IsUpper(cur) || // Rule (3)
+			unicode.IsUpper(prev) && unicode.IsUpper(cur) && unicode.IsLower(next) { // Rule (4)
+			buf.WriteRune('_')
+		}
+		buf.WriteRune(unicode.ToUpper(cur))
+	}
+	return buf.String()
+}
diff --git a/lib/vdl/vdlutil/util_test.go b/lib/vdl/vdlutil/util_test.go
new file mode 100644
index 0000000..0de4255
--- /dev/null
+++ b/lib/vdl/vdlutil/util_test.go
@@ -0,0 +1,68 @@
+// 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 vdlutil
+
+import (
+	"testing"
+)
+
+func TestFirstRuneToLower(t *testing.T) {
+	tests := []struct {
+		arg, want string
+	}{
+		{"foo", "foo"},
+		{"Foo", "foo"},
+		{"FOO", "fOO"},
+		{"foobar", "foobar"},
+		{"fooBar", "fooBar"},
+		{"FooBar", "fooBar"},
+		{"FOOBAR", "fOOBAR"},
+	}
+	for _, test := range tests {
+		if got, want := FirstRuneToLower(test.arg), test.want; got != want {
+			t.Errorf("FirstRuneToLower(%s) got %s, want %s", test.arg, got, want)
+		}
+	}
+}
+
+func TestFirstRuneToUpper(t *testing.T) {
+	tests := []struct {
+		arg, want string
+	}{
+		{"foo", "Foo"},
+		{"Foo", "Foo"},
+		{"FOO", "FOO"},
+		{"foobar", "Foobar"},
+		{"fooBar", "FooBar"},
+		{"FooBar", "FooBar"},
+		{"FOOBAR", "FOOBAR"},
+	}
+	for _, test := range tests {
+		if got, want := FirstRuneToUpper(test.arg), test.want; got != want {
+			t.Errorf("FirstRuneToUpper(%s) got %s, want %s", test.arg, got, want)
+		}
+	}
+}
+
+func TestConstCase(t *testing.T) {
+	testcases := []struct {
+		name, want string
+	}{
+		{"TestFunction", "TEST_FUNCTION"},
+		{"BIGNumber", "BIG_NUMBER"},
+		{"SHA256Hash", "SHA_256_HASH"},
+		{"Sha256Hash", "SHA_256_HASH"},
+		{"Sha256hash", "SHA_256_HASH"},
+		{"THISIsAHugeVarname", "THIS_IS_A_HUGE_VARNAME"},
+		{"Sha256MD5Function", "SHA_256_MD_5_FUNCTION"},
+		{"IfIHadADollar4EachTest", "IF_I_HAD_A_DOLLAR_4_EACH_TEST"},
+	}
+
+	for _, testcase := range testcases {
+		if want, got := testcase.want, ToConstCase(testcase.name); want != got {
+			t.Errorf("toConstCase(%q) error, want %q, got %q", testcase.name, want, got)
+		}
+	}
+}
diff --git a/lib/vdl/vdlutil/vlog.go b/lib/vdl/vdlutil/vlog.go
new file mode 100644
index 0000000..a760ca0
--- /dev/null
+++ b/lib/vdl/vdlutil/vlog.go
@@ -0,0 +1,27 @@
+// 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 vdlutil
+
+import (
+	"io/ioutil"
+	"log"
+	"os"
+)
+
+const (
+	logPrefix = ""
+	logFlags  = log.Lshortfile | log.Ltime | log.Lmicroseconds
+)
+
+var (
+	// Vlog is a logger that discards output by default, and only outputs real
+	// logs when SetVerbose is called.
+	Vlog = log.New(ioutil.Discard, logPrefix, logFlags)
+)
+
+// SetVerbose tells the vdl package (and subpackages) to enable verbose logging.
+func SetVerbose() {
+	Vlog = log.New(os.Stderr, logPrefix, logFlags)
+}
diff --git a/lib/xrpc/xserver.go b/lib/xrpc/xserver.go
new file mode 100644
index 0000000..971d1aa
--- /dev/null
+++ b/lib/xrpc/xserver.go
@@ -0,0 +1,123 @@
+// 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 xserver provides an alternate RPC server API with the goal of
+// being simpler to use and understand.
+package xrpc
+
+import (
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+)
+
+type server struct {
+	s rpc.Server
+}
+
+// NewServer creates a new Server instance to serve a service object.
+//
+// The server will listen for network connections as specified by the
+// ListenSpec attached to ctx. Depending on your RuntimeFactory, 'roaming'
+// support may be enabled. In this mode the server will listen for
+// changes in the network configuration using a Stream created on the
+// supplied Publisher and change the set of Endpoints it publishes to
+// the mount table accordingly.
+//
+// The server associates object with name by publishing the address of
+// this server in the namespace under the supplied name and using
+// authorizer to authorize access to it. RPCs invoked on the supplied
+// name will be delivered to methods implemented by the supplied
+// object.  Reflection is used to match requests to the object's
+// method set.  As a special-case, if the object implements the
+// Invoker interface, the Invoker is used to invoke methods directly,
+// without reflection.  If name is an empty string, no attempt will
+// made to publish.
+func NewServer(ctx *context.T, name string, object interface{}, auth security.Authorizer, opts ...rpc.ServerOpt) (rpc.XServer, error) {
+	s, err := v23.NewServer(ctx, opts...)
+	if err != nil {
+		return nil, err
+	}
+	if _, err = s.Listen(v23.GetListenSpec(ctx)); err != nil {
+		s.Stop()
+		return nil, err
+	}
+	if err = s.Serve(name, object, auth); err != nil {
+		s.Stop()
+		return nil, err
+	}
+	return &server{s: s}, nil
+}
+
+// NewDispatchingServer creates a new Server instance to serve a given dispatcher.
+//
+// The server will listen for network connections as specified by the
+// ListenSpec attached to ctx. Depending on your RuntimeFactory, 'roaming'
+// support may be enabled. In this mode the server will listen for
+// changes in the network configuration using a Stream created on the
+// supplied Publisher and change the set of Endpoints it publishes to
+// the mount table accordingly.
+//
+// The server associates dispatcher with the portion of the namespace
+// for which name is a prefix by publishing the address of this server
+// to the namespace under the supplied name. If name is an empty
+// string, no attempt will made to publish. RPCs invoked on the
+// supplied name will be delivered to the supplied Dispatcher's Lookup
+// method which will in turn return the object and security.Authorizer
+// used to serve the actual RPC call.  If name is an empty string, no
+// attempt will made to publish that name to a mount table.
+func NewDispatchingServer(ctx *context.T, name string, disp rpc.Dispatcher, opts ...rpc.ServerOpt) (rpc.XServer, error) {
+	s, err := v23.NewServer(ctx, opts...)
+	if err != nil {
+		return nil, err
+	}
+	if _, err = s.Listen(v23.GetListenSpec(ctx)); err != nil {
+		return nil, err
+	}
+	if err = s.ServeDispatcher(name, disp); err != nil {
+		return nil, err
+	}
+	return &server{s: s}, nil
+}
+
+// AddName adds the specified name to the mount table for this server.
+// AddName may be called multiple times.
+func (s *server) AddName(name string) error {
+	return s.s.AddName(name)
+}
+
+// RemoveName removes the specified name from the mount table.
+// RemoveName may be called multiple times.
+func (s *server) RemoveName(name string) {
+	s.s.RemoveName(name)
+}
+
+// Status returns the current status of the server, see ServerStatus
+// for details.
+func (s *server) Status() rpc.ServerStatus {
+	return s.s.Status()
+}
+
+// WatchNetwork registers a channel over which NetworkChange's will
+// be sent. The Server will not block sending data over this channel
+// and hence change events may be lost if the caller doesn't ensure
+// there is sufficient buffering in the channel.
+func (s *server) WatchNetwork(ch chan<- rpc.NetworkChange) {
+	s.s.WatchNetwork(ch)
+}
+
+// UnwatchNetwork unregisters a channel previously registered using
+// WatchNetwork.
+func (s *server) UnwatchNetwork(ch chan<- rpc.NetworkChange) {
+	s.s.UnwatchNetwork(ch)
+}
+
+// Stop gracefully stops all services on this Server.  New calls are
+// rejected, but any in-flight calls are allowed to complete.  All
+// published mountpoints are unmounted.  This call waits for this
+// process to complete, and returns once the server has been shut down.
+func (s *server) Stop() error {
+	return s.s.Stop()
+}
diff --git a/lib/xrpc/xserver_test.go b/lib/xrpc/xserver_test.go
new file mode 100644
index 0000000..95b5e4b
--- /dev/null
+++ b/lib/xrpc/xserver_test.go
@@ -0,0 +1,71 @@
+// 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 xrpc_test
+
+import (
+	"testing"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/x/ref/lib/xrpc"
+	_ "v.io/x/ref/runtime/factories/generic"
+	"v.io/x/ref/test"
+)
+
+func init() {
+	test.Init()
+}
+
+type service struct{}
+
+func (s *service) Yo(ctx *context.T, call rpc.ServerCall) (string, error) {
+	return "yo", nil
+}
+
+func TestXServer(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	server, err := xrpc.NewServer(ctx, "", &service{}, nil)
+	if err != nil {
+		t.Fatalf("Error creating server: %v", err)
+	}
+	ep := server.Status().Endpoints[0]
+
+	var out string
+	if err := v23.GetClient(ctx).Call(ctx, ep.Name(), "Yo", nil, []interface{}{&out}); err != nil {
+		t.Fatalf("Call failed: %v", err)
+	}
+	if out != "yo" {
+		t.Fatalf("Wanted yo, got %s", out)
+	}
+}
+
+type dispatcher struct{}
+
+func (d *dispatcher) Lookup(_ *context.T, suffix string) (interface{}, security.Authorizer, error) {
+	return &service{}, nil, nil
+}
+
+func TestXDispatchingServer(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	server, err := xrpc.NewDispatchingServer(ctx, "", &dispatcher{})
+	if err != nil {
+		t.Fatalf("Error creating server: %v", err)
+	}
+	ep := server.Status().Endpoints[0]
+
+	var out string
+	if err := v23.GetClient(ctx).Call(ctx, ep.Name(), "Yo", nil, []interface{}{&out}); err != nil {
+		t.Fatalf("Call failed: %v", err)
+	}
+	if out != "yo" {
+		t.Fatalf("Wanted yo, got %s", out)
+	}
+}
diff --git a/runtime/doc.go b/runtime/doc.go
new file mode 100644
index 0000000..c296404
--- /dev/null
+++ b/runtime/doc.go
@@ -0,0 +1,25 @@
+// 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 runtime and its subdirectories provide implementations of the
+// Vanadium runtime for different runtime environments.
+//
+// Each subdirectory of the runtime/factories package is a package that
+// implements the v23.RuntimeFactory function.
+//
+// runtime/internal has common functionality use in runtime/factories.
+//
+// RuntimeFactories register themselves by calling v.io/v23/rt.RegisterRuntimeFactory
+// in an init function.  Users choose a particular RuntimeFactory implementation
+// by importing the appropriate package in their main package.  It is an error
+// to import more than one RuntimeFactory, and the registration mechanism will
+// panic if this is attempted.
+//
+// The 'roaming' RuntimeFactory adds operating system support for varied network
+// configurations and in particular dhcp. It should be used by any application
+// that may 'roam' or be behind a 1-1 NAT.
+//
+// The 'static' RuntimeFactory does not provide dhcp support, but is otherwise
+// like the 'roaming' RuntimeFactory.
+package runtime
diff --git a/runtime/factories/chrome/chrome.go b/runtime/factories/chrome/chrome.go
new file mode 100644
index 0000000..07cb080
--- /dev/null
+++ b/runtime/factories/chrome/chrome.go
@@ -0,0 +1,49 @@
+// 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 chrome implements a RuntimeFactory for use within Chrome, in
+// particular for use by Chrome extensions.
+package chrome
+
+import (
+	"flag"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+
+	"v.io/x/ref/lib/flags"
+	"v.io/x/ref/runtime/internal"
+	_ "v.io/x/ref/runtime/internal/flow/protocols/ws"
+	_ "v.io/x/ref/runtime/internal/flow/protocols/wsh_nacl"
+	"v.io/x/ref/runtime/internal/lib/websocket"
+	grt "v.io/x/ref/runtime/internal/rt"
+
+	// 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)
+	commonFlags = flags.CreateAndRegister(flag.CommandLine, flags.Runtime)
+}
+
+func Init(ctx *context.T) (v23.Runtime, *context.T, v23.Shutdown, error) {
+	if err := internal.ParseFlagsAndConfigureGlobalLogger(commonFlags); err != nil {
+		return nil, nil, nil, err
+	}
+
+	protocols := []string{"wsh", "ws"}
+	listenSpec := rpc.ListenSpec{Addrs: rpc.ListenAddrs{{Protocol: "ws", Address: ""}}}
+	runtime, ctx, shutdown, err := grt.Init(ctx, nil, protocols, &listenSpec, nil, "", commonFlags.RuntimeFlags(), nil)
+	if err != nil {
+		return nil, nil, shutdown, err
+	}
+	ctx.VI(1).Infof("Initializing chrome RuntimeFactory.")
+	return runtime, ctx, shutdown, nil
+}
diff --git a/runtime/factories/fake/fake.go b/runtime/factories/fake/fake.go
new file mode 100644
index 0000000..f56e40a
--- /dev/null
+++ b/runtime/factories/fake/fake.go
@@ -0,0 +1,74 @@
+// 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 fake implements a fake RuntimeFactory, useful in tests for mocking
+// out certain components.
+package fake
+
+// TODO(mattr): Make a more complete, but still fake, implementation.
+
+import (
+	"sync"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+
+	"v.io/x/ref/runtime/internal"
+	_ "v.io/x/ref/runtime/internal/flow/protocols/tcp"
+	_ "v.io/x/ref/runtime/internal/flow/protocols/ws"
+	_ "v.io/x/ref/runtime/internal/flow/protocols/wsh"
+	"v.io/x/ref/runtime/internal/lib/websocket"
+
+	// 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 (
+	runtimeInfo struct {
+		mu       sync.Mutex
+		runtime  v23.Runtime  // GUARDED_BY mu
+		ctx      *context.T   // GUARDED_BY mu
+		shutdown v23.Shutdown // GUARDED_BY mu
+	}
+)
+
+func init() {
+	v23.RegisterRuntimeFactory(Init)
+	rpc.RegisterUnknownProtocol("wsh", websocket.HybridDial, websocket.HybridResolve, websocket.HybridListener)
+}
+
+func Init(ctx *context.T) (v23.Runtime, *context.T, v23.Shutdown, error) {
+	if err := internal.ConfigureGlobalLoggerFromFlags(); err != nil {
+		return nil, nil, nil, err
+	}
+
+	runtimeInfo.mu.Lock()
+	defer runtimeInfo.mu.Unlock()
+	if runtimeInfo.runtime != nil {
+		shutdown := func() {
+			runtimeInfo.mu.Lock()
+			runtimeInfo.shutdown()
+			runtimeInfo.runtime = nil
+			runtimeInfo.ctx = nil
+			runtimeInfo.shutdown = nil
+			runtimeInfo.mu.Unlock()
+		}
+		return runtimeInfo.runtime, runtimeInfo.ctx, shutdown, nil
+	}
+	return new(ctx)
+}
+
+// InjectRuntime allows packages to inject whichever runtime, ctx, and shutdown.
+// This allows a package that needs different runtimes in tests to swap them as needed.
+// The injected runtime will be valid until the shutdown returned from v23.Init is called.
+func InjectRuntime(runtime v23.Runtime, ctx *context.T, shutdown v23.Shutdown) {
+	runtimeInfo.mu.Lock()
+	runtimeInfo.runtime = runtime
+	runtimeInfo.ctx = ctx
+	runtimeInfo.shutdown = shutdown
+	runtimeInfo.mu.Unlock()
+}
diff --git a/runtime/factories/fake/fake_test.go b/runtime/factories/fake/fake_test.go
new file mode 100644
index 0000000..7aef3a8
--- /dev/null
+++ b/runtime/factories/fake/fake_test.go
@@ -0,0 +1,23 @@
+// 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 fake_test
+
+import (
+	"testing"
+
+	"v.io/v23"
+
+	_ "v.io/x/ref/runtime/factories/fake"
+)
+
+// Ensure that the fake RuntimeFactory can be used to initialize a fake runtime.
+func TestInit(t *testing.T) {
+	ctx, shutdown := v23.Init()
+	defer shutdown()
+
+	if !ctx.Initialized() {
+		t.Errorf("Got uninitialized context from Init.")
+	}
+}
diff --git a/runtime/factories/fake/naming.go b/runtime/factories/fake/naming.go
new file mode 100644
index 0000000..e7bb344
--- /dev/null
+++ b/runtime/factories/fake/naming.go
@@ -0,0 +1,26 @@
+// 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 fake
+
+import (
+	"v.io/v23/context"
+	"v.io/v23/namespace"
+	"v.io/v23/naming"
+	"v.io/x/ref/lib/apilog"
+	inaming "v.io/x/ref/runtime/internal/naming"
+)
+
+func (r *Runtime) NewEndpoint(ep string) (naming.Endpoint, error) {
+	defer apilog.LogCallf(nil, "ep=%.10s...", ep)(nil, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	return inaming.NewEndpoint(ep)
+}
+func (r *Runtime) WithNewNamespace(ctx *context.T, roots ...string) (*context.T, namespace.T, error) {
+	defer apilog.LogCallf(ctx, "roots...=%v", roots)(ctx, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	panic("unimplemented")
+}
+func (r *Runtime) GetNamespace(ctx *context.T) namespace.T {
+	// nologcall
+	return r.ns
+}
diff --git a/runtime/factories/fake/rpc.go b/runtime/factories/fake/rpc.go
new file mode 100644
index 0000000..3ce9bcc
--- /dev/null
+++ b/runtime/factories/fake/rpc.go
@@ -0,0 +1,66 @@
+// 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 fake
+
+import (
+	"v.io/v23/context"
+	"v.io/v23/flow"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/x/ref/lib/apilog"
+)
+
+// SetClient can be used to inject a mock client implementation into the context.
+func SetClient(ctx *context.T, client rpc.Client) *context.T {
+	return context.WithValue(ctx, clientKey, client)
+}
+func (r *Runtime) WithNewClient(ctx *context.T, opts ...rpc.ClientOpt) (*context.T, rpc.Client, error) {
+	defer apilog.LogCallf(ctx, "opts...=%v", opts)(ctx, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	panic("unimplemented")
+}
+func (r *Runtime) GetClient(ctx *context.T) rpc.Client {
+	defer apilog.LogCall(ctx)(ctx) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	c, _ := ctx.Value(clientKey).(rpc.Client)
+	return c
+}
+
+func (r *Runtime) NewServer(ctx *context.T, opts ...rpc.ServerOpt) (rpc.Server, error) {
+	defer apilog.LogCallf(ctx, "opts...=%v", opts)(ctx, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	panic("unimplemented")
+}
+func (r *Runtime) WithNewStreamManager(ctx *context.T) (*context.T, error) {
+	defer apilog.LogCall(ctx)(ctx) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	panic("unimplemented")
+}
+
+func (r *Runtime) GetListenSpec(ctx *context.T) rpc.ListenSpec {
+	defer apilog.LogCall(ctx)(ctx) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	return rpc.ListenSpec{}
+}
+
+func (r *Runtime) WithListenSpec(ctx *context.T, ls rpc.ListenSpec) *context.T {
+	defer apilog.LogCall(ctx)(ctx) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	return ctx
+}
+
+func (r *Runtime) ExperimentalGetFlowManager(ctx *context.T) flow.Manager {
+	defer apilog.LogCall(ctx)(ctx) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	panic("unimplemented")
+}
+
+func (r *Runtime) ExperimentalWithNewFlowManager(ctx *context.T) (*context.T, flow.Manager, error) {
+	defer apilog.LogCall(ctx)(ctx) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	panic("unimplemented")
+}
+
+func (r *Runtime) XWithNewServer(ctx *context.T, name string, object interface{}, auth security.Authorizer, opts ...rpc.ServerOpt) (*context.T, rpc.XServer, error) {
+	defer apilog.LogCall(ctx)(ctx) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	panic("unimplemented")
+}
+
+func (r *Runtime) XWithNewDispatchingServer(ctx *context.T, name string, disp rpc.Dispatcher, opts ...rpc.ServerOpt) (*context.T, rpc.XServer, error) {
+	defer apilog.LogCall(ctx)(ctx) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	panic("unimplemented")
+}
diff --git a/runtime/factories/fake/runtime.go b/runtime/factories/fake/runtime.go
new file mode 100644
index 0000000..8cb7fec
--- /dev/null
+++ b/runtime/factories/fake/runtime.go
@@ -0,0 +1,88 @@
+// 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 fake
+
+import (
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/namespace"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/x/ref/internal/logger"
+	"v.io/x/ref/lib/apilog"
+	tnaming "v.io/x/ref/runtime/internal/testing/mocks/naming"
+	"v.io/x/ref/test/testutil"
+)
+
+type contextKey int
+
+const (
+	clientKey = contextKey(iota)
+	principalKey
+	loggerKey
+	backgroundKey
+)
+
+type Runtime struct {
+	ns namespace.T
+}
+
+func new(ctx *context.T) (*Runtime, *context.T, v23.Shutdown, error) {
+	ctx = context.WithValue(ctx, principalKey, testutil.NewPrincipal("fake"))
+	ctx = context.WithLogger(ctx, logger.Global())
+	return &Runtime{ns: tnaming.NewSimpleNamespace()}, ctx, func() {}, nil
+}
+
+func (r *Runtime) Init(ctx *context.T) error {
+	return nil
+}
+
+func (r *Runtime) WithPrincipal(ctx *context.T, principal security.Principal) (*context.T, error) {
+	defer apilog.LogCallf(ctx, "principal=%v", principal)(ctx, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	return context.WithValue(ctx, principalKey, principal), nil
+}
+
+func (r *Runtime) GetPrincipal(ctx *context.T) security.Principal {
+	// nologcall
+	p, _ := ctx.Value(principalKey).(security.Principal)
+	return p
+}
+
+func (r *Runtime) GetAppCycle(ctx *context.T) v23.AppCycle {
+	// nologcall
+	panic("unimplemented")
+}
+
+func (r *Runtime) WithBackgroundContext(ctx *context.T) *context.T {
+	defer apilog.LogCall(ctx)(ctx) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	// Note we add an extra context with a nil value here.
+	// This prevents users from travelling back through the
+	// chain of background contexts.
+	ctx = context.WithValue(ctx, backgroundKey, nil)
+	return context.WithValue(ctx, backgroundKey, ctx)
+}
+
+func (r *Runtime) GetBackgroundContext(ctx *context.T) *context.T {
+	// nologcall
+	bctx, _ := ctx.Value(backgroundKey).(*context.T)
+	if bctx == nil {
+		// There should always be a background context.  If we don't find
+		// it, that means that the user passed us the background context
+		// in hopes of following the chain.  Instead we just give them
+		// back what they sent in, which is correct.
+		return ctx
+	}
+	return bctx
+}
+
+func (*Runtime) WithReservedNameDispatcher(ctx *context.T, d rpc.Dispatcher) *context.T {
+	defer apilog.LogCall(ctx)(ctx) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	panic("unimplemented")
+}
+
+func (*Runtime) GetReservedNameDispatcher(ctx *context.T) rpc.Dispatcher {
+	// nologcall
+	panic("unimplmeneted")
+}
diff --git a/runtime/factories/gce/gce.go b/runtime/factories/gce/gce.go
new file mode 100644
index 0000000..226449b
--- /dev/null
+++ b/runtime/factories/gce/gce.go
@@ -0,0 +1,83 @@
+// 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 linux
+
+// Package gce implements a RuntimeFactory for binaries that only run on Google
+// Compute Engine (GCE).
+package gce
+
+import (
+	"flag"
+	"fmt"
+	"net"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+
+	"v.io/x/lib/netstate"
+	"v.io/x/ref/lib/flags"
+	"v.io/x/ref/runtime/internal"
+	_ "v.io/x/ref/runtime/internal/flow/protocols/tcp"
+	_ "v.io/x/ref/runtime/internal/flow/protocols/ws"
+	_ "v.io/x/ref/runtime/internal/flow/protocols/wsh"
+	"v.io/x/ref/runtime/internal/gce"
+	"v.io/x/ref/runtime/internal/lib/appcycle"
+	"v.io/x/ref/runtime/internal/lib/websocket"
+	grt "v.io/x/ref/runtime/internal/rt"
+
+	// 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)
+	commonFlags = flags.CreateAndRegister(flag.CommandLine, flags.Runtime, flags.Listen)
+}
+
+func Init(ctx *context.T) (v23.Runtime, *context.T, v23.Shutdown, error) {
+	if err := internal.ParseFlagsAndConfigureGlobalLogger(commonFlags); err != nil {
+		return nil, nil, nil, err
+	}
+
+	if !gce.RunningOnGCE() {
+		return nil, nil, nil, fmt.Errorf("GCE profile used on a non-GCE system")
+	}
+
+	ac := appcycle.New()
+
+	lf := commonFlags.ListenFlags()
+	listenSpec := rpc.ListenSpec{
+		Addrs: rpc.ListenAddrs(lf.Addrs),
+		Proxy: lf.ListenProxy,
+	}
+
+	if ip, err := gce.ExternalIPAddress(); err != nil {
+		return nil, nil, nil, err
+	} else {
+		listenSpec.AddressChooser = netstate.AddressChooserFunc(func(network string, addrs []net.Addr) ([]net.Addr, error) {
+			return []net.Addr{netstate.NewNetAddr("wsh", ip.String())}, nil
+		})
+	}
+
+	runtime, ctx, shutdown, err := grt.Init(ctx, ac, nil, &listenSpec, nil, "", commonFlags.RuntimeFlags(), nil)
+	if err != nil {
+		return nil, nil, shutdown, err
+	}
+
+	ctx.VI(1).Infof("Initializing GCE RuntimeFactory.")
+
+	runtimeFactoryShutdown := func() {
+		ac.Shutdown()
+		shutdown()
+	}
+
+	return runtime, ctx, runtimeFactoryShutdown, nil
+}
diff --git a/runtime/factories/generic/generic.go b/runtime/factories/generic/generic.go
new file mode 100644
index 0000000..d3a0ac9
--- /dev/null
+++ b/runtime/factories/generic/generic.go
@@ -0,0 +1,72 @@
+// 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 generic implements a RuntimeFactory that is useful in tests. It
+// prefers listening on localhost addresses.
+package generic
+
+import (
+	"flag"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+
+	"v.io/x/ref/lib/flags"
+	"v.io/x/ref/runtime/internal"
+	_ "v.io/x/ref/runtime/internal/flow/protocols/tcp"
+	_ "v.io/x/ref/runtime/internal/flow/protocols/ws"
+	_ "v.io/x/ref/runtime/internal/flow/protocols/wsh"
+	"v.io/x/ref/runtime/internal/lib/appcycle"
+	"v.io/x/ref/runtime/internal/lib/websocket"
+	grt "v.io/x/ref/runtime/internal/rt"
+
+	// 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)
+	flags.SetDefaultHostPort(":0")
+	commonFlags = flags.CreateAndRegister(flag.CommandLine, flags.Runtime, flags.Listen)
+}
+
+func Init(ctx *context.T) (v23.Runtime, *context.T, v23.Shutdown, error) {
+	if err := internal.ParseFlagsAndConfigureGlobalLogger(commonFlags); err != nil {
+		return nil, nil, nil, err
+	}
+
+	ac := appcycle.New()
+
+	lf := commonFlags.ListenFlags()
+	listenSpec := rpc.ListenSpec{
+		Addrs:          rpc.ListenAddrs(lf.Addrs),
+		AddressChooser: internal.IPAddressChooser{},
+		Proxy:          lf.ListenProxy,
+	}
+
+	runtime, ctx, shutdown, err := grt.Init(ctx,
+		ac,
+		nil,
+		&listenSpec,
+		nil,
+		"",
+		commonFlags.RuntimeFlags(),
+		nil)
+	if err != nil {
+		return nil, nil, nil, err
+	}
+	ctx.VI(1).Infof("Initializing generic RuntimeFactory.")
+
+	runtimeFactoryShutdown := func() {
+		ac.Shutdown()
+		shutdown()
+	}
+	return runtime, ctx, runtimeFactoryShutdown, nil
+}
diff --git a/runtime/factories/generic/proxy.go b/runtime/factories/generic/proxy.go
new file mode 100644
index 0000000..862c1de
--- /dev/null
+++ b/runtime/factories/generic/proxy.go
@@ -0,0 +1,23 @@
+// 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 generic
+
+import (
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+
+	"v.io/x/ref/runtime/internal/rpc/stream/proxy"
+)
+
+// NewProxy creates a new Proxy that listens for network connections on the provided
+// (network, address) pair and routes VC traffic between accepted connections.
+//
+// auth encapsulates the authorization policy of the proxy - which
+// servers it is willing to proxy for.
+func NewProxy(ctx *context.T, spec rpc.ListenSpec, auth security.Authorizer, names ...string) (shutdown func(), endpoint naming.Endpoint, err error) {
+	return proxy.New(ctx, spec, auth, names...)
+}
diff --git a/runtime/factories/roaming/.api b/runtime/factories/roaming/.api
new file mode 100644
index 0000000..c15af37
--- /dev/null
+++ b/runtime/factories/roaming/.api
@@ -0,0 +1,4 @@
+pkg roaming, const SettingsStreamDesc ideal-string
+pkg roaming, const SettingsStreamName ideal-string
+pkg roaming, func Init(*context.T) (v23.Runtime, *context.T, v23.Shutdown, error)
+pkg roaming, func NewProxy(*context.T, rpc.ListenSpec, ...string) (func(), naming.Endpoint, error)
diff --git a/runtime/factories/roaming/net_watcher.go b/runtime/factories/roaming/net_watcher.go
new file mode 100644
index 0000000..9646b7d
--- /dev/null
+++ b/runtime/factories/roaming/net_watcher.go
@@ -0,0 +1,77 @@
+// 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 ignore
+
+package main
+
+import (
+	"fmt"
+	"os"
+	"strings"
+
+	"v.io/x/lib/netstate"
+
+	"v.io/v23"
+	"v.io/v23/config"
+
+	"v.io/x/ref/runtime/factories/roaming"
+)
+
+func main() {
+	ctx, shutdown := v23.Init()
+	defer shutdown()
+
+	profileName := "roaming"
+	fmt.Println("Profile: ", profileName)
+
+	accessible, err := netstate.GetAccessibleIPs()
+	interfaces, err := netstate.GetAllInterfaces()
+
+	fmt.Printf("Addresses\n")
+	for _, addr := range accessible {
+		fmt.Printf("%s\n", addr.DebugString())
+	}
+
+	fmt.Printf("\nInterfaces\n")
+	for _, ifc := range interfaces {
+		fmt.Printf("%s\n", ifc)
+	}
+
+	fmt.Printf("\nRoutes\n")
+	for _, ifc := range interfaces {
+		if ipifc, ok := ifc.(netstate.IPNetworkInterface); ok {
+			if routes := ipifc.IPRoutes(); len(routes) > 0 {
+				fmt.Printf("%s: %s\n", ifc.Name(), routes)
+			}
+		}
+	}
+
+	listenSpec := v23.GetListenSpec(ctx)
+	chooser := listenSpec.AddressChooser
+	if chooser != nil {
+		if gce, err := chooser("", nil); err == nil {
+			fmt.Printf("%s: 1:1 NAT address is %s\n", profileName, gce)
+		}
+	}
+
+	if chosen, err := listenSpec.AddressChooser("tcp", accessible.AsNetAddrs()); err != nil {
+		fmt.Printf("Failed to chosen address %s\n", err)
+	} else {
+		al := netstate.ConvertToAddresses(chosen)
+		fmt.Printf("Chosen:\n%s\n", strings.Replace(al.String(), ") ", ")\n", -1))
+	}
+
+	ch := make(chan config.Setting, 10)
+	settings, err := listenSpec.StreamPublisher.ForkStream(roaming.SettingsStreamName, ch)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "failed to fork stream: %s\n", err)
+	}
+	for _, setting := range settings.Latest {
+		fmt.Println("Setting: ", setting)
+	}
+	for setting := range ch {
+		fmt.Println("Setting: ", setting)
+	}
+}
diff --git a/runtime/factories/roaming/print_addrs.go b/runtime/factories/roaming/print_addrs.go
new file mode 100644
index 0000000..4f8cc6f
--- /dev/null
+++ b/runtime/factories/roaming/print_addrs.go
@@ -0,0 +1,23 @@
+// 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 ignore
+
+package main
+
+import (
+	"fmt"
+	"v.io/x/lib/netstate"
+)
+
+func main() {
+	al, err := netstate.GetAll()
+	if err != nil {
+		fmt.Printf("error getting networking state: %s", err)
+		return
+	}
+	for _, a := range al {
+		fmt.Println(a)
+	}
+}
diff --git a/runtime/factories/roaming/proxy.go b/runtime/factories/roaming/proxy.go
new file mode 100644
index 0000000..7860a52
--- /dev/null
+++ b/runtime/factories/roaming/proxy.go
@@ -0,0 +1,23 @@
+// 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 roaming
+
+import (
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+
+	"v.io/x/ref/runtime/internal/rpc/stream/proxy"
+)
+
+// NewProxy creates a new Proxy that listens for network connections on the provided
+// (network, address) pair and routes VC traffic between accepted connections.
+//
+// auth encapsulates the authorization policy of the proxy - which
+// servers it is willing to proxy for.
+func NewProxy(ctx *context.T, spec rpc.ListenSpec, auth security.Authorizer, names ...string) (shutdown func(), endpoint naming.Endpoint, err error) {
+	return proxy.New(ctx, spec, auth, names...)
+}
diff --git a/runtime/factories/roaming/roaming.go b/runtime/factories/roaming/roaming.go
new file mode 100644
index 0000000..b5f17c6
--- /dev/null
+++ b/runtime/factories/roaming/roaming.go
@@ -0,0 +1,197 @@
+// 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 linux darwin
+
+// Package roaming implements a RuntimeFactory suitable for a variety of network
+// configurations, including 1-1 NATs, dhcp auto-configuration, and Google
+// Compute Engine.
+//
+// The pubsub.Publisher mechanism is used for communicating networking
+// settings to the rpc.Server implementation of the runtime and publishes
+// the Settings it expects.
+package roaming
+
+import (
+	"flag"
+	"net"
+
+	"v.io/x/lib/netconfig"
+	"v.io/x/lib/netstate"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+
+	"v.io/x/ref/internal/logger"
+	"v.io/x/ref/lib/flags"
+	"v.io/x/ref/lib/pubsub"
+	"v.io/x/ref/lib/security/securityflag"
+	"v.io/x/ref/runtime/internal"
+	_ "v.io/x/ref/runtime/internal/flow/protocols/tcp"
+	_ "v.io/x/ref/runtime/internal/flow/protocols/ws"
+	_ "v.io/x/ref/runtime/internal/flow/protocols/wsh"
+	"v.io/x/ref/runtime/internal/lib/appcycle"
+	"v.io/x/ref/runtime/internal/lib/websocket"
+	irpc "v.io/x/ref/runtime/internal/rpc"
+	"v.io/x/ref/runtime/internal/rt"
+	"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"
+)
+
+const (
+	SettingsStreamName = "roaming"
+	SettingsStreamDesc = "pubsub stream used by the roaming RuntimeFactory"
+)
+
+var commonFlags *flags.Flags
+
+func init() {
+	v23.RegisterRuntimeFactory(Init)
+	rpc.RegisterUnknownProtocol("wsh", websocket.HybridDial, websocket.HybridResolve, websocket.HybridListener)
+	commonFlags = flags.CreateAndRegister(flag.CommandLine, flags.Runtime, flags.Listen)
+}
+
+func Init(ctx *context.T) (v23.Runtime, *context.T, v23.Shutdown, error) {
+	if err := internal.ParseFlagsAndConfigureGlobalLogger(commonFlags); err != nil {
+		return nil, nil, nil, err
+	}
+
+	lf := commonFlags.ListenFlags()
+	listenSpec := rpc.ListenSpec{
+		Addrs: rpc.ListenAddrs(lf.Addrs),
+		Proxy: lf.ListenProxy,
+	}
+	reservedDispatcher := debuglib.NewDispatcher(securityflag.NewAuthorizerOrDie())
+
+	ac := appcycle.New()
+
+	// Our address is private, so we test for running on GCE and for its
+	// 1:1 NAT configuration.
+	if !internal.HasPublicIP(logger.Global()) {
+		if addr := internal.GCEPublicAddress(logger.Global()); addr != nil {
+			listenSpec.AddressChooser = netstate.AddressChooserFunc(func(string, []net.Addr) ([]net.Addr, error) {
+				// TODO(cnicolaou): the protocol at least should
+				// be configurable, or maybe there's a RuntimeFactory specific
+				// flag to configure both the protocol and address.
+				return []net.Addr{netstate.NewNetAddr("wsh", addr.String())}, nil
+			})
+			runtime, ctx, shutdown, err := rt.Init(ctx, ac, nil, &listenSpec, nil, "", commonFlags.RuntimeFlags(), reservedDispatcher)
+			if err != nil {
+				return nil, nil, shutdown, err
+			}
+			runtimeFactoryShutdown := func() {
+				ac.Shutdown()
+				shutdown()
+			}
+			return runtime, ctx, runtimeFactoryShutdown, nil
+		}
+	}
+
+	publisher := pubsub.NewPublisher()
+
+	// Create stream in Init function to avoid a race between any
+	// goroutines started here and consumers started after Init returns.
+	ch := make(chan pubsub.Setting)
+	// TODO(cnicolaou): use stop to shutdown this stream when the RuntimeFactory shutdowns.
+	stop, err := publisher.CreateStream(SettingsStreamName, SettingsStreamDesc, ch)
+	if err != nil {
+		ac.Shutdown()
+		return nil, nil, nil, err
+	}
+
+	prev, err := netstate.GetAccessibleIPs()
+	if err != nil {
+		ac.Shutdown()
+		return nil, nil, nil, err
+	}
+
+	// Start the dhcp watcher.
+	watcher, err := netconfig.NewNetConfigWatcher()
+	if err != nil {
+		ac.Shutdown()
+		return nil, nil, nil, err
+	}
+
+	cleanupCh := make(chan struct{})
+	watcherCh := make(chan struct{})
+
+	listenSpec.AddressChooser = internal.IPAddressChooser{}
+
+	runtime, ctx, shutdown, err := rt.Init(ctx, ac, nil, &listenSpec, publisher, SettingsStreamName, commonFlags.RuntimeFlags(), reservedDispatcher)
+	if err != nil {
+		return nil, nil, shutdown, err
+	}
+
+	go monitorNetworkSettingsX(runtime, ctx, watcher, prev, stop, cleanupCh, watcherCh, ch)
+	runtimeFactoryShutdown := func() {
+		close(cleanupCh)
+		ac.Shutdown()
+		shutdown()
+		<-watcherCh
+	}
+	return runtime, ctx, runtimeFactoryShutdown, nil
+}
+
+// monitorNetworkSettings will monitor network configuration changes and
+// publish subsequent Settings to reflect any changes detected.
+func monitorNetworkSettingsX(
+	runtime *rt.Runtime,
+	ctx *context.T,
+	watcher netconfig.NetConfigWatcher,
+	prev netstate.AddrList,
+	pubStop, cleanup <-chan struct{},
+	watcherLoop chan<- struct{},
+	ch chan<- pubsub.Setting) {
+	defer close(ch)
+
+	listenSpec := runtime.GetListenSpec(ctx)
+
+	// TODO(cnicolaou): add support for listening on multiple network addresses.
+
+done:
+	for {
+		select {
+		case <-watcher.Channel():
+			netstate.InvalidateCache()
+			cur, err := netstate.GetAccessibleIPs()
+			if err != nil {
+				ctx.Errorf("failed to read network state: %s", err)
+				continue
+			}
+			removed := netstate.FindRemoved(prev, cur)
+			added := netstate.FindAdded(prev, cur)
+			ctx.VI(2).Infof("Previous: %d: %s", len(prev), prev)
+			ctx.VI(2).Infof("Current : %d: %s", len(cur), cur)
+			ctx.VI(2).Infof("Added   : %d: %s", len(added), added)
+			ctx.VI(2).Infof("Removed : %d: %s", len(removed), removed)
+			if len(removed) == 0 && len(added) == 0 {
+				ctx.VI(2).Infof("Network event that lead to no address changes since our last 'baseline'")
+				continue
+			}
+			if len(removed) > 0 {
+				ctx.VI(2).Infof("Sending removed: %s", removed)
+				ch <- irpc.NewRmAddrsSetting(removed.AsNetAddrs())
+			}
+			// We will always send the best currently available address
+			if chosen, err := listenSpec.AddressChooser.ChooseAddress(listenSpec.Addrs[0].Protocol, cur.AsNetAddrs()); err == nil && chosen != nil {
+				ctx.VI(2).Infof("Sending added and chosen: %s", chosen)
+				ch <- irpc.NewAddAddrsSetting(chosen)
+			} else {
+				ctx.VI(2).Infof("Ignoring added %s", added)
+			}
+			prev = cur
+		case <-cleanup:
+			break done
+		case <-pubStop:
+			goto done
+		}
+	}
+	watcher.Stop()
+	close(watcherLoop)
+}
diff --git a/runtime/factories/roaming/roaming_server.go b/runtime/factories/roaming/roaming_server.go
new file mode 100644
index 0000000..c87d37a
--- /dev/null
+++ b/runtime/factories/roaming/roaming_server.go
@@ -0,0 +1,48 @@
+// 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 ignore
+
+package main
+
+import (
+	"fmt"
+
+	"v.io/v23"
+	"v.io/v23/rpc"
+	"v.io/x/ref/lib/xrpc"
+	_ "v.io/x/ref/runtime/factories/roaming"
+)
+
+func main() {
+	ctx, shutdown := v23.Init()
+	defer shutdown()
+
+	server, err := xrpc.NewServer(ctx, "roamer", &dummy{}, nil)
+	if err != nil {
+		ctx.Fatalf("unexpected error: %q", err)
+	}
+	watcher := make(chan rpc.NetworkChange, 1)
+	server.WatchNetwork(watcher)
+
+	for {
+		status := server.Status()
+		fmt.Printf("Endpoints: %d created:\n", len(status.Endpoints))
+		for _, ep := range status.Endpoints {
+			fmt.Printf("  %s\n", ep)
+		}
+		fmt.Printf("Mounts: %d mounts:\n", len(status.Mounts))
+		for _, ms := range status.Mounts {
+			fmt.Printf("  %s\n", ms)
+		}
+		change := <-watcher
+		fmt.Printf("Network change: %s", change.DebugString())
+	}
+}
+
+type dummy struct{}
+
+func (d *dummy) Echo(call rpc.ServerCall, arg string) (string, error) {
+	return arg, nil
+}
diff --git a/runtime/factories/static/.api b/runtime/factories/static/.api
new file mode 100644
index 0000000..2d3ebb3
--- /dev/null
+++ b/runtime/factories/static/.api
@@ -0,0 +1,2 @@
+pkg static, func Init(*context.T) (v23.Runtime, *context.T, v23.Shutdown, error)
+pkg static, func NewProxy(*context.T, rpc.ListenSpec, ...string) (func(), naming.Endpoint, error)
diff --git a/runtime/factories/static/proxy.go b/runtime/factories/static/proxy.go
new file mode 100644
index 0000000..a5aa0b6
--- /dev/null
+++ b/runtime/factories/static/proxy.go
@@ -0,0 +1,23 @@
+// 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 static
+
+import (
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+
+	"v.io/x/ref/runtime/internal/rpc/stream/proxy"
+)
+
+// NewProxy creates a new Proxy that listens for network connections on the provided
+// (network, address) pair and routes VC traffic between accepted connections.
+//
+// auth encapsulates the authorization policy of the proxy - which
+// servers it is willing to proxy for.
+func NewProxy(ctx *context.T, spec rpc.ListenSpec, auth security.Authorizer, names ...string) (shutdown func(), endpoint naming.Endpoint, err error) {
+	return proxy.New(ctx, spec, auth, names...)
+}
diff --git a/runtime/factories/static/static.go b/runtime/factories/static/static.go
new file mode 100644
index 0000000..b04f6f8
--- /dev/null
+++ b/runtime/factories/static/static.go
@@ -0,0 +1,88 @@
+// 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 static implements a RuntimeFactory for static network configurations.
+package static
+
+import (
+	"flag"
+	"net"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+
+	"v.io/x/ref/internal/logger"
+	"v.io/x/ref/lib/flags"
+	"v.io/x/ref/lib/security/securityflag"
+	"v.io/x/ref/runtime/internal"
+	_ "v.io/x/ref/runtime/internal/flow/protocols/tcp"
+	_ "v.io/x/ref/runtime/internal/flow/protocols/ws"
+	_ "v.io/x/ref/runtime/internal/flow/protocols/wsh"
+	"v.io/x/ref/runtime/internal/lib/appcycle"
+	"v.io/x/ref/runtime/internal/lib/websocket"
+
+	"v.io/x/ref/runtime/internal/rt"
+	"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)
+	commonFlags = flags.CreateAndRegister(flag.CommandLine, flags.Runtime, flags.Listen)
+}
+
+func Init(ctx *context.T) (v23.Runtime, *context.T, v23.Shutdown, error) {
+	if err := internal.ParseFlagsAndConfigureGlobalLogger(commonFlags); err != nil {
+		return nil, nil, nil, err
+	}
+
+	lf := commonFlags.ListenFlags()
+	listenSpec := rpc.ListenSpec{
+		Addrs: rpc.ListenAddrs(lf.Addrs),
+		Proxy: lf.ListenProxy,
+	}
+	reservedDispatcher := debuglib.NewDispatcher(securityflag.NewAuthorizerOrDie())
+
+	ac := appcycle.New()
+
+	// Our address is private, so we test for running on GCE and for its 1:1 NAT
+	// configuration. GCEPublicAddress returns a non-nil addr if we are
+	// running on GCE.
+	if !internal.HasPublicIP(logger.Global()) {
+		if addr := internal.GCEPublicAddress(logger.Global()); addr != nil {
+			listenSpec.AddressChooser = rpc.AddressChooserFunc(func(string, []net.Addr) ([]net.Addr, error) {
+				return []net.Addr{addr}, nil
+			})
+			runtime, ctx, shutdown, err := rt.Init(ctx, ac, nil, &listenSpec, nil, "", commonFlags.RuntimeFlags(), reservedDispatcher)
+			if err != nil {
+				return nil, nil, nil, err
+			}
+			runtimeFactoryShutdown := func() {
+				ac.Shutdown()
+				shutdown()
+			}
+			return runtime, ctx, runtimeFactoryShutdown, nil
+		}
+	}
+	listenSpec.AddressChooser = internal.IPAddressChooser{}
+
+	runtime, ctx, shutdown, err := rt.Init(ctx, ac, nil, &listenSpec, nil, "", commonFlags.RuntimeFlags(), reservedDispatcher)
+	if err != nil {
+		return nil, nil, shutdown, err
+	}
+
+	runtimeFactoryShutdown := func() {
+		ac.Shutdown()
+		shutdown()
+	}
+	return runtime, ctx, runtimeFactoryShutdown, nil
+}
diff --git a/runtime/internal/README b/runtime/internal/README
new file mode 100644
index 0000000..875a4e1
--- /dev/null
+++ b/runtime/internal/README
@@ -0,0 +1,2 @@
+This directory and all of its subdirectories contain an implementation of the
+public APIs defined in v23.
diff --git a/runtime/internal/discovery/advertise.go b/runtime/internal/discovery/advertise.go
new file mode 100644
index 0000000..1422760
--- /dev/null
+++ b/runtime/internal/discovery/advertise.go
@@ -0,0 +1,16 @@
+// 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 discovery
+
+import (
+	"v.io/v23/context"
+	"v.io/v23/discovery"
+	"v.io/v23/security/access"
+)
+
+// Advertise implements discovery.Advertiser.
+func (ds *ds) Advertise(ctx *context.T, service discovery.Service, perms access.Permissions) error {
+	return nil
+}
diff --git a/runtime/internal/discovery/discovery.go b/runtime/internal/discovery/discovery.go
new file mode 100644
index 0000000..53501d7
--- /dev/null
+++ b/runtime/internal/discovery/discovery.go
@@ -0,0 +1,9 @@
+// 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 discovery
+
+// ds is an implementation of discovery.T.
+type ds struct {
+}
diff --git a/runtime/internal/discovery/plugin.go b/runtime/internal/discovery/plugin.go
new file mode 100644
index 0000000..c275d89
--- /dev/null
+++ b/runtime/internal/discovery/plugin.go
@@ -0,0 +1,9 @@
+// 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 discovery
+
+// Plugin is the basic interface for a plugin to discovery service.
+type Plugin interface {
+}
diff --git a/runtime/internal/discovery/scan.go b/runtime/internal/discovery/scan.go
new file mode 100644
index 0000000..f955326
--- /dev/null
+++ b/runtime/internal/discovery/scan.go
@@ -0,0 +1,15 @@
+// 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 discovery
+
+import (
+	"v.io/v23/context"
+	"v.io/v23/discovery"
+)
+
+// Scan implements discovery.Scanner.
+func (ds *ds) Scan(ctx *context.T, query string) (<-chan discovery.Update, error) {
+	return nil, nil
+}
diff --git a/runtime/internal/flow/conn/auth.go b/runtime/internal/flow/conn/auth.go
new file mode 100644
index 0000000..15eb1a9
--- /dev/null
+++ b/runtime/internal/flow/conn/auth.go
@@ -0,0 +1,339 @@
+// 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 conn
+
+import (
+	"crypto/rand"
+	"io"
+	"reflect"
+	"sync"
+	"time"
+
+	"golang.org/x/crypto/nacl/box"
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/flow"
+	"v.io/v23/flow/message"
+	"v.io/v23/rpc/version"
+	"v.io/v23/security"
+	"v.io/v23/verror"
+	"v.io/v23/vom"
+	slib "v.io/x/ref/lib/security"
+)
+
+func (c *Conn) dialHandshake(ctx *context.T, versions version.RPCVersionRange) error {
+	binding, err := c.setup(ctx, versions)
+	if err != nil {
+		return err
+	}
+	c.blessingsFlow = newBlessingsFlow(ctx, &c.loopWG,
+		c.newFlowLocked(ctx, blessingsFlowID, 0, 0, true, true), true)
+	if err = c.readRemoteAuth(ctx, binding); err != nil {
+		return err
+	}
+	if c.rBlessings.IsZero() {
+		return NewErrAcceptorBlessingsMissing(ctx)
+	}
+	signedBinding, err := v23.GetPrincipal(ctx).Sign(binding)
+	if err != nil {
+		return err
+	}
+	lAuth := &message.Auth{
+		ChannelBinding: signedBinding,
+	}
+	// We only send our blessings if we are a server in addition to being a client.
+	// If we are a pure client, we only send our public key.
+	if c.handler != nil {
+		if lAuth.BlessingsKey, lAuth.DischargeKey, err = c.refreshDischarges(ctx); err != nil {
+			return err
+		}
+	} else {
+		lAuth.PublicKey = c.lBlessings.PublicKey()
+	}
+	return c.mp.writeMsg(ctx, lAuth)
+}
+
+func (c *Conn) acceptHandshake(ctx *context.T, versions version.RPCVersionRange) error {
+	binding, err := c.setup(ctx, versions)
+	if err != nil {
+		return err
+	}
+	c.blessingsFlow = newBlessingsFlow(ctx, &c.loopWG,
+		c.newFlowLocked(ctx, blessingsFlowID, 0, 0, true, true), false)
+	signedBinding, err := v23.GetPrincipal(ctx).Sign(binding)
+	if err != nil {
+		return err
+	}
+	lAuth := &message.Auth{
+		ChannelBinding: signedBinding,
+	}
+	if lAuth.BlessingsKey, lAuth.DischargeKey, err = c.refreshDischarges(ctx); err != nil {
+		return err
+	}
+	if err = c.mp.writeMsg(ctx, lAuth); err != nil {
+		return err
+	}
+	return c.readRemoteAuth(ctx, binding)
+}
+
+func (c *Conn) setup(ctx *context.T, versions version.RPCVersionRange) ([]byte, error) {
+	pk, sk, err := box.GenerateKey(rand.Reader)
+	if err != nil {
+		return nil, err
+	}
+	lSetup := &message.Setup{
+		Versions:          versions,
+		PeerLocalEndpoint: c.local,
+		PeerNaClPublicKey: pk,
+	}
+	if c.remote != nil {
+		lSetup.PeerRemoteEndpoint = c.remote
+	}
+	ch := make(chan error)
+	go func() {
+		ch <- c.mp.writeMsg(ctx, lSetup)
+	}()
+	msg, err := c.mp.readMsg(ctx)
+	if err != nil {
+		<-ch
+		if verror.ErrorID(err) == message.ErrWrongProtocol.ID {
+			return nil, err
+		}
+		return nil, NewErrRecv(ctx, "unknown", err)
+	}
+	rSetup, valid := msg.(*message.Setup)
+	if !valid {
+		<-ch
+		return nil, NewErrUnexpectedMsg(ctx, reflect.TypeOf(msg).String())
+	}
+	if err := <-ch; err != nil {
+		return nil, NewErrSend(ctx, "setup", c.remote.String(), err)
+	}
+	if c.version, err = version.CommonVersion(ctx, lSetup.Versions, rSetup.Versions); err != nil {
+		return nil, err
+	}
+	// TODO(mattr): Decide which endpoints to actually keep, the ones we know locally
+	// or what the remote side thinks.
+	if rSetup.PeerRemoteEndpoint != nil {
+		c.local = rSetup.PeerRemoteEndpoint
+	}
+	if rSetup.PeerLocalEndpoint != nil {
+		c.remote = rSetup.PeerLocalEndpoint
+	}
+	if rSetup.PeerNaClPublicKey == nil {
+		return nil, NewErrMissingSetupOption(ctx, "peerNaClPublicKey")
+	}
+	return c.mp.setupEncryption(ctx, pk, sk, rSetup.PeerNaClPublicKey), nil
+}
+
+func (c *Conn) readRemoteAuth(ctx *context.T, binding []byte) error {
+	var rauth *message.Auth
+	for {
+		msg, err := c.mp.readMsg(ctx)
+		if err != nil {
+			return NewErrRecv(ctx, c.remote.String(), err)
+		}
+		if rauth, _ = msg.(*message.Auth); rauth != nil {
+			break
+		}
+		if err = c.handleMessage(ctx, msg); err != nil {
+			return err
+		}
+	}
+	var rPublicKey security.PublicKey
+	if rauth.BlessingsKey != 0 {
+		var err error
+		// TODO(mattr): Make sure we cancel out of this at some point.
+		c.rBlessings, _, err = c.blessingsFlow.get(ctx, rauth.BlessingsKey, rauth.DischargeKey)
+		if err != nil {
+			return err
+		}
+		rPublicKey = c.rBlessings.PublicKey()
+	} else {
+		rPublicKey = rauth.PublicKey
+	}
+	if rPublicKey == nil {
+		return NewErrNoPublicKey(ctx)
+	}
+	if !rauth.ChannelBinding.Verify(rPublicKey, binding) {
+		return NewErrInvalidChannelBinding(ctx)
+	}
+	return nil
+}
+
+func (c *Conn) refreshDischarges(ctx *context.T) (bkey, dkey uint64, err error) {
+	dis := slib.PrepareDischarges(ctx, c.lBlessings,
+		security.DischargeImpetus{}, time.Minute)
+	// Schedule the next update.
+	var timer *time.Timer
+	if dur, expires := minExpiryTime(c.lBlessings, dis); expires {
+		timer = time.AfterFunc(dur, func() {
+			c.refreshDischarges(ctx)
+		})
+	}
+	bkey, dkey, err = c.blessingsFlow.put(ctx, c.lBlessings, dis)
+	c.mu.Lock()
+	c.dischargeTimer = timer
+	c.mu.Unlock()
+	return
+}
+
+func minExpiryTime(blessings security.Blessings, discharges map[string]security.Discharge) (time.Duration, bool) {
+	var min time.Time
+	cavCount := len(blessings.ThirdPartyCaveats())
+	if cavCount == 0 {
+		return 0, false
+	}
+	for _, d := range discharges {
+		if exp := d.Expiry(); min.IsZero() || (!exp.IsZero() && exp.Before(min)) {
+			min = exp
+		}
+	}
+	if min.IsZero() && cavCount == len(discharges) {
+		return 0, false
+	}
+	now := time.Now()
+	d := min.Sub(now)
+	if d > time.Minute && cavCount > len(discharges) {
+		d = time.Minute
+	}
+	return d, true
+}
+
+type blessingsFlow struct {
+	enc *vom.Encoder
+	dec *vom.Decoder
+
+	mu      sync.Mutex
+	cond    *sync.Cond
+	closed  bool
+	nextKey uint64
+	byUID   map[string]*Blessings
+	byBKey  map[uint64]*Blessings
+}
+
+func newBlessingsFlow(ctx *context.T, loopWG *sync.WaitGroup, f flow.Flow, dialed bool) *blessingsFlow {
+	b := &blessingsFlow{
+		enc:     vom.NewEncoder(f),
+		dec:     vom.NewDecoder(f),
+		nextKey: 1,
+		byUID:   make(map[string]*Blessings),
+		byBKey:  make(map[uint64]*Blessings),
+	}
+	b.cond = sync.NewCond(&b.mu)
+	if !dialed {
+		b.nextKey++
+	}
+	loopWG.Add(1)
+	go b.readLoop(ctx, loopWG)
+	return b
+}
+
+func (b *blessingsFlow) put(ctx *context.T, blessings security.Blessings, discharges map[string]security.Discharge) (bkey, dkey uint64, err error) {
+	defer b.mu.Unlock()
+	b.mu.Lock()
+	buid := string(blessings.UniqueID())
+	element, has := b.byUID[buid]
+	if has && equalDischarges(discharges, element.Discharges) {
+		return element.BKey, element.DKey, nil
+	}
+	defer b.cond.Broadcast()
+	if has {
+		element.Discharges = dischargeList(discharges)
+		element.DKey = b.nextKey
+		b.nextKey += 2
+		return element.BKey, element.DKey, b.enc.Encode(Blessings{
+			Discharges: element.Discharges,
+			DKey:       element.DKey,
+		})
+	}
+	element = &Blessings{
+		Blessings:  blessings,
+		Discharges: dischargeList(discharges),
+		BKey:       b.nextKey,
+	}
+	b.nextKey += 2
+	if len(discharges) > 0 {
+		element.DKey = b.nextKey
+		b.nextKey += 2
+	}
+	b.byUID[buid] = element
+	b.byBKey[element.BKey] = element
+	return element.BKey, element.DKey, b.enc.Encode(element)
+}
+
+func (b *blessingsFlow) get(ctx *context.T, bkey, dkey uint64) (security.Blessings, map[string]security.Discharge, error) {
+	defer b.mu.Unlock()
+	b.mu.Lock()
+	for !b.closed {
+		element, has := b.byBKey[bkey]
+		if has && element.DKey == dkey {
+			return element.Blessings, dischargeMap(element.Discharges), nil
+		}
+		b.cond.Wait()
+	}
+	return security.Blessings{}, nil, NewErrBlessingsFlowClosed(ctx)
+}
+
+func (b *blessingsFlow) getLatestDischarges(ctx *context.T, blessings security.Blessings) (map[string]security.Discharge, error) {
+	defer b.mu.Unlock()
+	b.mu.Lock()
+	buid := string(blessings.UniqueID())
+	for !b.closed {
+		element, has := b.byUID[buid]
+		if has {
+			return dischargeMap(element.Discharges), nil
+		}
+		b.cond.Wait()
+	}
+	return nil, NewErrBlessingsFlowClosed(ctx)
+}
+
+func (b *blessingsFlow) readLoop(ctx *context.T, loopWG *sync.WaitGroup) {
+	defer loopWG.Done()
+	for {
+		var received Blessings
+		err := b.dec.Decode(&received)
+		b.mu.Lock()
+		if err != nil {
+			if err != io.EOF {
+				ctx.Errorf("Blessings flow closed: %v", err)
+			}
+			b.closed = true
+			b.mu.Unlock()
+			return
+		}
+		b.byUID[string(received.Blessings.UniqueID())] = &received
+		b.byBKey[received.BKey] = &received
+		b.cond.Broadcast()
+		b.mu.Unlock()
+	}
+}
+
+func dischargeList(in map[string]security.Discharge) []security.Discharge {
+	out := make([]security.Discharge, 0, len(in))
+	for _, d := range in {
+		out = append(out, d)
+	}
+	return out
+}
+func dischargeMap(in []security.Discharge) map[string]security.Discharge {
+	out := make(map[string]security.Discharge, len(in))
+	for _, d := range in {
+		out[d.ID()] = d
+	}
+	return out
+}
+func equalDischarges(m map[string]security.Discharge, s []security.Discharge) bool {
+	if len(m) != len(s) {
+		return false
+	}
+	for _, d := range s {
+		if !d.Equivalent(m[d.ID()]) {
+			return false
+		}
+	}
+	return true
+}
diff --git a/runtime/internal/flow/conn/auth_test.go b/runtime/internal/flow/conn/auth_test.go
new file mode 100644
index 0000000..422eb73
--- /dev/null
+++ b/runtime/internal/flow/conn/auth_test.go
@@ -0,0 +1,172 @@
+// 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 conn
+
+import (
+	"testing"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/flow"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/verror"
+	vsecurity "v.io/x/ref/lib/security"
+	"v.io/x/ref/runtime/factories/fake"
+	"v.io/x/ref/test/goroutines"
+	"v.io/x/ref/test/testutil"
+)
+
+func checkBlessings(t *testing.T, got, want security.Blessings, gotd map[string]security.Discharge) {
+	if !got.Equivalent(want) {
+		t.Errorf("got: %#v wanted %#v", got, want)
+	}
+	tpid := got.ThirdPartyCaveats()[0].ThirdPartyDetails().ID()
+	if _, has := gotd[tpid]; !has {
+		t.Errorf("got: %#v wanted %s", gotd, tpid)
+	}
+}
+
+func checkFlowBlessings(t *testing.T, df, af flow.Flow, db, ab security.Blessings) {
+	if msg, err := af.ReadMsg(); err != nil || string(msg) != "hello" {
+		t.Errorf("Got %s, %v wanted hello, nil", string(msg), err)
+	}
+	checkBlessings(t, af.LocalBlessings(), ab, af.LocalDischarges())
+	checkBlessings(t, af.RemoteBlessings(), db, af.RemoteDischarges())
+	checkBlessings(t, df.LocalBlessings(), db, df.LocalDischarges())
+	checkBlessings(t, df.RemoteBlessings(), ab, df.RemoteDischarges())
+}
+
+func dialFlow(t *testing.T, ctx *context.T, dc *Conn, b security.Blessings) flow.Flow {
+	df, err := dc.Dial(ctx, makeBFP(b))
+	if err != nil {
+		t.Fatal(err)
+	}
+	if _, err = df.WriteMsg([]byte("hello")); err != nil {
+		t.Fatal(err)
+	}
+	return df
+}
+
+func BlessWithTPCaveat(t *testing.T, ctx *context.T, p security.Principal, s string) security.Blessings {
+	dp := v23.GetPrincipal(ctx)
+	expcav, err := security.NewExpiryCaveat(time.Now().Add(time.Hour))
+	if err != nil {
+		t.Fatal(err)
+	}
+	tpcav, err := security.NewPublicKeyCaveat(dp.PublicKey(), "discharge",
+		security.ThirdPartyRequirements{}, expcav)
+	if err != nil {
+		t.Fatal(err)
+	}
+	b, err := testutil.IDProviderFromPrincipal(dp).NewBlessings(p, s, tpcav)
+	if err != nil {
+		t.Fatal(err)
+	}
+	return b
+}
+
+func NewPrincipalWithTPCaveat(t *testing.T, ctx *context.T, s string) *context.T {
+	p := testutil.NewPrincipal()
+	vsecurity.SetDefaultBlessings(p, BlessWithTPCaveat(t, ctx, p, s))
+	ctx, err := v23.WithPrincipal(ctx, p)
+	if err != nil {
+		t.Fatal(err)
+	}
+	return ctx
+}
+
+type fakeDischargeClient struct {
+	p security.Principal
+}
+
+func (fc *fakeDischargeClient) Call(_ *context.T, _, _ string, inArgs, outArgs []interface{}, _ ...rpc.CallOpt) error {
+	expiry, err := security.NewExpiryCaveat(time.Now().Add(time.Minute))
+	if err != nil {
+		panic(err)
+	}
+	*(outArgs[0].(*security.Discharge)), err = fc.p.MintDischarge(
+		inArgs[0].(security.Caveat), expiry)
+	return err
+}
+func (fc *fakeDischargeClient) StartCall(*context.T, string, string, []interface{}, ...rpc.CallOpt) (rpc.ClientCall, error) {
+	return nil, nil
+}
+func (fc *fakeDischargeClient) Close() {}
+
+func TestUnidirectional(t *testing.T) {
+	defer goroutines.NoLeaks(t, leakWaitTime)()
+
+	ctx, shutdown := v23.Init()
+	defer shutdown()
+	ctx = fake.SetClient(ctx, &fakeDischargeClient{v23.GetPrincipal(ctx)})
+
+	dctx := NewPrincipalWithTPCaveat(t, ctx, "dialer")
+	actx := NewPrincipalWithTPCaveat(t, ctx, "acceptor")
+	aflows := make(chan flow.Flow, 2)
+	dc, ac, _ := setupConns(t, dctx, actx, nil, aflows)
+	defer dc.Close(dctx, nil)
+	defer ac.Close(actx, nil)
+
+	df1 := dialFlow(t, dctx, dc, v23.GetPrincipal(dctx).BlessingStore().Default())
+	af1 := <-aflows
+	checkFlowBlessings(t, df1, af1,
+		v23.GetPrincipal(dctx).BlessingStore().Default(),
+		v23.GetPrincipal(actx).BlessingStore().Default())
+
+	db2 := BlessWithTPCaveat(t, ctx, v23.GetPrincipal(dctx), "other")
+	df2 := dialFlow(t, dctx, dc, db2)
+	af2 := <-aflows
+	checkFlowBlessings(t, df2, af2, db2,
+		v23.GetPrincipal(actx).BlessingStore().Default())
+
+	// We should not be able to dial in the other direction, because that flow
+	// manager is not willing to accept flows.
+	_, err := ac.Dial(actx, testBFP)
+	if verror.ErrorID(err) != ErrDialingNonServer.ID {
+		t.Errorf("got %v, wanted ErrDialingNonServer", err)
+	}
+}
+
+func TestBidirectional(t *testing.T) {
+	defer goroutines.NoLeaks(t, leakWaitTime)()
+
+	ctx, shutdown := v23.Init()
+	defer shutdown()
+	ctx = fake.SetClient(ctx, &fakeDischargeClient{v23.GetPrincipal(ctx)})
+
+	dctx := NewPrincipalWithTPCaveat(t, ctx, "dialer")
+	actx := NewPrincipalWithTPCaveat(t, ctx, "acceptor")
+	dflows := make(chan flow.Flow, 2)
+	aflows := make(chan flow.Flow, 2)
+	dc, ac, _ := setupConns(t, dctx, actx, dflows, aflows)
+	defer dc.Close(dctx, nil)
+	defer ac.Close(actx, nil)
+
+	df1 := dialFlow(t, dctx, dc, v23.GetPrincipal(dctx).BlessingStore().Default())
+	af1 := <-aflows
+	checkFlowBlessings(t, df1, af1,
+		v23.GetPrincipal(dctx).BlessingStore().Default(),
+		v23.GetPrincipal(actx).BlessingStore().Default())
+
+	db2 := BlessWithTPCaveat(t, ctx, v23.GetPrincipal(dctx), "other")
+	df2 := dialFlow(t, dctx, dc, db2)
+	af2 := <-aflows
+	checkFlowBlessings(t, df2, af2, db2,
+		v23.GetPrincipal(actx).BlessingStore().Default())
+
+	af3 := dialFlow(t, actx, ac, v23.GetPrincipal(actx).BlessingStore().Default())
+	df3 := <-dflows
+	checkFlowBlessings(t, af3, df3,
+		v23.GetPrincipal(actx).BlessingStore().Default(),
+		v23.GetPrincipal(dctx).BlessingStore().Default())
+
+	ab2 := BlessWithTPCaveat(t, ctx, v23.GetPrincipal(actx), "aother")
+	af4 := dialFlow(t, actx, ac, ab2)
+	df4 := <-dflows
+	checkFlowBlessings(t, af4, df4, ab2,
+		v23.GetPrincipal(dctx).BlessingStore().Default())
+}
diff --git a/runtime/internal/flow/conn/close_test.go b/runtime/internal/flow/conn/close_test.go
new file mode 100644
index 0000000..7ba0062
--- /dev/null
+++ b/runtime/internal/flow/conn/close_test.go
@@ -0,0 +1,161 @@
+// 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 conn
+
+import (
+	"bytes"
+	"fmt"
+	"testing"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	_ "v.io/x/ref/runtime/factories/fake"
+	"v.io/x/ref/test/goroutines"
+)
+
+func TestRemoteDialerClose(t *testing.T) {
+	defer goroutines.NoLeaks(t, leakWaitTime)()
+
+	ctx, shutdown := v23.Init()
+	defer shutdown()
+	d, a, w := setupConns(t, ctx, ctx, nil, nil)
+	d.Close(ctx, fmt.Errorf("Closing randomly."))
+	<-d.Closed()
+	<-a.Closed()
+	if !w.IsClosed() {
+		t.Errorf("The connection should be closed")
+	}
+}
+
+func TestRemoteAcceptorClose(t *testing.T) {
+	defer goroutines.NoLeaks(t, leakWaitTime)()
+
+	ctx, shutdown := v23.Init()
+	defer shutdown()
+	d, a, w := setupConns(t, ctx, ctx, nil, nil)
+	a.Close(ctx, fmt.Errorf("Closing randomly."))
+	<-a.Closed()
+	<-d.Closed()
+	if !w.IsClosed() {
+		t.Errorf("The connection should be closed")
+	}
+}
+
+func TestUnderlyingConnectionClosed(t *testing.T) {
+	defer goroutines.NoLeaks(t, leakWaitTime)()
+
+	ctx, shutdown := v23.Init()
+	defer shutdown()
+	d, a, w := setupConns(t, ctx, ctx, nil, nil)
+	w.Close()
+	<-a.Closed()
+	<-d.Closed()
+}
+
+func TestDialAfterConnClose(t *testing.T) {
+	defer goroutines.NoLeaks(t, leakWaitTime)()
+
+	ctx, shutdown := v23.Init()
+	defer shutdown()
+	d, a, _ := setupConns(t, ctx, ctx, nil, nil)
+
+	d.Close(ctx, fmt.Errorf("Closing randomly."))
+	<-d.Closed()
+	<-a.Closed()
+	if _, err := d.Dial(ctx, testBFP); err == nil {
+		t.Errorf("Nil error dialing on dialer")
+	}
+	if _, err := a.Dial(ctx, testBFP); err == nil {
+		t.Errorf("Nil error dialing on acceptor")
+	}
+}
+
+func TestReadWriteAfterConnClose(t *testing.T) {
+	defer goroutines.NoLeaks(t, leakWaitTime)()
+
+	ctx, shutdown := v23.Init()
+	defer shutdown()
+	for _, dialerDials := range []bool{true, false} {
+		df, flows, cl := setupFlow(t, ctx, ctx, dialerDials)
+		if _, err := df.WriteMsg([]byte("hello")); err != nil {
+			t.Fatalf("write failed: %v", err)
+		}
+		af := <-flows
+		if got, err := af.ReadMsg(); err != nil {
+			t.Fatalf("read failed: %v", err)
+		} else if !bytes.Equal(got, []byte("hello")) {
+			t.Errorf("got %s want %s", string(got), "hello")
+		}
+		if _, err := df.WriteMsg([]byte("there")); err != nil {
+			t.Fatalf("second write failed: %v", err)
+		}
+		df.(*flw).conn.Close(ctx, fmt.Errorf("Closing randomly."))
+		<-af.Conn().Closed()
+		if got, err := af.ReadMsg(); err != nil {
+			t.Fatalf("read failed: %v", err)
+		} else if !bytes.Equal(got, []byte("there")) {
+			t.Errorf("got %s want %s", string(got), "there")
+		}
+		if _, err := df.WriteMsg([]byte("fail")); err == nil {
+			t.Errorf("nil error for write after close.")
+		}
+		if _, err := af.ReadMsg(); err == nil {
+			t.Fatalf("nil error for read after close.")
+		}
+		cl()
+	}
+}
+
+func TestFlowCancelOnWrite(t *testing.T) {
+	defer goroutines.NoLeaks(t, leakWaitTime)()
+
+	ctx, shutdown := v23.Init()
+	defer shutdown()
+	dctx, cancel := context.WithCancel(ctx)
+	df, accept, cl := setupFlow(t, dctx, ctx, true)
+	defer cl()
+	done := make(chan struct{})
+	go func() {
+		if _, err := df.WriteMsg([]byte("hello")); err != nil {
+			t.Fatalf("could not write flow: %v", err)
+		}
+		for {
+			if _, err := df.WriteMsg([]byte("hello")); err == context.Canceled {
+				break
+			} else if err != nil {
+				t.Fatalf("unexpected error waiting for cancel: %v", err)
+			}
+		}
+		close(done)
+	}()
+	af := <-accept
+	cancel()
+	<-done
+	<-af.Closed()
+}
+
+func TestFlowCancelOnRead(t *testing.T) {
+	defer goroutines.NoLeaks(t, leakWaitTime)()
+
+	ctx, shutdown := v23.Init()
+	defer shutdown()
+	dctx, cancel := context.WithCancel(ctx)
+	df, accept, cl := setupFlow(t, dctx, ctx, true)
+	defer cl()
+	done := make(chan struct{})
+	go func() {
+		if _, err := df.WriteMsg([]byte("hello")); err != nil {
+			t.Fatalf("could not write flow: %v", err)
+		}
+		if _, err := df.ReadMsg(); err != context.Canceled {
+			t.Fatalf("unexpected error waiting for cancel: %v", err)
+		}
+		close(done)
+	}()
+	af := <-accept
+	cancel()
+	<-done
+	<-af.Closed()
+}
diff --git a/runtime/internal/flow/conn/conn.go b/runtime/internal/flow/conn/conn.go
new file mode 100644
index 0000000..d463ef2
--- /dev/null
+++ b/runtime/internal/flow/conn/conn.go
@@ -0,0 +1,314 @@
+// 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 conn
+
+import (
+	"reflect"
+	"sync"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/flow"
+	"v.io/v23/flow/message"
+	"v.io/v23/naming"
+	"v.io/v23/rpc/version"
+	"v.io/v23/security"
+	"v.io/v23/verror"
+	"v.io/x/ref/runtime/internal/flow/flowcontrol"
+)
+
+// flowID is a number assigned to identify a flow.
+// Each flow on a given conn will have a unique number.
+const (
+	invalidFlowID = iota
+	blessingsFlowID
+	reservedFlows = 10
+)
+
+const mtu = 1 << 16
+const defaultBufferSize = 1 << 20
+
+const (
+	expressPriority = iota
+	flowPriority
+	tearDownPriority
+)
+
+// FlowHandlers process accepted flows.
+type FlowHandler interface {
+	// HandleFlow processes an accepted flow.
+	HandleFlow(flow.Flow) error
+}
+
+// Conns are a multiplexing encrypted channels that can host Flows.
+type Conn struct {
+	fc                     *flowcontrol.FlowController
+	mp                     *messagePipe
+	handler                FlowHandler
+	version                version.RPCVersion
+	lBlessings, rBlessings security.Blessings
+	local, remote          naming.Endpoint
+	closed                 chan struct{}
+	blessingsFlow          *blessingsFlow
+	loopWG                 sync.WaitGroup
+
+	mu             sync.Mutex
+	nextFid        uint64
+	flows          map[uint64]*flw
+	dischargeTimer *time.Timer
+	lastUsedTime   time.Time
+}
+
+// Ensure that *Conn implements flow.ManagedConn.
+var _ flow.ManagedConn = &Conn{}
+
+// NewDialed dials a new Conn on the given conn.
+func NewDialed(
+	ctx *context.T,
+	conn flow.MsgReadWriteCloser,
+	local, remote naming.Endpoint,
+	versions version.RPCVersionRange,
+	handler FlowHandler) (*Conn, error) {
+	c := &Conn{
+		fc:           flowcontrol.New(defaultBufferSize, mtu),
+		mp:           newMessagePipe(conn),
+		handler:      handler,
+		lBlessings:   v23.GetPrincipal(ctx).BlessingStore().Default(),
+		local:        local,
+		remote:       remote,
+		closed:       make(chan struct{}),
+		nextFid:      reservedFlows,
+		flows:        map[uint64]*flw{},
+		lastUsedTime: time.Now(),
+	}
+	if err := c.dialHandshake(ctx, versions); err != nil {
+		c.Close(ctx, err)
+		return nil, err
+	}
+	c.loopWG.Add(1)
+	go c.readLoop(ctx)
+	return c, nil
+}
+
+// NewAccepted accepts a new Conn on the given conn.
+func NewAccepted(
+	ctx *context.T,
+	conn flow.MsgReadWriteCloser,
+	local naming.Endpoint,
+	versions version.RPCVersionRange,
+	handler FlowHandler) (*Conn, error) {
+	c := &Conn{
+		fc:           flowcontrol.New(defaultBufferSize, mtu),
+		mp:           newMessagePipe(conn),
+		handler:      handler,
+		lBlessings:   v23.GetPrincipal(ctx).BlessingStore().Default(),
+		local:        local,
+		closed:       make(chan struct{}),
+		nextFid:      reservedFlows + 1,
+		flows:        map[uint64]*flw{},
+		lastUsedTime: time.Now(),
+	}
+	if err := c.acceptHandshake(ctx, versions); err != nil {
+		c.Close(ctx, err)
+		return nil, err
+	}
+	c.loopWG.Add(1)
+	go c.readLoop(ctx)
+	return c, nil
+}
+
+// Dial dials a new flow on the Conn.
+func (c *Conn) Dial(ctx *context.T, fn flow.BlessingsForPeer) (flow.Flow, error) {
+	if c.rBlessings.IsZero() {
+		return nil, NewErrDialingNonServer(ctx)
+	}
+	rDischarges, err := c.blessingsFlow.getLatestDischarges(ctx, c.rBlessings)
+	if err != nil {
+		return nil, err
+	}
+	blessings, discharges, err := fn(ctx, c.local, c.remote, c.rBlessings, rDischarges)
+	if err != nil {
+		return nil, err
+	}
+	bkey, dkey, err := c.blessingsFlow.put(ctx, blessings, discharges)
+	if err != nil {
+		return nil, err
+	}
+	defer c.mu.Unlock()
+	c.mu.Lock()
+	if c.flows == nil {
+		return nil, NewErrConnectionClosed(ctx)
+	}
+	id := c.nextFid
+	c.nextFid++
+	return c.newFlowLocked(ctx, id, bkey, dkey, true, false), nil
+}
+
+// LocalEndpoint returns the local vanadium Endpoint
+func (c *Conn) LocalEndpoint() naming.Endpoint { return c.local }
+
+// RemoteEndpoint returns the remote vanadium Endpoint
+func (c *Conn) RemoteEndpoint() naming.Endpoint { return c.remote }
+
+// LastUsedTime returns the time at which the Conn had bytes read or written on it.
+func (c *Conn) LastUsedTime() time.Time {
+	defer c.mu.Unlock()
+	c.mu.Lock()
+	return c.lastUsedTime
+}
+
+// Closed returns a channel that will be closed after the Conn is shutdown.
+// After this channel is closed it is guaranteed that all Dial calls will fail
+// with an error and no more flows will be sent to the FlowHandler.
+func (c *Conn) Closed() <-chan struct{} { return c.closed }
+
+// Close shuts down a conn.
+func (c *Conn) Close(ctx *context.T, err error) {
+	c.mu.Lock()
+	var flows map[uint64]*flw
+	flows, c.flows = c.flows, nil
+	if c.dischargeTimer != nil {
+		c.dischargeTimer.Stop()
+		c.dischargeTimer = nil
+	}
+	c.mu.Unlock()
+
+	if flows == nil {
+		// This conn is already being torn down.
+		<-c.closed
+		return
+	}
+	c.internalClose(ctx, err, flows)
+}
+
+func (c *Conn) internalClose(ctx *context.T, err error, flows map[uint64]*flw) {
+	ctx.VI(2).Infof("Closing connection: %v", err)
+	if verror.ErrorID(err) != ErrConnClosedRemotely.ID {
+		msg := ""
+		if err != nil {
+			msg = err.Error()
+		}
+		cerr := c.fc.Run(ctx, "close", expressPriority, func(_ int) (int, bool, error) {
+			return 0, true, c.mp.writeMsg(ctx, &message.TearDown{Message: msg})
+		})
+		if cerr != nil {
+			ctx.Errorf("Error sending tearDown on connection to %s: %v", c.remote, cerr)
+		}
+	}
+	for _, f := range flows {
+		f.close(ctx, NewErrConnectionClosed(ctx))
+	}
+	if cerr := c.mp.close(); cerr != nil {
+		ctx.Errorf("Error closing underlying connection for %s: %v", c.remote, cerr)
+	}
+	c.loopWG.Wait()
+	close(c.closed)
+}
+
+func (c *Conn) release(ctx *context.T) {
+	counts := map[uint64]uint64{}
+	c.mu.Lock()
+	for fid, f := range c.flows {
+		if release := f.q.release(); release > 0 {
+			counts[fid] = uint64(release)
+		}
+	}
+	c.mu.Unlock()
+	if len(counts) == 0 {
+		return
+	}
+
+	err := c.fc.Run(ctx, "release", expressPriority, func(_ int) (int, bool, error) {
+		err := c.mp.writeMsg(ctx, &message.Release{
+			Counters: counts,
+		})
+		return 0, true, err
+	})
+	if err != nil {
+		c.Close(ctx, NewErrSend(ctx, "release", c.remote.String(), err))
+	}
+}
+
+func (c *Conn) handleMessage(ctx *context.T, m message.Message) error {
+	switch msg := m.(type) {
+	case *message.TearDown:
+		return NewErrConnClosedRemotely(ctx, msg.Message)
+
+	case *message.OpenFlow:
+		if c.handler == nil {
+			return NewErrUnexpectedMsg(ctx, "openFlow")
+		}
+		c.mu.Lock()
+		f := c.newFlowLocked(ctx, msg.ID, msg.BlessingsKey, msg.DischargeKey, false, true)
+		c.mu.Unlock()
+		c.handler.HandleFlow(f)
+
+	case *message.Release:
+		release := make([]flowcontrol.Release, 0, len(msg.Counters))
+		c.mu.Lock()
+		for fid, val := range msg.Counters {
+			if f := c.flows[fid]; f != nil {
+				release = append(release, flowcontrol.Release{
+					Worker: f.worker,
+					Tokens: int(val),
+				})
+			}
+		}
+		c.mu.Unlock()
+		if err := c.fc.Release(ctx, release); err != nil {
+			return err
+		}
+
+	case *message.Data:
+		c.mu.Lock()
+		f := c.flows[msg.ID]
+		c.mu.Unlock()
+		if f == nil {
+			ctx.Infof("Ignoring data message for unknown flow on connection to %s: %d", c.remote, msg.ID)
+			return nil
+		}
+		if err := f.q.put(ctx, msg.Payload); err != nil {
+			return err
+		}
+		if msg.Flags&message.CloseFlag != 0 {
+			f.close(ctx, NewErrFlowClosedRemotely(f.ctx))
+		}
+
+	default:
+		return NewErrUnexpectedMsg(ctx, reflect.TypeOf(msg).String())
+	}
+	return nil
+}
+
+func (c *Conn) readLoop(ctx *context.T) {
+	var err error
+	for {
+		msg, rerr := c.mp.readMsg(ctx)
+		if rerr != nil {
+			err = NewErrRecv(ctx, c.remote.String(), rerr)
+			break
+		}
+		if err = c.handleMessage(ctx, msg); err != nil {
+			break
+		}
+	}
+
+	c.mu.Lock()
+	var flows map[uint64]*flw
+	flows, c.flows = c.flows, nil
+	c.mu.Unlock()
+
+	c.loopWG.Done()
+	if flows != nil {
+		c.internalClose(ctx, err, flows)
+	}
+}
+
+func (c *Conn) markUsed() {
+	c.mu.Lock()
+	c.lastUsedTime = time.Now()
+	c.mu.Unlock()
+}
diff --git a/runtime/internal/flow/conn/conn_test.go b/runtime/internal/flow/conn/conn_test.go
new file mode 100644
index 0000000..b5b6eeb
--- /dev/null
+++ b/runtime/internal/flow/conn/conn_test.go
@@ -0,0 +1,84 @@
+// 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 conn
+
+import (
+	"bytes"
+	"crypto/rand"
+	"io"
+	"sync"
+	"testing"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/flow"
+	_ "v.io/x/ref/runtime/factories/fake"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/goroutines"
+)
+
+const leakWaitTime = 100 * time.Millisecond
+
+var randData []byte
+
+func init() {
+	test.Init()
+	randData = make([]byte, 2*defaultBufferSize)
+	if _, err := rand.Read(randData); err != nil {
+		panic("Could not read random data.")
+	}
+}
+
+func trunc(b []byte) []byte {
+	if len(b) > 100 {
+		return b[:100]
+	}
+	return b
+}
+
+func doWrite(t *testing.T, f flow.Flow, data []byte) {
+	mid := len(data) / 2
+	wrote, err := f.WriteMsg(data[:mid], data[mid:])
+	if err != nil || wrote != len(data) {
+		t.Errorf("Unexpected result for write: %d, %v wanted %d, nil", wrote, err, len(data))
+	}
+}
+
+func doRead(t *testing.T, f flow.Flow, want []byte, wg *sync.WaitGroup) {
+	for read := 0; len(want) > 0; read++ {
+		got, err := f.ReadMsg()
+		if err != nil && err != io.EOF {
+			t.Errorf("Unexpected error: %v", err)
+			break
+		}
+		if !bytes.Equal(got, want[:len(got)]) {
+			t.Errorf("On read %d got: %v want %v", read, trunc(got), trunc(want))
+			break
+		}
+		want = want[len(got):]
+	}
+	if len(want) != 0 {
+		t.Errorf("got %d leftover bytes, expected 0.", len(want))
+	}
+	wg.Done()
+}
+
+func TestLargeWrite(t *testing.T) {
+	defer goroutines.NoLeaks(t, leakWaitTime)()
+
+	ctx, shutdown := v23.Init()
+	df, flows, cl := setupFlow(t, ctx, ctx, true)
+	defer cl()
+	defer shutdown()
+
+	var wg sync.WaitGroup
+	wg.Add(2)
+	go doWrite(t, df, randData)
+	go doRead(t, df, randData, &wg)
+	af := <-flows
+	go doRead(t, af, randData, &wg)
+	go doWrite(t, af, randData)
+	wg.Wait()
+}
diff --git a/runtime/internal/flow/conn/errors.vdl b/runtime/internal/flow/conn/errors.vdl
new file mode 100644
index 0000000..7cb60ae
--- /dev/null
+++ b/runtime/internal/flow/conn/errors.vdl
@@ -0,0 +1,27 @@
+// 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 conn
+
+// These messages are constructed so as to avoid embedding a component/method name
+// and are thus more suitable for inclusion in other verrors.
+// This practice of omitting {1}{2} should be used throughout the flow implementations
+// since all of their errors are intended to be used as arguments to higher level errors.
+// TODO(suharshs,toddw): Allow skipping of {1}{2} in vdl generated errors.
+error (
+  MissingSetupOption(option string) {
+    "en": "missing required setup option{:option}."}
+  UnexpectedMsg(typ string) {"en": "unexpected message type{:typ}."}
+  ConnectionClosed() {"en": "connection closed."}
+  ConnClosedRemotely(msg string) {"en": "connection closed remotely{:msg}."}
+  FlowClosedRemotely() {"en": "flow closed remotely."}
+  Send(typ, dest string, err error) {"en": "failure sending {typ} message to {dest}{:err}."}
+  Recv(src string, err error) {"en": "error reading from {src}{:err}"}
+  CounterOverflow() {"en": "A remote process has sent more data than allowed."}
+  BlessingsFlowClosed() {"en": "The blessings flow was closed."}
+  InvalidChannelBinding() {"en": "The channel binding was invalid."}
+  NoPublicKey() {"en": "No public key was received by the remote end."}
+  DialingNonServer() {"en": "You are attempting to dial on a connection with no remote server."}
+  AcceptorBlessingsMissing() {"en": "The acceptor did not send blessings."}
+)
diff --git a/runtime/internal/flow/conn/errors.vdl.go b/runtime/internal/flow/conn/errors.vdl.go
new file mode 100644
index 0000000..16e1438
--- /dev/null
+++ b/runtime/internal/flow/conn/errors.vdl.go
@@ -0,0 +1,112 @@
+// 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 by the vanadium vdl tool.
+// Source: errors.vdl
+
+package conn
+
+import (
+	// VDL system imports
+	"v.io/v23/context"
+	"v.io/v23/i18n"
+	"v.io/v23/verror"
+)
+
+var (
+	ErrMissingSetupOption       = verror.Register("v.io/x/ref/runtime/internal/flow/conn.MissingSetupOption", verror.NoRetry, "{1:}{2:} missing required setup option{:3}.")
+	ErrUnexpectedMsg            = verror.Register("v.io/x/ref/runtime/internal/flow/conn.UnexpectedMsg", verror.NoRetry, "{1:}{2:} unexpected message type{:3}.")
+	ErrConnectionClosed         = verror.Register("v.io/x/ref/runtime/internal/flow/conn.ConnectionClosed", verror.NoRetry, "{1:}{2:} connection closed.")
+	ErrConnClosedRemotely       = verror.Register("v.io/x/ref/runtime/internal/flow/conn.ConnClosedRemotely", verror.NoRetry, "{1:}{2:} connection closed remotely{:3}.")
+	ErrFlowClosedRemotely       = verror.Register("v.io/x/ref/runtime/internal/flow/conn.FlowClosedRemotely", verror.NoRetry, "{1:}{2:} flow closed remotely.")
+	ErrSend                     = verror.Register("v.io/x/ref/runtime/internal/flow/conn.Send", verror.NoRetry, "{1:}{2:} failure sending {3} message to {4}{:5}.")
+	ErrRecv                     = verror.Register("v.io/x/ref/runtime/internal/flow/conn.Recv", verror.NoRetry, "{1:}{2:} error reading from {3}{:4}")
+	ErrCounterOverflow          = verror.Register("v.io/x/ref/runtime/internal/flow/conn.CounterOverflow", verror.NoRetry, "{1:}{2:} A remote process has sent more data than allowed.")
+	ErrBlessingsFlowClosed      = verror.Register("v.io/x/ref/runtime/internal/flow/conn.BlessingsFlowClosed", verror.NoRetry, "{1:}{2:} The blessings flow was closed.")
+	ErrInvalidChannelBinding    = verror.Register("v.io/x/ref/runtime/internal/flow/conn.InvalidChannelBinding", verror.NoRetry, "{1:}{2:} The channel binding was invalid.")
+	ErrNoPublicKey              = verror.Register("v.io/x/ref/runtime/internal/flow/conn.NoPublicKey", verror.NoRetry, "{1:}{2:} No public key was received by the remote end.")
+	ErrDialingNonServer         = verror.Register("v.io/x/ref/runtime/internal/flow/conn.DialingNonServer", verror.NoRetry, "{1:}{2:} You are attempting to dial on a connection with no remote server.")
+	ErrAcceptorBlessingsMissing = verror.Register("v.io/x/ref/runtime/internal/flow/conn.AcceptorBlessingsMissing", verror.NoRetry, "{1:}{2:} The acceptor did not send blessings.")
+)
+
+func init() {
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrMissingSetupOption.ID), "{1:}{2:} missing required setup option{:3}.")
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrUnexpectedMsg.ID), "{1:}{2:} unexpected message type{:3}.")
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrConnectionClosed.ID), "{1:}{2:} connection closed.")
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrConnClosedRemotely.ID), "{1:}{2:} connection closed remotely{:3}.")
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrFlowClosedRemotely.ID), "{1:}{2:} flow closed remotely.")
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrSend.ID), "{1:}{2:} failure sending {3} message to {4}{:5}.")
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrRecv.ID), "{1:}{2:} error reading from {3}{:4}")
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrCounterOverflow.ID), "{1:}{2:} A remote process has sent more data than allowed.")
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrBlessingsFlowClosed.ID), "{1:}{2:} The blessings flow was closed.")
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrInvalidChannelBinding.ID), "{1:}{2:} The channel binding was invalid.")
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrNoPublicKey.ID), "{1:}{2:} No public key was received by the remote end.")
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrDialingNonServer.ID), "{1:}{2:} You are attempting to dial on a connection with no remote server.")
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrAcceptorBlessingsMissing.ID), "{1:}{2:} The acceptor did not send blessings.")
+}
+
+// NewErrMissingSetupOption returns an error with the ErrMissingSetupOption ID.
+func NewErrMissingSetupOption(ctx *context.T, option string) error {
+	return verror.New(ErrMissingSetupOption, ctx, option)
+}
+
+// NewErrUnexpectedMsg returns an error with the ErrUnexpectedMsg ID.
+func NewErrUnexpectedMsg(ctx *context.T, typ string) error {
+	return verror.New(ErrUnexpectedMsg, ctx, typ)
+}
+
+// NewErrConnectionClosed returns an error with the ErrConnectionClosed ID.
+func NewErrConnectionClosed(ctx *context.T) error {
+	return verror.New(ErrConnectionClosed, ctx)
+}
+
+// NewErrConnClosedRemotely returns an error with the ErrConnClosedRemotely ID.
+func NewErrConnClosedRemotely(ctx *context.T, msg string) error {
+	return verror.New(ErrConnClosedRemotely, ctx, msg)
+}
+
+// NewErrFlowClosedRemotely returns an error with the ErrFlowClosedRemotely ID.
+func NewErrFlowClosedRemotely(ctx *context.T) error {
+	return verror.New(ErrFlowClosedRemotely, ctx)
+}
+
+// NewErrSend returns an error with the ErrSend ID.
+func NewErrSend(ctx *context.T, typ string, dest string, err error) error {
+	return verror.New(ErrSend, ctx, typ, dest, err)
+}
+
+// NewErrRecv returns an error with the ErrRecv ID.
+func NewErrRecv(ctx *context.T, src string, err error) error {
+	return verror.New(ErrRecv, ctx, src, err)
+}
+
+// NewErrCounterOverflow returns an error with the ErrCounterOverflow ID.
+func NewErrCounterOverflow(ctx *context.T) error {
+	return verror.New(ErrCounterOverflow, ctx)
+}
+
+// NewErrBlessingsFlowClosed returns an error with the ErrBlessingsFlowClosed ID.
+func NewErrBlessingsFlowClosed(ctx *context.T) error {
+	return verror.New(ErrBlessingsFlowClosed, ctx)
+}
+
+// NewErrInvalidChannelBinding returns an error with the ErrInvalidChannelBinding ID.
+func NewErrInvalidChannelBinding(ctx *context.T) error {
+	return verror.New(ErrInvalidChannelBinding, ctx)
+}
+
+// NewErrNoPublicKey returns an error with the ErrNoPublicKey ID.
+func NewErrNoPublicKey(ctx *context.T) error {
+	return verror.New(ErrNoPublicKey, ctx)
+}
+
+// NewErrDialingNonServer returns an error with the ErrDialingNonServer ID.
+func NewErrDialingNonServer(ctx *context.T) error {
+	return verror.New(ErrDialingNonServer, ctx)
+}
+
+// NewErrAcceptorBlessingsMissing returns an error with the ErrAcceptorBlessingsMissing ID.
+func NewErrAcceptorBlessingsMissing(ctx *context.T) error {
+	return verror.New(ErrAcceptorBlessingsMissing, ctx)
+}
diff --git a/runtime/internal/flow/conn/flow.go b/runtime/internal/flow/conn/flow.go
new file mode 100644
index 0000000..a3207dc
--- /dev/null
+++ b/runtime/internal/flow/conn/flow.go
@@ -0,0 +1,285 @@
+// 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 conn
+
+import (
+	"strconv"
+
+	"v.io/v23/context"
+	"v.io/v23/flow"
+	"v.io/v23/flow/message"
+	"v.io/v23/security"
+	"v.io/v23/verror"
+	"v.io/x/ref/runtime/internal/flow/flowcontrol"
+)
+
+type flw struct {
+	id         uint64
+	dialed     bool
+	ctx        *context.T
+	cancel     context.CancelFunc
+	conn       *Conn
+	worker     *flowcontrol.Worker
+	opened     bool
+	q          *readq
+	bkey, dkey uint64
+}
+
+// Ensure that *flw implements flow.Flow.
+var _ flow.Flow = &flw{}
+
+func (c *Conn) newFlowLocked(ctx *context.T, id uint64, bkey, dkey uint64, dialed, preopen bool) *flw {
+	f := &flw{
+		id:     id,
+		dialed: dialed,
+		conn:   c,
+		worker: c.fc.NewWorker(strconv.FormatUint(uint64(id), 10), flowPriority),
+		q:      newReadQ(),
+		bkey:   bkey,
+		dkey:   dkey,
+		opened: preopen,
+	}
+	f.SetContext(ctx)
+	c.flows[id] = f
+	return f
+}
+
+// Implement io.Reader.
+// Read and ReadMsg should not be called concurrently with themselves
+// or each other.
+func (f *flw) Read(p []byte) (n int, err error) {
+	f.conn.markUsed()
+	var release bool
+	if n, release, err = f.q.read(f.ctx, p); release {
+		f.conn.release(f.ctx)
+	}
+	if err != nil {
+		f.close(f.ctx, err)
+	}
+	return
+}
+
+// ReadMsg is like read, but it reads bytes in chunks.  Depending on the
+// implementation the batch boundaries might or might not be significant.
+// Read and ReadMsg should not be called concurrently with themselves
+// or each other.
+func (f *flw) ReadMsg() (buf []byte, err error) {
+	f.conn.markUsed()
+	var release bool
+	// TODO(mattr): Currently we only ever release counters when some flow
+	// reads.  We may need to do it more or less often.  Currently
+	// we'll send counters whenever a new flow is opened.
+	if buf, release, err = f.q.get(f.ctx); release {
+		f.conn.release(f.ctx)
+	}
+	if err != nil {
+		f.close(f.ctx, err)
+	}
+	return
+}
+
+// Implement io.Writer.
+// Write, WriteMsg, and WriteMsgAndClose should not be called concurrently
+// with themselves or each other.
+func (f *flw) Write(p []byte) (n int, err error) {
+	return f.WriteMsg(p)
+}
+
+func (f *flw) writeMsg(alsoClose bool, parts ...[]byte) (int, error) {
+	f.conn.markUsed()
+	sent := 0
+	var left []byte
+	err := f.worker.Run(f.ctx, func(tokens int) (int, bool, error) {
+		if !f.opened {
+			// TODO(mattr): we should be able to send multiple messages
+			// in a single writeMsg call.
+			err := f.conn.mp.writeMsg(f.ctx, &message.OpenFlow{
+				ID:              f.id,
+				InitialCounters: defaultBufferSize,
+				BlessingsKey:    f.bkey,
+				DischargeKey:    f.dkey,
+			})
+			if err != nil {
+				return 0, false, err
+			}
+			f.opened = true
+		}
+		size := 0
+		var bufs [][]byte
+		if len(left) > 0 {
+			size += len(left)
+			bufs = append(bufs, left)
+			left = nil
+		}
+		for size <= tokens && len(parts) > 0 {
+			bufs = append(bufs, parts[0])
+			size += len(parts[0])
+			parts = parts[1:]
+		}
+		if size > tokens {
+			lidx := len(bufs) - 1
+			last := bufs[lidx]
+			take := len(last) - (size - tokens)
+			bufs[lidx] = last[:take]
+			left = last[take:]
+			size = tokens
+		}
+		d := &message.Data{
+			ID:      f.id,
+			Payload: bufs,
+		}
+		done := len(left) == 0 && len(parts) == 0
+		if alsoClose && done {
+			d.Flags |= message.CloseFlag
+		}
+		sent += size
+		return size, done, f.conn.mp.writeMsg(f.ctx, d)
+	})
+	if alsoClose || err != nil {
+		f.close(f.ctx, err)
+	}
+	return sent, err
+}
+
+// WriteMsg is like Write, but allows writing more than one buffer at a time.
+// The data in each buffer is written sequentially onto the flow.  Returns the
+// number of bytes written.  WriteMsg must return a non-nil error if it writes
+// less than the total number of bytes from all buffers.
+// Write, WriteMsg, and WriteMsgAndClose should not be called concurrently
+// with themselves or each other.
+func (f *flw) WriteMsg(parts ...[]byte) (int, error) {
+	return f.writeMsg(false, parts...)
+}
+
+// WriteMsgAndClose performs WriteMsg and then closes the flow.
+// Write, WriteMsg, and WriteMsgAndClose should not be called concurrently
+// with themselves or each other.
+func (f *flw) WriteMsgAndClose(parts ...[]byte) (int, error) {
+	return f.writeMsg(true, parts...)
+}
+
+// SetContext sets the context associated with the flow.  Typically this is
+// used to set state that is only available after the flow is connected, such
+// as a more restricted flow timeout, or the language of the request.
+// Calling SetContext may invalidate values previously returned from Closed.
+//
+// The flow.Manager associated with ctx must be the same flow.Manager that the
+// flow was dialed or accepted from, otherwise an error is returned.
+// TODO(mattr): enforce this restriction.
+//
+// TODO(mattr): update v23/flow documentation.
+// SetContext may not be called concurrently with other methods.
+func (f *flw) SetContext(ctx *context.T) error {
+	if f.cancel != nil {
+		f.cancel()
+	}
+	f.ctx, f.cancel = context.WithCancel(ctx)
+	return nil
+}
+
+// LocalBlessings returns the blessings presented by the local end of the flow
+// during authentication.
+func (f *flw) LocalBlessings() security.Blessings {
+	if f.dialed {
+		blessings, _, err := f.conn.blessingsFlow.get(f.ctx, f.bkey, f.dkey)
+		if err != nil {
+			f.conn.Close(f.ctx, err)
+		}
+		return blessings
+	}
+	return f.conn.lBlessings
+}
+
+// RemoteBlessings returns the blessings presented by the remote end of the
+// flow during authentication.
+func (f *flw) RemoteBlessings() security.Blessings {
+	if !f.dialed {
+		blessings, _, err := f.conn.blessingsFlow.get(f.ctx, f.bkey, f.dkey)
+		if err != nil {
+			f.conn.Close(f.ctx, err)
+		}
+		return blessings
+	}
+	return f.conn.rBlessings
+}
+
+// LocalDischarges returns the discharges presented by the local end of the
+// flow during authentication.
+//
+// Discharges are organized in a map keyed by the discharge-identifier.
+func (f *flw) LocalDischarges() map[string]security.Discharge {
+	var discharges map[string]security.Discharge
+	var err error
+	if f.dialed {
+		_, discharges, err = f.conn.blessingsFlow.get(f.ctx, f.bkey, f.dkey)
+	} else {
+		discharges, err = f.conn.blessingsFlow.getLatestDischarges(f.ctx, f.conn.lBlessings)
+	}
+	if err != nil {
+		f.conn.Close(f.ctx, err)
+	}
+	return discharges
+}
+
+// RemoteDischarges returns the discharges presented by the remote end of the
+// flow during authentication.
+//
+// Discharges are organized in a map keyed by the discharge-identifier.
+func (f *flw) RemoteDischarges() map[string]security.Discharge {
+	var discharges map[string]security.Discharge
+	var err error
+	if !f.dialed {
+		_, discharges, err = f.conn.blessingsFlow.get(f.ctx, f.bkey, f.dkey)
+	} else {
+		discharges, err = f.conn.blessingsFlow.getLatestDischarges(f.ctx, f.conn.rBlessings)
+	}
+	if err != nil {
+		f.conn.Close(f.ctx, err)
+	}
+	return discharges
+}
+
+// Conn returns the connection the flow is multiplexed on.
+func (f *flw) Conn() flow.ManagedConn {
+	return f.conn
+}
+
+// Closed returns a channel that remains open until the flow has been closed remotely
+// or the context attached to the flow has been canceled.
+//
+// Note that after the returned channel is closed starting new writes will result
+// in an error, but reads of previously queued data are still possible.  No
+// new data will be queued.
+// TODO(mattr): update v23/flow docs.
+func (f *flw) Closed() <-chan struct{} {
+	return f.ctx.Done()
+}
+
+func (f *flw) close(ctx *context.T, err error) {
+	f.q.close(ctx)
+	f.cancel()
+	if eid := verror.ErrorID(err); eid != ErrFlowClosedRemotely.ID &&
+		eid != ErrConnectionClosed.ID {
+		// We want to try to send this message even if ctx is already canceled.
+		ctx, cancel := context.WithRootCancel(ctx)
+		err := f.worker.Run(ctx, func(tokens int) (int, bool, error) {
+			return 0, true, f.conn.mp.writeMsg(ctx, &message.Data{
+				ID:    f.id,
+				Flags: message.CloseFlag,
+			})
+		})
+		if err != nil {
+			ctx.Errorf("Could not send close flow message: %v", err)
+		}
+		cancel()
+	}
+}
+
+// Close marks the flow as closed. After Close is called, new data cannot be
+// written on the flow. Reads of already queued data are still possible.
+func (f *flw) Close() error {
+	f.close(f.ctx, nil)
+	return nil
+}
diff --git a/runtime/internal/flow/conn/message.go b/runtime/internal/flow/conn/message.go
new file mode 100644
index 0000000..dc59431
--- /dev/null
+++ b/runtime/internal/flow/conn/message.go
@@ -0,0 +1,78 @@
+// 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 conn
+
+import (
+	"v.io/v23/context"
+	"v.io/v23/flow"
+	"v.io/v23/flow/message"
+	"v.io/x/ref/runtime/internal/rpc/stream/crypto"
+)
+
+// TODO(mattr): Consider cleaning up the ControlCipher library to
+// eliminate extraneous functionality and reduce copying.
+type messagePipe struct {
+	rw       flow.MsgReadWriteCloser
+	cipher   crypto.ControlCipher
+	writeBuf []byte
+}
+
+func newMessagePipe(rw flow.MsgReadWriteCloser) *messagePipe {
+	return &messagePipe{
+		rw:       rw,
+		writeBuf: make([]byte, mtu),
+		cipher:   &crypto.NullControlCipher{},
+	}
+}
+
+func (p *messagePipe) setupEncryption(ctx *context.T, pk, sk, opk *[32]byte) []byte {
+	p.cipher = crypto.NewControlCipherRPC11(
+		(*crypto.BoxKey)(pk),
+		(*crypto.BoxKey)(sk),
+		(*crypto.BoxKey)(opk))
+	return p.cipher.ChannelBinding()
+}
+
+func (p *messagePipe) close() error {
+	return p.rw.Close()
+}
+
+func (p *messagePipe) writeMsg(ctx *context.T, m message.Message) (err error) {
+	// TODO(mattr): Because of the API of the underlying crypto library,
+	// an enormous amount of copying happens here.
+	// TODO(mattr): We allocate many buffers here to hold potentially
+	// many copies of the data.  The maximum memory usage per Conn is probably
+	// quite high.  We should try to reduce it.
+	if p.writeBuf, err = message.Append(ctx, m, p.writeBuf[:0]); err != nil {
+		return err
+	}
+	if needed := len(p.writeBuf) + p.cipher.MACSize(); cap(p.writeBuf) < needed {
+		tmp := make([]byte, needed)
+		copy(tmp, p.writeBuf)
+		p.writeBuf = tmp
+	} else {
+		p.writeBuf = p.writeBuf[:needed]
+	}
+	if err = p.cipher.Seal(p.writeBuf); err != nil {
+		return err
+	}
+	if _, err = p.rw.WriteMsg(p.writeBuf); err == nil {
+		ctx.VI(2).Infof("Wrote low-level message: %#v", m)
+	}
+	return err
+}
+
+func (p *messagePipe) readMsg(ctx *context.T) (message.Message, error) {
+	msg, err := p.rw.ReadMsg()
+	if err != nil {
+		return nil, err
+	}
+	if !p.cipher.Open(msg) {
+		return nil, message.NewErrInvalidMsg(ctx, 0, uint64(len(msg)), 0, nil)
+	}
+	m, err := message.Read(ctx, msg[:len(msg)-p.cipher.MACSize()])
+	ctx.VI(2).Infof("Read low-level message: %#v", m)
+	return m, nil
+}
diff --git a/runtime/internal/flow/conn/readq.go b/runtime/internal/flow/conn/readq.go
new file mode 100644
index 0000000..cd61bc7
--- /dev/null
+++ b/runtime/internal/flow/conn/readq.go
@@ -0,0 +1,153 @@
+// 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 conn
+
+import (
+	"io"
+	"sync"
+
+	"v.io/v23/context"
+)
+
+type readq struct {
+	mu   sync.Mutex
+	bufs [][]byte
+	b, e int
+
+	size      int
+	nbufs     int
+	toRelease int
+	notify    chan struct{}
+}
+
+const initialReadqBufferSize = 10
+
+func newReadQ() *readq {
+	return &readq{
+		bufs:      make([][]byte, initialReadqBufferSize),
+		notify:    make(chan struct{}, 1),
+		toRelease: defaultBufferSize,
+	}
+}
+
+func (r *readq) put(ctx *context.T, bufs [][]byte) error {
+	l := 0
+	for _, b := range bufs {
+		l += len(b)
+	}
+	if l == 0 {
+		return nil
+	}
+
+	defer r.mu.Unlock()
+	r.mu.Lock()
+	if r.e == -1 {
+		// The flow has already closed.  Simply drop the data.
+		return nil
+	}
+	newSize := l + r.size
+	if newSize > defaultBufferSize {
+		return NewErrCounterOverflow(ctx)
+	}
+	newBufs := r.nbufs + len(bufs)
+	r.reserveLocked(newBufs)
+	for _, b := range bufs {
+		r.bufs[r.e] = b
+		r.e = (r.e + 1) % len(r.bufs)
+	}
+	r.nbufs = newBufs
+	if r.size == 0 {
+		select {
+		case r.notify <- struct{}{}:
+		default:
+		}
+	}
+	r.size = newSize
+	return nil
+}
+
+func (r *readq) read(ctx *context.T, data []byte) (n int, release bool, err error) {
+	defer r.mu.Unlock()
+	r.mu.Lock()
+	if err := r.waitLocked(ctx); err != nil {
+		return 0, false, err
+	}
+	buf := r.bufs[r.b]
+	n = copy(data, buf)
+	buf = buf[n:]
+	if len(buf) > 0 {
+		r.bufs[r.b] = buf
+	} else {
+		r.nbufs -= 1
+		r.b = (r.b + 1) % len(r.bufs)
+	}
+	r.size -= n
+	r.toRelease += n
+	return n, r.toRelease > defaultBufferSize/2, nil
+}
+
+func (r *readq) get(ctx *context.T) (out []byte, release bool, err error) {
+	defer r.mu.Unlock()
+	r.mu.Lock()
+	if err := r.waitLocked(ctx); err != nil {
+		return nil, false, err
+	}
+	out = r.bufs[r.b]
+	r.b = (r.b + 1) % len(r.bufs)
+	r.size -= len(out)
+	r.nbufs -= 1
+	r.toRelease += len(out)
+	return out, r.toRelease > defaultBufferSize/2, nil
+}
+
+func (r *readq) waitLocked(ctx *context.T) (err error) {
+	for r.size == 0 && err == nil {
+		r.mu.Unlock()
+		select {
+		case _, ok := <-r.notify:
+			if !ok {
+				err = io.EOF
+			}
+		case <-ctx.Done():
+			if r.size == 0 {
+				err = ctx.Err()
+			}
+		}
+		r.mu.Lock()
+	}
+	return
+}
+
+func (r *readq) close(ctx *context.T) {
+	r.mu.Lock()
+	if r.e != -1 {
+		r.e = -1
+		r.toRelease = 0
+		close(r.notify)
+	}
+	r.mu.Unlock()
+}
+
+func (r *readq) reserveLocked(n int) {
+	if n < len(r.bufs) {
+		return
+	}
+	nb := make([][]byte, 2*n)
+	copied := 0
+	if r.e >= r.b {
+		copied = copy(nb, r.bufs[r.b:r.e])
+	} else {
+		copied = copy(nb, r.bufs[r.b:])
+		copied += copy(nb[copied:], r.bufs[:r.e])
+	}
+	r.bufs, r.b, r.e = nb, 0, copied
+}
+
+func (r *readq) release() (out int) {
+	r.mu.Lock()
+	out, r.toRelease = r.toRelease, 0
+	r.mu.Unlock()
+	return out
+}
diff --git a/runtime/internal/flow/conn/readq_test.go b/runtime/internal/flow/conn/readq_test.go
new file mode 100644
index 0000000..baea620
--- /dev/null
+++ b/runtime/internal/flow/conn/readq_test.go
@@ -0,0 +1,116 @@
+// 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 conn
+
+import (
+	"io"
+	"testing"
+
+	"v.io/v23"
+	"v.io/x/ref/test/goroutines"
+)
+
+func mkBufs(in ...string) [][]byte {
+	out := make([][]byte, len(in))
+	for i, s := range in {
+		out[i] = []byte(s)
+	}
+	return out
+}
+
+func TestReadqRead(t *testing.T) {
+	defer goroutines.NoLeaks(t, 0)()
+
+	ctx, shutdown := v23.Init()
+	defer shutdown()
+
+	r := newReadQ()
+	r.put(ctx, mkBufs("one", "two"))
+	r.put(ctx, mkBufs("thre", "reallong"))
+	r.close(ctx)
+
+	read := make([]byte, 4)
+	want := []string{"one", "two", "thre", "real", "long"}
+	for _, w := range want {
+		n, _, err := r.read(ctx, read)
+		if err != nil {
+			t.Fatalf("unexpected error: %v", err)
+		}
+		if got := string(read[:n]); got != w {
+			t.Errorf("got: %s, want %s", got, w)
+		}
+	}
+	if _, _, err := r.read(ctx, read); err != io.EOF {
+		t.Errorf("expected EOF got %v", err)
+	}
+}
+
+func TestReadqGet(t *testing.T) {
+	defer goroutines.NoLeaks(t, 0)()
+
+	ctx, shutdown := v23.Init()
+	defer shutdown()
+
+	r := newReadQ()
+	r.put(ctx, mkBufs("one", "two"))
+	r.put(ctx, mkBufs("thre", "reallong"))
+	r.close(ctx)
+
+	want := []string{"one", "two", "thre", "reallong"}
+	for _, w := range want {
+		out, _, err := r.get(ctx)
+		if err != nil {
+			t.Fatalf("unexpected error: %v", err)
+		}
+		if got := string(out); got != w {
+			t.Errorf("got: %s, want %s", got, w)
+		}
+	}
+	if _, _, err := r.get(ctx); err != io.EOF {
+		t.Errorf("expected EOF got %v", err)
+	}
+}
+
+func TestReadqMixed(t *testing.T) {
+	defer goroutines.NoLeaks(t, 0)()
+
+	ctx, shutdown := v23.Init()
+	defer shutdown()
+
+	r := newReadQ()
+	r.put(ctx, mkBufs("one", "two"))
+	r.put(ctx, mkBufs("thre", "reallong"))
+	r.close(ctx)
+
+	want := []string{"one", "two", "thre", "real", "long"}
+	for i, w := range want {
+		var (
+			err  error
+			got  string
+			n    int
+			out  []byte
+			read = make([]byte, 4)
+		)
+		if i%2 == 0 {
+			out, _, err = r.get(ctx)
+			got = string(out)
+		} else {
+			n, _, err = r.read(ctx, read)
+			got = string(read[:n])
+		}
+		if err != nil {
+			t.Fatalf("unexpected error: %v", err)
+		}
+		if got != w {
+			t.Errorf("got: %s, want %s", got, w)
+		}
+	}
+	if _, _, err := r.get(ctx); err != io.EOF {
+		t.Errorf("expected EOF got %v", err)
+	}
+	if _, _, err := r.read(ctx, nil); err != io.EOF {
+		t.Errorf("expected EOF got %v", err)
+	}
+}
diff --git a/runtime/internal/flow/conn/types.vdl b/runtime/internal/flow/conn/types.vdl
new file mode 100644
index 0000000..de776fa
--- /dev/null
+++ b/runtime/internal/flow/conn/types.vdl
@@ -0,0 +1,18 @@
+// 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 conn
+
+import "v.io/v23/security"
+
+// Blessings is used to transport blessings and their discharges
+// between the two ends of a Conn.  Since these objects can be large
+// we try not to send them more than once, therefore whenever we send
+// new blessings or discharges we associate them with an integer
+// key (BKey and DKey).  Thereafter we refer to them by their key.
+type Blessings struct {
+     Blessings  security.WireBlessings
+     Discharges []security.WireDischarge
+     BKey, DKey uint64
+}
\ No newline at end of file
diff --git a/runtime/internal/flow/conn/types.vdl.go b/runtime/internal/flow/conn/types.vdl.go
new file mode 100644
index 0000000..dc4bcd9
--- /dev/null
+++ b/runtime/internal/flow/conn/types.vdl.go
@@ -0,0 +1,37 @@
+// 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 by the vanadium vdl tool.
+// Source: types.vdl
+
+package conn
+
+import (
+	// VDL system imports
+	"v.io/v23/vdl"
+
+	// VDL user imports
+	"v.io/v23/security"
+)
+
+// Blessings is used to transport blessings and their discharges
+// between the two ends of a Conn.  Since these objects can be large
+// we try not to send them more than once, therefore whenever we send
+// new blessings or discharges we associate them with an integer
+// key (BKey and DKey).  Thereafter we refer to them by their key.
+type Blessings struct {
+	Blessings  security.Blessings
+	Discharges []security.Discharge
+	BKey       uint64
+	DKey       uint64
+}
+
+func (Blessings) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/runtime/internal/flow/conn.Blessings"`
+}) {
+}
+
+func init() {
+	vdl.Register((*Blessings)(nil))
+}
diff --git a/runtime/internal/flow/conn/util_test.go b/runtime/internal/flow/conn/util_test.go
new file mode 100644
index 0000000..dbb3537
--- /dev/null
+++ b/runtime/internal/flow/conn/util_test.go
@@ -0,0 +1,100 @@
+// 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 conn
+
+import (
+	"testing"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/flow"
+	"v.io/v23/naming"
+	"v.io/v23/rpc/version"
+	"v.io/v23/security"
+	securitylib "v.io/x/ref/lib/security"
+	"v.io/x/ref/runtime/internal/flow/flowtest"
+)
+
+type fh chan<- flow.Flow
+
+func (fh fh) HandleFlow(f flow.Flow) error {
+	if fh == nil {
+		panic("writing to nil flow handler")
+	}
+	fh <- f
+	return nil
+}
+
+func setupConns(t *testing.T, dctx, actx *context.T, dflows, aflows chan<- flow.Flow) (dialed, accepted *Conn, _ *flowtest.Wire) {
+	dmrw, amrw, w := flowtest.NewMRWPair(dctx)
+	versions := version.RPCVersionRange{Min: 3, Max: 5}
+	ep, err := v23.NewEndpoint("localhost:80")
+	if err != nil {
+		t.Fatalf("Unexpected error: %v", err)
+	}
+	dch := make(chan *Conn)
+	ach := make(chan *Conn)
+	go func() {
+		var handler FlowHandler
+		if dflows != nil {
+			handler = fh(dflows)
+		}
+		d, err := NewDialed(dctx, dmrw, ep, ep, versions, handler)
+		if err != nil {
+			panic(err)
+		}
+		dch <- d
+	}()
+	go func() {
+		var handler FlowHandler
+		if aflows != nil {
+			handler = fh(aflows)
+		}
+		a, err := NewAccepted(actx, amrw, ep, versions, handler)
+		if err != nil {
+			panic(err)
+		}
+		ach <- a
+	}()
+	return <-dch, <-ach, w
+}
+
+func setupFlow(t *testing.T, dctx, actx *context.T, dialFromDialer bool) (dialed flow.Flow, accepted <-chan flow.Flow, close func()) {
+	dflows, aflows := make(chan flow.Flow, 1), make(chan flow.Flow, 1)
+	d, a, _ := setupConns(t, dctx, actx, dflows, aflows)
+	if !dialFromDialer {
+		d, a = a, d
+		dctx, actx = actx, dctx
+		aflows, dflows = dflows, aflows
+	}
+	df, err := d.Dial(dctx, testBFP)
+	if err != nil {
+		t.Fatalf("Unexpected error: %v", err)
+	}
+	return df, aflows, func() { d.Close(dctx, nil); a.Close(actx, nil) }
+}
+
+func testBFP(
+	ctx *context.T,
+	localEndpoint, remoteEndpoint naming.Endpoint,
+	remoteBlessings security.Blessings,
+	remoteDischarges map[string]security.Discharge,
+) (security.Blessings, map[string]security.Discharge, error) {
+	return v23.GetPrincipal(ctx).BlessingStore().Default(), nil, nil
+}
+
+func makeBFP(in security.Blessings) flow.BlessingsForPeer {
+	return func(
+		ctx *context.T,
+		localEndpoint, remoteEndpoint naming.Endpoint,
+		remoteBlessings security.Blessings,
+		remoteDischarges map[string]security.Discharge,
+	) (security.Blessings, map[string]security.Discharge, error) {
+		dis := securitylib.PrepareDischarges(
+			ctx, in, security.DischargeImpetus{}, time.Minute)
+		return in, dis, nil
+	}
+}
diff --git a/runtime/internal/flow/flowcontrol/errors.vdl b/runtime/internal/flow/flowcontrol/errors.vdl
new file mode 100644
index 0000000..ddcab17
--- /dev/null
+++ b/runtime/internal/flow/flowcontrol/errors.vdl
@@ -0,0 +1,15 @@
+// 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 flowcontrol
+
+// These messages are constructed so as to avoid embedding a component/method name
+// and are thus more suitable for inclusion in other verrors.
+// This practice of omitting {1}{2} should be used throughout the flow implementations
+// since all of their errors are intended to be used as arguments to higher level errors.
+// TODO(suharshs,toddw): Allow skipping of {1}{2} in vdl generated errors.
+error (
+  ConcurrentRun() {"en": "Run called concurrently"}
+  WrongFlowController() {"en": "Release called for worker from different flow controller"}
+)
\ No newline at end of file
diff --git a/runtime/internal/flow/flowcontrol/errors.vdl.go b/runtime/internal/flow/flowcontrol/errors.vdl.go
new file mode 100644
index 0000000..5876a41
--- /dev/null
+++ b/runtime/internal/flow/flowcontrol/errors.vdl.go
@@ -0,0 +1,35 @@
+// 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 by the vanadium vdl tool.
+// Source: errors.vdl
+
+package flowcontrol
+
+import (
+	// VDL system imports
+	"v.io/v23/context"
+	"v.io/v23/i18n"
+	"v.io/v23/verror"
+)
+
+var (
+	ErrConcurrentRun       = verror.Register("v.io/x/ref/runtime/internal/flow/flowcontrol.ConcurrentRun", verror.NoRetry, "{1:}{2:} Run called concurrently")
+	ErrWrongFlowController = verror.Register("v.io/x/ref/runtime/internal/flow/flowcontrol.WrongFlowController", verror.NoRetry, "{1:}{2:} Release called for worker from different flow controller")
+)
+
+func init() {
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrConcurrentRun.ID), "{1:}{2:} Run called concurrently")
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrWrongFlowController.ID), "{1:}{2:} Release called for worker from different flow controller")
+}
+
+// NewErrConcurrentRun returns an error with the ErrConcurrentRun ID.
+func NewErrConcurrentRun(ctx *context.T) error {
+	return verror.New(ErrConcurrentRun, ctx)
+}
+
+// NewErrWrongFlowController returns an error with the ErrWrongFlowController ID.
+func NewErrWrongFlowController(ctx *context.T) error {
+	return verror.New(ErrWrongFlowController, ctx)
+}
diff --git a/runtime/internal/flow/flowcontrol/flowcontrol.go b/runtime/internal/flow/flowcontrol/flowcontrol.go
new file mode 100644
index 0000000..a27d48f
--- /dev/null
+++ b/runtime/internal/flow/flowcontrol/flowcontrol.go
@@ -0,0 +1,356 @@
+// 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 flowcontrol
+
+import (
+	"bytes"
+	"fmt"
+	"sync"
+
+	"v.io/v23/context"
+)
+
+// Runners are called by Workers.  For a given flow controller
+// only one Runner will be running at a time.  tokens specifies
+// the number of tokens available for this call.  Implementors
+// should return the number of tokens used, whether they are done
+// with all their work, and any error encountered.
+// Runners will be called repeatedly within a single Run call until
+// either err != nil or done is true.
+type Runner func(tokens int) (used int, done bool, err error)
+
+type counterState struct {
+	// TODO(mattr): Add deficit if we allow multi-slice writes.
+	borrowed     int  // Number of tokens borrowed from the shared pool.
+	released     int  // Number of tokens available via our flow control counters.
+	everReleased bool // True if tokens have ever been released to this worker.
+}
+
+type state int
+
+const (
+	idle = state(iota)
+	running
+	active
+)
+
+// Worker represents a single flowcontrolled worker.
+// Workers keep track of flow control counters to ensure
+// producers do not overwhelm consumers.  Only one Worker
+// will be executing at a time.
+type Worker struct {
+	name     string
+	fc       *FlowController
+	priority int
+	work     chan struct{}
+
+	// These variables are protected by fc.mu.
+	counters   *counterState // State related to the flow control counters.
+	state      state
+	next, prev *Worker // Used as a list when in an active queue.
+}
+
+func (w *Worker) String() string {
+	return fmt.Sprintf("%s(%p)", w.name, w)
+}
+
+// Run runs r potentially multiple times.
+// Only one worker's r function will run at a time for a given FlowController.
+// A single worker's Run function should not be called concurrently from multiple
+// goroutines.
+func (w *Worker) Run(ctx *context.T, r Runner) (err error) {
+	w.fc.mu.Lock()
+	if w.state != idle {
+		w.fc.mu.Unlock()
+		return NewErrConcurrentRun(ctx)
+	}
+
+	w.state = running
+	if w.readyLocked() {
+		w.fc.activateLocked(w)
+		w.state = active
+	}
+
+	for {
+		next := w.fc.nextWorkerLocked()
+		if w.fc.writing == w {
+			// We're already scheduled to write, but we should bail
+			// out if we're canceled.
+			select {
+			case <-ctx.Done():
+				err = ctx.Err()
+			default:
+			}
+		}
+		for w.fc.writing != w && err == nil {
+			w.fc.mu.Unlock()
+			if next != nil {
+				next.notify()
+			}
+			ctx.VI(4).Infof("worker waiting: %s\nfc: %s", w, w.fc)
+			select {
+			case <-ctx.Done():
+				err = ctx.Err()
+			case <-w.work:
+			}
+			w.fc.mu.Lock()
+		}
+		if err != nil {
+			w.fc.writing = nil
+			break
+		}
+
+		toWrite := w.fc.mtu
+		if w.counters != nil {
+			if !w.counters.everReleased {
+				toWrite = min(w.fc.shared, w.fc.mtu)
+				w.counters.released += toWrite
+				w.counters.borrowed += toWrite
+				w.fc.shared -= toWrite
+			} else {
+				toWrite = min(w.counters.released, w.fc.mtu)
+			}
+		}
+
+		w.fc.mu.Unlock()
+		var written int
+		var done bool
+		written, done, err = r(toWrite)
+		w.fc.mu.Lock()
+
+		if w.counters != nil {
+			w.counters.released -= written
+			if w.counters.released > 0 && w.counters.borrowed > 0 {
+				toReturn := min(w.counters.released, w.counters.borrowed)
+				w.counters.borrowed -= toReturn
+				w.counters.released -= toReturn
+				w.fc.shared += toReturn
+			}
+		}
+
+		w.fc.writing = nil
+		if err != nil || done {
+			break
+		}
+		if !w.readyLocked() {
+			w.fc.deactivateLocked(w)
+			w.state = running
+		}
+	}
+
+	w.state = idle
+	w.fc.deactivateLocked(w)
+	next := w.fc.nextWorkerLocked()
+	w.fc.mu.Unlock()
+	if next != nil {
+		next.notify()
+	}
+	return err
+}
+
+func (w *Worker) releaseLocked(ctx *context.T, tokens int) {
+	if w.counters == nil {
+		return
+	}
+	w.counters.everReleased = true
+	if w.counters.borrowed > 0 {
+		n := min(w.counters.borrowed, tokens)
+		w.counters.borrowed -= n
+		w.fc.shared += n
+		tokens -= n
+	}
+	w.counters.released += tokens
+	if w.state == running && w.readyLocked() {
+		w.fc.activateLocked(w)
+	}
+}
+
+// Release releases tokens to this worker.
+// Workers will first repay any debts to the flow controllers shared pool
+// and use any surplus in subsequent calls to Run.
+func (w *Worker) Release(ctx *context.T, tokens int) {
+	w.fc.mu.Lock()
+	w.releaseLocked(ctx, tokens)
+	next := w.fc.nextWorkerLocked()
+	w.fc.mu.Unlock()
+	if next != nil {
+		next.notify()
+	}
+}
+
+func (w *Worker) readyLocked() bool {
+	if w.counters == nil {
+		return true
+	}
+	return w.counters.released > 0 || (!w.counters.everReleased && w.fc.shared > 0)
+}
+
+func (w *Worker) notify() {
+	select {
+	case w.work <- struct{}{}:
+	default:
+	}
+}
+
+// FlowController manages multiple Workers to ensure only one runs at a time.
+// The workers also obey counters so that producers don't overwhelm consumers.
+type FlowController struct {
+	mtu int
+
+	mu      sync.Mutex
+	shared  int
+	active  []*Worker
+	writing *Worker
+}
+
+// New creates a new FlowController.  Shared is the number of shared tokens
+// that flows can borrow from before they receive their first Release.
+// Mtu is the maximum number of tokens to be consumed by a single Runner
+// invocation.
+func New(shared, mtu int) *FlowController {
+	return &FlowController{shared: shared, mtu: mtu}
+}
+
+// NewWorker creates a new worker.  Workers keep track of token counters
+// for a flow controlled process.  The order that workers
+// execute is controlled by priority.  Higher priority
+// workers that are ready will run before any lower priority
+// workers.
+func (fc *FlowController) NewWorker(name string, priority int) *Worker {
+	w := &Worker{
+		name:     name,
+		fc:       fc,
+		priority: priority,
+		work:     make(chan struct{}, 1),
+		counters: &counterState{},
+	}
+	w.next, w.prev = w, w
+	return w
+}
+
+type Release struct {
+	Worker *Worker
+	Tokens int
+}
+
+// Release releases to many Workers atomically.  It is conceptually
+// the same as calling release on each worker indepedently.
+func (fc *FlowController) Release(ctx *context.T, to []Release) error {
+	fc.mu.Lock()
+	for _, t := range to {
+		if t.Worker.fc != fc {
+			return NewErrWrongFlowController(ctx)
+		}
+		t.Worker.releaseLocked(ctx, t.Tokens)
+	}
+	next := fc.nextWorkerLocked()
+	fc.mu.Unlock()
+	if next != nil {
+		next.notify()
+	}
+	return nil
+}
+
+// Run runs the given runner on a non-flow controlled Worker.  This
+// worker does not wait for any flow control tokens and is limited
+// only by the MTU.
+func (fc *FlowController) Run(ctx *context.T, name string, p int, r Runner) error {
+	w := &Worker{
+		name:     name,
+		fc:       fc,
+		priority: p,
+		work:     make(chan struct{}, 1),
+	}
+	w.next, w.prev = w, w
+	return w.Run(ctx, r)
+}
+
+func (fc *FlowController) nextWorkerLocked() *Worker {
+	if fc.writing == nil {
+		for p, head := range fc.active {
+			if head != nil {
+				fc.active[p] = head.next
+				fc.writing = head
+				return head
+			}
+		}
+	}
+	return nil
+}
+
+func (fc *FlowController) activateLocked(w *Worker) {
+	if w.priority >= len(fc.active) {
+		newActive := make([]*Worker, int(w.priority)+1)
+		copy(newActive, fc.active)
+		fc.active = newActive
+	}
+	head := fc.active[w.priority]
+	if head == nil {
+		fc.active[w.priority] = w
+	} else {
+		w.prev, w.next = head.prev, head
+		w.prev.next, w.next.prev = w, w
+	}
+}
+
+func (fc *FlowController) deactivateLocked(w *Worker) {
+	if head := fc.active[w.priority]; head == w {
+		if w.next == w {
+			fc.active[w.priority] = nil
+		} else {
+			fc.active[w.priority] = w.next
+		}
+	}
+	w.next.prev, w.prev.next = w.prev, w.next
+	w.next, w.prev = w, w
+}
+
+func (fc *FlowController) numActive() int {
+	n := 0
+	fc.mu.Lock()
+	for _, head := range fc.active {
+		if head != nil {
+			n++
+			for cur := head.next; cur != head; cur = cur.next {
+				n++
+			}
+		}
+	}
+	fc.mu.Unlock()
+	return n
+}
+
+// String writes a string representation of the flow controller.
+// This can be helpful in debugging.
+func (fc *FlowController) String() string {
+	buf := &bytes.Buffer{}
+	fmt.Fprintf(buf, "FlowController %p: \n", fc)
+
+	fc.mu.Lock()
+	if fc.writing != nil {
+		fmt.Fprintf(buf, "writing: %s\n", fc.writing)
+	}
+	fmt.Fprintln(buf, "active:")
+	for p, head := range fc.active {
+		fmt.Fprintf(buf, "  %v: %s", p, head)
+		if head != nil {
+			for cur := head.next; cur != head; cur = cur.next {
+				fmt.Fprintf(buf, " %s", cur)
+			}
+		}
+		fmt.Fprintln(buf, "")
+	}
+	fc.mu.Unlock()
+	return buf.String()
+}
+
+func min(head int, rest ...int) int {
+	for _, r := range rest {
+		if r < head {
+			head = r
+		}
+	}
+	return head
+}
diff --git a/runtime/internal/flow/flowcontrol/flowcontrol_test.go b/runtime/internal/flow/flowcontrol/flowcontrol_test.go
new file mode 100644
index 0000000..3d9a297
--- /dev/null
+++ b/runtime/internal/flow/flowcontrol/flowcontrol_test.go
@@ -0,0 +1,293 @@
+// 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 flowcontrol
+
+import (
+	"bytes"
+	"crypto/rand"
+	"fmt"
+	"io"
+	"net"
+	"sync"
+	"testing"
+	"time"
+
+	"v.io/v23/context"
+	"v.io/v23/verror"
+	"v.io/x/ref/test"
+)
+
+var testdata = make([]byte, 1<<20)
+
+func init() {
+	test.Init()
+	_, err := io.ReadFull(rand.Reader, testdata)
+	if err != nil {
+		panic(err)
+	}
+}
+
+func TestFlowControl(t *testing.T) {
+	const (
+		workers  = 10
+		messages = 10
+	)
+
+	msgs := make(map[int][]byte)
+	fc := New(256, 64)
+
+	ctx, cancel := context.RootContext()
+	defer cancel()
+
+	var wg sync.WaitGroup
+	wg.Add(workers)
+	for i := 0; i < workers; i++ {
+		go func(idx int) {
+			el := fc.NewWorker(fmt.Sprintf("%d", idx), 0)
+			go el.Release(ctx, messages*5) // Try to make races happen
+			j := 0
+			el.Run(ctx, func(tokens int) (used int, done bool, err error) {
+				msgs[idx] = append(msgs[idx], []byte(fmt.Sprintf("%d-%d,", idx, j))...)
+				j++
+				return 3, j >= messages, nil
+			})
+			wg.Done()
+		}(i)
+	}
+	wg.Wait()
+
+	for i := 0; i < workers; i++ {
+		buf := &bytes.Buffer{}
+		for j := 0; j < messages; j++ {
+			fmt.Fprintf(buf, "%d-%d,", i, j)
+		}
+		if want, got := buf.String(), string(msgs[i]); want != got {
+			t.Errorf("Got %s, want %s for %d", got, want, i)
+		}
+	}
+}
+
+func expect(t *testing.T, work chan interface{}, values ...interface{}) {
+	for i, w := range values {
+		if got := <-work; got != w {
+			t.Errorf("expected %p in pos %d got %p", w, i, got)
+		}
+	}
+}
+
+func TestOrdering(t *testing.T) {
+	const mtu = 10
+
+	ctx, cancel := context.RootContext()
+	defer cancel()
+	fc := New(0, mtu)
+
+	work := make(chan interface{})
+	worker := func(p int) *Worker {
+		w := fc.NewWorker(fmt.Sprintf("%d", p), p)
+		go w.Run(ctx, func(t int) (int, bool, error) {
+			work <- w
+			return t, false, nil
+		})
+		w.Release(ctx, mtu)
+		<-work
+		return w
+	}
+
+	w0 := worker(0)
+	w1a := worker(1)
+	w1b := worker(1)
+	w1c := worker(1)
+	w2 := worker(2)
+
+	// Release to all the flows at once and ensure the writes
+	// happen in the correct order.
+	fc.Release(ctx, []Release{{w0, 2 * mtu}, {w1a, 2 * mtu}, {w1b, 3 * mtu}, {w1c, 0}, {w2, mtu}})
+	expect(t, work, w0, w0, w1a, w1b, w1a, w1b, w1b, w2)
+}
+
+func TestSharedCounters(t *testing.T) {
+	const (
+		mtu    = 10
+		shared = 2 * mtu
+	)
+
+	ctx, cancel := context.RootContext()
+	defer cancel()
+
+	fc := New(shared, mtu)
+
+	work := make(chan interface{})
+
+	worker := func(p int) *Worker {
+		w := fc.NewWorker(fmt.Sprintf("%d", p), p)
+		go w.Run(ctx, func(t int) (int, bool, error) {
+			work <- w
+			return t, false, nil
+		})
+		return w
+	}
+
+	// w0 should run twice on shared counters.
+	w0 := worker(0)
+	expect(t, work, w0, w0)
+
+	w1 := worker(1)
+	// Now Release to w0 which shouldn't allow it to run since it's just repaying, but
+	// should allow w1 to run on the returned shared counters.
+	w0.Release(ctx, 2*mtu)
+	expect(t, work, w1, w1)
+
+	// Releasing again will allow w0 to run.
+	w0.Release(ctx, mtu)
+	expect(t, work, w0)
+}
+
+func TestConcurrentRun(t *testing.T) {
+	ctx, cancel := context.RootContext()
+	defer cancel()
+	const mtu = 10
+	fc := New(mtu, mtu)
+
+	ready, wait := make(chan struct{}), make(chan struct{})
+	w := fc.NewWorker("", 0)
+	go w.Run(ctx, func(t int) (int, bool, error) {
+		close(ready)
+		<-wait
+		return t, true, nil
+	})
+	<-ready
+	if err := w.Run(ctx, nil); verror.ErrorID(err) != ErrConcurrentRun.ID {
+		t.Errorf("expected concurrent run error got: %v", err)
+	}
+	close(wait)
+}
+
+func TestNonFlowControlledRun(t *testing.T) {
+	ctx, cancel := context.RootContext()
+	defer cancel()
+	const mtu = 10
+	fc := New(0, mtu)
+
+	work := make(chan interface{})
+	ready, wait := make(chan struct{}), make(chan struct{})
+	// Start one worker running
+	go fc.Run(ctx, "0", 0, func(t int) (int, bool, error) {
+		close(ready)
+		<-wait
+		return t, true, nil
+	})
+	<-ready
+	// Now queue up sever workers and make sure they execute in order.
+	go fc.Run(ctx, "2", 2, func(t int) (int, bool, error) {
+		work <- "c"
+		return t, true, nil
+	})
+	go fc.Run(ctx, "1", 1, func(t int) (int, bool, error) {
+		work <- "b"
+		return t, true, nil
+	})
+	go fc.Run(ctx, "0", 0, func(t int) (int, bool, error) {
+		work <- "a"
+		return t, true, nil
+	})
+	for fc.numActive() < 4 {
+		time.Sleep(time.Millisecond)
+	}
+	close(wait)
+	expect(t, work, "a", "b", "c")
+}
+
+func newNullConn(mtu int) net.Conn {
+	ln, err := net.Listen("tcp", ":0")
+	if err != nil {
+		panic(err)
+	}
+	addr := ln.Addr()
+
+	go func() {
+		conn, err := ln.Accept()
+		if err != nil {
+			panic(err)
+		}
+		ln.Close()
+		buf := make([]byte, mtu)
+		for {
+			_, err := conn.Read(buf)
+			if err == io.EOF {
+				break
+			}
+			if err != nil {
+				panic(err)
+			}
+		}
+		conn.Close()
+	}()
+
+	conn, err := net.Dial(addr.Network(), addr.String())
+	if err != nil {
+		panic(err)
+	}
+	return conn
+}
+
+func BenchmarkWithFlowControl(b *testing.B) {
+	const (
+		mtu     = 1 << 16
+		shared  = 1 << 20
+		workers = 100
+	)
+	ctx, cancel := context.RootContext()
+	defer cancel()
+	s := newNullConn(mtu)
+
+	for n := 0; n < b.N; n++ {
+		fc := New(shared, mtu)
+		var wg sync.WaitGroup
+		wg.Add(workers)
+		for i := 0; i < workers; i++ {
+			go func(idx int) {
+				w := fc.NewWorker(fmt.Sprintf("%d", idx), 0)
+				w.Release(ctx, len(testdata))
+				t := testdata
+				err := w.Run(ctx, func(tokens int) (used int, done bool, err error) {
+					towrite := min(tokens, len(t))
+					written, err := s.Write(t[:min(tokens, len(t))])
+					t = t[written:]
+					return towrite, len(t) == 0, err
+				})
+				if err != nil {
+					panic(err)
+				}
+				wg.Done()
+			}(i)
+		}
+		wg.Wait()
+	}
+	if err := s.Close(); err != nil {
+		panic(err)
+	}
+}
+
+func BenchmarkWithoutFlowControl(b *testing.B) {
+	const (
+		workers = 100
+		mtu     = 1 << 16
+	)
+	s := newNullConn(mtu)
+	for n := 0; n < b.N; n++ {
+		for cursor := 0; cursor < len(testdata); cursor += mtu {
+			for i := 0; i < workers; i++ {
+				_, err := s.Write(testdata[cursor : cursor+mtu])
+				if err != nil {
+					panic(err)
+				}
+			}
+		}
+	}
+	if err := s.Close(); err != nil {
+		panic(err)
+	}
+}
diff --git a/runtime/internal/flow/flowtest/flowtest.go b/runtime/internal/flow/flowtest/flowtest.go
new file mode 100644
index 0000000..b9a9907
--- /dev/null
+++ b/runtime/internal/flow/flowtest/flowtest.go
@@ -0,0 +1,108 @@
+// 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 flowtest
+
+import (
+	"io"
+	"sync"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/security"
+
+	"v.io/x/ref/internal/logger"
+)
+
+type Wire struct {
+	ctx    *context.T
+	mu     sync.Mutex
+	c      *sync.Cond
+	closed bool
+}
+
+func (w *Wire) Close() {
+	w.mu.Lock()
+	w.closed = true
+	w.c.Broadcast()
+	w.mu.Unlock()
+}
+
+func (w *Wire) IsClosed() bool {
+	w.mu.Lock()
+	c := w.closed
+	w.mu.Unlock()
+	return c
+}
+
+type MRW struct {
+	wire *Wire
+	in   []byte
+	peer *MRW
+}
+
+func NewMRWPair(ctx *context.T) (*MRW, *MRW, *Wire) {
+	w := &Wire{ctx: ctx}
+	w.c = sync.NewCond(&w.mu)
+	a, b := &MRW{wire: w}, &MRW{wire: w}
+	a.peer, b.peer = b, a
+	return a, b, w
+}
+
+func (f *MRW) WriteMsg(data ...[]byte) (int, error) {
+	buf := []byte{}
+	for _, d := range data {
+		buf = append(buf, d...)
+	}
+	logbuf := buf
+	if len(buf) > 128 {
+		logbuf = buf[:128]
+	}
+	logger.Global().VI(2).Infof("Writing %d bytes to the wire: %#v", len(buf), logbuf)
+	defer f.wire.mu.Unlock()
+	f.wire.mu.Lock()
+	for f.peer.in != nil && !f.wire.closed {
+		f.wire.c.Wait()
+	}
+	if f.wire.closed {
+		return 0, io.EOF
+	}
+	f.peer.in = buf
+	f.wire.c.Broadcast()
+	return len(buf), nil
+}
+
+func (f *MRW) ReadMsg() (buf []byte, err error) {
+	defer f.wire.mu.Unlock()
+	f.wire.mu.Lock()
+	for f.in == nil && !f.wire.closed {
+		f.wire.c.Wait()
+	}
+	if f.wire.closed {
+		return nil, io.EOF
+	}
+	buf, f.in = f.in, nil
+	f.wire.c.Broadcast()
+	logbuf := buf
+	if len(buf) > 128 {
+		logbuf = buf[:128]
+	}
+	logger.Global().VI(2).Infof("Reading %d bytes from the wire: %#v", len(buf), logbuf)
+	return buf, nil
+}
+
+func (f *MRW) Close() error {
+	f.wire.Close()
+	return nil
+}
+
+func BlessingsForPeer(
+	ctx *context.T,
+	localEndpoint, remoteEndpoint naming.Endpoint,
+	remoteBlessings security.Blessings,
+	remoteDischarges map[string]security.Discharge,
+) (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
new file mode 100644
index 0000000..dc97290
--- /dev/null
+++ b/runtime/internal/flow/manager/conncache.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 manager
+
+import (
+	"sort"
+	"strings"
+	"sync"
+
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/x/ref/runtime/internal/flow/conn"
+)
+
+// ConnCache is a cache of Conns keyed by (protocol, address, blessingNames)
+// and (routingID).
+// Multiple goroutines can invoke methods on the ConnCache simultaneously.
+// TODO(suharshs): We should periodically look for closed connections and remove them.
+type ConnCache struct {
+	mu        *sync.Mutex
+	addrCache map[string]*connEntry           // keyed by (protocol, address, blessingNames)
+	ridCache  map[naming.RoutingID]*connEntry // keyed by naming.RoutingID
+	started   map[string]bool                 // keyed by (protocol, address, blessingNames)
+	cond      *sync.Cond
+}
+
+type connEntry struct {
+	conn    *conn.Conn
+	rid     naming.RoutingID
+	addrKey string
+}
+
+func NewConnCache() *ConnCache {
+	mu := &sync.Mutex{}
+	cond := sync.NewCond(mu)
+	return &ConnCache{
+		mu:        mu,
+		addrCache: make(map[string]*connEntry),
+		ridCache:  make(map[naming.RoutingID]*connEntry),
+		started:   make(map[string]bool),
+		cond:      cond,
+	}
+}
+
+// ReservedFind returns a Conn where the remote end of the connection is
+// identified by the provided (protocol, address, blessingNames). nil is
+// returned if there is no such Conn.
+//
+// ReservedFind will return an error iff the cache is closed.
+// If no error is returned, the caller is required to call Unreserve, to avoid
+// deadlock. The (protocol, address, blessingNames) provided to Unreserve must
+// be the same as the arguments provided to ReservedFind.
+// All new ReservedFind calls for the (protocol, address, blessings) will Block
+// until the corresponding Unreserve call is made.
+func (c *ConnCache) ReservedFind(protocol, address string, blessingNames []string) (*conn.Conn, error) {
+	k := key(protocol, address, blessingNames)
+	defer c.mu.Unlock()
+	c.mu.Lock()
+	for c.started[k] {
+		c.cond.Wait()
+	}
+	if c.addrCache == nil {
+		return nil, NewErrCacheClosed(nil)
+	}
+	c.started[k] = true
+	entry := c.addrCache[k]
+	if entry == nil {
+		return nil, nil
+	}
+	if isClosed(entry.conn) {
+		delete(c.addrCache, entry.addrKey)
+		delete(c.ridCache, entry.rid)
+		return nil, nil
+	}
+	return entry.conn, nil
+}
+
+// Unreserve marks the status of the (protocol, address, blessingNames) as no
+// longer started, and broadcasts waiting threads.
+func (c *ConnCache) Unreserve(protocol, address string, blessingNames []string) {
+	c.mu.Lock()
+	delete(c.started, key(protocol, address, blessingNames))
+	c.cond.Broadcast()
+	c.mu.Unlock()
+}
+
+// Insert adds conn to the cache.
+// An error will be returned iff the cache has been closed.
+func (c *ConnCache) Insert(conn *conn.Conn) error {
+	defer c.mu.Unlock()
+	c.mu.Lock()
+	if c.addrCache == nil {
+		return NewErrCacheClosed(nil)
+	}
+	ep := conn.RemoteEndpoint()
+	k := key(ep.Addr().Network(), ep.Addr().String(), ep.BlessingNames())
+	entry := &connEntry{
+		conn:    conn,
+		rid:     ep.RoutingID(),
+		addrKey: k,
+	}
+	c.addrCache[k] = entry
+	c.ridCache[entry.rid] = entry
+	return nil
+}
+
+// Close marks the ConnCache as closed and closes all Conns in the cache.
+func (c *ConnCache) Close(ctx *context.T) {
+	defer c.mu.Unlock()
+	c.mu.Lock()
+	c.addrCache, c.started = nil, nil
+	for _, d := range c.ridCache {
+		d.conn.Close(ctx, NewErrCacheClosed(ctx))
+	}
+}
+
+// KillConnections will close and remove num LRU Conns in the cache.
+// If connections are already closed they will be removed from the cache.
+// This is useful when the manager is approaching system FD limits.
+// If num is greater than the number of connections in the cache, all cached
+// connections will be closed and removed.
+// KillConnections returns an error iff the cache is closed.
+func (c *ConnCache) KillConnections(ctx *context.T, num int) error {
+	defer c.mu.Unlock()
+	c.mu.Lock()
+	if c.addrCache == nil {
+		return NewErrCacheClosed(ctx)
+	}
+	err := NewErrConnKilledToFreeResources(ctx)
+	pq := make(connEntries, 0, len(c.ridCache))
+	for _, e := range c.ridCache {
+		if isClosed(e.conn) {
+			delete(c.addrCache, e.addrKey)
+			delete(c.ridCache, e.rid)
+			continue
+		}
+		pq = append(pq, e)
+	}
+	sort.Sort(pq)
+	for i := 0; i < num; i++ {
+		d := pq[i]
+		d.conn.Close(ctx, err)
+		delete(c.addrCache, d.addrKey)
+		delete(c.ridCache, d.rid)
+	}
+	return nil
+}
+
+// TODO(suharshs): If sorting the connections becomes too slow, switch to
+// container/heap instead of sorting all the connections.
+type connEntries []*connEntry
+
+func (c connEntries) Len() int {
+	return len(c)
+}
+
+func (c connEntries) Less(i, j int) bool {
+	return c[i].conn.LastUsedTime().Before(c[j].conn.LastUsedTime())
+}
+
+func (c connEntries) Swap(i, j int) {
+	c[i], c[j] = c[j], c[i]
+}
+
+// FindWithRoutingID returns a Conn where the remote end of the connection
+// is identified by the provided rid. nil is returned if there is no such Conn.
+// FindWithRoutingID will return an error iff the cache is closed.
+func (c *ConnCache) FindWithRoutingID(rid naming.RoutingID) (*conn.Conn, error) {
+	defer c.mu.Unlock()
+	c.mu.Lock()
+	if c.addrCache == nil {
+		return nil, NewErrCacheClosed(nil)
+	}
+	entry := c.ridCache[rid]
+	if entry == nil {
+		return nil, nil
+	}
+	if isClosed(entry.conn) {
+		delete(c.addrCache, entry.addrKey)
+		delete(c.ridCache, entry.rid)
+		return nil, nil
+	}
+	return entry.conn, nil
+}
+
+// Size returns the number of Conns stored in the ConnCache.
+func (c *ConnCache) Size() int {
+	defer c.mu.Unlock()
+	c.mu.Lock()
+	return len(c.addrCache)
+}
+
+func key(protocol, address string, blessingNames []string) string {
+	// TODO(suharshs): We may be able to do something more inclusive with our
+	// blessingNames.
+	return strings.Join(append([]string{protocol, address}, blessingNames...), ",")
+}
+
+func isClosed(conn *conn.Conn) bool {
+	select {
+	case <-conn.Closed():
+		return true
+	default:
+		return false
+	}
+}
diff --git a/runtime/internal/flow/manager/conncache_test.go b/runtime/internal/flow/manager/conncache_test.go
new file mode 100644
index 0000000..20c1537
--- /dev/null
+++ b/runtime/internal/flow/manager/conncache_test.go
@@ -0,0 +1,312 @@
+// 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 manager
+
+import (
+	"strconv"
+	"testing"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/flow"
+	"v.io/v23/naming"
+	"v.io/v23/rpc/version"
+
+	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"
+)
+
+func TestCache(t *testing.T) {
+	ctx, shutdown := v23.Init()
+	defer shutdown()
+
+	c := NewConnCache()
+	remote := &inaming.Endpoint{
+		Protocol:  "tcp",
+		Address:   "127.0.0.1:1111",
+		RID:       naming.FixedRoutingID(0x5555),
+		Blessings: []string{"A", "B", "C"},
+	}
+	conn := makeConnAndFlow(t, ctx, remote).c
+	if err := c.Insert(conn); err != nil {
+		t.Fatal(err)
+	}
+	// We should be able to find the conn in the cache.
+	if got, err := c.ReservedFind(remote.Protocol, remote.Address, remote.Blessings); err != nil || got != conn {
+		t.Errorf("got %v, want %v, err: %v", got, conn, err)
+	}
+	c.Unreserve(remote.Protocol, remote.Address, remote.Blessings)
+	// Changing the protocol should fail.
+	if got, err := c.ReservedFind("wrong", remote.Address, remote.Blessings); err != nil || got != nil {
+		t.Errorf("got %v, want <nil>, err: %v", got, err)
+	}
+	c.Unreserve("wrong", remote.Address, remote.Blessings)
+	// Changing the address should fail.
+	if got, err := c.ReservedFind(remote.Protocol, "wrong", remote.Blessings); err != nil || got != nil {
+		t.Errorf("got %v, want <nil>, err: %v", got, err)
+	}
+	c.Unreserve(remote.Protocol, "wrong", remote.Blessings)
+	// Changing the blessingNames should fail.
+	if got, err := c.ReservedFind(remote.Protocol, remote.Address, []string{"wrong"}); err != nil || got != nil {
+		t.Errorf("got %v, want <nil>, err: %v", got, err)
+	}
+	c.Unreserve(remote.Protocol, remote.Address, []string{"wrong"})
+
+	// We should be able to find the conn in the cache by looking up the RoutingID.
+	if got, err := c.FindWithRoutingID(remote.RID); err != nil || got != conn {
+		t.Errorf("got %v, want %v, err: %v", got, conn, err)
+	}
+	// Looking up the wrong RID should fail.
+	if got, err := c.FindWithRoutingID(naming.FixedRoutingID(0x1111)); err != nil || got != nil {
+		t.Errorf("got %v, want <nil>, err: %v", got, err)
+	}
+
+	otherEP := &inaming.Endpoint{
+		Protocol:  "other",
+		Address:   "other",
+		Blessings: []string{"other"},
+	}
+	otherConn := makeConnAndFlow(t, ctx, otherEP).c
+
+	// Looking up a not yet inserted endpoint should fail.
+	if got, err := c.ReservedFind(otherEP.Protocol, otherEP.Address, otherEP.Blessings); 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, otherEP.Blessings)
+		if err != nil {
+			t.Fatal(err)
+		}
+		ch <- conn
+	}(ch)
+
+	// We insert the other conn into the cache.
+	if err := c.Insert(otherConn); err != nil {
+		t.Fatal(err)
+	}
+	c.Unreserve(otherEP.Protocol, otherEP.Address, otherEP.Blessings)
+	// Now the c.ReservedFind should have unblocked and returned the correct Conn.
+	if cachedConn := <-ch; cachedConn != otherConn {
+		t.Errorf("got %v, want %v", cachedConn, otherConn)
+	}
+
+	// Closing the cache should close all the connections in the cache.
+	// Ensure that the conns are not closed yet.
+	if isClosed(conn) {
+		t.Fatalf("wanted conn to not be closed")
+	}
+	if isClosed(otherConn) {
+		t.Fatalf("wanted otherConn to not be closed")
+	}
+	c.Close(ctx)
+	// Now the connections should be closed.
+	<-conn.Closed()
+	<-otherConn.Closed()
+}
+
+func TestLRU(t *testing.T) {
+	ctx, shutdown := v23.Init()
+	defer shutdown()
+
+	// Ensure that the least recently created conns are killed by KillConnections.
+	c := NewConnCache()
+	conns := nConnAndFlows(t, ctx, 10)
+	for _, conn := range conns {
+		if err := c.Insert(conn.c); err != nil {
+			t.Fatal(err)
+		}
+	}
+	if err := c.KillConnections(ctx, 3); err != nil {
+		t.Fatal(err)
+	}
+	if !cacheSizeMatches(c) {
+		t.Errorf("the size of the caches and LRU list do not match")
+	}
+	// conns[3:] should not be closed and still in the cache.
+	// conns[:3] should be closed and removed from the cache.
+	for _, conn := range conns[3:] {
+		if isClosed(conn.c) {
+			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)
+		}
+	}
+	for _, conn := range conns[:3] {
+		<-conn.c.Closed()
+		if isInCache(t, c, conn.c) {
+			t.Errorf("conn %v should not be in cache", conn)
+		}
+	}
+
+	// Ensure that writing to conns marks conns as more recently used.
+	c = NewConnCache()
+	conns = nConnAndFlows(t, ctx, 10)
+	for _, conn := range conns {
+		if err := c.Insert(conn.c); err != nil {
+			t.Fatal(err)
+		}
+	}
+	for _, conn := range conns[:7] {
+		conn.write()
+	}
+	if err := c.KillConnections(ctx, 3); err != nil {
+		t.Fatal(err)
+	}
+	if !cacheSizeMatches(c) {
+		t.Errorf("the size of the caches and LRU list do not match")
+	}
+	// conns[:7] should not be closed and still in the cache.
+	// conns[7:] should be closed and removed from the cache.
+	for _, conn := range conns[:7] {
+		if isClosed(conn.c) {
+			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)
+		}
+	}
+	for _, conn := range conns[7:] {
+		<-conn.c.Closed()
+		if isInCache(t, c, conn.c) {
+			t.Errorf("conn %v should not be in cache", conn)
+		}
+	}
+
+	// Ensure that reading from conns marks conns as more recently used.
+	c = NewConnCache()
+	conns = nConnAndFlows(t, ctx, 10)
+	for _, conn := range conns {
+		if err := c.Insert(conn.c); err != nil {
+			t.Fatal(err)
+		}
+	}
+	for _, conn := range conns[:7] {
+		conn.read()
+	}
+	if err := c.KillConnections(ctx, 3); err != nil {
+		t.Fatal(err)
+	}
+	if !cacheSizeMatches(c) {
+		t.Errorf("the size of the caches and LRU list do not match")
+	}
+	// conns[:7] should not be closed and still in the cache.
+	// conns[7:] should be closed and removed from the cache.
+	for _, conn := range conns[:7] {
+		if isClosed(conn.c) {
+			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)
+		}
+	}
+	for _, conn := range conns[7:] {
+		<-conn.c.Closed()
+		if isInCache(t, c, conn.c) {
+			t.Errorf("conn %v should not be in cache", conn)
+		}
+	}
+}
+
+func isInCache(t *testing.T, c *ConnCache, conn *connpackage.Conn) bool {
+	rep := conn.RemoteEndpoint()
+	rfconn, err := c.ReservedFind(rep.Addr().Network(), rep.Addr().String(), rep.BlessingNames())
+	if err != nil {
+		t.Errorf("got %v, want %v, err: %v", rfconn, conn, err)
+	}
+	c.Unreserve(rep.Addr().Network(), rep.Addr().String(), rep.BlessingNames())
+	ridconn, err := c.FindWithRoutingID(rep.RoutingID())
+	if err != nil {
+		t.Errorf("got %v, want %v, err: %v", ridconn, conn, err)
+	}
+	return rfconn != nil || ridconn != nil
+}
+
+func cacheSizeMatches(c *ConnCache) bool {
+	return len(c.addrCache) == len(c.ridCache)
+}
+
+type connAndFlow struct {
+	c *connpackage.Conn
+	f flow.Flow
+}
+
+func (c connAndFlow) write() {
+	_, err := c.f.WriteMsg([]byte{0})
+	if err != nil {
+		panic(err)
+	}
+}
+
+func (c connAndFlow) read() {
+	_, err := c.f.ReadMsg()
+	if err != nil {
+		panic(err)
+	}
+}
+
+func nConnAndFlows(t *testing.T, ctx *context.T, n int) []connAndFlow {
+	cfs := make([]connAndFlow, n)
+	for i := 0; i < n; i++ {
+		cfs[i] = makeConnAndFlow(t, ctx, &inaming.Endpoint{
+			Protocol: strconv.Itoa(i),
+			RID:      naming.FixedRoutingID(uint64(i)),
+		})
+	}
+	return cfs
+}
+
+func makeConnAndFlow(t *testing.T, ctx *context.T, ep naming.Endpoint) connAndFlow {
+	dmrw, amrw, _ := flowtest.NewMRWPair(ctx)
+	dch := make(chan *connpackage.Conn)
+	ach := make(chan *connpackage.Conn)
+	go func() {
+		d, err := connpackage.NewDialed(ctx, dmrw, ep, ep,
+			version.RPCVersionRange{Min: 1, Max: 5}, nil)
+		if err != nil {
+			t.Fatalf("Unexpected error: %v", err)
+		}
+		dch <- d
+	}()
+	fh := fh{t, make(chan struct{})}
+	go func() {
+		a, err := connpackage.NewAccepted(ctx, amrw, ep,
+			version.RPCVersionRange{Min: 1, Max: 5}, fh)
+		if err != nil {
+			t.Fatalf("Unexpected error: %v", err)
+		}
+		ach <- a
+	}()
+	conn := <-dch
+	<-ach
+	f, err := conn.Dial(ctx, flowtest.BlessingsForPeer)
+	if err != nil {
+		t.Fatal(err)
+	}
+	// Write a byte to send the openFlow message.
+	if _, err := f.Write([]byte{0}); err != nil {
+		t.Fatal(err)
+	}
+	<-fh.ch
+	return connAndFlow{conn, f}
+}
+
+type fh struct {
+	t  *testing.T
+	ch chan struct{}
+}
+
+func (h fh) HandleFlow(f flow.Flow) error {
+	go func() {
+		if _, err := f.WriteMsg([]byte{0}); err != nil {
+			h.t.Errorf("failed to write: %v", err)
+		}
+		close(h.ch)
+	}()
+	return nil
+}
diff --git a/runtime/internal/flow/manager/errors.vdl b/runtime/internal/flow/manager/errors.vdl
new file mode 100644
index 0000000..7598b21
--- /dev/null
+++ b/runtime/internal/flow/manager/errors.vdl
@@ -0,0 +1,18 @@
+// 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 manager
+
+// These messages are constructed so as to avoid embedding a component/method name
+// and are thus more suitable for inclusion in other verrors.
+// This practice of omitting {1}{2} should be used throughout the flow implementations
+// since all of their errors are intended to be used as arguments to higher level errors.
+// TODO(suharshs,toddw): Allow skipping of {1}{2} in vdl generated errors.
+error (
+  UnknownProtocol(protocol string) {"en":"unknown protocol{:protocol}"}
+  ManagerClosed() {"en": "manager is already closed"}
+  AcceptFailed(err error) {"en": "accept failed{:err}"}
+  CacheClosed() {"en":"cache is closed"}
+  ConnKilledToFreeResources() {"en": "Connection killed to free resources."}
+)
\ No newline at end of file
diff --git a/runtime/internal/flow/manager/errors.vdl.go b/runtime/internal/flow/manager/errors.vdl.go
new file mode 100644
index 0000000..be7aa07
--- /dev/null
+++ b/runtime/internal/flow/manager/errors.vdl.go
@@ -0,0 +1,56 @@
+// 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 by the vanadium vdl tool.
+// Source: errors.vdl
+
+package manager
+
+import (
+	// VDL system imports
+	"v.io/v23/context"
+	"v.io/v23/i18n"
+	"v.io/v23/verror"
+)
+
+var (
+	ErrUnknownProtocol           = verror.Register("v.io/x/ref/runtime/internal/flow/manager.UnknownProtocol", verror.NoRetry, "{1:}{2:} unknown protocol{:3}")
+	ErrManagerClosed             = verror.Register("v.io/x/ref/runtime/internal/flow/manager.ManagerClosed", verror.NoRetry, "{1:}{2:} manager is already closed")
+	ErrAcceptFailed              = verror.Register("v.io/x/ref/runtime/internal/flow/manager.AcceptFailed", verror.NoRetry, "{1:}{2:} accept failed{:3}")
+	ErrCacheClosed               = verror.Register("v.io/x/ref/runtime/internal/flow/manager.CacheClosed", verror.NoRetry, "{1:}{2:} cache is closed")
+	ErrConnKilledToFreeResources = verror.Register("v.io/x/ref/runtime/internal/flow/manager.ConnKilledToFreeResources", verror.NoRetry, "{1:}{2:} Connection killed to free resources.")
+)
+
+func init() {
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrUnknownProtocol.ID), "{1:}{2:} unknown protocol{:3}")
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrManagerClosed.ID), "{1:}{2:} manager is already closed")
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrAcceptFailed.ID), "{1:}{2:} accept failed{:3}")
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrCacheClosed.ID), "{1:}{2:} cache is closed")
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrConnKilledToFreeResources.ID), "{1:}{2:} Connection killed to free resources.")
+}
+
+// NewErrUnknownProtocol returns an error with the ErrUnknownProtocol ID.
+func NewErrUnknownProtocol(ctx *context.T, protocol string) error {
+	return verror.New(ErrUnknownProtocol, ctx, protocol)
+}
+
+// NewErrManagerClosed returns an error with the ErrManagerClosed ID.
+func NewErrManagerClosed(ctx *context.T) error {
+	return verror.New(ErrManagerClosed, ctx)
+}
+
+// NewErrAcceptFailed returns an error with the ErrAcceptFailed ID.
+func NewErrAcceptFailed(ctx *context.T, err error) error {
+	return verror.New(ErrAcceptFailed, ctx, err)
+}
+
+// NewErrCacheClosed returns an error with the ErrCacheClosed ID.
+func NewErrCacheClosed(ctx *context.T) error {
+	return verror.New(ErrCacheClosed, ctx)
+}
+
+// NewErrConnKilledToFreeResources returns an error with the ErrConnKilledToFreeResources ID.
+func NewErrConnKilledToFreeResources(ctx *context.T) error {
+	return verror.New(ErrConnKilledToFreeResources, ctx)
+}
diff --git a/runtime/internal/flow/manager/manager.go b/runtime/internal/flow/manager/manager.go
new file mode 100644
index 0000000..af90572
--- /dev/null
+++ b/runtime/internal/flow/manager/manager.go
@@ -0,0 +1,400 @@
+// 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 manager
+
+import (
+	"net"
+	"strings"
+	"sync"
+	"syscall"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/flow"
+	"v.io/v23/flow/message"
+	"v.io/v23/naming"
+	"v.io/v23/security"
+	"v.io/v23/verror"
+	"v.io/v23/vom"
+
+	"v.io/x/ref/runtime/internal/flow/conn"
+	"v.io/x/ref/runtime/internal/lib/upcqueue"
+	inaming "v.io/x/ref/runtime/internal/naming"
+	"v.io/x/ref/runtime/internal/rpc/version"
+)
+
+type manager struct {
+	rid    naming.RoutingID
+	closed <-chan struct{}
+	q      *upcqueue.T
+	cache  *ConnCache
+
+	mu              *sync.Mutex
+	listenEndpoints []naming.Endpoint
+}
+
+func New(ctx *context.T, rid naming.RoutingID) flow.Manager {
+	m := &manager{
+		rid:    rid,
+		closed: ctx.Done(),
+		q:      upcqueue.New(),
+		cache:  NewConnCache(),
+		mu:     &sync.Mutex{},
+	}
+	return m
+}
+
+// Listen causes the Manager to accept flows from the provided protocol and address.
+// Listen may be called muliple times.
+//
+// The flow.Manager associated with ctx must be the receiver of the method,
+// otherwise an error is returned.
+func (m *manager) Listen(ctx *context.T, protocol, address string) error {
+	var (
+		ep  naming.Endpoint
+		err error
+	)
+	if protocol == inaming.Network {
+		ep, err = m.proxyListen(ctx, address)
+	} else {
+		ep, err = m.listen(ctx, protocol, address)
+	}
+	if err != nil {
+		return err
+	}
+	m.mu.Lock()
+	m.listenEndpoints = append(m.listenEndpoints, ep)
+	m.mu.Unlock()
+	return nil
+}
+
+func (m *manager) listen(ctx *context.T, protocol, address string) (naming.Endpoint, error) {
+	ln, err := listen(ctx, protocol, address)
+	if err != nil {
+		return nil, flow.NewErrNetwork(ctx, err)
+	}
+	local := &inaming.Endpoint{
+		Protocol: protocol,
+		Address:  ln.Addr().String(),
+		RID:      m.rid,
+	}
+	go m.lnAcceptLoop(ctx, ln, local)
+	return local, nil
+}
+
+func (m *manager) proxyListen(ctx *context.T, address string) (naming.Endpoint, error) {
+	ep, err := inaming.NewEndpoint(address)
+	if err != nil {
+		return nil, flow.NewErrBadArg(ctx, err)
+	}
+	f, err := m.internalDial(ctx, ep, proxyBlessingsForPeer{}.run, &proxyFlowHandler{ctx: ctx, m: m})
+	if err != nil {
+		return nil, flow.NewErrNetwork(ctx, err)
+	}
+	// Write to ensure we send an openFlow message.
+	if _, err := f.Write([]byte{0}); err != nil {
+		return nil, flow.NewErrNetwork(ctx, err)
+	}
+	var lep string
+	if err := vom.NewDecoder(f).Decode(&lep); err != nil {
+		return nil, flow.NewErrNetwork(ctx, err)
+	}
+	return inaming.NewEndpoint(lep)
+}
+
+type proxyBlessingsForPeer struct{}
+
+// TODO(suharshs): Figure out what blessings to present here. And present discharges.
+func (proxyBlessingsForPeer) run(ctx *context.T, lep, rep naming.Endpoint, rb security.Blessings,
+	rd map[string]security.Discharge) (security.Blessings, map[string]security.Discharge, error) {
+	return v23.GetPrincipal(ctx).BlessingStore().Default(), nil, nil
+}
+
+func (m *manager) lnAcceptLoop(ctx *context.T, ln flow.Listener, local naming.Endpoint) {
+	const killConnectionsRetryDelay = 5 * time.Millisecond
+	for {
+		flowConn, err := ln.Accept(ctx)
+		for tokill := 1; isTemporaryError(err); tokill *= 2 {
+			if isTooManyOpenFiles(err) {
+				if err := m.cache.KillConnections(ctx, tokill); err != nil {
+					ctx.VI(2).Infof("failed to kill connections: %v", err)
+					continue
+				}
+			} else {
+				tokill = 1
+			}
+			time.Sleep(killConnectionsRetryDelay)
+			flowConn, err = ln.Accept(ctx)
+		}
+		if err != nil {
+			ctx.Errorf("ln.Accept on localEP %v failed: %v", local, err)
+			continue
+		}
+		c, err := conn.NewAccepted(
+			ctx,
+			flowConn,
+			local,
+			version.Supported,
+			&flowHandler{q: m.q, closed: m.closed},
+		)
+		if err != nil {
+			flowConn.Close()
+			ctx.Errorf("failed to accept flow.Conn on localEP %v failed: %v", local, err)
+			continue
+		}
+		if err := m.cache.Insert(c); err != nil {
+			ctx.VI(2).Infof("failed to cache conn %v: %v", c, err)
+		}
+	}
+}
+
+type flowHandler struct {
+	q      *upcqueue.T
+	closed <-chan struct{}
+}
+
+func (h *flowHandler) HandleFlow(f flow.Flow) error {
+	select {
+	case <-h.closed:
+		// This will make the Put call below return a upcqueue.ErrQueueIsClosed.
+		h.q.Close()
+	default:
+	}
+	return h.q.Put(f)
+}
+
+type proxyFlowHandler struct {
+	ctx *context.T
+	m   *manager
+}
+
+func (h *proxyFlowHandler) HandleFlow(f flow.Flow) error {
+	select {
+	case <-h.m.closed:
+		h.m.q.Close()
+		return upcqueue.ErrQueueIsClosed
+	default:
+	}
+	go func() {
+		c, err := conn.NewAccepted(
+			h.ctx,
+			f,
+			f.Conn().LocalEndpoint(),
+			version.Supported,
+			&flowHandler{q: h.m.q, closed: h.m.closed})
+		if err != nil {
+			h.ctx.Errorf("failed to create accepted conn: %v", err)
+			return
+		}
+		if err := h.m.cache.Insert(c); err != nil {
+			h.ctx.Errorf("failed to create accepted conn: %v", err)
+			return
+		}
+	}()
+	return nil
+}
+
+// ListeningEndpoints returns the endpoints that the Manager has explicitly
+// listened on. The Manager will accept new flows on these endpoints.
+// Returned endpoints all have a RoutingID unique to the Acceptor.
+func (m *manager) ListeningEndpoints() []naming.Endpoint {
+	m.mu.Lock()
+	ret := make([]naming.Endpoint, len(m.listenEndpoints))
+	copy(ret, m.listenEndpoints)
+	m.mu.Unlock()
+	return ret
+}
+
+// Accept blocks until a new Flow has been initiated by a remote process.
+// Flows are accepted from addresses that the Manager is listening on,
+// including outgoing dialed connections.
+//
+// For example:
+//   err := m.Listen(ctx, "tcp", ":0")
+//   for {
+//     flow, err := m.Accept(ctx)
+//     // process flow
+//   }
+//
+// can be used to accept Flows initiated by remote processes.
+//
+// The flow.Manager associated with ctx must be the receiver of the method,
+// otherwise an error is returned.
+func (m *manager) Accept(ctx *context.T) (flow.Flow, error) {
+	// TODO(suharshs): Ensure that m is attached to ctx.
+	item, err := m.q.Get(m.closed)
+	switch {
+	case err == upcqueue.ErrQueueIsClosed:
+		return nil, flow.NewErrNetwork(ctx, NewErrManagerClosed(ctx))
+	case err != nil:
+		return nil, flow.NewErrNetwork(ctx, NewErrAcceptFailed(ctx, err))
+	default:
+		return item.(flow.Flow), nil
+	}
+}
+
+// Dial creates a Flow to the provided remote endpoint, using 'fn' to
+// determine the blessings that will be sent to the remote end.
+//
+// To maximize re-use of connections, the Manager will also Listen on Dialed
+// connections for the lifetime of the connection.
+//
+// The flow.Manager associated with ctx must be the receiver of the method,
+// otherwise an error is returned.
+func (m *manager) Dial(ctx *context.T, remote naming.Endpoint, fn flow.BlessingsForPeer) (flow.Flow, error) {
+	return m.internalDial(ctx, remote, fn, &flowHandler{q: m.q, closed: m.closed})
+}
+
+func (m *manager) internalDial(ctx *context.T, remote naming.Endpoint, fn flow.BlessingsForPeer, fh conn.FlowHandler) (flow.Flow, error) {
+	// Look up the connection based on RoutingID first.
+	c, err := m.cache.FindWithRoutingID(remote.RoutingID())
+	if err != nil {
+		return nil, flow.NewErrBadState(ctx, err)
+	}
+	var (
+		protocol         flow.Protocol
+		network, address string
+	)
+	if c == nil {
+		addr := remote.Addr()
+		protocol, _ = flow.RegisteredProtocol(addr.Network())
+		// (network, address) in the endpoint might not always match up
+		// with the key used for caching conns. For example:
+		// - conn, err := net.Dial("tcp", "www.google.com:80")
+		//   fmt.Println(conn.RemoteAddr()) // Might yield the corresponding IP address
+		// - Similarly, an unspecified IP address (net.IP.IsUnspecified) like "[::]:80"
+		//   might yield "[::1]:80" (loopback interface) in conn.RemoteAddr().
+		// Thus we look for Conns with the resolved address.
+		network, address, err = resolve(ctx, protocol, addr.Network(), addr.String())
+		if err != nil {
+			return nil, flow.NewErrResolveFailed(ctx, err)
+		}
+		c, err = m.cache.ReservedFind(network, address, remote.BlessingNames())
+		if err != nil {
+			return nil, flow.NewErrBadState(ctx, err)
+		}
+		defer m.cache.Unreserve(network, address, remote.BlessingNames())
+	}
+	if c == nil {
+		flowConn, err := dial(ctx, protocol, network, address)
+		if err != nil {
+			return nil, flow.NewErrDialFailed(ctx, err)
+		}
+		// TODO(mattr): We should only pass a flowHandler to NewDialed if there
+		// is a server attached to this flow manager.  Perhaps we can signal
+		// "serving flow manager" by passing a 0 RID to non-serving flow managers?
+		c, err = conn.NewDialed(
+			ctx,
+			flowConn,
+			localEndpoint(flowConn, m.rid),
+			remote,
+			version.Supported,
+			fh,
+		)
+		if err != nil {
+			if verror.ErrorID(err) == message.ErrWrongProtocol.ID {
+				return nil, err
+			}
+			return nil, flow.NewErrDialFailed(ctx, err)
+		}
+		if err := m.cache.Insert(c); err != nil {
+			return nil, flow.NewErrBadState(ctx, err)
+		}
+	}
+	f, err := c.Dial(ctx, fn)
+	if err != nil {
+		return nil, flow.NewErrDialFailed(ctx, err)
+	}
+
+	// If we are dialing out to a Proxy, we need to dial a conn on this flow, and
+	// return a flow on that corresponding conn.
+	if remote.RoutingID() != c.RemoteEndpoint().RoutingID() {
+		c, err = conn.NewDialed(
+			ctx,
+			f,
+			c.LocalEndpoint(),
+			remote,
+			version.Supported,
+			fh,
+		)
+		if err != nil {
+			if verror.ErrorID(err) == message.ErrWrongProtocol.ID {
+				return nil, err
+			}
+			return nil, flow.NewErrDialFailed(ctx, err)
+		}
+		f, err = c.Dial(ctx, fn)
+		if err != nil {
+			return nil, flow.NewErrDialFailed(ctx, err)
+		}
+	}
+	return f, nil
+}
+
+// RoutingID returns the naming.Routing of the flow.Manager.
+func (m *manager) RoutingID() naming.RoutingID {
+	return m.rid
+}
+
+// Closed returns a channel that remains open for the lifetime of the Manager
+// object. Once the channel is closed any operations on the Manager will
+// necessarily fail.
+func (m *manager) Closed() <-chan struct{} {
+	return m.closed
+}
+
+func dial(ctx *context.T, p flow.Protocol, protocol, address string) (flow.Conn, error) {
+	if p != nil {
+		var timeout time.Duration
+		if dl, ok := ctx.Deadline(); ok {
+			timeout = dl.Sub(time.Now())
+		}
+		return p.Dial(ctx, protocol, address, timeout)
+	}
+	return nil, NewErrUnknownProtocol(ctx, protocol)
+}
+
+func resolve(ctx *context.T, p flow.Protocol, protocol, address string) (string, string, error) {
+	if p != nil {
+		net, addr, err := p.Resolve(ctx, protocol, address)
+		if err != nil {
+			return "", "", err
+		}
+		return net, addr, nil
+	}
+	return "", "", NewErrUnknownProtocol(ctx, protocol)
+}
+
+func listen(ctx *context.T, protocol, address string) (flow.Listener, error) {
+	if p, _ := flow.RegisteredProtocol(protocol); p != nil {
+		ln, err := p.Listen(ctx, protocol, address)
+		if err != nil {
+			return nil, err
+		}
+		return ln, nil
+	}
+	return nil, NewErrUnknownProtocol(ctx, protocol)
+}
+
+func localEndpoint(conn flow.Conn, rid naming.RoutingID) naming.Endpoint {
+	localAddr := conn.LocalAddr()
+	ep := &inaming.Endpoint{
+		Protocol: localAddr.Network(),
+		Address:  localAddr.String(),
+		RID:      rid,
+	}
+	return ep
+}
+
+func isTemporaryError(err error) bool {
+	oErr, ok := err.(*net.OpError)
+	return ok && oErr.Temporary()
+}
+
+func isTooManyOpenFiles(err error) bool {
+	oErr, ok := err.(*net.OpError)
+	return ok && strings.Contains(oErr.Err.Error(), syscall.EMFILE.Error())
+}
diff --git a/runtime/internal/flow/manager/manager_test.go b/runtime/internal/flow/manager/manager_test.go
new file mode 100644
index 0000000..0c45db3
--- /dev/null
+++ b/runtime/internal/flow/manager/manager_test.go
@@ -0,0 +1,116 @@
+// 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 manager
+
+import (
+	"bufio"
+	"strings"
+	"testing"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/flow"
+	"v.io/v23/naming"
+
+	_ "v.io/x/ref/runtime/factories/fake"
+	"v.io/x/ref/runtime/internal/flow/flowtest"
+	"v.io/x/ref/test"
+)
+
+func init() {
+	test.Init()
+}
+
+func TestDirectConnection(t *testing.T) {
+	ctx, shutdown := v23.Init()
+	defer shutdown()
+
+	rid := naming.FixedRoutingID(0x5555)
+	m := New(ctx, rid)
+	want := "read this please"
+
+	if err := m.Listen(ctx, "tcp", "127.0.0.1:0"); err != nil {
+		t.Fatal(err)
+	}
+
+	eps := m.ListeningEndpoints()
+	if len(eps) == 0 {
+		t.Fatalf("no endpoints listened on")
+	}
+	flow, err := m.Dial(ctx, eps[0], flowtest.BlessingsForPeer)
+	if err != nil {
+		t.Error(err)
+	}
+	writeLine(flow, want)
+
+	flow, err = m.Accept(ctx)
+	if err != nil {
+		t.Fatal(err)
+	}
+	got, err := readLine(flow)
+	if err != nil {
+		t.Error(err)
+	}
+	if got != want {
+		t.Errorf("got %v, want %v", got, want)
+	}
+}
+
+func TestDialCachedConn(t *testing.T) {
+	ctx, shutdown := v23.Init()
+	defer shutdown()
+
+	am := New(ctx, naming.FixedRoutingID(0x5555))
+	if err := am.Listen(ctx, "tcp", "127.0.0.1:0"); err != nil {
+		t.Fatal(err)
+	}
+
+	eps := am.ListeningEndpoints()
+	if len(eps) == 0 {
+		t.Fatalf("no endpoints listened on")
+	}
+	dm := New(ctx, naming.FixedRoutingID(0x1111))
+	// At first the cache should be empty.
+	if got, want := dm.(*manager).cache.Size(), 0; got != want {
+		t.Fatalf("got cache size %v, want %v", got, want)
+	}
+	// After dialing a connection the cache should hold one connection.
+	dialAndAccept(t, ctx, dm, am, eps[0], flowtest.BlessingsForPeer)
+	if got, want := dm.(*manager).cache.Size(), 1; got != want {
+		t.Fatalf("got cache size %v, want %v", got, want)
+	}
+	// After dialing another connection the cache should still hold one connection
+	// because the connections should be reused.
+	dialAndAccept(t, ctx, dm, am, eps[0], flowtest.BlessingsForPeer)
+	if got, want := dm.(*manager).cache.Size(), 1; got != want {
+		t.Fatalf("got cache size %v, want %v", got, want)
+	}
+}
+
+func dialAndAccept(t *testing.T, ctx *context.T, dm, am flow.Manager, ep naming.Endpoint, bFn flow.BlessingsForPeer) (df, af flow.Flow) {
+	var err error
+	df, err = dm.Dial(ctx, ep, bFn)
+	if err != nil {
+		t.Fatal(err)
+	}
+	// Write a line to ensure that the openFlow message is sent.
+	writeLine(df, "")
+	af, err = am.Accept(ctx)
+	if err != nil {
+		t.Fatal(err)
+	}
+	return
+}
+
+func readLine(f flow.Flow) (string, error) {
+	s, err := bufio.NewReader(f).ReadString('\n')
+	return strings.TrimRight(s, "\n"), err
+}
+
+func writeLine(f flow.Flow, data string) error {
+	data += "\n"
+	_, err := f.Write([]byte(data))
+	return err
+}
diff --git a/runtime/internal/flow/protocols/tcp/init.go b/runtime/internal/flow/protocols/tcp/init.go
new file mode 100644
index 0000000..f0074d5
--- /dev/null
+++ b/runtime/internal/flow/protocols/tcp/init.go
@@ -0,0 +1,17 @@
+// 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 (
+	"v.io/v23/flow"
+	"v.io/x/ref/runtime/internal/lib/tcputil"
+)
+
+func init() {
+	tcp := tcputil.TCP{}
+	flow.RegisterProtocol("tcp", tcp, "tcp4", "tcp6")
+	flow.RegisterProtocol("tcp4", tcp)
+	flow.RegisterProtocol("tcp6", tcp)
+}
diff --git a/runtime/internal/flow/protocols/ws/init.go b/runtime/internal/flow/protocols/ws/init.go
new file mode 100644
index 0000000..b2a2bf6
--- /dev/null
+++ b/runtime/internal/flow/protocols/ws/init.go
@@ -0,0 +1,19 @@
+// 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/flow"
+
+	websocket "v.io/x/ref/runtime/internal/lib/xwebsocket"
+)
+
+func init() {
+	// ws, ws4, ws6 represent websocket protocol instances.
+	protocol := websocket.WS{}
+	flow.RegisterProtocol("ws", protocol, "ws4", "ws6")
+	flow.RegisterProtocol("ws4", protocol)
+	flow.RegisterProtocol("ws6", protocol)
+}
diff --git a/runtime/internal/flow/protocols/wsh/init.go b/runtime/internal/flow/protocols/wsh/init.go
new file mode 100644
index 0000000..2a1cd2a
--- /dev/null
+++ b/runtime/internal/flow/protocols/wsh/init.go
@@ -0,0 +1,20 @@
+// 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/flow"
+
+	websocket "v.io/x/ref/runtime/internal/lib/xwebsocket"
+)
+
+func init() {
+	protocol := websocket.WSH{}
+	flow.RegisterProtocol("wsh", protocol, "tcp4", "tcp6", "ws4", "ws6")
+	flow.RegisterProtocol("wsh4", protocol, "tcp4", "ws4")
+	flow.RegisterProtocol("wsh6", protocol, "tcp6", "ws6")
+}
diff --git a/runtime/internal/flow/protocols/wsh_nacl/doc.go b/runtime/internal/flow/protocols/wsh_nacl/doc.go
new file mode 100644
index 0000000..ebeefda
--- /dev/null
+++ b/runtime/internal/flow/protocols/wsh_nacl/doc.go
@@ -0,0 +1,7 @@
+// 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. It will only be built if on the nacl architecture.
+package wsh_nacl
diff --git a/runtime/internal/flow/protocols/wsh_nacl/init.go b/runtime/internal/flow/protocols/wsh_nacl/init.go
new file mode 100644
index 0000000..f7874de
--- /dev/null
+++ b/runtime/internal/flow/protocols/wsh_nacl/init.go
@@ -0,0 +1,22 @@
+// 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 wsh_nacl
+
+import (
+	"v.io/v23/flow"
+
+	websocket "v.io/x/ref/runtime/internal/lib/xwebsocket"
+)
+
+func init() {
+	// We limit wsh to ws since in general nacl does not allow direct access
+	// to TCP/UDP networking.
+	wshNaCl := websocket.WS{}
+	flow.RegisterProtocol("wsh", wshNaCl, "ws4", "ws6")
+	flow.RegisterProtocol("wsh4", wshNaCl, "ws4")
+	flow.RegisterProtocol("wsh6", wshNaCl, "ws6")
+}
diff --git a/runtime/internal/gce/gce_android.go b/runtime/internal/gce/gce_android.go
new file mode 100644
index 0000000..ce22de5
--- /dev/null
+++ b/runtime/internal/gce/gce_android.go
@@ -0,0 +1,19 @@
+// 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
+
+package gce
+
+import (
+	"net"
+)
+
+func RunningOnGCE() bool {
+	return false
+}
+
+func ExternalIPAddress() (net.IP, error) {
+	panic("The GCE profile was unexpectedly used with android.")
+}
diff --git a/runtime/internal/gce/gce_linux.go b/runtime/internal/gce/gce_linux.go
new file mode 100644
index 0000000..456c544
--- /dev/null
+++ b/runtime/internal/gce/gce_linux.go
@@ -0,0 +1,80 @@
+// 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 linux,!android
+
+// Package gce functions to test whether the current process is running on
+// Google Compute Engine, and to extract settings from this environment.
+// Any server that knows it will only ever run on GCE can import this Profile,
+// but in most cases, other Profiles will use the utility routines provided
+// by it to test to ensure that they are running on GCE and to obtain
+// metadata directly from its service APIs.
+package gce
+
+import (
+	"fmt"
+	"io/ioutil"
+	"net"
+	"net/http"
+	"sync"
+	"time"
+)
+
+// This URL returns the external IP address assigned to the local GCE instance.
+// If a HTTP GET request fails for any reason, this is not a GCE instance. If
+// the result of the GET request doesn't contain a "Metadata-Flavor: Google"
+// header, it is also not a GCE instance. The body of the document contains the
+// external IP address, if present. Otherwise, the body is empty.
+// See https://developers.google.com/compute/docs/metadata for details.
+const url = "http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/access-configs/0/external-ip"
+
+// How long to wait for the HTTP request to return.
+const timeout = time.Second
+
+var (
+	once       sync.Once
+	isGCEErr   error
+	externalIP net.IP
+)
+
+// RunningOnGCE returns true if the current process is running
+// on a Google Compute Engine instance.
+func RunningOnGCE() bool {
+	once.Do(func() {
+		externalIP, isGCEErr = gceTest(url)
+	})
+	return isGCEErr == nil
+}
+
+// ExternalIPAddress returns the external IP address of this
+// Google Compute Engine instance, or nil if there is none. Must be
+// called after RunningOnGCE.
+func ExternalIPAddress() (net.IP, error) {
+	return externalIP, isGCEErr
+}
+
+func gceTest(url string) (net.IP, error) {
+	client := &http.Client{Timeout: timeout}
+	req, err := http.NewRequest("GET", url, nil)
+	if err != nil {
+		return nil, err
+	}
+	req.Header.Add("Metadata-Flavor", "Google")
+	resp, err := client.Do(req)
+	if err != nil {
+		return nil, err
+	}
+	defer resp.Body.Close()
+	if resp.StatusCode != 200 {
+		return nil, fmt.Errorf("http error: %d", resp.StatusCode)
+	}
+	if flavor := resp.Header["Metadata-Flavor"]; len(flavor) != 1 || flavor[0] != "Google" {
+		return nil, fmt.Errorf("unexpected http header: %q", flavor)
+	}
+	body, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		return nil, err
+	}
+	return net.ParseIP(string(body)), nil
+}
diff --git a/runtime/internal/gce/gce_other.go b/runtime/internal/gce/gce_other.go
new file mode 100644
index 0000000..bfc9198
--- /dev/null
+++ b/runtime/internal/gce/gce_other.go
@@ -0,0 +1,10 @@
+// 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 !linux
+
+package gce
+
+// This package is a no-op on non-linux systems, but we still need at least
+// one file in the package to keep go happy!
diff --git a/runtime/internal/gce/gce_test.go b/runtime/internal/gce/gce_test.go
new file mode 100644
index 0000000..0cfe65e
--- /dev/null
+++ b/runtime/internal/gce/gce_test.go
@@ -0,0 +1,68 @@
+// 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 linux,!android
+
+package gce
+
+import (
+	"fmt"
+	"net"
+	"net/http"
+	"testing"
+)
+
+func startServer(t *testing.T) (net.Addr, func()) {
+	l, err := net.Listen("tcp", "127.0.0.1:0")
+	if err != nil {
+		t.Fatal(err)
+	}
+	http.HandleFunc("/404", func(w http.ResponseWriter, r *http.Request) {
+		w.WriteHeader(http.StatusNotFound)
+	})
+	http.HandleFunc("/200_not_gce", func(w http.ResponseWriter, r *http.Request) {
+		fmt.Fprintf(w, "Hello")
+	})
+	http.HandleFunc("/gce_no_ip", func(w http.ResponseWriter, r *http.Request) {
+		// When a GCE instance doesn't have an external IP address, the
+		// request returns a 200 with an empty body.
+		w.Header().Add("Metadata-Flavor", "Google")
+		if m := r.Header["Metadata-Flavor"]; len(m) != 1 || m[0] != "Google" {
+			w.WriteHeader(http.StatusForbidden)
+			return
+		}
+	})
+	http.HandleFunc("/gce_with_ip", func(w http.ResponseWriter, r *http.Request) {
+		// When a GCE instance has an external IP address, the request
+		// returns the IP address as body.
+		w.Header().Add("Metadata-Flavor", "Google")
+		if m := r.Header["Metadata-Flavor"]; len(m) != 1 || m[0] != "Google" {
+			w.WriteHeader(http.StatusForbidden)
+			return
+		}
+		fmt.Fprintf(w, "1.2.3.4")
+	})
+
+	go http.Serve(l, nil)
+	return l.Addr(), func() { l.Close() }
+}
+
+func TestGCE(t *testing.T) {
+	addr, stop := startServer(t)
+	defer stop()
+	baseURL := "http://" + addr.String()
+
+	if ip, err := gceTest(baseURL + "/404"); err == nil || ip != nil {
+		t.Errorf("expected error, but not got nil")
+	}
+	if ip, err := gceTest(baseURL + "/200_not_gce"); err == nil || ip != nil {
+		t.Errorf("expected error, but not got nil")
+	}
+	if ip, err := gceTest(baseURL + "/gce_no_ip"); err != nil || ip != nil {
+		t.Errorf("Unexpected result. Got (%v, %v), want nil:nil", ip, err)
+	}
+	if ip, err := gceTest(baseURL + "/gce_with_ip"); err != nil || ip.String() != "1.2.3.4" {
+		t.Errorf("Unexpected result. Got (%v, %v), want nil:1.2.3.4", ip, err)
+	}
+}
diff --git a/runtime/internal/gce_linux.go b/runtime/internal/gce_linux.go
new file mode 100644
index 0000000..aaf489f
--- /dev/null
+++ b/runtime/internal/gce_linux.go
@@ -0,0 +1,36 @@
+// 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 linux
+
+package internal
+
+import (
+	"net"
+
+	"v.io/v23/logging"
+
+	"v.io/x/ref/runtime/internal/gce"
+)
+
+// GCEPublicAddress returns the public IP address of the GCE instance
+// it is run from, or nil if run from anywhere else. The returned address
+// is the public address of a 1:1 NAT tunnel to this host.
+func GCEPublicAddress(log logging.Logger) *net.IPAddr {
+	if !gce.RunningOnGCE() {
+		return nil
+	}
+	var pub *net.IPAddr
+	// Determine the IP address from GCE's metadata
+	if ip, err := gce.ExternalIPAddress(); err != nil {
+		log.Infof("failed to query GCE metadata: %s", err)
+	} else {
+		// 1:1 NAT case, our network config will not change.
+		pub = &net.IPAddr{IP: ip}
+	}
+	if pub == nil {
+		log.Infof("failed to determine public IP address to publish with")
+	}
+	return pub
+}
diff --git a/runtime/internal/gce_other.go b/runtime/internal/gce_other.go
new file mode 100644
index 0000000..4aea338
--- /dev/null
+++ b/runtime/internal/gce_other.go
@@ -0,0 +1,20 @@
+// 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 !linux
+
+package internal
+
+import (
+	"net"
+
+	"v.io/v23/logging"
+)
+
+// GCEPublicAddress returns the public IP address of the GCE instance
+// it is run from, or nil if run from anywhere else. The returned address
+// is the public address of a 1:1 NAT tunnel to this host.
+func GCEPublicAddress(logging.Logger) *net.IPAddr {
+	return nil
+}
diff --git a/runtime/internal/lib/appcycle/appcycle.go b/runtime/internal/lib/appcycle/appcycle.go
new file mode 100644
index 0000000..d35bb0f
--- /dev/null
+++ b/runtime/internal/lib/appcycle/appcycle.go
@@ -0,0 +1,164 @@
+// 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 appcycle
+
+import (
+	"fmt"
+	"os"
+	"sync"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/x/ref/lib/apilog"
+
+	public "v.io/v23/services/appcycle"
+)
+
+type AppCycle struct {
+	sync.RWMutex
+	waiters      []chan<- string
+	taskTrackers []chan<- v23.Task
+	task         v23.Task
+	shutDown     bool
+	disp         *invoker
+}
+
+type invoker struct {
+	ac *AppCycle
+}
+
+func New() *AppCycle {
+	ac := new(AppCycle)
+	ac.disp = &invoker{ac}
+	return ac
+}
+
+func (m *AppCycle) Shutdown() {
+	m.Lock()
+	defer m.Unlock()
+	if m.shutDown {
+		return
+	}
+	m.shutDown = true
+	for _, t := range m.taskTrackers {
+		close(t)
+	}
+	m.taskTrackers = nil
+}
+
+func (m *AppCycle) stop(ctx *context.T, msg string) {
+	ctx.Infof("stop(%v)", msg)
+	defer ctx.Infof("stop(%v) done", msg)
+	m.RLock()
+	defer m.RUnlock()
+	if len(m.waiters) == 0 {
+		ctx.Infof("Unhandled stop. Exiting.")
+		os.Exit(v23.UnhandledStopExitCode)
+	}
+	for _, w := range m.waiters {
+		select {
+		case w <- msg:
+		default:
+		}
+	}
+}
+
+func (m *AppCycle) Stop(ctx *context.T) {
+	defer apilog.LogCall(nil)(nil) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	m.stop(ctx, v23.LocalStop)
+}
+
+func (*AppCycle) ForceStop(ctx *context.T) {
+	defer apilog.LogCall(nil)(nil) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	os.Exit(v23.ForceStopExitCode)
+}
+
+func (m *AppCycle) WaitForStop(_ *context.T, ch chan<- string) {
+	defer apilog.LogCallf(nil, "ch=")(nil, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	m.Lock()
+	defer m.Unlock()
+	m.waiters = append(m.waiters, ch)
+}
+
+func (m *AppCycle) TrackTask(ch chan<- v23.Task) {
+	defer apilog.LogCallf(nil, "ch=")(nil, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	m.Lock()
+	defer m.Unlock()
+	if m.shutDown {
+		close(ch)
+		return
+	}
+	m.taskTrackers = append(m.taskTrackers, ch)
+}
+
+func (m *AppCycle) advanceTask(progress, goal int32) {
+	m.Lock()
+	defer m.Unlock()
+	m.task.Goal += goal
+	m.task.Progress += progress
+	for _, t := range m.taskTrackers {
+		select {
+		case t <- m.task:
+		default:
+			// TODO(caprita): Make it such that the latest task
+			// update is always added to the channel even if channel
+			// is full.  One way is to pull an element from t and
+			// then re-try the push.
+		}
+	}
+}
+
+func (m *AppCycle) AdvanceGoal(delta int32) {
+	defer apilog.LogCallf(nil, "delta=%v", delta)(nil, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	if delta <= 0 {
+		return
+	}
+	m.advanceTask(0, delta)
+}
+
+func (m *AppCycle) AdvanceProgress(delta int32) {
+	defer apilog.LogCallf(nil, "delta=%v", delta)(nil, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	if delta <= 0 {
+		return
+	}
+	m.advanceTask(delta, 0)
+}
+
+func (m *AppCycle) Remote() interface{} {
+	defer apilog.LogCall(nil)(nil) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	return public.AppCycleServer(m.disp)
+}
+
+func (d *invoker) Stop(ctx *context.T, call public.AppCycleStopServerCall) error {
+	defer apilog.LogCallf(ctx, "call=")(ctx, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	blessings, _ := security.RemoteBlessingNames(ctx, call.Security())
+	ctx.Infof("AppCycle Stop request from %v", blessings)
+	// The size of the channel should be reasonably sized to expect not to
+	// miss updates while we're waiting for the stream to unblock.
+	ch := make(chan v23.Task, 10)
+	d.ac.TrackTask(ch)
+	// TODO(caprita): Include identity of Stop issuer in message.
+	d.ac.stop(ctx, v23.RemoteStop)
+	for {
+		task, ok := <-ch
+		if !ok {
+			// Channel closed, meaning process shutdown is imminent.
+			break
+		}
+		actask := public.Task{Progress: task.Progress, Goal: task.Goal}
+		ctx.Infof("AppCycle Stop progress %d/%d", task.Progress, task.Goal)
+		call.SendStream().Send(actask)
+	}
+	ctx.Infof("AppCycle Stop done")
+	return nil
+}
+
+func (d *invoker) ForceStop(ctx *context.T, _ rpc.ServerCall) error {
+	defer apilog.LogCall(nil)(nil) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	d.ac.ForceStop(ctx)
+	return fmt.Errorf("ForceStop should not reply as the process should be dead")
+}
diff --git a/runtime/internal/lib/bqueue/bqueue.go b/runtime/internal/lib/bqueue/bqueue.go
new file mode 100644
index 0000000..ab428cf
--- /dev/null
+++ b/runtime/internal/lib/bqueue/bqueue.go
@@ -0,0 +1,132 @@
+// 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 bqueue implements several kinds of buffer queues, as a N-writer,
+// 1-reader queue.  By "buffer," we mean iobuf.Slice values.  Each writer has a
+// separate bounded queue to which it writes buffers.  The queue also supports
+// flow control.
+//
+// Initialization:
+//
+//     // Create a new queue using one of the implementations
+//     // (currently only bqueue/drrqueue).
+//     q := drrqueue.New()
+//
+// Reader API:
+//
+//     // Returns the next buffer in the queue, blocking until there is one
+//     // available.  Returns with an error if <q> is closed:
+//     _, buf, err := q.Get()
+//
+// Writer API:
+//
+//     // Allocate a new Writer with the id, priority, and space for N elements.
+//     w := q.New(id, priority, N)
+//
+//     // Add <buf> to the <w>.  Blocks until there is space in the Writer.
+//     // Aborts if <cancel> is closed or contains a value.
+//     err := w.Put(buf, cancel)
+//
+//     w.Release(N)  // Make the next N buffers available to q.Get().
+//
+// The q.Release() method is used for rate limiting.  Buffers can be added with
+// q.Put(), but they are not passed to q.Get() until they are released.
+package bqueue
+
+import (
+	"errors"
+
+	"v.io/x/ref/runtime/internal/lib/iobuf"
+)
+
+// Priority is an integer priority.  Smaller is greater priority.
+//
+// For performance, priorities should be dense and start from 0.  Some
+// implementations like drrqueue have use space linear in the max priority.
+type Priority uint // TODO(jyh): Change the dense requirement if we need it.
+
+// ID is the type of Writer identifiers.
+type ID int64
+
+// FlushFunc is the type of flushing functions.  See T.Get for more info.
+type FlushFunc func() error
+
+// T specifies a buffer queue.  The NewWriter method is used to create new
+// writer queues, and the Get method returns the next buffer to be served.
+type T interface {
+	Close()
+	String() string
+
+	// Find returns the Writer with the specified ID.  Returns nil if there is
+	// no such writer.
+	Find(id ID) Writer
+
+	// Get returns the next contents of the queue.  Get returns a Writer and an
+	// array of elements dequeued from the Writer.  The number of elements
+	// returned depends on the implementation (for example, drrqueue specifies a
+	// cap on how many bytes can be dequeued per Writer per round-robin cycle).
+	// In addition, multiple elements are returned so that iobuf.Coalesce() can
+	// be used to coalesce the contents.
+	//
+	// Get blocks until at least one element can be returned or the queue is
+	// closed.  If non-nil, the <flush> function is called just before Get
+	// blocks.
+	//
+	// If a Writer is closed (the Writer's Close() method was called), then Get
+	// returns the Writer with empty contents.  The caller should call
+	// writer.Shutdown() to remove the Writer and prevent it from being returned
+	// in subsequent calls.
+	//
+	// It is not safe to call Get() concurrently.
+	Get(flush FlushFunc) (Writer, []*iobuf.Slice, error)
+
+	// NewWriter allocates a new Writer.
+	NewWriter(id ID, p Priority, n int) (Writer, error)
+}
+
+// Writer represents a single writer queue.  Writer queues are served
+// according to the policy defined by the container queue T.
+type Writer interface {
+	ID() ID
+
+	// Close closes the Writer, without discarding the contents.  All Put
+	// operations currently running may, or may not, add their values to the
+	// Writer.  All Put operations that happen-after the Close will fail.
+	Close()
+
+	// Shutdown closes the Writer as in Close and also discards the contents.
+	// If removeWriter is true the writer will be removed from the
+	// associated T's queue entirely, otherwise the now empty writer will
+	// remain and eventually be returned by a T.Get.
+	Shutdown(removeWriter bool)
+
+	// IsClosed returns true if the Writer is closed.
+	IsClosed() bool
+
+	// IsDrained returns true if the Writer is closed and has no data
+	IsDrained() bool
+
+	// Put adds an element to the queue.  Put blocks until there is space in
+	// the Writer.  The element is not made available to T.Get until it is
+	// released with the Release method.  Returns an error if the queue is
+	// closed or the operation is cancelled.
+	Put(buf *iobuf.Slice, cancel <-chan struct{}) error
+
+	// TryPut is like Put, but it is nonblocking.
+	TryPut(buf *iobuf.Slice) error
+
+	// Release allows the next <n> elements to be removed from the Writer and
+	// passed to Get.  If <n> is negative, all messages are released and flow
+	// control is no longer used.
+	Release(n int) error
+}
+
+var (
+	ErrBQueueIsClosed        = errors.New("bqueue: queue is closed")
+	ErrWriterAlreadyExists   = errors.New("bqueue: writer already exists with this identifier")
+	ErrWriterIsClosed        = errors.New("bqueue: writer is closed")
+	ErrCantToggleFlowControl = errors.New("bqueue: can't turn on flow control when it is off")
+	ErrCancelled             = errors.New("bqueue: operation was canceled")
+	ErrTryAgain              = errors.New("bqueue: writer is not ready, try again")
+)
diff --git a/runtime/internal/lib/bqueue/drrqueue/drrqueue.go b/runtime/internal/lib/bqueue/drrqueue/drrqueue.go
new file mode 100644
index 0000000..a42731b
--- /dev/null
+++ b/runtime/internal/lib/bqueue/drrqueue/drrqueue.go
@@ -0,0 +1,420 @@
+// 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 drrqueue implements a deficit round-robin buffer queue.
+//
+//     Efficient Fair Queueing Using Deficit Round-Robin
+//     M. Shreedhar and George Varghese
+//     IEEE/ACM Transactions on Networking, Vol. 4, No. 3, June 1996
+//
+// The queue supports N-writers and 1-reader queue.  By "buffer," we mean []byte
+// blocks.
+//
+// Writers have a priority that takes precedence over the deficit.  Writers
+// with greater priority are served first.  Deficits are not even updated for
+// lower priorities when higher priority Writers are being served.
+package drrqueue
+
+// LOCKING DISCIPLINE:
+//
+// Each Writer has a lock, and so does T.  Locks are always taken in order:
+// Writer.mutex first, then T.mutex.  Never take the locks in the opposite
+// order.
+
+import (
+	"fmt"
+	"io"
+	"sync"
+
+	"v.io/x/ref/runtime/internal/lib/bqueue"
+	"v.io/x/ref/runtime/internal/lib/deque"
+	"v.io/x/ref/runtime/internal/lib/iobuf"
+	vsync "v.io/x/ref/runtime/internal/lib/sync"
+)
+
+// T defines the type of round-robin buffer queues.  The queue has multiple
+// input Writer queues that are served according to the deficit round-robin
+// policy.
+type T struct {
+	mutex sync.Mutex
+	cond  *sync.Cond
+
+	// active contains an array of active Writers, indexed by Priority.
+	active [][]*writer
+
+	// writers contains all of the Writers.
+	writers map[bqueue.ID]*writer
+
+	// quantum is the amount of data that each Writer can send per round-robin cycle.
+	quantum int
+
+	isClosed bool
+}
+
+// Writer is a single bounded input queue supporting a Put operation.
+type writer struct {
+	id       bqueue.ID
+	q        *T
+	priority bqueue.Priority
+
+	// free contains the number of free bytes in the writer queue.
+	//
+	// INVARIANT: free + size == size of the queue (a constant).  This can't be
+	// computed, because <free> is a semaphore, but it is true nonetheless.
+	free *vsync.Semaphore
+
+	// The following are all protected by the mutex.
+	mutex    sync.Mutex
+	isClosed bool
+	contents deque.T
+	size     int // Total number of bytes in the queue.
+	released int // Number of bytes that can be dequeued (negative for unlimited).
+
+	// The following are protected by q.mutex.  mutex is not required.
+	isActive activeMode
+	deficit  int
+}
+
+// activeMode has three states:
+//   busy: The Writer is being updated by Get.  It is not in the active list.
+//   idle: The Writer is inactive.  It is not in the active list.
+//   active: The Writer is in the active list.
+type activeMode int
+
+const (
+	busy activeMode = iota
+	idle
+	active
+)
+
+// ID returns the numeric identifier for the queue.
+func (w *writer) ID() bqueue.ID {
+	return w.id
+}
+
+// Close closes the writer, without discarding the contents.  All Put operations
+// currently running may, or may not, add their values to the queue.  All Put
+// operations that happen-after the Close will fail.
+func (w *writer) Close() {
+	w.mutex.Lock()
+	w.isClosed = true
+	w.updateStateLocked(false, 0)
+	w.mutex.Unlock()
+	w.free.Close()
+}
+
+// Shutdown closes the writer as in Close and also discards the contents.
+// If removeWriter is true the writer will be removed from the
+// associated T's queue entirely, otherwise the now empty writer will
+// remain and eventually be returned by a T.Get.
+func (w *writer) Shutdown(removeWriter bool) {
+	w.mutex.Lock()
+
+	w.isClosed = true
+	if !removeWriter {
+		w.contents.Clear()
+		w.size = 0
+		w.updateStateLocked(false, 0)
+	}
+
+	w.mutex.Unlock()
+
+	if removeWriter {
+		w.q.removeWriter(w)
+	}
+	w.free.Close()
+}
+
+// IsClosed returns true iff the Writer is closed.
+func (w *writer) IsClosed() bool {
+	w.mutex.Lock()
+	defer w.mutex.Unlock()
+	return w.isClosed
+}
+
+// IsDrained returns true iff the Writer is closed and empty.
+func (w *writer) IsDrained() bool {
+	w.mutex.Lock()
+	defer w.mutex.Unlock()
+	return w.isClosed && w.size == 0
+}
+
+// Put adds an element to the queue.  Put blocks until there is space in the
+// Writer.  The element is not made available to T.Get until it is released with
+// the Release method.  Returns an error if the queue is closed or the operation
+// is cancelled.
+func (w *writer) Put(buf *iobuf.Slice, cancel <-chan struct{}) error {
+	// Block until there is space in the Writer.
+	if err := w.free.DecN(uint(buf.Size()), cancel); err != nil {
+		return err
+	}
+	return w.putContents(buf)
+}
+
+// TryPut is like Put, but it is nonblocking.
+func (w *writer) TryPut(buf *iobuf.Slice) error {
+	if err := w.free.TryDecN(uint(buf.Size())); err != nil {
+		return err
+	}
+	return w.putContents(buf)
+}
+
+func (w *writer) putContents(buf *iobuf.Slice) error {
+	w.mutex.Lock()
+	defer w.mutex.Unlock()
+	if w.isClosed {
+		return bqueue.ErrWriterIsClosed
+	}
+	w.contents.PushBack(buf)
+	w.size += buf.Size()
+	w.updateStateLocked(false, 0)
+	return nil
+}
+
+// Release allows the next <bytes> to be removed from the queue and passed to
+// Get.  If <bytes> is negative, all messages are released and flow control is
+// no longer used.
+func (w *writer) Release(bytes int) error {
+	w.mutex.Lock()
+	defer w.mutex.Unlock()
+	if w.released < 0 && bytes >= 0 {
+		return bqueue.ErrCantToggleFlowControl
+	}
+	if bytes < 0 {
+		w.released = -1
+	} else {
+		w.released += bytes
+	}
+	w.updateStateLocked(false, 0)
+	return nil
+}
+
+// getContents returns as much data as possible, up to the deficit, and then
+// updates the state.
+func (w *writer) getContents(deficit int) ([]*iobuf.Slice, bool) {
+	w.mutex.Lock()
+	defer w.mutex.Unlock()
+
+	// Collect the contents into bufs
+	if w.released >= 0 && deficit > w.released {
+		deficit = w.released
+	}
+
+	// Writer is closed.
+	if w.size == 0 && w.isClosed {
+		return nil, true
+	}
+
+	var consumed int
+	var bufs []*iobuf.Slice
+	for w.contents.Size() != 0 {
+		b := w.contents.Front().(*iobuf.Slice)
+		size := consumed + b.Size()
+		if size > deficit {
+			break
+		}
+		consumed = size
+		bufs = append(bufs, b)
+		w.contents.PopFront()
+	}
+
+	// Update counters by number of bytes consumed.
+	w.size -= consumed
+	// Decrement released, but only if it is nonnegative.
+	if w.released >= 0 {
+		w.released -= consumed
+		if w.released < 0 {
+			panic("released is negative")
+		}
+	}
+	w.updateStateLocked(true, consumed)
+
+	return bufs, bufs != nil
+}
+
+// updateStateLocked updates the ready state of the Writer.
+//
+// REQUIRES: w.mutex is locked.
+func (w *writer) updateStateLocked(overrideBusy bool, consumed int) {
+	w.free.IncN(uint(consumed))
+
+	// The w.isActive state does not depend on the deficit.
+	isActive := (w.size == 0 && w.isClosed) ||
+		(w.size != 0 && (w.released < 0 || w.contents.Front().(*iobuf.Slice).Size() <= w.released))
+	w.q.updateWriterState(w, overrideBusy, isActive, consumed)
+}
+
+// New returns a new T.  Each writer is allowed to send quantum bytes per round-robin cycle.
+func New(quantum int) bqueue.T {
+	q := &T{writers: make(map[bqueue.ID]*writer), quantum: quantum}
+	q.cond = sync.NewCond(&q.mutex)
+	return q
+}
+
+// Close closes the queue.
+func (q *T) Close() {
+	q.mutex.Lock()
+	writers := q.writers
+	q.isClosed = true
+	q.writers = make(map[bqueue.ID]*writer)
+	for i := 0; i != len(q.active); i++ {
+		q.active[i] = nil
+	}
+	q.cond.Signal()
+	q.mutex.Unlock()
+
+	// Close the queues outside the q.mutex lock to preserve lock order.
+	for _, w := range writers {
+		w.Shutdown(true)
+	}
+}
+
+// NewWriter allocates a new Writer.
+func (q *T) NewWriter(id bqueue.ID, p bqueue.Priority, bytes int) (bqueue.Writer, error) {
+	w := &writer{
+		id:       id,
+		priority: p,
+		q:        q,
+		free:     vsync.NewSemaphore(),
+		isActive: idle,
+	}
+	w.free.IncN(uint(bytes))
+
+	q.mutex.Lock()
+	defer q.mutex.Unlock()
+	if q.isClosed {
+		return nil, bqueue.ErrBQueueIsClosed
+	}
+	q.addPriorityLocked(p)
+	if _, ok := q.writers[w.id]; ok {
+		return nil, bqueue.ErrWriterAlreadyExists
+	}
+	q.writers[w.id] = w
+	return w, nil
+}
+
+// String provides a string representation of the queue.
+func (q *T) String() string {
+	q.mutex.Lock()
+	defer q.mutex.Unlock()
+	s := "q{"
+	for _, w := range q.writers {
+		s += fmt.Sprintf("Writer{id: %d, size: %d, released: %d}, ", w.id, w.size, w.released)
+	}
+	s += "}"
+	return s
+}
+
+// Find returns the queue with the specified ID.
+func (q *T) Find(id bqueue.ID) bqueue.Writer {
+	q.mutex.Lock()
+	defer q.mutex.Unlock()
+	w, ok := q.writers[id]
+	if !ok {
+		// Don't return w; that would return a non-nil Writer interface
+		// containing nil.
+		return nil
+	}
+	return w
+}
+
+// Get returns the next element from a queue.  Get blocks until a buffer is
+// available or the queue is closed.
+func (q *T) Get(flush bqueue.FlushFunc) (bqueue.Writer, []*iobuf.Slice, error) {
+	for {
+		w, deficit, err := q.nextWriter(flush)
+		if w == nil {
+			return nil, nil, err
+		}
+		bufs, ok := w.getContents(deficit)
+		if ok {
+			return w, bufs, nil
+		}
+	}
+}
+
+// nextWriter walks through the pending buffers and returns the first active
+// Writer.  The writer is removed from the active queue and made 'busy' so that
+// it will not be re-added to the active queue.
+func (q *T) nextWriter(flush bqueue.FlushFunc) (*writer, int, error) {
+	q.mutex.Lock()
+	defer q.mutex.Unlock()
+	for {
+		if q.isClosed {
+			return nil, 0, io.EOF
+		}
+		for p, writers := range q.active {
+			if len(writers) != 0 {
+				w := writers[0]
+				w.isActive = busy
+				w.deficit += q.quantum
+				q.active[p] = writers[1:]
+				return w, w.deficit, nil
+			}
+		}
+		if flush != nil {
+			flush()
+		}
+		q.cond.Wait()
+	}
+}
+
+// addPriorityLocked adds a ready queue with the specified priority level.
+//
+// REQUIRES: q.mutex is locked.
+func (q *T) addPriorityLocked(p bqueue.Priority) {
+	if int(p) >= len(q.active) {
+		newActive := make([][]*writer, int(p)+1)
+		copy(newActive, q.active)
+		q.active = newActive
+	}
+}
+
+// removeWriter removes the queue from the q.
+//
+// NOTE: does not require that w.mutex is locked.
+func (q *T) removeWriter(w *writer) {
+	q.mutex.Lock()
+	if w.isActive == active {
+		// Remove the writer from the active queue.
+		active := q.active[w.priority]
+		for i, w2 := range active {
+			if w2 == w {
+				copy(active[i:], active[i+1:])
+				q.active[w.priority] = active[:len(active)-1]
+				break
+			}
+		}
+	}
+	w.isActive = idle
+	delete(q.writers, w.id)
+	q.mutex.Unlock()
+}
+
+// updateWriterState updates the active state of the queue.
+//
+// REQUIRES: w.mutex is locked.
+func (q *T) updateWriterState(w *writer, overrideBusy bool, isActive bool, consumed int) {
+	q.mutex.Lock()
+	if isActive {
+		if w.isActive == idle || w.isActive == busy && overrideBusy {
+			q.active[w.priority] = append(q.active[w.priority], w)
+			w.isActive = active
+			w.deficit -= consumed
+			if w.deficit < 0 {
+				panic("deficit is negative")
+			}
+			q.cond.Signal()
+		}
+	} else {
+		if w.isActive == active {
+			panic("Writer is active when it should not be")
+		}
+		if overrideBusy {
+			w.isActive = idle
+		}
+		w.deficit = 0
+	}
+	q.mutex.Unlock()
+}
diff --git a/runtime/internal/lib/bqueue/drrqueue/drrqueue_test.go b/runtime/internal/lib/bqueue/drrqueue/drrqueue_test.go
new file mode 100644
index 0000000..dd1d71d
--- /dev/null
+++ b/runtime/internal/lib/bqueue/drrqueue/drrqueue_test.go
@@ -0,0 +1,268 @@
+// 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 drrqueue
+
+import (
+	"log"
+	"runtime"
+	"testing"
+
+	"v.io/x/ref/runtime/internal/lib/bqueue"
+	"v.io/x/ref/runtime/internal/lib/iobuf"
+)
+
+const (
+	testQuantum = 1 << 14 // 16K
+	iobufSize   = 1 << 16 // 64K
+)
+
+// concat concatenates the buffers into a string.
+func concat(bufs []*iobuf.Slice) string {
+	buf := []byte{}
+	for _, b := range bufs {
+		buf = append(buf, b.Contents...)
+		b.Release()
+	}
+	return string(buf)
+}
+
+// mkbufs makes a iobuf.Slice from a string.
+func mkbufs(s string) *iobuf.Slice {
+	return iobuf.NewSlice([]byte(s))
+}
+
+// makeBuffer makes a byte buffer filled with the initial char.
+func makeBuffer(size int, c byte) string {
+	b := make([]byte, size)
+	for i := 0; i != size; i++ {
+		b[i] = c
+	}
+	return string(b)
+}
+
+// A "reader" copies data from the q to a string channel.
+func startReader(q bqueue.T) chan string {
+	c := make(chan string)
+	go func() {
+		for {
+			_, bufs, err := q.Get(nil)
+			if err != nil {
+				log.Printf("Reader: %s", err)
+				break
+			}
+			c <- concat(bufs)
+		}
+		c <- "DONE"
+	}()
+	return c
+}
+
+// expectedGet compares the sequence returned from q.Get() against a sequence
+// of expected strings.
+func expectGet(t *testing.T, q bqueue.T, strings []string) {
+	_, file, line, _ := runtime.Caller(1)
+	for _, s1 := range strings {
+		_, buf, err := q.Get(nil)
+		if err != nil {
+			t.Errorf("%s(%d): Unexpected error: %v", file, line, err)
+			break
+		}
+		s2 := concat(buf)
+		if s2 != s1 {
+			t.Errorf("%s(%d): Expected %q, but received %q", file, line, s1, s2)
+		}
+	}
+}
+
+// TestSimple tests a Put/Release/Get sequence.
+func TestSimple(t *testing.T) {
+	q := New(testQuantum)
+	w, _ := q.NewWriter(0, 0, 5)
+	log.Printf("PutV")
+	w.Put(mkbufs("Hello"), nil)
+	log.Printf("Release")
+	w.Release(5)
+	log.Printf("Get")
+	w2, buf, err := q.Get(nil)
+	if err != nil {
+		t.Errorf("Unexpected error: %s", err)
+	}
+	s := concat(buf)
+	if s != "Hello" {
+		t.Errorf("Expected 'Hello', received %q", s)
+	}
+	if w2 != w {
+		t.Errorf("Writer mistmatch")
+	}
+}
+
+func TestShutdownWithoutRemove(t *testing.T) {
+	q := New(testQuantum)
+	w1, _ := q.NewWriter(0, 0, 100)
+	w2, _ := q.NewWriter(1, 1, 100)
+
+	w1.Put(mkbufs("1_1"), nil)
+	w1.Put(mkbufs("1_2"), nil)
+	w2.Put(mkbufs("2_1"), nil)
+	w2.Put(mkbufs("2_2"), nil)
+
+	w1.Release(3)
+	w2.Release(3)
+
+	w, buf, err := q.Get(nil)
+	if s := concat(buf); err != nil || w.ID() != w1.ID() || s != "1_1" {
+		t.Errorf("Expected '1_1' from 0 with nil error, found %s from %d with %v", s, w.ID(), err)
+	}
+
+	w1.Shutdown(false)
+
+	w, buf, err = q.Get(nil)
+	if s := concat(buf); err != nil || w.ID() != w1.ID() || s != "" {
+		t.Errorf("Expected '' from 0 with nil error, found %s from %d with %v", s, w.ID(), err)
+	}
+
+	// Now we have to remove the writer from q.
+	w1.Shutdown(true)
+
+	w, buf, err = q.Get(nil)
+	if s := concat(buf); err != nil || w.ID() != w2.ID() || s != "2_1" {
+		t.Errorf("Expected '2_1' from 1 with nil error, found %s from %d with %v", s, w.ID(), err)
+	}
+}
+
+// TestRelease tests whether data is released in Release() order.
+func TestRelease(t *testing.T) {
+	q := New(testQuantum)
+	c := startReader(q)
+	w1, _ := q.NewWriter(0, 0, 10)
+	w2, _ := q.NewWriter(1, 0, 10)
+	w1.Put(mkbufs("A1"), nil)
+	w1.Put(mkbufs("A2"), nil)
+	w2.Put(mkbufs("B1"), nil)
+	w2.Put(mkbufs("B2"), nil)
+	select {
+	case s := <-c:
+		t.Errorf("Unexpected Get: %q", s)
+	default:
+	}
+
+	w2.Release(1)
+	select {
+	case s := <-c:
+		t.Errorf("Expected no release, but received %q", s)
+	default:
+	}
+
+	w2.Release(1)
+	s := <-c
+	if s != "B1" {
+		t.Errorf("Expected 'B1', but received %q", s)
+	}
+
+	w1.Release(4)
+	s = <-c
+	if s != "A1A2" {
+		t.Errorf("Expected 'A1', but received %q", s)
+	}
+
+	w1.Release(2)
+	select {
+	case s := <-c:
+		t.Errorf("Unexpected Get: %q", s)
+	default:
+	}
+
+	w1.Put(mkbufs("A3"), nil)
+	s = <-c
+	if s != "A3" {
+		t.Errorf("Expected 'A3', but received %q", s)
+	}
+
+	w2.Release(2)
+	s = <-c
+	if s != "B2" {
+		t.Errorf("Expected 'B2', but received %q", s)
+	}
+
+	select {
+	case s := <-c:
+		t.Errorf("Unexpected Get: %q", s)
+	default:
+	}
+
+	q.Close()
+	s = <-c
+	if s != "DONE" {
+		t.Errorf("Expected 'DONE', but received %q", s)
+	}
+}
+
+// TestPriority tests the priority.
+func TestPriority(t *testing.T) {
+	q := New(testQuantum)
+	w1, _ := q.NewWriter(0, 1, 100)
+	w2, _ := q.NewWriter(1, 0, 100)
+	w1.Release(100)
+	w2.Release(100)
+
+	w1.Put(mkbufs("a"), nil)
+	w1.Put(mkbufs("b"), nil)
+	w2.Put(mkbufs("c"), nil)
+	w2.Put(mkbufs("d"), nil)
+	expectGet(t, q, []string{"cd", "ab"})
+
+	w1.Put(mkbufs("a"), nil)
+	w1.Put(mkbufs("b"), nil)
+	w2.Put(mkbufs("c"), nil)
+	w2.Put(mkbufs("d"), nil)
+	expectGet(t, q, []string{"cd", "ab"})
+}
+
+// TestRoundRobin tests the round robin policy.
+func TestRoundRobin(t *testing.T) {
+	q := New(testQuantum)
+	w1, _ := q.NewWriter(0, 0, 100)
+	w2, _ := q.NewWriter(1, 0, 100)
+	w1.Release(100)
+	w2.Release(100)
+
+	w1.Put(mkbufs("a"), nil)
+	w1.Put(mkbufs("b"), nil)
+	w2.Put(mkbufs("c"), nil)
+	w2.Put(mkbufs("d"), nil)
+	expectGet(t, q, []string{"ab", "cd"})
+
+	w2.Put(mkbufs("a"), nil)
+	w1.Put(mkbufs("b"), nil)
+	w2.Put(mkbufs("c"), nil)
+	w1.Put(mkbufs("d"), nil)
+	w1.Put(mkbufs("e"), nil)
+	expectGet(t, q, []string{"ac", "bde"})
+}
+
+// TestDeficit tests the deficit counter.
+func TestDeficit(t *testing.T) {
+	q := New(testQuantum)
+	w1, _ := q.NewWriter(0, 0, testQuantum*10)
+	w2, _ := q.NewWriter(1, 0, testQuantum*10)
+	w1.Release(-1)
+	w2.Release(-1)
+
+	b1a := makeBuffer(2*testQuantum, '1')
+	b1b := makeBuffer(2*testQuantum, '2')
+	b2a := makeBuffer(testQuantum, '3')
+	b2b := makeBuffer(testQuantum, '4')
+	b2c := makeBuffer(testQuantum, '5')
+	b2d := makeBuffer(testQuantum, '6')
+	b2e := makeBuffer(testQuantum, '7')
+	w1.Put(mkbufs(b1a), nil)
+	w1.Put(mkbufs(b1b), nil)
+	w2.Put(mkbufs(b2a), nil)
+	w2.Put(mkbufs(b2b), nil)
+	w2.Put(mkbufs(b2c), nil)
+	w2.Put(mkbufs(b2d), nil)
+	w2.Put(mkbufs(b2e), nil)
+	expectGet(t, q, []string{b2a, b1a, b2b, b2c, b1b, b2d, b2e})
+}
diff --git a/runtime/internal/lib/dependency/dependency.go b/runtime/internal/lib/dependency/dependency.go
new file mode 100644
index 0000000..758449f
--- /dev/null
+++ b/runtime/internal/lib/dependency/dependency.go
@@ -0,0 +1,127 @@
+// 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 dependency keeps track of a dependency graph.
+// You add edges to the graph by specifying an object and the objects it depends on.
+// You can then call FinsihAndWait when the object is finished to wait until
+// all the dependents are also finished.
+package dependency
+
+import (
+	"fmt"
+	"sync"
+)
+
+var NotFoundError = fmt.Errorf(
+	"Attempting to create an object whose dependency has already been terminated.")
+
+// Every object in a Graph depends on the all key.  We can wait on this key
+// to know when all objects have been closed.
+type all struct{}
+
+type node struct {
+	dependents int
+	cond       *sync.Cond
+	dependsOn  []*node
+}
+
+// Graph keeps track of a number of objects and their dependents.
+// Typical usage looks like:
+//
+// g := NewGraph()
+//
+// // Instruct the graph that A depends on B and C.
+// if err := g.Depend(A, B, C); err != nil {
+//   // Oops, B or C is already terminating, clean up A immediately.
+// }
+// // D depends on A (You should check the error as above).
+// g.Depend(D, A)
+// ...
+// // At some point we want to mark A as closed to new users and
+// // wait for all the objects that depend on it to finish
+// // (in this case D).
+// finish := g.CloseAndWait(A)
+// // Now we know D (and any other depdendents) are finished, so we
+// // can clean up A.
+// A.CleanUp()
+// // Now notify the objects A depended on that they have one less
+// // dependent.
+// finish()
+type Graph struct {
+	mu    sync.Mutex
+	nodes map[interface{}]*node
+}
+
+// NewGraph returns a new Graph ready to be used.
+func NewGraph() *Graph {
+	graph := &Graph{nodes: map[interface{}]*node{}}
+	graph.nodes[all{}] = &node{cond: sync.NewCond(&graph.mu)}
+	return graph
+}
+
+// Depend adds obj as a node in the dependency graph and notes it's
+// dependencies on all the objects in 'on'.  If any of the
+// dependencies are already closed (or are not in the graph at all)
+// then Depend returns NotFoundError and does not add any edges.
+func (g *Graph) Depend(obj interface{}, on ...interface{}) error {
+	g.mu.Lock()
+	defer g.mu.Unlock()
+
+	nodes := make([]*node, len(on)+1)
+	for i := range on {
+		if nodes[i] = g.nodes[on[i]]; nodes[i] == nil {
+			return NotFoundError
+		}
+	}
+	if alln := g.nodes[all{}]; alln == nil {
+		return NotFoundError
+	} else {
+		nodes[len(on)] = alln
+	}
+	for _, n := range nodes {
+		n.dependents++
+	}
+	if n := g.nodes[obj]; n != nil {
+		n.dependsOn = append(n.dependsOn, nodes...)
+	} else {
+		g.nodes[obj] = &node{
+			cond:      sync.NewCond(&g.mu),
+			dependsOn: nodes,
+		}
+	}
+	return nil
+}
+
+// CloseAndWait closes an object to new dependents and waits for all
+// dependants to complete.  When this function returns you can safely
+// clean up Obj knowing that no users remain.  Once obj is finished
+// with the objects it depends on, you should call the returned function.
+func (g *Graph) CloseAndWait(obj interface{}) func() {
+	g.mu.Lock()
+	defer g.mu.Unlock()
+	n := g.nodes[obj]
+	if n == nil {
+		return func() {}
+	}
+	delete(g.nodes, obj)
+	for n.dependents > 0 {
+		n.cond.Wait()
+	}
+	return func() {
+		g.mu.Lock()
+		defer g.mu.Unlock()
+		for _, dn := range n.dependsOn {
+			if dn.dependents--; dn.dependents == 0 {
+				dn.cond.Broadcast()
+			}
+		}
+	}
+}
+
+// CloseAndWaitForAll closes the graph.  No new objects or dependencies can be added
+// and this function returns only after all existing objects have called
+// Finish on their finishers.
+func (g *Graph) CloseAndWaitForAll() {
+	g.CloseAndWait(all{})
+}
diff --git a/runtime/internal/lib/dependency/dependency_test.go b/runtime/internal/lib/dependency/dependency_test.go
new file mode 100644
index 0000000..a47374b
--- /dev/null
+++ b/runtime/internal/lib/dependency/dependency_test.go
@@ -0,0 +1,98 @@
+// 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 dependency
+
+import (
+	"testing"
+	"time"
+)
+
+var nextId = 0
+
+type Dep struct {
+	deps    []*Dep
+	stopped bool
+	id      int
+}
+
+func NewDep(deps ...*Dep) *Dep {
+	d := &Dep{deps: deps, id: nextId}
+	nextId++
+	return d
+}
+
+func (d *Dep) Use(t *testing.T, by *Dep) {
+	if d.stopped {
+		t.Errorf("Object %d using %d after stop.", by.id, d.id)
+	}
+}
+
+func (d *Dep) Stop(t *testing.T) {
+	d.Use(t, d)
+	d.stopped = true
+	for _, dd := range d.deps {
+		dd.Use(t, d)
+	}
+}
+
+func TestGraph(t *testing.T) {
+	a := NewDep()
+	b, c := NewDep(a), NewDep(a)
+	d := NewDep(c)
+
+	g := NewGraph()
+	if err := g.Depend(a); err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+	if err := g.Depend(b, a); err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+	if err := g.Depend(c, a); err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+	if err := g.Depend(d, c); err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	alldone := make(chan struct{})
+	go func() {
+		g.CloseAndWaitForAll()
+		close(alldone)
+	}()
+
+	// Close d, which is a leaf.
+	finish := g.CloseAndWait(d)
+	d.Stop(t)
+	finish()
+
+	// Set a to close and wait which should wait for b and c.
+	done := make(chan struct{})
+	go func() {
+		finish := g.CloseAndWait(a)
+		a.Stop(t)
+		finish()
+		close(done)
+	}()
+
+	// done and alldone shouldn't be finished yet.
+	select {
+	case <-time.After(time.Second):
+	case <-done:
+		t.Errorf("done is finished before it's time")
+	case <-alldone:
+		t.Errorf("alldone is finished before it's time")
+	}
+
+	// Now close b and c.
+	finish = g.CloseAndWait(b)
+	b.Stop(t)
+	finish()
+	finish = g.CloseAndWait(c)
+	c.Stop(t)
+	finish()
+
+	<-done
+	<-alldone
+}
diff --git a/runtime/internal/lib/deque/deque.go b/runtime/internal/lib/deque/deque.go
new file mode 100644
index 0000000..5537fae
--- /dev/null
+++ b/runtime/internal/lib/deque/deque.go
@@ -0,0 +1,119 @@
+// 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 deque implements a deque using a circular array.
+package deque
+
+const (
+	initialQueueSize = 4
+)
+
+// T is the type of queues.
+type T struct {
+	contents []interface{}
+
+	// Boundary cases.
+	// o If full, size==len and fx==bx
+	// o If empty, size==0 and fx==bx
+	// o On initialization, contents=nil, size==0, fx==bx.
+	size int // Number of elements in the queue.
+	fx   int // Index of the first element.
+	bx   int // Index one past the last element (the index of the last element is (bx-1)%len).
+}
+
+// Size returns the number of items in the queue.
+func (q *T) Size() int {
+	return q.size
+}
+
+// Clear removes all the elements of the queue.
+func (q *T) Clear() {
+	q.fx = 0
+	q.bx = 0
+	q.size = 0
+	q.contents = nil
+}
+
+// PushBack adds an element to the back of the queue.
+func (q *T) PushBack(item interface{}) {
+	q.reserve()
+	q.contents[q.bx] = item
+	q.bx = (q.bx + 1) % len(q.contents)
+	q.size++
+}
+
+// PushFront adds an element to the front of the deque.
+func (q *T) PushFront(item interface{}) {
+	q.reserve()
+	q.fx = (q.fx + len(q.contents) - 1) % len(q.contents)
+	q.contents[q.fx] = item
+	q.size++
+}
+
+// Front returns the first element of the deque, or nil if there is none.
+func (q *T) Front() interface{} {
+	if q.size == 0 {
+		return nil
+	}
+	return q.contents[q.fx]
+}
+
+// Back returns the last element of the deque, or nil if there is none.
+func (q *T) Back() interface{} {
+	if q.size == 0 {
+		return nil
+	}
+	return q.contents[(q.bx+len(q.contents)-1)%len(q.contents)]
+}
+
+// PopFront removes an element from the front of the queue and returns it.
+func (q *T) PopFront() interface{} {
+	if q.size == 0 {
+		return nil
+	}
+	item := q.contents[q.fx]
+	q.contents[q.fx] = nil
+	q.fx = (q.fx + 1) % len(q.contents)
+	q.size--
+	return item
+}
+
+// PopBack removes an element from the front of the queue and returns it.
+func (q *T) PopBack() interface{} {
+	if q.size == 0 {
+		return nil
+	}
+	q.bx = (q.bx + len(q.contents) - 1) % len(q.contents)
+	item := q.contents[q.bx]
+	q.contents[q.bx] = nil
+	q.size--
+	return item
+}
+
+// Iter iterates over the elements of the deque.  f should return false to
+// terminate the iteration early.
+func (q *T) Iter(f func(item interface{}) bool) {
+	for i := 0; i != q.size; i++ {
+		ix := (q.fx + i) % len(q.contents)
+		if !f(q.contents[ix]) {
+			break
+		}
+	}
+}
+
+// Reserve space for at least one additional element.
+func (q *T) reserve() {
+	if q.size == len(q.contents) {
+		if q.contents == nil {
+			q.contents = make([]interface{}, initialQueueSize)
+			return
+		}
+		contents := make([]interface{}, q.size*2)
+		i := copy(contents[:], q.contents[q.fx:])
+		copy(contents[i:], q.contents[:q.fx])
+		q.contents = contents
+		q.fx = 0
+		q.bx = q.size
+	}
+}
diff --git a/runtime/internal/lib/deque/deque_test.go b/runtime/internal/lib/deque/deque_test.go
new file mode 100644
index 0000000..2595bf4
--- /dev/null
+++ b/runtime/internal/lib/deque/deque_test.go
@@ -0,0 +1,254 @@
+// 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 deque
+
+import (
+	"testing"
+
+	"v.io/x/ref/test/testutil"
+)
+
+//go:generate v23 test generate
+
+func TestBasic(t *testing.T) {
+	var q T
+	if q.Size() != 0 {
+		t.Errorf("Expected size to be 0, got %d", q.Size())
+	}
+	item := q.PopFront()
+	if item != nil {
+		t.Errorf("Expected front to be nil, got %v", item)
+	}
+	item = q.PopBack()
+	if item != nil {
+		t.Errorf("Expected back to be nil, got %v", item)
+	}
+
+	q.PushFront(1)
+	item = q.PopFront()
+	if item != 1 {
+		t.Errorf("Expected item to be 1, got %v", item)
+	}
+	item = q.PopFront()
+	if item != nil {
+		t.Errorf("Expected back to be nil, got %v", item)
+	}
+
+	q.PushBack(2)
+	item = q.PopBack()
+	if item != 2 {
+		t.Errorf("Expected item to be 2, got %v, %#v", item, q)
+	}
+	item = q.PopBack()
+	if item != nil {
+		t.Errorf("Expected back to be nil, got %v", item)
+	}
+}
+
+func TestBackToFront(t *testing.T) {
+	var q T
+	for i := 0; i != 100; i++ {
+		q.PushBack(i)
+	}
+	for i := 0; i != 100; i++ {
+		item := q.PopFront()
+		if item != i {
+			t.Errorf("Expected %d, got %v", i, item)
+		}
+	}
+	item := q.PopFront()
+	if item != nil {
+		t.Errorf("Expected nil, got %v", item)
+	}
+}
+
+func TestFrontToBack(t *testing.T) {
+	var q T
+	for i := 0; i != 100; i++ {
+		q.PushFront(i)
+	}
+	for i := 0; i != 100; i++ {
+		item := q.PopBack()
+		if item != i {
+			t.Errorf("Expected %d, got %v", i, item)
+		}
+	}
+	item := q.PopBack()
+	if item != nil {
+		t.Errorf("Expected nil, got %v", item)
+	}
+}
+
+func TestFrontToFront(t *testing.T) {
+	var q T
+	for i := 0; i != 100; i++ {
+		q.PushFront(i)
+	}
+	for i := 99; i >= 0; i-- {
+		item := q.PopFront()
+		if item != i {
+			t.Errorf("Expected %d, got %v", i, item)
+		}
+	}
+	item := q.PopFront()
+	if item != nil {
+		t.Errorf("Expected nil, got %v", item)
+	}
+}
+
+func TestBackToBack(t *testing.T) {
+	var q T
+	for i := 0; i != 100; i++ {
+		q.PushBack(i)
+	}
+	for i := 99; i >= 0; i-- {
+		item := q.PopBack()
+		if item != i {
+			t.Errorf("Expected %d, got %v", i, item)
+		}
+	}
+	item := q.PopBack()
+	if item != nil {
+		t.Errorf("Expected nil, got %v", item)
+	}
+}
+
+func TestClear(t *testing.T) {
+	var q T
+	for i := 0; i != 100; i++ {
+		q.PushBack(i)
+	}
+	q.Clear()
+	size := q.Size()
+	if size != 0 {
+		t.Errorf("Expected size 0, but got %d", size)
+	}
+	item := q.PopFront()
+	if item != nil {
+		t.Errorf("Expected front to be nil, got %v", item)
+	}
+	item = q.PopBack()
+	if item != nil {
+		t.Errorf("Expected back to be nil, got %v", item)
+	}
+}
+
+func TestRandom(t *testing.T) {
+	testutil.InitRandGenerator(t.Logf)
+	var q T
+	var contents []int
+	for i := 0; i != 1000; i++ {
+		switch testutil.RandomIntn(4) {
+		case 0:
+			i := testutil.RandomInt()
+			contents = append([]int{i}, contents...)
+			q.PushFront(i)
+		case 1:
+			i := testutil.RandomInt()
+			contents = append(contents, i)
+			q.PushBack(i)
+		case 2:
+			size := len(contents)
+			if q.Size() != size {
+				t.Errorf("Expected size %d, but got %d", size, q.Size())
+			}
+			item := q.PopFront()
+			if size == 0 {
+				if item != nil {
+					t.Errorf("Expected nil, but got %v", item)
+				}
+			} else {
+				if item != contents[0] {
+					t.Errorf("Expected %d, got %d", contents[0], item)
+				}
+				contents = contents[1:]
+			}
+		case 3:
+			size := len(contents)
+			if q.Size() != size {
+				t.Errorf("Expected size %d, got %d", size, q.Size())
+			}
+			item := q.PopBack()
+			if size == 0 {
+				if item != nil {
+					t.Errorf("Expected nil, got %v", item)
+				}
+			} else {
+				if item != contents[len(contents)-1] {
+					t.Errorf("Expected %d, got %d", contents[len(contents)-1], item)
+				}
+				contents = contents[:len(contents)-1]
+			}
+		}
+	}
+}
+
+func TestIter(t *testing.T) {
+	var q T
+
+	// Iterate, quadratic.
+	for i := 0; i != 10; i++ {
+		check := 0
+		q.Iter(func(it interface{}) bool {
+			if it != check {
+				t.Errorf("Expected %d, actual %v", check, it)
+			}
+			check++
+			return true
+		})
+		if check != i {
+			t.Errorf("Queue is too short, expected %d, actual %d", i, check)
+		}
+		q.PushBack(i)
+	}
+
+	// Check short iteration.
+	iterations := 0
+	q.Iter(func(it interface{}) bool {
+		iterations++
+		return it.(int) < 5
+	})
+	if iterations != 6 {
+		t.Errorf("Iteration did not stop correctly,expected 6 iterations, got %d", iterations)
+	}
+}
+
+func BenchmarkPushFront(b *testing.B) {
+	var q T
+	for i := 0; i < b.N; i++ {
+		q.PushFront(i)
+	}
+}
+
+func BenchmarkPushBack(b *testing.B) {
+	var q T
+	for i := 0; i < b.N; i++ {
+		q.PushBack(i)
+	}
+}
+
+func BenchmarkPopFront(b *testing.B) {
+	var q T
+	for i := 0; i < b.N; i++ {
+		q.PushBack(i)
+	}
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		q.PopFront()
+	}
+}
+
+func BenchmarkPopBack(b *testing.B) {
+	var q T
+	for i := 0; i < b.N; i++ {
+		q.PushFront(i)
+	}
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		q.PopBack()
+	}
+}
diff --git a/runtime/internal/lib/deque/v23_internal_test.go b/runtime/internal/lib/deque/v23_internal_test.go
new file mode 100644
index 0000000..e8e5310
--- /dev/null
+++ b/runtime/internal/lib/deque/v23_internal_test.go
@@ -0,0 +1,20 @@
+// 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 deque
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	os.Exit(m.Run())
+}
diff --git a/runtime/internal/lib/framer/errors.vdl b/runtime/internal/lib/framer/errors.vdl
new file mode 100644
index 0000000..0c2f74b
--- /dev/null
+++ b/runtime/internal/lib/framer/errors.vdl
@@ -0,0 +1,14 @@
+// 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 framer
+
+// These messages are constructed so as to avoid embedding a component/method name
+// and are thus more suitable for inclusion in other verrors.
+// This practice of omitting {1}{2} should be used throughout the flow implementations
+// since all of their errors are intended to be used as arguments to higher level errors.
+// TODO(suharshs,toddw): Allow skipping of {1}{2} in vdl generated errors.
+error (
+  LargerThan3ByteUInt() {"en":"integer too large to represent in 3 bytes"}
+)
\ No newline at end of file
diff --git a/runtime/internal/lib/framer/errors.vdl.go b/runtime/internal/lib/framer/errors.vdl.go
new file mode 100644
index 0000000..2da6252
--- /dev/null
+++ b/runtime/internal/lib/framer/errors.vdl.go
@@ -0,0 +1,28 @@
+// 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 by the vanadium vdl tool.
+// Source: errors.vdl
+
+package framer
+
+import (
+	// VDL system imports
+	"v.io/v23/context"
+	"v.io/v23/i18n"
+	"v.io/v23/verror"
+)
+
+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")
+)
+
+func init() {
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrLargerThan3ByteUInt.ID), "{1:}{2:} integer too large to represent in 3 bytes")
+}
+
+// NewErrLargerThan3ByteUInt returns an error with the ErrLargerThan3ByteUInt ID.
+func NewErrLargerThan3ByteUInt(ctx *context.T) error {
+	return verror.New(ErrLargerThan3ByteUInt, ctx)
+}
diff --git a/runtime/internal/lib/framer/framer.go b/runtime/internal/lib/framer/framer.go
new file mode 100644
index 0000000..c4c5d73
--- /dev/null
+++ b/runtime/internal/lib/framer/framer.go
@@ -0,0 +1,107 @@
+// 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 framer
+
+import (
+	"io"
+
+	"v.io/v23/flow"
+	"v.io/v23/flow/message"
+)
+
+// framer is a wrapper of io.ReadWriter that adds framing to a net.Conn
+// and implements flow.MsgReadWriteCloser.
+type framer struct {
+	io.ReadWriteCloser
+	buf []byte
+}
+
+func New(c io.ReadWriteCloser) flow.MsgReadWriteCloser {
+	return &framer{ReadWriteCloser: c}
+}
+
+func (f *framer) WriteMsg(data ...[]byte) (int, error) {
+	// Compute the message size.
+	msgSize := 0
+	for _, b := range data {
+		msgSize += len(b)
+	}
+	// Construct a buffer to write that has space for the 3 bytes of framing.
+	// If a previous buffer is large enough, reuse it.
+	bufSize := msgSize + 3
+	if bufSize > len(f.buf) {
+		f.buf = make([]byte, bufSize)
+	}
+	if err := write3ByteUint(f.buf[:3], msgSize); err != nil {
+		return 0, err
+	}
+	head := 3
+	for _, b := range data {
+		l := len(b)
+		copy(f.buf[head:head+l], b)
+		head += l
+	}
+	// Write the buffer to the io.ReadWriter. Remove the frame size
+	// from the returned number of bytes written.
+	n, err := f.Write(f.buf[:bufSize])
+	if err != nil {
+		return n - 3, err
+	}
+	return n - 3, nil
+}
+
+func (f *framer) ReadMsg() ([]byte, error) {
+	// Read the message size.
+	frame := make([]byte, 3)
+	if _, err := io.ReadFull(f, frame); err != nil {
+		return nil, err
+	}
+	msgSize := read3ByteUint(frame)
+	if msgSize > 0x01ffff {
+		// Although it's possible to have messages up to 16MB, we never
+		// send messages over about 64kb.  temporarily we're using the
+		// arrival of a message > 128kb as a signal that we're talking to
+		// an old version of the protocol.  This means we cannot adjust
+		// the MTU above 128k until after the transition is over.
+		//
+		// In the old protocol we send <type><frame high><frame med><frame low>
+		// Practically the values can be:
+		// <0x00, 0x01, 0x80, 0x81><0x00-0x01><0x00-0xff><0-0xff>
+		//
+		// In the new protocol we send <frame high><frame med><frame low><type>
+		// However, in order to be distinct the frame bytes are actually
+		// 0xffffff - size.  Practically the values can be
+		// <0xff, 0xfe><0x00-0xff><0x00-0xff><0x00-0x07>.
+		//
+		// This means if we receive an old protocol message, we should get
+		// a very large size.
+		// TODO(mattr): Remove this.
+		return nil, message.NewErrWrongProtocol(nil)
+	}
+
+	// Read the message.
+	msg := make([]byte, msgSize)
+	if _, err := io.ReadFull(f, msg); err != nil {
+		return nil, err
+	}
+	return msg, nil
+}
+
+const maxPacketSize = 0xffffff
+
+func write3ByteUint(dst []byte, n int) error {
+	if n > maxPacketSize || n < 0 {
+		return NewErrLargerThan3ByteUInt(nil)
+	}
+	n = maxPacketSize - n
+	dst[0] = byte((n & 0xff0000) >> 16)
+	dst[1] = byte((n & 0x00ff00) >> 8)
+	dst[2] = byte(n & 0x0000ff)
+	return nil
+}
+
+func read3ByteUint(src []byte) int {
+	return maxPacketSize - (int(src[0])<<16 | int(src[1])<<8 | int(src[2]))
+}
diff --git a/runtime/internal/lib/framer/framer_test.go b/runtime/internal/lib/framer/framer_test.go
new file mode 100644
index 0000000..bc1aef1
--- /dev/null
+++ b/runtime/internal/lib/framer/framer_test.go
@@ -0,0 +1,56 @@
+// 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 framer
+
+import (
+	"bytes"
+	"testing"
+)
+
+type readWriteCloser struct {
+	bytes.Buffer
+}
+
+func (*readWriteCloser) Close() error {
+	return nil
+}
+
+func TestFramer(t *testing.T) {
+	f := &framer{ReadWriteCloser: &readWriteCloser{}}
+	bufs := [][]byte{[]byte("read "), []byte("this "), []byte("please.")}
+	want := []byte("read this please.")
+	l := len(want)
+	if n, err := f.WriteMsg(bufs...); err != nil || n != l {
+		t.Fatalf("got %v, %v, want %v, nil", n, err, l)
+	}
+	if got, err := f.ReadMsg(); err != nil || !bytes.Equal(got, want) {
+		t.Errorf("got %v, %v, want %v, nil", got, err, want)
+	}
+	// Framing a smaller message afterwards should reuse the internal buffer
+	// from the first sent message.
+	bufs = [][]byte{[]byte("read "), []byte("this "), []byte("too.")}
+	want = []byte("read this too.")
+	oldBufferLen := l + 3
+	l = len(want)
+	if n, err := f.WriteMsg(bufs...); err != nil || n != l {
+		t.Fatalf("got %v, %v, want %v, nil", n, err, l)
+	}
+	if got, err := f.ReadMsg(); err != nil || !bytes.Equal(got, want) {
+		t.Errorf("got %v, %v, want %v, nil", got, err, want)
+	}
+	if len(f.buf) != oldBufferLen {
+		t.Errorf("framer internal buffer should have been reused")
+	}
+	// Sending larger message afterwards should work as well.
+	bufs = [][]byte{[]byte("read "), []byte("this "), []byte("way bigger message.")}
+	want = []byte("read this way bigger message.")
+	l = len(want)
+	if n, err := f.WriteMsg(bufs...); err != nil || n != l {
+		t.Fatalf("got %v, %v, want %v, nil", n, err, l)
+	}
+	if got, err := f.ReadMsg(); err != nil || !bytes.Equal(got, want) {
+		t.Errorf("got %v, %v, want %v, nil", got, err, want)
+	}
+}
diff --git a/runtime/internal/lib/iobuf/allocator.go b/runtime/internal/lib/iobuf/allocator.go
new file mode 100644
index 0000000..f586c39
--- /dev/null
+++ b/runtime/internal/lib/iobuf/allocator.go
@@ -0,0 +1,74 @@
+// 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 iobuf
+
+import "v.io/x/ref/internal/logger"
+
+// Allocator is an allocator for Slices that tries to allocate contiguously.
+// That is, sequential allocations will tend to be contiguous, which means
+// that Coalesce() will usually be able to perform coalescing (without
+// copying the data).
+//
+//    calloc := iobuf.Allocator(...)
+//    slice1 := calloc.Alloc(10)
+//    slice2 := calloc.Alloc(20)
+//    slices := iobuf.Coalesce([]*iobuf.Slice{slice1, slice2})
+//    // slices should contain 1 element with length 30.
+type Allocator struct {
+	pool    *Pool
+	iobuf   *buf
+	index   uint
+	reserve uint
+}
+
+// NewAllocator returns a new Slice allocator.
+//
+// <reserve> is the number of spare bytes to reserve at the beginning of
+// each allocated Slice. This can be used to reserve space for a header,
+// for example.
+//
+// NOTE: It's a bit weird to set the number of reserve bytes in the NewAllocator
+// call; it seems more natural in the Alloc call. But it's convenient to set it
+// in NewAllocator, because in our use-cases, the code that calls Alloc doesn't
+// know the number of reserve bytes.
+func NewAllocator(pool *Pool, reserve uint) *Allocator {
+	return &Allocator{pool: pool, reserve: reserve}
+}
+
+// Release releases the allocator.
+func (a *Allocator) Release() {
+	if a.iobuf != nil {
+		a.iobuf.release()
+		a.iobuf = nil
+	}
+	a.pool = nil
+}
+
+// Alloc allocates a new Slice.
+func (a *Allocator) Alloc(bytes uint) *Slice {
+	n := bytes + a.reserve
+	if a.iobuf == nil {
+		if a.pool == nil {
+			logger.Global().Info("iobuf.Allocator has already been closed")
+			return nil
+		}
+		a.iobuf = a.pool.alloc(n)
+	} else if uint(len(a.iobuf.Contents)) < a.index+n {
+		a.iobuf.release()
+		a.iobuf = a.pool.alloc(n)
+		a.index = 0
+	}
+	free := a.index
+	base := free + a.reserve
+	a.index += uint(n)
+	return a.iobuf.slice(free, base, a.index)
+}
+
+// Copy allocates a Slice and copies the buf into it.
+func (a *Allocator) Copy(buf []byte) *Slice {
+	slice := a.Alloc(uint(len(buf)))
+	copy(slice.Contents, buf)
+	return slice
+}
diff --git a/runtime/internal/lib/iobuf/allocator_test.go b/runtime/internal/lib/iobuf/allocator_test.go
new file mode 100644
index 0000000..86a5bc4
--- /dev/null
+++ b/runtime/internal/lib/iobuf/allocator_test.go
@@ -0,0 +1,89 @@
+// 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 iobuf
+
+import (
+	"fmt"
+	"testing"
+)
+
+func testAllocatorAlloc(t *testing.T, bytes, reserve uint) {
+	pool := NewPool(iobufSize)
+	defer pool.Close()
+	alloc := NewAllocator(pool, reserve)
+	defer alloc.Release()
+
+	const count = 1000
+	var slices [count]*Slice
+	for i := 0; i != count; i++ {
+		slices[i] = alloc.Alloc(bytes)
+		copy(slices[i].Contents, []byte(fmt.Sprintf("slice[%d]", i)))
+	}
+	for i := 0; i != count; i++ {
+		expected := fmt.Sprintf("slice[%d]", i)
+		expectEq(t, expected, string(slices[i].Contents[0:len(expected)]))
+		if slices[i].ExpandFront(reserve + 1) {
+			t.Errorf("slice[%d] should not have a reserved byte %d", i, reserve+1)
+		}
+		if !slices[i].ExpandFront(reserve) {
+			t.Errorf("slice[%d] should have a reserved byte %d", i, reserve)
+		}
+		slices[i].Release()
+	}
+}
+
+func TestAllocatorAllocSmallWitReserve_0(t *testing.T)   { testAllocatorAlloc(t, 50, 0) }
+func TestAllocatorAllocSmallWitReserve_10(t *testing.T)  { testAllocatorAlloc(t, 50, 10) }
+func TestAllocatorAllocSmallWitReserve_100(t *testing.T) { testAllocatorAlloc(t, 50, 100) }
+
+func TestAllocatorAllocLargeWitReserve_0(t *testing.T)   { testAllocatorAlloc(t, 1000, 0) }
+func TestAllocatorAllocLargeWitReserve_10(t *testing.T)  { testAllocatorAlloc(t, 1000, 10) }
+func TestAllocatorAllocLargeWitReserve_100(t *testing.T) { testAllocatorAlloc(t, 1000, 100) }
+
+func testAllocatorCopy(t *testing.T, reserve uint) {
+	pool := NewPool(iobufSize)
+	defer pool.Close()
+	alloc := NewAllocator(pool, reserve)
+	defer alloc.Release()
+
+	const count = 1000
+	var slices [count]*Slice
+	for i := 0; i != count; i++ {
+		slices[i] = alloc.Copy([]byte(fmt.Sprintf("slice[%d]", i)))
+	}
+	for i := 0; i != count; i++ {
+		expectEq(t, fmt.Sprintf("slice[%d]", i), string(slices[i].Contents))
+		if slices[i].ExpandFront(reserve + 1) {
+			t.Errorf("slice[%d] should not have a reserved byte %d", i, reserve+1)
+		}
+		if !slices[i].ExpandFront(reserve) {
+			t.Errorf("slice[%d] should have a reserved byte %d", i, reserve)
+		}
+		slices[i].Release()
+	}
+}
+
+func TestAllocatorCopyWitReserve_0(t *testing.T)   { testAllocatorCopy(t, 0) }
+func TestAllocatorCopyWitReserve_10(t *testing.T)  { testAllocatorCopy(t, 10) }
+func TestAllocatorCopyWitReserve_100(t *testing.T) { testAllocatorCopy(t, 100) }
+
+// Check that the Allocator is unusable after it is closed.
+func TestAllocatorClose(t *testing.T) {
+	pool := NewPool(iobufSize)
+	defer pool.Close()
+	alloc := NewAllocator(NewPool(iobufSize), 0)
+
+	slice := alloc.Alloc(10)
+	if slice == nil {
+		t.Fatalf("slice should not be nil")
+	}
+	slice.Release()
+
+	alloc.Release()
+	slice = alloc.Alloc(10)
+	if slice != nil {
+		t.Errorf("slice should be nil")
+	}
+}
diff --git a/runtime/internal/lib/iobuf/iobuf.go b/runtime/internal/lib/iobuf/iobuf.go
new file mode 100644
index 0000000..7c64eda
--- /dev/null
+++ b/runtime/internal/lib/iobuf/iobuf.go
@@ -0,0 +1,145 @@
+// 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 iobuf performs explicit memory management for data buffers used
+// to perform network IO.  The intent is that it is more efficient to perform
+// manual allocation than to rely on the Go garbage collector to manage large
+// chunks of frequently recycled memory.
+//
+// In this model, a Pool is a collection of contiguous memory area (of type
+// <buf>) used for memory allocation.  The bufs are subdivided into slices of
+// type Slice.
+//
+//     Pool: a source of memory areas.
+//     Slice: a contiguous region of allocated memory.
+//     Allocator: a Slice allocator.
+//     Reader: an IO reader that reads into Slices.
+//
+// There is an analogy with sbrk/malloc: the Pool is the source of memory (like
+// sbrk), and the Allocator hands out small areas (like malloc).  Allocations
+// are mostly sequential within a buf, allowing sequentially-allocated Slices to
+// be coalesced at some later point.
+//
+// For efficiency, Slice values hold reference counts to the underlying buf.
+// When all references are to a buf released, the buf is recycled into its Pool.
+// This does not happen automatically.  The caller is responsible for calling
+// slice.Release() when finished using a slice.
+package iobuf
+
+import (
+	"sync"
+	"sync/atomic"
+
+	"v.io/x/ref/internal/logger"
+)
+
+// A iobuf is a storage space for memory read from the network.  The data should
+// be read into the Contents field, then sliced up into Slice slices that
+// correspond to header, payload, etc.
+//
+// iobufs are reference counted.  The count includes one reference for the iobuf
+// itself, plus one for each Slice.
+type buf struct {
+	refcount int32 // Use atomic operations.
+	Contents []byte
+	pool     *Pool
+}
+
+// Pool manages a pool of iobufs.  The size of the pool is not fixed,
+// it can grow without bound.
+//
+// The implementation here allocates a new iobuf whenever there is an allocation
+// request and the pool is empty.  For iobufs to be recycled, explicit Release()
+// calls are required.  However, if these Release() calls are missing, the
+// program will continue to function, recycling the buffers through the gc.
+// Therefore, if you forget Release() calls, you will be putting pressure on gc
+// to recycle the iobufs.  You can examine the <allocated> field to check how
+// many iobufs have been allocated during the lifetime of the Pool.
+type Pool struct {
+	minSize   uint
+	mutex     sync.Mutex
+	freelist  []*buf
+	allocated uint64 // Total number of iobufs allocated.
+}
+
+const defaultMinSize = 1 << 12
+
+// NewPool creates a new pool. The pool will allocate iobufs in multiples of minSize.
+// If minSize is zero, the default value (4K) will be used.
+func NewPool(minSize uint) *Pool {
+	if minSize == 0 {
+		minSize = defaultMinSize
+	}
+	return &Pool{minSize: minSize, freelist: []*buf{}}
+}
+
+// Close shuts down the Pool.
+func (pool *Pool) Close() {
+	pool.mutex.Lock()
+	pool.freelist = nil
+	pool.mutex.Unlock()
+}
+
+// alloc allocates a new iobuf.  The returned iobuf has at least <size> bytes of free space.
+func (pool *Pool) alloc(size uint) *buf {
+	if size == 0 {
+		size = pool.minSize
+	} else if r := size % pool.minSize; r > 0 {
+		size += pool.minSize - r
+	}
+
+	pool.mutex.Lock()
+	defer pool.mutex.Unlock()
+	if pool.freelist == nil {
+		logger.Global().Info("iobuf.Pool is closed")
+		return nil
+	}
+
+	// Search for an iobuf that is large enough.
+	for i := len(pool.freelist) - 1; i >= 0; i-- {
+		iobuf := pool.freelist[i]
+		if uint(len(iobuf.Contents)) >= size {
+			pool.freelist[i] = pool.freelist[len(pool.freelist)-1]
+			pool.freelist = pool.freelist[:len(pool.freelist)-1]
+			atomic.AddInt32(&iobuf.refcount, 1)
+			return iobuf
+		}
+	}
+
+	// All the free buffers are too small.  Allocate a fresh one.
+	pool.allocated++
+	iobuf := &buf{refcount: 1, Contents: make([]byte, size), pool: pool}
+	return iobuf
+}
+
+// release recycles an iobuf that has a zero refcount.
+func (pool *Pool) release(iobuf *buf) {
+	pool.mutex.Lock()
+	defer pool.mutex.Unlock()
+	// TODO(jyh): Ideally we would like to overwrite the iobuf so that if there
+	// are slices still referring to it (due to a double-Release), it will be
+	// more likely that the problem is noticed.  Implement this if we define a
+	// "debug mode."
+	if pool.freelist != nil {
+		pool.freelist = append(pool.freelist, iobuf)
+	}
+}
+
+// release decrements the iobuf's refcount, recycling the iobuf when the count
+// reaches zero.
+func (iobuf *buf) release() {
+	refcount := atomic.AddInt32(&iobuf.refcount, -1)
+	if refcount < 0 {
+		logger.Global().Infof("Refcount is negative: %d.  This is a bug in the program.", refcount)
+	}
+	if refcount == 0 {
+		iobuf.pool.release(iobuf)
+	}
+}
+
+// slice creates an Slice that refers to a slice of the iobuf contents.
+func (iobuf *buf) slice(free, base, bound uint) *Slice {
+	atomic.AddInt32(&iobuf.refcount, 1)
+	return &Slice{iobuf: iobuf, free: free, base: base, Contents: iobuf.Contents[base:bound]}
+}
diff --git a/runtime/internal/lib/iobuf/iobuf_test.go b/runtime/internal/lib/iobuf/iobuf_test.go
new file mode 100644
index 0000000..377debb
--- /dev/null
+++ b/runtime/internal/lib/iobuf/iobuf_test.go
@@ -0,0 +1,121 @@
+// 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 iobuf
+
+import (
+	"runtime"
+	"sync"
+	"testing"
+)
+
+const (
+	iobufSize = 1 << 16
+)
+
+func expectEq(t *testing.T, x, y interface{}) {
+	if x != y {
+		_, file, line, _ := runtime.Caller(1)
+		t.Errorf("%s(%d): expected %v, actual %v", file, line, x, y)
+	}
+}
+
+// Test basic reference counting.
+func TestFreelist(t *testing.T) {
+	pool := NewPool(iobufSize)
+	defer pool.Close()
+
+	iobuf := pool.alloc(0)
+	expectEq(t, iobufSize, len(iobuf.Contents))
+	expectEq(t, uint64(1), pool.allocated)
+	expectEq(t, 0, len(pool.freelist))
+	iobuf.release()
+
+	expectEq(t, 1, len(pool.freelist))
+	iobuf = pool.alloc(0)
+	expectEq(t, 0, len(pool.freelist))
+	pool.alloc(0).release()
+	iobuf.release()
+	expectEq(t, 2, len(pool.freelist))
+}
+
+// Test slice reference counting.
+func TestRefcount(t *testing.T) {
+	pool := NewPool(iobufSize)
+	defer pool.Close()
+
+	iobuf := pool.alloc(0)
+	slice1 := iobuf.slice(0, 0, 10)
+	slice2 := iobuf.slice(10, 10, 20)
+	iobuf.release()
+	expectEq(t, 0, len(pool.freelist))
+	slice1.Release()
+	expectEq(t, 0, len(pool.freelist))
+	slice2.Release()
+	expectEq(t, 1, len(pool.freelist))
+}
+
+// Check that the Pool is unusable after it is closed.
+func TestPoolClose(t *testing.T) {
+	pool := NewPool(iobufSize)
+
+	iobuf := pool.alloc(1024)
+	if iobuf == nil {
+		t.Fatalf("iobuf should not be nil")
+	}
+	iobuf.release()
+	pool.Close()
+	iobuf = pool.alloc(1024)
+	if iobuf != nil {
+		t.Errorf("iobuf should be nil")
+	}
+}
+
+func TestIOBUFConcurrency(t *testing.T) {
+	pool := NewPool(iobufSize)
+	defer pool.Close()
+
+	const threadCount = 100
+
+	var pending sync.WaitGroup
+	pending.Add(threadCount)
+	for i := 0; i != threadCount; i++ {
+		go func() {
+			iobufThrasher(t, pool)
+			pending.Done()
+		}()
+	}
+	pending.Wait()
+}
+
+func iobufThrasher(t *testing.T, pool *Pool) {
+	const (
+		iobufCount = 100
+		sliceCount = 100
+	)
+	message := "Hello world"
+	for i := 0; i != iobufCount; i++ {
+		iobuf := pool.alloc(0)
+		var slices []*Slice
+		var base uint
+		for j := 0; j != sliceCount; j++ {
+			if base+uint(len(message)) > uint(len(iobuf.Contents)) {
+				iobuf.release()
+				iobuf = pool.alloc(0)
+			}
+			slice := iobuf.slice(base, base, base+uint(len(message)))
+			base += uint(len(message))
+			copy(slice.Contents, []byte(message))
+			slices = append(slices, slice)
+		}
+		iobuf.release()
+		for _, slice := range slices {
+			content := string(slice.Contents)
+			if content != message {
+				t.Errorf("Expected %q, got %q", message, content)
+			}
+			slice.Release()
+		}
+	}
+}
diff --git a/runtime/internal/lib/iobuf/reader.go b/runtime/internal/lib/iobuf/reader.go
new file mode 100644
index 0000000..50954ea
--- /dev/null
+++ b/runtime/internal/lib/iobuf/reader.go
@@ -0,0 +1,71 @@
+// 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 iobuf
+
+import (
+	"io"
+)
+
+// Reader wraps an io.Reader to provide a buffered Read() operation.
+type Reader struct {
+	pool        *Pool
+	iobuf       *buf
+	base, bound uint
+	conn        io.Reader
+}
+
+// NewReader returns a new io reader.
+func NewReader(pool *Pool, conn io.Reader) *Reader {
+	iobuf := pool.alloc(0)
+	return &Reader{pool: pool, iobuf: iobuf, conn: conn}
+}
+
+// Close closes the Reader.  Do not call this concurrently with Read.  Instead,
+// close r.conn, wait until Read() has completed, then perform the Close().
+func (r *Reader) Close() {
+	r.iobuf.release()
+	r.pool = nil
+	r.iobuf = nil
+}
+
+// Fill ensures that the input contains at least <bytes> bytes.  Returns an
+// error iff the input is short (even if some input was read).
+func (r *Reader) fill(bytes uint) error {
+	if r.bound-r.base >= bytes {
+		return nil
+	}
+
+	// If there is not enough space to read the data contiguously, allocate a
+	// new iobuf.
+	if uint(len(r.iobuf.Contents))-r.base < bytes {
+		iobuf := r.pool.alloc(bytes)
+		r.bound = uint(copy(iobuf.Contents, r.iobuf.Contents[r.base:r.bound]))
+		r.base = 0
+		r.iobuf.release()
+		r.iobuf = iobuf
+	}
+
+	// Read into the iobuf.
+	for r.bound-r.base < bytes {
+		amount, err := r.conn.Read(r.iobuf.Contents[r.bound:])
+		if amount == 0 && err != nil {
+			return err
+		}
+		r.bound += uint(amount)
+	}
+	return nil
+}
+
+// Read returns the next <n> bytes of input as a Slice.  Returns an error if the
+// read was short, even if some input was read.
+func (r *Reader) Read(n int) (*Slice, error) {
+	bytes := uint(n)
+	if err := r.fill(bytes); err != nil {
+		return nil, err
+	}
+	slice := r.iobuf.slice(r.base, r.base, r.base+bytes)
+	r.base += bytes
+	return slice, nil
+}
diff --git a/runtime/internal/lib/iobuf/reader_test.go b/runtime/internal/lib/iobuf/reader_test.go
new file mode 100644
index 0000000..a5cb1a9
--- /dev/null
+++ b/runtime/internal/lib/iobuf/reader_test.go
@@ -0,0 +1,66 @@
+// 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 iobuf
+
+import (
+	"io"
+	"testing"
+)
+
+const (
+	maxReadSize = 10
+)
+
+type testReader struct {
+	off      int
+	isClosed bool
+}
+
+func (r *testReader) Read(buf []byte) (int, error) {
+	if r.isClosed {
+		return 0, io.EOF
+	}
+	amount := len(buf)
+	for i := 0; i != amount; i++ {
+		buf[i] = charAt(r.off + i)
+	}
+	r.off += amount
+	return amount, nil
+}
+
+func TestReader(t *testing.T) {
+	pool := NewPool(iobufSize)
+	defer pool.Close()
+
+	var tr testReader
+	r := NewReader(pool, &tr)
+	defer r.Close()
+
+	const amount = 4
+	const loopCount = 1000
+	for off := 0; off < loopCount*amount; off += amount {
+		s, err := r.Read(amount)
+		if err != nil {
+			t.Errorf("Unexpected error: %v", err)
+		}
+		checkBuf(t, s.Contents, off)
+		s.Release()
+	}
+
+	tr.isClosed = true
+	for off := amount * loopCount; off != tr.off; off++ {
+		s, err := r.Read(1)
+		if err != nil {
+			t.Errorf("Unexpected error: %v", err)
+		}
+		checkBuf(t, s.Contents, off)
+		s.Release()
+	}
+
+	_, err := r.Read(1)
+	if err == nil {
+		t.Errorf("Expected error")
+	}
+}
diff --git a/runtime/internal/lib/iobuf/slice.go b/runtime/internal/lib/iobuf/slice.go
new file mode 100644
index 0000000..d48a463
--- /dev/null
+++ b/runtime/internal/lib/iobuf/slice.go
@@ -0,0 +1,95 @@
+// 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 iobuf
+
+// Slice refers to an iobuf and the byte slice for the actual data.
+type Slice struct {
+	iobuf    *buf
+	free     uint   // Free area before base, if any.
+	base     uint   // Index into the underlying iobuf.
+	Contents []byte // iobuf.Contents[base:bound]
+}
+
+// Size returns the number of bytes in the Slice.
+func (slice *Slice) Size() int {
+	return len(slice.Contents)
+}
+
+// FreeEntirePrefix sets the free index to zero.  Be careful when using this,
+// you should ensure that no Slices are using the free region.
+func (slice *Slice) FreeEntirePrefix() {
+	slice.free = 0
+}
+
+// Release releases the slice, decrementing the reference count on the iobuf
+// and destroying the slice.
+func (slice *Slice) Release() {
+	if slice.iobuf != nil {
+		slice.iobuf.release()
+		slice.iobuf = nil
+	}
+	slice.Contents = nil
+}
+
+// ReleasePrevious releases the <prev> slice, extending the free prefix of the
+// target slice if possible.
+func (slice *Slice) ReleasePrevious(prev *Slice) {
+	if prev.iobuf == slice.iobuf && prev.base+uint(len(prev.Contents)) == slice.free {
+		slice.free = prev.free
+	}
+	prev.Release()
+}
+
+// TruncateFront removes <bytes> from the front of the Slice.
+func (slice *Slice) TruncateFront(bytes uint) {
+	if bytes > uint(len(slice.Contents)) {
+		bytes = uint(len(slice.Contents))
+	}
+	slice.base += bytes
+	slice.Contents = slice.Contents[bytes:]
+}
+
+// ExpandFront tries to expand the Slice by <bytes> before the front of the Slice.
+// Returns true if the Slice was expanded.
+func (slice *Slice) ExpandFront(bytes uint) bool {
+	if slice.free+bytes > slice.base || slice.iobuf == nil {
+		return false
+	}
+	bound := slice.base + uint(len(slice.Contents))
+	slice.base -= bytes
+	slice.Contents = slice.iobuf.Contents[slice.base:bound]
+	return true
+}
+
+// Coalesce a sequence of slices.  If two slices are adjacent, they are
+// combined.  Takes ownership of the slices, caller takes ownership of the
+// result.
+func Coalesce(slices []*Slice, maxSize uint) []*Slice {
+	if len(slices) <= 1 {
+		return slices
+	}
+	var result []*Slice
+	c := slices[0]
+	for i := 1; i != len(slices); i++ {
+		s := slices[i]
+		if uint(len(c.Contents)+len(s.Contents)) <= maxSize &&
+			c.iobuf != nil && s.iobuf == c.iobuf &&
+			c.base+uint(len(c.Contents)) == s.base {
+			// The two slices are adjacent.  Merge them.
+			c.Contents = c.iobuf.Contents[c.base : s.base+uint(len(s.Contents))]
+			s.Release()
+		} else {
+			result = append(result, c)
+			c = s
+		}
+	}
+	return append(result, c)
+}
+
+// NewSlice creates a Slice from a byte array.  The value is not copied into an
+// iobuf, it continues to refer to the buffer that was passed in.
+func NewSlice(buf []byte) *Slice {
+	return &Slice{Contents: buf}
+}
diff --git a/runtime/internal/lib/iobuf/slice_test.go b/runtime/internal/lib/iobuf/slice_test.go
new file mode 100644
index 0000000..c5cc00e
--- /dev/null
+++ b/runtime/internal/lib/iobuf/slice_test.go
@@ -0,0 +1,81 @@
+// 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 iobuf
+
+import (
+	"testing"
+)
+
+func TestExpandFront(t *testing.T) {
+	pool := NewPool(iobufSize)
+	defer pool.Close()
+	alloc := NewAllocator(pool, 8)
+	defer alloc.Release()
+
+	slice := alloc.Alloc(10)
+	if slice.Size() != 10 {
+		t.Errorf("Expected length 10, got %d", slice.Size())
+	}
+	copy(slice.Contents, []byte("0123456789"))
+	ok := slice.ExpandFront(8)
+	if !ok {
+		t.Errorf("Expected ExpandFront to succeed")
+	}
+	if slice.Size() != 18 {
+		t.Errorf("Expected length 18, got %d", slice.Size())
+	}
+	slice.TruncateFront(11)
+	if slice.Size() != 7 {
+		t.Errorf("Expected length 9, got %d", slice.Size())
+	}
+	ok = slice.ExpandFront(3)
+	if slice.Size() != 10 {
+		t.Errorf("Expected length 10, got %d", slice.Size())
+	}
+	if string(slice.Contents) != "0123456789" {
+		t.Errorf("Expected 0123456789, got %q", string(slice.Contents))
+	}
+	ok = slice.ExpandFront(10)
+	if ok {
+		t.Errorf("Expected expansion to fail")
+	}
+}
+
+func TestCoalesce(t *testing.T) {
+	pool := NewPool(iobufSize)
+	defer pool.Close()
+	alloc := NewAllocator(pool, 0)
+	defer alloc.Release()
+
+	const count = 1000
+	const blocksize = 1024
+	var slices [count]*Slice
+	for i := 0; i != count; i++ {
+		var block [blocksize]byte
+		for j := 0; j != blocksize; j++ {
+			block[j] = charAt(i*blocksize + j)
+		}
+		slices[i] = alloc.Copy(block[:])
+	}
+	coalesced := Coalesce(slices[:], blocksize*4)
+	expectEq(t, count/4, len(coalesced))
+
+	off := 0
+	for _, buf := range coalesced {
+		checkBuf(t, buf.Contents, off)
+		off += len(buf.Contents)
+		buf.Release()
+	}
+}
+
+func charAt(i int) byte {
+	return "0123456789abcedf"[i%16]
+}
+
+func checkBuf(t *testing.T, buf []byte, off int) {
+	for i, c := range buf {
+		expectEq(t, charAt(off+i), c)
+	}
+}
diff --git a/runtime/internal/lib/pcqueue/pcqueue.go b/runtime/internal/lib/pcqueue/pcqueue.go
new file mode 100644
index 0000000..c1b0602
--- /dev/null
+++ b/runtime/internal/lib/pcqueue/pcqueue.go
@@ -0,0 +1,155 @@
+// 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.
+
+// A producer/consumer queue is a concurrent bounded buffer supporting
+// multiple concurrent producers and consumers, with timeouts.  The queue can be
+// closed from either end, by the producer and/or the consumer.  When closed,
+// the contents are discarded, and subsequent operations return an error.
+//
+// Note: the main reason to use a producer/consumer queue instead of a channel
+// is to allow the consumer to close the channel.  This queue can be used for
+// many-to-many communication with multiple producers and/or multiple consumers.
+// Any of the producers and any of the consumers are allowed to close the
+// queue.
+package pcqueue
+
+import (
+	"errors"
+	"sync"
+)
+
+var (
+	ErrQueueIsClosed = errors.New("queue is closed")
+	ErrCancelled     = errors.New("operation was canceled")
+	ErrTryAgain      = errors.New("operation failed, try again")
+)
+
+// T is a producer/consumer queue.  It fulfills the same purpose as a Go
+// channel, the main advantage is that the Put() operation does not panic, even
+// after the queue is closed.  The main disadvantage is that the T can't
+// be used in a select operation.
+type T struct {
+	// The mutex R/W mode depends only on whether the immediate struct fields
+	// are being read or modified.  It isn't related to whether the channel
+	// operations are mutating.  For example, the Put() method takes a read lock
+	// because it reads the contents and isClosed fields.  It mutates the
+	// contents channel, but that doesn't matter.
+	mutex    sync.RWMutex
+	contents chan interface{} // GUARDED_BY(mutex)
+	isClosed bool             // GUARDED_BY(mutex), true iff <closed> is closed.
+
+	closed chan struct{}
+}
+
+// New(size) returns a producer/consumer queue with maximum
+// <size> elements.
+func New(maxSize int) *T {
+	return &T{
+		contents: make(chan interface{}, maxSize),
+		closed:   make(chan struct{})}
+}
+
+// Put(item, cancel) adds an item to the queue, or returns an error if the queue
+// is closed or the operation is cancelled.  The <cancel> channel may be nil, in
+// which case the operation can't be cancelled.
+func (q *T) Put(item interface{}, cancel <-chan struct{}) error {
+	contents := q.putChannel()
+	select {
+	case contents <- item:
+	case <-q.closed:
+		return ErrQueueIsClosed
+	case <-cancel:
+		return ErrCancelled
+	}
+	return nil
+}
+
+// Get(cancel) returns the next item from the queue, or an error if
+// the queue is closed or the operation is cancelled.
+func (q *T) Get(cancel <-chan struct{}) (interface{}, error) {
+	contents := q.getChannel()
+	select {
+	case v := <-contents:
+		return v, nil
+	case <-q.closed:
+		return q.drain()
+	case <-cancel:
+		return nil, ErrCancelled
+	}
+}
+
+// TryPut attempts to add an item to the queue.  If the queue is full,
+// ErrTryAgain is returned immediately, without blocking.  If the queue is
+// closed, ErrQueueIsClosed is returned.
+func (q *T) TryPut(item interface{}) error {
+	contents := q.putChannel()
+	select {
+	case contents <- item:
+		return nil
+	default:
+	}
+	q.mutex.RLock()
+	defer q.mutex.RUnlock()
+	if q.isClosed {
+		return ErrQueueIsClosed
+	}
+	return ErrTryAgain
+}
+
+// Close() closes the queue, without discarding the contents.  All Put*() operations
+// currently running may, or may not, add their values to the queue.  All Put*()
+// operations that happen-after the Close() will fail.
+func (q *T) Close() {
+	q.mutex.Lock()
+	if !q.isClosed {
+		q.isClosed = true
+		close(q.closed)
+	}
+	q.mutex.Unlock()
+}
+
+// Shutdown() closes the queue and discards all contents.  Any concurrent Get()
+// and Put() operations might exchange values, but all operations that
+// happen-after the Shutdown() will fail.
+func (q *T) Shutdown() {
+	q.mutex.Lock()
+	if !q.isClosed {
+		q.isClosed = true
+		close(q.closed)
+	}
+	q.contents = nil
+	q.mutex.Unlock()
+}
+
+// putChannel() returns a channel for inserting new values.  Returns nil if
+// the queue has been closed.
+func (q *T) putChannel() chan interface{} {
+	q.mutex.RLock()
+	defer q.mutex.RUnlock()
+	if q.isClosed {
+		return nil
+	}
+	return q.contents
+}
+
+// getChannel() returns the <contents> channel.
+func (q *T) getChannel() chan interface{} {
+	q.mutex.RLock()
+	defer q.mutex.RUnlock()
+	return q.contents
+}
+
+// drain() returns any queued elements.  Once the queue is empty, all subsequent
+// values are discarded.
+func (q *T) drain() (interface{}, error) {
+	q.mutex.Lock()
+	defer q.mutex.Unlock()
+	select {
+	case v := <-q.contents:
+		return v, nil
+	default:
+		q.contents = nil
+		return nil, ErrQueueIsClosed
+	}
+}
diff --git a/runtime/internal/lib/pcqueue/pcqueue_test.go b/runtime/internal/lib/pcqueue/pcqueue_test.go
new file mode 100644
index 0000000..49c13fb
--- /dev/null
+++ b/runtime/internal/lib/pcqueue/pcqueue_test.go
@@ -0,0 +1,485 @@
+// 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 pcqueue
+
+import (
+	"sync"
+	"sync/atomic"
+	"testing"
+	"time"
+
+	"v.io/x/ref/internal/logger"
+)
+
+//go:generate v23 test generate
+
+const (
+	queueSize    = 10
+	elementCount = 100
+	writerCount  = 10
+	readerCount  = 10
+)
+
+// Test normal Put()/Get() combination.
+func TestSimplePut(t *testing.T) {
+	queue := New(0)
+	done := make(chan struct{}, 1)
+	go func() {
+		queue.Put(1, nil)
+		done <- struct{}{}
+	}()
+
+	select {
+	case <-done:
+		t.Errorf("Unexpected completion")
+	default:
+	}
+
+	item, err := queue.Get(nil)
+	if err != nil {
+		t.Errorf("Get: %v", err)
+	}
+
+	if item.(int) != 1 {
+		t.Errorf("Expected 1, actual=%v", item)
+	}
+
+	<-done
+}
+
+// Test normal Put()/Get() combination.
+func TestSimpleGet(t *testing.T) {
+	queue := New(0)
+	done := make(chan struct{}, 1)
+	go func() {
+		item, err := queue.Get(nil)
+		if err != nil {
+			t.Errorf("Get: %v", item)
+		}
+		if item.(int) != 1 {
+			t.Errorf("Expected 1, actual=%v", item)
+		}
+		done <- struct{}{}
+	}()
+
+	select {
+	case <-done:
+		t.Errorf("Unexpected completion")
+	default:
+	}
+
+	queue.Put(1, nil)
+	<-done
+}
+
+// Test normal queue operation with a single producer and single consumer.
+func TestSequential(t *testing.T) {
+	queue := New(queueSize)
+	done := make(chan struct{}, 1)
+	cancel := make(chan struct{})
+
+	// Check that the queue elements are sequentially increasing ints.
+	logger.Global().VI(1).Infof("Start consumer")
+	go func() {
+		for i := 0; i != elementCount; i++ {
+			item, err := queue.Get(cancel)
+			if err != nil {
+				t.Errorf("Get: %v", err)
+			}
+			if item == nil {
+				break
+			}
+			j := item.(int)
+			if j != i {
+				t.Errorf("Expected %d, actual %d", i, j)
+			}
+		}
+		done <- struct{}{}
+	}()
+
+	// Generate the sequential ints.
+	logger.Global().VI(1).Infof("Put values")
+	for i := 0; i != elementCount; i++ {
+		queue.Put(i, nil)
+	}
+
+	// Wait for the consumer.
+	logger.Global().VI(1).Infof("Waiting for consumer")
+	<-done
+
+	// Any subsequent read should timeout.
+	logger.Global().VI(1).Infof("Start consumer")
+	go func() {
+		_, err := queue.Get(cancel)
+		if err != ErrCancelled {
+			t.Errorf("Expected timeout: %v", err)
+		}
+		logger.Global().VI(1).Infof("Consumer done")
+		done <- struct{}{}
+	}()
+
+	logger.Global().VI(1).Infof("Sleep a little")
+	time.Sleep(100 * time.Millisecond)
+	select {
+	case <-done:
+		t.Errorf("Unexpected completion")
+	default:
+	}
+
+	logger.Global().VI(1).Infof("Cancel")
+	close(cancel)
+
+	logger.Global().VI(1).Infof("Wait for consumer")
+	<-done
+}
+
+// Test timeouts for PutWithTimeout() when there is no consumer.
+func TestSequentialPutCancel(t *testing.T) {
+	queue := New(queueSize)
+	done := make(chan struct{}, 1)
+	cancel := make(chan struct{})
+
+	logger.Global().VI(1).Infof("Put values")
+	for i := 0; i != queueSize; i++ {
+		err := queue.Put(i, nil)
+		if err != nil {
+			t.Errorf("Put: %v", err)
+		}
+	}
+
+	logger.Global().VI(1).Infof("Start producer")
+	go func() {
+		err := queue.Put(0, cancel)
+		if err != ErrCancelled {
+			t.Errorf("Put: expected cancellation: %v", err)
+		}
+		done <- struct{}{}
+	}()
+
+	logger.Global().VI(1).Infof("Sleep a little")
+	time.Sleep(100 * time.Millisecond)
+	select {
+	case <-done:
+		t.Errorf("Unexpected completion")
+	default:
+	}
+
+	logger.Global().VI(1).Infof("Cancel")
+	close(cancel)
+
+	logger.Global().VI(1).Infof("Wait for producer")
+	<-done
+}
+
+// Test that Get() returns an error when the queue is closed.
+func TestSequentialClose(t *testing.T) {
+	queue := New(queueSize)
+	err := queue.Put(0, nil)
+	if err != nil {
+		t.Errorf("Put: %v", err)
+	}
+	queue.Close()
+
+	// Check that Get() returns the element.
+	item, err := queue.Get(nil)
+	if err != nil {
+		t.Errorf("Get: %v", err)
+	}
+	if item.(int) != 0 {
+		t.Errorf("Unexpected value: %v", item)
+	}
+
+	// Check that Get() returns an error.
+	_, err = queue.Get(nil)
+	if err != ErrQueueIsClosed {
+		t.Errorf("Expected queue to be closed: %v", err)
+	}
+
+	// Check that Put() returns an error.
+	err = queue.Put(0, nil)
+	if err != ErrQueueIsClosed {
+		t.Errorf("Expected queue to be closed: %v", err)
+	}
+}
+
+// Test that concurrent Puts() may add values to the queue.
+func TestConcurrentClose(t *testing.T) {
+	queue := New(0)
+	pending := &sync.WaitGroup{}
+	pending.Add(2 * writerCount)
+	for i := 0; i != writerCount; i++ {
+		go func() {
+			err := queue.Put(1, nil)
+			if err != nil {
+				logger.Global().VI(1).Infof("Put: %v", err)
+			}
+			pending.Done()
+		}()
+	}
+	time.Sleep(100 * time.Millisecond)
+	queue.Close()
+	for i := 0; i != writerCount; i++ {
+		go func() {
+			err := queue.Put(2, nil)
+			if err == nil {
+				t.Errorf("Expected error")
+			}
+			pending.Done()
+		}()
+	}
+
+	readers := 0
+	for {
+		item, err := queue.Get(nil)
+		if err != nil {
+			break
+		}
+		if item.(int) != 1 {
+			t.Errorf("Expected 1, actual=%v", item)
+		}
+		readers++
+	}
+	logger.Global().VI(1).Infof("%d operations completed", readers)
+	if readers > writerCount {
+		t.Errorf("Too many readers")
+	}
+	pending.Wait()
+}
+
+// Test that Get() returns an error when the queue is shut down.
+func TestSequentialShutdown(t *testing.T) {
+	queue := New(queueSize)
+	err := queue.Put(0, nil)
+	if err != nil {
+		t.Errorf("Put: %v", err)
+	}
+	queue.Shutdown()
+
+	// Check that Get() returns an error.
+	_, err = queue.Get(nil)
+	if err != ErrQueueIsClosed {
+		t.Errorf("Expected queue to be closed: %v", err)
+	}
+
+	// Check that Put() returns an error.
+	err = queue.Put(0, nil)
+	if err != ErrQueueIsClosed {
+		t.Errorf("Expected queue to be closed: %v", err)
+	}
+}
+
+// Test with concurrent producers, but a single consumer.
+func TestConcurrentPutNoTimeouts(t *testing.T) {
+	queue := New(queueSize)
+	pending := &sync.WaitGroup{}
+
+	// Generate the sequential ints.
+	for i := 0; i != writerCount; i++ {
+		pending.Add(1)
+		go func() {
+			for j := 0; j != elementCount; j++ {
+				queue.Put(j, nil)
+			}
+			pending.Done()
+		}()
+	}
+
+	// Sum up the results and compare.
+	sum := 0
+	for i := 0; i != writerCount*elementCount; i++ {
+		item, err := queue.Get(nil)
+		if err != nil {
+			t.Errorf("Get: %v", err)
+		}
+		if item == nil {
+			break
+		}
+		sum += item.(int)
+	}
+	expected := writerCount * elementCount * (elementCount - 1) / 2
+	if sum != expected {
+		t.Errorf("Expected sum %d, received %d", expected, sum)
+	}
+
+	pending.Wait()
+}
+
+// Test with concurrent consumers and concurrent producers.
+func TestConcurrentGet(t *testing.T) {
+	queue := New(queueSize)
+	done := make(chan struct{})
+	pending := &sync.WaitGroup{}
+	pending.Add(readerCount + writerCount)
+	cancel := make(chan struct{})
+
+	// Sum up the results and compare.
+	sum := uint32(0)
+	count := uint32(0)
+	logger.Global().VI(1).Infof("Start consumers")
+	for i := 0; i != readerCount; i++ {
+		pid := i
+		go func() {
+			for {
+				c := atomic.LoadUint32(&count)
+				if c == writerCount*elementCount {
+					break
+				}
+
+				// The timeout is required for termination.
+				item, err := queue.Get(cancel)
+				if err != nil {
+					continue
+				}
+				atomic.AddUint32(&sum, uint32(item.(int)))
+				atomic.AddUint32(&count, 1)
+			}
+			logger.Global().VI(1).Infof("Consumer %d done", pid)
+			pending.Done()
+		}()
+	}
+
+	// Generate the sequential ints.
+	logger.Global().VI(1).Infof("Start producers")
+	for i := 0; i != writerCount; i++ {
+		pid := i
+		go func() {
+			for j := 0; j != elementCount; j++ {
+				err := queue.Put(j, nil)
+				if err != nil {
+					t.Errorf("Put: %v", err)
+				}
+			}
+			logger.Global().VI(1).Infof("Producer %d done", pid)
+			pending.Done()
+		}()
+	}
+
+	logger.Global().VI(1).Infof("Start termination checker")
+	go func() {
+		pending.Wait()
+		done <- struct{}{}
+	}()
+
+	logger.Global().VI(1).Infof("Wait for processes")
+	stop := false
+	for !stop {
+		time.Sleep(100 * time.Millisecond)
+		select {
+		case <-done:
+			stop = true
+		default:
+			cancel <- struct{}{}
+		}
+	}
+
+	logger.Global().VI(1).Infof("Checking the sum")
+	expected := writerCount * elementCount * (elementCount - 1) / 2
+	s := atomic.LoadUint32(&sum)
+	if s != uint32(expected) {
+		t.Errorf("Expected sum %d, received %d", expected, sum)
+	}
+}
+
+func TestSimpleTryPut(t *testing.T) {
+	q := New(1)
+	if err := q.TryPut(1); err != nil {
+		t.Errorf("TryPut(1) got error: %q", err)
+	}
+
+	if err := q.TryPut(2); err != ErrTryAgain {
+		t.Errorf("TryPut(2) got error: %q; want: %q", err, ErrTryAgain)
+	}
+
+	if item, err := q.Get(nil); err != nil {
+		t.Errorf("Get() got error: %q", err)
+	} else if item.(int) != 1 {
+		t.Errorf("Get() = %v; want: %v", item, 1)
+	}
+
+	q.Close()
+	if err := q.TryPut(3); err != ErrQueueIsClosed {
+		t.Errorf("TryPut(3) got error: %q; want: %q", err, ErrQueueIsClosed)
+	}
+}
+
+func TestSequentialTryPut(t *testing.T) {
+	q := New(queueSize)
+	const numIter = 5
+	for i := 0; i < numIter; i++ {
+		// All succeed.
+		for j := i * queueSize; j < (i+1)*queueSize; j++ {
+			if err := q.TryPut(j); err != nil {
+				t.Errorf("TryPut(%v) returned error: %q", j, err)
+			}
+		}
+		// All fail.
+		for j := (i + 1) * queueSize; j < (i+2)*queueSize; j++ {
+			if err := q.TryPut(j); err != ErrTryAgain {
+				t.Errorf("TryPut(%v) returned error %q; want %q", j, err, ErrTryAgain)
+			}
+		}
+		// Empty the queue.
+		for j := i * queueSize; j < (i+1)*queueSize; j++ {
+			item, err := q.Get(nil)
+			if err != nil {
+				t.Errorf("Get() returned error: %q", err)
+			} else if item.(int) != j {
+				t.Errorf("Get() = %v; want %v", item.(int), j)
+			}
+		}
+	}
+
+	q.Close()
+	for i := numIter * queueSize; i < (numIter+1)*queueSize; i++ {
+		if err := q.TryPut(i); err != ErrQueueIsClosed {
+			t.Errorf("TryPut(%v) returned error %q; want %q", i, err, ErrQueueIsClosed)
+		}
+	}
+}
+
+func TestConcurrentTryPut(t *testing.T) {
+	q := New(queueSize)
+	pending := &sync.WaitGroup{}
+	for i := 0; i != writerCount; i++ {
+		pending.Add(1)
+		go func() {
+			for j := 0; j != elementCount; j++ {
+				// TryPut(j) until we succeed.
+				for {
+					err := q.TryPut(j)
+					if err == nil {
+						break
+					}
+					if err == ErrTryAgain {
+						time.Sleep(1 * time.Millisecond)
+					} else {
+						t.Errorf("%v: TryPut(%v) returned error %q; want %q", i, j, err, ErrTryAgain)
+					}
+				}
+			}
+			pending.Done()
+		}()
+	}
+
+	// Sum up the results and compare.
+	sum := 0
+	for i := 0; i != writerCount*elementCount; i++ {
+		item, err := q.Get(nil)
+		if err != nil {
+			t.Errorf("Get() returned error: %q", err)
+			continue
+		}
+		if item == nil {
+			continue
+		}
+		sum += item.(int)
+	}
+
+	if expected := writerCount * elementCount * (elementCount - 1) / 2; sum != expected {
+		t.Errorf("got sum %v, want %v", expected, sum)
+	}
+
+	pending.Wait()
+}
diff --git a/runtime/internal/lib/pcqueue/v23_internal_test.go b/runtime/internal/lib/pcqueue/v23_internal_test.go
new file mode 100644
index 0000000..40cedde
--- /dev/null
+++ b/runtime/internal/lib/pcqueue/v23_internal_test.go
@@ -0,0 +1,20 @@
+// 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 pcqueue
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	os.Exit(m.Run())
+}
diff --git a/runtime/internal/lib/publisher/publisher.go b/runtime/internal/lib/publisher/publisher.go
new file mode 100644
index 0000000..270fec2
--- /dev/null
+++ b/runtime/internal/lib/publisher/publisher.go
@@ -0,0 +1,389 @@
+// 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 publisher provides a type to publish names to a mounttable.
+package publisher
+
+// TODO(toddw): Add unittests.
+
+import (
+	"fmt"
+	"sort"
+	"strings"
+	"time"
+
+	"v.io/v23/context"
+	"v.io/v23/namespace"
+	"v.io/v23/naming"
+	"v.io/v23/options"
+	"v.io/v23/rpc"
+	"v.io/v23/verror"
+)
+
+// Publisher manages the publishing of servers in mounttable.
+type Publisher interface {
+	// AddServer adds a new server to be mounted.
+	AddServer(server string)
+	// RemoveServer removes a server from the list of mounts.
+	RemoveServer(server string)
+	// AddName adds a new name for all servers to be mounted as.
+	AddName(name string, ServesMountTable bool, IsLeaf bool)
+	// RemoveName removes a name.
+	RemoveName(name string)
+	// Status returns a snapshot of the publisher's current state.
+	Status() rpc.MountState
+	// DebugString returns a string representation of the publisher
+	// meant solely for debugging.
+	DebugString() string
+	// Stop causes the publishing to stop and initiates unmounting of the
+	// mounted names.  Stop performs the unmounting asynchronously, and
+	// WaitForStop should be used to wait until it is done.
+	// Once Stop is called Add/RemoveServer and AddName become noops.
+	Stop()
+	// WaitForStop waits until all unmounting initiated by Stop is finished.
+	WaitForStop()
+}
+
+// The publisher adds this much slack to each TTL.
+const mountTTLSlack = 20 * time.Second
+
+// publisher maintains the name->server associations in the mounttable.  It
+// spawns its own goroutine that does the actual work; the publisher itself
+// simply coordinates concurrent access by sending and receiving on the
+// appropriate channels.
+type publisher struct {
+	cmdchan  chan interface{} // value is one of {server,name,debug}Cmd
+	stopchan chan struct{}    // closed when no longer accepting commands.
+	donechan chan struct{}    // closed when the publisher is done
+	ctx      *context.T
+}
+
+type addServerCmd struct {
+	server string // server to add
+}
+
+type removeServerCmd struct {
+	server string // server to remove
+}
+
+type addNameCmd struct {
+	name string // name to add
+	mt   bool   // true if server serves a mount table
+	leaf bool   // true if server is a leaf
+}
+
+type removeNameCmd struct {
+	name string // name to remove
+}
+
+type debugCmd chan string // debug string is sent when the cmd is done
+
+type statusCmd chan rpc.MountState // status info is sent when cmd is done
+
+type stopCmd struct{} // sent to the runloop when we want it to exit.
+
+// New returns a new publisher that updates mounts on ns every period.
+func New(ctx *context.T, ns namespace.T, period time.Duration) Publisher {
+	p := &publisher{
+		cmdchan:  make(chan interface{}),
+		stopchan: make(chan struct{}),
+		donechan: make(chan struct{}),
+		ctx:      ctx,
+	}
+	go runLoop(ctx, p.cmdchan, p.donechan, ns, period)
+	return p
+}
+
+func (p *publisher) sendCmd(cmd interface{}) bool {
+	select {
+	case p.cmdchan <- cmd:
+		return true
+	case <-p.stopchan:
+		return false
+	case <-p.donechan:
+		return false
+	}
+}
+
+func (p *publisher) AddServer(server string) {
+	p.sendCmd(addServerCmd{server})
+}
+
+func (p *publisher) RemoveServer(server string) {
+	p.sendCmd(removeServerCmd{server})
+}
+
+func (p *publisher) AddName(name string, mt bool, leaf bool) {
+	p.sendCmd(addNameCmd{name, mt, leaf})
+}
+
+func (p *publisher) RemoveName(name string) {
+	p.sendCmd(removeNameCmd{name})
+}
+
+func (p *publisher) Status() rpc.MountState {
+	status := make(statusCmd)
+	if p.sendCmd(status) {
+		return <-status
+	}
+	return rpc.MountState{}
+}
+
+func (p *publisher) DebugString() (dbg string) {
+	debug := make(debugCmd)
+	if p.sendCmd(debug) {
+		dbg = <-debug
+	} else {
+		dbg = "stopped"
+	}
+	return
+}
+
+// Stop stops the publisher, which in practical terms means un-mounting
+// everything and preventing any further publish operations.  The caller can
+// be confident that no new names or servers will get published once Stop
+// returns.  To wait for existing mounts to be cleaned up, use WaitForStop.
+//
+// Stopping the publisher is irreversible.
+//
+// Once the publisher is stopped, any further calls on its public methods
+// (including Stop) are no-ops.
+func (p *publisher) Stop() {
+	p.sendCmd(stopCmd{})
+	close(p.stopchan) // stop accepting new commands now.
+}
+
+func (p *publisher) WaitForStop() {
+	<-p.donechan
+}
+
+func runLoop(ctx *context.T, cmdchan chan interface{}, donechan chan struct{}, ns namespace.T, period time.Duration) {
+	ctx.VI(2).Info("rpc pub: start runLoop")
+	state := newPubState(ctx, ns, period)
+	for {
+		select {
+		case cmd := <-cmdchan:
+			switch tcmd := cmd.(type) {
+			case stopCmd:
+				state.unmountAll()
+				close(donechan)
+				ctx.VI(2).Info("rpc pub: exit runLoop")
+				return
+			case addServerCmd:
+				state.addServer(tcmd.server)
+			case removeServerCmd:
+				state.removeServer(tcmd.server)
+			case addNameCmd:
+				state.addName(tcmd.name, tcmd.mt, tcmd.leaf)
+			case removeNameCmd:
+				state.removeName(tcmd.name)
+			case statusCmd:
+				tcmd <- state.getStatus()
+				close(tcmd)
+			case debugCmd:
+				tcmd <- state.debugString()
+				close(tcmd)
+			}
+		case <-state.timeout():
+			// Sync everything once every period, to refresh the ttls.
+			state.sync()
+		}
+	}
+}
+
+type mountKey struct {
+	name, server string
+}
+
+// pubState maintains the state for our periodic mounts.  It is not thread-safe;
+// it's only used in the sequential publisher runLoop.
+type pubState struct {
+	ctx      *context.T
+	ns       namespace.T
+	period   time.Duration
+	deadline time.Time           // deadline for the next sync call
+	names    map[string]nameAttr // names that have been added
+	servers  map[string]bool     // servers that have been added, true
+	// map each (name,server) to its status.
+	mounts map[mountKey]*rpc.MountStatus
+}
+
+type nameAttr struct {
+	servesMT bool
+	isLeaf   bool
+}
+
+func newPubState(ctx *context.T, ns namespace.T, period time.Duration) *pubState {
+	return &pubState{
+		ctx:      ctx,
+		ns:       ns,
+		period:   period,
+		deadline: time.Now().Add(period),
+		names:    make(map[string]nameAttr),
+		servers:  make(map[string]bool),
+		mounts:   make(map[mountKey]*rpc.MountStatus),
+	}
+}
+
+func (ps *pubState) timeout() <-chan time.Time {
+	return time.After(ps.deadline.Sub(time.Now()))
+}
+
+func (ps *pubState) addName(name string, mt bool, leaf bool) {
+	// Each non-dup name that is added causes new mounts to be created for all
+	// existing servers.
+	if _, exists := ps.names[name]; exists {
+		return
+	}
+	attr := nameAttr{mt, leaf}
+	ps.names[name] = attr
+	for server, _ := range ps.servers {
+		status := new(rpc.MountStatus)
+		ps.mounts[mountKey{name, server}] = status
+		ps.mount(name, server, status, attr)
+	}
+}
+
+func (ps *pubState) removeName(name string) {
+	if _, exists := ps.names[name]; !exists {
+		return
+	}
+	for server, _ := range ps.servers {
+		if status, exists := ps.mounts[mountKey{name, server}]; exists {
+			ps.unmount(name, server, status, true)
+		}
+	}
+	delete(ps.names, name)
+}
+
+func (ps *pubState) addServer(server string) {
+	// Each non-dup server that is added causes new mounts to be created for all
+	// existing names.
+	if _, exists := ps.servers[server]; !exists {
+		ps.servers[server] = true
+		for name, attr := range ps.names {
+			status := new(rpc.MountStatus)
+			ps.mounts[mountKey{name, server}] = status
+			ps.mount(name, server, status, attr)
+		}
+	}
+}
+
+func (ps *pubState) removeServer(server string) {
+	if _, exists := ps.servers[server]; !exists {
+		return
+	}
+	delete(ps.servers, server)
+	for name, _ := range ps.names {
+		if status, exists := ps.mounts[mountKey{name, server}]; exists {
+			ps.unmount(name, server, status, true)
+		}
+	}
+}
+
+func (ps *pubState) mount(name, server string, status *rpc.MountStatus, attr nameAttr) {
+	// Always mount with ttl = period + slack, regardless of whether this is
+	// triggered by a newly added server or name, or by sync.  The next call
+	// to sync will occur within the next period, and refresh all mounts.
+	ttl := ps.period + mountTTLSlack
+	last := status
+	status.LastMount = time.Now()
+	status.LastMountErr = ps.ns.Mount(ps.ctx, name, server, ttl, naming.ServesMountTable(attr.servesMT), naming.IsLeaf(attr.isLeaf))
+	status.TTL = ttl
+	// If the mount status changed, log it.
+	if status.LastMountErr != nil {
+		if verror.ErrorID(last.LastMountErr) != verror.ErrorID(status.LastMountErr) || ps.ctx.V(2) {
+			ps.ctx.Errorf("rpc pub: couldn't mount(%v, %v, %v): %v", name, server, ttl, status.LastMountErr)
+		}
+	} else {
+		if last.LastMount.IsZero() || last.LastMountErr != nil || ps.ctx.V(2) {
+			ps.ctx.Infof("rpc pub: mount(%v, %v, %v)", name, server, ttl)
+		}
+	}
+}
+
+func (ps *pubState) sync() {
+	ps.deadline = time.Now().Add(ps.period) // set deadline for the next sync
+	for key, status := range ps.mounts {
+		if status.LastUnmountErr != nil {
+			// Desired state is "unmounted", failed at previous attempt. Retry.
+			ps.unmount(key.name, key.server, status, true)
+		} else {
+			ps.mount(key.name, key.server, status, ps.names[key.name])
+		}
+	}
+}
+
+func (ps *pubState) unmount(name, server string, status *rpc.MountStatus, retry bool) {
+	status.LastUnmount = time.Now()
+	var opts []naming.NamespaceOpt
+	if !retry {
+		opts = []naming.NamespaceOpt{options.NoRetry{}}
+	}
+	status.LastUnmountErr = ps.ns.Unmount(ps.ctx, name, server, opts...)
+	if status.LastUnmountErr != nil {
+		ps.ctx.Errorf("rpc pub: couldn't unmount(%v, %v): %v", name, server, status.LastUnmountErr)
+	} else {
+		ps.ctx.VI(1).Infof("rpc pub: unmount(%v, %v)", name, server)
+		delete(ps.mounts, mountKey{name, server})
+	}
+}
+
+func (ps *pubState) unmountAll() {
+	for key, status := range ps.mounts {
+		ps.unmount(key.name, key.server, status, false)
+	}
+}
+
+func copyNamesToSlice(sl map[string]nameAttr) []string {
+	var ret []string
+	for s, _ := range sl {
+		if len(s) == 0 {
+			continue
+		}
+		ret = append(ret, s)
+	}
+	return ret
+}
+
+func copyServersToSlice(sl map[string]bool) []string {
+	var ret []string
+	for s, _ := range sl {
+		if len(s) == 0 {
+			continue
+		}
+		ret = append(ret, s)
+	}
+	return ret
+}
+
+func (ps *pubState) getStatus() rpc.MountState {
+	st := make([]rpc.MountStatus, 0, len(ps.mounts))
+	names := copyNamesToSlice(ps.names)
+	servers := copyServersToSlice(ps.servers)
+	sort.Strings(names)
+	sort.Strings(servers)
+	for _, name := range names {
+		for _, server := range servers {
+			if v := ps.mounts[mountKey{name, server}]; v != nil {
+				mst := *v
+				mst.Name = name
+				mst.Server = server
+				st = append(st, mst)
+			}
+		}
+	}
+	return st
+}
+
+// TODO(toddw): sort the names/servers so that the output order is stable.
+func (ps *pubState) debugString() string {
+	l := make([]string, 2+len(ps.mounts))
+	l = append(l, fmt.Sprintf("Publisher period:%v deadline:%v", ps.period, ps.deadline))
+	l = append(l, "==============================Mounts============================================")
+	for key, status := range ps.mounts {
+		l = append(l, fmt.Sprintf("[%s,%s] mount(%v, %v, %s) unmount(%v, %v)", key.name, key.server, status.LastMount, status.LastMountErr, status.TTL, status.LastUnmount, status.LastUnmountErr))
+	}
+	return strings.Join(l, "\n")
+}
diff --git a/runtime/internal/lib/publisher/publisher_test.go b/runtime/internal/lib/publisher/publisher_test.go
new file mode 100644
index 0000000..cd69d15
--- /dev/null
+++ b/runtime/internal/lib/publisher/publisher_test.go
@@ -0,0 +1,153 @@
+// 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 publisher_test
+
+import (
+	"fmt"
+	"reflect"
+	"sort"
+	"testing"
+	"time"
+
+	"v.io/v23/context"
+	"v.io/v23/namespace"
+	"v.io/v23/vtrace"
+
+	"v.io/x/ref/lib/flags"
+	"v.io/x/ref/runtime/internal/lib/publisher"
+	tnaming "v.io/x/ref/runtime/internal/testing/mocks/naming"
+	ivtrace "v.io/x/ref/runtime/internal/vtrace"
+)
+
+//go:generate v23 test generate
+
+func testContext() *context.T {
+	ctx, _ := context.RootContext()
+	ctx, err := ivtrace.Init(ctx, flags.VtraceFlags{})
+	if err != nil {
+		panic(err)
+	}
+	ctx, _ = vtrace.WithNewSpan(ctx, "")
+	ctx, _ = context.WithDeadline(ctx, time.Now().Add(20*time.Second))
+	return ctx
+}
+
+func resolveWithRetry(t *testing.T, ns namespace.T, ctx *context.T, name string, expected int) []string {
+	deadline := time.Now().Add(time.Minute)
+	for {
+		me, err := ns.Resolve(ctx, name)
+		if err == nil && len(me.Names()) == expected {
+			return me.Names()
+		}
+		if time.Now().After(deadline) {
+			t.Fatalf("failed to resolve %q", name)
+		} else {
+			continue
+		}
+		time.Sleep(100 * time.Millisecond)
+	}
+}
+
+func verifyMissing(t *testing.T, ns namespace.T, ctx *context.T, name string) {
+	deadline := time.Now().Add(time.Minute)
+	for {
+		if _, err := ns.Resolve(ctx, "foo"); err == nil {
+			if time.Now().After(deadline) {
+				t.Errorf("%q is still mounted", name)
+			}
+			time.Sleep(100 * time.Millisecond)
+		} else {
+			break
+		}
+	}
+}
+
+func TestAddAndRemove(t *testing.T) {
+	tctx := testContext()
+	ns := tnaming.NewSimpleNamespace()
+	pub := publisher.New(testContext(), ns, time.Second)
+	pub.AddName("foo", false, false)
+	pub.AddServer("foo-addr")
+	if got, want := resolveWithRetry(t, ns, tctx, "foo", 1), []string{"/foo-addr"}; !reflect.DeepEqual(got, want) {
+		t.Errorf("got %q, want %q", got, want)
+	}
+	pub.AddServer("bar-addr")
+	got, want := resolveWithRetry(t, ns, tctx, "foo", 2), []string{"/bar-addr", "/foo-addr"}
+	sort.Strings(got)
+	if !reflect.DeepEqual(got, want) {
+		t.Errorf("got %q, want %q", got, want)
+	}
+	pub.AddName("baz", false, false)
+	got = resolveWithRetry(t, ns, tctx, "baz", 2)
+	sort.Strings(got)
+	if !reflect.DeepEqual(got, want) {
+		t.Errorf("got %q, want %q", got, want)
+	}
+	pub.RemoveName("foo")
+	verifyMissing(t, ns, tctx, "foo")
+}
+
+func TestStatus(t *testing.T) {
+	tctx := testContext()
+	ns := tnaming.NewSimpleNamespace()
+	pub := publisher.New(testContext(), ns, time.Second)
+	pub.AddName("foo", false, false)
+	status := pub.Status()
+	if got, want := len(status), 0; got != want {
+		t.Errorf("got %d, want %d", got, want)
+	}
+	pub.AddServer("foo-addr")
+
+	// Wait for the publisher to asynchronously publish the
+	// requisite number of servers.
+	ch := make(chan error, 1)
+	waitFor := func(n int) {
+		deadline := time.Now().Add(time.Minute)
+		for {
+			status = pub.Status()
+			if got, want := len(status), n; got != want {
+				if time.Now().After(deadline) {
+					ch <- fmt.Errorf("got %d, want %d", got, want)
+					return
+				}
+				time.Sleep(100 * time.Millisecond)
+			} else {
+				ch <- nil
+				return
+			}
+		}
+	}
+
+	go waitFor(1)
+	if err := <-ch; err != nil {
+		t.Fatalf("%s", err)
+	}
+
+	pub.AddServer("bar-addr")
+	pub.AddName("baz", false, false)
+
+	go waitFor(4)
+	if err := <-ch; err != nil {
+		t.Fatalf("%s", err)
+	}
+
+	status = pub.Status()
+	names := status.Names()
+	if got, want := names, []string{"baz", "foo"}; !reflect.DeepEqual(got, want) {
+		t.Errorf("got %q, want %q", got, want)
+	}
+	servers := status.Servers()
+	if got, want := servers, []string{"bar-addr", "foo-addr"}; !reflect.DeepEqual(got, want) {
+		t.Errorf("got %q, want %q", got, want)
+	}
+	pub.RemoveName("foo")
+	verifyMissing(t, ns, tctx, "foo")
+
+	status = pub.Status()
+	go waitFor(2)
+	if err := <-ch; err != nil {
+		t.Fatalf("%s", err)
+	}
+}
diff --git a/runtime/internal/lib/publisher/v23_internal_test.go b/runtime/internal/lib/publisher/v23_internal_test.go
new file mode 100644
index 0000000..569e177
--- /dev/null
+++ b/runtime/internal/lib/publisher/v23_internal_test.go
@@ -0,0 +1,20 @@
+// 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 publisher
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	os.Exit(m.Run())
+}
diff --git a/runtime/internal/lib/sync/doc.go b/runtime/internal/lib/sync/doc.go
new file mode 100644
index 0000000..7e9faa2
--- /dev/null
+++ b/runtime/internal/lib/sync/doc.go
@@ -0,0 +1,6 @@
+// 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 sync provides synchronization primitives.
+package sync
diff --git a/runtime/internal/lib/sync/lock.go b/runtime/internal/lib/sync/lock.go
new file mode 100644
index 0000000..cb5d741
--- /dev/null
+++ b/runtime/internal/lib/sync/lock.go
@@ -0,0 +1,31 @@
+// 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 sync
+
+import "sync"
+
+// DebugMutex supports checking whether a mutex is locked.
+type DebugMutex struct {
+	mutex    sync.Mutex
+	isLocked bool
+}
+
+func (m *DebugMutex) Lock() {
+	m.mutex.Lock()
+	m.isLocked = true
+}
+
+func (m *DebugMutex) Unlock() {
+	m.CheckLocked()
+	m.isLocked = false
+	m.mutex.Unlock()
+}
+
+// CheckLocked panics if the lock is not held.
+func (m *DebugMutex) CheckLocked() {
+	if !m.isLocked {
+		panic("Mutex is not locked")
+	}
+}
diff --git a/runtime/internal/lib/sync/semaphore.go b/runtime/internal/lib/sync/semaphore.go
new file mode 100644
index 0000000..4fac408
--- /dev/null
+++ b/runtime/internal/lib/sync/semaphore.go
@@ -0,0 +1,175 @@
+// 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 sync
+
+import (
+	"errors"
+	"sync"
+)
+
+// Semaphore is an implementation of unbounded semaphores.  Abstractly, a
+// semaphore holds a nonnegative integer value, and supports operations to
+// increment and decrement the value.  The semaphore value is not allowed to be
+// negative; decrement operations block until the semaphore value is positive.
+// http://en.wikipedia.org/wiki/Semaphore_%28programming%29
+//
+// The standard suggestion for implementing semaphores in Go is to use a
+// buffered channel, where the number of elements in the channel is the max
+// value of the semaphore.  However, what we implement here is _unbounded_
+// semaphores (up to a max value of 2^31-1).
+//
+// A mutex and integer is used to keep track of the numerical value of the
+// and a channel is used for notification of changes.
+// When decrementing, the value of the semaphore is decremented and if not
+// sufficient, DecN will block until it can subtract more.
+//
+// Because of this looping, the semaphore is not fair.
+// The reason for using a channel for notifications is for cancellation.
+// The Dec(cancel <-chan struct{}) method takes a cancelation channel, so we use a
+// "select" operation to determine whether to perform a semaphore operation or
+// abort because the semaphore is close or the operation was canceled.
+//
+// NOTE: when the Semaphore is closed, the Dec (or DecN) operations are
+// unblocked, returning an error (ErrClosed) if the semaphore value is 0 (or
+// less than the DecN value), respectively.  However, even with the Semaphore
+// closed, if the semaphore value is non-zero (or sufficient to satisfy the DecN
+// value), Dec (or DecN) performs the decrement successfully and returns without
+// an error.  Regardless of whether the Semaphore is closed or not, Inc/IncN
+// succeed in incrementing the semaphore value.
+type Semaphore struct {
+	mu    sync.Mutex
+	value uint // GUARDED_BY(mu)
+
+	notify chan bool
+
+	isClosed bool
+	closed   chan struct{}
+}
+
+var (
+	ErrClosed   = errors.New("semaphore is closed")
+	ErrCanceled = errors.New("semaphore operation was canceled")
+	ErrTryAgain = errors.New("semaphore operation failed, try again")
+)
+
+// NewSemaphore allocates a semaphore with an initial value.
+func NewSemaphore() *Semaphore {
+	return &Semaphore{notify: make(chan bool, 1), closed: make(chan struct{})}
+}
+
+// Close closes the semaphore.  Subsequent operations do not block.
+func (s *Semaphore) Close() {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+	if !s.isClosed {
+		s.isClosed = true
+		close(s.closed)
+	}
+}
+
+// DecN decrements the semaphore.  Blocks until the final value of the semaphore
+// is nonnegative, or the <cancel> channel is closed (or has a value).
+func (s *Semaphore) DecN(n uint, cancel <-chan struct{}) error {
+	s.mu.Lock()
+	if s.value >= n {
+		s.value -= n
+		s.mu.Unlock()
+		return nil
+	}
+	taken := s.value
+	s.value = 0
+	s.mu.Unlock()
+
+	for taken < n {
+		needed := n - taken
+		select {
+		case <-s.notify:
+			notify := true
+			s.mu.Lock()
+			if s.value <= needed {
+				needed = s.value
+				notify = false
+			}
+			taken += needed
+			s.value -= needed
+			s.mu.Unlock()
+			if notify {
+				select {
+				case s.notify <- true:
+				default:
+				}
+			}
+		case <-s.closed:
+			// Close does not zero out the semaphore, so check to
+			// see if the DecN can be satisfied.
+			s.mu.Lock()
+			if s.value >= needed {
+				s.value -= needed
+				taken += needed
+			}
+			s.mu.Unlock()
+			if taken < n {
+				return ErrClosed
+			}
+		case <-cancel:
+			s.IncN(taken)
+			return ErrCanceled
+		}
+	}
+	return nil
+}
+
+// TryDecN tries to decrement the semaphore.
+func (s *Semaphore) TryDecN(n uint) error {
+	if n == 0 {
+		return nil
+	}
+	s.mu.Lock()
+	if s.value >= n {
+		s.value -= n
+		s.mu.Unlock()
+		return nil
+	}
+	closed := s.isClosed
+	s.mu.Unlock()
+
+	if closed {
+		return ErrClosed
+	}
+	return ErrTryAgain
+}
+
+// IncN increments the semaphore.  Wakes any potential waiters.
+func (s *Semaphore) IncN(n uint) error {
+	if n == 0 {
+		// Avoid notifications when there is no change to the value.
+		return nil
+	}
+
+	s.mu.Lock()
+	s.value += n
+	s.mu.Unlock()
+
+	select {
+	case s.notify <- true:
+	default:
+	}
+	return nil
+}
+
+// Dec decrements the semaphore by 1.
+func (s *Semaphore) Dec(cancel <-chan struct{}) error {
+	return s.DecN(1, cancel)
+}
+
+// TryDec tries to decrement the semaphore by 1.
+func (s *Semaphore) TryDec() error {
+	return s.TryDecN(1)
+}
+
+// Inc increments the semaphore by 1.
+func (s *Semaphore) Inc() error {
+	return s.IncN(1)
+}
diff --git a/runtime/internal/lib/sync/semaphore_test.go b/runtime/internal/lib/sync/semaphore_test.go
new file mode 100644
index 0000000..21d6be2
--- /dev/null
+++ b/runtime/internal/lib/sync/semaphore_test.go
@@ -0,0 +1,223 @@
+// 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 sync
+
+import (
+	"sync"
+	"sync/atomic"
+	"testing"
+)
+
+//go:generate v23 test generate
+
+func TestSemaphore(t *testing.T) {
+	s1 := NewSemaphore()
+	s2 := NewSemaphore()
+
+	var pending sync.WaitGroup
+	pending.Add(1)
+	go func() {
+		s1.Dec(nil)
+		s2.Inc()
+		pending.Done()
+	}()
+
+	s1.Inc()
+	s2.Dec(nil)
+	pending.Wait()
+}
+
+func TestCriticalSection(t *testing.T) {
+	s := NewSemaphore()
+	s.Inc()
+
+	var count int32
+	var pending sync.WaitGroup
+	pending.Add(100)
+	for i := 0; i != 100; i++ {
+		go func() {
+			for j := 0; j != 100; j++ {
+				s.Dec(nil)
+				// Critical section.
+				v := atomic.AddInt32(&count, 1)
+				if v > 1 {
+					t.Errorf("Race detected")
+				}
+				atomic.AddInt32(&count, -1)
+				s.Inc()
+			}
+			pending.Done()
+		}()
+	}
+	pending.Wait()
+}
+
+func TestIncN(t *testing.T) {
+	s := NewSemaphore()
+	// done is used to synchronize the decrementer with the incrementer.  Each
+	// time Dec is called, a value is sent on Done.  The incrementer waits for
+	// these messages to check that the decrement has happened.
+	done := make(chan struct{})
+	for i := 0; i != 100; i++ {
+		go func() {
+			s.Dec(nil)
+			done <- struct{}{}
+		}()
+	}
+
+	// Produce in blocks of 10.
+	for i := 0; i != 100; i += 10 {
+		s.IncN(10)
+		for j := 0; j != 10; j++ {
+			<-done
+		}
+	}
+}
+
+func TestTryDecN(t *testing.T) {
+	s := NewSemaphore()
+	err := s.TryDecN(0)
+	if err != nil {
+		t.Errorf("TryDecN: %s", err)
+	}
+	err = s.TryDecN(1)
+	if err == nil {
+		t.Errorf("TryDecN: expected error")
+	}
+	s.IncN(1)
+	err = s.TryDecN(2)
+	if err == nil {
+		t.Errorf("TryDecN: expected error")
+	}
+	err = s.TryDecN(1)
+	if err != nil {
+		t.Errorf("TryDecN: %s", err)
+	}
+
+	s.IncN(5)
+	err = s.TryDecN(3)
+	if err != nil {
+		t.Errorf("TryDecN: %s", err)
+	}
+	err = s.TryDecN(3)
+	if err == nil {
+		t.Errorf("TryDecN: expected error")
+	}
+}
+
+func TestClosed(t *testing.T) {
+	s := NewSemaphore()
+	var pending sync.WaitGroup
+	pending.Add(1)
+	go func() {
+		if err := s.Dec(nil); err == nil {
+			t.Errorf("error should not be nil")
+		}
+		pending.Done()
+	}()
+	s.Close()
+	pending.Wait()
+}
+
+func TestCloseWhileInDec(t *testing.T) {
+	const N = 100
+	s := NewSemaphore()
+	var pending sync.WaitGroup
+	pending.Add(N)
+
+	dec := func(idx int) {
+		defer pending.Done()
+		if err := s.Dec(nil); err != nil {
+			t.Fatalf("%v (%d)", err, idx)
+		}
+	}
+
+	for i := 0; i < N; i++ {
+		go dec(i)
+	}
+	s.IncN(N + 1)
+	s.Close()
+	pending.Wait()
+	if got, want := s.DecN(2, nil), ErrClosed; got != want {
+		t.Errorf("Expected error %v, got %v instead", want, got)
+	}
+}
+
+func TestCancel(t *testing.T) {
+	s := NewSemaphore()
+	cancel := make(chan struct{})
+	var pending sync.WaitGroup
+	pending.Add(1)
+	go func() {
+		if err := s.Dec(cancel); err == nil {
+			t.Errorf("error should not be nil")
+		}
+		pending.Done()
+	}()
+	close(cancel)
+	pending.Wait()
+}
+
+func BenchmarkInc(b *testing.B) {
+	s := NewSemaphore()
+	for i := 0; i < b.N; i++ {
+		s.Inc()
+	}
+}
+
+func BenchmarkDec(b *testing.B) {
+	s := NewSemaphore()
+	cancel := make(chan struct{})
+	s.IncN(uint(b.N))
+	for i := 0; i < b.N; i++ {
+		s.Dec(cancel)
+	}
+}
+
+func BenchmarkDecCanceled(b *testing.B) {
+	s := NewSemaphore()
+	cancel := make(chan struct{})
+	close(cancel)
+	for i := 0; i < b.N; i++ {
+		s.Dec(cancel)
+	}
+}
+
+func BenchmarkTryDec_TryAgain(b *testing.B) {
+	s := NewSemaphore()
+	for i := 0; i < b.N; i++ {
+		s.TryDecN(1)
+	}
+}
+
+func BenchmarkTryDec_Success(b *testing.B) {
+	s := NewSemaphore()
+	s.IncN(uint(b.N))
+	for i := 0; i < b.N; i++ {
+		if err := s.TryDecN(1); err != nil {
+			b.Fatalf("TryDecN failed with %v in iteration %d", err, i)
+			return
+		}
+	}
+}
+
+func BenchmarkIncDecInterleaved(b *testing.B) {
+	c := make(chan bool)
+	s := NewSemaphore()
+	go func() {
+		for i := 0; i < b.N; i++ {
+			s.Dec(nil)
+		}
+		c <- true
+	}()
+	go func() {
+		for i := 0; i < b.N; i++ {
+			s.Inc()
+		}
+		c <- true
+	}()
+	<-c
+	<-c
+}
diff --git a/runtime/internal/lib/sync/v23_internal_test.go b/runtime/internal/lib/sync/v23_internal_test.go
new file mode 100644
index 0000000..16f69e9
--- /dev/null
+++ b/runtime/internal/lib/sync/v23_internal_test.go
@@ -0,0 +1,20 @@
+// 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 sync
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	os.Exit(m.Run())
+}
diff --git a/runtime/internal/lib/sync/wait_group.go b/runtime/internal/lib/sync/wait_group.go
new file mode 100644
index 0000000..ba5b4ae
--- /dev/null
+++ b/runtime/internal/lib/sync/wait_group.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.
+
+package sync
+
+import "sync"
+
+// WaitGroup implements a sync.WaitGroup-like structure that does not require
+// all calls to Add to be made before Wait, instead calls to Add after Wait
+// will fail.
+//
+// As a result, WaitGroup cannot be "re-used" in the way that sync.WaitGroup
+// can. In the following example using sync.WaitGroup, Add, Done and Wait behave
+// in the same way in rounds 1 and 2.
+//
+// var wg sync.WaitGroup
+//
+// Round #1.
+// wg.Add(1)
+// go wg.Done()
+// wg.Wait()
+//
+// Round #2.
+// wg.Add(1)
+// go wg.Done()
+// wg.Wait()
+//
+// However, an equivalent program using WaitGroup would receive an error on the
+// second call to TryAdd.
+type WaitGroup struct {
+	mu      sync.Mutex
+	waiting bool
+	pending sync.WaitGroup
+}
+
+// TryAdd attempts to increment the counter. If Wait has already been called,
+// TryAdd fails to increment the counter and returns false.
+// If the counter becomes zero, all goroutines blocked on Wait are released.
+func (w *WaitGroup) TryAdd() (added bool) {
+	w.mu.Lock()
+	defer w.mu.Unlock()
+	if w.waiting {
+		return false
+	}
+	w.pending.Add(1)
+	return true
+}
+
+// Done decrements the counter. If the counter goes negative, Done panics.
+func (w *WaitGroup) Done() {
+	w.pending.Done()
+}
+
+// Wait blocks until the counter is zero.
+func (w *WaitGroup) Wait() {
+	w.mu.Lock()
+	w.waiting = true
+	w.mu.Unlock()
+	w.pending.Wait()
+}
diff --git a/runtime/internal/lib/sync/wait_group_test.go b/runtime/internal/lib/sync/wait_group_test.go
new file mode 100644
index 0000000..30c43e6
--- /dev/null
+++ b/runtime/internal/lib/sync/wait_group_test.go
@@ -0,0 +1,126 @@
+// 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 sync
+
+import (
+	"testing"
+
+	"v.io/x/ref/test/testutil"
+)
+
+//go:generate v23 test generate
+
+// TestRandom tests Wait after a random sequence of TryAdd's and Done's that
+// leaves the counter at 0.
+func TestRandom(t *testing.T) {
+	testutil.InitRandGenerator(t.Logf)
+	var w WaitGroup
+	N := 100
+
+	count := 0
+	for n := 0; n < N; n++ {
+		if count == 0 || testutil.RandomIntn(2) == 0 {
+			if !w.TryAdd() {
+				t.Fatal("TryAdd failed")
+			}
+			count++
+			continue
+		}
+		w.Done()
+		count--
+	}
+	for d := 0; d < count; d++ {
+		w.Done()
+	}
+
+	w.Wait()
+}
+
+func TestConcurrentWait(t *testing.T) {
+	for r := 0; r < 100; r++ {
+		var w WaitGroup
+
+		done := make(chan struct{}, 1)
+
+		if !w.TryAdd() {
+			t.Fatal("TryAdd failed")
+		}
+
+		go func() {
+			w.Wait()
+			// w.Wait() should not return before w.Done() sets the counter
+			// to 0.
+			// This test is not deterministic as we cannot infer the order
+			// in which Wait() and the last Done() return. Hopefully, bugs
+			// will revealed by repeating the test.
+			select {
+			case <-done:
+			default:
+				t.Fatal("Wait returned before Done.")
+			}
+		}()
+
+		for w.TryAdd() {
+			w.Done()
+		}
+		close(done)
+		w.Done()
+	}
+}
+
+func TestTryAddFailsAfterWait(t *testing.T) {
+	var w WaitGroup
+
+	if !w.TryAdd() {
+		t.Fatal("TryAdd failed")
+	}
+
+	go w.Wait()
+
+	// At some point, another goroutine will be in w.Wait() and w.TryAdd()
+	// should fail. If this doesn't happen, the test will never terminate.
+	for w.TryAdd() {
+		w.Done()
+	}
+	w.Done()
+}
+
+func TestIdempotentWait(t *testing.T) {
+	var w WaitGroup
+
+	done := make(chan struct{}, 1)
+
+	if !w.TryAdd() {
+		t.Fatal("TryAdd failed")
+	}
+
+	// w.Wait() should be idempotent.
+	for i := 0; i < 2; i++ {
+		go func() {
+			w.Wait()
+			select {
+			case <-done:
+			default:
+				t.Fatal("Wait returned before Done.")
+			}
+		}()
+	}
+
+	for w.TryAdd() {
+		w.Done()
+	}
+	close(done)
+	w.Done()
+}
+
+func TestDoneFailsBeforeAdd(t *testing.T) {
+	var w WaitGroup
+	defer func() {
+		if r := recover(); r == nil {
+			t.Fatal("Done succeeded before Add.")
+		}
+	}()
+	w.Done()
+}
diff --git a/runtime/internal/lib/tcputil/tcputil.go b/runtime/internal/lib/tcputil/tcputil.go
new file mode 100644
index 0000000..39a167a
--- /dev/null
+++ b/runtime/internal/lib/tcputil/tcputil.go
@@ -0,0 +1,106 @@
+// 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 tcputil contains functions commonly used to manipulate TCP
+// connections.
+package tcputil
+
+import (
+	"net"
+	"time"
+
+	"v.io/v23/context"
+	"v.io/v23/flow"
+
+	"v.io/x/ref/runtime/internal/lib/framer"
+)
+
+const keepAlivePeriod = 30 * time.Second
+
+// EnableTCPKeepAlive enabled the KeepAlive option on a TCP connection.
+//
+// Some cloud providers (like Google Compute Engine) blackhole inactive TCP
+// connections, we need to set TCP keep alive option to prevent that.
+// See: https://developers.google.com/compute/docs/troubleshooting#communicatewithinternet
+//
+// The same problem can happen when one end of a TCP connection dies and the
+// TCP FIN or RST packet doesn't reach the other end, e.g. when the machine
+// dies, falls off the network, or when there is packet loss. So, it is best to
+// enable this option for all TCP connections.
+func EnableTCPKeepAlive(conn net.Conn) error {
+	if tcpconn, ok := conn.(*net.TCPConn); ok {
+		if err := tcpconn.SetKeepAlivePeriod(keepAlivePeriod); err != nil {
+			return err
+		}
+		return tcpconn.SetKeepAlive(true)
+	}
+	return nil
+}
+
+type TCP struct{}
+
+// Dial dials a net.Conn to a the specific address and adds framing to the connection.
+func (TCP) Dial(ctx *context.T, network, address string, timeout time.Duration) (flow.Conn, error) {
+	conn, err := net.DialTimeout(network, address, timeout)
+	if err != nil {
+		return nil, err
+	}
+	if err := EnableTCPKeepAlive(conn); err != nil {
+		return nil, err
+	}
+	return NewTCPConn(conn), nil
+}
+
+// Resolve performs a DNS resolution on the provided network and address.
+func (TCP) Resolve(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
+}
+
+// Listen returns a listener that sets KeepAlive on all accepted connections.
+// Connections returned from the listener will be framed.
+func (TCP) Listen(ctx *context.T, network, address string) (flow.Listener, error) {
+	ln, err := net.Listen(network, address)
+	if err != nil {
+		return nil, err
+	}
+	return &tcpListener{ln}, nil
+}
+
+// tcpListener is a wrapper around net.Listener that sets KeepAlive on all
+// accepted connections and returns framed flow.Conns.
+type tcpListener struct {
+	netLn net.Listener
+}
+
+func (ln *tcpListener) Accept(ctx *context.T) (flow.Conn, error) {
+	conn, err := ln.netLn.Accept()
+	if err != nil {
+		return nil, err
+	}
+	if err := EnableTCPKeepAlive(conn); err != nil {
+		return nil, err
+	}
+	return NewTCPConn(conn), nil
+}
+
+func (ln *tcpListener) Addr() net.Addr {
+	return ln.netLn.Addr()
+}
+
+func NewTCPConn(c net.Conn) flow.Conn {
+	return tcpConn{framer.New(c), c.LocalAddr()}
+}
+
+type tcpConn struct {
+	flow.MsgReadWriteCloser
+	localAddr net.Addr
+}
+
+func (c tcpConn) LocalAddr() net.Addr {
+	return c.localAddr
+}
diff --git a/runtime/internal/lib/upcqueue/upcqueue.go b/runtime/internal/lib/upcqueue/upcqueue.go
new file mode 100644
index 0000000..fe19778
--- /dev/null
+++ b/runtime/internal/lib/upcqueue/upcqueue.go
@@ -0,0 +1,133 @@
+// 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 upcqueue implements an unbounded producer/consumer queue.  An
+// unbounded producer/consumer queue is a concurrent buffer supporting multiple
+// concurrent producers and consumers, with timeouts.  The queue can be closed
+// from either end, by the producer and/or the consumer.  When closed, the
+// contents are discarded, and subsequent operations return an error.
+//
+// Note: the main reason to use a producer/consumer queue instead of a channel
+// is to allow the consumer to close the channel.  This queue can be used for
+// many-to-many communication with multiple producers and/or multiple consumers.
+// Any of the producers and any of the consumers are allowed to close the
+// queue.
+package upcqueue
+
+import (
+	"errors"
+	"sync"
+
+	"v.io/x/ref/runtime/internal/lib/deque"
+	vsync "v.io/x/ref/runtime/internal/lib/sync"
+)
+
+var (
+	ErrQueueIsClosed = errors.New("queue is closed")
+)
+
+// T is an unbounded producer/consumer queue.  It fulfills the same purpose as a
+// Go channel, the main advantage is that the Put() operation does not panic,
+// even after the queue is closed.  The main disadvantage is that the T can't be
+// used in a select operation.
+type T struct {
+	mutex    sync.Mutex
+	contents deque.T // GUARDED_BY(mutex)
+	isClosed bool    // GUARDED_BY(mutex)
+
+	// Number of available elements.
+	avail *vsync.Semaphore
+}
+
+// New returns a producer/consumer queue.
+func New() *T {
+	return &T{avail: vsync.NewSemaphore()}
+}
+
+// Put adds an item to the queue, or returns an error if the queue is closed.
+func (q *T) Put(item interface{}) error {
+	q.mutex.Lock()
+	if q.isClosed {
+		q.mutex.Unlock()
+		return ErrQueueIsClosed
+	}
+	q.contents.PushBack(item)
+	q.mutex.Unlock()
+	q.avail.Inc()
+	return nil
+}
+
+// Get returns the next item from the queue, or an error if the queue is closed
+// or the operation is cancelled.
+func (q *T) Get(cancel <-chan struct{}) (interface{}, error) {
+	err := q.avail.Dec(cancel)
+	if err != nil {
+		if err == vsync.ErrClosed {
+			err = ErrQueueIsClosed
+		}
+		return nil, err
+	}
+	return q.getContents()
+}
+
+// TryGet returns the next item from the queue if there is one.
+func (q *T) TryGet() (interface{}, error) {
+	err := q.avail.TryDec()
+	if err != nil {
+		if err == vsync.ErrClosed {
+			err = ErrQueueIsClosed
+		}
+		return nil, err
+	}
+	return q.getContents()
+}
+
+func (q *T) getContents() (interface{}, error) {
+	q.mutex.Lock()
+	defer q.mutex.Unlock()
+	if q.contents.Size() == 0 {
+		if q.isClosed {
+			return nil, ErrQueueIsClosed
+		}
+		return nil, nil
+	}
+	item := q.contents.PopFront()
+	return item, nil
+}
+
+// Close closes the queue, without discarding the contents.  All Put* operations
+// currently running may, or may not, add their values to the queue.  All Put*
+// operations that happen-after the Close will fail.
+func (q *T) Close() {
+	q.mutex.Lock()
+	q.isClosed = true
+	q.mutex.Unlock()
+	q.avail.Close()
+}
+
+// Shutdown closes the queue and returns the contents.  Any concurrent Get
+// and Put operations might exchange values, but all operations that
+// happen-after the Shutdown will fail.
+func (q *T) Shutdown() []interface{} {
+	q.mutex.Lock()
+	q.isClosed = true
+	var contents []interface{}
+	for {
+		item := q.contents.PopFront()
+		if item == nil {
+			break
+		}
+		contents = append(contents, item)
+	}
+	q.mutex.Unlock()
+	q.avail.Close()
+	return contents
+}
+
+// IsClosed returns whether the queue has been closed (with Close or Shutdown).
+func (q *T) IsClosed() bool {
+	q.mutex.Lock()
+	defer q.mutex.Unlock()
+	return q.isClosed
+}
diff --git a/runtime/internal/lib/upcqueue/upcqueue_test.go b/runtime/internal/lib/upcqueue/upcqueue_test.go
new file mode 100644
index 0000000..3f5220c
--- /dev/null
+++ b/runtime/internal/lib/upcqueue/upcqueue_test.go
@@ -0,0 +1,350 @@
+// 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 upcqueue
+
+import (
+	"sync"
+	"sync/atomic"
+	"testing"
+	"time"
+
+	"v.io/x/ref/internal/logger"
+	vsync "v.io/x/ref/runtime/internal/lib/sync"
+)
+
+//go:generate v23 test generate
+
+const (
+	elementCount = 100
+	writerCount  = 10
+	readerCount  = 10
+)
+
+// Test normal Put()/Get() combination.
+func TestSimplePut(t *testing.T) {
+	queue := New()
+	done := make(chan struct{}, 1)
+	go func() {
+		queue.Put(1)
+		done <- struct{}{}
+	}()
+
+	<-done
+
+	item, err := queue.Get(nil)
+	if err != nil {
+		t.Errorf("Get: %v", err)
+	}
+
+	if item.(int) != 1 {
+		t.Errorf("Expected 1, actual=%v", item)
+	}
+}
+
+// Test normal Put()/Get() combination.
+func TestSimpleGet(t *testing.T) {
+	queue := New()
+	done := make(chan struct{}, 1)
+	go func() {
+		item, err := queue.Get(nil)
+		if err != nil {
+			t.Errorf("Get: %v", item)
+		}
+		if item.(int) != 1 {
+			t.Errorf("Expected 1, actual=%v", item)
+		}
+		done <- struct{}{}
+	}()
+
+	select {
+	case <-done:
+		t.Errorf("Unexpected completion")
+	default:
+	}
+
+	queue.Put(1)
+	<-done
+}
+
+// Test normal queue operation with a single producer and single consumer.
+func TestSequential(t *testing.T) {
+	queue := New()
+	done := make(chan struct{}, 1)
+	cancel := make(chan struct{})
+
+	// Check that the queue elements are sequentially increasing ints.
+	logger.Global().VI(1).Infof("Start consumer")
+	go func() {
+		for i := 0; i != elementCount; i++ {
+			item, err := queue.Get(cancel)
+			if err != nil {
+				t.Errorf("Get: %v", err)
+			}
+			if item == nil {
+				break
+			}
+			j := item.(int)
+			if j != i {
+				t.Errorf("Expected %d, actual %d", i, j)
+			}
+		}
+		done <- struct{}{}
+	}()
+
+	// Generate the sequential ints.
+	logger.Global().VI(1).Infof("Put values")
+	for i := 0; i != elementCount; i++ {
+		queue.Put(i)
+	}
+
+	// Wait for the consumer.
+	logger.Global().VI(1).Infof("Waiting for consumer")
+	<-done
+
+	// Any subsequent read should timeout.
+	logger.Global().VI(1).Infof("Start consumer")
+	go func() {
+		_, err := queue.Get(cancel)
+		if err != vsync.ErrCanceled {
+			t.Errorf("Expected timeout: %v", err)
+		}
+		logger.Global().VI(1).Infof("Consumer done")
+		done <- struct{}{}
+	}()
+
+	logger.Global().VI(1).Infof("Sleep a little")
+	time.Sleep(100 * time.Millisecond)
+	select {
+	case <-done:
+		t.Errorf("Unexpected completion")
+	default:
+	}
+
+	logger.Global().VI(1).Infof("Cancel")
+	close(cancel)
+
+	logger.Global().VI(1).Infof("Wait for consumer")
+	<-done
+}
+
+// Test that Get() returns an error when the queue is closed.
+func TestSequentialClose(t *testing.T) {
+	logger.Global().VI(1).Infof("Put")
+	queue := New()
+	err := queue.Put(0)
+	if err != nil {
+		t.Errorf("Put: %v", err)
+	}
+	logger.Global().VI(1).Infof("Close")
+	queue.Close()
+
+	// Check that Get() returns the element.
+	logger.Global().VI(1).Infof("Get")
+	item, err := queue.Get(nil)
+	if err != nil {
+		t.Errorf("Get: %v", err)
+	}
+	if item.(int) != 0 {
+		t.Errorf("Unexpected value: %v", item)
+	}
+
+	// Check that Get() returns an error.
+	logger.Global().VI(1).Infof("Get")
+	_, err = queue.Get(nil)
+	if err != ErrQueueIsClosed {
+		t.Errorf("Expected queue to be closed: %v", err)
+	}
+
+	// Check that Put() returns an error.
+	logger.Global().VI(1).Infof("Put")
+	err = queue.Put(0)
+	if err != ErrQueueIsClosed {
+		t.Errorf("Expected queue to be closed: %v", err)
+	}
+}
+
+// Test that concurrent Puts() may add values to the queue.
+func TestConcurrentClose(t *testing.T) {
+	queue := New()
+	pending := &sync.WaitGroup{}
+	pending.Add(2 * writerCount)
+	for i := 0; i != writerCount; i++ {
+		go func() {
+			err := queue.Put(1)
+			if err != nil {
+				logger.Global().VI(1).Infof("Put: %v", err)
+			}
+			pending.Done()
+		}()
+	}
+	time.Sleep(100 * time.Millisecond)
+	queue.Close()
+	for i := 0; i != writerCount; i++ {
+		go func() {
+			err := queue.Put(2)
+			if err == nil {
+				t.Errorf("Expected error")
+			}
+			pending.Done()
+		}()
+	}
+
+	readers := 0
+	for {
+		item, err := queue.Get(nil)
+		if err != nil {
+			break
+		}
+		if item.(int) != 1 {
+			t.Errorf("Expected 1, actual=%v", item)
+		}
+		readers++
+	}
+	logger.Global().VI(1).Infof("%d operations completed", readers)
+	if readers > writerCount {
+		t.Errorf("Too many readers")
+	}
+	pending.Wait()
+}
+
+// Test that Get() returns an error when the queue is shut down.
+func TestSequentialShutdown(t *testing.T) {
+	queue := New()
+
+	logger.Global().VI(1).Infof("Put")
+	err := queue.Put(0)
+	if err != nil {
+		t.Errorf("Put: %v", err)
+	}
+
+	logger.Global().VI(1).Infof("Shutdown")
+	queue.Shutdown()
+
+	// Check that Get() returns an error.
+	logger.Global().VI(1).Infof("Get")
+	_, err = queue.Get(nil)
+	if err != ErrQueueIsClosed {
+		t.Errorf("Expected queue to be closed: %v", err)
+	}
+
+	// Check that Put() returns an error.
+	logger.Global().VI(1).Infof("Put")
+	err = queue.Put(0)
+	if err != ErrQueueIsClosed {
+		t.Errorf("Expected queue to be closed: %v", err)
+	}
+}
+
+// Test with concurrent producers, but a single consumer.
+func TestConcurrentPutNoTimeouts(t *testing.T) {
+	queue := New()
+	pending := &sync.WaitGroup{}
+
+	// Generate the sequential ints.
+	for i := 0; i != writerCount; i++ {
+		pending.Add(1)
+		go func() {
+			for j := 0; j != elementCount; j++ {
+				queue.Put(j)
+			}
+			pending.Done()
+		}()
+	}
+
+	// Sum up the results and compare.
+	sum := 0
+	for i := 0; i != writerCount*elementCount; i++ {
+		item, err := queue.Get(nil)
+		if err != nil {
+			t.Errorf("Get: %v", err)
+		}
+		if item == nil {
+			break
+		}
+		sum += item.(int)
+	}
+	expected := writerCount * elementCount * (elementCount - 1) / 2
+	if sum != expected {
+		t.Errorf("Expected sum %d, received %d", expected, sum)
+	}
+
+	pending.Wait()
+}
+
+// Test with concurrent consumers and concurrent producers.
+func TestConcurrentGet(t *testing.T) {
+	queue := New()
+	done := make(chan struct{})
+	pending := &sync.WaitGroup{}
+	pending.Add(readerCount + writerCount)
+	cancel := make(chan struct{})
+
+	// Sum up the results and compare.
+	sum := uint32(0)
+	count := uint32(0)
+	logger.Global().VI(1).Infof("Start consumers")
+	for i := 0; i != readerCount; i++ {
+		pid := i
+		go func() {
+			for {
+				c := atomic.LoadUint32(&count)
+				if c == writerCount*elementCount {
+					break
+				}
+
+				// The timeout is required for termination.
+				item, err := queue.Get(cancel)
+				if err != nil {
+					continue
+				}
+				atomic.AddUint32(&sum, uint32(item.(int)))
+				atomic.AddUint32(&count, 1)
+			}
+			logger.Global().VI(1).Infof("Consumer %d done", pid)
+			pending.Done()
+		}()
+	}
+
+	// Generate the sequential ints.
+	logger.Global().VI(1).Infof("Start producers")
+	for i := 0; i != writerCount; i++ {
+		pid := i
+		go func() {
+			for j := 0; j != elementCount; j++ {
+				err := queue.Put(j)
+				if err != nil {
+					t.Errorf("Put: %v", err)
+				}
+			}
+			logger.Global().VI(1).Infof("Producer %d done", pid)
+			pending.Done()
+		}()
+	}
+
+	logger.Global().VI(1).Infof("Start termination checker")
+	go func() {
+		pending.Wait()
+		done <- struct{}{}
+	}()
+
+	logger.Global().VI(1).Infof("Wait for processes")
+	stop := false
+	for !stop {
+		time.Sleep(100 * time.Millisecond)
+		select {
+		case <-done:
+			stop = true
+		default:
+			cancel <- struct{}{}
+		}
+	}
+
+	logger.Global().VI(1).Infof("Checking the sum")
+	expected := writerCount * elementCount * (elementCount - 1) / 2
+	s := atomic.LoadUint32(&sum)
+	if s != uint32(expected) {
+		t.Errorf("Expected sum %d, received %d", expected, sum)
+	}
+}
diff --git a/runtime/internal/lib/upcqueue/v23_internal_test.go b/runtime/internal/lib/upcqueue/v23_internal_test.go
new file mode 100644
index 0000000..794f10f
--- /dev/null
+++ b/runtime/internal/lib/upcqueue/v23_internal_test.go
@@ -0,0 +1,20 @@
+// 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 upcqueue
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	os.Exit(m.Run())
+}
diff --git a/runtime/internal/lib/websocket/conn.go b/runtime/internal/lib/websocket/conn.go
new file mode 100644
index 0000000..77c7e99
--- /dev/null
+++ b/runtime/internal/lib/websocket/conn.go
@@ -0,0 +1,171 @@
+// 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
new file mode 100644
index 0000000..ca7e16d
--- /dev/null
+++ b/runtime/internal/lib/websocket/conn_nacl.go
@@ -0,0 +1,115 @@
+// 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
new file mode 100644
index 0000000..292f616
--- /dev/null
+++ b/runtime/internal/lib/websocket/conn_test.go
@@ -0,0 +1,120 @@
+// 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
new file mode 100644
index 0000000..f7a3b21
--- /dev/null
+++ b/runtime/internal/lib/websocket/dialer.go
@@ -0,0 +1,47 @@
+// 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
new file mode 100644
index 0000000..fb92d0a
--- /dev/null
+++ b/runtime/internal/lib/websocket/hybrid.go
@@ -0,0 +1,56 @@
+// 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
new file mode 100644
index 0000000..f31f096
--- /dev/null
+++ b/runtime/internal/lib/websocket/listener.go
@@ -0,0 +1,233 @@
+// 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
new file mode 100644
index 0000000..ebf6255
--- /dev/null
+++ b/runtime/internal/lib/websocket/listener_nacl.go
@@ -0,0 +1,24 @@
+// 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
new file mode 100644
index 0000000..f19ddd3
--- /dev/null
+++ b/runtime/internal/lib/websocket/listener_test.go
@@ -0,0 +1,98 @@
+// 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
new file mode 100644
index 0000000..5a99c23
--- /dev/null
+++ b/runtime/internal/lib/websocket/resolver.go
@@ -0,0 +1,23 @@
+// 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
new file mode 100644
index 0000000..a7466d1
--- /dev/null
+++ b/runtime/internal/lib/websocket/util_test.go
@@ -0,0 +1,292 @@
+// 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"
+)
+
+//go:generate v23 test generate
+
+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/v23_internal_test.go b/runtime/internal/lib/websocket/v23_internal_test.go
new file mode 100644
index 0000000..a77fac4
--- /dev/null
+++ b/runtime/internal/lib/websocket/v23_internal_test.go
@@ -0,0 +1,20 @@
+// 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 websocket
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	os.Exit(m.Run())
+}
diff --git a/runtime/internal/lib/websocket/ws_test.go b/runtime/internal/lib/websocket/ws_test.go
new file mode 100644
index 0000000..b368cce
--- /dev/null
+++ b/runtime/internal/lib/websocket/ws_test.go
@@ -0,0 +1,107 @@
+// 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/lib/xwebsocket/conn.go b/runtime/internal/lib/xwebsocket/conn.go
new file mode 100644
index 0000000..7d2c248
--- /dev/null
+++ b/runtime/internal/lib/xwebsocket/conn.go
@@ -0,0 +1,138 @@
+// 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 xwebsocket
+
+import (
+	"fmt"
+	"io"
+	"net"
+	"sync"
+	"time"
+
+	"github.com/gorilla/websocket"
+
+	"v.io/v23/flow"
+)
+
+// WebsocketConn provides a flow.Conn interface for a websocket connection.
+func WebsocketConn(ws *websocket.Conn) flow.Conn {
+	return &wrappedConn{ws: ws}
+}
+
+// wrappedConn provides a flow.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) ReadMsg() ([]byte, error) {
+	c.readLock.Lock()
+	defer c.readLock.Unlock()
+	t, b, err := c.ws.ReadMessage()
+	if err != nil {
+		return nil, err
+	}
+	if t != websocket.BinaryMessage {
+		return nil, fmt.Errorf("Unexpected message type %d", t)
+	}
+	return b, nil
+}
+
+func (c *wrappedConn) WriteMsg(bufs ...[]byte) (int, error) {
+	c.writeLock.Lock()
+	defer c.writeLock.Unlock()
+	if len(bufs) == 0 {
+		return 0, nil
+	}
+	var b []byte
+	for _, buf := range bufs {
+		b = append(b, buf...)
+	}
+	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 c.ws.LocalAddr()
+}
+
+// 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/xwebsocket/conn_nacl.go b/runtime/internal/lib/xwebsocket/conn_nacl.go
new file mode 100644
index 0000000..35cc5bb
--- /dev/null
+++ b/runtime/internal/lib/xwebsocket/conn_nacl.go
@@ -0,0 +1,92 @@
+// 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 xwebsocket
+
+import (
+	"net"
+	"net/url"
+	"runtime/ppapi"
+	"sync"
+	"time"
+
+	"v.io/v23/context"
+	"v.io/v23/flow"
+)
+
+// Ppapi instance which must be set before the Dial is called.
+var PpapiInstance ppapi.Instance
+
+func WebsocketConn(address string, ws *ppapi.WebsocketConn) flow.Conn {
+	return &wrappedConn{
+		address: address,
+		ws:      ws,
+	}
+}
+
+type wrappedConn struct {
+	address   string
+	ws        *ppapi.WebsocketConn
+	readLock  sync.Mutex
+	writeLock sync.Mutex
+}
+
+func Dial(ctx *context.T, protocol, address string, timeout time.Duration) (flow.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) ReadMsg() ([]byte, error) {
+	defer c.readLock.Unlock()
+	c.readLock.Lock()
+	return c.ws.ReceiveMessage()
+}
+
+func (c *wrappedConn) WriteMsg(bufs ...[]byte) (int, error) {
+	defer c.writeLock.Unlock()
+	c.writeLock.Lock()
+	var b []byte
+	for _, buf := range bufs {
+		b = append(b, buf...)
+	}
+	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}
+}
+
+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/xwebsocket/conn_test.go b/runtime/internal/lib/xwebsocket/conn_test.go
new file mode 100644
index 0000000..c8c6357
--- /dev/null
+++ b/runtime/internal/lib/xwebsocket/conn_test.go
@@ -0,0 +1,93 @@
+// 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 xwebsocket
+
+import (
+	"bytes"
+	"net"
+	"net/http"
+	"sync"
+	"testing"
+	"time"
+
+	"github.com/gorilla/websocket"
+
+	"v.io/v23/context"
+	"v.io/v23/flow"
+)
+
+func writer(c flow.Conn, data []byte, times int, wg *sync.WaitGroup) {
+	defer wg.Done()
+	for i := 0; i < times; i++ {
+		c.WriteMsg(data)
+	}
+}
+
+func reader(t *testing.T, c flow.Conn, expected []byte, totalWrites int) {
+	totalReads := 0
+	for buf, err := c.ReadMsg(); err == nil; buf, err = c.ReadMsg() {
+		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 := WS{}.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/xwebsocket/errors.vdl b/runtime/internal/lib/xwebsocket/errors.vdl
new file mode 100644
index 0000000..5d27eb3
--- /dev/null
+++ b/runtime/internal/lib/xwebsocket/errors.vdl
@@ -0,0 +1,10 @@
+// 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 xwebsocket
+
+error (
+  ListenerClosed() {"en":"listener is already closed."}
+  ListenCalledInNaCl() {"en": "Listen cannot be called in NaCl code."}
+)
\ No newline at end of file
diff --git a/runtime/internal/lib/xwebsocket/errors.vdl.go b/runtime/internal/lib/xwebsocket/errors.vdl.go
new file mode 100644
index 0000000..e436641
--- /dev/null
+++ b/runtime/internal/lib/xwebsocket/errors.vdl.go
@@ -0,0 +1,35 @@
+// 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 by the vanadium vdl tool.
+// Source: errors.vdl
+
+package xwebsocket
+
+import (
+	// VDL system imports
+	"v.io/v23/context"
+	"v.io/v23/i18n"
+	"v.io/v23/verror"
+)
+
+var (
+	ErrListenerClosed     = verror.Register("v.io/x/ref/runtime/internal/lib/xwebsocket.ListenerClosed", verror.NoRetry, "{1:}{2:} listener is already closed.")
+	ErrListenCalledInNaCl = verror.Register("v.io/x/ref/runtime/internal/lib/xwebsocket.ListenCalledInNaCl", verror.NoRetry, "{1:}{2:} Listen cannot be called in NaCl code.")
+)
+
+func init() {
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrListenerClosed.ID), "{1:}{2:} listener is already closed.")
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrListenCalledInNaCl.ID), "{1:}{2:} Listen cannot be called in NaCl code.")
+}
+
+// NewErrListenerClosed returns an error with the ErrListenerClosed ID.
+func NewErrListenerClosed(ctx *context.T) error {
+	return verror.New(ErrListenerClosed, ctx)
+}
+
+// NewErrListenCalledInNaCl returns an error with the ErrListenCalledInNaCl ID.
+func NewErrListenCalledInNaCl(ctx *context.T) error {
+	return verror.New(ErrListenCalledInNaCl, ctx)
+}
diff --git a/runtime/internal/lib/xwebsocket/listener.go b/runtime/internal/lib/xwebsocket/listener.go
new file mode 100644
index 0000000..3e6c48b
--- /dev/null
+++ b/runtime/internal/lib/xwebsocket/listener.go
@@ -0,0 +1,219 @@
+// 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 xwebsocket
+
+import (
+	"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"
+	"v.io/v23/flow"
+)
+
+const 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{} // flow.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
+}
+
+func listener(protocol, address string, hybrid bool) (flow.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: netLn, c: ln.httpQ})
+	return ln, nil
+}
+
+func (ln *wsTCPListener) Accept(ctx *context.T) (flow.Conn, error) {
+	for {
+		item, ok := <-ln.acceptQ
+		if !ok {
+			return nil, NewErrListenerClosed(ctx)
+		}
+		switch v := item.(type) {
+		case flow.Conn:
+			return v, nil
+		case error:
+			return nil, v
+		default:
+			logger.Global().Errorf("Unexpected type %T in channel (%v)", v, v)
+		}
+	}
+}
+
+func (ln *wsTCPListener) Addr() net.Addr {
+	protocol := "ws"
+	if ln.hybrid {
+		protocol = "wsh"
+	}
+	return addr{protocol, ln.netLn.Addr().String()}
+}
+
+func (ln *wsTCPListener) Close() error {
+	ln.mu.Lock()
+	if ln.closed {
+		ln.mu.Unlock()
+		return NewErrListenerClosed(nil)
+	}
+	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 a non-HTTP, then the connection is added to ln.acceptQ.
+// If a HTTP, 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 <- tcputil.NewTCPConn(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)
+}
+
+// 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, NewErrListenerClosed(nil)
+	}
+	return conn, nil
+}
+
+type addr struct{ n, a string }
+
+func (a addr) Network() string { return a.n }
+func (a addr) String() string  { return a.a }
+
+func drainChan(c <-chan interface{}) {
+	for {
+		item, ok := <-c
+		if !ok {
+			return
+		}
+		if conn, ok := item.(flow.Conn); ok {
+			conn.Close()
+		}
+	}
+}
diff --git a/runtime/internal/lib/xwebsocket/listener_test.go b/runtime/internal/lib/xwebsocket/listener_test.go
new file mode 100644
index 0000000..4b5307e
--- /dev/null
+++ b/runtime/internal/lib/xwebsocket/listener_test.go
@@ -0,0 +1,96 @@
+// 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 xwebsocket
+
+import (
+	"bytes"
+	"log"
+	"net"
+	"strings"
+	"testing"
+	"time"
+
+	"v.io/v23/context"
+)
+
+func TestAcceptsAreNotSerialized(t *testing.T) {
+	ctx, _ := context.RootContext()
+	ln, err := WSH{}.Listen(ctx, "wsh", "127.0.0.1:0")
+	if err != nil {
+		t.Fatal(err)
+	}
+	portscan := make(chan struct{})
+
+	// Goroutine that continuously accepts connections.
+	go func() {
+		for {
+			conn, err := ln.Accept(ctx)
+			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 := WS{}.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 := WSH{}.Listen(ctx, "wsh", "127.0.0.1:0")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// Goroutine that continuously accepts connections.
+	go func() {
+		for {
+			_, err := ln.Accept(ctx)
+			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/xwebsocket/ws.go b/runtime/internal/lib/xwebsocket/ws.go
new file mode 100644
index 0000000..0429cc8
--- /dev/null
+++ b/runtime/internal/lib/xwebsocket/ws.go
@@ -0,0 +1,68 @@
+// 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 xwebsocket
+
+import (
+	"net"
+	"net/http"
+	"net/url"
+	"time"
+
+	"v.io/x/ref/runtime/internal/lib/tcputil"
+
+	"github.com/gorilla/websocket"
+
+	"v.io/v23/context"
+	"v.io/v23/flow"
+)
+
+// 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"}
+
+const bufferSize = 4096
+
+type WS struct{}
+
+func (WS) Dial(ctx *context.T, protocol, address string, timeout time.Duration) (flow.Conn, error) {
+	var deadline time.Time
+	if timeout > 0 {
+		deadline = time.Now().Add(timeout)
+	}
+	tcp := mapWebSocketToTCP[protocol]
+	conn, err := net.DialTimeout(tcp, address, timeout)
+	if err != nil {
+		return nil, err
+	}
+	conn.SetReadDeadline(deadline)
+	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{}, bufferSize, bufferSize)
+	if err != nil {
+		return nil, err
+	}
+	var zero time.Time
+	conn.SetDeadline(zero)
+	return WebsocketConn(ws), nil
+}
+
+func (WS) 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
+}
+
+func (WS) Listen(ctx *context.T, protocol, address string) (flow.Listener, error) {
+	return listener(protocol, address, false)
+}
diff --git a/runtime/internal/lib/xwebsocket/ws_test.go b/runtime/internal/lib/xwebsocket/ws_test.go
new file mode 100644
index 0000000..2411030
--- /dev/null
+++ b/runtime/internal/lib/xwebsocket/ws_test.go
@@ -0,0 +1,104 @@
+// 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 xwebsocket_test
+
+import (
+	"bytes"
+	"crypto/rand"
+	"testing"
+	"time"
+
+	"v.io/x/ref/runtime/internal/lib/tcputil"
+	websocket "v.io/x/ref/runtime/internal/lib/xwebsocket"
+
+	"v.io/v23/context"
+	"v.io/v23/flow"
+)
+
+func TestWSToWS(t *testing.T) {
+	runTest(t, websocket.WS{}, websocket.WS{}, "ws", "ws")
+}
+
+func TestWSToWSH(t *testing.T) {
+	runTest(t, websocket.WS{}, websocket.WSH{}, "ws", "wsh")
+}
+
+func TestWSHToWSH(t *testing.T) {
+	runTest(t, websocket.WSH{}, websocket.WSH{}, "wsh", "wsh")
+}
+
+func TestTCPToWSH(t *testing.T) {
+	runTest(t, tcputil.TCP{}, websocket.WSH{}, "tcp", "wsh")
+}
+
+var randData []byte
+
+const (
+	chunkSize = 1 << 10
+	numChunks = 10
+)
+
+func init() {
+	randData = make([]byte, chunkSize*numChunks)
+	if _, err := rand.Read(randData); err != nil {
+		panic(err)
+	}
+}
+
+func runTest(t *testing.T, dialObj, listenObj flow.Protocol, dialP, listenP string) {
+	ctx, _ := context.RootContext()
+	address := "127.0.0.1:0"
+	timeout := time.Second
+	acceptCh := make(chan flow.Conn)
+
+	ln, err := listenObj.Listen(ctx, listenP, address)
+	if err != nil {
+		t.Fatal(err)
+	}
+	go func() {
+		a, err := ln.Accept(ctx)
+		if err != nil {
+			t.Fatal(err)
+		}
+		acceptCh <- a
+	}()
+
+	dialed, err := dialObj.Dial(ctx, dialP, ln.Addr().String(), timeout)
+	if err != nil {
+		t.Fatal(err)
+	}
+	go writeData(t, dialed, randData)
+	go readData(t, dialed, randData)
+	accepted := <-acceptCh
+	go writeData(t, accepted, randData)
+	go readData(t, accepted, randData)
+}
+
+func writeData(t *testing.T, c flow.Conn, data []byte) {
+	for i := 0; i < numChunks; i++ {
+		if _, err := c.WriteMsg(data[:chunkSize]); err != nil {
+			t.Fatal(err)
+		}
+		data = data[chunkSize:]
+	}
+}
+
+func readData(t *testing.T, c flow.Conn, expected []byte) {
+	read := make([]byte, len(expected))
+	read = read[:0]
+	for i := 0; i < numChunks; i++ {
+		b, err := c.ReadMsg()
+		if err != nil {
+			t.Fatal(err)
+		}
+		if len(b) != chunkSize {
+			t.Errorf("got message of size %v, want %v", len(b), chunkSize)
+		}
+		read = append(read, b...)
+	}
+	if !bytes.Equal(read, expected) {
+		t.Errorf("read %v, want %v", read, expected)
+	}
+}
diff --git a/runtime/internal/lib/xwebsocket/wsh.go b/runtime/internal/lib/xwebsocket/wsh.go
new file mode 100644
index 0000000..b563ac6
--- /dev/null
+++ b/runtime/internal/lib/xwebsocket/wsh.go
@@ -0,0 +1,58 @@
+// 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 xwebsocket
+
+import (
+	"net"
+	"time"
+
+	"v.io/x/ref/runtime/internal/lib/tcputil"
+
+	"v.io/v23/context"
+	"v.io/v23/flow"
+)
+
+type WSH struct{}
+
+// Dial returns flow.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 (WSH) Dial(ctx *context.T, network, address string, timeout time.Duration) (flow.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 tcputil.NewTCPConn(conn), nil
+}
+
+// Resolve performs a DNS resolution on the network, address and always
+// returns tcp as its Network.
+func (WSH) Resolve(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
+}
+
+// Listener returns a flow.Conn 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 (WSH) Listen(ctx *context.T, protocol, address string) (flow.Listener, error) {
+	return listener(protocol, address, true)
+}
diff --git a/runtime/internal/lib/xwebsocket/wsh_nacl.go b/runtime/internal/lib/xwebsocket/wsh_nacl.go
new file mode 100644
index 0000000..1e8e58d
--- /dev/null
+++ b/runtime/internal/lib/xwebsocket/wsh_nacl.go
@@ -0,0 +1,38 @@
+// 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 xwebsocket
+
+import (
+	"net/url"
+	"time"
+
+	"v.io/v23/context"
+	"v.io/v23/flow"
+)
+
+type WS struct{}
+
+func (WS) Dial(ctx *context.T, protocol, address string, timeout time.Duration) (flow.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 (WS) Resolve(ctx *context.T, protocol, address string) (string, string, error) {
+	return "ws", address, nil
+}
+
+func (WS) Listen(ctx *context.T, protocol, address string) (flow.Listener, error) {
+	return nil, NewErrListenCalledInNaCl(ctx)
+}
diff --git a/runtime/internal/mojo_util.go b/runtime/internal/mojo_util.go
new file mode 100644
index 0000000..308ba01
--- /dev/null
+++ b/runtime/internal/mojo_util.go
@@ -0,0 +1,39 @@
+// 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 mojo
+
+package internal
+
+import (
+	"flag"
+	"log"
+	"strings"
+
+	"v.io/x/ref/lib/flags"
+)
+
+// NOTE(nlacasse): This variable must be set at build time by passing the
+// "-ldflags" flag to "go build" like so:
+// go build -ldflags "-X v.io/x/ref/runtime/internal.commandLineFlags '--flag1=foo --flag2=bar'"
+var commandLineFlags string
+
+// TODO(sadovsky): Terrible, terrible hack.
+func parseFlagsInternal(f *flags.Flags, config map[string]string) error {
+	// We expect that command-line flags have not been parsed. v23_util
+	// performs command-line parsing at this point. For Mojo, we instead parse
+	// command-line flags from the commandLineFlags variable set at build time.
+	// TODO(sadovsky): Maybe move this check to util.go, or drop it?
+	if flag.CommandLine.Parsed() {
+		panic("flag.CommandLine.Parse() has been called")
+	}
+
+	// NOTE(nlacasse): Don't use vlog here, since vlog output depends on
+	// command line flags which have not been parsed yet.
+	log.Printf("Parsing flags: %v\n", commandLineFlags)
+
+	// TODO(sadovsky): Support argument quoting. More generally, parse this env
+	// var similar to how bash parses arguments.
+	return f.Parse(strings.Split(commandLineFlags, " "), config)
+}
diff --git a/runtime/internal/naming/doc.go b/runtime/internal/naming/doc.go
new file mode 100644
index 0000000..9d1a1b1
--- /dev/null
+++ b/runtime/internal/naming/doc.go
@@ -0,0 +1,6 @@
+// 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 naming provides an implementation of the interfaces in v.io/v23/naming.
+package naming
diff --git a/runtime/internal/naming/endpoint.go b/runtime/internal/naming/endpoint.go
new file mode 100644
index 0000000..9f32a92
--- /dev/null
+++ b/runtime/internal/naming/endpoint.go
@@ -0,0 +1,288 @@
+// 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 naming
+
+import (
+	"errors"
+	"fmt"
+	"net"
+	"regexp"
+	"strconv"
+	"strings"
+
+	"v.io/v23/naming"
+	"v.io/x/lib/metadata"
+)
+
+const (
+	separator          = "@"
+	suffix             = "@@"
+	blessingsSeparator = ","
+	routeSeparator     = ","
+)
+
+var (
+	errInvalidEndpointString = errors.New("invalid endpoint string")
+	hostportEP               = regexp.MustCompile("^(?:\\((.*)\\)@)?([^@]+)$")
+)
+
+// TODO(suharshs): Remove endpoint version 5 after the transition to 6 is complete.
+
+// Network is the string returned by naming.Endpoint.Network implementations
+// defined in this package.
+const Network = "v23"
+
+// Endpoint is a naming.Endpoint implementation used to convey RPC information.
+type Endpoint struct {
+	Protocol     string
+	Address      string
+	RID          naming.RoutingID
+	RouteList    []string
+	Blessings    []string
+	IsMountTable bool
+	IsLeaf       bool
+}
+
+// NewEndpoint creates a new endpoint from a string as per naming.NewEndpoint
+func NewEndpoint(input string) (*Endpoint, error) {
+	ep := new(Endpoint)
+
+	// We have to guess this is a mount table if we don't know.
+	ep.IsMountTable = true
+
+	// If the endpoint does not end in a @, it must be in [blessing@]host:port format.
+	if parts := hostportEP.FindStringSubmatch(input); len(parts) > 0 {
+		hostport := parts[len(parts)-1]
+		var blessing string
+		if len(parts) > 2 {
+			blessing = parts[1]
+		}
+		err := ep.parseHostPort(blessing, hostport)
+		return ep, err
+	}
+	// Trim the prefix and suffix and parse the rest.
+	input = strings.TrimPrefix(strings.TrimSuffix(input, suffix), separator)
+	parts := strings.Split(input, separator)
+	version, err := strconv.ParseUint(parts[0], 10, 16)
+	if err != nil {
+		return nil, fmt.Errorf("invalid version: %v", err)
+	}
+
+	switch version {
+	case 6:
+		err = ep.parseV6(parts)
+	case 5:
+		err = ep.parseV5(parts)
+	default:
+		err = errInvalidEndpointString
+	}
+	return ep, err
+}
+
+func (ep *Endpoint) parseHostPort(blessing, hostport string) error {
+	// Could be in host:port format.
+	if _, _, err := net.SplitHostPort(hostport); err != nil {
+		return errInvalidEndpointString
+	}
+	ep.Protocol = naming.UnknownProtocol
+	ep.Address = hostport
+	ep.RID = naming.NullRoutingID
+	if len(blessing) > 0 {
+		ep.Blessings = []string{blessing}
+	}
+	return nil
+}
+
+func parseMountTableFlag(input string) (bool, bool, error) {
+	switch len(input) {
+	case 0:
+		return true, false, nil
+	case 1:
+		switch f := input[0]; f {
+		case 'l':
+			return false, true, nil
+		case 'm':
+			return true, false, nil
+		case 's':
+			return false, false, nil
+		default:
+			return false, false, fmt.Errorf("%c is not one of 'l', 'm', or 's'", f)
+		}
+	}
+	return false, false, fmt.Errorf("flag is either missing or too long")
+}
+
+func (ep *Endpoint) parseV5(parts []string) error {
+	if len(parts) < 5 {
+		return errInvalidEndpointString
+	}
+
+	ep.Protocol = parts[1]
+	if len(ep.Protocol) == 0 {
+		ep.Protocol = naming.UnknownProtocol
+	}
+
+	var ok bool
+	if ep.Address, ok = naming.Unescape(parts[2]); !ok {
+		return fmt.Errorf("invalid address: bad escape %s", parts[2])
+	}
+	if len(ep.Address) == 0 {
+		ep.Address = net.JoinHostPort("", "0")
+	}
+
+	if err := ep.RID.FromString(parts[3]); err != nil {
+		return fmt.Errorf("invalid routing id: %v", err)
+	}
+
+	var err error
+	if ep.IsMountTable, ep.IsLeaf, err = parseMountTableFlag(parts[4]); err != nil {
+		return fmt.Errorf("invalid mount table flag: %v", err)
+	}
+	// Join the remaining and re-split.
+	if str := strings.Join(parts[5:], separator); len(str) > 0 {
+		ep.Blessings = strings.Split(str, blessingsSeparator)
+	}
+	return nil
+}
+
+func (ep *Endpoint) parseV6(parts []string) error {
+	if len(parts) < 6 {
+		return errInvalidEndpointString
+	}
+
+	ep.Protocol = parts[1]
+	if len(ep.Protocol) == 0 {
+		ep.Protocol = naming.UnknownProtocol
+	}
+
+	var ok bool
+	if ep.Address, ok = naming.Unescape(parts[2]); !ok {
+		return fmt.Errorf("invalid address: bad escape %s", parts[2])
+	}
+	if len(ep.Address) == 0 {
+		ep.Address = net.JoinHostPort("", "0")
+	}
+
+	if len(parts[3]) > 0 {
+		ep.RouteList = strings.Split(parts[3], routeSeparator)
+		for i := range ep.RouteList {
+			if ep.RouteList[i], ok = naming.Unescape(ep.RouteList[i]); !ok {
+				return fmt.Errorf("invalid route: bad escape %s", ep.RouteList[i])
+			}
+		}
+	}
+
+	if err := ep.RID.FromString(parts[4]); err != nil {
+		return fmt.Errorf("invalid routing id: %v", err)
+	}
+
+	var err error
+	if ep.IsMountTable, ep.IsLeaf, err = parseMountTableFlag(parts[5]); err != nil {
+		return fmt.Errorf("invalid mount table flag: %v", err)
+	}
+	// Join the remaining and re-split.
+	if str := strings.Join(parts[6:], separator); len(str) > 0 {
+		ep.Blessings = strings.Split(str, blessingsSeparator)
+	}
+	return nil
+}
+
+func (ep *Endpoint) RoutingID() naming.RoutingID {
+	//nologcall
+	return ep.RID
+}
+
+func (ep *Endpoint) Routes() []string {
+	//nologcall
+	return ep.RouteList
+}
+
+func (ep *Endpoint) Network() string {
+	//nologcall
+	return Network
+}
+
+func init() {
+	metadata.Insert("v23.RPCEndpointVersion", fmt.Sprint(defaultVersion))
+}
+
+var defaultVersion = 5
+
+func (ep *Endpoint) VersionedString(version int) string {
+	// nologcall
+	switch version {
+	case 5:
+		mt := "s"
+		switch {
+		case ep.IsLeaf:
+			mt = "l"
+		case ep.IsMountTable:
+			mt = "m"
+		}
+		blessings := strings.Join(ep.Blessings, blessingsSeparator)
+		return fmt.Sprintf("@5@%s@%s@%s@%s@%s@@",
+			ep.Protocol, naming.Escape(ep.Address, "@"), ep.RID, mt, blessings)
+	case 6:
+		mt := "s"
+		switch {
+		case ep.IsLeaf:
+			mt = "l"
+		case ep.IsMountTable:
+			mt = "m"
+		}
+		blessings := strings.Join(ep.Blessings, blessingsSeparator)
+		escaped := make([]string, len(ep.RouteList))
+		for i := range ep.RouteList {
+			escaped[i] = naming.Escape(ep.RouteList[i], routeSeparator)
+		}
+		routes := strings.Join(escaped, routeSeparator)
+		return fmt.Sprintf("@6@%s@%s@%s@%s@%s@%s@@",
+			ep.Protocol, naming.Escape(ep.Address, "@"), routes, ep.RID, mt, blessings)
+	default:
+		return ep.VersionedString(defaultVersion)
+	}
+}
+
+func (ep *Endpoint) String() string {
+	//nologcall
+	return ep.VersionedString(defaultVersion)
+}
+
+func (ep *Endpoint) Name() string {
+	//nologcall
+	return naming.JoinAddressName(ep.String(), "")
+}
+
+func (ep *Endpoint) Addr() net.Addr {
+	//nologcall
+	return &addr{network: ep.Protocol, address: ep.Address}
+}
+
+func (ep *Endpoint) ServesMountTable() bool {
+	//nologcall
+	return ep.IsMountTable
+}
+
+func (ep *Endpoint) ServesLeaf() bool {
+	//nologcall
+	return ep.IsLeaf
+}
+
+func (ep *Endpoint) BlessingNames() []string {
+	//nologcall
+	return ep.Blessings
+}
+
+type addr struct {
+	network, address string
+}
+
+func (a *addr) Network() string {
+	return a.network
+}
+
+func (a *addr) String() string {
+	return a.address
+}
diff --git a/runtime/internal/naming/endpoint_test.go b/runtime/internal/naming/endpoint_test.go
new file mode 100644
index 0000000..f699440
--- /dev/null
+++ b/runtime/internal/naming/endpoint_test.go
@@ -0,0 +1,302 @@
+// 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 naming
+
+import (
+	"net"
+	"reflect"
+	"testing"
+
+	"v.io/v23/naming"
+)
+
+func TestEndpointV5(t *testing.T) {
+	defver := defaultVersion
+	defer func() {
+		defaultVersion = defver
+	}()
+	v5a := &Endpoint{
+		Protocol:     naming.UnknownProtocol,
+		Address:      "batman.com:1234",
+		RID:          naming.FixedRoutingID(0xdabbad00),
+		IsMountTable: true,
+	}
+	v5b := &Endpoint{
+		Protocol:     naming.UnknownProtocol,
+		Address:      "batman.com:2345",
+		RID:          naming.FixedRoutingID(0xdabbad00),
+		IsMountTable: false,
+	}
+	v5c := &Endpoint{
+		Protocol:     "tcp",
+		Address:      "batman.com:2345",
+		RID:          naming.FixedRoutingID(0x0),
+		IsMountTable: false,
+	}
+	v5d := &Endpoint{
+		Protocol:     "ws6",
+		Address:      "batman.com:2345",
+		RID:          naming.FixedRoutingID(0x0),
+		IsMountTable: false,
+	}
+	v5e := &Endpoint{
+		Protocol:     "tcp",
+		Address:      "batman.com:2345",
+		RID:          naming.FixedRoutingID(0xba77),
+		IsMountTable: true,
+		Blessings:    []string{"dev.v.io/foo@bar.com", "dev.v.io/bar@bar.com/delegate"},
+	}
+	v5f := &Endpoint{
+		Protocol:     "tcp",
+		Address:      "batman.com:2345",
+		RID:          naming.FixedRoutingID(0xba77),
+		IsMountTable: true,
+		// Blessings that look similar to other parts of the endpoint.
+		Blessings: []string{"@@", "@s", "@m"},
+	}
+	testcasesA := []struct {
+		endpoint naming.Endpoint
+		address  string
+	}{
+		{v5a, "batman.com:1234"},
+		{v5b, "batman.com:2345"},
+		{v5c, "batman.com:2345"},
+	}
+	for _, test := range testcasesA {
+		addr := test.endpoint.Addr()
+		if addr.String() != test.address {
+			t.Errorf("unexpected address %q, not %q", addr.String(), test.address)
+		}
+	}
+	// Test v5 endpoints.
+	testcasesC := []struct {
+		Endpoint naming.Endpoint
+		String   string
+		Version  int
+	}{
+		{v5a, "@5@@batman.com:1234@000000000000000000000000dabbad00@m@@@", 5},
+		{v5b, "@5@@batman.com:2345@000000000000000000000000dabbad00@s@@@", 5},
+		{v5c, "@5@tcp@batman.com:2345@00000000000000000000000000000000@s@@@", 5},
+		{v5d, "@5@ws6@batman.com:2345@00000000000000000000000000000000@s@@@", 5},
+		{v5e, "@5@tcp@batman.com:2345@0000000000000000000000000000ba77@m@dev.v.io/foo@bar.com,dev.v.io/bar@bar.com/delegate@@", 5},
+		{v5f, "@5@tcp@batman.com:2345@0000000000000000000000000000ba77@m@@@,@s,@m@@", 5},
+	}
+	for i, test := range testcasesC {
+		if got, want := test.Endpoint.VersionedString(test.Version), test.String; got != want {
+			t.Errorf("Test %d: Got %q want %q for endpoint (v%d): %#v", i, got, want, test.Version, test.Endpoint)
+		}
+		ep, err := NewEndpoint(test.String)
+		if err != nil {
+			t.Errorf("Test %d: NewEndpoint(%q) failed with %v", i, test.String, err)
+			continue
+		}
+		if !reflect.DeepEqual(ep, test.Endpoint) {
+			t.Errorf("Test %d: Got endpoint %#v, want %#v for string %q", i, ep, test.Endpoint, test.String)
+		}
+	}
+}
+
+func TestEndpoint(t *testing.T) {
+	defver := defaultVersion
+	defer func() {
+		defaultVersion = defver
+	}()
+	defaultVersion = 6
+	v6a := &Endpoint{
+		Protocol:     naming.UnknownProtocol,
+		Address:      "batman.com:1234",
+		RID:          naming.FixedRoutingID(0xdabbad00),
+		IsMountTable: true,
+	}
+	v6b := &Endpoint{
+		Protocol:     naming.UnknownProtocol,
+		Address:      "batman.com:2345",
+		RID:          naming.FixedRoutingID(0xdabbad00),
+		IsMountTable: false,
+	}
+	v6c := &Endpoint{
+		Protocol:     "tcp",
+		Address:      "batman.com:2345",
+		RID:          naming.FixedRoutingID(0x0),
+		IsMountTable: false,
+	}
+	v6d := &Endpoint{
+		Protocol:     "ws6",
+		Address:      "batman.com:2345",
+		RID:          naming.FixedRoutingID(0x0),
+		IsMountTable: false,
+	}
+	v6e := &Endpoint{
+		Protocol:     "tcp",
+		Address:      "batman.com:2345",
+		RID:          naming.FixedRoutingID(0xba77),
+		RouteList:    []string{"1"},
+		IsMountTable: true,
+		Blessings:    []string{"dev.v.io/foo@bar.com", "dev.v.io/bar@bar.com/delegate"},
+	}
+	v6f := &Endpoint{
+		Protocol:     "tcp",
+		Address:      "batman.com:2345",
+		RouteList:    []string{"1", "2", "3"},
+		RID:          naming.FixedRoutingID(0xba77),
+		IsMountTable: true,
+		// Blessings that look similar to other parts of the endpoint.
+		Blessings: []string{"@@", "@s", "@m"},
+	}
+	v6g := &Endpoint{
+		Protocol: "tcp",
+		Address:  "batman.com:2345",
+		// Routes that have commas should be escaped correctly
+		RouteList:    []string{"a,b", ",ab", "ab,"},
+		RID:          naming.FixedRoutingID(0xba77),
+		IsMountTable: true,
+	}
+
+	testcasesA := []struct {
+		endpoint naming.Endpoint
+		address  string
+	}{
+		{v6a, "batman.com:1234"},
+		{v6b, "batman.com:2345"},
+		{v6c, "batman.com:2345"},
+	}
+	for _, test := range testcasesA {
+		addr := test.endpoint.Addr()
+		if addr.String() != test.address {
+			t.Errorf("unexpected address %q, not %q", addr.String(), test.address)
+		}
+	}
+
+	// Test v6 endpoints.
+	testcasesC := []struct {
+		Endpoint naming.Endpoint
+		String   string
+		Version  int
+	}{
+		{v6a, "@6@@batman.com:1234@@000000000000000000000000dabbad00@m@@@", 6},
+		{v6b, "@6@@batman.com:2345@@000000000000000000000000dabbad00@s@@@", 6},
+		{v6c, "@6@tcp@batman.com:2345@@00000000000000000000000000000000@s@@@", 6},
+		{v6d, "@6@ws6@batman.com:2345@@00000000000000000000000000000000@s@@@", 6},
+		{v6e, "@6@tcp@batman.com:2345@1@0000000000000000000000000000ba77@m@dev.v.io/foo@bar.com,dev.v.io/bar@bar.com/delegate@@", 6},
+		{v6f, "@6@tcp@batman.com:2345@1,2,3@0000000000000000000000000000ba77@m@@@,@s,@m@@", 6},
+		{v6g, "@6@tcp@batman.com:2345@a%2Cb,%2Cab,ab%2C@0000000000000000000000000000ba77@m@@@", 6},
+	}
+
+	for i, test := range testcasesC {
+		if got, want := test.Endpoint.VersionedString(test.Version), test.String; got != want {
+			t.Errorf("Test %d: Got %q want %q for endpoint (v%d): %#v", i, got, want, test.Version, test.Endpoint)
+		}
+		ep, err := NewEndpoint(test.String)
+		if err != nil {
+			t.Errorf("Test %d: NewEndpoint(%q) failed with %v", i, test.String, err)
+			continue
+		}
+		if !reflect.DeepEqual(ep, test.Endpoint) {
+			t.Errorf("Test %d: Got endpoint %#v, want %#v for string %q", i, ep, test.Endpoint, test.String)
+		}
+	}
+}
+
+type endpointTest struct {
+	input, output string
+	err           error
+}
+
+func runEndpointTests(t *testing.T, testcases []endpointTest) {
+	for _, test := range testcases {
+		ep, err := NewEndpoint(test.input)
+		if err == nil && test.err == nil && ep.String() != test.output {
+			t.Errorf("NewEndpoint(%q): unexpected endpoint string %q != %q",
+				test.input, ep.String(), test.output)
+			continue
+		}
+		switch {
+		case test.err == err: // do nothing
+		case test.err == nil && err != nil:
+			t.Errorf("NewEndpoint(%q): unexpected error %q", test.output, err)
+		case test.err != nil && err == nil:
+			t.Errorf("NewEndpoint(%q): missing error %q", test.output, test.err)
+		case err.Error() != test.err.Error():
+			t.Errorf("NewEndpoint(%q): unexpected error  %q != %q", test.output, err, test.err)
+		}
+	}
+}
+
+func TestHostPortEndpoint(t *testing.T) {
+	defver := defaultVersion
+	defer func() {
+		defaultVersion = defver
+	}()
+	defaultVersion = 6
+	testcases := []endpointTest{
+		{"localhost:10", "@6@@localhost:10@@00000000000000000000000000000000@m@@@", nil},
+		{"localhost:", "@6@@localhost:@@00000000000000000000000000000000@m@@@", nil},
+		{"localhost", "", errInvalidEndpointString},
+		{"(dev.v.io/service/mounttabled)@ns.dev.v.io:8101", "@6@@ns.dev.v.io:8101@@00000000000000000000000000000000@m@dev.v.io/service/mounttabled@@", nil},
+		{"(dev.v.io/users/foo@bar.com)@ns.dev.v.io:8101", "@6@@ns.dev.v.io:8101@@00000000000000000000000000000000@m@dev.v.io/users/foo@bar.com@@", nil},
+		{"(@1@tcp)@ns.dev.v.io:8101", "@6@@ns.dev.v.io:8101@@00000000000000000000000000000000@m@@1@tcp@@", nil},
+	}
+	runEndpointTests(t, testcases)
+}
+
+func TestParseHostPort(t *testing.T) {
+	dns := &Endpoint{
+		Protocol:     "tcp",
+		Address:      "batman.com:4444",
+		IsMountTable: true,
+	}
+	ipv4 := &Endpoint{
+		Protocol:     "tcp",
+		Address:      "192.168.1.1:4444",
+		IsMountTable: true,
+	}
+	ipv6 := &Endpoint{
+		Protocol:     "tcp",
+		Address:      "[01:02::]:4444",
+		IsMountTable: true,
+	}
+	testcases := []struct {
+		Endpoint   naming.Endpoint
+		Host, Port string
+	}{
+		{dns, "batman.com", "4444"},
+		{ipv4, "192.168.1.1", "4444"},
+		{ipv6, "01:02::", "4444"},
+	}
+
+	for _, test := range testcases {
+		addr := net.JoinHostPort(test.Host, test.Port)
+		epString := naming.FormatEndpoint("tcp", addr)
+		if ep, err := NewEndpoint(epString); err != nil {
+			t.Errorf("NewEndpoint(%q) failed with %v", addr, err)
+		} else {
+			if !reflect.DeepEqual(test.Endpoint, ep) {
+				t.Errorf("Got endpoint %T = %#v, want %T = %#v for string %q", ep, ep, test.Endpoint, test.Endpoint, addr)
+			}
+		}
+	}
+}
+
+func TestEscapeEndpoint(t *testing.T) {
+	defver := defaultVersion
+	defer func() {
+		defaultVersion = defver
+	}()
+	testcases := []naming.Endpoint{
+		&Endpoint{Protocol: "unix", Address: "@", RID: naming.FixedRoutingID(0xdabbad00)},
+		&Endpoint{Protocol: "unix", Address: "@/%", RID: naming.FixedRoutingID(0xdabbad00)},
+	}
+	for i, ep := range testcases {
+		epstr := ep.String()
+		got, err := NewEndpoint(epstr)
+		if err != nil {
+			t.Errorf("Test %d: NewEndpoint(%q) failed with %v", i, epstr, err)
+			continue
+		}
+		if !reflect.DeepEqual(ep, got) {
+			t.Errorf("Test %d: Got endpoint %#v, want %#v", i, got, ep)
+		}
+	}
+}
diff --git a/runtime/internal/naming/namespace/all_test.go b/runtime/internal/naming/namespace/all_test.go
new file mode 100644
index 0000000..04febd1
--- /dev/null
+++ b/runtime/internal/naming/namespace/all_test.go
@@ -0,0 +1,805 @@
+// 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 namespace_test
+
+import (
+	"fmt"
+	"runtime"
+	"runtime/debug"
+	"sync"
+	"testing"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/glob"
+	"v.io/v23/namespace"
+	"v.io/v23/naming"
+	"v.io/v23/options"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/verror"
+
+	"v.io/x/ref/lib/xrpc"
+	_ "v.io/x/ref/runtime/factories/generic"
+	inamespace "v.io/x/ref/runtime/internal/naming/namespace"
+	"v.io/x/ref/services/mounttable/mounttablelib"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/testutil"
+)
+
+//go:generate v23 test generate
+
+func resolveWithRetry(ctx *context.T, name string, opts ...naming.NamespaceOpt) *naming.MountEntry {
+	ns := v23.GetNamespace(ctx)
+	for {
+		mp, err := ns.Resolve(ctx, name, opts...)
+		if err == nil {
+			return mp
+		}
+		time.Sleep(100 * time.Millisecond)
+	}
+}
+
+func createContexts(t *testing.T) (sc, c *context.T, cleanup func()) {
+	ctx, shutdown := test.V23Init()
+	var (
+		err error
+		psc = testutil.NewPrincipal("sc")
+		pc  = testutil.NewPrincipal("c")
+	)
+	// Setup the principals so that they recognize each other.
+	if err := psc.AddToRoots(pc.BlessingStore().Default()); err != nil {
+		t.Fatal(err)
+	}
+	if err := pc.AddToRoots(psc.BlessingStore().Default()); err != nil {
+		t.Fatal(err)
+	}
+	if sc, err = v23.WithPrincipal(ctx, psc); err != nil {
+		t.Fatal(err)
+	}
+	if c, err = v23.WithPrincipal(ctx, pc); err != nil {
+		t.Fatal(err)
+	}
+	return sc, c, shutdown
+}
+
+func boom(t *testing.T, f string, v ...interface{}) {
+	t.Logf(f, v...)
+	t.Fatal(string(debug.Stack()))
+}
+
+// N squared but who cares, this is a little test.
+// Ignores dups.
+func contains(container, contained []string) (string, bool) {
+L:
+	for _, d := range contained {
+		for _, r := range container {
+			if r == d {
+				continue L
+			}
+		}
+		return d, false
+	}
+	return "", true
+}
+
+func compare(t *testing.T, caller, name string, got, want []string) {
+	// Compare ignoring dups.
+	a, foundA := contains(got, want)
+	b, foundB := contains(want, got)
+	if !foundA {
+		t.Logf("%s: %q: failed to find %q: got %v, want %v", caller, name, a, got, want)
+		boom(t, "%s: %q: failed to find %q: got %v, want %v", caller, name, a, got, want)
+	}
+	if !foundB {
+		t.Logf("%s: %q: failed to find %q: got %v, want %v", caller, name, a, got, want)
+		boom(t, "%s: %q: failed to find %q: got %v, want %v", caller, name, b, got, want)
+	}
+}
+
+func doGlob(t *testing.T, ctx *context.T, ns namespace.T, pattern string, limit int) []string {
+	var replies []string
+
+	sctx, done := context.WithTimeout(ctx, 2*time.Minute)
+	defer done()
+	rc, err := ns.Glob(sctx, pattern)
+	if err != nil {
+		boom(t, "Glob(%s): %s", pattern, err)
+	}
+	for s := range rc {
+		switch v := s.(type) {
+		case *naming.GlobReplyEntry:
+			replies = append(replies, v.Value.Name)
+			if limit > 0 && len(replies) > limit {
+				boom(t, "Glob returns too many results, perhaps not limiting recursion")
+			}
+		case *naming.GlobReplyError:
+			boom(t, "Glob failed at %q: %v", v.Value.Name, v.Value.Error)
+		}
+	}
+	return replies
+}
+
+type testServer struct {
+	suffix string
+}
+
+func (testServer) KnockKnock(*context.T, rpc.ServerCall) (string, error) {
+	return "Who's there?", nil
+}
+
+// testServer has the following namespace:
+// "" -> {level1} -> {level2}
+func (t *testServer) GlobChildren__(_ *context.T, call rpc.GlobChildrenServerCall, m *glob.Element) error {
+	switch t.suffix {
+	case "":
+		if n := "level1"; m.Match(n) {
+			call.SendStream().Send(naming.GlobChildrenReplyName{Value: n})
+		}
+	case "level1":
+		if n := "level2"; m.Match(n) {
+			call.SendStream().Send(naming.GlobChildrenReplyName{Value: n})
+		}
+	default:
+		return nil
+	}
+	return nil
+}
+
+type dispatcher struct{}
+
+func (d *dispatcher) Lookup(_ *context.T, suffix string) (interface{}, security.Authorizer, error) {
+	return &testServer{suffix}, security.AllowEveryone(), nil
+}
+
+func knockKnock(t *testing.T, ctx *context.T, name string) {
+	client := v23.GetClient(ctx)
+	var result string
+	if err := client.Call(ctx, name, "KnockKnock", nil, []interface{}{&result}); err != nil {
+		boom(t, "Call failed: %s", err)
+	}
+	if result != "Who's there?" {
+		boom(t, "Wrong result: %v", result)
+	}
+}
+
+func doResolveTest(t *testing.T, fname string, f func(*context.T, string, ...naming.NamespaceOpt) (*naming.MountEntry, error), ctx *context.T, name string, want []string, opts ...naming.NamespaceOpt) {
+	maxretries := 5
+	var lastErr error
+	for i := 0; i < maxretries; i++ {
+		me, err := f(ctx, name, opts...)
+		if err == nil {
+			if i > 0 {
+				t.Logf("doResolveTest: retried %d times", i)
+			}
+			compare(t, fname, name, me.Names(), want)
+			return
+		}
+		if err != nil && verror.Action(err).RetryAction() != 0 {
+			boom(t, "Failed to %s %s: %s, attempt %d", fname, name, err, i)
+		}
+		lastErr = err
+	}
+	boom(t, "Failed to %s %s: %s after %d attempts", fname, name, lastErr, maxretries)
+}
+
+func testResolveToMountTable(t *testing.T, ctx *context.T, ns namespace.T, name string, want ...string) {
+	doResolveTest(t, "ResolveToMountTable", ns.ResolveToMountTable, ctx, name, want)
+}
+
+func testResolveToMountTableWithPattern(t *testing.T, ctx *context.T, ns namespace.T, name string, pattern naming.NamespaceOpt, want ...string) {
+	doResolveTest(t, "ResolveToMountTable", ns.ResolveToMountTable, ctx, name, want, pattern)
+}
+
+func testResolve(t *testing.T, ctx *context.T, ns namespace.T, name string, want ...string) {
+	doResolveTest(t, "Resolve", ns.Resolve, ctx, name, want)
+}
+
+func testResolveWithPattern(t *testing.T, ctx *context.T, ns namespace.T, name string, pattern naming.NamespaceOpt, want ...string) {
+	doResolveTest(t, "Resolve", ns.Resolve, ctx, name, want, pattern)
+}
+
+type serverEntry struct {
+	mountPoint string
+	stop       func() error
+	endpoint   naming.Endpoint
+	name       string
+}
+
+func runServer(t *testing.T, ctx *context.T, disp rpc.Dispatcher, mountPoint string) *serverEntry {
+	return run(t, ctx, disp, mountPoint, false)
+}
+
+func runMT(t *testing.T, ctx *context.T, mountPoint string) *serverEntry {
+	mtd, err := mounttablelib.NewMountTableDispatcher(ctx, "", "", "mounttable")
+	if err != nil {
+		boom(t, "NewMountTableDispatcher returned error: %v", err)
+	}
+	return run(t, ctx, mtd, mountPoint, true)
+}
+
+func run(t *testing.T, ctx *context.T, disp rpc.Dispatcher, mountPoint string, mt bool) *serverEntry {
+	s, err := xrpc.NewDispatchingServer(ctx, mountPoint, disp, options.ServesMountTable(mt))
+	if err != nil {
+		boom(t, "r.NewServer: %s", err)
+	}
+	eps := s.Status().Endpoints
+	t.Logf("server %q -> %s", eps[0].Name(), mountPoint)
+	// Wait until the mount point appears in the mount table.
+	resolveWithRetry(ctx, mountPoint)
+	return &serverEntry{mountPoint: mountPoint, stop: s.Stop, endpoint: eps[0], name: eps[0].Name()}
+}
+
+const (
+	mt1MP = "mt1"
+	mt2MP = "mt2"
+	mt3MP = "mt3"
+	mt4MP = "mt4"
+	mt5MP = "mt5"
+	j1MP  = "joke1"
+	j2MP  = "joke2"
+	j3MP  = "joke3"
+
+	ttl = 5 * time.Minute
+)
+
+// runMountTables creates a root mountable with some mount tables mounted
+// in it: mt{1,2,3,4,5}
+func runMountTables(t *testing.T, ctx *context.T) (*serverEntry, map[string]*serverEntry) {
+	root := runMT(t, ctx, "")
+	v23.GetNamespace(ctx).SetRoots(root.name)
+	t.Logf("mountTable %q -> %s", root.mountPoint, root.endpoint)
+
+	mps := make(map[string]*serverEntry)
+	for _, mp := range []string{mt1MP, mt2MP, mt3MP, mt4MP, mt5MP} {
+		m := runMT(t, ctx, mp)
+		t.Logf("mountTable %q -> %s", mp, m.endpoint)
+		mps[mp] = m
+	}
+	return root, mps
+}
+
+// createNamespace creates a hierarchy of mounttables and servers
+// as follows:
+// /mt1, /mt2, /mt3, /mt4, /mt5, /joke1, /joke2, /joke3.
+// That is, mt1 is a mount table mounted in the root mount table,
+// joke1 is a server mounted in the root mount table.
+func createNamespace(t *testing.T, ctx *context.T) (*serverEntry, map[string]*serverEntry, map[string]*serverEntry, func()) {
+	root, mts := runMountTables(t, ctx)
+	jokes := make(map[string]*serverEntry)
+	// Let's run some non-mount table services.
+	for _, j := range []string{j1MP, j2MP, j3MP} {
+		disp := &dispatcher{}
+		jokes[j] = runServer(t, ctx, disp, j)
+	}
+	return root, mts, jokes, func() {
+		for _, s := range jokes {
+			s.stop()
+		}
+		for _, s := range mts {
+			s.stop()
+		}
+		root.stop()
+	}
+}
+
+// runNestedMountTables creates some nested mount tables in the hierarchy
+// created by createNamespace as follows:
+// /mt4/foo, /mt4/foo/bar and /mt4/baz where foo, bar and baz are mount tables.
+func runNestedMountTables(t *testing.T, ctx *context.T, mts map[string]*serverEntry) {
+	ns := v23.GetNamespace(ctx)
+	// Set up some nested mounts and verify resolution.
+	for _, m := range []string{"mt4/foo", "mt4/foo/bar"} {
+		mts[m] = runMT(t, ctx, m)
+	}
+
+	// Use a global name for a mount, rather than a relative one.
+	// We directly mount baz into the mt4/foo mount table.
+	globalMP := naming.JoinAddressName(mts["mt4/foo"].name, "baz")
+	mts["baz"] = runMT(t, ctx, "baz")
+	sctx, done := context.WithTimeout(ctx, 2*time.Minute)
+	defer done()
+	if err := ns.Mount(sctx, globalMP, mts["baz"].name, ttl); err != nil {
+		boom(t, "Failed to Mount %s: %s", globalMP, err)
+	}
+}
+
+// TestNamespaceCommon tests common use of the Namespace library
+// against a root mount table and some mount tables mounted on it.
+func TestNamespaceCommon(t *testing.T) {
+	_, c, cleanup := createContexts(t)
+	defer cleanup()
+
+	root, mts, jokes, stopper := createNamespace(t, c)
+	defer stopper()
+	ns := v23.GetNamespace(c)
+
+	// All of the initial mounts are served by the root mounttable
+	// and hence ResolveToMountTable should return the root mountable
+	// as the address portion of the terminal name for those mounttables.
+	testResolveToMountTable(t, c, ns, "", root.name)
+	for _, m := range []string{mt2MP, mt3MP, mt5MP} {
+		rootMT := naming.Join(root.name, m)
+		// All of these mount tables are hosted by the root mount table
+		testResolveToMountTable(t, c, ns, m, rootMT)
+
+		// The server registered for each mount point is a mount table
+		testResolve(t, c, ns, m, mts[m].name)
+
+		// ResolveToMountTable will walk through to the sub MountTables
+		mtbar := naming.Join(m, "bar")
+		subMT := naming.Join(mts[m].name, "bar")
+		testResolveToMountTable(t, c, ns, mtbar, subMT)
+	}
+
+	for _, j := range []string{j1MP, j2MP, j3MP} {
+		testResolve(t, c, ns, j, jokes[j].name)
+	}
+}
+
+// TestNamespaceDetails tests more detailed use of the Namespace library.
+func TestNamespaceDetails(t *testing.T) {
+	sc, c, cleanup := createContexts(t)
+	defer cleanup()
+
+	root, mts, _, stopper := createNamespace(t, sc)
+	defer stopper()
+
+	ns := v23.GetNamespace(c)
+	ns.SetRoots(root.name)
+
+	// /mt2 is not an endpoint. Thus, the example below will fail.
+	mt3Server := mts[mt3MP].name
+	mt2a := "/mt2/a"
+	if err := ns.Mount(c, mt2a, mt3Server, ttl); verror.ErrorID(err) == naming.ErrNoSuchName.ID {
+		boom(t, "Successfully mounted %s - expected an err %v, not %v", mt2a, naming.ErrNoSuchName, err)
+	}
+
+	// Mount using the relative name.
+	// This means walk through mt2 if it already exists and mount within
+	// the lower level mount table, if the name doesn't exist we'll create
+	// a new name for it.
+	mt2a = "mt2/a"
+	if err := ns.Mount(c, mt2a, mt3Server, ttl); err != nil {
+		boom(t, "Failed to Mount %s: %s", mt2a, err)
+	}
+
+	mt2mt := naming.Join(mts[mt2MP].name, "a")
+	// The mt2/a is served by the mt2 mount table
+	testResolveToMountTable(t, c, ns, mt2a, mt2mt)
+	// The server for mt2a is mt3server from the second mount above.
+	testResolve(t, c, ns, mt2a, mt3Server)
+
+	// Add two more mounts. The // should be stripped off of the
+	// second.
+	for _, mp := range []struct{ name, server string }{
+		{"mt2", mts[mt4MP].name},
+		{"mt2//", mts[mt5MP].name},
+	} {
+		if err := ns.Mount(c, mp.name, mp.server, ttl, naming.ServesMountTable(true)); err != nil {
+			boom(t, "Failed to Mount %s: %s", mp.name, err)
+		}
+	}
+
+	names := []string{naming.JoinAddressName(mts[mt4MP].name, "a"),
+		naming.JoinAddressName(mts[mt5MP].name, "a")}
+	names = append(names, naming.JoinAddressName(mts[mt2MP].name, "a"))
+	// We now have 3 mount tables prepared to serve mt2/a
+	testResolveToMountTable(t, c, ns, "mt2/a", names...)
+	names = []string{mts[mt4MP].name, mts[mt5MP].name}
+	names = append(names, mts[mt2MP].name)
+	testResolve(t, c, ns, "mt2", names...)
+}
+
+// TestNestedMounts tests some more deeply nested mounts
+func TestNestedMounts(t *testing.T) {
+	sc, c, cleanup := createContexts(t)
+	defer cleanup()
+
+	root, mts, _, stopper := createNamespace(t, sc)
+	runNestedMountTables(t, sc, mts)
+	defer stopper()
+
+	ns := v23.GetNamespace(c)
+	ns.SetRoots(root.name)
+
+	// Set up some nested mounts and verify resolution.
+	for _, m := range []string{"mt4/foo", "mt4/foo/bar"} {
+		testResolve(t, c, ns, m, mts[m].name)
+	}
+
+	testResolveToMountTable(t, c, ns, "mt4/foo",
+		naming.JoinAddressName(mts[mt4MP].name, "foo"))
+	testResolveToMountTable(t, c, ns, "mt4/foo/bar",
+		naming.JoinAddressName(mts["mt4/foo"].name, "bar"))
+	testResolveToMountTable(t, c, ns, "mt4/foo/baz",
+		naming.JoinAddressName(mts["mt4/foo"].name, "baz"))
+}
+
+// TestServers tests invoking RPCs on simple servers
+func TestServers(t *testing.T) {
+	sc, c, cleanup := createContexts(t)
+	defer cleanup()
+
+	root, mts, jokes, stopper := createNamespace(t, sc)
+	defer stopper()
+	ns := v23.GetNamespace(c)
+	ns.SetRoots(root.name)
+
+	// Let's run some non-mount table services
+	for _, j := range []string{j1MP, j2MP, j3MP} {
+		testResolve(t, c, ns, j, jokes[j].name)
+		knockKnock(t, c, j)
+		globalName := naming.JoinAddressName(mts["mt4"].name, j)
+		disp := &dispatcher{}
+		gj := "g_" + j
+		jokes[gj] = runServer(t, c, disp, globalName)
+		testResolve(t, c, ns, "mt4/"+j, jokes[gj].name)
+		knockKnock(t, c, "mt4/"+j)
+		testResolveToMountTable(t, c, ns, "mt4/"+j, globalName)
+		testResolveToMountTable(t, c, ns, "mt4/"+j+"/garbage", globalName+"/garbage")
+	}
+}
+
+// TestGlob tests some glob patterns.
+func TestGlob(t *testing.T) {
+	sc, c, cleanup := createContexts(t)
+	defer cleanup()
+
+	root, mts, _, stopper := createNamespace(t, sc)
+	runNestedMountTables(t, sc, mts)
+	defer stopper()
+	ns := v23.GetNamespace(c)
+	ns.SetRoots(root.name)
+
+	tln := []string{"baz", "mt1", "mt2", "mt3", "mt4", "mt5", "joke1", "joke2", "joke3"}
+	barbaz := []string{"mt4/foo/bar", "mt4/foo/baz"}
+	level12 := []string{"joke1/level1", "joke1/level1/level2", "joke2/level1", "joke2/level1/level2", "joke3/level1", "joke3/level1/level2"}
+	foo := append([]string{"mt4/foo"}, barbaz...)
+	foo = append(foo, level12...)
+	// Try various globs.
+	globTests := []struct {
+		pattern  string
+		expected []string
+	}{
+		{"*", tln},
+		{"x", []string{}},
+		{"m*", []string{"mt1", "mt2", "mt3", "mt4", "mt5"}},
+		{"mt[2,3]", []string{"mt2", "mt3"}},
+		{"*z", []string{"baz"}},
+		{"joke1/*", []string{"joke1/level1"}},
+		{"j?ke1/level1/*", []string{"joke1/level1/level2"}},
+		{"joke1/level1/*", []string{"joke1/level1/level2"}},
+		{"joke1/level1/level2/...", []string{"joke1/level1/level2"}},
+		{"...", append(append(tln, foo...), "")},
+		{"*/...", append(tln, foo...)},
+		{"*/foo/*", barbaz},
+		{"*/*/*z", []string{"mt4/foo/baz"}},
+		{"*/f??/*z", []string{"mt4/foo/baz"}},
+		{"mt4/foo/baz", []string{"mt4/foo/baz"}},
+	}
+	for _, test := range globTests {
+		out := doGlob(t, c, ns, test.pattern, 0)
+		compare(t, "Glob", test.pattern, out, test.expected)
+		// Do the same with a full rooted name.
+		out = doGlob(t, c, ns, naming.JoinAddressName(root.name, test.pattern), 0)
+		var expectedWithRoot []string
+		for _, s := range test.expected {
+			expectedWithRoot = append(expectedWithRoot, naming.JoinAddressName(root.name, s))
+		}
+		compare(t, "Glob", test.pattern, out, expectedWithRoot)
+	}
+}
+
+type GlobbableServer struct {
+	callCount int
+	mu        sync.Mutex
+}
+
+func (g *GlobbableServer) Glob__(*context.T, rpc.GlobServerCall, *glob.Glob) error {
+	g.mu.Lock()
+	defer g.mu.Unlock()
+	g.callCount++
+	return nil
+}
+
+func (g *GlobbableServer) GetAndResetCount() int {
+	g.mu.Lock()
+	defer g.mu.Unlock()
+	cnt := g.callCount
+	g.callCount = 0
+
+	return cnt
+}
+
+// TestGlobEarlyStop tests that Glob doesn't query terminal servers with finished patterns.
+func TestGlobEarlyStop(t *testing.T) {
+	sc, c, cleanup := createContexts(t)
+	defer cleanup()
+
+	root, mts, _, stopper := createNamespace(t, sc)
+	runNestedMountTables(t, sc, mts)
+	defer stopper()
+
+	globServer := &GlobbableServer{}
+	name := naming.JoinAddressName(mts["mt4/foo/bar"].name, "glob")
+	runningGlobServer := runServer(t, c, testutil.LeafDispatcher(globServer, nil), name)
+	defer runningGlobServer.stop()
+
+	ns := v23.GetNamespace(c)
+	ns.SetRoots(root.name)
+
+	tests := []struct {
+		pattern       string
+		expectedCalls int
+		expected      []string
+	}{
+		{"mt4/foo/bar/glob", 0, []string{"mt4/foo/bar/glob"}},
+		{"mt4/foo/bar/glob/...", 1, []string{"mt4/foo/bar/glob"}},
+		{"mt4/foo/bar/glob/*", 1, nil},
+		{"mt4/foo/bar/***", 0, []string{"mt4/foo/bar", "mt4/foo/bar/glob"}},
+		{"mt4/foo/bar/...", 1, []string{"mt4/foo/bar", "mt4/foo/bar/glob"}},
+		{"mt4/foo/bar/*", 0, []string{"mt4/foo/bar/glob"}},
+		{"mt4/***/bar/***", 0, []string{"mt4/foo/bar", "mt4/foo/bar/glob"}},
+		{"mt4/*/bar/***", 0, []string{"mt4/foo/bar", "mt4/foo/bar/glob"}},
+	}
+	// Test allowing the tests to descend into leaves.
+	for _, test := range tests {
+		out := doGlob(t, c, ns, test.pattern, 0)
+		compare(t, "Glob", test.pattern, out, test.expected)
+		if calls := globServer.GetAndResetCount(); calls != test.expectedCalls {
+			boom(t, "Wrong number of Glob calls to terminal server got: %d want: %d.", calls, test.expectedCalls)
+		}
+	}
+}
+
+func TestCycles(t *testing.T) {
+	sc, c, cleanup := createContexts(t)
+	defer cleanup()
+
+	root, _, _, stopper := createNamespace(t, sc)
+	defer stopper()
+	ns := v23.GetNamespace(c)
+	ns.SetRoots(root.name)
+
+	c1 := runMT(t, c, "c1")
+	c2 := runMT(t, c, "c2")
+	c3 := runMT(t, c, "c3")
+	defer c1.stop()
+	defer c2.stop()
+	defer c3.stop()
+
+	m := "c1/c2"
+	if err := ns.Mount(c, m, c1.name, ttl, naming.ServesMountTable(true)); err != nil {
+		boom(t, "Failed to Mount %s: %s", "c1/c2", err)
+	}
+
+	m = "c1/c2/c3"
+	if err := ns.Mount(c, m, c3.name, ttl, naming.ServesMountTable(true)); err != nil {
+		boom(t, "Failed to Mount %s: %s", m, err)
+	}
+
+	m = "c1/c3/c4"
+	if err := ns.Mount(c, m, c1.name, ttl, naming.ServesMountTable(true)); err != nil {
+		boom(t, "Failed to Mount %s: %s", m, err)
+	}
+
+	// Since c1 was mounted with the Serve call, it will have both the tcp and ws endpoints.
+	testResolve(t, c, ns, "c1", c1.name)
+	testResolve(t, c, ns, "c1/c2", c1.name)
+	testResolve(t, c, ns, "c1/c3", c3.name)
+	testResolve(t, c, ns, "c1/c3/c4", c1.name)
+	testResolve(t, c, ns, "c1/c3/c4/c3/c4", c1.name)
+	cycle := "c3/c4"
+	for i := 0; i < 40; i++ {
+		cycle += "/c3/c4"
+	}
+	if _, err := ns.Resolve(c, "c1/"+cycle); verror.ErrorID(err) != naming.ErrResolutionDepthExceeded.ID {
+		boom(t, "Failed to detect cycle")
+	}
+
+	// Perform the glob with a response length limit and dup suppression.  The dup supression
+	// should win.
+	r := doGlob(t, c, ns, "c1/...", 1000)
+	if len(r) != 6 {
+		t.Fatalf("expected 6 replies, got %v", r)
+	}
+}
+
+// TestGoroutineLeaks tests for leaking goroutines - we have many:-(
+func TestGoroutineLeaks(t *testing.T) {
+	t.Skip()
+	sc, _, cleanup := createContexts(t)
+	defer cleanup()
+
+	_, _, _, stopper := createNamespace(t, sc)
+	defer func() {
+		sc.Infof("%d goroutines:", runtime.NumGoroutine())
+	}()
+	defer stopper()
+	defer func() {
+		sc.Infof("%d goroutines:", runtime.NumGoroutine())
+	}()
+	//panic("this will show up lots of goroutine+channel leaks!!!!")
+}
+
+func TestBadRoots(t *testing.T) {
+	if _, err := inamespace.New(); err != nil {
+		t.Errorf("namespace.New should not have failed with no roots")
+	}
+	if _, err := inamespace.New("not a rooted name"); err == nil {
+		t.Errorf("namespace.New should have failed with an unrooted name")
+	}
+}
+
+func bless(blesser, delegate security.Principal, extension string) {
+	b, err := blesser.Bless(delegate.PublicKey(), blesser.BlessingStore().Default(), extension, security.UnconstrainedUse())
+	if err != nil {
+		panic(err)
+	}
+	delegate.BlessingStore().SetDefault(b)
+}
+
+func TestAuthorizationDuringResolve(t *testing.T) {
+	ctx, shutdown := v23.Init()
+	defer shutdown()
+
+	var (
+		rootMtCtx, _   = v23.WithPrincipal(ctx, testutil.NewPrincipal()) // root mounttable
+		mtCtx, _       = v23.WithPrincipal(ctx, testutil.NewPrincipal()) // intermediate mounttable
+		serverCtx, _   = v23.WithPrincipal(ctx, testutil.NewPrincipal()) // end server
+		clientCtx, _   = v23.WithPrincipal(ctx, testutil.NewPrincipal()) // client process (doing Resolves).
+		clientNs       = v23.GetNamespace(clientCtx)
+		serverNs       = v23.GetNamespace(serverCtx)
+		idp            = testutil.NewIDProvider("idp") // identity provider
+		serverEndpoint = naming.FormatEndpoint("tcp", "127.0.0.1:14141")
+	)
+
+	// Setup default blessings for the processes.
+	idp.Bless(v23.GetPrincipal(rootMtCtx), "rootmt")
+	idp.Bless(v23.GetPrincipal(serverCtx), "server")
+	idp.Bless(v23.GetPrincipal(mtCtx), "childmt")
+	idp.Bless(v23.GetPrincipal(clientCtx), "client")
+
+	// Setup the namespace root for all the "processes".
+	rootmt := runMT(t, rootMtCtx, "")
+	for _, ctx := range []*context.T{mtCtx, serverCtx, clientCtx} {
+		v23.GetNamespace(ctx).SetRoots(rootmt.name)
+	}
+	// Disable caching in the client so that any Mount calls by the server
+	// are noticed immediately.
+	clientNs.CacheCtl(naming.DisableCache(true))
+
+	// Intermediate mounttables should be authenticated.
+	mt := runMT(t, mtCtx, "mt")
+	defer func() {
+		mt.stop()
+	}()
+
+	// Mount a server on "mt".
+	if err := serverNs.Mount(serverCtx, "mt/server", serverEndpoint, time.Minute, naming.ReplaceMount(true)); err != nil {
+		t.Error(err)
+	}
+
+	// The namespace root should be authenticated too
+	resolveWithRetry(clientCtx, "mt/server")
+	// Host:Port and Endpoint versions of the other namespace root
+	// (which has different blessings)
+	hproot := fmt.Sprintf("(otherroot)@%v", rootmt.endpoint.Addr())
+	eproot := naming.FormatEndpoint(rootmt.endpoint.Addr().Network(), rootmt.endpoint.Addr().String(), rootmt.endpoint.RoutingID(), naming.BlessingOpt("otherroot"), naming.ServesMountTable(rootmt.endpoint.ServesMountTable()))
+	for _, root := range []string{hproot, eproot} {
+		name := naming.JoinAddressName(root, "mt")
+		// Rooted name resolutions should fail authorization because of the "otherrot"
+		if e, err := clientNs.Resolve(clientCtx, name); verror.ErrorID(err) != verror.ErrNotTrusted.ID {
+			t.Errorf("resolve(%q) returned (%v, errorid=%v %v), wanted errorid=%v", name, e, verror.ErrorID(err), err, verror.ErrNotTrusted.ID)
+		}
+		// But not fail if the skip-authorization option is provided
+		if e, err := clientNs.Resolve(clientCtx, name, options.SkipServerEndpointAuthorization{}); err != nil {
+			t.Errorf("resolve(%q): Got (%v, %v), expected resolution to succeed", name, e, err)
+		}
+
+		// The namespace root from the context should be authorized as well.
+		ctx, ns, _ := v23.WithNewNamespace(clientCtx, naming.JoinAddressName(root, ""))
+		if e, err := ns.Resolve(ctx, "mt/server"); verror.ErrorID(err) != verror.ErrNotTrusted.ID {
+			t.Errorf("resolve with root=%q returned (%v, errorid=%v %v), wanted errorid=%v: %s", root, e, verror.ErrorID(err), err, verror.ErrNotTrusted.ID, verror.DebugString(err))
+		}
+		if _, err := ns.Resolve(ctx, "mt/server", options.SkipServerEndpointAuthorization{}); err != nil {
+			t.Errorf("resolve with root=%q should have succeeded when authorization checks are skipped. Got %v: %s", root, err, verror.DebugString(err))
+		}
+	}
+
+	// Imagine that the network address of "mt" has been taken over by an
+	// attacker. However, this attacker cannot mess with the mount entry
+	// for "mt". This would result in "mt" and its mount entry (in the
+	// global mounttable) having inconsistent blessings. Simulate this by
+	// explicitly changing the mount entry for "mt".
+	goodChildMTEndpoint := naming.FormatEndpoint(mt.endpoint.Addr().Network(), mt.endpoint.Addr().String(), naming.BlessingOpt("idp/goodchildmt"), mt.endpoint.RoutingID())
+	if err := v23.GetNamespace(mtCtx).Mount(mtCtx, "mt", goodChildMTEndpoint, time.Minute, naming.ServesMountTable(true), naming.ReplaceMount(true)); err != nil {
+		t.Error(err)
+	}
+
+	if e, err := clientNs.Resolve(serverCtx, "mt/server", options.SkipServerEndpointAuthorization{}); err != nil {
+		t.Errorf("Resolve should succeed when skipping server authorization. Got (%v, %v) %s", e, err, verror.DebugString(err))
+	} else if e, err := clientNs.Resolve(serverCtx, "mt/server"); verror.ErrorID(err) != verror.ErrNotTrusted.ID {
+		t.Errorf("Resolve should have failed with %q because an attacker has taken over the intermediate mounttable. Got (%+v, errorid=%q:%v)", verror.ErrNotTrusted.ID, e, verror.ErrorID(err), err)
+	}
+}
+
+// TestDelete tests deleting some parts of the name space.
+func TestDelete(t *testing.T) {
+	_, c, cleanup := createContexts(t)
+	defer cleanup()
+	ns := v23.GetNamespace(c)
+
+	// Create a root mount table with mount tables mounted at mt1, mt1, ...
+	root, _, _, stopper := createNamespace(t, c)
+	defer stopper()
+	ns.SetRoots(root.name)
+
+	// We should be able to remove servers below the root.
+	if err := ns.Delete(c, "mt1", false); err != nil {
+		t.Errorf("Delete failed: %s", err)
+	}
+
+	// Create a server below one level down.
+	if err := ns.Mount(c, "mt2/b/c", "/madeup:1111/server", time.Minute); err != nil {
+		t.Errorf("Mount mt2/b/c failed: %s", err)
+	}
+
+	// We should not be able to delete mt2/b...
+	if err := ns.Delete(c, "mt2/b", false); err == nil {
+		t.Errorf("Delete mt2/b should have failed")
+	}
+
+	// ...unless we include its children.
+	if err := ns.Delete(c, "mt2/b", true); err != nil {
+		t.Errorf("Delete failed: %s", err)
+	}
+}
+
+type leafObject struct{}
+
+func (leafObject) Foo(*context.T, rpc.ServerCall) error {
+	return nil
+}
+
+func TestLeaf(t *testing.T) {
+	_, ctx, cleanup := createContexts(t)
+	defer cleanup()
+	root := runMT(t, ctx, "")
+	defer func() { root.stop() }()
+
+	ns := v23.GetNamespace(ctx)
+	ns.SetRoots(root.name)
+
+	server, err := xrpc.NewServer(ctx, "leaf", &leafObject{}, nil)
+	if err != nil {
+		boom(t, "xrpc.NewServer: %s", err)
+	}
+	defer server.Stop()
+
+	mountEntry := resolveWithRetry(ctx, "leaf")
+	if expected := true; mountEntry.IsLeaf != expected {
+		boom(t, "unexpected mountEntry.IsLeaf value. Got %v, expected %v", mountEntry.IsLeaf, expected)
+	}
+
+	c, err := ns.Glob(ctx, "leaf")
+	if err != nil {
+		boom(t, "ns.Glob failed: %v", err)
+	}
+	count := 0
+	for result := range c {
+		if me, ok := result.(*naming.GlobReplyEntry); ok {
+			count++
+			if expected := true; me.Value.IsLeaf != expected {
+				boom(t, "unexpected me.IsLeaf value. Got %v, expected %v", me.Value.IsLeaf, expected)
+			}
+		}
+	}
+	if count == 0 {
+		boom(t, "Glob did not return any results. Expected 1")
+	}
+}
diff --git a/runtime/internal/naming/namespace/cache.go b/runtime/internal/naming/namespace/cache.go
new file mode 100644
index 0000000..85c8f49
--- /dev/null
+++ b/runtime/internal/naming/namespace/cache.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 namespace
+
+import (
+	"math/rand"
+	"strings"
+	"sync"
+	"time"
+
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/verror"
+)
+
+// maxCacheEntries is the max number of cache entries to keep.  It exists only so that we
+// can avoid edge cases blowing us up the cache.
+const maxCacheEntries = 4000
+
+// cacheHisteresisSize is how much we back off to if the cache gets filled up.
+const cacheHisteresisSize = (3 * maxCacheEntries) / 4
+
+// cache is a generic interface to the resolution cache.
+type cache interface {
+	remember(ctx *context.T, prefix string, entry *naming.MountEntry)
+	forget(ctx *context.T, names []string)
+	lookup(ctx *context.T, name string) (naming.MountEntry, error)
+	isNotMT(s string) bool
+	setNotMT(s string)
+}
+
+// ttlCache is an instance of cache that obeys ttl from the mount points.
+type ttlCache struct {
+	sync.Mutex
+	entries map[string]naming.MountEntry
+	notMT   map[string]time.Time
+}
+
+// newTTLCache creates an empty ttlCache.
+func newTTLCache() cache {
+	return &ttlCache{entries: make(map[string]naming.MountEntry), notMT: make(map[string]time.Time)}
+}
+
+func isStale(now time.Time, e naming.MountEntry) bool {
+	for _, s := range e.Servers {
+		if s.Deadline.Before(now) {
+			return true
+		}
+	}
+	return false
+}
+
+// randomDrop randomly removes one cache entry.  Assumes we've already locked the cache.
+func (c *ttlCache) randomDrop() {
+	n := rand.Intn(len(c.entries))
+	for k := range c.entries {
+		if n == 0 {
+			delete(c.entries, k)
+			break
+		}
+		n--
+	}
+}
+
+// cleaner reduces the number of entries.  Assumes we've already locked the cache.
+func (c *ttlCache) cleaner() {
+	// First dump any stale entries.
+	now := time.Now()
+	for k, v := range c.entries {
+		if len(c.entries) < cacheHisteresisSize {
+			return
+		}
+		if isStale(now, v) {
+			delete(c.entries, k)
+		}
+	}
+
+	// If we haven't gotten low enough, dump randomly.
+	for len(c.entries) >= cacheHisteresisSize {
+		c.randomDrop()
+	}
+}
+
+// remember the servers associated with name with suffix removed.
+func (c *ttlCache) remember(ctx *context.T, prefix string, entry *naming.MountEntry) {
+	// Remove suffix.  We only care about the name that gets us
+	// to the mounttable from the last mounttable.
+	prefix = naming.Clean(prefix)
+	entry.Name = naming.Clean(entry.Name)
+	prefix = naming.TrimSuffix(prefix, entry.Name)
+	// Copy the entry.
+	var ce naming.MountEntry
+	for _, s := range entry.Servers {
+		ce.Servers = append(ce.Servers, s)
+	}
+	ce.ServesMountTable = entry.ServesMountTable
+	c.Lock()
+	// Enforce an upper limit on the cache size.
+	if len(c.entries) >= maxCacheEntries {
+		if _, ok := c.entries[prefix]; !ok {
+			c.cleaner()
+		}
+	}
+	c.entries[prefix] = ce
+	c.Unlock()
+}
+
+// forget cache entries whose index begins with an element of names.  If names is nil
+// forget all cached entries.
+func (c *ttlCache) forget(ctx *context.T, names []string) {
+	c.Lock()
+	defer c.Unlock()
+	for key := range c.entries {
+		for _, n := range names {
+			n = naming.Clean(n)
+			if strings.HasPrefix(key, n) {
+				delete(c.entries, key)
+				break
+			}
+		}
+	}
+}
+
+// lookup searches the cache for a maximal prefix of name and returns the associated servers,
+// prefix, and suffix.  If any of the associated servers is expired, don't return anything
+// since that would reduce availability.
+func (c *ttlCache) lookup(ctx *context.T, name string) (naming.MountEntry, error) {
+	name = naming.Clean(name)
+	c.Lock()
+	defer c.Unlock()
+	now := time.Now()
+	for prefix, suffix := name, ""; len(prefix) > 0; prefix, suffix = backup(prefix, suffix) {
+		e, ok := c.entries[prefix]
+		if !ok {
+			continue
+		}
+		if isStale(now, e) {
+			return e, verror.New(naming.ErrNoSuchName, nil, name)
+		}
+		ctx.VI(2).Infof("namespace cache %s -> %v %s", name, e.Servers, e.Name)
+		e.Name = suffix
+		return e, nil
+	}
+	return naming.MountEntry{}, verror.New(naming.ErrNoSuchName, nil, name)
+}
+
+// setNotMT caches the fact that a server as not a mounttable.
+func (c *ttlCache) setNotMT(s string) {
+	c.Lock()
+	defer c.Unlock()
+	// Don't set if this is an endpoint since the endpoint contains the
+	// mounttable attribute and we should not override it.
+	//
+	// While looking for "@@" is not definitive for an endpoint containing
+	// the mounttable attribute, it is only incorrect for older version
+	// endpoints which should be rare.  This is just an optimization and
+	// not performing it is not an error.
+	if strings.Contains(s, "@@") {
+		return
+	}
+	// Set it for a minute.  This should be long enough to cut down on the
+	// extra resolutions without preserving mistakes.
+	c.notMT[s] = time.Now().Add(time.Minute)
+}
+
+// isNotMT looks in the cache to see if the server has been found to not be
+// a mounttable.
+func (c *ttlCache) isNotMT(s string) bool {
+	c.Lock()
+	defer c.Unlock()
+	if strings.Contains(s, "@@") {
+		return false
+	}
+	if expires, ok := c.notMT[s]; ok {
+		if !time.Now().After(expires) {
+			return true
+		}
+		delete(c.notMT, s)
+	}
+	return false
+}
+
+// backup moves the last element of the prefix to the suffix.
+func backup(prefix, suffix string) (string, string) {
+	for i := len(prefix) - 1; i > 0; i-- {
+		if prefix[i] != '/' {
+			continue
+		}
+		suffix = naming.Join(prefix[i+1:], suffix)
+		prefix = prefix[:i]
+		return prefix, suffix
+	}
+	return "", naming.Join(prefix, suffix)
+}
+
+// nullCache is an instance of cache that does nothing.
+type nullCache int
+
+func newNullCache() cache                                                          { return nullCache(1) }
+func (nullCache) remember(ctx *context.T, prefix string, entry *naming.MountEntry) {}
+func (nullCache) forget(ctx *context.T, names []string)                            {}
+func (nullCache) lookup(ctx *context.T, name string) (e naming.MountEntry, err error) {
+	return e, verror.New(naming.ErrNoSuchName, nil, name)
+}
+func (nullCache) isNotMT(s string) bool { return false }
+func (nullCache) setNotMT(s string)     {}
diff --git a/runtime/internal/naming/namespace/cache_test.go b/runtime/internal/naming/namespace/cache_test.go
new file mode 100644
index 0000000..457ea85
--- /dev/null
+++ b/runtime/internal/naming/namespace/cache_test.go
@@ -0,0 +1,204 @@
+// 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 namespace
+
+import (
+	"fmt"
+	"testing"
+	"time"
+
+	"v.io/v23/naming"
+	vdltime "v.io/v23/vdlroot/time"
+	"v.io/x/ref/test"
+)
+
+func compatible(server string, servers []naming.MountedServer) bool {
+	if len(servers) == 0 {
+		return server == ""
+	}
+	return servers[0].Server == server
+}
+
+func future(secs uint32) vdltime.Deadline {
+	return vdltime.Deadline{Time: time.Now().Add(time.Duration(secs) * time.Second)}
+}
+
+// TestCache tests the cache directly rather than via the namespace methods.
+func TestCache(t *testing.T) {
+	ctx, cancel := test.TestContext()
+	defer cancel()
+	preload := []struct {
+		name   string
+		suffix string
+		server string
+	}{
+		{"/h1//a/b/c/d", "c/d", "/h2"},
+		{"/h2//c/d", "d", "/h3"},
+		{"/h3//d", "", "/h4:1234"},
+	}
+	c := newTTLCache()
+	for _, p := range preload {
+		e := &naming.MountEntry{Name: p.suffix, Servers: []naming.MountedServer{{Server: p.server, Deadline: future(30)}}}
+		c.remember(ctx, p.name, e)
+	}
+
+	tests := []struct {
+		name    string
+		suffix  string
+		server  string
+		succeed bool
+	}{
+		{"/h1//a/b/c/d", "c/d", "/h2", true},
+		{"/h2//c/d", "d", "/h3", true},
+		{"/h3//d", "", "/h4:1234", true},
+		{"/notintcache", "", "", false},
+		{"/h1//a/b/f//g", "f/g", "/h2", true},
+		{"/h3//d//e", "e", "/h4:1234", true},
+	}
+	for _, p := range tests {
+		e, err := c.lookup(ctx, p.name)
+		if (err == nil) != p.succeed {
+			t.Errorf("%s: lookup failed", p.name)
+		}
+		if e.Name != p.suffix || !compatible(p.server, e.Servers) {
+			t.Errorf("%s: got %v, %s not %s, %s", p.name, e.Name, e.Servers, p.suffix, p.server)
+		}
+	}
+}
+
+func TestCacheLimit(t *testing.T) {
+	ctx, cancel := test.TestContext()
+	defer cancel()
+	c := newTTLCache().(*ttlCache)
+	e := &naming.MountEntry{Servers: []naming.MountedServer{naming.MountedServer{Server: "the rain in spain", Deadline: future(3000)}}}
+	for i := 0; i < maxCacheEntries; i++ {
+		c.remember(ctx, fmt.Sprintf("%d", i), e)
+		if len(c.entries) > maxCacheEntries {
+			t.Errorf("unexpected cache size: got %d not %d", len(c.entries), maxCacheEntries)
+		}
+	}
+	// Adding one more element should reduce us to 3/4 full.
+	c.remember(ctx, fmt.Sprintf("%d", maxCacheEntries), e)
+	if len(c.entries) != cacheHisteresisSize {
+		t.Errorf("cache shrunk wrong amount: got %d not %d", len(c.entries), cacheHisteresisSize)
+	}
+}
+
+func TestCacheTTL(t *testing.T) {
+	ctx, cancel := test.TestContext()
+	defer cancel()
+	before := vdltime.Deadline{Time: time.Now()}
+	c := newTTLCache().(*ttlCache)
+	// Fill cache.
+	e := &naming.MountEntry{Servers: []naming.MountedServer{naming.MountedServer{Server: "the rain in spain", Deadline: future(3000)}}}
+	for i := 0; i < maxCacheEntries; i++ {
+		c.remember(ctx, fmt.Sprintf("%d", i), e)
+	}
+	// Time out half the entries.
+	i := len(c.entries) / 2
+	for k := range c.entries {
+		c.entries[k].Servers[0].Deadline = before
+		if i == 0 {
+			break
+		}
+		i--
+	}
+	// Add an entry and make sure we now have room.
+	c.remember(ctx, fmt.Sprintf("%d", maxCacheEntries+2), e)
+	if len(c.entries) > cacheHisteresisSize {
+		t.Errorf("entries did not timeout: got %d not %d", len(c.entries), cacheHisteresisSize)
+	}
+}
+
+func TestFlushCacheEntry(t *testing.T) {
+	ctx, cancel := test.TestContext()
+	defer cancel()
+	preload := []struct {
+		name   string
+		server string
+	}{
+		{"/h1//a/b", "/h2//c"},
+		{"/h2//c", "/h3"},
+		{"/h3//d", "/h4:1234"},
+	}
+	ns, _ := New()
+	c := ns.resolutionCache.(*ttlCache)
+	for _, p := range preload {
+		e := &naming.MountEntry{Servers: []naming.MountedServer{naming.MountedServer{Server: "p.server", Deadline: future(3000)}}}
+		c.remember(ctx, p.name, e)
+	}
+	toflush := "/h1/xyzzy"
+	if ns.FlushCacheEntry(ctx, toflush) {
+		t.Errorf("%s should not have caused anything to flush", toflush)
+	}
+	toflush = "/h1/a/b/d/e"
+	if !ns.FlushCacheEntry(ctx, toflush) {
+		t.Errorf("%s should have caused something to flush", toflush)
+	}
+	name := preload[2].name
+	if _, err := c.lookup(ctx, name); err != nil {
+		t.Errorf("%s should not have been flushed", name)
+	}
+	if len(c.entries) != 2 {
+		t.Errorf("%s flushed too many entries", toflush)
+	}
+	toflush = preload[1].name
+	if !ns.FlushCacheEntry(ctx, toflush) {
+		t.Errorf("%s should have caused something to flush", toflush)
+	}
+	if _, ok := c.entries[toflush]; ok {
+		t.Errorf("%s should have been flushed", name)
+	}
+	if len(c.entries) != 1 {
+		t.Errorf("%s flushed too many entries", toflush)
+	}
+}
+
+func disabled(ctls []naming.CacheCtl) bool {
+	for _, c := range ctls {
+		if v, ok := c.(naming.DisableCache); ok && bool(v) {
+			return true
+		}
+	}
+	return false
+}
+
+func TestCacheDisableEnable(t *testing.T) {
+	ctx, cancel := test.TestContext()
+	defer cancel()
+	ns, _ := New()
+
+	// Default should be working resolution cache.
+	name := "/h1//a"
+	serverName := "/h2//"
+	c := ns.resolutionCache.(*ttlCache)
+	e := &naming.MountEntry{Servers: []naming.MountedServer{naming.MountedServer{Server: serverName, Deadline: future(3000)}}}
+	c.remember(ctx, name, e)
+	if ne, err := c.lookup(ctx, name); err != nil || ne.Servers[0].Server != serverName {
+		t.Errorf("should have found the server in the cache")
+	}
+
+	// Turn off the resolution cache.
+	ctls := ns.CacheCtl(naming.DisableCache(true))
+	if !disabled(ctls) {
+		t.Errorf("caching not disabled")
+	}
+	nc := ns.resolutionCache.(nullCache)
+	nc.remember(ctx, name, e)
+	if _, err := nc.lookup(ctx, name); err == nil {
+		t.Errorf("should not have found the server in the cache")
+	}
+
+	// Turn on the resolution cache.
+	ctls = ns.CacheCtl(naming.DisableCache(false))
+	if disabled(ctls) {
+		t.Errorf("caching disabled")
+	}
+	c = ns.resolutionCache.(*ttlCache)
+	c.remember(ctx, name, e)
+	if ne, err := c.lookup(ctx, name); err != nil || ne.Servers[0].Server != serverName {
+		t.Errorf("should have found the server in the cache")
+	}
+}
diff --git a/runtime/internal/naming/namespace/glob.go b/runtime/internal/naming/namespace/glob.go
new file mode 100644
index 0000000..ae24caf
--- /dev/null
+++ b/runtime/internal/naming/namespace/glob.go
@@ -0,0 +1,261 @@
+// 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 namespace
+
+import (
+	"io"
+	"strings"
+	"sync"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/glob"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+	"v.io/v23/verror"
+	"v.io/x/ref/lib/apilog"
+)
+
+type tracks struct {
+	m      sync.Mutex
+	places map[string]struct{}
+}
+
+func (tr *tracks) beenThereDoneThat(servers []string, pstr string) bool {
+	tr.m.Lock()
+	defer tr.m.Unlock()
+	found := false
+	for _, s := range servers {
+		x := s + "!" + pstr
+		if _, ok := tr.places[x]; ok {
+			found = true
+		}
+		tr.places[x] = struct{}{}
+	}
+	return found
+}
+
+// task is a sub-glob that has to be performed against a mount table.  Tasks are
+// done in parrallel to speed up the glob.
+type task struct {
+	pattern *glob.Glob         // pattern to match
+	er      *naming.GlobError  // error for that particular point in the name space
+	me      *naming.MountEntry // server to match at
+	error   error              // any error performing this task
+	depth   int                // number of mount tables traversed recursively
+}
+
+// globAtServer performs a Glob on the servers at a mount point.  It cycles through the set of
+// servers until it finds one that replies.
+func (ns *namespace) globAtServer(ctx *context.T, t *task, replies chan *task, tr *tracks, opts []rpc.CallOpt) {
+	defer func() {
+		if t.error == nil {
+			replies <- nil
+		} else {
+			replies <- t
+		}
+	}()
+	client := v23.GetClient(ctx)
+	pstr := t.pattern.String()
+	ctx.VI(2).Infof("globAtServer(%v, %v)", *t.me, pstr)
+
+	servers := []string{}
+	for _, s := range t.me.Servers {
+		servers = append(servers, naming.JoinAddressName(s.Server, ""))
+	}
+
+	// If there are no servers to call, this isn't a mount point.  No sense
+	// trying to call servers that aren't there.
+	if len(servers) == 0 {
+		t.error = nil
+		return
+	}
+
+	// If we've been there before with the same request, give up.
+	if tr.beenThereDoneThat(servers, pstr) {
+		t.error = nil
+		return
+	}
+
+	call, err := ns.parallelStartCall(ctx, client, servers, rpc.GlobMethod, []interface{}{pstr}, opts)
+	if err != nil {
+		t.error = err
+		return
+	}
+
+	// At this point we're commited to the server that answered the call
+	// first. Cycle through all replies from that server.
+	for {
+		// If the mount table returns an error, we're done.  Send the task to the channel
+		// including the error.  This terminates the task.
+		var gr naming.GlobReply
+		err := call.Recv(&gr)
+		if err == io.EOF {
+			break
+		}
+		if err != nil {
+			t.error = err
+			return
+		}
+
+		var x *task
+		switch v := gr.(type) {
+		case naming.GlobReplyEntry:
+			// Convert to the ever so slightly different name.MountTable version of a MountEntry
+			// and add it to the list.
+			x = &task{
+				me: &naming.MountEntry{
+					Name:             naming.Join(t.me.Name, v.Value.Name),
+					Servers:          v.Value.Servers,
+					ServesMountTable: v.Value.ServesMountTable,
+					IsLeaf:           v.Value.IsLeaf,
+				},
+				depth: t.depth + 1,
+			}
+		case naming.GlobReplyError:
+			// Pass on the error.
+			x = &task{
+				er:    &v.Value,
+				depth: t.depth + 1,
+			}
+		}
+
+		// x.depth is the number of servers we've walked through since we've gone
+		// recursive (i.e. with pattern length of 0).  Limit the depth of globs.
+		// TODO(p): return an error?
+		if t.pattern.Len() == 0 {
+			if x.depth > ns.maxRecursiveGlobDepth {
+				continue
+			}
+		}
+		replies <- x
+	}
+	t.error = call.Finish()
+	return
+}
+
+// depth returns the directory depth of a given name.  It is used to pick off the unsatisfied part of the pattern.
+func depth(name string) int {
+	name = strings.Trim(naming.Clean(name), "/")
+	if name == "" {
+		return 0
+	}
+	return strings.Count(name, "/") + 1
+}
+
+// globLoop fires off a go routine for each server and reads backs replies.
+func (ns *namespace) globLoop(ctx *context.T, e *naming.MountEntry, prefix string, pattern *glob.Glob, reply chan naming.GlobReply, tr *tracks, opts []rpc.CallOpt) {
+	defer close(reply)
+
+	// Provide enough buffers to avoid too much switching between the readers and the writers.
+	// This size is just a guess.
+	replies := make(chan *task, 100)
+	defer close(replies)
+
+	// Push the first task into the channel to start the ball rolling.  This task has the
+	// root of the search and the full pattern.  It will be the first task fired off in the for
+	// loop that follows.
+	replies <- &task{me: e, pattern: pattern}
+	replies <- nil
+	inFlight := 1
+
+	// Perform a parallel search of the name graph.  Each task will send what it learns
+	// on the replies channel.  If the reply is a mount point and the pattern is not completely
+	// fulfilled, a new task will be fired off to handle it.
+	for inFlight != 0 {
+		t := <-replies
+		// A nil reply represents a successfully terminated task.
+		// If no tasks are running, return.
+		if t == nil {
+			inFlight--
+			continue
+		}
+
+		// We want to output this entry if there was a real error other than
+		// "not a mount table".
+		//
+		// An error reply is also a terminated task.
+		// If no tasks are running, return.
+		if t.error != nil {
+			if !notAnMT(t.error) {
+				reply <- &naming.GlobReplyError{Value: naming.GlobError{Name: naming.Join(prefix, t.me.Name), Error: t.error}}
+			}
+			inFlight--
+			continue
+		}
+
+		// If this is just an error from the mount table, pass it on.
+		if t.er != nil {
+			x := *t.er
+			x.Name = naming.Join(prefix, x.Name)
+			reply <- &naming.GlobReplyError{Value: x}
+			continue
+		}
+
+		// Get the pattern elements below the current path.
+		suffix := pattern
+		for i := depth(t.me.Name) - 1; i >= 0; i-- {
+			suffix = suffix.Tail()
+		}
+
+		// If we've satisfied the request and this isn't the root,
+		// reply to the caller.
+		if suffix.Len() == 0 && t.depth != 0 {
+			x := *t.me
+			x.Name = naming.Join(prefix, x.Name)
+			reply <- &naming.GlobReplyEntry{Value: x}
+		}
+
+		// If the pattern is finished (so we're only querying about the root on the
+		// remote server) and the server is not another MT, then we needn't send the
+		// query on since we know the server will not supply a new address for the
+		// current name.
+		if suffix.Empty() {
+			if !t.me.ServesMountTable {
+				continue
+			}
+		}
+
+		// If this is restricted recursive and not a mount table, don't descend into it.
+		if suffix.Restricted() && suffix.Len() == 0 && !t.me.ServesMountTable {
+			continue
+		}
+
+		// Perform a glob at the next server.
+		inFlight++
+		t.pattern = suffix
+		go ns.globAtServer(ctx, t, replies, tr, opts)
+	}
+}
+
+// Glob implements naming.MountTable.Glob.
+func (ns *namespace) Glob(ctx *context.T, pattern string, opts ...naming.NamespaceOpt) (<-chan naming.GlobReply, error) {
+	defer apilog.LogCallf(ctx, "pattern=%.10s...,opts...=%v", pattern, opts)(ctx, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	// Root the pattern.  If we have no servers to query, give up.
+	e, patternWasRooted := ns.rootMountEntry(pattern)
+	if len(e.Servers) == 0 {
+		return nil, verror.New(naming.ErrNoMountTable, ctx)
+	}
+
+	// If the name doesn't parse, give up.
+	g, err := glob.Parse(e.Name)
+	if err != nil {
+		return nil, err
+	}
+
+	tr := &tracks{places: make(map[string]struct{})}
+
+	// If pattern was already rooted, make sure we tack that root
+	// onto all returned names.  Otherwise, just return the relative
+	// name.
+	var prefix string
+	if patternWasRooted {
+		prefix = e.Servers[0].Server
+	}
+	e.Name = ""
+	reply := make(chan naming.GlobReply, 100)
+	go ns.globLoop(ctx, e, prefix, g, reply, tr, getCallOpts(opts))
+	return reply, nil
+}
diff --git a/runtime/internal/naming/namespace/glob_test.go b/runtime/internal/naming/namespace/glob_test.go
new file mode 100644
index 0000000..fd6c86d
--- /dev/null
+++ b/runtime/internal/naming/namespace/glob_test.go
@@ -0,0 +1,31 @@
+// 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 namespace
+
+import (
+	"testing"
+)
+
+func TestDepth(t *testing.T) {
+	cases := []struct {
+		name  string
+		depth int
+	}{
+		{"", 0},
+		{"foo", 1},
+		{"foo/", 1},
+		{"foo/bar", 2},
+		{"foo//bar", 2},
+		{"/foo/bar", 2},
+		{"//", 0},
+		{"//foo//bar", 2},
+		{"/foo/bar//baz//baf/", 4},
+	}
+	for _, c := range cases {
+		if got, want := depth(c.name), c.depth; want != got {
+			t.Errorf("%q: unexpected depth: %d not %d", c.name, got, want)
+		}
+	}
+}
diff --git a/runtime/internal/naming/namespace/mount.go b/runtime/internal/naming/namespace/mount.go
new file mode 100644
index 0000000..884cec1
--- /dev/null
+++ b/runtime/internal/naming/namespace/mount.go
@@ -0,0 +1,108 @@
+// 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 namespace
+
+import (
+	"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/ref/lib/apilog"
+)
+
+// mountIntoMountTable mounts a single server into a single mount table.
+func mountIntoMountTable(ctx *context.T, client rpc.Client, name, server string, ttl time.Duration, flags naming.MountFlag, id string, opts ...rpc.CallOpt) (s status) {
+	s.id = id
+	ctx = withTimeout(ctx)
+	s.err = client.Call(ctx, name, "Mount", []interface{}{server, uint32(ttl.Seconds()), flags}, nil, append(opts, options.NoResolve{})...)
+	return
+}
+
+// Mount implements Namespace.Mount.
+func (ns *namespace) Mount(ctx *context.T, name, server string, ttl time.Duration, opts ...naming.NamespaceOpt) error {
+	defer apilog.LogCallf(ctx, "name=%.10s...,server=%.10s...,ttl=%v,opts...=%v", name, server, ttl, opts)(ctx, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	var flags naming.MountFlag
+	for _, o := range opts {
+		// NB: used a switch since we'll be adding more options.
+		switch v := o.(type) {
+		case naming.ReplaceMount:
+			if v {
+				flags |= naming.MountFlag(naming.Replace)
+			}
+		case naming.ServesMountTable:
+			if v {
+				flags |= naming.MountFlag(naming.MT)
+			}
+		case naming.IsLeaf:
+			if v {
+				flags |= naming.MountFlag(naming.Leaf)
+			}
+		}
+	}
+
+	client := v23.GetClient(ctx)
+	// Mount the server in all the returned mount tables.
+	f := func(ctx *context.T, mt, id string) status {
+		return mountIntoMountTable(ctx, client, mt, server, ttl, flags, id, getCallOpts(opts)...)
+	}
+	err := ns.dispatch(ctx, name, f, opts)
+	ctx.VI(1).Infof("Mount(%s, %q) -> %v", name, server, err)
+	return err
+}
+
+// unmountFromMountTable removes a single mounted server from a single mount table.
+func unmountFromMountTable(ctx *context.T, client rpc.Client, name, server string, id string, opts ...rpc.CallOpt) (s status) {
+	s.id = id
+	ctx = withTimeout(ctx)
+	s.err = client.Call(ctx, name, "Unmount", []interface{}{server}, nil, append(opts, options.NoResolve{})...)
+	return
+}
+
+// Unmount implements Namespace.Unmount.
+func (ns *namespace) Unmount(ctx *context.T, name, server string, opts ...naming.NamespaceOpt) error {
+	defer apilog.LogCallf(ctx, "name=%.10s...,server=%.10s...,opts...=%v", name, server, opts)(ctx, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	// Unmount the server from all the mount tables.
+	client := v23.GetClient(ctx)
+	f := func(ctx *context.T, mt, id string) status {
+		return unmountFromMountTable(ctx, client, mt, server, id, getCallOpts(opts)...)
+	}
+	err := ns.dispatch(ctx, name, f, opts)
+	ctx.VI(1).Infof("Unmount(%s, %s) -> %v", name, server, err)
+	return err
+}
+
+// deleteFromMountTable deletes a name from a single mount table.  If there are any children
+// and deleteSubtree isn't true, nothing is deleted.
+func deleteFromMountTable(ctx *context.T, client rpc.Client, name string, deleteSubtree bool, id string, opts ...rpc.CallOpt) (s status) {
+	s.id = id
+	ctx = withTimeout(ctx)
+	s.err = client.Call(ctx, name, "Delete", []interface{}{deleteSubtree}, nil, append(opts, options.NoResolve{})...)
+	return
+}
+
+// RDeleteemove implements Namespace.Delete.
+func (ns *namespace) Delete(ctx *context.T, name string, deleteSubtree bool, opts ...naming.NamespaceOpt) error {
+	defer apilog.LogCallf(ctx, "name=%.10s...,deleteSubtree=%v,opts...=%v", name, deleteSubtree, opts)(ctx, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	// Remove from all the mount tables.
+	client := v23.GetClient(ctx)
+	f := func(ctx *context.T, mt, id string) status {
+		return deleteFromMountTable(ctx, client, mt, deleteSubtree, id, getCallOpts(opts)...)
+	}
+	err := ns.dispatch(ctx, name, f, opts)
+	ctx.VI(1).Infof("Remove(%s, %v) -> %v", name, deleteSubtree, err)
+	return err
+}
+
+func str2pattern(strs []string) (ret []security.BlessingPattern) {
+	ret = make([]security.BlessingPattern, len(strs))
+	for i, s := range strs {
+		ret[i] = security.BlessingPattern(s)
+	}
+	return
+}
diff --git a/runtime/internal/naming/namespace/namespace.go b/runtime/internal/naming/namespace/namespace.go
new file mode 100644
index 0000000..1a9f109
--- /dev/null
+++ b/runtime/internal/naming/namespace/namespace.go
@@ -0,0 +1,229 @@
+// 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 namespace
+
+import (
+	"sync"
+	"time"
+
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	vdltime "v.io/v23/vdlroot/time"
+	"v.io/v23/verror"
+
+	"v.io/x/ref/lib/apilog"
+	inaming "v.io/x/ref/runtime/internal/naming"
+)
+
+const defaultMaxResolveDepth = 32
+const defaultMaxRecursiveGlobDepth = 10
+
+const pkgPath = "v.io/x/ref/runtime/internal/naming/namespace"
+
+var (
+	errNotRootedName = verror.Register(pkgPath+".errNotRootedName", verror.NoRetry, "{1:}{2:} At least one root is not a rooted name{:_}")
+)
+
+// namespace is an implementation of naming.Namespace.
+type namespace struct {
+	sync.RWMutex
+
+	// the default root servers for resolutions in this namespace.
+	roots []string
+
+	// depth limits
+	maxResolveDepth       int
+	maxRecursiveGlobDepth int
+
+	// cache for name resolutions
+	resolutionCache cache
+}
+
+func rooted(names []string) bool {
+	for _, n := range names {
+		if a, _ := naming.SplitAddressName(n); len(a) == 0 {
+			return false
+		}
+	}
+	return true
+}
+
+func badRoots(roots []string) error {
+	return verror.New(errNotRootedName, nil, roots)
+}
+
+// Create a new namespace.
+func New(roots ...string) (*namespace, error) {
+	if !rooted(roots) {
+		return nil, badRoots(roots)
+	}
+	// A namespace with no roots can still be used for lookups of rooted names.
+	return &namespace{
+		roots:                 roots,
+		maxResolveDepth:       defaultMaxResolveDepth,
+		maxRecursiveGlobDepth: defaultMaxRecursiveGlobDepth,
+		resolutionCache:       newTTLCache(),
+	}, nil
+}
+
+// SetRoots implements naming.Namespace.SetRoots
+func (ns *namespace) SetRoots(roots ...string) error {
+	defer apilog.LogCallf(nil, "roots...=%v", roots)(nil, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	// Allow roots to be cleared with a call of SetRoots()
+	if len(roots) > 0 && !rooted(roots) {
+		return badRoots(roots)
+	}
+	ns.Lock()
+	defer ns.Unlock()
+	// TODO(cnicolaou): filter out duplicate values.
+	ns.roots = roots
+	return nil
+}
+
+// SetDepthLimits overrides the default limits.
+func (ns *namespace) SetDepthLimits(resolve, glob int) {
+	if resolve >= 0 {
+		ns.maxResolveDepth = resolve
+	}
+	if glob >= 0 {
+		ns.maxRecursiveGlobDepth = glob
+	}
+}
+
+// Roots implements naming.Namespace.Roots
+func (ns *namespace) Roots() []string {
+	//nologcall
+	ns.RLock()
+	defer ns.RUnlock()
+	roots := make([]string, len(ns.roots))
+	for i, r := range ns.roots {
+		roots[i] = r
+	}
+	return roots
+}
+
+// rootName 'roots' a name: if name is not a rooted name, it prepends the root
+// mounttable's OA.
+func (ns *namespace) rootName(name string) []string {
+	name = naming.Clean(name)
+	if address, _ := naming.SplitAddressName(name); len(address) == 0 {
+		var ret []string
+		ns.RLock()
+		defer ns.RUnlock()
+		for _, r := range ns.roots {
+			ret = append(ret, naming.Join(r, name))
+		}
+		return ret
+	}
+	return []string{name}
+}
+
+// rootMountEntry 'roots' a name creating a mount entry for the name.
+//
+// Returns:
+// (1) MountEntry
+// (2) Whether "name" is a rooted name or not (if not, the namespace roots
+//     configured in "ns" will be used).
+func (ns *namespace) rootMountEntry(name string, opts ...naming.NamespaceOpt) (*naming.MountEntry, bool) {
+	_, name = security.SplitPatternName(naming.Clean(name))
+	e := new(naming.MountEntry)
+	deadline := vdltime.Deadline{Time: time.Now().Add(time.Hour)} // plenty of time for a call
+	address, suffix := naming.SplitAddressName(name)
+	if len(address) == 0 {
+		e.ServesMountTable = true
+		e.Name = name
+		ns.RLock()
+		defer ns.RUnlock()
+		for _, r := range ns.roots {
+			e.Servers = append(e.Servers, naming.MountedServer{Server: r, Deadline: deadline})
+		}
+		return e, false
+	}
+	servesMT := true
+	if ep, err := inaming.NewEndpoint(address); err == nil {
+		servesMT = ep.ServesMountTable()
+	}
+	e.ServesMountTable = servesMT
+	e.Name = suffix
+	e.Servers = []naming.MountedServer{{Server: naming.JoinAddressName(address, ""), Deadline: deadline}}
+	return e, true
+}
+
+// notAnMT returns true if the error indicates this isn't a mounttable server.
+func notAnMT(err error) bool {
+	switch verror.ErrorID(err) {
+	case verror.ErrBadArg.ID:
+		// This should cover "rpc: wrong number of in-args".
+		return true
+	case verror.ErrNoExist.ID, verror.ErrUnknownMethod.ID, verror.ErrUnknownSuffix.ID:
+		// This should cover "rpc: unknown method", "rpc: dispatcher not
+		// found", and dispatcher Lookup not found errors.
+		return true
+	case verror.ErrBadProtocol.ID:
+		// This covers "rpc: response decoding failed: EOF".
+		return true
+	}
+	return false
+}
+
+// All operations against the mount table service use this fixed timeout unless overridden.
+const callTimeout = 30 * time.Second
+
+// withTimeout returns a new context if the orinal has no timeout set.
+func withTimeout(ctx *context.T) *context.T {
+	if _, ok := ctx.Deadline(); !ok {
+		ctx, _ = context.WithTimeout(ctx, callTimeout)
+	}
+	return ctx
+}
+
+// withTimeoutAndCancel returns a new context with a deadline and a cancellation function.
+func withTimeoutAndCancel(ctx *context.T) (nctx *context.T, cancel context.CancelFunc) {
+	if _, ok := ctx.Deadline(); !ok {
+		nctx, cancel = context.WithTimeout(ctx, callTimeout)
+	} else {
+		nctx, cancel = context.WithCancel(ctx)
+	}
+	return
+}
+
+// CacheCtl implements naming.Namespace.CacheCtl
+func (ns *namespace) CacheCtl(ctls ...naming.CacheCtl) []naming.CacheCtl {
+	defer apilog.LogCallf(nil, "ctls...=%v", ctls)(nil, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	for _, c := range ctls {
+		switch v := c.(type) {
+		case naming.DisableCache:
+			ns.Lock()
+			if _, isDisabled := ns.resolutionCache.(nullCache); isDisabled {
+				if !v {
+					ns.resolutionCache = newTTLCache()
+				}
+			} else {
+				if v {
+					ns.resolutionCache = newNullCache()
+				}
+			}
+			ns.Unlock()
+		}
+	}
+	ns.RLock()
+	defer ns.RUnlock()
+	if _, isDisabled := ns.resolutionCache.(nullCache); isDisabled {
+		return []naming.CacheCtl{naming.DisableCache(true)}
+	}
+	return nil
+}
+
+func getCallOpts(opts []naming.NamespaceOpt) []rpc.CallOpt {
+	var out []rpc.CallOpt
+	for _, o := range opts {
+		if co, ok := o.(rpc.CallOpt); ok {
+			out = append(out, co)
+		}
+	}
+	return out
+}
diff --git a/runtime/internal/naming/namespace/parallelstartcall.go b/runtime/internal/naming/namespace/parallelstartcall.go
new file mode 100644
index 0000000..77525a2
--- /dev/null
+++ b/runtime/internal/naming/namespace/parallelstartcall.go
@@ -0,0 +1,122 @@
+// 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 namespace
+
+import (
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/options"
+	"v.io/v23/rpc"
+	"v.io/v23/verror"
+	inaming "v.io/x/ref/runtime/internal/naming"
+)
+
+type startStatus struct {
+	index int
+	err   error
+	call  rpc.ClientCall
+}
+
+func tryStartCall(ctx *context.T, client rpc.Client, target, method string, args []interface{}, c chan startStatus, index int, opts ...rpc.CallOpt) {
+	call, err := client.StartCall(ctx, target, method, args, append(opts, options.NoResolve{})...)
+	c <- startStatus{index: index, err: err, call: call}
+}
+
+// parallelStartCall returns the first succeeding StartCall.
+func (ns *namespace) parallelStartCall(ctx *context.T, client rpc.Client, servers []string, method string, args []interface{}, opts []rpc.CallOpt) (rpc.ClientCall, error) {
+	if len(servers) == 0 {
+		return nil, verror.New(verror.ErrNoExist, ctx, "no servers to resolve query")
+	}
+
+	// StartCall to each of the servers.
+	c := make(chan startStatus, len(servers))
+	cancelFuncs := make([]context.CancelFunc, len(servers))
+	for index, server := range servers {
+		callCtx, cancel := withTimeoutAndCancel(ctx)
+		cancelFuncs[index] = cancel
+		go tryStartCall(callCtx, client, server, method, args, c, index, opts...)
+	}
+
+	// First positive response wins.  Cancel the rest.  The cancellation
+	// will prevent any RPCs from starting or progressing.  We do not close
+	// the channel since some go routines may still be in flight and want to
+	// write status to it.  The channel will be garbage collected when all
+	// references to it disappear.
+	var final startStatus
+	for range servers {
+		final = <-c
+		if final.err == nil {
+			cancelFuncs[final.index] = nil
+			break
+		}
+	}
+	// Cancel the rest.
+	for _, cancel := range cancelFuncs {
+		if cancel != nil {
+			cancel()
+		}
+	}
+	return final.call, final.err
+}
+
+type status struct {
+	id  string
+	err error
+}
+
+// nameToRID converts a name to a routing ID string. If a routing ID can't be obtained,
+// it just returns the name.
+func nameToRID(name string) string {
+	address, _ := naming.SplitAddressName(name)
+	if ep, err := inaming.NewEndpoint(address); err == nil {
+		return ep.RID.String()
+	}
+	return name
+}
+
+// collectStati collects n status messages from channel c and returns an error if, for
+// any id, there is no successful reply.
+func collectStati(c chan status, n int) error {
+	// Make a map indexed by the routing id (or address if routing id not found) of
+	// each mount table.  A mount table may be reachable via multiple addresses but
+	// each address should have the same routing id.  We should only return an error
+	// if any of the ids had no successful mounts.
+	statusByID := make(map[string]error)
+	// Get the status of each request.
+	for i := 0; i < n; i++ {
+		s := <-c
+		if _, ok := statusByID[s.id]; !ok || s.err == nil {
+			statusByID[s.id] = s.err
+		}
+	}
+	// Return any error.
+	for _, s := range statusByID {
+		if s != nil {
+			return s
+		}
+	}
+	return nil
+}
+
+// dispatch executes f in parallel for each mount table implementing mTName.
+func (ns *namespace) dispatch(ctx *context.T, mTName string, f func(*context.T, string, string) status, opts []naming.NamespaceOpt) error {
+	// Resolve to all the mount tables implementing name.
+	me, err := ns.ResolveToMountTable(ctx, mTName, opts...)
+	if err != nil {
+		return err
+	}
+	mts := me.Names()
+	// Apply f to each of the returned mount tables.
+	c := make(chan status, len(mts))
+	for _, mt := range mts {
+		go func(mt string) {
+			c <- f(ctx, mt, nameToRID(mt))
+		}(mt)
+	}
+	finalerr := collectStati(c, len(mts))
+	// Forget any previous cached information about these names.
+	ns.resolutionCache.forget(ctx, mts)
+	return finalerr
+}
diff --git a/runtime/internal/naming/namespace/perms.go b/runtime/internal/naming/namespace/perms.go
new file mode 100644
index 0000000..df7302c
--- /dev/null
+++ b/runtime/internal/naming/namespace/perms.go
@@ -0,0 +1,58 @@
+// 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 namespace
+
+import (
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/options"
+	"v.io/v23/rpc"
+	"v.io/v23/security/access"
+	"v.io/x/ref/lib/apilog"
+)
+
+// setPermsInMountTable sets the Permissions in a single server.
+func setPermsInMountTable(ctx *context.T, client rpc.Client, name string, perms access.Permissions, version, id string, opts []rpc.CallOpt) (s status) {
+	s.id = id
+	ctx = withTimeout(ctx)
+	s.err = client.Call(ctx, name, "SetPermissions", []interface{}{perms, version}, nil, append(opts, options.NoResolve{})...)
+	return
+}
+
+func (ns *namespace) SetPermissions(ctx *context.T, name string, perms access.Permissions, version string, opts ...naming.NamespaceOpt) error {
+	defer apilog.LogCallf(ctx, "name=%.10s...,perms=,version=%.10s...,opts...=%v", name, version, opts)(ctx, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	client := v23.GetClient(ctx)
+
+	// Apply to all mount tables implementing the name.
+	f := func(ctx *context.T, mt, id string) status {
+		return setPermsInMountTable(ctx, client, mt, perms, version, id, getCallOpts(opts))
+	}
+	err := ns.dispatch(ctx, name, f, opts)
+	ctx.VI(1).Infof("SetPermissions(%s, %v, %s) -> %v", name, perms, version, err)
+	return err
+}
+
+// GetPermissions gets Permissions from a mount table.
+func (ns *namespace) GetPermissions(ctx *context.T, name string, opts ...naming.NamespaceOpt) (perms access.Permissions, version string, err error) {
+	defer apilog.LogCallf(ctx, "name=%.10s...,opts...=%v", name, opts)(ctx, "perms=,version=%.10s...,err=%v", &version, &err) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	client := v23.GetClient(ctx)
+
+	// Resolve to all the mount tables implementing name.
+	me, rerr := ns.ResolveToMountTable(ctx, name, opts...)
+	if rerr != nil {
+		err = rerr
+		return
+	}
+	mts := me.Names()
+
+	call, serr := ns.parallelStartCall(ctx, client, mts, "GetPermissions", []interface{}{}, getCallOpts(opts))
+	if serr != nil {
+		err = serr
+		return
+	}
+	err = call.Finish(&perms, &version)
+	return
+}
diff --git a/runtime/internal/naming/namespace/perms_test.go b/runtime/internal/naming/namespace/perms_test.go
new file mode 100644
index 0000000..bf2dba4
--- /dev/null
+++ b/runtime/internal/naming/namespace/perms_test.go
@@ -0,0 +1,250 @@
+// 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 namespace_test
+
+import (
+	"fmt"
+	"reflect"
+	"testing"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/security/access"
+
+	"v.io/x/ref/lib/xrpc"
+	_ "v.io/x/ref/runtime/factories/generic"
+	"v.io/x/ref/services/mounttable/mounttablelib"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/testutil"
+)
+
+func init() {
+	test.Init()
+}
+
+func initTest() (rootCtx *context.T, aliceCtx *context.T, bobCtx *context.T, shutdown v23.Shutdown) {
+	ctx, shutdown := test.V23Init()
+	var err error
+	if rootCtx, err = v23.WithPrincipal(ctx, testutil.NewPrincipal("root")); err != nil {
+		panic("failed to set root principal")
+	}
+	if aliceCtx, err = v23.WithPrincipal(ctx, testutil.NewPrincipal("alice")); err != nil {
+		panic("failed to set alice principal")
+	}
+	if bobCtx, err = v23.WithPrincipal(ctx, testutil.NewPrincipal("bob")); err != nil {
+		panic("failed to set bob principal")
+	}
+	for _, r := range []*context.T{rootCtx, aliceCtx, bobCtx} {
+		// A hack to set the namespace roots to a value that won't work.
+		v23.GetNamespace(r).SetRoots()
+		// And have all principals recognize each others blessings.
+		p1 := v23.GetPrincipal(r)
+		for _, other := range []*context.T{rootCtx, aliceCtx, bobCtx} {
+			// testutil.NewPrincipal has already setup each
+			// principal to use the same blessing for both server
+			// and client activities.
+			if err := p1.AddToRoots(v23.GetPrincipal(other).BlessingStore().Default()); err != nil {
+				panic(err)
+			}
+		}
+	}
+	return rootCtx, aliceCtx, bobCtx, shutdown
+}
+
+// Create a new mounttable service.
+func newMT(t *testing.T, ctx *context.T) (func(), string) {
+	estr, stopFunc, err := mounttablelib.StartServers(ctx, v23.GetListenSpec(ctx), "", "", "", "", "mounttable")
+	if err != nil {
+		t.Fatalf("r.NewServer: %s", err)
+	}
+	return stopFunc, estr
+}
+
+type nopServer struct{ x int }
+
+func (s *nopServer) NOP(*context.T, rpc.ServerCall) error {
+	return nil
+}
+
+var nobody = []security.BlessingPattern{""}
+var everybody = []security.BlessingPattern{"..."}
+var closedPerms = access.Permissions{
+	"Resolve": access.AccessList{
+		In: nobody,
+	},
+	"Read": access.AccessList{
+		In: nobody,
+	},
+	"Admin": access.AccessList{
+		In: nobody,
+	},
+	"Create": access.AccessList{
+		In: nobody,
+	},
+	"Mount": access.AccessList{
+		In: nobody,
+	},
+}
+var closedPermsWithOwnerAdded = access.Permissions{
+	"Resolve": access.AccessList{
+		In: nobody,
+	},
+	"Read": access.AccessList{
+		In: nobody,
+	},
+	"Admin": access.AccessList{
+		In: []security.BlessingPattern{"", "root"},
+	},
+	"Create": access.AccessList{
+		In: nobody,
+	},
+	"Mount": access.AccessList{
+		In: nobody,
+	},
+}
+var openPerms = access.Permissions{
+	"Resolve": access.AccessList{
+		In: everybody,
+	},
+	"Read": access.AccessList{
+		In: everybody,
+	},
+	"Admin": access.AccessList{
+		In: everybody,
+	},
+	"Create": access.AccessList{
+		In: everybody,
+	},
+	"Mount": access.AccessList{
+		In: everybody,
+	},
+}
+
+func TestPermissions(t *testing.T) {
+	// Create three different personalities.
+	// TODO(p): Use the multiple personalities to test Permissions functionality.
+	rootCtx, aliceCtx, _, shutdown := initTest()
+	defer shutdown()
+
+	// Create root mounttable.
+	stop, rmtAddr := newMT(t, rootCtx)
+	fmt.Printf("rmt at %s\n", rmtAddr)
+	defer stop()
+	ns := v23.GetNamespace(rootCtx)
+	ns.SetRoots("/" + rmtAddr)
+
+	// Create two parallel mount tables.
+	stop1, mt1Addr := newMT(t, rootCtx)
+	fmt.Printf("mt1 at %s\n", mt1Addr)
+	defer stop1()
+	stop2, mt2Addr := newMT(t, rootCtx)
+	fmt.Printf("mt2 at %s\n", mt2Addr)
+	defer stop2()
+
+	// Mount them into the root.
+	if err := ns.Mount(rootCtx, "a/b/c", mt1Addr, 0, naming.ServesMountTable(true)); err != nil {
+		t.Fatalf("Failed to Mount %s onto a/b/c: %s", "/"+mt1Addr, err)
+	}
+	if err := ns.Mount(rootCtx, "a/b/c", mt2Addr, 0, naming.ServesMountTable(true)); err != nil {
+		t.Fatalf("Failed to Mount %s onto a/b/c: %s", "/"+mt2Addr, err)
+	}
+
+	// Set/Get the mount point's Permissions.
+	perms, version, err := ns.GetPermissions(rootCtx, "a/b/c")
+	if err != nil {
+		t.Fatalf("GetPermissions a/b/c: %s", err)
+	}
+	if err := ns.SetPermissions(rootCtx, "a/b/c", openPerms, version); err != nil {
+		t.Fatalf("SetPermissions a/b/c: %s", err)
+	}
+	nacl, _, err := ns.GetPermissions(rootCtx, "a/b/c")
+	if err != nil {
+		t.Fatalf("GetPermissions a/b/c: %s", err)
+	}
+	if !reflect.DeepEqual(openPerms, nacl) {
+		t.Fatalf("want %v, got %v", openPerms, nacl)
+	}
+
+	// Now Set/Get the parallel mount point's Permissions.
+	name := "a/b/c/d/e"
+	version = "" // Parallel setperms with any other value is dangerous
+	if err := ns.SetPermissions(rootCtx, name, openPerms, version); err != nil {
+		t.Fatalf("SetPermissions %s: %s", name, err)
+	}
+	nacl, _, err = ns.GetPermissions(rootCtx, name)
+	if err != nil {
+		t.Fatalf("GetPermissions %s: %s", name, err)
+	}
+	if !reflect.DeepEqual(openPerms, nacl) {
+		t.Fatalf("want %v, got %v", openPerms, nacl)
+	}
+
+	// Get from each server individually to make sure both are set.
+	name = naming.Join(mt1Addr, "d/e")
+	nacl, _, err = ns.GetPermissions(rootCtx, name)
+	if err != nil {
+		t.Fatalf("GetPermissions %s: %s", name, err)
+	}
+	if !reflect.DeepEqual(openPerms, nacl) {
+		t.Fatalf("want %v, got %v", openPerms, nacl)
+	}
+	name = naming.Join(mt2Addr, "d/e")
+	nacl, _, err = ns.GetPermissions(rootCtx, name)
+	if err != nil {
+		t.Fatalf("GetPermissions %s: %s", name, err)
+	}
+	if !reflect.DeepEqual(openPerms, nacl) {
+		t.Fatalf("want %v, got %v", perms, nacl)
+	}
+
+	// Create mount points accessible only by root's key and owner.
+	name = "a/b/c/d/f"
+	deadbody := "/the:8888/rain"
+	if err := ns.SetPermissions(rootCtx, name, closedPerms, version); err != nil {
+		t.Fatalf("SetPermissions %s: %s", name, err)
+	}
+	nacl, _, err = ns.GetPermissions(rootCtx, name)
+	if err != nil {
+		t.Fatalf("GetPermissions %s: %s", name, err)
+	}
+	if !reflect.DeepEqual(closedPermsWithOwnerAdded, nacl) {
+		t.Fatalf("want %v, got %v", closedPermsWithOwnerAdded, nacl)
+	}
+	if err := ns.Mount(rootCtx, name, deadbody, 10000); err != nil {
+		t.Fatalf("Mount %s: %s", name, err)
+	}
+
+	// Alice shouldn't be able to resolve it.
+	_, err = v23.GetNamespace(aliceCtx).Resolve(aliceCtx, name)
+	if err == nil {
+		t.Fatalf("as alice we shouldn't be able to Resolve %s", name)
+	}
+
+	// Root should be able to resolve it.
+	_, err = ns.Resolve(rootCtx, name)
+	if err != nil {
+		t.Fatalf("as root Resolve %s: %s", name, err)
+	}
+
+	// Create a mount point via Serve accessible only by root's key and owner.
+	name = "a/b/c/d/g"
+	if err := ns.SetPermissions(rootCtx, name, closedPerms, version); err != nil {
+		t.Fatalf("SetPermissions %s: %s", name, err)
+	}
+	if _, err := xrpc.NewServer(rootCtx, name, &nopServer{1}, nil); err != nil {
+		t.Fatalf("v23.NewServer failed: %v", err)
+	}
+	// Alice shouldn't be able to resolve it.
+	_, err = v23.GetNamespace(aliceCtx).Resolve(aliceCtx, name)
+	if err == nil {
+		t.Fatalf("as alice we shouldn't be able to Resolve %s", name)
+	}
+
+	// Root should be able to resolve it.
+	resolveWithRetry(rootCtx, name)
+}
diff --git a/runtime/internal/naming/namespace/resolve.go b/runtime/internal/naming/namespace/resolve.go
new file mode 100644
index 0000000..7f96c8d
--- /dev/null
+++ b/runtime/internal/naming/namespace/resolve.go
@@ -0,0 +1,204 @@
+// 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 namespace
+
+import (
+	"errors"
+	"runtime"
+	"strings"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/options"
+	"v.io/v23/rpc"
+	"v.io/v23/verror"
+	"v.io/x/ref/lib/apilog"
+)
+
+func (ns *namespace) resolveAgainstMountTable(ctx *context.T, client rpc.Client, e *naming.MountEntry, opts ...rpc.CallOpt) (*naming.MountEntry, error) {
+	// Try each server till one answers.
+	finalErr := errors.New("no servers to resolve query")
+	opts = append(opts, options.NoResolve{})
+	for _, s := range e.Servers {
+		// If the server was not specified as an endpoint (perhaps as host:port)
+		// then we really don't know if this is a mounttable or not.  Check the
+		// cache to see if we've tried in the recent past and it came back as not
+		// a mounttable.
+		if ns.resolutionCache.isNotMT(s.Server) {
+			finalErr = verror.New(verror.ErrUnknownMethod, nil, "ResolveStep")
+			continue
+		}
+		// Assume a mount table and make the call.
+		name := naming.JoinAddressName(s.Server, e.Name)
+		// First check the cache.
+		if ne, err := ns.resolutionCache.lookup(ctx, name); err == nil {
+			ctx.VI(2).Infof("resolveAMT %s from cache -> %v", name, convertServersToStrings(ne.Servers, ne.Name))
+			return &ne, nil
+		}
+		// Not in cache, call the real server.
+		callCtx := ctx
+		if _, hasDeadline := ctx.Deadline(); !hasDeadline {
+			// Only set a per-call timeout if a deadline has not already
+			// been set.
+			callCtx = withTimeout(ctx)
+		}
+		entry := new(naming.MountEntry)
+		if err := client.Call(callCtx, name, "ResolveStep", nil, []interface{}{entry}, opts...); err != nil {
+			// If any replica says the name doesn't exist, return that fact.
+			if verror.ErrorID(err) == naming.ErrNoSuchName.ID || verror.ErrorID(err) == naming.ErrNoSuchNameRoot.ID {
+				return nil, err
+			}
+			// If it wasn't a mounttable remember that fact.  The check for the __ is for
+			// the debugging hack in the local namespace of every server.  That part never
+			// answers mounttable RPCs and shouldn't make us think this isn't a mounttable
+			// server.
+			if notAnMT(err) && !strings.HasPrefix(e.Name, "__") {
+				ns.resolutionCache.setNotMT(s.Server)
+			}
+			// Keep track of the final error and continue with next server.
+			finalErr = err
+			ctx.VI(2).Infof("resolveAMT: Finish %s failed: %s", name, err)
+			continue
+		}
+		// Add result to cache.
+		ns.resolutionCache.remember(ctx, name, entry)
+		ctx.VI(2).Infof("resolveAMT %s -> %v", name, entry)
+		return entry, nil
+	}
+	ctx.VI(2).Infof("resolveAMT %v -> %v", e.Servers, finalErr)
+	return nil, finalErr
+}
+
+func terminal(e *naming.MountEntry) bool {
+	return len(e.Name) == 0
+}
+
+// Resolve implements v.io/v23/naming.Namespace.
+func (ns *namespace) Resolve(ctx *context.T, name string, opts ...naming.NamespaceOpt) (*naming.MountEntry, error) {
+	defer apilog.LogCallf(ctx, "name=%.10s...,opts...=%v", name, opts)(ctx, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	e, _ := ns.rootMountEntry(name, opts...)
+	if ctx.V(2) {
+		_, file, line, _ := runtime.Caller(1)
+		ctx.Infof("Resolve(%s) called from %s:%d", name, file, line)
+		ctx.Infof("Resolve(%s) -> rootMountEntry %v", name, *e)
+	}
+	if skipResolve(opts) {
+		return e, nil
+	}
+	if len(e.Servers) == 0 {
+		return nil, verror.New(naming.ErrNoSuchName, ctx, name)
+	}
+	client := v23.GetClient(ctx)
+	callOpts := getCallOpts(opts)
+
+	// Iterate walking through mount table servers.
+	for remaining := ns.maxResolveDepth; remaining > 0; remaining-- {
+		ctx.VI(2).Infof("Resolve(%s) loop %v", name, *e)
+		if !e.ServesMountTable || terminal(e) {
+			ctx.VI(1).Infof("Resolve(%s) -> %v", name, *e)
+			return e, nil
+		}
+		var err error
+		curr := e
+		if e, err = ns.resolveAgainstMountTable(ctx, client, curr, callOpts...); err != nil {
+			// Lots of reasons why another error can happen.  We are trying
+			// to single out "this isn't a mount table".
+			if notAnMT(err) {
+				ctx.VI(1).Infof("Resolve(%s) -> %v", name, curr)
+				return curr, nil
+			}
+			if verror.ErrorID(err) == naming.ErrNoSuchNameRoot.ID {
+				err = verror.New(naming.ErrNoSuchName, ctx, name)
+			}
+			ctx.VI(1).Infof("Resolve(%s) -> (%s: %v)", err, name, curr)
+			return nil, err
+		}
+	}
+	return nil, verror.New(naming.ErrResolutionDepthExceeded, ctx)
+}
+
+// ResolveToMountTable implements v.io/v23/naming.Namespace.
+func (ns *namespace) ResolveToMountTable(ctx *context.T, name string, opts ...naming.NamespaceOpt) (*naming.MountEntry, error) {
+	defer apilog.LogCallf(ctx, "name=%.10s...,opts...=%v", name, opts)(ctx, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	e, _ := ns.rootMountEntry(name, opts...)
+	if ctx.V(2) {
+		_, file, line, _ := runtime.Caller(1)
+		ctx.Infof("ResolveToMountTable(%s) called from %s:%d", name, file, line)
+		ctx.Infof("ResolveToMountTable(%s) -> rootNames %v", name, e)
+	}
+	if len(e.Servers) == 0 {
+		return nil, verror.New(naming.ErrNoMountTable, ctx)
+	}
+	callOpts := getCallOpts(opts)
+	client := v23.GetClient(ctx)
+	last := e
+	for remaining := ns.maxResolveDepth; remaining > 0; remaining-- {
+		ctx.VI(2).Infof("ResolveToMountTable(%s) loop %v", name, e)
+		var err error
+		curr := e
+		// If the next name to resolve doesn't point to a mount table, we're done.
+		if !e.ServesMountTable || terminal(e) {
+			ctx.VI(1).Infof("ResolveToMountTable(%s) -> %v", name, last)
+			return last, nil
+		}
+		if e, err = ns.resolveAgainstMountTable(ctx, client, e, callOpts...); err != nil {
+			if verror.ErrorID(err) == naming.ErrNoSuchNameRoot.ID {
+				ctx.VI(1).Infof("ResolveToMountTable(%s) -> %v (NoSuchRoot: %v)", name, last, curr)
+				return last, nil
+			}
+			if verror.ErrorID(err) == naming.ErrNoSuchName.ID {
+				ctx.VI(1).Infof("ResolveToMountTable(%s) -> %v (NoSuchName: %v)", name, curr, curr)
+				return curr, nil
+			}
+			// Lots of reasons why another error can happen.  We are trying
+			// to single out "this isn't a mount table".
+			if notAnMT(err) {
+				ctx.VI(1).Infof("ResolveToMountTable(%s) -> %v", name, last)
+				return last, nil
+			}
+			// TODO(caprita): If the server is unreachable for
+			// example, we may still want to return its parent
+			// mounttable rather than an error.
+			ctx.VI(1).Infof("ResolveToMountTable(%s) -> %v", name, err)
+			return nil, err
+		}
+		last = curr
+	}
+	return nil, verror.New(naming.ErrResolutionDepthExceeded, ctx)
+}
+
+// FlushCache flushes the most specific entry found for name.  It returns true if anything was
+// actually flushed.
+func (ns *namespace) FlushCacheEntry(ctx *context.T, name string) bool {
+	defer apilog.LogCallf(nil, "name=%.10s...", name)(nil, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	flushed := false
+	for _, n := range ns.rootName(name) {
+		// Walk the cache as we would in a resolution.  Unlike a resolution, we have to follow
+		// all branches since we want to flush all entries at which we might end up whereas in a resolution,
+		// we stop with the first branch that works.
+		if e, err := ns.resolutionCache.lookup(ctx, n); err == nil {
+			// Recurse.
+			for _, s := range e.Servers {
+				flushed = flushed || ns.FlushCacheEntry(ctx, naming.Join(s.Server, e.Name))
+			}
+			if !flushed {
+				// Forget the entry we just used.
+				ns.resolutionCache.forget(ctx, []string{naming.TrimSuffix(n, e.Name)})
+				flushed = true
+			}
+		}
+	}
+	return flushed
+}
+
+func skipResolve(opts []naming.NamespaceOpt) bool {
+	for _, o := range opts {
+		if _, ok := o.(options.NoResolve); ok {
+			return true
+		}
+	}
+	return false
+}
diff --git a/runtime/internal/naming/namespace/stub.go b/runtime/internal/naming/namespace/stub.go
new file mode 100644
index 0000000..9c0ca59
--- /dev/null
+++ b/runtime/internal/naming/namespace/stub.go
@@ -0,0 +1,21 @@
+// 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 namespace
+
+import "v.io/v23/naming"
+
+func convertServersToStrings(servers []naming.MountedServer, suffix string) (ret []string) {
+	for _, s := range servers {
+		ret = append(ret, naming.Join(s.Server, suffix))
+	}
+	return
+}
+
+func convertStringsToServers(servers []string) (ret []naming.MountedServer) {
+	for _, s := range servers {
+		ret = append(ret, naming.MountedServer{Server: s})
+	}
+	return
+}
diff --git a/runtime/internal/naming/namespace/v23_internal_test.go b/runtime/internal/naming/namespace/v23_internal_test.go
new file mode 100644
index 0000000..cf82452
--- /dev/null
+++ b/runtime/internal/naming/namespace/v23_internal_test.go
@@ -0,0 +1,20 @@
+// 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 namespace
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	os.Exit(m.Run())
+}
diff --git a/runtime/internal/platform/platform.go b/runtime/internal/platform/platform.go
new file mode 100644
index 0000000..889db7c
--- /dev/null
+++ b/runtime/internal/platform/platform.go
@@ -0,0 +1,58 @@
+// 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 platform
+
+import (
+	"fmt"
+	"v.io/v23/security"
+)
+
+// Platform describes the hardware and software environment that this
+// process is running on. It is modeled on the Unix uname call.
+type Platform struct {
+	// Vendor is the manufacturer of the system. It must be possible
+	// to crypographically verify the authenticity of the Vendor
+	// using the value of Vendor. The test must fail if the underlying
+	// hardware does not provide appropriate support for this.
+	// TODO(ashankar, ataly): provide an API for verifying vendor authenticity.
+	Vendor string
+
+	// AIKCertificate attests that the platform contains a TPM that is trusted
+	// and has valid EK (Endorsement Key) and platform credentials. The
+	// AIKCertificate is bound to an AIK public key (attestation identity key)
+	// that is secure on the TPM and can be used for signing operations.
+	// TODO(gauthamt): provide an implementation of how a device gets this
+	// certificate and how a remote process uses it to verify device identity.
+	AIKCertificate *security.Certificate
+
+	// Model is the model description, including version information.
+	Model string
+
+	// System is the name of the operating system.
+	// E.g. 'Linux', 'Darwin'
+	System string
+
+	// Release is the specific release of System if known, the empty
+	// string otherwise.
+	// E.g. 3.12.24-1-ARCH on a Raspberry pi running Arch Linux
+	Release string
+
+	// Version is the version of System if known, the empty string otherwise.
+	// E.g. #1 PREEMPT Thu Jul 10 23:57:15 MDT 2014 on a Raspberry Pi B
+	// running Arch Linux
+	Version string
+
+	// Machine is the hardware identifier
+	// E.g. armv6l on a Raspberry Pi B
+	Machine string
+
+	// Node is the name of the name of the node that the
+	// the platform is running on. This is not necessarily unique.
+	Node string
+}
+
+func (p *Platform) String() string {
+	return fmt.Sprintf("%s/%s node %s running %s (%s %s) on machine %s", p.Vendor, p.Model, p.Node, p.System, p.Release, p.Version, p.Machine)
+}
diff --git a/runtime/internal/platform/platform_darwin.go b/runtime/internal/platform/platform_darwin.go
new file mode 100644
index 0000000..227124a
--- /dev/null
+++ b/runtime/internal/platform/platform_darwin.go
@@ -0,0 +1,31 @@
+// 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 platform
+
+// #include <sys/utsname.h>
+// #include <errno.h>
+import "C"
+
+import "fmt"
+
+// GetPlatform returns the description of the Platform this process is running on.
+// A default value for Platform is provided even if an error is
+// returned; nil is never returned for the first return result.
+func GetPlatform() (*Platform, error) {
+	var t C.struct_utsname
+	if r, err := C.uname(&t); r != 0 {
+		return &Platform{}, fmt.Errorf("uname failed: errno %d", err)
+	}
+	d := &Platform{
+		Vendor:  "google",
+		Model:   "generic",
+		System:  C.GoString(&t.sysname[0]),
+		Version: C.GoString(&t.version[0]),
+		Release: C.GoString(&t.release[0]),
+		Machine: C.GoString(&t.machine[0]),
+		Node:    C.GoString(&t.nodename[0]),
+	}
+	return d, nil
+}
diff --git a/runtime/internal/platform/platform_linux.go b/runtime/internal/platform/platform_linux.go
new file mode 100644
index 0000000..49da97c
--- /dev/null
+++ b/runtime/internal/platform/platform_linux.go
@@ -0,0 +1,27 @@
+// 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 platform
+
+import "syscall"
+
+// GetPlatform returns the description of the Platform this process is running on.
+// A default value for Platform is provided even if an error is
+// returned; nil is never returned for the first return result.
+func GetPlatform() (*Platform, error) {
+	var uts syscall.Utsname
+	if err := syscall.Uname(&uts); err != nil {
+		return &Platform{}, err
+	}
+	d := &Platform{
+		Vendor:  "google",
+		Model:   "generic",
+		System:  utsStr(uts.Sysname[:]),
+		Version: utsStr(uts.Version[:]),
+		Release: utsStr(uts.Release[:]),
+		Machine: utsStr(uts.Machine[:]),
+		Node:    utsStr(uts.Nodename[:]),
+	}
+	return d, nil
+}
diff --git a/runtime/internal/platform/platform_nacl.go b/runtime/internal/platform/platform_nacl.go
new file mode 100644
index 0000000..afbdbea
--- /dev/null
+++ b/runtime/internal/platform/platform_nacl.go
@@ -0,0 +1,23 @@
+// 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 platform
+
+// GetPlatform returns the description of the Platform this process is running on.
+// A default value for Platform is provided even if an error is
+// returned; nil is never returned for the first return result.
+func GetPlatform() (*Platform, error) {
+	d := &Platform{
+		Vendor:  "google",
+		Model:   "generic",
+		System:  "nacl",
+		Version: "0",
+		Release: "0",
+		Machine: "0",
+		Node:    "0",
+	}
+	return d, nil
+}
diff --git a/runtime/internal/platform/uts_str_linux_arm.go b/runtime/internal/platform/uts_str_linux_arm.go
new file mode 100644
index 0000000..d33cb5e
--- /dev/null
+++ b/runtime/internal/platform/uts_str_linux_arm.go
@@ -0,0 +1,20 @@
+// 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 linux,arm
+
+package platform
+
+// str converts the input byte slice to a string, ignoring everything following
+// a null character (including the null character).
+func utsStr(c []uint8) string {
+	ret := make([]byte, 0, len(c))
+	for _, v := range c {
+		if v == 0 {
+			break
+		}
+		ret = append(ret, byte(v))
+	}
+	return string(ret)
+}
diff --git a/runtime/internal/platform/uts_str_linux_nonarm.go b/runtime/internal/platform/uts_str_linux_nonarm.go
new file mode 100644
index 0000000..1c7e345
--- /dev/null
+++ b/runtime/internal/platform/uts_str_linux_nonarm.go
@@ -0,0 +1,20 @@
+// 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 linux,!arm
+
+package platform
+
+// str converts the input byte slice to a string, ignoring everything following
+// a null character (including the null character).
+func utsStr(c []int8) string {
+	ret := make([]byte, 0, len(c))
+	for _, v := range c {
+		if v == 0 {
+			break
+		}
+		ret = append(ret, byte(v))
+	}
+	return string(ret)
+}
diff --git a/runtime/internal/rpc/benchmark/README.md b/runtime/internal/rpc/benchmark/README.md
new file mode 100644
index 0000000..dd398f5
--- /dev/null
+++ b/runtime/internal/rpc/benchmark/README.md
@@ -0,0 +1,128 @@
+This directory contains code uses to measure the performance of the Vanadium RPC stack.
+
+---
+
+This benchmarks use Go's testing package to run benchmarks. Each benchmark involves
+one server and one client. The server has two very simple methods that echo the data
+received from the client back to the client.
+
+* `client ---- Echo(payload) ----> server`
+* `client <--- return payload ---- server`
+
+There are two versions of the Echo method:
+
+* `Echo(payload []byte) ([]byte], error)`
+* `EchoStream() <[]byte,[]byte> error`
+
+# Microbenchmarks
+## `benchmark_test.go`
+
+The first benchmarks use the non-streaming version of Echo with a varying
+payload size. The second benchmarks use the streaming version with varying
+number of chunks and payload sizes. The third one is for measuring the
+performance with multiple clients hosted in the same process.
+
+This test creates a VC before the benchmark begins. So, the VC creation
+overhead is excluded.
+
+```
+$ v23 go test -bench=. -timeout=1h -cpu=1 -benchtime=5s v.io/x/ref/runtime/internal/rpc/benchmark
+PASS
+Benchmark____1B     1000           8301357 ns/op           0.00 MB/s
+--- Histogram (unit: ms)
+        Count: 1000  Min: 7  Max: 17  Avg: 7.89
+        ------------------------------------------------------------
+        [  7,   8)   505   50.5%   50.5%  #####
+        [  8,   9)   389   38.9%   89.4%  ####
+        [  9,  10)    38    3.8%   93.2%
+        [ 10,  11)    12    1.2%   94.4%
+        [ 11,  12)     4    0.4%   94.8%
+        [ 12,  14)    19    1.9%   96.7%
+        [ 14,  16)    23    2.3%   99.0%
+        [ 16,  18)    10    1.0%  100.0%
+        [ 18,  21)     0    0.0%  100.0%
+        [ 21,  24)     0    0.0%  100.0%
+        [ 24, inf)     0    0.0%  100.0%
+Benchmark___10B     1000           8587341 ns/op           0.00 MB/s
+...
+```
+
+`RESULTS.txt` has the full benchmark results.
+
+## `simple/main.go`
+
+`simple/main.go` is a simple command-line tool to run the main benchmarks to measure
+RPC setup time, latency, and throughput.
+
+```
+$ v23 go run simple/main.go
+RPC Connection  33.48 ms/rpc
+RPC (echo 1000B)  1.31 ms/rpc (763.05 qps)
+RPC Streaming (echo 1000B)  0.11 ms/rpc
+RPC Streaming Throughput (echo 1MB) 313.91 MB/s
+```
+
+# Client/Server
+## `{benchmark,benchmarkd}/main.go`
+
+`benchmarkd/main.go` and `benchmark/main.go` are simple command-line tools to run the
+benchmark server and client as separate processes. Unlike the benchmarks above,
+this test includes the startup cost of name resolution, creating the VC, etc. in
+the first RPC.
+
+```
+$ v23 go run benchmarkd/main.go \
+  -v23.tcp.address=localhost:8888 -v23.permissions.literal='{"Read": {"In": ["..."]}}'
+```
+
+(In a different shell)
+
+```
+$ v23 go run benchmark/main.go \
+  -server=/localhost:8888 -iterations=100 -chunk_count=0 -payload_size=10
+iterations: 100  chunk_count: 0  payload_size: 10
+elapsed time: 1.369034277s
+Histogram (unit: ms)
+Count: 100  Min: 7  Max: 94  Avg: 13.17
+------------------------------------------------------------
+[  7,   8)    1    1.0%    1.0%
+[  8,   9)    4    4.0%    5.0%
+[  9,  10)   17   17.0%   22.0%  ##
+[ 10,  12)   24   24.0%   46.0%  ##
+[ 12,  15)   24   24.0%   70.0%  ##
+[ 15,  19)   28   28.0%   98.0%  ###
+[ 19,  24)    1    1.0%   99.0%
+[ 24,  32)    0    0.0%   99.0%
+[ 32,  42)    0    0.0%   99.0%
+[ 42,  56)    0    0.0%   99.0%
+[ 56,  75)    0    0.0%   99.0%
+[ 75, 101)    1    1.0%  100.0%
+[101, 136)    0    0.0%  100.0%
+[136, 183)    0    0.0%  100.0%
+[183, 247)    0    0.0%  100.0%
+[247, 334)    0    0.0%  100.0%
+[334, inf)    0    0.0%  100.0%
+```
+
+# Raspberry Pi
+
+On a Raspberry Pi, everything is much slower. The same tests show the following
+results:
+
+```
+$ ./main
+RPC Connection  1765.47 ms/rpc
+RPC (echo 1000B)  78.61 ms/rpc (12.72 qps)
+RPC Streaming (echo 1000B)  23.85 ms/rpc
+RPC Streaming Throughput (echo 1MB) 0.92 MB/s
+```
+
+On a Raspberry Pi 2,
+
+```
+$ ./main
+RPC Connection  847.41 ms/rpc
+RPC (echo 1000B)  16.47 ms/rpc (60.71 qps)
+RPC Streaming (echo 1000B)  3.33 ms/rpc
+RPC Streaming Throughput (echo 1MB) 2.31 MB/s
+```
diff --git a/runtime/internal/rpc/benchmark/RESULTS.txt b/runtime/internal/rpc/benchmark/RESULTS.txt
new file mode 100644
index 0000000..6460076
--- /dev/null
+++ b/runtime/internal/rpc/benchmark/RESULTS.txt
@@ -0,0 +1,864 @@
+* 'Benchmark___NNB' shows the average time to execute a simple Echo RPC with a payload
+  of NN bytes.
+* 'Benchmark___CC_chunk____NNB' shows the average time to execute a streaming RPC with
+  a payload of CC chunks of NN bytes.
+* 'Benchmark__per_chunk___NNB' shows the average time to send one chunk of NN bytes.
+* 'Benchmark___NNB_mux___CC_chunks___MMB' shows the average time to execute a simple
+  Echo RPC with a payload of NN bytes while streaming payloads of CC chunks of MM bytes
+  continuously in the same process.
+
+================================================================================
+Date: 04/16/2015
+Platform: Intel(R) Xeon(R) CPU E5-2689 0 @ 2.60GHz,  66114888KB Memory
+
+Benchmark____1B-2	   10000	    805392 ns/op	   0.00 MB/s
+--- Histogram (unit: µs)
+	Count: 10000  Min: 693  Max: 2503  Avg: 804.62
+	------------------------------------------------------------
+	[ 693,  694)      2    0.0%    0.0%  
+	[ 694,  695)      1    0.0%    0.0%  
+	[ 695,  697)      2    0.0%    0.1%  
+	[ 697,  701)      2    0.0%    0.1%  
+	[ 701,  708)     25    0.2%    0.3%  
+	[ 708,  720)     91    0.9%    1.2%  
+	[ 720,  740)   1444   14.4%   15.7%  #
+	[ 740,  773)   3540   35.4%   51.1%  ####
+	[ 773,  827)   3820   38.2%   89.3%  ####
+	[ 827,  917)    534    5.3%   94.6%  #
+	[ 917, 1065)    135    1.4%   96.0%  
+	[1065, 1309)     43    0.4%   96.4%  
+	[1309, 1712)    307    3.1%   99.5%  
+	[1712, 2377)     53    0.5%  100.0%  
+	[2377, 3474)      1    0.0%  100.0%  
+	[3474, 5283)      0    0.0%  100.0%  
+	[5283,  inf)      0    0.0%  100.0%  
+Benchmark___10B-2	   10000	    808029 ns/op	   0.02 MB/s
+--- Histogram (unit: µs)
+	Count: 10000  Min: 695  Max: 2678  Avg: 807.25
+	------------------------------------------------------------
+	[ 695,  696)      2    0.0%    0.0%  
+	[ 696,  697)      1    0.0%    0.0%  
+	[ 697,  699)      3    0.0%    0.1%  
+	[ 699,  703)      5    0.1%    0.1%  
+	[ 703,  710)     13    0.1%    0.2%  
+	[ 710,  722)    101    1.0%    1.2%  
+	[ 722,  742)    616    6.2%    7.4%  #
+	[ 742,  776)   4487   44.9%   52.3%  ####
+	[ 776,  833)   4001   40.0%   92.3%  ####
+	[ 833,  928)    397    4.0%   96.3%  
+	[ 928, 1085)    138    1.4%   97.6%  
+	[1085, 1346)      3    0.0%   97.7%  
+	[1346, 1780)     43    0.4%   98.1%  
+	[1780, 2500)    186    1.9%  100.0%  
+	[2500, 3695)      4    0.0%  100.0%  
+	[3695, 5677)      0    0.0%  100.0%  
+	[5677,  inf)      0    0.0%  100.0%  
+Benchmark__100B-2	   10000	    821234 ns/op	   0.24 MB/s
+--- Histogram (unit: µs)
+	Count: 10000  Min: 703  Max: 3921  Avg: 820.44
+	------------------------------------------------------------
+	[ 703,  704)      1    0.0%    0.0%  
+	[ 704,  705)      1    0.0%    0.0%  
+	[ 705,  707)      0    0.0%    0.0%  
+	[ 707,  712)      3    0.0%    0.1%  
+	[ 712,  720)      7    0.1%    0.1%  
+	[ 720,  734)     46    0.5%    0.6%  
+	[ 734,  759)    995   10.0%   10.5%  #
+	[ 759,  802)   6544   65.4%   76.0%  #######
+	[ 802,  876)   1829   18.3%   94.3%  ##
+	[ 876, 1003)    391    3.9%   98.2%  
+	[1003, 1220)     15    0.1%   98.3%  
+	[1220, 1593)      2    0.0%   98.3%  
+	[1593, 2232)     16    0.2%   98.5%  
+	[2232, 3328)    148    1.5%  100.0%  
+	[3328, 5206)      2    0.0%  100.0%  
+	[5206, 8424)      0    0.0%  100.0%  
+	[8424,  inf)      0    0.0%  100.0%  
+Benchmark___1KB-2	   10000	    836132 ns/op	   2.39 MB/s
+--- Histogram (unit: µs)
+	Count: 10000  Min: 735  Max: 4663  Avg: 835.34
+	------------------------------------------------------------
+	[ 735,  736)      1    0.0%    0.0%  
+	[ 736,  737)      0    0.0%    0.0%  
+	[ 737,  740)      5    0.1%    0.1%  
+	[ 740,  745)     20    0.2%    0.3%  
+	[ 745,  754)    155    1.6%    1.8%  
+	[ 754,  769)   1175   11.8%   13.6%  #
+	[ 769,  796)   3923   39.2%   52.8%  ####
+	[ 796,  843)   3499   35.0%   87.8%  ###
+	[ 843,  925)    762    7.6%   95.4%  #
+	[ 925, 1068)    304    3.0%   98.4%  
+	[1068, 1316)     14    0.1%   98.6%  
+	[1316, 1748)      0    0.0%   98.6%  
+	[1748, 2498)      0    0.0%   98.6%  
+	[2498, 3801)    140    1.4%  100.0%  
+	[3801, 6063)      2    0.0%  100.0%  
+	[6063, 9991)      0    0.0%  100.0%  
+	[9991,  inf)      0    0.0%  100.0%  
+Benchmark__10KB-2	   10000	    922035 ns/op	  21.69 MB/s
+--- Histogram (unit: µs)
+	Count: 10000  Min: 773  Max: 18022  Avg: 921.25
+	------------------------------------------------------------
+	[  773,   774)      1    0.0%    0.0%  
+	[  774,   775)      1    0.0%    0.0%  
+	[  775,   778)      2    0.0%    0.0%  
+	[  778,   785)      4    0.0%    0.1%  
+	[  785,   798)     45    0.5%    0.5%  
+	[  798,   823)   1654   16.5%   17.1%  ##
+	[  823,   872)   6089   60.9%   78.0%  ######
+	[  872,   966)   1494   14.9%   92.9%  #
+	[  966,  1147)    486    4.9%   97.8%  
+	[ 1147,  1495)      4    0.0%   97.8%  
+	[ 1495,  2162)      0    0.0%   97.8%  
+	[ 2162,  3441)     18    0.2%   98.0%  
+	[ 3441,  5892)    201    2.0%  100.0%  
+	[ 5892, 10589)      0    0.0%  100.0%  
+	[10589, 19590)      1    0.0%  100.0%  
+	[19590, 36838)      0    0.0%  100.0%  
+	[36838,   inf)      0    0.0%  100.0%  
+Benchmark_100KB-2	    5000	   2039340 ns/op	  98.07 MB/s
+--- Histogram (unit: ms)
+	Count: 5000  Min: 1  Max: 10  Avg: 1.44
+	------------------------------------------------------------
+	[  1,   2)  4496   89.9%   89.9%  #########
+	[  2,   3)    66    1.3%   91.2%  
+	[  3,   4)     0    0.0%   91.2%  
+	[  4,   5)   119    2.4%   93.6%  
+	[  5,   6)   162    3.2%   96.9%  
+	[  6,   8)    26    0.5%   97.4%  
+	[  8,  10)   123    2.5%   99.8%  
+	[ 10,  12)     8    0.2%  100.0%  
+	[ 12,  15)     0    0.0%  100.0%  
+	[ 15, inf)     0    0.0%  100.0%  
+
+Benchmark____1_chunk_____1B-2	   10000	   1008412 ns/op	   0.00 MB/s
+--- Histogram (unit: µs)
+	Count: 10000  Min: 871  Max: 2447  Avg: 1007.61
+	------------------------------------------------------------
+	[ 871,  872)      1    0.0%    0.0%  
+	[ 872,  873)      0    0.0%    0.0%  
+	[ 873,  875)      0    0.0%    0.0%  
+	[ 875,  879)      1    0.0%    0.0%  
+	[ 879,  886)      1    0.0%    0.0%  
+	[ 886,  897)     13    0.1%    0.2%  
+	[ 897,  916)    130    1.3%    1.5%  
+	[ 916,  947)   3640   36.4%   37.9%  ####
+	[ 947,  997)   4612   46.1%   84.0%  #####
+	[ 997, 1079)    739    7.4%   91.4%  #
+	[1079, 1214)    304    3.0%   94.4%  
+	[1214, 1435)     37    0.4%   94.8%  
+	[1435, 1796)    291    2.9%   97.7%  
+	[1796, 2386)    230    2.3%  100.0%  
+	[2386, 3350)      1    0.0%  100.0%  
+	[3350, 4925)      0    0.0%  100.0%  
+	[4925,  inf)      0    0.0%  100.0%  
+Benchmark____1_chunk____10B-2	   10000	   1132272 ns/op	   0.02 MB/s
+--- Histogram (unit: µs)
+	Count: 10000  Min: 907  Max: 4069  Avg: 1131.39
+	------------------------------------------------------------
+	[ 907,  908)      1    0.0%    0.0%  
+	[ 908,  909)      0    0.0%    0.0%  
+	[ 909,  911)      1    0.0%    0.0%  
+	[ 911,  916)      5    0.1%    0.1%  
+	[ 916,  924)     69    0.7%    0.8%  
+	[ 924,  938)    516    5.2%    5.9%  #
+	[ 938,  963)   1376   13.8%   19.7%  #
+	[ 963, 1005)   1147   11.5%   31.2%  #
+	[1005, 1078)    791    7.9%   39.1%  #
+	[1078, 1203)   4877   48.8%   87.8%  #####
+	[1203, 1418)    884    8.8%   96.7%  #
+	[1418, 1786)     19    0.2%   96.9%  
+	[1786, 2416)    122    1.2%   98.1%  
+	[2416, 3495)    172    1.7%   99.8%  
+	[3495, 5342)     20    0.2%  100.0%  
+	[5342, 8503)      0    0.0%  100.0%  
+	[8503,  inf)      0    0.0%  100.0%  
+Benchmark____1_chunk___100B-2	   10000	   1183806 ns/op	   0.17 MB/s
+--- Histogram (unit: µs)
+	Count: 10000  Min: 909  Max: 6281  Avg: 1182.87
+	------------------------------------------------------------
+	[  909,   910)      1    0.0%    0.0%  
+	[  910,   911)      0    0.0%    0.0%  
+	[  911,   914)      0    0.0%    0.0%  
+	[  914,   919)      2    0.0%    0.0%  
+	[  919,   928)     30    0.3%    0.3%  
+	[  928,   945)    177    1.8%    2.1%  
+	[  945,   976)    422    4.2%    6.3%  
+	[  976,  1031)    390    3.9%   10.2%  
+	[ 1031,  1128)   3324   33.2%   43.5%  ###
+	[ 1128,  1301)   5231   52.3%   95.8%  #####
+	[ 1301,  1607)    198    2.0%   97.8%  
+	[ 1607,  2150)      0    0.0%   97.8%  
+	[ 2150,  3114)     96    1.0%   98.7%  
+	[ 3114,  4823)    111    1.1%   99.8%  
+	[ 4823,  7853)     18    0.2%  100.0%  
+	[ 7853, 13224)      0    0.0%  100.0%  
+	[13224,   inf)      0    0.0%  100.0%  
+Benchmark____1_chunk____1KB-2	   10000	   1176691 ns/op	   1.70 MB/s
+--- Histogram (unit: µs)
+	Count: 10000  Min: 901  Max: 8877  Avg: 1175.76
+	------------------------------------------------------------
+	[  901,   902)      1    0.0%    0.0%  
+	[  902,   903)      0    0.0%    0.0%  
+	[  903,   906)      0    0.0%    0.0%  
+	[  906,   912)      0    0.0%    0.0%  
+	[  912,   922)      0    0.0%    0.0%  
+	[  922,   941)     59    0.6%    0.6%  
+	[  941,   977)    562    5.6%    6.2%  #
+	[  977,  1043)    795    8.0%   14.2%  #
+	[ 1043,  1163)   5802   58.0%   72.2%  ######
+	[ 1163,  1382)   2543   25.4%   97.6%  ###
+	[ 1382,  1781)     54    0.5%   98.2%  
+	[ 1781,  2507)      0    0.0%   98.2%  
+	[ 2507,  3829)    123    1.2%   99.4%  
+	[ 3829,  6236)     56    0.6%  100.0%  
+	[ 6236, 10617)      5    0.1%  100.0%  
+	[10617, 18592)      0    0.0%  100.0%  
+	[18592,   inf)      0    0.0%  100.0%  
+Benchmark____1_chunk___10KB-2	    5000	   1209775 ns/op	  16.53 MB/s
+--- Histogram (unit: µs)
+	Count: 5000  Min: 931  Max: 6999  Avg: 1208.87
+	------------------------------------------------------------
+	[  931,   932)     1    0.0%    0.0%  
+	[  932,   933)     0    0.0%    0.0%  
+	[  933,   936)     1    0.0%    0.0%  
+	[  936,   941)     3    0.1%    0.1%  
+	[  941,   951)    23    0.5%    0.6%  
+	[  951,   969)   114    2.3%    2.8%  
+	[  969,  1001)   302    6.0%    8.9%  #
+	[ 1001,  1059)   872   17.4%   26.3%  ##
+	[ 1059,  1163)  2295   45.9%   72.2%  #####
+	[ 1163,  1349)  1104   22.1%   94.3%  ##
+	[ 1349,  1681)   158    3.2%   97.5%  
+	[ 1681,  2275)     0    0.0%   97.5%  
+	[ 2275,  3337)     0    0.0%   97.5%  
+	[ 3337,  5236)    79    1.6%   99.0%  
+	[ 5236,  8631)    48    1.0%  100.0%  
+	[ 8631, 14699)     0    0.0%  100.0%  
+	[14699,   inf)     0    0.0%  100.0%  
+Benchmark____1_chunk__100KB-2	    3000	   2075895 ns/op	  96.34 MB/s
+--- Histogram (unit: ms)
+	Count: 3000  Min: 1  Max: 7  Avg: 1.40
+	------------------------------------------------------------
+	[  1,   2)  2569   85.6%   85.6%  #########
+	[  2,   3)   155    5.2%   90.8%  #
+	[  3,   4)     0    0.0%   90.8%  
+	[  4,   5)   141    4.7%   95.5%  
+	[  5,   6)    74    2.5%   98.0%  
+	[  6,   7)    38    1.3%   99.2%  
+	[  7, inf)    23    0.8%  100.0%  
+Benchmark___10_chunk_____1B-2	    3000	   2076926 ns/op	   0.01 MB/s
+--- Histogram (unit: ms)
+	Count: 3000  Min: 1  Max: 5  Avg: 1.26
+	------------------------------------------------------------
+	[  1,   2)  2489   83.0%   83.0%  ########
+	[  2,   3)   416   13.9%   96.8%  #
+	[  3,   4)     0    0.0%   96.8%  
+	[  4,   5)     6    0.2%   97.0%  
+	[  5, inf)    89    3.0%  100.0%  
+Benchmark___10_chunk____10B-2	    3000	   2229984 ns/op	   0.09 MB/s
+--- Histogram (unit: ms)
+	Count: 3000  Min: 1  Max: 10  Avg: 1.75
+	------------------------------------------------------------
+	[  1,   2)  1141   38.0%   38.0%  ####
+	[  2,   3)  1758   58.6%   96.6%  ######
+	[  3,   4)    15    0.5%   97.1%  
+	[  4,   5)     0    0.0%   97.1%  
+	[  5,   6)    33    1.1%   98.2%  
+	[  6,   8)    30    1.0%   99.2%  
+	[  8,  10)    14    0.5%   99.7%  
+	[ 10,  12)     9    0.3%  100.0%  
+	[ 12,  15)     0    0.0%  100.0%  
+	[ 15, inf)     0    0.0%  100.0%  
+Benchmark___10_chunk___100B-2	    3000	   2348741 ns/op	   0.85 MB/s
+--- Histogram (unit: ms)
+	Count: 3000  Min: 1  Max: 12  Avg: 2.07
+	------------------------------------------------------------
+	[  1,   2)   278    9.3%    9.3%  #
+	[  2,   3)  2613   87.1%   96.4%  #########
+	[  3,   4)    29    1.0%   97.3%  
+	[  4,   5)     0    0.0%   97.3%  
+	[  5,   6)     0    0.0%   97.3%  
+	[  6,   8)    44    1.5%   98.8%  
+	[  8,  10)     9    0.3%   99.1%  
+	[ 10,  13)    27    0.9%  100.0%  
+	[ 13,  16)     0    0.0%  100.0%  
+	[ 16,  20)     0    0.0%  100.0%  
+	[ 20,  24)     0    0.0%  100.0%  
+	[ 24, inf)     0    0.0%  100.0%  
+Benchmark___10_chunk____1KB-2	    3000	   2426600 ns/op	   8.24 MB/s
+--- Histogram (unit: ms)
+	Count: 3000  Min: 1  Max: 11  Avg: 2.15
+	------------------------------------------------------------
+	[  1,   2)   119    4.0%    4.0%  
+	[  2,   3)  2730   91.0%   95.0%  #########
+	[  3,   4)    59    2.0%   96.9%  
+	[  4,   5)     0    0.0%   96.9%  
+	[  5,   6)     1    0.0%   97.0%  
+	[  6,   8)    55    1.8%   98.8%  
+	[  8,  10)    10    0.3%   99.1%  
+	[ 10,  12)    26    0.9%  100.0%  
+	[ 12,  15)     0    0.0%  100.0%  
+	[ 15,  18)     0    0.0%  100.0%  
+	[ 18, inf)     0    0.0%  100.0%  
+Benchmark___10_chunk___10KB-2	    2000	   3151285 ns/op	  63.47 MB/s
+--- Histogram (unit: ms)
+	Count: 2000  Min: 2  Max: 12  Avg: 2.51
+	------------------------------------------------------------
+	[  2,   3)  1760   88.0%   88.0%  #########
+	[  3,   4)    89    4.5%   92.5%  
+	[  4,   5)    29    1.5%   93.9%  
+	[  5,   6)     0    0.0%   93.9%  
+	[  6,   7)     6    0.3%   94.2%  
+	[  7,   9)    45    2.2%   96.5%  
+	[  9,  11)    29    1.5%   97.9%  
+	[ 11,  13)    42    2.1%  100.0%  
+	[ 13,  16)     0    0.0%  100.0%  
+	[ 16,  19)     0    0.0%  100.0%  
+	[ 19, inf)     0    0.0%  100.0%  
+Benchmark___10_chunk__100KB-2	    1000	   9469459 ns/op	 211.21 MB/s
+--- Histogram (unit: ms)
+	Count: 1000  Min: 6  Max: 14  Avg: 8.96
+	------------------------------------------------------------
+	[  6,   7)     1    0.1%    0.1%  
+	[  7,   8)   626   62.6%   62.7%  ######
+	[  8,   9)    52    5.2%   67.9%  #
+	[  9,  10)    13    1.3%   69.2%  
+	[ 10,  11)     2    0.2%   69.4%  
+	[ 11,  13)    58    5.8%   75.2%  #
+	[ 13,  15)   248   24.8%  100.0%  ##
+	[ 15,  17)     0    0.0%  100.0%  
+	[ 17, inf)     0    0.0%  100.0%  
+
+Benchmark__100_chunk_____1B-2	     500	  12427739 ns/op	   0.02 MB/s
+--- Histogram (unit: ms)
+	Count: 500  Min: 11  Max: 14  Avg: 11.96
+	------------------------------------------------------------
+	[ 11,  12)  124   24.8%   24.8%  ##
+	[ 12,  13)  279   55.8%   80.6%  ######
+	[ 13,  14)   88   17.6%   98.2%  ##
+	[ 14, inf)    9    1.8%  100.0%  
+Benchmark__100_chunk____10B-2	     500	  12206256 ns/op	   0.16 MB/s
+--- Histogram (unit: ms)
+	Count: 500  Min: 11  Max: 14  Avg: 11.73
+	------------------------------------------------------------
+	[ 11,  12)  189   37.8%   37.8%  ####
+	[ 12,  13)  261   52.2%   90.0%  #####
+	[ 13,  14)   48    9.6%   99.6%  #
+	[ 14, inf)    2    0.4%  100.0%  
+Benchmark__100_chunk___100B-2	     500	  12241029 ns/op	   1.63 MB/s
+--- Histogram (unit: ms)
+	Count: 500  Min: 11  Max: 14  Avg: 11.75
+	------------------------------------------------------------
+	[ 11,  12)  184   36.8%   36.8%  ####
+	[ 12,  13)  265   53.0%   89.8%  #####
+	[ 13,  14)   45    9.0%   98.8%  #
+	[ 14, inf)    6    1.2%  100.0%  
+Benchmark__100_chunk____1KB-2	     500	  13043623 ns/op	  15.33 MB/s
+--- Histogram (unit: ms)
+	Count: 500  Min: 11  Max: 33  Avg: 12.53
+	------------------------------------------------------------
+	[ 11,  12)    3    0.6%    0.6%  
+	[ 12,  13)  284   56.8%   57.4%  ######
+	[ 13,  14)  176   35.2%   92.6%  ####
+	[ 14,  15)   35    7.0%   99.6%  #
+	[ 15,  17)    1    0.2%   99.8%  
+	[ 17,  19)    0    0.0%   99.8%  
+	[ 19,  22)    0    0.0%   99.8%  
+	[ 22,  26)    0    0.0%   99.8%  
+	[ 26,  31)    0    0.0%   99.8%  
+	[ 31,  37)    1    0.2%  100.0%  
+	[ 37,  44)    0    0.0%  100.0%  
+	[ 44,  53)    0    0.0%  100.0%  
+	[ 53,  64)    0    0.0%  100.0%  
+	[ 64,  78)    0    0.0%  100.0%  
+	[ 78,  95)    0    0.0%  100.0%  
+	[ 95, 116)    0    0.0%  100.0%  
+	[116, inf)    0    0.0%  100.0%  
+Benchmark__100_chunk___10KB-2	     500	  18162047 ns/op	 110.12 MB/s
+--- Histogram (unit: ms)
+	Count: 500  Min: 16  Max: 21  Avg: 17.64
+	------------------------------------------------------------
+	[ 16,  17)   79   15.8%   15.8%  ##
+	[ 17,  18)  153   30.6%   46.4%  ###
+	[ 18,  19)  165   33.0%   79.4%  ###
+	[ 19,  20)   78   15.6%   95.0%  ##
+	[ 20,  21)   24    4.8%   99.8%  
+	[ 21, inf)    1    0.2%  100.0%  
+Benchmark__100_chunk__100KB-2	     100	  68538838 ns/op	 291.81 MB/s
+--- Histogram (unit: ms)
+	Count: 100  Min: 65  Max: 71  Avg: 68.06
+	------------------------------------------------------------
+	[ 65,  66)    6    6.0%    6.0%  #
+	[ 66,  67)    7    7.0%   13.0%  #
+	[ 67,  68)   15   15.0%   28.0%  ##
+	[ 68,  69)   35   35.0%   63.0%  ####
+	[ 69,  70)   26   26.0%   89.0%  ###
+	[ 70,  71)    6    6.0%   95.0%  #
+	[ 71, inf)    5    5.0%  100.0%  #
+Benchmark___1K_chunk_____1B-2	     100	 108491530 ns/op	   0.02 MB/s
+--- Histogram (unit: ms)
+	Count: 100  Min: 98  Max: 122  Avg: 107.97
+	------------------------------------------------------------
+	[ 98,  99)    1    1.0%    1.0%  
+	[ 99, 100)    1    1.0%    2.0%  
+	[100, 101)    2    2.0%    4.0%  
+	[101, 102)    4    4.0%    8.0%  
+	[102, 104)    6    6.0%   14.0%  #
+	[104, 106)   12   12.0%   26.0%  #
+	[106, 109)   28   28.0%   54.0%  ###
+	[109, 113)   32   32.0%   86.0%  ###
+	[113, 118)   13   13.0%   99.0%  #
+	[118, 124)    1    1.0%  100.0%  
+	[124, 132)    0    0.0%  100.0%  
+	[132, 142)    0    0.0%  100.0%  
+	[142, 154)    0    0.0%  100.0%  
+	[154, 169)    0    0.0%  100.0%  
+	[169, 188)    0    0.0%  100.0%  
+	[188, 212)    0    0.0%  100.0%  
+	[212, inf)    0    0.0%  100.0%  
+Benchmark___1K_chunk____10B-2	     100	 107576447 ns/op	   0.19 MB/s
+--- Histogram (unit: ms)
+	Count: 100  Min: 97  Max: 119  Avg: 107.04
+	------------------------------------------------------------
+	[ 97,  98)    1    1.0%    1.0%  
+	[ 98,  99)    1    1.0%    2.0%  
+	[ 99, 100)    4    4.0%    6.0%  
+	[100, 101)    1    1.0%    7.0%  
+	[101, 103)    6    6.0%   13.0%  #
+	[103, 105)   15   15.0%   28.0%  ##
+	[105, 108)   28   28.0%   56.0%  ###
+	[108, 112)   29   29.0%   85.0%  ###
+	[112, 117)   13   13.0%   98.0%  #
+	[117, 123)    2    2.0%  100.0%  
+	[123, 130)    0    0.0%  100.0%  
+	[130, 139)    0    0.0%  100.0%  
+	[139, 150)    0    0.0%  100.0%  
+	[150, 164)    0    0.0%  100.0%  
+	[164, 181)    0    0.0%  100.0%  
+	[181, 202)    0    0.0%  100.0%  
+	[202, inf)    0    0.0%  100.0%  
+Benchmark___1K_chunk___100B-2	     100	 108458019 ns/op	   1.84 MB/s
+--- Histogram (unit: ms)
+	Count: 100  Min: 98  Max: 117  Avg: 107.93
+	------------------------------------------------------------
+	[ 98,  99)    1    1.0%    1.0%  
+	[ 99, 100)    1    1.0%    2.0%  
+	[100, 101)    2    2.0%    4.0%  
+	[101, 102)    3    3.0%    7.0%  
+	[102, 104)    9    9.0%   16.0%  #
+	[104, 106)    8    8.0%   24.0%  #
+	[106, 109)   28   28.0%   52.0%  ###
+	[109, 112)   34   34.0%   86.0%  ###
+	[112, 116)   12   12.0%   98.0%  #
+	[116, 121)    2    2.0%  100.0%  
+	[121, 128)    0    0.0%  100.0%  
+	[128, 136)    0    0.0%  100.0%  
+	[136, 146)    0    0.0%  100.0%  
+	[146, 158)    0    0.0%  100.0%  
+	[158, 173)    0    0.0%  100.0%  
+	[173, 191)    0    0.0%  100.0%  
+	[191, inf)    0    0.0%  100.0%  
+Benchmark___1K_chunk____1KB-2	     100	 118334262 ns/op	  16.90 MB/s
+--- Histogram (unit: ms)
+	Count: 100  Min: 105  Max: 129  Avg: 117.82
+	------------------------------------------------------------
+	[105, 106)    2    2.0%    2.0%  
+	[106, 107)    3    3.0%    5.0%  
+	[107, 108)    3    3.0%    8.0%  
+	[108, 109)    3    3.0%   11.0%  
+	[109, 111)    3    3.0%   14.0%  
+	[111, 113)    2    2.0%   16.0%  
+	[113, 116)   10   10.0%   26.0%  #
+	[116, 120)   25   25.0%   51.0%  ###
+	[120, 125)   43   43.0%   94.0%  ####
+	[125, 131)    6    6.0%  100.0%  #
+	[131, 139)    0    0.0%  100.0%  
+	[139, 149)    0    0.0%  100.0%  
+	[149, 161)    0    0.0%  100.0%  
+	[161, 176)    0    0.0%  100.0%  
+	[176, 195)    0    0.0%  100.0%  
+	[195, 219)    0    0.0%  100.0%  
+	[219, inf)    0    0.0%  100.0%  
+Benchmark___1K_chunk___10KB-2	      50	 150361259 ns/op	 133.01 MB/s
+--- Histogram (unit: ms)
+	Count: 50  Min: 144  Max: 156  Avg: 149.92
+	------------------------------------------------------------
+	[144, 145)   3    6.0%    6.0%  #
+	[145, 146)   3    6.0%   12.0%  #
+	[146, 147)   4    8.0%   20.0%  #
+	[147, 148)   4    8.0%   28.0%  #
+	[148, 149)   4    8.0%   36.0%  #
+	[149, 151)   8   16.0%   52.0%  ##
+	[151, 153)  11   22.0%   74.0%  ##
+	[153, 156)  12   24.0%   98.0%  ##
+	[156, 159)   1    2.0%  100.0%  
+	[159, 163)   0    0.0%  100.0%  
+	[163, 168)   0    0.0%  100.0%  
+	[168, 174)   0    0.0%  100.0%  
+	[174, inf)   0    0.0%  100.0%  
+Benchmark___1K_chunk__100KB-2	      10	 691013740 ns/op	 289.43 MB/s
+--- Histogram (unit: ms)
+	Count: 10  Min: 683  Max: 699  Avg: 690.40
+	------------------------------------------------------------
+	[683, 684)   1   10.0%   10.0%  #
+	[684, 685)   0    0.0%   10.0%  
+	[685, 686)   1   10.0%   20.0%  #
+	[686, 687)   0    0.0%   20.0%  
+	[687, 689)   2   20.0%   40.0%  ##
+	[689, 691)   1   10.0%   50.0%  #
+	[691, 694)   3   30.0%   80.0%  ###
+	[694, 697)   1   10.0%   90.0%  #
+	[697, 701)   1   10.0%  100.0%  #
+	[701, 706)   0    0.0%  100.0%  
+	[706, 712)   0    0.0%  100.0%  
+	[712, 719)   0    0.0%  100.0%  
+	[719, 728)   0    0.0%  100.0%  
+	[728, 739)   0    0.0%  100.0%  
+	[739, 752)   0    0.0%  100.0%  
+	[752, 768)   0    0.0%  100.0%  
+	[768, inf)   0    0.0%  100.0%  
+
+Benchmark__per_chunk____1B-2	  100000	    113488 ns/op	   0.02 MB/s
+Benchmark__per_chunk___10B-2	  100000	    110432 ns/op	   0.18 MB/s
+Benchmark__per_chunk__100B-2	  100000	    109446 ns/op	   1.83 MB/s
+Benchmark__per_chunk___1KB-2	  100000	    118905 ns/op	  16.82 MB/s
+Benchmark__per_chunk__10KB-2	   50000	    165005 ns/op	 121.21 MB/s
+Benchmark__per_chunk_100KB-2	   10000	    672988 ns/op	 297.18 MB/s
+
+Benchmark___10B_mux__100_chunks___10B-2	    3000	   2096423 ns/op	   0.01 MB/s
+--- Histogram (unit: µs)
+	Count: 3000  Min: 853  Max: 7497  Avg: 2095.56
+	------------------------------------------------------------
+	[  853,   854)     1    0.0%    0.0%  
+	[  854,   855)     0    0.0%    0.0%  
+	[  855,   858)     0    0.0%    0.0%  
+	[  858,   863)     0    0.0%    0.0%  
+	[  863,   873)     0    0.0%    0.0%  
+	[  873,   891)     0    0.0%    0.0%  
+	[  891,   924)     3    0.1%    0.1%  
+	[  924,   984)    24    0.8%    0.9%  
+	[  984,  1093)   119    4.0%    4.9%  
+	[ 1093,  1289)   580   19.3%   24.2%  ##
+	[ 1289,  1642)  1010   33.7%   57.9%  ###
+	[ 1642,  2277)   488   16.3%   74.2%  ##
+	[ 2277,  3419)   271    9.0%   83.2%  #
+	[ 3419,  5473)   423   14.1%   97.3%  #
+	[ 5473,  9167)    81    2.7%  100.0%  
+	[ 9167, 15811)     0    0.0%  100.0%  
+	[15811,   inf)     0    0.0%  100.0%  
+Benchmark___10B_mux__100_chunks__100B-2	    3000	   2238813 ns/op	   0.01 MB/s
+--- Histogram (unit: µs)
+	Count: 3000  Min: 808  Max: 9123  Avg: 2237.91
+	------------------------------------------------------------
+	[  808,   809)     1    0.0%    0.0%  
+	[  809,   810)     0    0.0%    0.0%  
+	[  810,   813)     0    0.0%    0.0%  
+	[  813,   819)     0    0.0%    0.0%  
+	[  819,   830)     0    0.0%    0.0%  
+	[  830,   850)     0    0.0%    0.0%  
+	[  850,   886)     5    0.2%    0.2%  
+	[  886,   953)     9    0.3%    0.5%  
+	[  953,  1076)    67    2.2%    2.7%  
+	[ 1076,  1300)   493   16.4%   19.2%  ##
+	[ 1300,  1710)  1176   39.2%   58.4%  ####
+	[ 1710,  2459)   507   16.9%   75.3%  ##
+	[ 2459,  3826)   206    6.9%   82.1%  #
+	[ 3826,  6321)   467   15.6%   97.7%  ##
+	[ 6321, 10876)    69    2.3%  100.0%  
+	[10876, 19190)     0    0.0%  100.0%  
+	[19190,   inf)     0    0.0%  100.0%  
+Benchmark___10B_mux__100_chunks___1KB-2	    3000	   2578794 ns/op	   0.01 MB/s
+--- Histogram (unit: µs)
+	Count: 3000  Min: 856  Max: 10981  Avg: 2577.84
+	------------------------------------------------------------
+	[  856,   857)     1    0.0%    0.0%  
+	[  857,   858)     0    0.0%    0.0%  
+	[  858,   861)     0    0.0%    0.0%  
+	[  861,   867)     0    0.0%    0.0%  
+	[  867,   878)     0    0.0%    0.0%  
+	[  878,   899)     1    0.0%    0.1%  
+	[  899,   939)     1    0.0%    0.1%  
+	[  939,  1012)    13    0.4%    0.5%  
+	[ 1012,  1148)    69    2.3%    2.8%  
+	[ 1148,  1401)   392   13.1%   15.9%  #
+	[ 1401,  1869)  1141   38.0%   53.9%  ####
+	[ 1869,  2734)   603   20.1%   74.0%  ##
+	[ 2734,  4334)   228    7.6%   81.6%  #
+	[ 4334,  7294)   466   15.5%   97.2%  ##
+	[ 7294, 12768)    85    2.8%  100.0%  
+	[12768, 22893)     0    0.0%  100.0%  
+	[22893,   inf)     0    0.0%  100.0%  
+Benchmark___10B_mux__100_chunks__10KB-2	    2000	   5713209 ns/op	   0.00 MB/s
+--- Histogram (unit: µs)
+	Count: 2000  Min: 881  Max: 19620  Avg: 5711.99
+	------------------------------------------------------------
+	[  881,   882)     1    0.1%    0.1%  
+	[  882,   883)     0    0.0%    0.1%  
+	[  883,   886)     0    0.0%    0.1%  
+	[  886,   893)     0    0.0%    0.1%  
+	[  893,   906)     0    0.0%    0.1%  
+	[  906,   932)     2    0.1%    0.2%  
+	[  932,   983)     7    0.4%    0.5%  
+	[  983,  1081)    40    2.0%    2.5%  
+	[ 1081,  1271)   128    6.4%    8.9%  #
+	[ 1271,  1637)   217   10.9%   19.8%  #
+	[ 1637,  2342)   197    9.9%   29.6%  #
+	[ 2342,  3701)   393   19.7%   49.2%  ##
+	[ 3701,  6320)   314   15.7%   65.0%  ##
+	[ 6320, 11367)   391   19.6%   84.5%  ##
+	[11367, 21092)   310   15.5%  100.0%  ##
+	[21092, 39830)     0    0.0%  100.0%  
+	[39830,   inf)     0    0.0%  100.0%  
+Benchmark___10B_mux___1K_chunks___10B-2	    3000	   2563937 ns/op	   0.01 MB/s
+--- Histogram (unit: µs)
+	Count: 3000  Min: 963  Max: 42356  Avg: 2562.97
+	------------------------------------------------------------
+	[  963,   964)     2    0.1%    0.1%  
+	[  964,   966)     1    0.0%    0.1%  
+	[  966,   970)     2    0.1%    0.2%  
+	[  970,   978)     0    0.0%    0.2%  
+	[  978,   995)     2    0.1%    0.2%  
+	[  995,  1029)     8    0.3%    0.5%  
+	[ 1029,  1099)    61    2.0%    2.5%  
+	[ 1099,  1241)   302   10.1%   12.6%  #
+	[ 1241,  1530)  1053   35.1%   47.7%  ####
+	[ 1530,  2119)  1182   39.4%   87.1%  ####
+	[ 2119,  3315)   121    4.0%   91.1%  
+	[ 3315,  5745)    71    2.4%   93.5%  
+	[ 5745, 10682)   116    3.9%   97.4%  
+	[10682, 20712)    21    0.7%   98.1%  
+	[20712, 41088)    54    1.8%   99.9%  
+	[41088, 82480)     4    0.1%  100.0%  
+	[82480,   inf)     0    0.0%  100.0%  
+Benchmark___10B_mux___1K_chunks__100B-2	    3000	   2657054 ns/op	   0.01 MB/s
+--- Histogram (unit: µs)
+	Count: 3000  Min: 931  Max: 46417  Avg: 2656.13
+	------------------------------------------------------------
+	[  931,   932)     1    0.0%    0.0%  
+	[  932,   934)     0    0.0%    0.0%  
+	[  934,   938)     0    0.0%    0.0%  
+	[  938,   946)     0    0.0%    0.0%  
+	[  946,   963)     0    0.0%    0.0%  
+	[  963,   998)     7    0.2%    0.3%  
+	[  998,  1070)    40    1.3%    1.6%  
+	[ 1070,  1219)   247    8.2%    9.8%  #
+	[ 1219,  1523)  1090   36.3%   46.2%  ####
+	[ 1523,  2146)  1210   40.3%   86.5%  ####
+	[ 2146,  3420)   129    4.3%   90.8%  
+	[ 3420,  6024)    74    2.5%   93.3%  
+	[ 6024, 11348)   130    4.3%   97.6%  
+	[11348, 22232)    14    0.5%   98.1%  
+	[22232, 44483)    57    1.9%  100.0%  
+	[44483, 89969)     1    0.0%  100.0%  
+	[89969,   inf)     0    0.0%  100.0%  
+Benchmark___10B_mux___1K_chunks___1KB-2	    2000	   2880796 ns/op	   0.01 MB/s
+--- Histogram (unit: µs)
+	Count: 2000  Min: 996  Max: 48649  Avg: 2879.79
+	------------------------------------------------------------
+	[  996,   997)     1    0.1%    0.1%  
+	[  997,   999)     0    0.0%    0.1%  
+	[  999,  1003)     0    0.0%    0.1%  
+	[ 1003,  1011)     0    0.0%    0.1%  
+	[ 1011,  1028)     0    0.0%    0.1%  
+	[ 1028,  1064)     0    0.0%    0.1%  
+	[ 1064,  1138)    17    0.9%    0.9%  
+	[ 1138,  1290)   126    6.3%    7.2%  #
+	[ 1290,  1602)   685   34.2%   41.5%  ###
+	[ 1602,  2242)   850   42.5%   84.0%  ####
+	[ 2242,  3556)   145    7.2%   91.2%  #
+	[ 3556,  6251)    42    2.1%   93.3%  
+	[ 6251, 11777)    84    4.2%   97.5%  
+	[11777, 23109)    10    0.5%   98.0%  
+	[23109, 46348)    37    1.9%   99.9%  
+	[46348, 94001)     3    0.2%  100.0%  
+	[94001,   inf)     0    0.0%  100.0%  
+Benchmark___10B_mux___1K_chunks__10KB-2	     300	  20013539 ns/op	   0.00 MB/s
+--- Histogram (unit: ms)
+	Count: 300  Min: 1  Max: 68  Avg: 19.55
+	------------------------------------------------------------
+	[  1,   2)   47   15.7%   15.7%  ##
+	[  2,   3)   20    6.7%   22.3%  #
+	[  3,   4)   17    5.7%   28.0%  #
+	[  4,   6)   11    3.7%   31.7%  
+	[  6,   9)   12    4.0%   35.7%  
+	[  9,  13)   22    7.3%   43.0%  #
+	[ 13,  18)   20    6.7%   49.7%  #
+	[ 18,  25)   34   11.3%   61.0%  #
+	[ 25,  34)   52   17.3%   78.3%  ##
+	[ 34,  46)   43   14.3%   92.7%  #
+	[ 46,  62)   21    7.0%   99.7%  #
+	[ 62,  83)    1    0.3%  100.0%  
+	[ 83, 111)    0    0.0%  100.0%  
+	[111, 149)    0    0.0%  100.0%  
+	[149, 199)    0    0.0%  100.0%  
+	[199, 265)    0    0.0%  100.0%  
+	[265, inf)    0    0.0%  100.0%  
+Benchmark___1KB_mux__100_chunks___10B-2	    3000	   2184759 ns/op	   0.92 MB/s
+--- Histogram (unit: µs)
+	Count: 3000  Min: 818  Max: 14154  Avg: 2183.87
+	------------------------------------------------------------
+	[  818,   819)     1    0.0%    0.0%  
+	[  819,   820)     0    0.0%    0.0%  
+	[  820,   823)     0    0.0%    0.0%  
+	[  823,   829)     0    0.0%    0.0%  
+	[  829,   841)     1    0.0%    0.1%  
+	[  841,   864)     0    0.0%    0.1%  
+	[  864,   908)     3    0.1%    0.2%  
+	[  908,   992)    16    0.5%    0.7%  
+	[  992,  1150)   179    6.0%    6.7%  #
+	[ 1150,  1448)   869   29.0%   35.6%  ###
+	[ 1448,  2010)  1256   41.9%   77.5%  ####
+	[ 2010,  3069)   149    5.0%   82.5%  
+	[ 3069,  5064)   380   12.7%   95.1%  #
+	[ 5064,  8822)    91    3.0%   98.2%  
+	[ 8822, 15901)    55    1.8%  100.0%  
+	[15901, 29236)     0    0.0%  100.0%  
+	[29236,   inf)     0    0.0%  100.0%  
+Benchmark___1KB_mux__100_chunks__100B-2	    3000	   2260787 ns/op	   0.88 MB/s
+--- Histogram (unit: µs)
+	Count: 3000  Min: 919  Max: 16047  Avg: 2259.90
+	------------------------------------------------------------
+	[  919,   920)     1    0.0%    0.0%  
+	[  920,   921)     0    0.0%    0.0%  
+	[  921,   924)     0    0.0%    0.0%  
+	[  924,   930)     0    0.0%    0.0%  
+	[  930,   943)     1    0.0%    0.1%  
+	[  943,   967)     3    0.1%    0.2%  
+	[  967,  1013)    10    0.3%    0.5%  
+	[ 1013,  1102)    62    2.1%    2.6%  
+	[ 1102,  1271)   287    9.6%   12.1%  #
+	[ 1271,  1593)  1026   34.2%   46.3%  ###
+	[ 1593,  2204)   980   32.7%   79.0%  ###
+	[ 2204,  3365)    98    3.3%   82.3%  
+	[ 3365,  5572)   411   13.7%   96.0%  #
+	[ 5572,  9764)    80    2.7%   98.6%  
+	[ 9764, 17727)    41    1.4%  100.0%  
+	[17727, 32854)     0    0.0%  100.0%  
+	[32854,   inf)     0    0.0%  100.0%  
+Benchmark___1KB_mux__100_chunks___1KB-2	    3000	   2484297 ns/op	   0.81 MB/s
+--- Histogram (unit: µs)
+	Count: 3000  Min: 909  Max: 17797  Avg: 2483.36
+	------------------------------------------------------------
+	[  909,   910)     1    0.0%    0.0%  
+	[  910,   911)     0    0.0%    0.0%  
+	[  911,   914)     0    0.0%    0.0%  
+	[  914,   921)     0    0.0%    0.0%  
+	[  921,   934)     0    0.0%    0.0%  
+	[  934,   959)     2    0.1%    0.1%  
+	[  959,  1008)    17    0.6%    0.7%  
+	[ 1008,  1101)    42    1.4%    2.1%  
+	[ 1101,  1280)   219    7.3%    9.4%  #
+	[ 1280,  1623)   904   30.1%   39.5%  ###
+	[ 1623,  2281)  1155   38.5%   78.0%  ####
+	[ 2281,  3540)   131    4.4%   82.4%  
+	[ 3540,  5950)   396   13.2%   95.6%  #
+	[ 5950, 10562)    64    2.1%   97.7%  
+	[10562, 19387)    69    2.3%  100.0%  
+	[19387, 36275)     0    0.0%  100.0%  
+	[36275,   inf)     0    0.0%  100.0%  
+Benchmark___1KB_mux__100_chunks__10KB-2	    1000	   5790870 ns/op	   0.35 MB/s
+--- Histogram (unit: µs)
+	Count: 1000  Min: 948  Max: 23009  Avg: 5789.90
+	------------------------------------------------------------
+	[  948,   949)     1    0.1%    0.1%  
+	[  949,   950)     0    0.0%    0.1%  
+	[  950,   953)     0    0.0%    0.1%  
+	[  953,   960)     2    0.2%    0.3%  
+	[  960,   974)     0    0.0%    0.3%  
+	[  974,  1002)     2    0.2%    0.5%  
+	[ 1002,  1056)     5    0.5%    1.0%  
+	[ 1056,  1162)    23    2.3%    3.3%  
+	[ 1162,  1369)    49    4.9%    8.2%  
+	[ 1369,  1772)    70    7.0%   15.2%  #
+	[ 1772,  2558)   132   13.2%   28.4%  #
+	[ 2558,  4090)   236   23.6%   52.0%  ##
+	[ 4090,  7074)   123   12.3%   64.3%  #
+	[ 7074, 12888)   290   29.0%   93.3%  ###
+	[12888, 24213)    67    6.7%  100.0%  #
+	[24213, 46274)     0    0.0%  100.0%  
+	[46274,   inf)     0    0.0%  100.0%  
+Benchmark___1KB_mux___1K_chunks___10B-2	    3000	   2732019 ns/op	   0.73 MB/s
+--- Histogram (unit: µs)
+	Count: 3000  Min: 960  Max: 49925  Avg: 2731.11
+	------------------------------------------------------------
+	[  960,   961)     1    0.0%    0.0%  
+	[  961,   963)     0    0.0%    0.0%  
+	[  963,   967)     1    0.0%    0.1%  
+	[  967,   975)     1    0.0%    0.1%  
+	[  975,   992)     2    0.1%    0.2%  
+	[  992,  1028)    11    0.4%    0.5%  
+	[ 1028,  1103)    38    1.3%    1.8%  
+	[ 1103,  1257)   295    9.8%   11.6%  #
+	[ 1257,  1574)  1111   37.0%   48.7%  ####
+	[ 1574,  2225)  1209   40.3%   89.0%  ####
+	[ 2225,  3563)    84    2.8%   91.8%  
+	[ 3563,  6312)    54    1.8%   93.6%  
+	[ 6312, 11960)    61    2.0%   95.6%  
+	[11960, 23562)    76    2.5%   98.1%  
+	[23562, 47397)    53    1.8%   99.9%  
+	[47397, 96362)     3    0.1%  100.0%  
+	[96362,   inf)     0    0.0%  100.0%  
+Benchmark___1KB_mux___1K_chunks__100B-2	    2000	   2642355 ns/op	   0.76 MB/s
+--- Histogram (unit: µs)
+	Count: 2000  Min: 964  Max: 47935  Avg: 2641.43
+	------------------------------------------------------------
+	[  964,   965)     1    0.1%    0.1%  
+	[  965,   967)     0    0.0%    0.1%  
+	[  967,   971)     0    0.0%    0.1%  
+	[  971,   979)     0    0.0%    0.1%  
+	[  979,   996)     1    0.1%    0.1%  
+	[  996,  1032)     3    0.2%    0.2%  
+	[ 1032,  1105)    22    1.1%    1.4%  
+	[ 1105,  1256)   214   10.7%   12.1%  #
+	[ 1256,  1566)   733   36.6%   48.7%  ####
+	[ 1566,  2201)   818   40.9%   89.6%  ####
+	[ 2201,  3502)    73    3.7%   93.2%  
+	[ 3502,  6168)    21    1.1%   94.3%  
+	[ 6168, 11631)    35    1.8%   96.1%  
+	[11631, 22823)    42    2.1%   98.2%  
+	[22823, 45751)    35    1.8%   99.9%  
+	[45751, 92722)     2    0.1%  100.0%  
+	[92722,   inf)     0    0.0%  100.0%  
+Benchmark___1KB_mux___1K_chunks___1KB-2	    2000	   3128442 ns/op	   0.64 MB/s
+--- Histogram (unit: ms)
+	Count: 2000  Min: 1  Max: 54  Avg: 2.58
+	------------------------------------------------------------
+	[  1,   2)  1494   74.7%   74.7%  #######
+	[  2,   3)   330   16.5%   91.2%  ##
+	[  3,   4)    18    0.9%   92.1%  
+	[  4,   6)    19    1.0%   93.1%  
+	[  6,   8)    12    0.6%   93.7%  
+	[  8,  11)    17    0.9%   94.5%  
+	[ 11,  15)    33    1.7%   96.2%  
+	[ 15,  21)    30    1.5%   97.7%  
+	[ 21,  29)     9    0.5%   98.1%  
+	[ 29,  39)    17    0.9%   99.0%  
+	[ 39,  53)    20    1.0%  100.0%  
+	[ 53,  71)     1    0.1%  100.0%  
+	[ 71,  94)     0    0.0%  100.0%  
+	[ 94, 125)     0    0.0%  100.0%  
+	[125, 165)     0    0.0%  100.0%  
+	[165, 218)     0    0.0%  100.0%  
+	[218, inf)     0    0.0%  100.0%  
+Benchmark___1KB_mux___1K_chunks__10KB-2	     300	  21164935 ns/op	   0.09 MB/s
+--- Histogram (unit: µs)
+	Count: 300  Min: 973  Max: 67388  Avg: 21163.92
+	------------------------------------------------------------
+	[   973,    974)    1    0.3%    0.3%  
+	[   974,    976)    0    0.0%    0.3%  
+	[   976,    980)    1    0.3%    0.7%  
+	[   980,    989)    0    0.0%    0.7%  
+	[   989,   1008)    0    0.0%    0.7%  
+	[  1008,   1048)    0    0.0%    0.7%  
+	[  1048,   1132)    2    0.7%    1.3%  
+	[  1132,   1309)    7    2.3%    3.7%  
+	[  1309,   1682)   14    4.7%    8.3%  
+	[  1682,   2464)   15    5.0%   13.3%  #
+	[  2464,   4104)   28    9.3%   22.7%  #
+	[  4104,   7542)   25    8.3%   31.0%  #
+	[  7542,  14749)   37   12.3%   43.3%  #
+	[ 14749,  29860)   72   24.0%   67.3%  ##
+	[ 29860,  61539)   95   31.7%   99.0%  ###
+	[ 61539, 127953)    3    1.0%  100.0%  
+	[127953,    inf)    0    0.0%  100.0%  
diff --git a/runtime/internal/rpc/benchmark/benchmark.vdl b/runtime/internal/rpc/benchmark/benchmark.vdl
new file mode 100644
index 0000000..c3aacaa
--- /dev/null
+++ b/runtime/internal/rpc/benchmark/benchmark.vdl
@@ -0,0 +1,18 @@
+// 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 benchmark provides simple tools to measure the performance of the
+// IPC system.
+package benchmark
+
+import (
+	"v.io/v23/security/access"
+)
+
+type Benchmark interface {
+  // Echo returns the payload that it receives.
+  Echo(Payload []byte) ([]byte | error) {access.Read}
+  // EchoStream returns the payload that it receives via the stream.
+  EchoStream() stream<[]byte,[]byte> error {access.Read}
+}
diff --git a/runtime/internal/rpc/benchmark/benchmark.vdl.go b/runtime/internal/rpc/benchmark/benchmark.vdl.go
new file mode 100644
index 0000000..2a63c41
--- /dev/null
+++ b/runtime/internal/rpc/benchmark/benchmark.vdl.go
@@ -0,0 +1,338 @@
+// 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 by the vanadium vdl tool.
+// Source: benchmark.vdl
+
+// package benchmark provides simple tools to measure the performance of the
+// IPC system.
+package benchmark
+
+import (
+	// VDL system imports
+	"io"
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/vdl"
+
+	// VDL user imports
+	"v.io/v23/security/access"
+)
+
+// BenchmarkClientMethods is the client interface
+// containing Benchmark methods.
+type BenchmarkClientMethods interface {
+	// Echo returns the payload that it receives.
+	Echo(ctx *context.T, Payload []byte, opts ...rpc.CallOpt) ([]byte, error)
+	// EchoStream returns the payload that it receives via the stream.
+	EchoStream(*context.T, ...rpc.CallOpt) (BenchmarkEchoStreamClientCall, error)
+}
+
+// BenchmarkClientStub adds universal methods to BenchmarkClientMethods.
+type BenchmarkClientStub interface {
+	BenchmarkClientMethods
+	rpc.UniversalServiceMethods
+}
+
+// BenchmarkClient returns a client stub for Benchmark.
+func BenchmarkClient(name string) BenchmarkClientStub {
+	return implBenchmarkClientStub{name}
+}
+
+type implBenchmarkClientStub struct {
+	name string
+}
+
+func (c implBenchmarkClientStub) Echo(ctx *context.T, i0 []byte, opts ...rpc.CallOpt) (o0 []byte, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "Echo", []interface{}{i0}, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implBenchmarkClientStub) EchoStream(ctx *context.T, opts ...rpc.CallOpt) (ocall BenchmarkEchoStreamClientCall, err error) {
+	var call rpc.ClientCall
+	if call, err = v23.GetClient(ctx).StartCall(ctx, c.name, "EchoStream", nil, opts...); err != nil {
+		return
+	}
+	ocall = &implBenchmarkEchoStreamClientCall{ClientCall: call}
+	return
+}
+
+// BenchmarkEchoStreamClientStream is the client stream for Benchmark.EchoStream.
+type BenchmarkEchoStreamClientStream interface {
+	// RecvStream returns the receiver side of the Benchmark.EchoStream client stream.
+	RecvStream() interface {
+		// Advance stages an item so that it may be retrieved via Value.  Returns
+		// true iff there is an item to retrieve.  Advance must be called before
+		// Value is called.  May block if an item is not available.
+		Advance() bool
+		// Value returns the item that was staged by Advance.  May panic if Advance
+		// returned false or was not called.  Never blocks.
+		Value() []byte
+		// Err returns any error encountered by Advance.  Never blocks.
+		Err() error
+	}
+	// SendStream returns the send side of the Benchmark.EchoStream client stream.
+	SendStream() interface {
+		// Send places the item onto the output stream.  Returns errors
+		// encountered while sending, or if Send is called after Close or
+		// the stream has been canceled.  Blocks if there is no buffer
+		// space; will unblock when buffer space is available or after
+		// the stream has been canceled.
+		Send(item []byte) error
+		// Close indicates to the server that no more items will be sent;
+		// server Recv calls will receive io.EOF after all sent items.
+		// This is an optional call - e.g. a client might call Close if it
+		// needs to continue receiving items from the server after it's
+		// done sending.  Returns errors encountered while closing, or if
+		// Close is called after the stream has been canceled.  Like Send,
+		// blocks if there is no buffer space available.
+		Close() error
+	}
+}
+
+// BenchmarkEchoStreamClientCall represents the call returned from Benchmark.EchoStream.
+type BenchmarkEchoStreamClientCall interface {
+	BenchmarkEchoStreamClientStream
+	// Finish performs the equivalent of SendStream().Close, then blocks until
+	// the server is done, and returns the positional return values for the call.
+	//
+	// Finish returns immediately if the call has been canceled; depending on the
+	// timing the output could either be an error signaling cancelation, or the
+	// valid positional return values from the server.
+	//
+	// Calling Finish is mandatory for releasing stream resources, unless the call
+	// has been canceled or any of the other methods return an error.  Finish should
+	// be called at most once.
+	Finish() error
+}
+
+type implBenchmarkEchoStreamClientCall struct {
+	rpc.ClientCall
+	valRecv []byte
+	errRecv error
+}
+
+func (c *implBenchmarkEchoStreamClientCall) RecvStream() interface {
+	Advance() bool
+	Value() []byte
+	Err() error
+} {
+	return implBenchmarkEchoStreamClientCallRecv{c}
+}
+
+type implBenchmarkEchoStreamClientCallRecv struct {
+	c *implBenchmarkEchoStreamClientCall
+}
+
+func (c implBenchmarkEchoStreamClientCallRecv) Advance() bool {
+	c.c.errRecv = c.c.Recv(&c.c.valRecv)
+	return c.c.errRecv == nil
+}
+func (c implBenchmarkEchoStreamClientCallRecv) Value() []byte {
+	return c.c.valRecv
+}
+func (c implBenchmarkEchoStreamClientCallRecv) Err() error {
+	if c.c.errRecv == io.EOF {
+		return nil
+	}
+	return c.c.errRecv
+}
+func (c *implBenchmarkEchoStreamClientCall) SendStream() interface {
+	Send(item []byte) error
+	Close() error
+} {
+	return implBenchmarkEchoStreamClientCallSend{c}
+}
+
+type implBenchmarkEchoStreamClientCallSend struct {
+	c *implBenchmarkEchoStreamClientCall
+}
+
+func (c implBenchmarkEchoStreamClientCallSend) Send(item []byte) error {
+	return c.c.Send(item)
+}
+func (c implBenchmarkEchoStreamClientCallSend) Close() error {
+	return c.c.CloseSend()
+}
+func (c *implBenchmarkEchoStreamClientCall) Finish() (err error) {
+	err = c.ClientCall.Finish()
+	return
+}
+
+// BenchmarkServerMethods is the interface a server writer
+// implements for Benchmark.
+type BenchmarkServerMethods interface {
+	// Echo returns the payload that it receives.
+	Echo(ctx *context.T, call rpc.ServerCall, Payload []byte) ([]byte, error)
+	// EchoStream returns the payload that it receives via the stream.
+	EchoStream(*context.T, BenchmarkEchoStreamServerCall) error
+}
+
+// BenchmarkServerStubMethods is the server interface containing
+// Benchmark methods, as expected by rpc.Server.
+// The only difference between this interface and BenchmarkServerMethods
+// is the streaming methods.
+type BenchmarkServerStubMethods interface {
+	// Echo returns the payload that it receives.
+	Echo(ctx *context.T, call rpc.ServerCall, Payload []byte) ([]byte, error)
+	// EchoStream returns the payload that it receives via the stream.
+	EchoStream(*context.T, *BenchmarkEchoStreamServerCallStub) error
+}
+
+// BenchmarkServerStub adds universal methods to BenchmarkServerStubMethods.
+type BenchmarkServerStub interface {
+	BenchmarkServerStubMethods
+	// Describe the Benchmark interfaces.
+	Describe__() []rpc.InterfaceDesc
+}
+
+// BenchmarkServer returns a server stub for Benchmark.
+// It converts an implementation of BenchmarkServerMethods into
+// an object that may be used by rpc.Server.
+func BenchmarkServer(impl BenchmarkServerMethods) BenchmarkServerStub {
+	stub := implBenchmarkServerStub{
+		impl: impl,
+	}
+	// Initialize GlobState; always check the stub itself first, to handle the
+	// case where the user has the Glob method defined in their VDL source.
+	if gs := rpc.NewGlobState(stub); gs != nil {
+		stub.gs = gs
+	} else if gs := rpc.NewGlobState(impl); gs != nil {
+		stub.gs = gs
+	}
+	return stub
+}
+
+type implBenchmarkServerStub struct {
+	impl BenchmarkServerMethods
+	gs   *rpc.GlobState
+}
+
+func (s implBenchmarkServerStub) Echo(ctx *context.T, call rpc.ServerCall, i0 []byte) ([]byte, error) {
+	return s.impl.Echo(ctx, call, i0)
+}
+
+func (s implBenchmarkServerStub) EchoStream(ctx *context.T, call *BenchmarkEchoStreamServerCallStub) error {
+	return s.impl.EchoStream(ctx, call)
+}
+
+func (s implBenchmarkServerStub) Globber() *rpc.GlobState {
+	return s.gs
+}
+
+func (s implBenchmarkServerStub) Describe__() []rpc.InterfaceDesc {
+	return []rpc.InterfaceDesc{BenchmarkDesc}
+}
+
+// BenchmarkDesc describes the Benchmark interface.
+var BenchmarkDesc rpc.InterfaceDesc = descBenchmark
+
+// descBenchmark hides the desc to keep godoc clean.
+var descBenchmark = rpc.InterfaceDesc{
+	Name:    "Benchmark",
+	PkgPath: "v.io/x/ref/runtime/internal/rpc/benchmark",
+	Methods: []rpc.MethodDesc{
+		{
+			Name: "Echo",
+			Doc:  "// Echo returns the payload that it receives.",
+			InArgs: []rpc.ArgDesc{
+				{"Payload", ``}, // []byte
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"", ``}, // []byte
+			},
+			Tags: []*vdl.Value{vdl.ValueOf(access.Tag("Read"))},
+		},
+		{
+			Name: "EchoStream",
+			Doc:  "// EchoStream returns the payload that it receives via the stream.",
+			Tags: []*vdl.Value{vdl.ValueOf(access.Tag("Read"))},
+		},
+	},
+}
+
+// BenchmarkEchoStreamServerStream is the server stream for Benchmark.EchoStream.
+type BenchmarkEchoStreamServerStream interface {
+	// RecvStream returns the receiver side of the Benchmark.EchoStream server stream.
+	RecvStream() interface {
+		// Advance stages an item so that it may be retrieved via Value.  Returns
+		// true iff there is an item to retrieve.  Advance must be called before
+		// Value is called.  May block if an item is not available.
+		Advance() bool
+		// Value returns the item that was staged by Advance.  May panic if Advance
+		// returned false or was not called.  Never blocks.
+		Value() []byte
+		// Err returns any error encountered by Advance.  Never blocks.
+		Err() error
+	}
+	// SendStream returns the send side of the Benchmark.EchoStream server stream.
+	SendStream() interface {
+		// Send places the item onto the output stream.  Returns errors encountered
+		// while sending.  Blocks if there is no buffer space; will unblock when
+		// buffer space is available.
+		Send(item []byte) error
+	}
+}
+
+// BenchmarkEchoStreamServerCall represents the context passed to Benchmark.EchoStream.
+type BenchmarkEchoStreamServerCall interface {
+	rpc.ServerCall
+	BenchmarkEchoStreamServerStream
+}
+
+// BenchmarkEchoStreamServerCallStub is a wrapper that converts rpc.StreamServerCall into
+// a typesafe stub that implements BenchmarkEchoStreamServerCall.
+type BenchmarkEchoStreamServerCallStub struct {
+	rpc.StreamServerCall
+	valRecv []byte
+	errRecv error
+}
+
+// Init initializes BenchmarkEchoStreamServerCallStub from rpc.StreamServerCall.
+func (s *BenchmarkEchoStreamServerCallStub) Init(call rpc.StreamServerCall) {
+	s.StreamServerCall = call
+}
+
+// RecvStream returns the receiver side of the Benchmark.EchoStream server stream.
+func (s *BenchmarkEchoStreamServerCallStub) RecvStream() interface {
+	Advance() bool
+	Value() []byte
+	Err() error
+} {
+	return implBenchmarkEchoStreamServerCallRecv{s}
+}
+
+type implBenchmarkEchoStreamServerCallRecv struct {
+	s *BenchmarkEchoStreamServerCallStub
+}
+
+func (s implBenchmarkEchoStreamServerCallRecv) Advance() bool {
+	s.s.errRecv = s.s.Recv(&s.s.valRecv)
+	return s.s.errRecv == nil
+}
+func (s implBenchmarkEchoStreamServerCallRecv) Value() []byte {
+	return s.s.valRecv
+}
+func (s implBenchmarkEchoStreamServerCallRecv) Err() error {
+	if s.s.errRecv == io.EOF {
+		return nil
+	}
+	return s.s.errRecv
+}
+
+// SendStream returns the send side of the Benchmark.EchoStream server stream.
+func (s *BenchmarkEchoStreamServerCallStub) SendStream() interface {
+	Send(item []byte) error
+} {
+	return implBenchmarkEchoStreamServerCallSend{s}
+}
+
+type implBenchmarkEchoStreamServerCallSend struct {
+	s *BenchmarkEchoStreamServerCallStub
+}
+
+func (s implBenchmarkEchoStreamServerCallSend) Send(item []byte) error {
+	return s.s.Send(item)
+}
diff --git a/runtime/internal/rpc/benchmark/benchmark/doc.go b/runtime/internal/rpc/benchmark/benchmark/doc.go
new file mode 100644
index 0000000..8016f5e
--- /dev/null
+++ b/runtime/internal/rpc/benchmark/benchmark/doc.go
@@ -0,0 +1,106 @@
+// 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
+
+/*
+Command benchmark runs the benchmark client.
+
+Usage:
+   benchmark [flags]
+
+The benchmark flags are:
+ -chunk_count=0
+   Number of chunks to send per streaming RPC (if zero, use non-streaming RPC).
+ -iterations=100
+   Number of iterations to run.
+ -mux_chunk_count=0
+   Number of chunks to send in background.
+ -mux_payload_size=0
+   Size of payload to send in background.
+ -payload_size=0
+   Size of payload in bytes.
+ -server=
+   Address of the server to connect to.
+
+The global flags are:
+ -alsologtostderr=true
+   log to standard error as well as files
+ -log_backtrace_at=:0
+   when logging hits line file:N, emit a stack trace
+ -log_dir=
+   if non-empty, write log files to this directory
+ -logtostderr=false
+   log to standard error instead of files
+ -max_stack_buf_size=4292608
+   max size in bytes of the buffer to use for logging stack traces
+ -metadata=<just specify -metadata to activate>
+   Displays metadata for the program and exits.
+ -stderrthreshold=2
+   logs at or above this threshold go to stderr
+ -test.bench=
+   regular expression to select benchmarks to run
+ -test.benchmem=false
+   print memory allocations for benchmarks
+ -test.benchtime=1s
+   approximate run time for each benchmark
+ -test.blockprofile=
+   write a goroutine blocking profile to the named file after execution
+ -test.blockprofilerate=1
+   if >= 0, calls runtime.SetBlockProfileRate()
+ -test.count=1
+   run tests and benchmarks `n` times
+ -test.coverprofile=
+   write a coverage profile to the named file after execution
+ -test.cpu=
+   comma-separated list of number of CPUs to use for each test
+ -test.cpuprofile=
+   write a cpu profile to the named file during execution
+ -test.memprofile=
+   write a memory profile to the named file after execution
+ -test.memprofilerate=0
+   if >=0, sets runtime.MemProfileRate
+ -test.outputdir=
+   directory in which to write profiles
+ -test.parallel=<number of threads>
+   maximum test parallelism
+ -test.run=
+   regular expression to select tests and examples to run
+ -test.short=false
+   run smaller test suite to save time
+ -test.timeout=0
+   if positive, sets an aggregate time limit for all tests
+ -test.trace=
+   write an execution trace to the named file after execution
+ -test.v=false
+   verbose: print additional output
+ -v23.credentials=
+   directory to use for storing security credentials
+ -v23.i18n-catalogue=
+   18n catalogue files to load, comma separated
+ -v23.namespace.root=[/(dev.v.io/role/vprod/service/mounttabled)@ns.dev.v.io:8101]
+   local namespace root; can be repeated to provided multiple roots
+ -v23.proxy=
+   object name of proxy service to use to export services across network
+   boundaries
+ -v23.tcp.address=
+   address to listen on
+ -v23.tcp.protocol=wsh
+   protocol to listen with
+ -v23.vtrace.cache-size=1024
+   The number of vtrace traces to store in memory.
+ -v23.vtrace.collect-regexp=
+   Spans and annotations that match this regular expression will trigger trace
+   collection.
+ -v23.vtrace.dump-on-shutdown=true
+   If true, dump all stored traces on runtime shutdown.
+ -v23.vtrace.sample-rate=0
+   Rate (from 0.0 to 1.0) to sample vtrace traces.
+ -vmodule=
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
+*/
+package main
diff --git a/runtime/internal/rpc/benchmark/benchmark/main.go b/runtime/internal/rpc/benchmark/benchmark/main.go
new file mode 100644
index 0000000..15bf193
--- /dev/null
+++ b/runtime/internal/rpc/benchmark/benchmark/main.go
@@ -0,0 +1,72 @@
+// 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.
+
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go . -help
+
+package main
+
+import (
+	"fmt"
+	"testing"
+	"time"
+
+	"v.io/x/lib/cmdline"
+
+	"v.io/v23/context"
+
+	"v.io/x/ref/lib/v23cmd"
+	_ "v.io/x/ref/runtime/factories/generic"
+	"v.io/x/ref/runtime/internal/rpc/benchmark/internal"
+	"v.io/x/ref/test/benchmark"
+)
+
+var (
+	server                                                         string
+	iterations, chunkCnt, payloadSize, chunkCntMux, payloadSizeMux int
+)
+
+func main() {
+	cmdRoot.Flags.StringVar(&server, "server", "", "Address of the server to connect to.")
+	cmdRoot.Flags.IntVar(&iterations, "iterations", 100, "Number of iterations to run.")
+	cmdRoot.Flags.IntVar(&chunkCnt, "chunk_count", 0, "Number of chunks to send per streaming RPC (if zero, use non-streaming RPC).")
+	cmdRoot.Flags.IntVar(&payloadSize, "payload_size", 0, "Size of payload in bytes.")
+	cmdRoot.Flags.IntVar(&chunkCntMux, "mux_chunk_count", 0, "Number of chunks to send in background.")
+	cmdRoot.Flags.IntVar(&payloadSizeMux, "mux_payload_size", 0, "Size of payload to send in background.")
+
+	cmdline.HideGlobalFlagsExcept()
+	cmdline.Main(cmdRoot)
+}
+
+var cmdRoot = &cmdline.Command{
+	Runner: v23cmd.RunnerFunc(runBenchmark),
+	Name:   "benchmark",
+	Short:  "Run the benchmark client",
+	Long:   "Command benchmark runs the benchmark client.",
+}
+
+func runBenchmark(ctx *context.T, env *cmdline.Env, args []string) error {
+	if chunkCntMux > 0 && payloadSizeMux > 0 {
+		dummyB := testing.B{}
+		_, stop := internal.StartEchoStream(&dummyB, ctx, server, 0, chunkCntMux, payloadSizeMux, nil)
+		defer stop()
+		ctx.Infof("Started background streaming (chunk_size=%d, payload_size=%d)", chunkCntMux, payloadSizeMux)
+	}
+
+	dummyB := testing.B{}
+	stats := benchmark.NewStats(16)
+
+	now := time.Now()
+	if chunkCnt == 0 {
+		internal.CallEcho(&dummyB, ctx, server, iterations, payloadSize, stats)
+	} else {
+		internal.CallEchoStream(&dummyB, ctx, server, iterations, chunkCnt, payloadSize, stats)
+	}
+	elapsed := time.Since(now)
+
+	fmt.Printf("iterations: %d  chunk_count: %d  payload_size: %d\n", iterations, chunkCnt, payloadSize)
+	fmt.Printf("elapsed time: %v\n", elapsed)
+	stats.Print(env.Stdout)
+	return nil
+}
diff --git a/runtime/internal/rpc/benchmark/benchmark_test.go b/runtime/internal/rpc/benchmark/benchmark_test.go
new file mode 100644
index 0000000..22f63b9
--- /dev/null
+++ b/runtime/internal/rpc/benchmark/benchmark_test.go
@@ -0,0 +1,128 @@
+// 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 benchmark_test
+
+import (
+	"os"
+	"testing"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/x/ref/lib/security/securityflag"
+	"v.io/x/ref/lib/xrpc"
+	_ "v.io/x/ref/runtime/factories/static"
+	"v.io/x/ref/runtime/internal/rpc/benchmark/internal"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/benchmark"
+)
+
+var (
+	serverAddr string
+	ctx        *context.T
+)
+
+// Benchmarks for non-streaming RPC.
+func runEcho(b *testing.B, payloadSize int) {
+	internal.CallEcho(b, ctx, serverAddr, b.N, payloadSize, benchmark.AddStats(b, 16))
+}
+
+func Benchmark____1B(b *testing.B) { runEcho(b, 1) }
+func Benchmark___10B(b *testing.B) { runEcho(b, 10) }
+func Benchmark__100B(b *testing.B) { runEcho(b, 100) }
+func Benchmark___1KB(b *testing.B) { runEcho(b, 1000) }
+func Benchmark__10KB(b *testing.B) { runEcho(b, 10000) }
+func Benchmark_100KB(b *testing.B) { runEcho(b, 100000) }
+
+// Benchmarks for streaming RPC.
+func runEchoStream(b *testing.B, chunkCnt, payloadSize int) {
+	internal.CallEchoStream(b, ctx, serverAddr, b.N, chunkCnt, payloadSize, benchmark.AddStats(b, 16))
+}
+
+func Benchmark____1_chunk_____1B(b *testing.B) { runEchoStream(b, 1, 1) }
+func Benchmark____1_chunk____10B(b *testing.B) { runEchoStream(b, 1, 10) }
+func Benchmark____1_chunk___100B(b *testing.B) { runEchoStream(b, 1, 100) }
+func Benchmark____1_chunk____1KB(b *testing.B) { runEchoStream(b, 1, 1000) }
+func Benchmark____1_chunk___10KB(b *testing.B) { runEchoStream(b, 1, 10000) }
+func Benchmark____1_chunk__100KB(b *testing.B) { runEchoStream(b, 1, 100000) }
+func Benchmark___10_chunk_____1B(b *testing.B) { runEchoStream(b, 10, 1) }
+func Benchmark___10_chunk____10B(b *testing.B) { runEchoStream(b, 10, 10) }
+func Benchmark___10_chunk___100B(b *testing.B) { runEchoStream(b, 10, 100) }
+func Benchmark___10_chunk____1KB(b *testing.B) { runEchoStream(b, 10, 1000) }
+func Benchmark___10_chunk___10KB(b *testing.B) { runEchoStream(b, 10, 10000) }
+func Benchmark___10_chunk__100KB(b *testing.B) { runEchoStream(b, 10, 100000) }
+func Benchmark__100_chunk_____1B(b *testing.B) { runEchoStream(b, 100, 1) }
+func Benchmark__100_chunk____10B(b *testing.B) { runEchoStream(b, 100, 10) }
+func Benchmark__100_chunk___100B(b *testing.B) { runEchoStream(b, 100, 100) }
+func Benchmark__100_chunk____1KB(b *testing.B) { runEchoStream(b, 100, 1000) }
+func Benchmark__100_chunk___10KB(b *testing.B) { runEchoStream(b, 100, 10000) }
+func Benchmark__100_chunk__100KB(b *testing.B) { runEchoStream(b, 100, 100000) }
+func Benchmark___1K_chunk_____1B(b *testing.B) { runEchoStream(b, 1000, 1) }
+func Benchmark___1K_chunk____10B(b *testing.B) { runEchoStream(b, 1000, 10) }
+func Benchmark___1K_chunk___100B(b *testing.B) { runEchoStream(b, 1000, 100) }
+func Benchmark___1K_chunk____1KB(b *testing.B) { runEchoStream(b, 1000, 1000) }
+func Benchmark___1K_chunk___10KB(b *testing.B) { runEchoStream(b, 1000, 10000) }
+func Benchmark___1K_chunk__100KB(b *testing.B) { runEchoStream(b, 1000, 100000) }
+
+// Benchmarks for per-chunk throughput in streaming RPC.
+func runPerChunk(b *testing.B, payloadSize int) {
+	internal.CallEchoStream(b, ctx, serverAddr, 1, b.N, payloadSize, benchmark.NewStats(1))
+}
+
+func Benchmark__per_chunk____1B(b *testing.B) { runPerChunk(b, 1) }
+func Benchmark__per_chunk___10B(b *testing.B) { runPerChunk(b, 10) }
+func Benchmark__per_chunk__100B(b *testing.B) { runPerChunk(b, 100) }
+func Benchmark__per_chunk___1KB(b *testing.B) { runPerChunk(b, 1000) }
+func Benchmark__per_chunk__10KB(b *testing.B) { runPerChunk(b, 10000) }
+func Benchmark__per_chunk_100KB(b *testing.B) { runPerChunk(b, 100000) }
+
+// Benchmarks for non-streaming RPC while running streaming RPC in background.
+func runMux(b *testing.B, payloadSize, chunkCntB, payloadSizeB int) {
+	_, stop := internal.StartEchoStream(&testing.B{}, ctx, serverAddr, 0, chunkCntB, payloadSizeB, benchmark.NewStats(1))
+	internal.CallEcho(b, ctx, serverAddr, b.N, payloadSize, benchmark.AddStats(b, 16))
+	stop()
+}
+
+func Benchmark___10B_mux__100_chunks___10B(b *testing.B) { runMux(b, 10, 100, 10) }
+func Benchmark___10B_mux__100_chunks__100B(b *testing.B) { runMux(b, 10, 100, 100) }
+func Benchmark___10B_mux__100_chunks___1KB(b *testing.B) { runMux(b, 10, 100, 1000) }
+func Benchmark___10B_mux__100_chunks__10KB(b *testing.B) { runMux(b, 10, 100, 10000) }
+func Benchmark___10B_mux___1K_chunks___10B(b *testing.B) { runMux(b, 10, 1000, 10) }
+func Benchmark___10B_mux___1K_chunks__100B(b *testing.B) { runMux(b, 10, 1000, 100) }
+func Benchmark___10B_mux___1K_chunks___1KB(b *testing.B) { runMux(b, 10, 1000, 1000) }
+func Benchmark___10B_mux___1K_chunks__10KB(b *testing.B) { runMux(b, 10, 1000, 10000) }
+func Benchmark___1KB_mux__100_chunks___10B(b *testing.B) { runMux(b, 1000, 100, 10) }
+func Benchmark___1KB_mux__100_chunks__100B(b *testing.B) { runMux(b, 1000, 100, 100) }
+func Benchmark___1KB_mux__100_chunks___1KB(b *testing.B) { runMux(b, 1000, 100, 1000) }
+func Benchmark___1KB_mux__100_chunks__10KB(b *testing.B) { runMux(b, 1000, 100, 10000) }
+func Benchmark___1KB_mux___1K_chunks___10B(b *testing.B) { runMux(b, 1000, 1000, 10) }
+func Benchmark___1KB_mux___1K_chunks__100B(b *testing.B) { runMux(b, 1000, 1000, 100) }
+func Benchmark___1KB_mux___1K_chunks___1KB(b *testing.B) { runMux(b, 1000, 1000, 1000) }
+func Benchmark___1KB_mux___1K_chunks__10KB(b *testing.B) { runMux(b, 1000, 1000, 10000) }
+
+// A single empty test to avoid:
+// testing: warning: no tests to run
+// from showing up when running benchmarks in this package via "go test"
+func TestNoOp(t *testing.T) {}
+
+func TestMain(m *testing.M) {
+	test.Init()
+	// We do not use defer here since this program will exit at the end of
+	// this function through os.Exit().
+	var shutdown v23.Shutdown
+	ctx, shutdown = test.V23Init()
+
+	server, err := xrpc.NewServer(ctx, "", internal.NewService(), securityflag.NewAuthorizerOrDie())
+	if err != nil {
+		ctx.Fatalf("NewServer failed: %v", err)
+	}
+	serverAddr = server.Status().Endpoints[0].Name()
+
+	// Create a VC to exclude the VC setup time from the benchmark.
+	internal.CallEcho(&testing.B{}, ctx, serverAddr, 1, 0, benchmark.NewStats(1))
+
+	r := benchmark.RunTestMain(m)
+	shutdown()
+	os.Exit(r)
+}
diff --git a/runtime/internal/rpc/benchmark/benchmarkd/doc.go b/runtime/internal/rpc/benchmark/benchmarkd/doc.go
new file mode 100644
index 0000000..a2bf3f8
--- /dev/null
+++ b/runtime/internal/rpc/benchmark/benchmarkd/doc.go
@@ -0,0 +1,99 @@
+// 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
+
+/*
+Command benchmarkd runs the benchmark server.
+
+Usage:
+   benchmarkd
+
+The global flags are:
+ -alsologtostderr=true
+   log to standard error as well as files
+ -log_backtrace_at=:0
+   when logging hits line file:N, emit a stack trace
+ -log_dir=
+   if non-empty, write log files to this directory
+ -logtostderr=false
+   log to standard error instead of files
+ -max_stack_buf_size=4292608
+   max size in bytes of the buffer to use for logging stack traces
+ -metadata=<just specify -metadata to activate>
+   Displays metadata for the program and exits.
+ -stderrthreshold=2
+   logs at or above this threshold go to stderr
+ -test.bench=
+   regular expression to select benchmarks to run
+ -test.benchmem=false
+   print memory allocations for benchmarks
+ -test.benchtime=1s
+   approximate run time for each benchmark
+ -test.blockprofile=
+   write a goroutine blocking profile to the named file after execution
+ -test.blockprofilerate=1
+   if >= 0, calls runtime.SetBlockProfileRate()
+ -test.count=1
+   run tests and benchmarks `n` times
+ -test.coverprofile=
+   write a coverage profile to the named file after execution
+ -test.cpu=
+   comma-separated list of number of CPUs to use for each test
+ -test.cpuprofile=
+   write a cpu profile to the named file during execution
+ -test.memprofile=
+   write a memory profile to the named file after execution
+ -test.memprofilerate=0
+   if >=0, sets runtime.MemProfileRate
+ -test.outputdir=
+   directory in which to write profiles
+ -test.parallel=<number of threads>
+   maximum test parallelism
+ -test.run=
+   regular expression to select tests and examples to run
+ -test.short=false
+   run smaller test suite to save time
+ -test.timeout=0
+   if positive, sets an aggregate time limit for all tests
+ -test.trace=
+   write an execution trace to the named file after execution
+ -test.v=false
+   verbose: print additional output
+ -v=0
+   log level for V logs
+ -v23.credentials=
+   directory to use for storing security credentials
+ -v23.i18n-catalogue=
+   18n catalogue files to load, comma separated
+ -v23.namespace.root=[/(dev.v.io/role/vprod/service/mounttabled)@ns.dev.v.io:8101]
+   local namespace root; can be repeated to provided multiple roots
+ -v23.permissions.file=map[]
+   specify a perms file as <name>:<permsfile>
+ -v23.permissions.literal=
+   explicitly specify the runtime perms as a JSON-encoded access.Permissions.
+   Overrides all --v23.permissions.file flags.
+ -v23.proxy=
+   object name of proxy service to use to export services across network
+   boundaries
+ -v23.tcp.address=
+   address to listen on
+ -v23.tcp.protocol=wsh
+   protocol to listen with
+ -v23.vtrace.cache-size=1024
+   The number of vtrace traces to store in memory.
+ -v23.vtrace.collect-regexp=
+   Spans and annotations that match this regular expression will trigger trace
+   collection.
+ -v23.vtrace.dump-on-shutdown=true
+   If true, dump all stored traces on runtime shutdown.
+ -v23.vtrace.sample-rate=0
+   Rate (from 0.0 to 1.0) to sample vtrace traces.
+ -vmodule=
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
+*/
+package main
diff --git a/runtime/internal/rpc/benchmark/benchmarkd/main.go b/runtime/internal/rpc/benchmark/benchmarkd/main.go
new file mode 100644
index 0000000..f20bb3c
--- /dev/null
+++ b/runtime/internal/rpc/benchmark/benchmarkd/main.go
@@ -0,0 +1,43 @@
+// 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.
+
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go . -help
+
+package main
+
+import (
+	"v.io/x/lib/cmdline"
+
+	"v.io/v23/context"
+
+	"v.io/x/ref/lib/security/securityflag"
+	"v.io/x/ref/lib/signals"
+	"v.io/x/ref/lib/v23cmd"
+	"v.io/x/ref/lib/xrpc"
+	_ "v.io/x/ref/runtime/factories/roaming"
+	"v.io/x/ref/runtime/internal/rpc/benchmark/internal"
+)
+
+func main() {
+	cmdline.HideGlobalFlagsExcept()
+	cmdline.Main(cmdRoot)
+}
+
+var cmdRoot = &cmdline.Command{
+	Runner: v23cmd.RunnerFunc(runBenchmarkD),
+	Name:   "benchmarkd",
+	Short:  "Run the benchmark server",
+	Long:   "Command benchmarkd runs the benchmark server.",
+}
+
+func runBenchmarkD(ctx *context.T, env *cmdline.Env, args []string) error {
+	server, err := xrpc.NewServer(ctx, "", internal.NewService(), securityflag.NewAuthorizerOrDie())
+	if err != nil {
+		ctx.Fatalf("NewServer failed: %v", err)
+	}
+	ctx.Infof("Listening on %s", server.Status().Endpoints[0].Name())
+	<-signals.ShutdownOnSignals(ctx)
+	return nil
+}
diff --git a/runtime/internal/rpc/benchmark/internal/client.go b/runtime/internal/rpc/benchmark/internal/client.go
new file mode 100644
index 0000000..8a712b2
--- /dev/null
+++ b/runtime/internal/rpc/benchmark/internal/client.go
@@ -0,0 +1,151 @@
+// 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
+
+import (
+	"bytes"
+	"fmt"
+	"testing"
+	"time"
+
+	"v.io/v23/context"
+
+	"v.io/x/ref/runtime/internal/rpc/benchmark"
+	tbm "v.io/x/ref/test/benchmark"
+)
+
+// CallEcho calls 'Echo' method 'iterations' times with the given payload size.
+func CallEcho(b *testing.B, ctx *context.T, address string, iterations, payloadSize int, stats *tbm.Stats) {
+	stub := benchmark.BenchmarkClient(address)
+	payload := make([]byte, payloadSize)
+	for i := range payload {
+		payload[i] = byte(i & 0xff)
+	}
+
+	b.SetBytes(int64(payloadSize) * 2) // 2 for round trip of each payload.
+	b.ResetTimer()                     // Exclude setup time from measurement.
+
+	for i := 0; i < iterations; i++ {
+		b.StartTimer()
+		start := time.Now()
+
+		r, err := stub.Echo(ctx, payload)
+
+		elapsed := time.Since(start)
+		b.StopTimer()
+
+		if err != nil {
+			ctx.Fatalf("Echo failed: %v", err)
+		}
+		if !bytes.Equal(r, payload) {
+			ctx.Fatalf("Echo returned %v, but expected %v", r, payload)
+		}
+
+		stats.Add(elapsed)
+	}
+}
+
+// CallEchoStream calls 'EchoStream' method 'iterations' times. Each iteration sends
+// 'chunkCnt' chunks on the stream and receives the same number of chunks back. Each
+// chunk has the given payload size.
+func CallEchoStream(b *testing.B, ctx *context.T, address string, iterations, chunkCnt, payloadSize int, stats *tbm.Stats) {
+	done, _ := StartEchoStream(b, ctx, address, iterations, chunkCnt, payloadSize, stats)
+	<-done
+}
+
+// StartEchoStream starts to call 'EchoStream' method 'iterations' times. This does
+// not block, and returns a channel that will receive the number of iterations when
+// it's done. It also returns a callback function to stop the streaming. Each iteration
+// requests 'chunkCnt' chunks on the stream and receives that number of chunks back.
+// Each chunk has the given payload size. Zero 'iterations' means unlimited.
+func StartEchoStream(b *testing.B, ctx *context.T, address string, iterations, chunkCnt, payloadSize int, stats *tbm.Stats) (<-chan int, func()) {
+	stub := benchmark.BenchmarkClient(address)
+	payload := make([]byte, payloadSize)
+	for i := range payload {
+		payload[i] = byte(i & 0xff)
+	}
+
+	stop := make(chan struct{})
+	stopped := func() bool {
+		select {
+		case <-stop:
+			return true
+		default:
+			return false
+		}
+	}
+	done := make(chan int, 1)
+
+	if b.N > 0 {
+		// 2 for round trip of each payload.
+		b.SetBytes(int64((iterations*chunkCnt/b.N)*payloadSize) * 2)
+	}
+	b.ResetTimer() // Exclude setup time from measurement.
+
+	go func() {
+		defer close(done)
+
+		n := 0
+		for ; !stopped() && (iterations == 0 || n < iterations); n++ {
+			b.StartTimer()
+			start := time.Now()
+
+			stream, err := stub.EchoStream(ctx)
+			if err != nil {
+				ctx.Fatalf("EchoStream failed: %v", err)
+			}
+
+			rDone := make(chan error, 1)
+			go func() {
+				defer close(rDone)
+
+				rStream := stream.RecvStream()
+				i := 0
+				for ; rStream.Advance(); i++ {
+					r := rStream.Value()
+					if !bytes.Equal(r, payload) {
+						rDone <- fmt.Errorf("EchoStream returned %v, but expected %v", r, payload)
+						return
+					}
+				}
+				if i != chunkCnt {
+					rDone <- fmt.Errorf("EchoStream returned %d chunks, but expected %d", i, chunkCnt)
+					return
+				}
+				rDone <- rStream.Err()
+			}()
+
+			sStream := stream.SendStream()
+			for i := 0; i < chunkCnt; i++ {
+				if err = sStream.Send(payload); err != nil {
+					ctx.Fatalf("EchoStream Send failed: %v", err)
+				}
+			}
+			if err = sStream.Close(); err != nil {
+				ctx.Fatalf("EchoStream Close failed: %v", err)
+			}
+
+			if err = <-rDone; err != nil {
+				ctx.Fatalf("%v", err)
+			}
+
+			if err = stream.Finish(); err != nil {
+				ctx.Fatalf("Finish failed: %v", err)
+			}
+
+			elapsed := time.Since(start)
+			b.StopTimer()
+
+			stats.Add(elapsed)
+		}
+
+		done <- n
+	}()
+
+	return done, func() {
+		close(stop)
+		<-done
+	}
+}
diff --git a/runtime/internal/rpc/benchmark/internal/server.go b/runtime/internal/rpc/benchmark/internal/server.go
new file mode 100644
index 0000000..8bbd8ea
--- /dev/null
+++ b/runtime/internal/rpc/benchmark/internal/server.go
@@ -0,0 +1,35 @@
+// 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
+
+import (
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+
+	"v.io/x/ref/runtime/internal/rpc/benchmark"
+)
+
+type impl struct {
+}
+
+func NewService() benchmark.BenchmarkServerStub {
+	return benchmark.BenchmarkServer(&impl{})
+}
+
+func (i *impl) Echo(_ *context.T, _ rpc.ServerCall, payload []byte) ([]byte, error) {
+	return payload, nil
+}
+
+func (i *impl) EchoStream(_ *context.T, call benchmark.BenchmarkEchoStreamServerCall) error {
+	rStream := call.RecvStream()
+	sStream := call.SendStream()
+	for rStream.Advance() {
+		sStream.Send(rStream.Value())
+	}
+	if err := rStream.Err(); err != nil {
+		return err
+	}
+	return nil
+}
diff --git a/runtime/internal/rpc/benchmark/simple/main.go b/runtime/internal/rpc/benchmark/simple/main.go
new file mode 100644
index 0000000..8173e82
--- /dev/null
+++ b/runtime/internal/rpc/benchmark/simple/main.go
@@ -0,0 +1,136 @@
+// 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/x/ref/lib/security/securityflag"
+	"v.io/x/ref/lib/xrpc"
+
+	_ "v.io/x/ref/runtime/factories/static"
+	"v.io/x/ref/runtime/internal/rpc/benchmark/internal"
+	"v.io/x/ref/runtime/internal/rpc/stream/manager"
+	"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 (
+	serverEP naming.Endpoint
+	ctx      *context.T
+)
+
+// 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)
+
+	principal := testutil.NewPrincipal("test")
+	nctx, _ := v23.WithPrincipal(ctx, principal)
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		client := manager.InternalNew(ctx, naming.FixedRoutingID(0xc))
+
+		b.StartTimer()
+		_, err := client.Dial(nctx, serverEP)
+		if err != nil {
+			ctx.Fatalf("Dial failed: %v", err)
+		}
+
+		b.StopTimer()
+
+		client.Shutdown()
+	}
+}
+
+// Benchmark for non-streaming RPC.
+func benchmarkRPC(b *testing.B) {
+	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) {
+	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) {
+	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))
+
+	// Create a connection to exclude the setup time from the following benchmarks.
+	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())
+	test.Init()
+
+	var shutdown v23.Shutdown
+	ctx, shutdown = test.V23Init()
+	defer shutdown()
+
+	server, err := xrpc.NewServer(ctx, "", internal.NewService(), securityflag.NewAuthorizerOrDie())
+	if err != nil {
+		ctx.Fatalf("NewServer failed: %v", err)
+	}
+	serverEP = server.Status().Endpoints[0]
+
+	runBenchmarks()
+}
diff --git a/runtime/internal/rpc/blessings_cache.go b/runtime/internal/rpc/blessings_cache.go
new file mode 100644
index 0000000..7b201bb
--- /dev/null
+++ b/runtime/internal/rpc/blessings_cache.go
@@ -0,0 +1,179 @@
+// 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 rpc
+
+import (
+	"crypto/sha256"
+	"sync"
+
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/verror"
+
+	"v.io/x/ref/runtime/internal/rpc/stream"
+)
+
+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.
+	errMissingBlessingsKey    = reg(".blessingsKey", "key {3} was not in blessings cache")
+	errInvalidClientBlessings = reg("invalidClientBlessings", "client sent invalid Blessings")
+)
+
+// clientEncodeBlessings gets or inserts the blessings into the cache.
+func clientEncodeBlessings(cache stream.VCDataCache, blessings security.Blessings) rpc.BlessingsRequest {
+	blessingsCacheAny := cache.GetOrInsert(clientBlessingsCacheKey{}, newClientBlessingsCache)
+	blessingsCache := blessingsCacheAny.(*clientBlessingsCache)
+	return blessingsCache.getOrInsert(blessings)
+}
+
+// clientAckBlessings verifies that the server has updated its cache to include blessings.
+// This means that subsequent rpcs from the client with blessings can send only a cache key.
+func clientAckBlessings(cache stream.VCDataCache, blessings security.Blessings) {
+	blessingsCacheAny := cache.GetOrInsert(clientBlessingsCacheKey{}, newClientBlessingsCache)
+	blessingsCache := blessingsCacheAny.(*clientBlessingsCache)
+	blessingsCache.acknowledge(blessings)
+}
+
+// serverDecodeBlessings insert the key and blessings into the cache or get the blessings if only
+// key is provided in req.
+func serverDecodeBlessings(cache stream.VCDataCache, req rpc.BlessingsRequest, stats *rpcStats) (security.Blessings, error) {
+	blessingsCacheAny := cache.GetOrInsert(serverBlessingsCacheKey{}, newServerBlessingsCache)
+	blessingsCache := blessingsCacheAny.(*serverBlessingsCache)
+	return blessingsCache.getOrInsert(req, stats)
+}
+
+// IMPLEMENTATION DETAILS BELOW
+
+// clientBlessingsCache is a thread-safe map from blessings to cache id.
+type clientBlessingsCache struct {
+	sync.RWMutex
+	m     map[[sha256.Size]byte]clientCacheValue
+	curId uint64
+}
+
+type clientCacheValue struct {
+	id uint64
+	// ack is set to true once the server has confirmed receipt of the cache id.
+	// Clients that insert into the cache when ack is false must send both the id
+	// and the blessings.
+	ack bool
+}
+
+// clientBlessingsCacheKey is the key used to retrieve the clientBlessingsCache from the VCDataCache.
+type clientBlessingsCacheKey struct{}
+
+func newClientBlessingsCache() interface{} {
+	return &clientBlessingsCache{m: make(map[[sha256.Size]byte]clientCacheValue)}
+}
+
+func getBlessingsHashKey(blessings security.Blessings) (key [sha256.Size]byte) {
+	h := sha256.New()
+	for _, chain := range security.MarshalBlessings(blessings).CertificateChains {
+		if len(chain) == 0 {
+			continue
+		}
+		cert := chain[len(chain)-1]
+		s := sha256.Sum256(cert.Signature.R)
+		h.Write(s[:])
+		s = sha256.Sum256(cert.Signature.S)
+		h.Write(s[:])
+	}
+	copy(key[:], h.Sum(nil))
+	return
+}
+
+func (c *clientBlessingsCache) getOrInsert(blessings security.Blessings) rpc.BlessingsRequest {
+	key := getBlessingsHashKey(blessings)
+	c.RLock()
+	val, exists := c.m[key]
+	c.RUnlock()
+	if exists {
+		return c.makeBlessingsRequest(val, blessings)
+	}
+	// If the val doesn't exist we must create a new id, update the cache, and send the id and blessings.
+	c.Lock()
+	// We must check that the val wasn't inserted in the time we changed locks.
+	val, exists = c.m[key]
+	if !exists {
+		val = clientCacheValue{id: c.nextIdLocked()}
+		c.m[key] = val
+	}
+	c.Unlock()
+	return c.makeBlessingsRequest(val, blessings)
+}
+
+func (c *clientBlessingsCache) acknowledge(blessings security.Blessings) {
+	key := getBlessingsHashKey(blessings)
+	c.Lock()
+	val := c.m[key]
+	val.ack = true
+	c.m[key] = val
+	c.Unlock()
+}
+
+func (c *clientBlessingsCache) makeBlessingsRequest(val clientCacheValue, blessings security.Blessings) rpc.BlessingsRequest {
+	if val.ack {
+		// when the value is acknowledged, only send the key, since the server has confirmed that it knows the key.
+		return rpc.BlessingsRequest{Key: val.id}
+	}
+	// otherwise we still need to send both key and blessings, but we must ensure that we send the same key.
+	return rpc.BlessingsRequest{Key: val.id, Blessings: &blessings}
+}
+
+// nextIdLocked creates a new id for inserting blessings. It must be called after acquiring a writer lock.
+func (c *clientBlessingsCache) nextIdLocked() uint64 {
+	c.curId++
+	return c.curId
+}
+
+// serverBlessingsCache is a thread-safe map from cache key to blessings.
+type serverBlessingsCache struct {
+	sync.RWMutex
+	m map[uint64]security.Blessings
+}
+
+// serverBlessingsCacheKey is the key used to retrieve the serverBlessingsCache from the VCDataCache.
+type serverBlessingsCacheKey struct{}
+
+func newServerBlessingsCache() interface{} {
+	return &serverBlessingsCache{m: make(map[uint64]security.Blessings)}
+}
+
+func (c *serverBlessingsCache) getOrInsert(req rpc.BlessingsRequest, stats *rpcStats) (security.Blessings, error) {
+	// In the case that the key sent is 0, we are running in SecurityNone
+	// and should return the zero value.
+	if req.Key == 0 {
+		return security.Blessings{}, nil
+	}
+	if req.Blessings == nil {
+		// Fastpath, lookup based on the key.
+		c.RLock()
+		cached, exists := c.m[req.Key]
+		c.RUnlock()
+		if !exists {
+			return security.Blessings{}, verror.New(errMissingBlessingsKey, nil, req.Key)
+		}
+		stats.recordBlessingCache(true)
+		return cached, nil
+	}
+	// Always count the slow path as a cache miss since we do not get the benefit of sending only the cache key.
+	stats.recordBlessingCache(false)
+	// Slowpath, might need to update the cache, or check that the received blessings are
+	// the same as what's in the cache.
+	recv := *req.Blessings
+	c.Lock()
+	defer c.Unlock()
+	if cached, exists := c.m[req.Key]; exists {
+		if !cached.Equivalent(recv) {
+			return security.Blessings{}, verror.New(errInvalidClientBlessings, nil)
+		}
+		return cached, nil
+	}
+	c.m[req.Key] = recv
+	return recv, nil
+}
diff --git a/runtime/internal/rpc/cancel_test.go b/runtime/internal/rpc/cancel_test.go
new file mode 100644
index 0000000..9a11c07
--- /dev/null
+++ b/runtime/internal/rpc/cancel_test.go
@@ -0,0 +1,126 @@
+// 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 rpc
+
+import (
+	"testing"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/namespace"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/verror"
+
+	"v.io/x/ref/runtime/internal/rpc/stream"
+	"v.io/x/ref/runtime/internal/rpc/stream/manager"
+	tnaming "v.io/x/ref/runtime/internal/testing/mocks/naming"
+)
+
+type canceld struct {
+	sm       stream.Manager
+	ns       namespace.T
+	name     string
+	child    string
+	started  chan struct{}
+	canceled chan struct{}
+	stop     func() error
+}
+
+func (c *canceld) Run(ctx *context.T, _ rpc.StreamServerCall) error {
+	close(c.started)
+
+	client, err := InternalNewClient(c.sm, c.ns)
+	if err != nil {
+		ctx.Error(err)
+		return err
+	}
+
+	ctx.Infof("Run: %s", c.child)
+	if c.child != "" {
+		if _, err = client.StartCall(ctx, c.child, "Run", []interface{}{}); err != nil {
+			ctx.Error(err)
+			return err
+		}
+	}
+
+	<-ctx.Done()
+	close(c.canceled)
+	return nil
+}
+
+func makeCanceld(ctx *context.T, ns namespace.T, name, child string) (*canceld, error) {
+	sm := manager.InternalNew(ctx, naming.FixedRoutingID(0x111111111))
+	s, err := testInternalNewServer(ctx, sm, ns)
+	if err != nil {
+		return nil, err
+	}
+
+	if _, err := s.Listen(listenSpec); err != nil {
+		return nil, err
+	}
+
+	c := &canceld{
+		sm:       sm,
+		ns:       ns,
+		name:     name,
+		child:    child,
+		started:  make(chan struct{}, 0),
+		canceled: make(chan struct{}, 0),
+		stop:     s.Stop,
+	}
+
+	if err := s.Serve(name, c, security.AllowEveryone()); err != nil {
+		return nil, err
+	}
+	ctx.Infof("Serving: %q", name)
+	return c, nil
+}
+
+// TestCancellationPropagation tests that cancellation propogates along an
+// RPC call chain without user intervention.
+func TestCancellationPropagation(t *testing.T) {
+	ctx, shutdown := initForTest()
+	defer shutdown()
+	var (
+		sm               = manager.InternalNew(ctx, naming.FixedRoutingID(0x555555555))
+		ns               = tnaming.NewSimpleNamespace()
+		pclient, pserver = newClientServerPrincipals()
+		serverCtx, _     = v23.WithPrincipal(ctx, pserver)
+		clientCtx, _     = v23.WithPrincipal(ctx, pclient)
+	)
+	client, err := InternalNewClient(sm, ns)
+	if err != nil {
+		t.Error(err)
+	}
+
+	c1, err := makeCanceld(serverCtx, ns, "c1", "c2")
+	if err != nil {
+		t.Fatal("Can't start server:", err, verror.DebugString(err))
+	}
+	defer c1.stop()
+	c2, err := makeCanceld(serverCtx, ns, "c2", "")
+	if err != nil {
+		t.Fatal("Can't start server:", err)
+	}
+	defer c2.stop()
+
+	clientCtx, cancel := context.WithCancel(clientCtx)
+	_, err = client.StartCall(clientCtx, "c1", "Run", []interface{}{})
+	if err != nil {
+		t.Fatal("can't call: ", err)
+	}
+
+	<-c1.started
+	<-c2.started
+
+	ctx.Info("cancelling initial call")
+	cancel()
+
+	ctx.Info("waiting for children to be canceled")
+	<-c1.canceled
+	<-c2.canceled
+}
diff --git a/runtime/internal/rpc/client.go b/runtime/internal/rpc/client.go
new file mode 100644
index 0000000..5b9860e
--- /dev/null
+++ b/runtime/internal/rpc/client.go
@@ -0,0 +1,1081 @@
+// 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 rpc
+
+import (
+	"fmt"
+	"io"
+	"math/rand"
+	"net"
+	"reflect"
+	"sync"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/i18n"
+	"v.io/v23/namespace"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/vdl"
+	vtime "v.io/v23/vdlroot/time"
+	"v.io/v23/verror"
+	"v.io/v23/vom"
+	"v.io/v23/vtrace"
+
+	"v.io/x/ref/lib/apilog"
+	inaming "v.io/x/ref/runtime/internal/naming"
+	"v.io/x/ref/runtime/internal/rpc/stream"
+	"v.io/x/ref/runtime/internal/rpc/stream/vc"
+)
+
+const pkgPath = "v.io/x/ref/runtime/internal/rpc"
+
+func reg(id, msg string) verror.IDAction {
+	// Note: the error action is never used and is instead computed
+	// at a higher level. The errors here are purely for informational
+	// purposes.
+	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.
+	errClientCloseAlreadyCalled  = reg(".errCloseAlreadyCalled", "rpc.Client.Close has already been called")
+	errClientFinishAlreadyCalled = reg(".errFinishAlreadyCalled", "rpc.ClientCall.Finish has already been called")
+	errNonRootedName             = reg(".errNonRootedName", "{3} does not appear to contain an address")
+	errInvalidEndpoint           = reg(".errInvalidEndpoint", "failed to parse endpoint")
+	errIncompatibleEndpoint      = reg(".errIncompatibleEndpoint", "incompatible endpoint")
+	errRequestEncoding           = reg(".errRequestEncoding", "failed to encode request {3}{:4}")
+	errDischargeEncoding         = reg(".errDischargeEncoding", "failed to encode discharges {:3}")
+	errBlessingEncoding          = reg(".errBlessingEncoding", "failed to encode blessing {3}{:4}")
+	errArgEncoding               = reg(".errArgEncoding", "failed to encode arg #{3}{:4:}")
+	errMismatchedResults         = reg(".errMismatchedResults", "got {3} results, but want {4}")
+	errResultDecoding            = reg(".errResultDecoding", "failed to decode result #{3}{:4}")
+	errResponseDecoding          = reg(".errResponseDecoding", "failed to decode response{:3}")
+	errRemainingStreamResults    = reg(".errRemaingStreamResults", "stream closed with remaining stream results")
+	errNoBlessingsForPeer        = reg(".errNoBlessingsForPeer", "no blessings tagged for peer {3}{:4}")
+	errBlessingGrant             = reg(".errBlessingGrant", "failed to grant blessing to server with blessings{:3}")
+	errBlessingAdd               = reg(".errBlessingAdd", "failed to add blessing granted to server{:3}")
+	errServerAuthorizeFailed     = reg(".errServerAuthorizedFailed", "failed to authorize flow with remote blessings{:3} {:4}")
+
+	errPrepareBlessingsAndDischarges = reg(".prepareBlessingsAndDischarges", "failed to prepare blessings and discharges: remote blessings{:3} {:4}")
+
+	errDischargeImpetus = reg(".errDischargeImpetus", "couldn't make discharge impetus{:3}")
+	errNoPrincipal      = reg(".errNoPrincipal", "principal required for secure connections")
+)
+
+type client struct {
+	streamMgr          stream.Manager
+	ns                 namespace.T
+	vcOpts             []stream.VCOpt // vc opts passed to dial
+	preferredProtocols []string
+
+	// We cache the IP networks on the device since it is not that cheap to read
+	// network interfaces through os syscall.
+	// TODO(toddw): this can be removed since netstate now implements caching
+	// directly.
+	ipNets []*net.IPNet
+
+	vcCache *vc.VCCache
+
+	wg     sync.WaitGroup
+	mu     sync.Mutex
+	closed bool
+
+	dc vc.DischargeClient
+}
+
+var _ rpc.Client = (*client)(nil)
+
+func InternalNewClient(streamMgr stream.Manager, ns namespace.T, opts ...rpc.ClientOpt) (rpc.Client, error) {
+	c := &client{
+		streamMgr: streamMgr,
+		ns:        ns,
+		vcCache:   vc.NewVCCache(),
+	}
+	ipNets, err := ipNetworks()
+	if err != nil {
+		return nil, err
+	}
+	c.ipNets = ipNets
+	c.dc = InternalNewDischargeClient(nil, c, 0)
+	for _, opt := range opts {
+		// Collect all client opts that are also vc opts.
+		switch v := opt.(type) {
+		case stream.VCOpt:
+			c.vcOpts = append(c.vcOpts, v)
+		case PreferredProtocols:
+			c.preferredProtocols = v
+		}
+	}
+
+	return c, nil
+}
+
+func (c *client) createFlow(ctx *context.T, principal security.Principal, ep naming.Endpoint, vcOpts []stream.VCOpt) (stream.Flow, *verror.SubErr) {
+	suberr := func(err error) *verror.SubErr {
+		return &verror.SubErr{Err: err, Options: verror.Print}
+	}
+
+	found, err := c.vcCache.ReservedFind(ep, principal)
+	if err != nil {
+		return nil, suberr(verror.New(errClientCloseAlreadyCalled, ctx))
+	}
+	defer c.vcCache.Unreserve(ep, principal)
+	if found != nil {
+		// We are serializing the creation of all flows per VC. This is okay
+		// because if one flow creation is to block, it is likely that all others
+		// for that VC would block as well.
+		if flow, err := found.Connect(); err == nil {
+			return flow, nil
+		}
+		// If the vc fails to establish a new flow, we assume it's
+		// broken, remove it from the cache, and proceed to establishing
+		// a new vc.
+		//
+		// TODO(suharshs): The decision to redial 1 time when the dialing the vc
+		// in the cache fails is a bit inconsistent with the behavior when a newly
+		// dialed vc.Connect fails. We should revisit this.
+		//
+		// TODO(caprita): Should we distinguish errors due to vc being
+		// closed from other errors?  If not, should we call vc.Close()
+		// before removing the vc from the cache?
+		if err := c.vcCache.Delete(found); err != nil {
+			return nil, suberr(verror.New(errClientCloseAlreadyCalled, ctx))
+		}
+	}
+
+	sm := c.streamMgr
+	v, err := sm.Dial(ctx, ep, vcOpts...)
+	if err != nil {
+		return nil, suberr(err)
+	}
+
+	flow, err := v.Connect()
+	if err != nil {
+		return nil, suberr(err)
+	}
+
+	if err := c.vcCache.Insert(v.(*vc.VC)); err != nil {
+		sm.ShutdownEndpoint(ep)
+		return nil, suberr(verror.New(errClientCloseAlreadyCalled, ctx))
+	}
+
+	return flow, nil
+}
+
+// A randomized exponential backoff. The randomness deters error convoys
+// from forming.  The first time you retry n should be 0, then 1 etc.
+func backoff(n uint, deadline time.Time) bool {
+	// This is ((100 to 200) * 2^n) ms.
+	b := time.Duration((100+rand.Intn(100))<<n) * time.Millisecond
+	if b > maxBackoff {
+		b = maxBackoff
+	}
+	r := deadline.Sub(time.Now())
+	if b > r {
+		// We need to leave a little time for the call to start or
+		// we'll just timeout in startCall before we actually do
+		// anything.  If we just have a millisecond left, give up.
+		if r <= time.Millisecond {
+			return false
+		}
+		b = r - time.Millisecond
+	}
+	time.Sleep(b)
+	return true
+}
+
+func (c *client) StartCall(ctx *context.T, name, method string, args []interface{}, opts ...rpc.CallOpt) (rpc.ClientCall, error) {
+	defer apilog.LogCallf(ctx, "name=%.10s...,method=%.10s...,args=,opts...=%v", name, method, opts)(ctx, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	return c.startCall(ctx, name, method, args, opts)
+}
+
+func (c *client) Call(ctx *context.T, name, method string, inArgs, outArgs []interface{}, opts ...rpc.CallOpt) error {
+	defer apilog.LogCallf(ctx, "name=%.10s...,method=%.10s...,inArgs=,outArgs=,opts...=%v", name, method, opts)(ctx, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	deadline := getDeadline(ctx, opts)
+
+	var lastErr error
+	for retries := uint(0); ; retries++ {
+		call, err := c.startCall(ctx, name, method, inArgs, opts)
+		if err != nil {
+			return err
+		}
+		err = call.Finish(outArgs...)
+		if err == nil {
+			return nil
+		}
+		lastErr = err
+		// We only retry if RetryBackoff is returned by the application because other
+		// RetryConnection and RetryRefetch required actions by the client before
+		// retrying.
+		if !shouldRetryBackoff(verror.Action(lastErr), deadline, opts) {
+			ctx.VI(4).Infof("Cannot retry after error: %s", lastErr)
+			break
+		}
+		if !backoff(retries, deadline) {
+			break
+		}
+		ctx.VI(4).Infof("Retrying due to error: %s", lastErr)
+	}
+	return lastErr
+}
+
+func getDeadline(ctx *context.T, opts []rpc.CallOpt) time.Time {
+	// Context specified deadline.
+	deadline, hasDeadline := ctx.Deadline()
+	if !hasDeadline {
+		// Default deadline.
+		deadline = time.Now().Add(defaultCallTimeout)
+	}
+	if r, ok := getRetryTimeoutOpt(opts); ok {
+		// Caller specified deadline.
+		deadline = time.Now().Add(r)
+	}
+	return deadline
+}
+
+func shouldRetryBackoff(action verror.ActionCode, deadline time.Time, opts []rpc.CallOpt) bool {
+	switch {
+	case noRetry(opts):
+		return false
+	case action != verror.RetryBackoff:
+		return false
+	case time.Now().After(deadline):
+		return false
+	}
+	return true
+}
+
+func shouldRetry(action verror.ActionCode, requireResolve bool, deadline time.Time, opts []rpc.CallOpt) bool {
+	switch {
+	case noRetry(opts):
+		return false
+	case action != verror.RetryConnection && action != verror.RetryRefetch:
+		return false
+	case time.Now().After(deadline):
+		return false
+	case requireResolve && getNoNamespaceOpt(opts):
+		// If we're skipping resolution and there are no servers for
+		// this call retrying is not going to help, we can't come up
+		// with new servers if there is no resolution.
+		return false
+	}
+	return true
+}
+
+func mkDischargeImpetus(serverBlessings []string, method string, args []interface{}) (security.DischargeImpetus, error) {
+	var impetus security.DischargeImpetus
+	if len(serverBlessings) > 0 {
+		impetus.Server = make([]security.BlessingPattern, len(serverBlessings))
+		for i, b := range serverBlessings {
+			impetus.Server[i] = security.BlessingPattern(b)
+		}
+	}
+	impetus.Method = method
+	if len(args) > 0 {
+		impetus.Arguments = make([]*vdl.Value, len(args))
+		for i, a := range args {
+			vArg, err := vdl.ValueFromReflect(reflect.ValueOf(a))
+			if err != nil {
+				return security.DischargeImpetus{}, err
+			}
+			impetus.Arguments[i] = vArg
+		}
+	}
+	return impetus, nil
+}
+
+// startCall ensures StartCall always returns verror.E.
+func (c *client) startCall(ctx *context.T, name, method string, args []interface{}, opts []rpc.CallOpt) (rpc.ClientCall, error) {
+	if !ctx.Initialized() {
+		return nil, verror.ExplicitNew(verror.ErrBadArg, i18n.LangID("en-us"), "<rpc.Client>", "StartCall", "context not initialized")
+	}
+	ctx, span := vtrace.WithNewSpan(ctx, fmt.Sprintf("<rpc.Client>%q.%s", name, method))
+	if err := canCreateServerAuthorizer(ctx, opts); err != nil {
+		return nil, verror.New(verror.ErrBadArg, ctx, err)
+	}
+
+	deadline := getDeadline(ctx, opts)
+
+	var lastErr error
+	for retries := uint(0); ; retries++ {
+		call, action, requireResolve, err := c.tryCall(ctx, name, method, args, opts)
+		if err == nil {
+			return call, nil
+		}
+		lastErr = err
+		if !shouldRetry(action, requireResolve, deadline, opts) {
+			span.Annotatef("Cannot retry after error: %s", err)
+			break
+		}
+		if !backoff(retries, deadline) {
+			break
+		}
+		span.Annotatef("Retrying due to error: %s", err)
+	}
+	return nil, lastErr
+}
+
+type serverStatus struct {
+	index             int
+	server, suffix    string
+	flow              stream.Flow
+	blessings         []string                    // authorized server blessings
+	rejectedBlessings []security.RejectedBlessing // rejected server blessings
+	serverErr         *verror.SubErr
+}
+
+func suberrName(server, name, method string) string {
+	// In the case the client directly dialed an endpoint we want to avoid printing
+	// the endpoint twice.
+	if server == name {
+		return fmt.Sprintf("%s.%s", server, method)
+	}
+	return fmt.Sprintf("%s:%s.%s", server, name, method)
+}
+
+// tryCreateFlow attempts to establish a Flow to "server" (which must be a
+// rooted name), over which a method invocation request could be sent.
+//
+// The server at the remote end of the flow is authorized using the provided
+// authorizer, both during creation of the VC underlying the flow and the
+// flow itself.
+// TODO(cnicolaou): implement real, configurable load balancing.
+func (c *client) tryCreateFlow(ctx *context.T, principal security.Principal, index int, name, server, method string, auth security.Authorizer, ch chan<- *serverStatus, vcOpts []stream.VCOpt) {
+	defer c.wg.Done()
+	status := &serverStatus{index: index, server: server}
+	var span vtrace.Span
+	ctx, span = vtrace.WithNewSpan(ctx, "<client>tryCreateFlow")
+	span.Annotatef("address:%v", server)
+	defer func() {
+		ch <- status
+		span.Finish()
+	}()
+	suberr := func(err error) *verror.SubErr {
+		return &verror.SubErr{
+			Name:    suberrName(server, name, method),
+			Err:     err,
+			Options: verror.Print,
+		}
+	}
+
+	address, suffix := naming.SplitAddressName(server)
+	if len(address) == 0 {
+		status.serverErr = suberr(verror.New(errNonRootedName, ctx, server))
+		return
+	}
+	status.suffix = suffix
+
+	ep, err := inaming.NewEndpoint(address)
+	if err != nil {
+		status.serverErr = suberr(verror.New(errInvalidEndpoint, ctx))
+		return
+	}
+	if status.flow, status.serverErr = c.createFlow(ctx, principal, ep, append(vcOpts, &vc.ServerAuthorizer{Suffix: status.suffix, Method: method, Policy: auth})); status.serverErr != nil {
+		status.serverErr.Name = suberrName(server, name, method)
+		ctx.VI(2).Infof("rpc: Failed to create Flow with %v: %v", server, status.serverErr.Err)
+		return
+	}
+
+	// Authorize the remote end of the flow using the provided authorizer.
+	if status.flow.LocalPrincipal() == nil {
+		// LocalPrincipal is nil which means we are operating under
+		// SecurityNone.
+		return
+	}
+
+	seccall := security.NewCall(&security.CallParams{
+		LocalPrincipal:   status.flow.LocalPrincipal(),
+		LocalBlessings:   status.flow.LocalBlessings(),
+		RemoteBlessings:  status.flow.RemoteBlessings(),
+		LocalEndpoint:    status.flow.LocalEndpoint(),
+		RemoteEndpoint:   status.flow.RemoteEndpoint(),
+		RemoteDischarges: status.flow.RemoteDischarges(),
+		Method:           method,
+		Suffix:           status.suffix,
+	})
+	if err := auth.Authorize(ctx, seccall); err != nil {
+		// We will test for errServerAuthorizeFailed in failedTryCall and report
+		// verror.ErrNotTrusted
+		status.serverErr = suberr(verror.New(errServerAuthorizeFailed, ctx, status.flow.RemoteBlessings(), err))
+		ctx.VI(2).Infof("rpc: Failed to authorize Flow created with server %v: %s", server, status.serverErr.Err)
+		status.flow.Close()
+		status.flow = nil
+		return
+	}
+	status.blessings, status.rejectedBlessings = security.RemoteBlessingNames(ctx, seccall)
+	return
+}
+
+// tryCall makes a single attempt at a call. It may connect to multiple servers
+// (all that serve "name"), but will invoke the method on at most one of them
+// (the server running on the most preferred protcol and network amongst all
+// the servers that were successfully connected to and authorized).
+// if requireResolve is true on return, then we shouldn't bother retrying unless
+// you can re-resolve.
+func (c *client) tryCall(ctx *context.T, name, method string, args []interface{}, opts []rpc.CallOpt) (call rpc.ClientCall, action verror.ActionCode, requireResolve bool, err error) {
+	var resolved *naming.MountEntry
+	var blessingPattern security.BlessingPattern
+	blessingPattern, name = security.SplitPatternName(name)
+	if resolved, err = c.ns.Resolve(ctx, name, getNamespaceOpts(opts)...); err != nil {
+		// We always return NoServers as the error so that the caller knows
+		// that's ok to retry the operation since the name may be registered
+		// in the near future.
+		switch {
+		case verror.ErrorID(err) == naming.ErrNoSuchName.ID:
+			return nil, verror.RetryRefetch, false, verror.New(verror.ErrNoServers, ctx, name)
+		case verror.ErrorID(err) == verror.ErrNoServers.ID:
+			// Avoid wrapping errors unnecessarily.
+			return nil, verror.NoRetry, false, err
+		case verror.ErrorID(err) == verror.ErrTimeout.ID:
+			// If the call timed out we actually want to propagate that error.
+			return nil, verror.NoRetry, false, err
+		default:
+			return nil, verror.NoRetry, false, verror.New(verror.ErrNoServers, ctx, name, err)
+		}
+	} else {
+		if len(resolved.Servers) == 0 {
+			// This should never happen.
+			return nil, verror.NoRetry, true, verror.New(verror.ErrInternal, ctx, name)
+		}
+		// An empty set of protocols means all protocols...
+		if resolved.Servers, err = filterAndOrderServers(resolved.Servers, c.preferredProtocols, c.ipNets); err != nil {
+			return nil, verror.RetryRefetch, true, verror.New(verror.ErrNoServers, ctx, name, err)
+		}
+	}
+
+	// We need to ensure calls to v23 factory methods do not occur during runtime
+	// initialization. Currently, the agent, which uses SecurityNone, is the only caller
+	// during runtime initialization. We would like to set the principal in the context
+	// to nil if we are running in SecurityNone, but this always results in a panic since
+	// the agent client would trigger the call v23.WithPrincipal during runtime
+	// initialization. So, we gate the call to v23.GetPrincipal instead since the agent
+	// client will have callEncrypted == false.
+	// Potential solutions to this are:
+	// (1) Create a separate client for the agent so that this code doesn't have to
+	//     account for its use during runtime initialization.
+	// (2) Have a ctx.IsRuntimeInitialized() method that we can additionally predicate
+	//     on here.
+	var principal security.Principal
+	if callEncrypted(opts) {
+		if principal = v23.GetPrincipal(ctx); principal == nil {
+			return nil, verror.NoRetry, false, verror.New(errNoPrincipal, ctx)
+		}
+	}
+
+	// servers is now ordered by the priority heurestic implemented in
+	// filterAndOrderServers.
+	//
+	// Try to connect to all servers in parallel.  Provide sufficient
+	// buffering for all of the connections to finish instantaneously. This
+	// is important because we want to process the responses in priority
+	// order; that order is indicated by the order of entries in servers.
+	// So, if two respones come in at the same 'instant', we prefer the
+	// first in the resolved.Servers)
+	attempts := len(resolved.Servers)
+
+	responses := make([]*serverStatus, attempts)
+	ch := make(chan *serverStatus, attempts)
+	vcOpts := append(translateVCOpts(opts), c.vcOpts...)
+	authorizer := newServerAuthorizer(blessingPattern, opts...)
+	for i, server := range resolved.Names() {
+		// Create a copy of vcOpts for each call to tryCreateFlow
+		// to avoid concurrent tryCreateFlows from stepping on each
+		// other while manipulating their copy of the options.
+		vcOptsCopy := make([]stream.VCOpt, len(vcOpts))
+		copy(vcOptsCopy, vcOpts)
+		c.mu.Lock()
+		if c.closed {
+			c.mu.Unlock()
+			return nil, verror.NoRetry, false, verror.New(errClientCloseAlreadyCalled, ctx)
+		}
+		c.wg.Add(1)
+		c.mu.Unlock()
+
+		go c.tryCreateFlow(ctx, principal, i, name, server, method, authorizer, ch, vcOptsCopy)
+	}
+
+	var timeoutChan <-chan time.Time
+	if deadline, ok := ctx.Deadline(); ok {
+		timeoutChan = time.After(deadline.Sub(time.Now()))
+	}
+
+	for {
+		// Block for at least one new response from the server, or the timeout.
+		select {
+		case r := <-ch:
+			responses[r.index] = r
+			// Read as many more responses as we can without blocking.
+		LoopNonBlocking:
+			for {
+				select {
+				default:
+					break LoopNonBlocking
+				case r := <-ch:
+					responses[r.index] = r
+				}
+			}
+		case <-timeoutChan:
+			ctx.VI(2).Infof("rpc: timeout on connection to server %v ", name)
+			_, _, _, err := c.failedTryCall(ctx, name, method, responses, ch)
+			if verror.ErrorID(err) != verror.ErrTimeout.ID {
+				return nil, verror.NoRetry, false, verror.New(verror.ErrTimeout, ctx, err)
+			}
+			return nil, verror.NoRetry, false, err
+		}
+
+		dc := c.dc
+		if shouldNotFetchDischarges(opts) {
+			dc = nil
+		}
+		// Process new responses, in priority order.
+		numResponses := 0
+		for _, r := range responses {
+			if r != nil {
+				numResponses++
+			}
+			if r == nil || r.flow == nil {
+				continue
+			}
+
+			doneChan := ctx.Done()
+			r.flow.SetDeadline(doneChan)
+			fc, err := newFlowClient(ctx, r.flow, r.blessings, dc)
+			if err != nil {
+				return nil, verror.NoRetry, false, err
+			}
+
+			if err := fc.prepareBlessingsAndDischarges(ctx, method, r.suffix, args, r.rejectedBlessings, opts); err != nil {
+				r.serverErr = &verror.SubErr{
+					Name:    suberrName(r.server, name, method),
+					Options: verror.Print,
+					Err:     verror.New(verror.ErrNotTrusted, nil, verror.New(errPrepareBlessingsAndDischarges, ctx, r.flow.RemoteBlessings(), err)),
+				}
+				ctx.VI(2).Infof("rpc: err: %s", r.serverErr)
+				r.flow.Close()
+				r.flow = nil
+				continue
+			}
+
+			// This is the 'point of no return'; once the RPC is started (fc.start
+			// below) we can't be sure if it makes it to the server or not so, this
+			// code will never call fc.start more than once to ensure that we provide
+			// 'at-most-once' rpc semantics at this level. Retrying the network
+			// connections (i.e. creating flows) is fine since we can cleanup that
+			// state if we abort a call (i.e. close the flow).
+			//
+			// We must ensure that all flows other than r.flow are closed.
+			//
+			// TODO(cnicolaou): all errors below are marked as NoRetry
+			// because we want to provide at-most-once rpc semantics so
+			// we only ever attempt an RPC once. In the future, we'll cache
+			// responses on the server and then we can retry in-flight
+			// RPCs.
+			go cleanupTryCall(r, responses, ch)
+
+			if doneChan != nil {
+				go func() {
+					select {
+					case <-doneChan:
+						vtrace.GetSpan(fc.ctx).Annotate("Canceled")
+						fc.flow.Cancel()
+					case <-fc.flow.Closed():
+					}
+				}()
+			}
+
+			deadline, _ := ctx.Deadline()
+			if verr := fc.start(r.suffix, method, args, deadline); verr != nil {
+				return nil, verror.NoRetry, false, verr
+			}
+			return fc, verror.NoRetry, false, nil
+		}
+		if numResponses == len(responses) {
+			return c.failedTryCall(ctx, name, method, responses, ch)
+		}
+	}
+}
+
+// cleanupTryCall ensures we've waited for every response from the tryCreateFlow
+// goroutines, and have closed the flow from each one except skip.  This is a
+// blocking function; it should be called in its own goroutine.
+func cleanupTryCall(skip *serverStatus, responses []*serverStatus, ch chan *serverStatus) {
+	numPending := 0
+	for _, r := range responses {
+		switch {
+		case r == nil:
+			// The response hasn't arrived yet.
+			numPending++
+		case r == skip || r.flow == nil:
+			// Either we should skip this flow, or we've closed the flow for this
+			// response already; nothing more to do.
+		default:
+			// We received the response, but haven't closed the flow yet.
+			r.flow.Close()
+		}
+	}
+	// Now we just need to wait for the pending responses and close their flows.
+	for i := 0; i < numPending; i++ {
+		if r := <-ch; r.flow != nil {
+			r.flow.Close()
+		}
+	}
+}
+
+// failedTryCall performs ©asynchronous cleanup for tryCall, and returns an
+// appropriate error from the responses we've already received.  All parallel
+// calls in tryCall failed or we timed out if we get here.
+func (c *client) failedTryCall(ctx *context.T, name, method string, responses []*serverStatus, ch chan *serverStatus) (rpc.ClientCall, verror.ActionCode, bool, error) {
+	go cleanupTryCall(nil, responses, ch)
+	c.ns.FlushCacheEntry(ctx, name)
+	suberrs := []verror.SubErr{}
+	topLevelError := verror.ErrNoServers
+	topLevelAction := verror.RetryRefetch
+	onlyErrNetwork := true
+	for _, r := range responses {
+		if r != nil && r.serverErr != nil && r.serverErr.Err != nil {
+			switch verror.ErrorID(r.serverErr.Err) {
+			case stream.ErrNotTrusted.ID, verror.ErrNotTrusted.ID, errServerAuthorizeFailed.ID:
+				topLevelError = verror.ErrNotTrusted
+				topLevelAction = verror.NoRetry
+				onlyErrNetwork = false
+			case stream.ErrAborted.ID, stream.ErrNetwork.ID:
+				// do nothing
+			default:
+				onlyErrNetwork = false
+			}
+			suberrs = append(suberrs, *r.serverErr)
+		}
+	}
+
+	if onlyErrNetwork {
+		// If we only encountered network errors, then report ErrBadProtocol.
+		topLevelError = verror.ErrBadProtocol
+	}
+
+	// TODO(cnicolaou): we get system errors for things like dialing using
+	// the 'ws' protocol which can never succeed even if we retry the connection,
+	// hence we return RetryRefetch below except for the case where the servers
+	// are not trusted, in case there's no point in retrying at all.
+	// TODO(cnicolaou): implementing at-most-once rpc semantics in the future
+	// will require thinking through all of the cases where the RPC can
+	// be retried by the client whilst it's actually being executed on the
+	// server.
+	return nil, topLevelAction, false, verror.AddSubErrs(verror.New(topLevelError, ctx), ctx, suberrs...)
+}
+
+// prepareBlessingsAndDischarges prepares blessings and discharges for
+// the call.
+//
+// This includes: (1) preparing blessings that must be granted to the
+// server, (2) preparing blessings that the client authenticates with,
+// and, (3) preparing any discharges for third-party caveats on the client's
+// blessings.
+func (fc *flowClient) prepareBlessingsAndDischarges(ctx *context.T, method, suffix string, args []interface{}, rejectedServerBlessings []security.RejectedBlessing, opts []rpc.CallOpt) error {
+	// LocalPrincipal is nil which means we are operating under
+	// SecurityNone.
+	if fc.flow.LocalPrincipal() == nil {
+		return nil
+	}
+
+	// Fetch blessings from the client's blessing store that are to be
+	// shared with the server.
+	if fc.blessings = fc.flow.LocalPrincipal().BlessingStore().ForPeer(fc.server...); fc.blessings.IsZero() {
+		// TODO(ataly, ashankar): We need not error out here and instead can just send the <nil> blessings
+		// to the server.
+		return verror.New(errNoBlessingsForPeer, fc.ctx, fc.server, rejectedServerBlessings)
+	}
+
+	// Fetch any discharges for third-party caveats on the client's blessings.
+	if !fc.blessings.IsZero() && fc.dc != nil {
+		impetus, err := mkDischargeImpetus(fc.server, method, args)
+		if err != nil {
+			return verror.New(verror.ErrBadProtocol, fc.ctx, verror.New(errDischargeImpetus, nil, err))
+		}
+		fc.discharges = fc.dc.PrepareDischarges(fc.ctx, fc.blessings.ThirdPartyCaveats(), impetus)
+	}
+
+	// Prepare blessings that must be granted to the server (using any
+	// rpc.Granter implementation in 'opts').
+	//
+	// NOTE(ataly, suharshs): Before invoking the granter, we set the parameters
+	// of the current call. The user can now retrieve the principal via
+	// v23.GetPrincipal(ctx), or via call.LocalPrincipal(). While in theory the
+	// two principals can be different, the flow.LocalPrincipal == nil check at
+	// the beginning of this method ensures that the two are the same and non-nil
+	// at this point in the code.
+	ldischargeMap := make(map[string]security.Discharge)
+	for _, d := range fc.discharges {
+		ldischargeMap[d.ID()] = d
+	}
+	seccall := security.NewCall(&security.CallParams{
+		LocalPrincipal:   fc.flow.LocalPrincipal(),
+		LocalBlessings:   fc.blessings,
+		RemoteBlessings:  fc.flow.RemoteBlessings(),
+		LocalEndpoint:    fc.flow.LocalEndpoint(),
+		RemoteEndpoint:   fc.flow.RemoteEndpoint(),
+		LocalDischarges:  ldischargeMap,
+		RemoteDischarges: fc.flow.RemoteDischarges(),
+		Method:           method,
+		Suffix:           suffix,
+	})
+	if err := fc.prepareGrantedBlessings(ctx, seccall, opts); err != nil {
+		return err
+	}
+	return nil
+}
+
+func (fc *flowClient) prepareGrantedBlessings(ctx *context.T, call security.Call, opts []rpc.CallOpt) error {
+	for _, o := range opts {
+		switch v := o.(type) {
+		case rpc.Granter:
+			if b, err := v.Grant(ctx, call); err != nil {
+				return verror.New(errBlessingGrant, fc.ctx, err)
+			} else if fc.grantedBlessings, err = security.UnionOfBlessings(fc.grantedBlessings, b); err != nil {
+				return verror.New(errBlessingAdd, fc.ctx, err)
+			}
+		}
+	}
+	return nil
+}
+
+func (c *client) Close() {
+	defer apilog.LogCall(nil)(nil) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	c.mu.Lock()
+	c.closed = true
+	c.mu.Unlock()
+	for _, v := range c.vcCache.Close() {
+		c.streamMgr.ShutdownEndpoint(v.RemoteEndpoint())
+	}
+	c.wg.Wait()
+}
+
+// flowClient implements the RPC client-side protocol for a single RPC, over a
+// flow that's already connected to the server.
+type flowClient struct {
+	ctx      *context.T   // context to annotate with call details
+	dec      *vom.Decoder // to decode responses and results from the server
+	enc      *vom.Encoder // to encode requests and args to the server
+	server   []string     // Blessings bound to the server that authorize it to receive the RPC request from the client.
+	flow     stream.Flow  // the underlying flow
+	response rpc.Response // each decoded response message is kept here
+
+	discharges []security.Discharge // discharges used for this request
+	dc         vc.DischargeClient   // client-global discharge-client
+
+	blessings        security.Blessings // the local blessings for the current RPC.
+	grantedBlessings security.Blessings // the blessings granted to the server.
+
+	sendClosedMu sync.Mutex
+	sendClosed   bool // is the send side already closed? GUARDED_BY(sendClosedMu)
+	finished     bool // has Finish() already been called?
+}
+
+var _ rpc.ClientCall = (*flowClient)(nil)
+var _ rpc.Stream = (*flowClient)(nil)
+
+func newFlowClient(ctx *context.T, flow stream.Flow, server []string, dc vc.DischargeClient) (*flowClient, error) {
+	fc := &flowClient{
+		ctx:    ctx,
+		flow:   flow,
+		server: server,
+		dc:     dc,
+	}
+	typeenc := flow.VCDataCache().Get(vc.TypeEncoderKey{})
+	if typeenc == nil {
+		fc.enc = vom.NewEncoder(flow)
+		fc.dec = vom.NewDecoder(flow)
+	} else {
+		fc.enc = vom.NewEncoderWithTypeEncoder(flow, typeenc.(*vom.TypeEncoder))
+		typedec := flow.VCDataCache().Get(vc.TypeDecoderKey{})
+		fc.dec = vom.NewDecoderWithTypeDecoder(flow, typedec.(*vom.TypeDecoder))
+	}
+	return fc, nil
+}
+
+// close determines the appropriate error to return, in particular,
+// if a timeout or cancelation has occured then any error
+// is turned into a timeout or cancelation as appropriate.
+// Cancelation takes precedence over timeout. This is needed because
+// a timeout can lead to any other number of errors due to the underlying
+// network connection being shutdown abruptly.
+func (fc *flowClient) close(err error) error {
+	subErr := verror.SubErr{Err: err, Options: verror.Print}
+	subErr.Name = "remote=" + fc.flow.RemoteEndpoint().String()
+	if cerr := fc.flow.Close(); cerr != nil && err == nil {
+		return verror.New(verror.ErrInternal, fc.ctx, subErr)
+	}
+	if err == nil {
+		return nil
+	}
+	switch verror.ErrorID(err) {
+	case verror.ErrCanceled.ID:
+		return err
+	case verror.ErrTimeout.ID:
+		// Canceled trumps timeout.
+		if fc.ctx.Err() == context.Canceled {
+			return verror.AddSubErrs(verror.New(verror.ErrCanceled, fc.ctx), fc.ctx, subErr)
+		}
+		return err
+	default:
+		switch fc.ctx.Err() {
+		case context.DeadlineExceeded:
+			timeout := verror.New(verror.ErrTimeout, fc.ctx)
+			err := verror.AddSubErrs(timeout, fc.ctx, subErr)
+			return err
+		case context.Canceled:
+			canceled := verror.New(verror.ErrCanceled, fc.ctx)
+			err := verror.AddSubErrs(canceled, fc.ctx, subErr)
+			return err
+		}
+	}
+	switch verror.ErrorID(err) {
+	case errRequestEncoding.ID, errArgEncoding.ID, errResponseDecoding.ID:
+		return verror.New(verror.ErrBadProtocol, fc.ctx, err)
+	}
+	return err
+}
+
+func (fc *flowClient) start(suffix, method string, args []interface{}, deadline time.Time) error {
+	// Encode the Blessings information for the client to authorize the flow.
+	var blessingsRequest rpc.BlessingsRequest
+	if fc.flow.LocalPrincipal() != nil {
+		blessingsRequest = clientEncodeBlessings(fc.flow.VCDataCache(), fc.blessings)
+	}
+	req := rpc.Request{
+		Suffix:           suffix,
+		Method:           method,
+		NumPosArgs:       uint64(len(args)),
+		Deadline:         vtime.Deadline{Time: deadline},
+		GrantedBlessings: fc.grantedBlessings,
+		Blessings:        blessingsRequest,
+		Discharges:       fc.discharges,
+		TraceRequest:     vtrace.GetRequest(fc.ctx),
+		Language:         string(i18n.GetLangID(fc.ctx)),
+	}
+	if err := fc.enc.Encode(req); err != nil {
+		berr := verror.New(verror.ErrBadProtocol, fc.ctx, verror.New(errRequestEncoding, fc.ctx, fmt.Sprintf("%#v", req), err))
+		return fc.close(berr)
+	}
+	for ix, arg := range args {
+		if err := fc.enc.Encode(arg); err != nil {
+			berr := verror.New(errArgEncoding, fc.ctx, ix, err)
+			return fc.close(berr)
+		}
+	}
+	return nil
+}
+
+func (fc *flowClient) Send(item interface{}) error {
+	defer apilog.LogCallf(nil, "item=")(nil, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	if fc.sendClosed {
+		return verror.New(verror.ErrAborted, fc.ctx)
+	}
+
+	// The empty request header indicates what follows is a streaming arg.
+	if err := fc.enc.Encode(rpc.Request{}); err != nil {
+		berr := verror.New(errRequestEncoding, fc.ctx, rpc.Request{}, err)
+		return fc.close(berr)
+	}
+	if err := fc.enc.Encode(item); err != nil {
+		berr := verror.New(errArgEncoding, fc.ctx, -1, err)
+		return fc.close(berr)
+	}
+	return nil
+}
+
+// decodeNetError tests for a net.Error from the lower stream code and
+// translates it into an appropriate error to be returned by the higher level
+// RPC api calls. It also tests for the net.Error being a stream.NetError
+// and if so, uses the error it stores rather than the stream.NetError itself
+// as its retrun value. This allows for the stack trace of the original
+// error to be chained to that of any verror created with it as a first parameter.
+func decodeNetError(ctx *context.T, err error) (verror.IDAction, error) {
+	if neterr, ok := err.(net.Error); ok {
+		if streamNeterr, ok := err.(*stream.NetError); ok {
+			err = streamNeterr.Err() // return the error stored in the stream.NetError
+		}
+		if neterr.Timeout() || neterr.Temporary() {
+			// If a read is canceled in the lower levels we see
+			// a timeout error - see readLocked in vc/reader.go
+			if ctx.Err() == context.Canceled {
+				return verror.ErrCanceled, err
+			}
+			return verror.ErrTimeout, err
+		}
+	}
+	if id := verror.ErrorID(err); id != verror.ErrUnknown.ID {
+		return verror.IDAction{
+			ID:     id,
+			Action: verror.Action(err),
+		}, err
+	}
+	return verror.ErrBadProtocol, err
+}
+
+func (fc *flowClient) Recv(itemptr interface{}) error {
+	defer apilog.LogCallf(nil, "itemptr=")(nil, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	switch {
+	case fc.response.Error != nil:
+		return verror.New(verror.ErrBadProtocol, fc.ctx, fc.response.Error)
+	case fc.response.EndStreamResults:
+		return io.EOF
+	}
+
+	// Decode the response header and handle errors and EOF.
+	if err := fc.dec.Decode(&fc.response); err != nil {
+		id, verr := decodeNetError(fc.ctx, err)
+		berr := verror.New(id, fc.ctx, verror.New(errResponseDecoding, fc.ctx, verr))
+		return fc.close(berr)
+	}
+	if fc.response.Error != nil {
+		return fc.response.Error
+	}
+	if fc.response.EndStreamResults {
+		// Return EOF to indicate to the caller that there are no more stream
+		// results.  Any error sent by the server is kept in fc.response.Error, and
+		// returned to the user in Finish.
+		return io.EOF
+	}
+	// Decode the streaming result.
+	if err := fc.dec.Decode(itemptr); err != nil {
+		id, verr := decodeNetError(fc.ctx, err)
+		berr := verror.New(id, fc.ctx, verror.New(errResponseDecoding, fc.ctx, verr))
+		// TODO(cnicolaou): should we be caching this?
+		fc.response.Error = berr
+		return fc.close(berr)
+	}
+	return nil
+}
+
+func (fc *flowClient) CloseSend() error {
+	defer apilog.LogCall(nil)(nil) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	return fc.closeSend()
+}
+
+// closeSend ensures CloseSend always returns verror.E.
+func (fc *flowClient) closeSend() error {
+	fc.sendClosedMu.Lock()
+	defer fc.sendClosedMu.Unlock()
+	if fc.sendClosed {
+		return nil
+	}
+	if err := fc.enc.Encode(rpc.Request{EndStreamArgs: true}); err != nil {
+		// TODO(caprita): Indiscriminately closing the flow below causes
+		// a race as described in:
+		// https://docs.google.com/a/google.com/document/d/1C0kxfYhuOcStdV7tnLZELZpUhfQCZj47B0JrzbE29h8/edit
+		//
+		// There should be a finer grained way to fix this (for example,
+		// encoding errors should probably still result in closing the
+		// flow); on the flip side, there may exist other instances
+		// where we are closing the flow but should not.
+		//
+		// For now, commenting out the line below removes the flakiness
+		// from our existing unit tests, but this needs to be revisited
+		// and fixed correctly.
+		//
+		//   return fc.close(verror.ErrBadProtocolf("rpc: end stream args encoding failed: %v", err))
+	}
+	fc.sendClosed = true
+	return nil
+}
+
+func (fc *flowClient) Finish(resultptrs ...interface{}) error {
+	defer apilog.LogCallf(nil, "resultptrs...=%v", resultptrs)(nil, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	err := fc.finish(resultptrs...)
+	vtrace.GetSpan(fc.ctx).Finish()
+	return err
+}
+
+// finish ensures Finish always returns a verror.E.
+func (fc *flowClient) finish(resultptrs ...interface{}) error {
+	if fc.finished {
+		err := verror.New(errClientFinishAlreadyCalled, fc.ctx)
+		return fc.close(verror.New(verror.ErrBadState, fc.ctx, err))
+	}
+	fc.finished = true
+
+	// Call closeSend implicitly, if the user hasn't already called it.  There are
+	// three cases:
+	// 1) Server is blocked on Recv waiting for the final request message.
+	// 2) Server has already finished processing, the final response message and
+	//    out args are queued up on the client, and the flow is closed.
+	// 3) Between 1 and 2: the server isn't blocked on Recv, but the final
+	//    response and args aren't queued up yet, and the flow isn't closed.
+	//
+	// We must call closeSend to handle case (1) and unblock the server; otherwise
+	// we'll deadlock with both client and server waiting for each other.  We must
+	// ignore the error (if any) to handle case (2).  In that case the flow is
+	// closed, meaning writes will fail and reads will succeed, and closeSend will
+	// always return an error.  But this isn't a "real" error; the client should
+	// read the rest of the results and succeed.
+	_ = fc.closeSend()
+	// Decode the response header, if it hasn't already been decoded by Recv.
+	if fc.response.Error == nil && !fc.response.EndStreamResults {
+		if err := fc.dec.Decode(&fc.response); err != nil {
+			id, verr := decodeNetError(fc.ctx, err)
+			berr := verror.New(id, fc.ctx, verror.New(errResponseDecoding, fc.ctx, verr))
+			return fc.close(berr)
+		}
+		// The response header must indicate the streaming results have ended.
+		if fc.response.Error == nil && !fc.response.EndStreamResults {
+			berr := verror.New(errRemainingStreamResults, fc.ctx)
+			return fc.close(berr)
+		}
+	}
+	if fc.response.AckBlessings {
+		clientAckBlessings(fc.flow.VCDataCache(), fc.blessings)
+	}
+	// Incorporate any VTrace info that was returned.
+	vtrace.GetStore(fc.ctx).Merge(fc.response.TraceResponse)
+	if fc.response.Error != nil {
+		id := verror.ErrorID(fc.response.Error)
+		if id == verror.ErrNoAccess.ID && fc.dc != nil {
+			// In case the error was caused by a bad discharge, we do not want to get stuck
+			// with retrying again and again with this discharge. As there is no direct way
+			// to detect it, we conservatively flush all discharges we used from the cache.
+			// TODO(ataly,andreser): add verror.BadDischarge and handle it explicitly?
+			fc.ctx.VI(3).Infof("Discarding %d discharges as RPC failed with %v", len(fc.discharges), fc.response.Error)
+			fc.dc.Invalidate(fc.ctx, fc.discharges...)
+		}
+		if id == errBadNumInputArgs.ID || id == errBadInputArg.ID {
+			return fc.close(verror.New(verror.ErrBadProtocol, fc.ctx, fc.response.Error))
+		}
+		return fc.close(verror.Convert(verror.ErrInternal, fc.ctx, fc.response.Error))
+	}
+	if got, want := fc.response.NumPosResults, uint64(len(resultptrs)); got != want {
+		berr := verror.New(verror.ErrBadProtocol, fc.ctx, verror.New(errMismatchedResults, fc.ctx, got, want))
+		return fc.close(berr)
+	}
+	for ix, r := range resultptrs {
+		if err := fc.dec.Decode(r); err != nil {
+			id, verr := decodeNetError(fc.ctx, err)
+			berr := verror.New(id, fc.ctx, verror.New(errResultDecoding, fc.ctx, ix, verr))
+			return fc.close(berr)
+		}
+	}
+	return fc.close(nil)
+}
+
+func (fc *flowClient) RemoteBlessings() ([]string, security.Blessings) {
+	defer apilog.LogCall(nil)(nil) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	return fc.server, fc.flow.RemoteBlessings()
+}
+
+func bpatterns(patterns []string) []security.BlessingPattern {
+	if patterns == nil {
+		return nil
+	}
+	bpatterns := make([]security.BlessingPattern, len(patterns))
+	for i, p := range patterns {
+		bpatterns[i] = security.BlessingPattern(p)
+	}
+	return bpatterns
+}
diff --git a/runtime/internal/rpc/consts.go b/runtime/internal/rpc/consts.go
new file mode 100644
index 0000000..b194861
--- /dev/null
+++ b/runtime/internal/rpc/consts.go
@@ -0,0 +1,20 @@
+// 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 rpc
+
+import "time"
+
+const (
+	// The publisher re-mounts on this period.
+	publishPeriod = time.Minute
+
+	// The server uses this timeout for incoming calls before the real timeout is known.
+	// The client uses this as the default max time for connecting to the server including
+	// name resolution.
+	defaultCallTimeout = time.Minute
+
+	// The client uses this as the maximum time between retry attempts when starting a call.
+	maxBackoff = time.Minute
+)
diff --git a/runtime/internal/rpc/debug_test.go b/runtime/internal/rpc/debug_test.go
new file mode 100644
index 0000000..26aabf6
--- /dev/null
+++ b/runtime/internal/rpc/debug_test.go
@@ -0,0 +1,135 @@
+// 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 rpc
+
+import (
+	"io"
+	"reflect"
+	"sort"
+	"testing"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/options"
+	"v.io/v23/rpc"
+
+	"v.io/x/ref/lib/stats"
+	"v.io/x/ref/runtime/internal/rpc/stream/manager"
+	tnaming "v.io/x/ref/runtime/internal/testing/mocks/naming"
+	"v.io/x/ref/services/debug/debuglib"
+	"v.io/x/ref/test/testutil"
+)
+
+func TestDebugServer(t *testing.T) {
+	ctx, shutdown := initForTest()
+	defer shutdown()
+	// Setup the client and server principals, with the client willing to share its
+	// blessing with the server.
+	var (
+		pclient = testutil.NewPrincipal("client")
+		pserver = testutil.NewPrincipal("server")
+		bclient = bless(pserver, pclient, "client") // server/client blessing.
+		sctx, _ = v23.WithPrincipal(ctx, pserver)
+		cctx, _ = v23.WithPrincipal(ctx, pclient)
+	)
+	pclient.AddToRoots(bclient)                    // Client recognizes "server" as a root of blessings.
+	pclient.BlessingStore().Set(bclient, "server") // Client presents bclient to server
+
+	debugDisp := debuglib.NewDispatcher(nil)
+
+	sm := manager.InternalNew(ctx, naming.FixedRoutingID(0x555555555))
+	defer sm.Shutdown()
+	ns := tnaming.NewSimpleNamespace()
+
+	server, err := testInternalNewServer(sctx, sm, ns, ReservedNameDispatcher{debugDisp})
+	if err != nil {
+		t.Fatalf("InternalNewServer failed: %v", err)
+	}
+	defer server.Stop()
+	eps, err := server.Listen(listenSpec)
+	if err != nil {
+		t.Fatalf("server.Listen failed: %v", err)
+	}
+	if err := server.Serve("", &testObject{}, nil); err != nil {
+		t.Fatalf("server.Serve failed: %v", err)
+	}
+
+	client, err := InternalNewClient(sm, ns)
+	if err != nil {
+		t.Fatalf("InternalNewClient failed: %v", err)
+	}
+	defer client.Close()
+	ep := eps[0]
+	// Call the Foo method on ""
+	{
+		var value string
+		if err := client.Call(cctx, ep.Name(), "Foo", nil, []interface{}{&value}); err != nil {
+			t.Fatalf("client.Call failed: %v", err)
+		}
+		if want := "BAR"; value != want {
+			t.Errorf("unexpected value: Got %v, want %v", value, want)
+		}
+	}
+	// Call Value on __debug/stats/testing/foo
+	{
+		foo := stats.NewString("testing/foo")
+		foo.Set("The quick brown fox jumps over the lazy dog")
+		addr := naming.JoinAddressName(ep.String(), "__debug/stats/testing/foo")
+		var value string
+		if err := client.Call(cctx, addr, "Value", nil, []interface{}{&value}, options.NoResolve{}); err != nil {
+			t.Fatalf("client.Call failed: %v", err)
+		}
+		if want := foo.Value(); value != want {
+			t.Errorf("unexpected result: Got %v, want %v", value, want)
+		}
+	}
+
+	// Call Glob
+	testcases := []struct {
+		name, pattern string
+		expected      []string
+	}{
+		{"", "*", []string{}},
+		{"", "__*", []string{"__debug"}},
+		{"", "__*/*", []string{"__debug/logs", "__debug/pprof", "__debug/stats", "__debug/vtrace"}},
+		{"__debug", "*", []string{"logs", "pprof", "stats", "vtrace"}},
+	}
+	for _, tc := range testcases {
+		addr := naming.JoinAddressName(ep.String(), tc.name)
+		call, err := client.StartCall(cctx, addr, rpc.GlobMethod, []interface{}{tc.pattern}, options.NoResolve{})
+		if err != nil {
+			t.Fatalf("client.StartCall failed for %q: %v", tc.name, err)
+		}
+		results := []string{}
+		for {
+			var gr naming.GlobReply
+			if err := call.Recv(&gr); err != nil {
+				if err != io.EOF {
+					t.Fatalf("Recv failed for %q: %v. Results received thus far: %q", tc.name, err, results)
+				}
+				break
+			}
+			switch v := gr.(type) {
+			case naming.GlobReplyEntry:
+				results = append(results, v.Value.Name)
+			}
+		}
+		if err := call.Finish(); err != nil {
+			t.Fatalf("call.Finish failed for %q: %v", tc.name, err)
+		}
+		sort.Strings(results)
+		if !reflect.DeepEqual(tc.expected, results) {
+			t.Errorf("unexpected results for %q. Got %v, want %v", tc.name, results, tc.expected)
+		}
+	}
+}
+
+type testObject struct {
+}
+
+func (o testObject) Foo(*context.T, rpc.ServerCall) (string, error) {
+	return "BAR", nil
+}
diff --git a/runtime/internal/rpc/discharges.go b/runtime/internal/rpc/discharges.go
new file mode 100644
index 0000000..f0b3446
--- /dev/null
+++ b/runtime/internal/rpc/discharges.go
@@ -0,0 +1,206 @@
+// 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 rpc
+
+import (
+	"sync"
+	"time"
+
+	"v.io/x/ref/lib/apilog"
+	"v.io/x/ref/runtime/internal/rpc/stream/vc"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/vdl"
+	"v.io/v23/vtrace"
+)
+
+// NoDischarges specifies that the RPC call should not fetch discharges.
+type NoDischarges struct{}
+
+func (NoDischarges) RPCCallOpt() {
+	defer apilog.LogCall(nil)(nil) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+}
+func (NoDischarges) NSOpt() {
+	defer apilog.LogCall(nil)(nil) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+}
+
+// discharger implements vc.DischargeClient.
+type dischargeClient struct {
+	c                     rpc.Client
+	defaultCtx            *context.T
+	dischargeExpiryBuffer time.Duration
+}
+
+// InternalNewDischargeClient creates a vc.DischargeClient that will be used to
+// fetch discharges to support blessings presented to a remote process.
+//
+// defaultCtx is the context used when none (nil) is explicitly provided to the
+// PrepareDischarges call. This typically happens when fetching discharges on
+// behalf of a server accepting connections, i.e., before any notion of the
+// "context" of an API call has been established.
+// dischargeExpiryBuffer specifies how much before discharge expiration we should
+// refresh discharges.
+// Attempts will be made to refresh a discharge DischargeExpiryBuffer before they expire.
+func InternalNewDischargeClient(defaultCtx *context.T, client rpc.Client, dischargeExpiryBuffer time.Duration) vc.DischargeClient {
+	return &dischargeClient{
+		c:                     client,
+		defaultCtx:            defaultCtx,
+		dischargeExpiryBuffer: dischargeExpiryBuffer,
+	}
+}
+
+func (*dischargeClient) RPCStreamListenerOpt() {}
+func (*dischargeClient) RPCStreamVCOpt()       {}
+
+// PrepareDischarges retrieves the caveat discharges required for using blessings
+// at server. The discharges are either found in the dischargeCache, in the call
+// options, or requested from the discharge issuer indicated on the caveat.
+// Note that requesting a discharge is an rpc call, so one copy of this
+// function must be able to successfully terminate while another is blocked.
+func (d *dischargeClient) PrepareDischarges(ctx *context.T, forcaveats []security.Caveat, impetus security.DischargeImpetus) (ret []security.Discharge) {
+	if len(forcaveats) == 0 {
+		return
+	}
+	// Make a copy since this copy will be mutated.
+	var caveats []security.Caveat
+	var filteredImpetuses []security.DischargeImpetus
+	for _, cav := range forcaveats {
+		// It shouldn't happen, but in case there are non-third-party
+		// caveats, drop them.
+		if tp := cav.ThirdPartyDetails(); tp != nil {
+			caveats = append(caveats, cav)
+			filteredImpetuses = append(filteredImpetuses, filteredImpetus(tp.Requirements(), impetus))
+		}
+	}
+
+	if ctx == nil {
+		ctx = d.defaultCtx
+	}
+	bstore := v23.GetPrincipal(ctx).BlessingStore()
+	// Gather discharges from cache.
+	discharges, rem := discharges(bstore, caveats, impetus)
+	if rem > 0 {
+		// Fetch discharges for caveats for which no discharges were
+		// found in the cache.
+		if ctx != nil {
+			var span vtrace.Span
+			ctx, span = vtrace.WithNewSpan(ctx, "Fetching Discharges")
+			defer span.Finish()
+		}
+		d.fetchDischarges(ctx, caveats, filteredImpetuses, discharges)
+	}
+	for _, d := range discharges {
+		if d.ID() != "" {
+			ret = append(ret, d)
+		}
+	}
+	return
+}
+
+func discharges(bs security.BlessingStore, caveats []security.Caveat, imp security.DischargeImpetus) (out []security.Discharge, rem int) {
+	out = make([]security.Discharge, len(caveats))
+	for i := range caveats {
+		out[i] = bs.Discharge(caveats[i], imp)
+		if out[i].ID() == "" {
+			rem++
+		}
+	}
+	return
+}
+
+func (d *dischargeClient) Invalidate(ctx *context.T, discharges ...security.Discharge) {
+	if ctx == nil {
+		ctx = d.defaultCtx
+	}
+	bstore := v23.GetPrincipal(ctx).BlessingStore()
+	bstore.ClearDischarges(discharges...)
+}
+
+// fetchDischarges fills out by fetching discharges for caveats from the
+// appropriate discharge service. Since there may be dependencies in the
+// caveats, fetchDischarges keeps retrying until either all discharges can be
+// fetched or no new discharges are fetched.
+// REQUIRES: len(caveats) == len(out)
+// REQUIRES: caveats[i].ThirdPartyDetails() != nil for 0 <= i < len(caveats)
+func (d *dischargeClient) fetchDischarges(ctx *context.T, caveats []security.Caveat, impetuses []security.DischargeImpetus, out []security.Discharge) {
+	bstore := v23.GetPrincipal(ctx).BlessingStore()
+	var wg sync.WaitGroup
+	for {
+		type fetched struct {
+			idx       int
+			discharge security.Discharge
+			caveat    security.Caveat
+			impetus   security.DischargeImpetus
+		}
+		discharges := make(chan fetched, len(caveats))
+		want := 0
+		for i := range caveats {
+			if !d.shouldFetchDischarge(out[i]) {
+				continue
+			}
+			want++
+			wg.Add(1)
+			go func(i int, ctx *context.T, cav security.Caveat) {
+				defer wg.Done()
+				tp := cav.ThirdPartyDetails()
+				var dis security.Discharge
+				ctx.VI(3).Infof("Fetching discharge for %v", tp)
+				if err := d.c.Call(ctx, tp.Location(), "Discharge", []interface{}{cav, impetuses[i]}, []interface{}{&dis}, NoDischarges{}); err != nil {
+					ctx.VI(3).Infof("Discharge fetch for %v failed: %v", tp, err)
+					return
+				}
+				discharges <- fetched{i, dis, caveats[i], impetuses[i]}
+			}(i, ctx, caveats[i])
+		}
+		wg.Wait()
+		close(discharges)
+		var got int
+		for fetched := range discharges {
+			bstore.CacheDischarge(fetched.discharge, fetched.caveat, fetched.impetus)
+			out[fetched.idx] = fetched.discharge
+			got++
+		}
+		if want > 0 {
+			ctx.VI(3).Infof("fetchDischarges: got %d of %d discharge(s) (total %d caveats)", got, want, len(caveats))
+		}
+		if got == 0 || got == want {
+			return
+		}
+	}
+}
+
+// filteredImpetus returns a copy of 'before' after removing any values that are not required as per 'r'.
+func filteredImpetus(r security.ThirdPartyRequirements, before security.DischargeImpetus) (after security.DischargeImpetus) {
+	if r.ReportServer && len(before.Server) > 0 {
+		after.Server = make([]security.BlessingPattern, len(before.Server))
+		for i := range before.Server {
+			after.Server[i] = before.Server[i]
+		}
+	}
+	if r.ReportMethod {
+		after.Method = before.Method
+	}
+	if r.ReportArguments && len(before.Arguments) > 0 {
+		after.Arguments = make([]*vdl.Value, len(before.Arguments))
+		for i := range before.Arguments {
+			after.Arguments[i] = vdl.CopyValue(before.Arguments[i])
+		}
+	}
+	return
+}
+
+func (d *dischargeClient) shouldFetchDischarge(dis security.Discharge) bool {
+	if dis.ID() == "" {
+		return true
+	}
+	expiry := dis.Expiry()
+	if expiry.IsZero() {
+		return false
+	}
+	return expiry.Before(time.Now().Add(d.dischargeExpiryBuffer))
+}
diff --git a/runtime/internal/rpc/errors.vdl b/runtime/internal/rpc/errors.vdl
new file mode 100644
index 0000000..8f24707
--- /dev/null
+++ b/runtime/internal/rpc/errors.vdl
@@ -0,0 +1,30 @@
+// 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 rpc
+
+error (
+	// Internal errors.
+	badRequest(err error) {
+		"en": "failed to decode request: {err}",
+	}
+	badNumInputArgs(suffix, method string, numCalled, numWanted uint64) {
+		"en": "wrong number of input arguments for {suffix}.{method} (called with {numCalled} args, want {numWanted})",
+	}
+	badInputArg(suffix, method string, index uint64, err error) {
+		"en": "failed to decode request {suffix}.{method} arg #{index}: {err}",
+	}
+	badBlessings(err error) {
+		"en": "failed to decode blessings: {err}",
+	}
+	badBlessingsCache(err error) {
+		"en": "failed to find blessings in cache: {err}",
+	}
+	badDischarge(index uint64, err error) {
+		"en": "failed to decode discharge #{index}: {err}",
+	}
+	badAuth(suffix, method string, err error) {
+		"en": "not authorized to call {suffix}.{method}: {err}",
+	}
+)
diff --git a/runtime/internal/rpc/errors.vdl.go b/runtime/internal/rpc/errors.vdl.go
new file mode 100644
index 0000000..2c543d7
--- /dev/null
+++ b/runtime/internal/rpc/errors.vdl.go
@@ -0,0 +1,71 @@
+// 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 by the vanadium vdl tool.
+// Source: errors.vdl
+
+package rpc
+
+import (
+	// VDL system imports
+	"v.io/v23/context"
+	"v.io/v23/i18n"
+	"v.io/v23/verror"
+)
+
+var (
+	// Internal errors.
+	errBadRequest        = verror.Register("v.io/x/ref/runtime/internal/rpc.badRequest", verror.NoRetry, "{1:}{2:} failed to decode request: {3}")
+	errBadNumInputArgs   = verror.Register("v.io/x/ref/runtime/internal/rpc.badNumInputArgs", verror.NoRetry, "{1:}{2:} wrong number of input arguments for {3}.{4} (called with {5} args, want {6})")
+	errBadInputArg       = verror.Register("v.io/x/ref/runtime/internal/rpc.badInputArg", verror.NoRetry, "{1:}{2:} failed to decode request {3}.{4} arg #{5}: {6}")
+	errBadBlessings      = verror.Register("v.io/x/ref/runtime/internal/rpc.badBlessings", verror.NoRetry, "{1:}{2:} failed to decode blessings: {3}")
+	errBadBlessingsCache = verror.Register("v.io/x/ref/runtime/internal/rpc.badBlessingsCache", verror.NoRetry, "{1:}{2:} failed to find blessings in cache: {3}")
+	errBadDischarge      = verror.Register("v.io/x/ref/runtime/internal/rpc.badDischarge", verror.NoRetry, "{1:}{2:} failed to decode discharge #{3}: {4}")
+	errBadAuth           = verror.Register("v.io/x/ref/runtime/internal/rpc.badAuth", verror.NoRetry, "{1:}{2:} not authorized to call {3}.{4}: {5}")
+)
+
+func init() {
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(errBadRequest.ID), "{1:}{2:} failed to decode request: {3}")
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(errBadNumInputArgs.ID), "{1:}{2:} wrong number of input arguments for {3}.{4} (called with {5} args, want {6})")
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(errBadInputArg.ID), "{1:}{2:} failed to decode request {3}.{4} arg #{5}: {6}")
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(errBadBlessings.ID), "{1:}{2:} failed to decode blessings: {3}")
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(errBadBlessingsCache.ID), "{1:}{2:} failed to find blessings in cache: {3}")
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(errBadDischarge.ID), "{1:}{2:} failed to decode discharge #{3}: {4}")
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(errBadAuth.ID), "{1:}{2:} not authorized to call {3}.{4}: {5}")
+}
+
+// newErrBadRequest returns an error with the errBadRequest ID.
+func newErrBadRequest(ctx *context.T, err error) error {
+	return verror.New(errBadRequest, ctx, err)
+}
+
+// newErrBadNumInputArgs returns an error with the errBadNumInputArgs ID.
+func newErrBadNumInputArgs(ctx *context.T, suffix string, method string, numCalled uint64, numWanted uint64) error {
+	return verror.New(errBadNumInputArgs, ctx, suffix, method, numCalled, numWanted)
+}
+
+// newErrBadInputArg returns an error with the errBadInputArg ID.
+func newErrBadInputArg(ctx *context.T, suffix string, method string, index uint64, err error) error {
+	return verror.New(errBadInputArg, ctx, suffix, method, index, err)
+}
+
+// newErrBadBlessings returns an error with the errBadBlessings ID.
+func newErrBadBlessings(ctx *context.T, err error) error {
+	return verror.New(errBadBlessings, ctx, err)
+}
+
+// newErrBadBlessingsCache returns an error with the errBadBlessingsCache ID.
+func newErrBadBlessingsCache(ctx *context.T, err error) error {
+	return verror.New(errBadBlessingsCache, ctx, err)
+}
+
+// newErrBadDischarge returns an error with the errBadDischarge ID.
+func newErrBadDischarge(ctx *context.T, index uint64, err error) error {
+	return verror.New(errBadDischarge, ctx, index, err)
+}
+
+// newErrBadAuth returns an error with the errBadAuth ID.
+func newErrBadAuth(ctx *context.T, suffix string, method string, err error) error {
+	return verror.New(errBadAuth, ctx, suffix, method, err)
+}
diff --git a/runtime/internal/rpc/full_test.go b/runtime/internal/rpc/full_test.go
new file mode 100644
index 0000000..2da28c4
--- /dev/null
+++ b/runtime/internal/rpc/full_test.go
@@ -0,0 +1,2136 @@
+// 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 rpc
+
+import (
+	"encoding/hex"
+	"errors"
+	"fmt"
+	"io"
+	"net"
+	"path/filepath"
+	"reflect"
+	"runtime"
+	"sort"
+	"strings"
+	"sync"
+	"testing"
+	"time"
+
+	"v.io/x/lib/netstate"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/i18n"
+	"v.io/v23/namespace"
+	"v.io/v23/naming"
+	"v.io/v23/options"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/security/access"
+	"v.io/v23/uniqueid"
+	"v.io/v23/vdl"
+	"v.io/v23/verror"
+	"v.io/v23/vtrace"
+
+	"v.io/x/ref/lib/pubsub"
+	"v.io/x/ref/lib/stats"
+	"v.io/x/ref/runtime/internal/lib/publisher"
+	"v.io/x/ref/runtime/internal/lib/websocket"
+	inaming "v.io/x/ref/runtime/internal/naming"
+	_ "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/internal/rpc/stream"
+	imanager "v.io/x/ref/runtime/internal/rpc/stream/manager"
+	"v.io/x/ref/runtime/internal/rpc/stream/vc"
+	tnaming "v.io/x/ref/runtime/internal/testing/mocks/naming"
+	"v.io/x/ref/test/testutil"
+)
+
+//go:generate v23 test generate
+
+var (
+	errMethod     = verror.New(verror.ErrAborted, nil)
+	clock         = new(fakeClock)
+	listenAddrs   = rpc.ListenAddrs{{"tcp", "127.0.0.1:0"}}
+	listenWSAddrs = rpc.ListenAddrs{{"ws", "127.0.0.1:0"}, {"tcp", "127.0.0.1:0"}}
+	listenSpec    = rpc.ListenSpec{Addrs: listenAddrs}
+	listenWSSpec  = rpc.ListenSpec{Addrs: listenWSAddrs}
+)
+
+type fakeClock struct {
+	sync.Mutex
+	time int64
+}
+
+func (c *fakeClock) Now() int64 {
+	c.Lock()
+	defer c.Unlock()
+	return c.time
+}
+
+func (c *fakeClock) Advance(steps uint) {
+	c.Lock()
+	c.time += int64(steps)
+	c.Unlock()
+}
+
+func testInternalNewServerWithPubsub(ctx *context.T, streamMgr stream.Manager, ns namespace.T, settingsPublisher *pubsub.Publisher, settingsStreamName string, opts ...rpc.ServerOpt) (rpc.Server, error) {
+	client, err := InternalNewClient(streamMgr, ns)
+	if err != nil {
+		return nil, err
+	}
+	return InternalNewServer(ctx, streamMgr, ns, settingsPublisher, settingsStreamName, client, opts...)
+}
+
+func testInternalNewServer(ctx *context.T, streamMgr stream.Manager, ns namespace.T, opts ...rpc.ServerOpt) (rpc.Server, error) {
+	return testInternalNewServerWithPubsub(ctx, streamMgr, ns, nil, "", opts...)
+}
+
+type userType string
+
+type testServer struct{}
+
+func (*testServer) Closure(*context.T, rpc.ServerCall) error {
+	return nil
+}
+
+func (*testServer) Error(*context.T, rpc.ServerCall) error {
+	return errMethod
+}
+
+func (*testServer) Echo(_ *context.T, call rpc.ServerCall, arg string) (string, error) {
+	return fmt.Sprintf("method:%q,suffix:%q,arg:%q", "Echo", call.Suffix(), arg), nil
+}
+
+func (*testServer) EchoUser(_ *context.T, call rpc.ServerCall, arg string, u userType) (string, userType, error) {
+	return fmt.Sprintf("method:%q,suffix:%q,arg:%q", "EchoUser", call.Suffix(), arg), u, nil
+}
+
+func (*testServer) EchoLang(ctx *context.T, call rpc.ServerCall) (string, error) {
+	return string(i18n.GetLangID(ctx)), nil
+}
+
+func (*testServer) EchoBlessings(ctx *context.T, call rpc.ServerCall) (server, client string, _ error) {
+	local := security.LocalBlessingNames(ctx, call.Security())
+	remote, _ := security.RemoteBlessingNames(ctx, call.Security())
+	return fmt.Sprintf("%v", local), fmt.Sprintf("%v", remote), nil
+}
+
+func (*testServer) EchoGrantedBlessings(_ *context.T, call rpc.ServerCall, arg string) (result, blessing string, _ error) {
+	return arg, fmt.Sprintf("%v", call.GrantedBlessings()), nil
+}
+
+func (*testServer) EchoAndError(_ *context.T, call rpc.ServerCall, arg string) (string, error) {
+	result := fmt.Sprintf("method:%q,suffix:%q,arg:%q", "EchoAndError", call.Suffix(), arg)
+	if arg == "error" {
+		return result, errMethod
+	}
+	return result, nil
+}
+
+func (*testServer) Stream(_ *context.T, call rpc.StreamServerCall, arg string) (string, error) {
+	result := fmt.Sprintf("method:%q,suffix:%q,arg:%q", "Stream", call.Suffix(), arg)
+	var u userType
+	var err error
+	for err = call.Recv(&u); err == nil; err = call.Recv(&u) {
+		result += " " + string(u)
+		if err := call.Send(u); err != nil {
+			return "", err
+		}
+	}
+	if err == io.EOF {
+		err = nil
+	}
+	return result, err
+}
+
+func (*testServer) Unauthorized(*context.T, rpc.StreamServerCall) (string, error) {
+	return "UnauthorizedResult", nil
+}
+
+type testServerAuthorizer struct{}
+
+func (testServerAuthorizer) Authorize(ctx *context.T, call security.Call) error {
+	// Verify that the Call object seen by the authorizer
+	// has the necessary fields.
+	lb := call.LocalBlessings()
+	if lb.IsZero() {
+		return fmt.Errorf("testServerAuthorzer: Call object %v has no LocalBlessings", call)
+	}
+	if tpcavs := lb.ThirdPartyCaveats(); len(tpcavs) > 0 && call.LocalDischarges() == nil {
+		return fmt.Errorf("testServerAuthorzer: Call object %v has no LocalDischarges even when LocalBlessings have third-party caveats", call)
+
+	}
+	if call.LocalPrincipal() == nil {
+		return fmt.Errorf("testServerAuthorzer: Call object %v has no LocalPrincipal", call)
+	}
+	if call.Method() == "" {
+		return fmt.Errorf("testServerAuthorzer: Call object %v has no Method", call)
+	}
+	if call.LocalEndpoint() == nil {
+		return fmt.Errorf("testServerAuthorzer: Call object %v has no LocalEndpoint", call)
+	}
+	if call.RemoteEndpoint() == nil {
+		return fmt.Errorf("testServerAuthorzer: Call object %v has no RemoteEndpoint", call)
+	}
+
+	// Do not authorize the method "Unauthorized".
+	if call.Method() == "Unauthorized" {
+		return fmt.Errorf("testServerAuthorizer denied access")
+	}
+	return nil
+}
+
+type testServerDisp struct{ server interface{} }
+
+func (t testServerDisp) Lookup(_ *context.T, suffix string) (interface{}, security.Authorizer, error) {
+	// If suffix is "nilAuth" we use default authorization, if it is "aclAuth" we
+	// use an AccessList-based authorizer, and otherwise we use the custom testServerAuthorizer.
+	var authorizer security.Authorizer
+	switch suffix {
+	case "discharger":
+		return &dischargeServer{}, testServerAuthorizer{}, nil
+	case "nilAuth":
+		authorizer = nil
+	case "aclAuth":
+		authorizer = &access.AccessList{
+			In: []security.BlessingPattern{"client", "server"},
+		}
+	default:
+		authorizer = testServerAuthorizer{}
+	}
+	return t.server, authorizer, nil
+}
+
+type dischargeServer struct {
+	mu     sync.Mutex
+	called bool
+}
+
+func (ds *dischargeServer) Discharge(ctx *context.T, call rpc.StreamServerCall, cav security.Caveat, _ security.DischargeImpetus) (security.Discharge, error) {
+	ds.mu.Lock()
+	ds.called = true
+	ds.mu.Unlock()
+	tp := cav.ThirdPartyDetails()
+	if tp == nil {
+		return security.Discharge{}, fmt.Errorf("discharger: %v does not represent a third-party caveat", cav)
+	}
+	if err := tp.Dischargeable(ctx, call.Security()); err != nil {
+		return security.Discharge{}, fmt.Errorf("third-party caveat %v cannot be discharged for this context: %v", cav, err)
+	}
+	// Add a fakeTimeCaveat to be able to control discharge expiration via 'clock'.
+	expiry, err := security.NewCaveat(fakeTimeCaveat, clock.Now())
+	if err != nil {
+		return security.Discharge{}, fmt.Errorf("failed to create an expiration on the discharge: %v", err)
+	}
+	return call.Security().LocalPrincipal().MintDischarge(cav, expiry)
+}
+
+func startServer(t *testing.T, ctx *context.T, principal security.Principal, sm stream.Manager, ns namespace.T, name string, disp rpc.Dispatcher, opts ...rpc.ServerOpt) (naming.Endpoint, rpc.Server) {
+	return startServerWS(t, ctx, principal, sm, ns, name, disp, noWebsocket, opts...)
+}
+
+func endpointsToStrings(eps []naming.Endpoint) []string {
+	r := make([]string, len(eps))
+	for i, e := range eps {
+		r[i] = e.String()
+	}
+	sort.Strings(r)
+	return r
+}
+
+func startServerWS(t *testing.T, ctx *context.T, principal security.Principal, sm stream.Manager, ns namespace.T, name string, disp rpc.Dispatcher, shouldUseWebsocket websocketMode, opts ...rpc.ServerOpt) (naming.Endpoint, rpc.Server) {
+	ctx.VI(1).Info("InternalNewServer")
+	ctx, _ = v23.WithPrincipal(ctx, principal)
+	server, err := testInternalNewServer(ctx, sm, ns, opts...)
+	if err != nil {
+		t.Errorf("InternalNewServer failed: %v", err)
+	}
+	ctx.VI(1).Info("server.Listen")
+	spec := listenSpec
+	if shouldUseWebsocket {
+		spec = listenWSSpec
+	}
+	eps, err := server.Listen(spec)
+	if err != nil {
+		t.Errorf("server.Listen failed: %v", err)
+	}
+	ctx.VI(1).Info("server.Serve")
+	if err := server.ServeDispatcher(name, disp); err != nil {
+		t.Errorf("server.ServeDispatcher failed: %v", err)
+	}
+
+	status := server.Status()
+	if got, want := endpointsToStrings(status.Endpoints), endpointsToStrings(eps); !reflect.DeepEqual(got, want) {
+		t.Fatalf("got %v, want %v", got, want)
+	}
+	names := status.Mounts.Names()
+	if len(names) != 1 || names[0] != name {
+		t.Fatalf("unexpected names: %v", names)
+	}
+	return eps[0], server
+}
+
+func loc(d int) string {
+	_, file, line, _ := runtime.Caller(d + 1)
+	return fmt.Sprintf("%s:%d", filepath.Base(file), line)
+}
+
+func verifyMount(t *testing.T, ctx *context.T, ns namespace.T, name string) []string {
+	for {
+		me, err := ns.Resolve(ctx, name)
+		if err == nil {
+			return me.Names()
+		}
+		time.Sleep(10 * time.Millisecond)
+	}
+}
+
+func verifyMountMissing(t *testing.T, ctx *context.T, ns namespace.T, name string) {
+	for {
+		if _, err := ns.Resolve(ctx, name); err != nil {
+			// Assume that any error (since we're using a mock namespace) means
+			// that the name is no longer present.
+			return
+		}
+		time.Sleep(10 * time.Millisecond)
+	}
+}
+
+func stopServer(t *testing.T, ctx *context.T, server rpc.Server, ns namespace.T, name string) {
+	ctx.VI(1).Info("server.Stop")
+	new_name := "should_appear_in_mt/server"
+	verifyMount(t, ctx, ns, name)
+
+	// publish a second name
+	if err := server.AddName(new_name); err != nil {
+		t.Errorf("server.Serve failed: %v", err)
+	}
+	verifyMount(t, ctx, ns, new_name)
+
+	if err := server.Stop(); err != nil {
+		t.Errorf("server.Stop failed: %v", err)
+	}
+
+	verifyMountMissing(t, ctx, ns, name)
+	verifyMountMissing(t, ctx, ns, new_name)
+
+	// Check that we can no longer serve after Stop.
+	err := server.AddName("name doesn't matter")
+	if err == nil || verror.ErrorID(err) != verror.ErrBadState.ID {
+		t.Errorf("either no error, or a wrong error was returned: %v", err)
+	}
+	ctx.VI(1).Info("server.Stop DONE")
+}
+
+// fakeWSName creates a name containing a endpoint address that forces
+// the use of websockets. It does so by resolving the original name
+// and choosing the 'ws' endpoint from the set of endpoints returned.
+// It must return a name since it'll be passed to StartCall.
+func fakeWSName(ctx *context.T, ns namespace.T, name string) (string, error) {
+	// Find the ws endpoint and use that.
+	me, err := ns.Resolve(ctx, name)
+	if err != nil {
+		return "", err
+	}
+	names := me.Names()
+	for _, s := range names {
+		if strings.Index(s, "@ws@") != -1 {
+			return s, nil
+		}
+	}
+	return "", fmt.Errorf("No ws endpoint found %v", names)
+}
+
+type bundle struct {
+	client rpc.Client
+	server rpc.Server
+	ep     naming.Endpoint
+	ns     namespace.T
+	sm     stream.Manager
+	name   string
+}
+
+func (b bundle) cleanup(t *testing.T, ctx *context.T) {
+	if b.server != nil {
+		stopServer(t, ctx, b.server, b.ns, b.name)
+	}
+	if b.client != nil {
+		b.client.Close()
+	}
+}
+
+func createBundle(t *testing.T, ctx *context.T, server security.Principal, ts interface{}) (b bundle) {
+	return createBundleWS(t, ctx, server, ts, noWebsocket)
+}
+
+func createBundleWS(t *testing.T, ctx *context.T, server security.Principal, ts interface{}, shouldUseWebsocket websocketMode) (b bundle) {
+	b.sm = imanager.InternalNew(ctx, naming.FixedRoutingID(0x555555555))
+	b.ns = tnaming.NewSimpleNamespace()
+	b.name = "mountpoint/server"
+	if server != nil {
+		b.ep, b.server = startServerWS(t, ctx, server, b.sm, b.ns, b.name, testServerDisp{ts}, shouldUseWebsocket)
+	}
+	var err error
+	if b.client, err = InternalNewClient(b.sm, b.ns); err != nil {
+		t.Fatalf("InternalNewClient failed: %v", err)
+	}
+	return
+}
+
+func matchesErrorPattern(err error, id verror.IDAction, pattern string) bool {
+	if len(pattern) > 0 && err != nil && strings.Index(err.Error(), pattern) < 0 {
+		return false
+	}
+	if err == nil && id.ID == "" {
+		return true
+	}
+	return verror.ErrorID(err) == id.ID
+}
+
+func runServer(t *testing.T, ctx *context.T, ns namespace.T, name string, obj interface{}, opts ...rpc.ServerOpt) stream.Manager {
+	rid, err := naming.NewRoutingID()
+	if err != nil {
+		t.Fatal(err)
+	}
+	sm := imanager.InternalNew(ctx, rid)
+	server, err := testInternalNewServer(ctx, sm, ns, opts...)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if _, err := server.Listen(listenSpec); err != nil {
+		t.Fatal(err)
+	}
+	if err := server.Serve(name, obj, security.AllowEveryone()); err != nil {
+		t.Fatal(err)
+	}
+	return sm
+}
+
+func TestMultipleCallsToServeAndName(t *testing.T) {
+	ctx, shutdown := initForTest()
+	defer shutdown()
+	sm := imanager.InternalNew(ctx, naming.FixedRoutingID(0x555555555))
+	ns := tnaming.NewSimpleNamespace()
+	sctx, _ := v23.WithPrincipal(ctx, testutil.NewPrincipal("server"))
+	server, err := testInternalNewServer(sctx, sm, ns)
+	if err != nil {
+		t.Errorf("InternalNewServer failed: %v", err)
+	}
+	_, err = server.Listen(listenSpec)
+	if err != nil {
+		t.Errorf("server.Listen failed: %v", err)
+	}
+
+	disp := &testServerDisp{&testServer{}}
+	if err := server.ServeDispatcher("mountpoint/server", disp); err != nil {
+		t.Errorf("server.ServeDispatcher failed: %v", err)
+	}
+
+	n1 := "mountpoint/server"
+	n2 := "should_appear_in_mt/server"
+	n3 := "should_appear_in_mt/server"
+	n4 := "should_not_appear_in_mt/server"
+
+	verifyMount(t, ctx, ns, n1)
+
+	if server.ServeDispatcher(n2, disp) == nil {
+		t.Errorf("server.ServeDispatcher should have failed")
+	}
+
+	if err := server.Serve(n2, &testServer{}, nil); err == nil {
+		t.Errorf("server.Serve should have failed")
+	}
+
+	if err := server.AddName(n3); err != nil {
+		t.Errorf("server.AddName failed: %v", err)
+	}
+
+	if err := server.AddName(n3); err != nil {
+		t.Errorf("server.AddName failed: %v", err)
+	}
+	verifyMount(t, ctx, ns, n2)
+	verifyMount(t, ctx, ns, n3)
+
+	server.RemoveName(n1)
+	verifyMountMissing(t, ctx, ns, n1)
+
+	server.RemoveName("some randome name")
+
+	if err := server.ServeDispatcher(n4, &testServerDisp{&testServer{}}); err == nil {
+		t.Errorf("server.ServeDispatcher should have failed")
+	}
+	verifyMountMissing(t, ctx, ns, n4)
+
+	if err := server.Stop(); err != nil {
+		t.Errorf("server.Stop failed: %v", err)
+	}
+
+	verifyMountMissing(t, ctx, ns, n1)
+	verifyMountMissing(t, ctx, ns, n2)
+	verifyMountMissing(t, ctx, ns, n3)
+}
+
+func TestRPCServerAuthorization(t *testing.T) {
+	ctx, shutdown := initForTest()
+	defer shutdown()
+
+	const (
+		publicKeyErr        = "not matched by server key"
+		missingDischargeErr = "missing discharge"
+		expiryErr           = "is after expiry"
+		allowedErr          = "do not match any allowed server patterns"
+	)
+	type O []rpc.CallOpt // shorthand
+	var (
+		pprovider, pclient, pserver = testutil.NewPrincipal("root"), testutil.NewPrincipal(), testutil.NewPrincipal()
+		pdischarger                 = pprovider
+		now                         = time.Now()
+		noErrID                     verror.IDAction
+
+		// Third-party caveats on blessings presented by server.
+		cavTPValid   = mkThirdPartyCaveat(pdischarger.PublicKey(), "mountpoint/dischargeserver", mkCaveat(security.NewExpiryCaveat(now.Add(24*time.Hour))))
+		cavTPExpired = mkThirdPartyCaveat(pdischarger.PublicKey(), "mountpoint/dischargeserver", mkCaveat(security.NewExpiryCaveat(now.Add(-1*time.Second))))
+
+		// Server blessings.
+		bServer          = bless(pprovider, pserver, "server")
+		bServerExpired   = bless(pprovider, pserver, "expiredserver", mkCaveat(security.NewExpiryCaveat(time.Now().Add(-1*time.Second))))
+		bServerTPValid   = bless(pprovider, pserver, "serverWithTPCaveats", cavTPValid)
+		bServerTPExpired = bless(pprovider, pserver, "serverWithExpiredTPCaveats", cavTPExpired)
+		bOther           = bless(pprovider, pserver, "other")
+		bTwoBlessings, _ = security.UnionOfBlessings(bServer, bOther)
+
+		mgr   = imanager.InternalNew(ctx, naming.FixedRoutingID(0x1111111))
+		ns    = tnaming.NewSimpleNamespace()
+		tests = []struct {
+			server security.Blessings // blessings presented by the server to the client.
+			name   string             // name provided by the client to StartCall
+			opts   O                  // options provided to StartCall.
+			errID  verror.IDAction
+			err    string
+		}{
+			// Client accepts talking to the server only if the
+			// server presents valid blessings (and discharges)
+			// consistent with the ones published in the endpoint.
+			{bServer, "mountpoint/server", nil, noErrID, ""},
+			{bServerTPValid, "mountpoint/server", nil, noErrID, ""},
+
+			// Client will not talk to a server that presents
+			// expired blessings or is missing discharges.
+			{bServerExpired, "mountpoint/server", nil, verror.ErrNotTrusted, expiryErr},
+			{bServerTPExpired, "mountpoint/server", nil, verror.ErrNotTrusted, missingDischargeErr},
+
+			// Testing the AllowedServersPolicy option.
+			{bServer, "mountpoint/server", O{options.AllowedServersPolicy{"otherroot"}}, verror.ErrNotTrusted, allowedErr},
+			{bServer, "mountpoint/server", O{options.AllowedServersPolicy{"root"}}, noErrID, ""},
+			{bTwoBlessings, "mountpoint/server", O{options.AllowedServersPolicy{"root/other"}}, noErrID, ""},
+
+			// Test the ServerPublicKey option.
+			{bOther, "mountpoint/server", O{options.SkipServerEndpointAuthorization{}, options.ServerPublicKey{
+				PublicKey: bOther.PublicKey(),
+			}}, noErrID, ""},
+			{bOther, "mountpoint/server", O{options.SkipServerEndpointAuthorization{}, options.ServerPublicKey{
+				PublicKey: testutil.NewPrincipal("irrelevant").PublicKey(),
+			}}, verror.ErrNotTrusted, publicKeyErr},
+
+			// Test the "paranoid" names, where the pattern is provided in the name.
+			{bServer, "__(root/server)/mountpoint/server", nil, noErrID, ""},
+			{bServer, "__(root/other)/mountpoint/server", nil, verror.ErrNotTrusted, allowedErr},
+			{bTwoBlessings, "__(root/server)/mountpoint/server", O{options.AllowedServersPolicy{"root/other"}}, noErrID, ""},
+		}
+	)
+	// Start the discharge server.
+	_, dischargeServer := startServer(t, ctx, pdischarger, mgr, ns, "mountpoint/dischargeserver", testutil.LeafDispatcher(&dischargeServer{}, security.AllowEveryone()))
+	defer stopServer(t, ctx, dischargeServer, ns, "mountpoint/dischargeserver")
+
+	// Make the client and server principals trust root certificates from
+	// pprovider
+	pclient.AddToRoots(pprovider.BlessingStore().Default())
+	pserver.AddToRoots(pprovider.BlessingStore().Default())
+	// Set a blessing that the client is willing to share with servers
+	// (that are blessed by pprovider).
+	pclient.BlessingStore().Set(bless(pprovider, pclient, "client"), "root")
+
+	clientCtx, _ := v23.WithPrincipal(ctx, pclient)
+	client, err := InternalNewClient(mgr, ns)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer client.Close()
+
+	var server rpc.Server
+	stop := func() {
+		if server != nil {
+			stopServer(t, ctx, server, ns, "mountpoint/server")
+		}
+	}
+	defer stop()
+	for i, test := range tests {
+		stop() // Stop any server started in the previous test.
+		name := fmt.Sprintf("(#%d: Name:%q, Server:%q, opts:%v)", i, test.name, test.server, test.opts)
+		if err := pserver.BlessingStore().SetDefault(test.server); err != nil {
+			t.Fatalf("SetDefault failed on server's BlessingStore: %v", err)
+		}
+		if _, err := pserver.BlessingStore().Set(test.server, "root"); err != nil {
+			t.Fatalf("Set failed on server's BlessingStore: %v", err)
+		}
+		_, server = startServer(t, ctx, pserver, mgr, ns, "mountpoint/server", testServerDisp{&testServer{}})
+		clientCtx, cancel := context.WithCancel(clientCtx)
+		call, err := client.StartCall(clientCtx, test.name, "Method", nil, test.opts...)
+		if !matchesErrorPattern(err, test.errID, test.err) {
+			t.Errorf(`%s: client.StartCall: got error "%v", want to match "%v"`, name, err, test.err)
+		} else if call != nil {
+			blessings, proof := call.RemoteBlessings()
+			if proof.IsZero() {
+				t.Errorf("%s: Returned zero value for remote blessings", name)
+			}
+			// Currently all tests are configured so that the only
+			// blessings presented by the server that are
+			// recognized by the client match the pattern
+			// "root"
+			if len(blessings) < 1 || !security.BlessingPattern("root").MatchedBy(blessings...) {
+				t.Errorf("%s: Client sees server as %v, expected a single blessing matching root", name, blessings)
+			}
+		}
+		cancel()
+	}
+}
+
+func TestServerManInTheMiddleAttack(t *testing.T) {
+	ctx, shutdown := initForTest()
+	defer shutdown()
+	// Test scenario: A server mounts itself, but then some other service
+	// somehow "takes over" the network endpoint (a naughty router
+	// perhaps), thus trying to steal traffic.
+	var (
+		pclient        = testutil.NewPrincipal("client")
+		pserver        = testutil.NewPrincipal("server")
+		pattacker      = testutil.NewPrincipal("attacker")
+		attackerCtx, _ = v23.WithPrincipal(ctx, pattacker)
+		cctx, _        = v23.WithPrincipal(ctx, pclient)
+	)
+	// Client recognizes both the server and the attacker's blessings.
+	// (Though, it doesn't need to do the latter for the purposes of this
+	// test).
+	pclient.AddToRoots(pserver.BlessingStore().Default())
+	pclient.AddToRoots(pattacker.BlessingStore().Default())
+
+	// Start up the attacker's server.
+	attacker, err := testInternalNewServer(
+		attackerCtx,
+		imanager.InternalNew(ctx, naming.FixedRoutingID(0xaaaaaaaaaaaaaaaa)),
+		// (To prevent the attacker for legitimately mounting on the
+		// namespace that the client will use, provide it with a
+		// different namespace).
+		tnaming.NewSimpleNamespace(),
+	)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if _, err := attacker.Listen(listenSpec); err != nil {
+		t.Fatal(err)
+	}
+	if err := attacker.ServeDispatcher("mountpoint/server", testServerDisp{&testServer{}}); err != nil {
+		t.Fatal(err)
+	}
+	var ep naming.Endpoint
+	if status := attacker.Status(); len(status.Endpoints) < 1 {
+		t.Fatalf("Attacker server does not have an endpoint: %+v", status)
+	} else {
+		ep = status.Endpoints[0]
+	}
+
+	// The legitimate server would have mounted the same endpoint on the
+	// namespace, but with different blessings.
+	ns := tnaming.NewSimpleNamespace()
+	ep.(*inaming.Endpoint).Blessings = []string{"server"}
+	if err := ns.Mount(ctx, "mountpoint/server", ep.Name(), time.Hour); err != nil {
+		t.Fatal(err)
+	}
+
+	// The RPC call should fail because the blessings presented by the
+	// (attacker's) server are not consistent with the ones registered in
+	// the mounttable trusted by the client.
+	client, err := InternalNewClient(
+		imanager.InternalNew(cctx, naming.FixedRoutingID(0xcccccccccccccccc)),
+		ns)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer client.Close()
+	ctx, _ = v23.WithPrincipal(cctx, pclient)
+	if _, err := client.StartCall(cctx, "mountpoint/server", "Closure", nil); verror.ErrorID(err) != verror.ErrNotTrusted.ID {
+		t.Errorf("Got error %v (errorid=%v), want errorid=%v", err, verror.ErrorID(err), verror.ErrNotTrusted.ID)
+	}
+	// But the RPC should succeed if the client explicitly
+	// decided to skip server authorization.
+	if _, err := client.StartCall(cctx, "mountpoint/server", "Closure", nil, options.SkipServerEndpointAuthorization{}); err != nil {
+		t.Errorf("Unexpected error(%v) when skipping server authorization", err)
+	}
+}
+
+type websocketMode bool
+type closeSendMode bool
+
+const (
+	useWebsocket websocketMode = true
+	noWebsocket  websocketMode = false
+
+	closeSend   closeSendMode = true
+	noCloseSend closeSendMode = false
+)
+
+func TestRPC(t *testing.T) {
+	testRPC(t, closeSend, noWebsocket)
+}
+
+func TestRPCWithWebsocket(t *testing.T) {
+	testRPC(t, closeSend, useWebsocket)
+}
+
+// TestCloseSendOnFinish tests that Finish informs the server that no more
+// inputs will be sent by the client if CloseSend has not already done so.
+func TestRPCCloseSendOnFinish(t *testing.T) {
+	testRPC(t, noCloseSend, noWebsocket)
+}
+
+func TestRPCCloseSendOnFinishWithWebsocket(t *testing.T) {
+	testRPC(t, noCloseSend, useWebsocket)
+}
+
+func testRPC(t *testing.T, shouldCloseSend closeSendMode, shouldUseWebsocket websocketMode) {
+	ctx, shutdown := initForTest()
+	defer shutdown()
+	type v []interface{}
+	type testcase struct {
+		name       string
+		method     string
+		args       v
+		streamArgs v
+		startErr   error
+		results    v
+		finishErr  error
+	}
+	var (
+		tests = []testcase{
+			{"mountpoint/server/suffix", "Closure", nil, nil, nil, nil, nil},
+			{"mountpoint/server/suffix", "Error", nil, nil, nil, nil, errMethod},
+
+			{"mountpoint/server/suffix", "Echo", v{"foo"}, nil, nil, v{`method:"Echo",suffix:"suffix",arg:"foo"`}, nil},
+			{"mountpoint/server/suffix/abc", "Echo", v{"bar"}, nil, nil, v{`method:"Echo",suffix:"suffix/abc",arg:"bar"`}, nil},
+
+			{"mountpoint/server/suffix", "EchoUser", v{"foo", userType("bar")}, nil, nil, v{`method:"EchoUser",suffix:"suffix",arg:"foo"`, userType("bar")}, nil},
+			{"mountpoint/server/suffix/abc", "EchoUser", v{"baz", userType("bla")}, nil, nil, v{`method:"EchoUser",suffix:"suffix/abc",arg:"baz"`, userType("bla")}, nil},
+			{"mountpoint/server/suffix", "Stream", v{"foo"}, v{userType("bar"), userType("baz")}, nil, v{`method:"Stream",suffix:"suffix",arg:"foo" bar baz`}, nil},
+			{"mountpoint/server/suffix/abc", "Stream", v{"123"}, v{userType("456"), userType("789")}, nil, v{`method:"Stream",suffix:"suffix/abc",arg:"123" 456 789`}, nil},
+			{"mountpoint/server/suffix", "EchoBlessings", nil, nil, nil, v{"[server]", "[client]"}, nil},
+			{"mountpoint/server/suffix", "EchoAndError", v{"bugs bunny"}, nil, nil, v{`method:"EchoAndError",suffix:"suffix",arg:"bugs bunny"`}, nil},
+			{"mountpoint/server/suffix", "EchoAndError", v{"error"}, nil, nil, nil, errMethod},
+			{"mountpoint/server/suffix", "EchoLang", nil, nil, nil, v{"foolang"}, nil},
+		}
+		name = func(t testcase) string {
+			return fmt.Sprintf("%s.%s(%v)", t.name, t.method, t.args)
+		}
+
+		pclient, pserver = newClientServerPrincipals()
+		b                = createBundleWS(t, ctx, pserver, &testServer{}, shouldUseWebsocket)
+	)
+	defer b.cleanup(t, ctx)
+	ctx, _ = v23.WithPrincipal(ctx, pclient)
+	ctx = i18n.WithLangID(ctx, "foolang")
+	for _, test := range tests {
+		ctx.VI(1).Infof("%s client.StartCall", name(test))
+		vname := test.name
+		if shouldUseWebsocket {
+			var err error
+			vname, err = fakeWSName(ctx, b.ns, vname)
+			if err != nil && err != test.startErr {
+				t.Errorf(`%s ns.Resolve got error "%v", want "%v"`, name(test), err, test.startErr)
+				continue
+			}
+		}
+		call, err := b.client.StartCall(ctx, vname, test.method, test.args)
+		if err != test.startErr {
+			t.Errorf(`%s client.StartCall got error "%v", want "%v"`, name(test), err, test.startErr)
+			continue
+		}
+		for _, sarg := range test.streamArgs {
+			ctx.VI(1).Infof("%s client.Send(%v)", name(test), sarg)
+			if err := call.Send(sarg); err != nil {
+				t.Errorf(`%s call.Send(%v) got unexpected error "%v"`, name(test), sarg, err)
+			}
+			var u userType
+			if err := call.Recv(&u); err != nil {
+				t.Errorf(`%s call.Recv(%v) got unexpected error "%v"`, name(test), sarg, err)
+			}
+			if !reflect.DeepEqual(u, sarg) {
+				t.Errorf("%s call.Recv got value %v, want %v", name(test), u, sarg)
+			}
+		}
+		if shouldCloseSend {
+			ctx.VI(1).Infof("%s call.CloseSend", name(test))
+			// When the method does not involve streaming
+			// arguments, the server gets all the arguments in
+			// StartCall and then sends a response without
+			// (unnecessarily) waiting for a CloseSend message from
+			// the client.  If the server responds before the
+			// CloseSend call is made at the client, the CloseSend
+			// call will fail.  Thus, only check for errors on
+			// CloseSend if there are streaming arguments to begin
+			// with (i.e., only if the server is expected to wait
+			// for the CloseSend notification).
+			if err := call.CloseSend(); err != nil && len(test.streamArgs) > 0 {
+				t.Errorf(`%s call.CloseSend got unexpected error "%v"`, name(test), err)
+			}
+		}
+		ctx.VI(1).Infof("%s client.Finish", name(test))
+		results := makeResultPtrs(test.results)
+		err = call.Finish(results...)
+		if got, want := err, test.finishErr; (got == nil) != (want == nil) {
+			t.Errorf(`%s call.Finish got error "%v", want "%v'`, name(test), got, want)
+		} else if want != nil && verror.ErrorID(got) != verror.ErrorID(want) {
+			t.Errorf(`%s call.Finish got error "%v", want "%v"`, name(test), got, want)
+		}
+		checkResultPtrs(t, name(test), results, test.results)
+	}
+}
+
+func TestMultipleFinish(t *testing.T) {
+	ctx, shutdown := initForTest()
+	defer shutdown()
+	type v []interface{}
+	var (
+		pclient, pserver = newClientServerPrincipals()
+		b                = createBundle(t, ctx, pserver, &testServer{})
+	)
+	defer b.cleanup(t, ctx)
+	ctx, _ = v23.WithPrincipal(ctx, pclient)
+	call, err := b.client.StartCall(ctx, "mountpoint/server/suffix", "Echo", v{"foo"})
+	if err != nil {
+		t.Fatalf(`client.StartCall got error "%v"`, err)
+	}
+	var results string
+	err = call.Finish(&results)
+	if err != nil {
+		t.Fatalf(`call.Finish got error "%v"`, err)
+	}
+	// Calling Finish a second time should result in a useful error.
+	if err = call.Finish(&results); !matchesErrorPattern(err, verror.ErrBadState, "Finish has already been called") {
+		t.Fatalf(`got "%v", want "%v"`, err, verror.ErrBadState)
+	}
+}
+
+// granter implements rpc.Granter.
+//
+// It returns the specified (security.Blessings, error) pair if either the
+// blessing or the error is specified. Otherwise it returns a blessing
+// derived from the local blessings of the current call.
+type granter struct {
+	rpc.CallOpt
+	b   security.Blessings
+	err error
+}
+
+func (g granter) Grant(ctx *context.T, call security.Call) (security.Blessings, error) {
+	if !g.b.IsZero() || g.err != nil {
+		return g.b, g.err
+	}
+	return call.LocalPrincipal().Bless(call.RemoteBlessings().PublicKey(), call.LocalBlessings(), "blessed", security.UnconstrainedUse())
+}
+
+func TestGranter(t *testing.T) {
+	var (
+		pclient, pserver = newClientServerPrincipals()
+		ctx, shutdown    = initForTest()
+		b                = createBundle(t, ctx, pserver, &testServer{})
+	)
+	defer shutdown()
+	defer b.cleanup(t, ctx)
+
+	ctx, _ = v23.WithPrincipal(ctx, pclient)
+	tests := []struct {
+		granter                       rpc.Granter
+		startErrID, finishErrID       verror.IDAction
+		blessing, starterr, finisherr string
+	}{
+		{blessing: ""},
+		{granter: granter{b: bless(pclient, pserver, "blessed")}, blessing: "client/blessed"},
+		{granter: granter{err: errors.New("hell no")}, startErrID: verror.ErrNotTrusted, starterr: "hell no"},
+		{granter: granter{}, blessing: "client/blessed"},
+		{granter: granter{b: pclient.BlessingStore().Default()}, finishErrID: verror.ErrNoAccess, finisherr: "blessing granted not bound to this server"},
+	}
+	for i, test := range tests {
+		call, err := b.client.StartCall(ctx, "mountpoint/server/suffix", "EchoGrantedBlessings", []interface{}{"argument"}, test.granter)
+		if !matchesErrorPattern(err, test.startErrID, test.starterr) {
+			t.Errorf("%d: %+v: StartCall returned error %v", i, test, err)
+		}
+		if err != nil {
+			continue
+		}
+		var result, blessing string
+		if err = call.Finish(&result, &blessing); !matchesErrorPattern(err, test.finishErrID, test.finisherr) {
+			t.Errorf("%+v: Finish returned error %v", test, err)
+		}
+		if err != nil {
+			continue
+		}
+		if result != "argument" || blessing != test.blessing {
+			t.Errorf("%+v: Got (%q, %q)", test, result, blessing)
+		}
+	}
+}
+
+// dischargeTestServer implements the discharge service. Always fails to
+// issue a discharge, but records the impetus and traceid of the RPC call.
+type dischargeTestServer struct {
+	p       security.Principal
+	impetus []security.DischargeImpetus
+	traceid []uniqueid.Id
+}
+
+func (s *dischargeTestServer) Discharge(ctx *context.T, _ rpc.ServerCall, cav security.Caveat, impetus security.DischargeImpetus) (security.Discharge, error) {
+	s.impetus = append(s.impetus, impetus)
+	s.traceid = append(s.traceid, vtrace.GetSpan(ctx).Trace())
+	return security.Discharge{}, fmt.Errorf("discharges not issued")
+}
+
+func (s *dischargeTestServer) Release() ([]security.DischargeImpetus, []uniqueid.Id) {
+	impetus, traceid := s.impetus, s.traceid
+	s.impetus, s.traceid = nil, nil
+	return impetus, traceid
+}
+
+func TestDischargeImpetusAndContextPropagation(t *testing.T) {
+	ctx, shutdown := initForTest()
+	defer shutdown()
+	var (
+		pserver     = testutil.NewPrincipal("server")
+		pdischarger = testutil.NewPrincipal("discharger")
+		pclient     = testutil.NewPrincipal("client")
+		pctx, _     = v23.WithPrincipal(ctx, pdischarger)
+		sctx, _     = v23.WithPrincipal(ctx, pserver)
+
+		sm = imanager.InternalNew(ctx, naming.FixedRoutingID(0x555555555))
+		ns = tnaming.NewSimpleNamespace()
+	)
+
+	// Setup the client so that it shares a blessing with a third-party caveat with the server.
+	setClientBlessings := func(req security.ThirdPartyRequirements) security.Principal {
+		cav, err := security.NewPublicKeyCaveat(pdischarger.PublicKey(), "mountpoint/discharger", req, security.UnconstrainedUse())
+		if err != nil {
+			t.Fatalf("Failed to create ThirdPartyCaveat(%+v): %v", req, err)
+		}
+		b, err := pclient.BlessSelf("client_for_server", cav)
+		if err != nil {
+			t.Fatalf("BlessSelf failed: %v", err)
+		}
+		pclient.BlessingStore().Set(b, "server")
+		return pclient
+	}
+
+	// Initialize the client principal.
+	// It trusts both the application server and the discharger.
+	pclient.AddToRoots(pserver.BlessingStore().Default())
+	pclient.AddToRoots(pdischarger.BlessingStore().Default())
+
+	// Setup the discharge server.
+	var tester dischargeTestServer
+	dischargeServer, err := testInternalNewServer(pctx, sm, ns)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer dischargeServer.Stop()
+	if _, err := dischargeServer.Listen(listenSpec); err != nil {
+		t.Fatal(err)
+	}
+	if err := dischargeServer.Serve("mountpoint/discharger", &tester, &testServerAuthorizer{}); err != nil {
+		t.Fatal(err)
+	}
+
+	// Setup the application server.
+	appServer, err := testInternalNewServer(sctx, sm, ns)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer appServer.Stop()
+	eps, err := appServer.Listen(listenSpec)
+	if err != nil {
+		t.Fatal(err)
+	}
+	// TODO(bjornick,cnicolaou,ashankar): This is a hack to workaround the
+	// fact that a single Listen on the "tcp" protocol followed by a call
+	// to Serve(<name>, ...) transparently creates two endpoints (one for
+	// tcp, one for websockets) and maps both to <name> via a mount.
+	// Because all endpoints to a name are tried in a parallel, this
+	// transparency makes this test hard to follow (many discharge fetch
+	// attempts are made - one for VIF authentication, one for VC
+	// authentication and one for the actual RPC - and having them be made
+	// to two different endpoints in parallel leads to a lot of
+	// non-determinism). The last plan of record known by the author of
+	// this comment was to stop this sly creation of two endpoints and
+	// require that they be done explicitly. When that happens, this hack
+	// can go away, but till then, this workaround allows the test to be
+	// more predictable by ensuring there is only one VIF/VC/Flow to the
+	// server.
+	object := naming.JoinAddressName(eps[0].String(), "object") // instead of "mountpoint/object"
+	if err := appServer.Serve("mountpoint/object", &testServer{}, &testServerAuthorizer{}); err != nil {
+		t.Fatal(err)
+	}
+	tests := []struct {
+		Requirements security.ThirdPartyRequirements
+		Impetus      security.DischargeImpetus
+	}{
+		{ // No requirements, no impetus
+			Requirements: security.ThirdPartyRequirements{},
+			Impetus:      security.DischargeImpetus{},
+		},
+		{ // Require everything
+			Requirements: security.ThirdPartyRequirements{ReportServer: true, ReportMethod: true, ReportArguments: true},
+			Impetus:      security.DischargeImpetus{Server: []security.BlessingPattern{"server"}, Method: "Method", Arguments: []*vdl.Value{vdl.StringValue("argument")}},
+		},
+		{ // Require only the method name
+			Requirements: security.ThirdPartyRequirements{ReportMethod: true},
+			Impetus:      security.DischargeImpetus{Method: "Method"},
+		},
+	}
+
+	for _, test := range tests {
+		pclient := setClientBlessings(test.Requirements)
+		cctx, _ := v23.WithPrincipal(ctx, pclient)
+		client, err := InternalNewClient(sm, ns)
+		if err != nil {
+			t.Fatalf("InternalNewClient(%+v) failed: %v", test.Requirements, err)
+		}
+		defer client.Close()
+		tid := vtrace.GetSpan(cctx).Trace()
+		// StartCall should fetch the discharge, do not worry about finishing the RPC - do not care about that for this test.
+		if _, err := client.StartCall(cctx, object, "Method", []interface{}{"argument"}); err != nil {
+			t.Errorf("StartCall(%+v) failed: %v", test.Requirements, err)
+			continue
+		}
+		impetus, traceid := tester.Release()
+		// There should have been exactly 1 attempt to fetch discharges when making
+		// the RPC to the remote object.
+		if len(impetus) != 1 || len(traceid) != 1 {
+			t.Errorf("Test %+v: Got (%d, %d) (#impetus, #traceid), wanted exactly one", test.Requirements, len(impetus), len(traceid))
+			continue
+		}
+		// VC creation does not have any "impetus", it is established without
+		// knowledge of the context of the RPC. So ignore that.
+		//
+		// TODO(ashankar): Should the impetus of the RPC that initiated the
+		// VIF/VC creation be propagated?
+		if got, want := impetus[len(impetus)-1], test.Impetus; !reflect.DeepEqual(got, want) {
+			t.Errorf("Test %+v: Got impetus %v, want %v", test.Requirements, got, want)
+		}
+		// But the context used for all of this should be the same
+		// (thereby allowing debug traces to link VIF/VC creation with
+		// the RPC that initiated them).
+		for idx, got := range traceid {
+			if !reflect.DeepEqual(got, tid) {
+				t.Errorf("Test %+v: %d - Got trace id %q, want %q", test.Requirements, idx, hex.EncodeToString(got[:]), hex.EncodeToString(tid[:]))
+			}
+		}
+	}
+}
+
+func TestRPCClientAuthorization(t *testing.T) {
+	ctx, shutdown := initForTest()
+	defer shutdown()
+
+	type v []interface{}
+	var (
+		// Principals
+		pclient, pserver = testutil.NewPrincipal("client"), testutil.NewPrincipal("server")
+		pdischarger      = testutil.NewPrincipal("discharger")
+
+		now = time.Now()
+
+		serverName          = "mountpoint/server"
+		dischargeServerName = "mountpoint/dischargeserver"
+
+		// Caveats on blessings to the client: First-party caveats
+		cavOnlyEcho = mkCaveat(security.NewMethodCaveat("Echo"))
+		cavExpired  = mkCaveat(security.NewExpiryCaveat(now.Add(-1 * time.Second)))
+		// Caveats on blessings to the client: Third-party caveats
+		cavTPValid   = mkThirdPartyCaveat(pdischarger.PublicKey(), dischargeServerName, mkCaveat(security.NewExpiryCaveat(now.Add(24*time.Hour))))
+		cavTPExpired = mkThirdPartyCaveat(pdischarger.PublicKey(), dischargeServerName, mkCaveat(security.NewExpiryCaveat(now.Add(-1*time.Second))))
+
+		// Client blessings that will be tested.
+		bServerClientOnlyEcho  = bless(pserver, pclient, "onlyecho", cavOnlyEcho)
+		bServerClientExpired   = bless(pserver, pclient, "expired", cavExpired)
+		bServerClientTPValid   = bless(pserver, pclient, "dischargeable_third_party_caveat", cavTPValid)
+		bServerClientTPExpired = bless(pserver, pclient, "expired_third_party_caveat", cavTPExpired)
+		bClient                = pclient.BlessingStore().Default()
+		bRandom, _             = pclient.BlessSelf("random")
+
+		mgr   = imanager.InternalNew(ctx, naming.FixedRoutingID(0x1111111))
+		ns    = tnaming.NewSimpleNamespace()
+		tests = []struct {
+			blessings  security.Blessings // Blessings used by the client
+			name       string             // object name on which the method is invoked
+			method     string
+			args       v
+			results    v
+			authorized bool // Whether or not the RPC should be authorized by the server.
+		}{
+			// There are three different authorization policies (security.Authorizer implementations)
+			// used by the server, depending on the suffix (see testServerDisp.Lookup):
+			// - nilAuth suffix: the default authorization policy (only delegates of or delegators of the server can call RPCs)
+			// - aclAuth suffix: the AccessList only allows blessings matching the patterns "server" or "client"
+			// - other suffixes: testServerAuthorizer allows any principal to call any method except "Unauthorized"
+
+			// Expired blessings should fail nilAuth and aclAuth (which care about names), but should succeed on
+			// other suffixes (which allow all blessings), unless calling the Unauthorized method.
+			{bServerClientExpired, "mountpoint/server/nilAuth", "Echo", v{"foo"}, v{""}, false},
+			{bServerClientExpired, "mountpoint/server/aclAuth", "Echo", v{"foo"}, v{""}, false},
+			{bServerClientExpired, "mountpoint/server/suffix", "Echo", v{"foo"}, v{""}, true},
+			{bServerClientExpired, "mountpoint/server/suffix", "Unauthorized", nil, v{""}, false},
+
+			// Same for blessings that should fail to obtain a discharge for the third party caveat.
+			{bServerClientTPExpired, "mountpoint/server/nilAuth", "Echo", v{"foo"}, v{""}, false},
+			{bServerClientTPExpired, "mountpoint/server/aclAuth", "Echo", v{"foo"}, v{""}, false},
+			{bServerClientTPExpired, "mountpoint/server/suffix", "Echo", v{"foo"}, v{""}, true},
+			{bServerClientTPExpired, "mountpoint/server/suffix", "Unauthorized", nil, v{""}, false},
+
+			// The "server/client" blessing (with MethodCaveat("Echo")) should satisfy all authorization policies
+			// when "Echo" is called.
+			{bServerClientOnlyEcho, "mountpoint/server/nilAuth", "Echo", v{"foo"}, v{""}, true},
+			{bServerClientOnlyEcho, "mountpoint/server/aclAuth", "Echo", v{"foo"}, v{""}, true},
+			{bServerClientOnlyEcho, "mountpoint/server/suffix", "Echo", v{"foo"}, v{""}, true},
+
+			// The "server/client" blessing (with MethodCaveat("Echo")) should satisfy no authorization policy
+			// when any other method is invoked, except for the testServerAuthorizer policy (which will
+			// not recognize the blessing "server/onlyecho", but it would authorize anyone anyway).
+			{bServerClientOnlyEcho, "mountpoint/server/nilAuth", "Closure", nil, nil, false},
+			{bServerClientOnlyEcho, "mountpoint/server/aclAuth", "Closure", nil, nil, false},
+			{bServerClientOnlyEcho, "mountpoint/server/suffix", "Closure", nil, nil, true},
+
+			// The "client" blessing doesn't satisfy the default authorization policy, but does satisfy
+			// the AccessList and the testServerAuthorizer policy.
+			{bClient, "mountpoint/server/nilAuth", "Echo", v{"foo"}, v{""}, false},
+			{bClient, "mountpoint/server/aclAuth", "Echo", v{"foo"}, v{""}, true},
+			{bClient, "mountpoint/server/suffix", "Echo", v{"foo"}, v{""}, true},
+			{bClient, "mountpoint/server/suffix", "Unauthorized", nil, v{""}, false},
+
+			// The "random" blessing does not satisfy either the default policy or the AccessList, but does
+			// satisfy testServerAuthorizer.
+			{bRandom, "mountpoint/server/nilAuth", "Echo", v{"foo"}, v{""}, false},
+			{bRandom, "mountpoint/server/aclAuth", "Echo", v{"foo"}, v{""}, false},
+			{bRandom, "mountpoint/server/suffix", "Echo", v{"foo"}, v{""}, true},
+			{bRandom, "mountpoint/server/suffix", "Unauthorized", nil, v{""}, false},
+
+			// The "server/dischargeable_third_party_caveat" blessing satisfies all policies.
+			// (the discharges should be fetched).
+			{bServerClientTPValid, "mountpoint/server/nilAuth", "Echo", v{"foo"}, v{""}, true},
+			{bServerClientTPValid, "mountpoint/server/aclAuth", "Echo", v{"foo"}, v{""}, true},
+			{bServerClientTPValid, "mountpoint/server/suffix", "Echo", v{"foo"}, v{""}, true},
+			{bServerClientTPValid, "mountpoint/server/suffix", "Unauthorized", nil, v{""}, false},
+		}
+	)
+
+	// Start the main server.
+	_, server := startServer(t, ctx, pserver, mgr, ns, serverName, testServerDisp{&testServer{}})
+	defer stopServer(t, ctx, server, ns, serverName)
+
+	// Start the discharge server.
+	_, dischargeServer := startServer(t, ctx, pdischarger, mgr, ns, dischargeServerName, testutil.LeafDispatcher(&dischargeServer{}, security.AllowEveryone()))
+	defer stopServer(t, ctx, dischargeServer, ns, dischargeServerName)
+
+	// The server should recognize the client principal as an authority on "client" and "random" blessings.
+	pserver.AddToRoots(bClient)
+	pserver.AddToRoots(bRandom)
+	// And the client needs to recognize the server's and discharger's blessings to decide which of its
+	// own blessings to share.
+	pclient.AddToRoots(pserver.BlessingStore().Default())
+	pclient.AddToRoots(pdischarger.BlessingStore().Default())
+	// Set a blessing on the client's blessing store to be presented to the discharge server.
+	pclient.BlessingStore().Set(pclient.BlessingStore().Default(), "discharger")
+	// testutil.NewPrincipal sets up a principal that shares blessings with all servers, undo that.
+	pclient.BlessingStore().Set(security.Blessings{}, security.AllPrincipals)
+
+	for i, test := range tests {
+		name := fmt.Sprintf("#%d: %q.%s(%v) by %v", i, test.name, test.method, test.args, test.blessings)
+		client, err := InternalNewClient(mgr, ns)
+		if err != nil {
+			t.Fatalf("InternalNewClient failed: %v", err)
+		}
+		defer client.Close()
+
+		pclient.BlessingStore().Set(test.blessings, "server")
+		ctx, _ := v23.WithPrincipal(ctx, pclient)
+		err = client.Call(ctx, test.name, test.method, test.args, makeResultPtrs(test.results))
+		if err != nil && test.authorized {
+			t.Errorf(`%s client.Call got error: "%v", wanted the RPC to succeed`, name, err)
+		} else if err == nil && !test.authorized {
+			t.Errorf("%s call.Finish succeeded, expected authorization failure", name)
+		} else if !test.authorized && verror.ErrorID(err) != verror.ErrNoAccess.ID {
+			t.Errorf("%s. call.Finish returned error %v(%v), wanted %v", name, verror.ErrorID(verror.Convert(verror.ErrNoAccess, nil, err)), err, verror.ErrNoAccess)
+		}
+	}
+}
+
+// singleBlessingStore implements security.BlessingStore. It is a
+// BlessingStore that marks the last blessing that was set on it as
+// shareable with any peer. It does not care about the public key that
+// blessing being set is bound to.
+type singleBlessingStore struct {
+	b security.Blessings
+}
+
+func (s *singleBlessingStore) Set(b security.Blessings, _ security.BlessingPattern) (security.Blessings, error) {
+	s.b = b
+	return security.Blessings{}, nil
+}
+func (s *singleBlessingStore) ForPeer(...string) security.Blessings {
+	return s.b
+}
+func (*singleBlessingStore) SetDefault(b security.Blessings) error {
+	return nil
+}
+func (*singleBlessingStore) Default() security.Blessings {
+	return security.Blessings{}
+}
+func (*singleBlessingStore) PublicKey() security.PublicKey {
+	return nil
+}
+func (*singleBlessingStore) DebugString() string {
+	return ""
+}
+func (*singleBlessingStore) PeerBlessings() map[security.BlessingPattern]security.Blessings {
+	return nil
+}
+func (*singleBlessingStore) CacheDischarge(security.Discharge, security.Caveat, security.DischargeImpetus) {
+	return
+}
+func (*singleBlessingStore) ClearDischarges(...security.Discharge) {
+	return
+}
+func (*singleBlessingStore) Discharge(security.Caveat, security.DischargeImpetus) security.Discharge {
+	return security.Discharge{}
+}
+
+// singleBlessingPrincipal implements security.Principal. It is a wrapper over
+// a security.Principal that intercepts  all invocations on the
+// principal's BlessingStore and serves them via a singleBlessingStore.
+type singleBlessingPrincipal struct {
+	security.Principal
+	b singleBlessingStore
+}
+
+func (p *singleBlessingPrincipal) BlessingStore() security.BlessingStore {
+	return &p.b
+}
+
+func TestRPCClientBlessingsPublicKey(t *testing.T) {
+	ctx, shutdown := initForTest()
+	defer shutdown()
+	var (
+		pprovider, pserver = testutil.NewPrincipal("root"), testutil.NewPrincipal("server")
+		pclient            = &singleBlessingPrincipal{Principal: testutil.NewPrincipal("client")}
+
+		bserver = bless(pprovider, pserver, "server")
+		bclient = bless(pprovider, pclient, "client")
+		bvictim = bless(pprovider, testutil.NewPrincipal("victim"), "victim")
+	)
+	// Make the client and server trust blessings from pprovider.
+	pclient.AddToRoots(pprovider.BlessingStore().Default())
+	pserver.AddToRoots(pprovider.BlessingStore().Default())
+
+	// Make the server present bserver to all clients and start the server.
+	pserver.BlessingStore().SetDefault(bserver)
+	b := createBundle(t, ctx, pserver, &testServer{})
+	defer b.cleanup(t, ctx)
+
+	ctx, _ = v23.WithPrincipal(ctx, pclient)
+	tests := []struct {
+		blessings security.Blessings
+		errID     verror.IDAction
+		err       string
+	}{
+		{blessings: bclient},
+		// server disallows clients from authenticating with blessings not bound to
+		// the client principal's public key
+		{blessings: bvictim, errID: verror.ErrNoAccess, err: "bound to a different public key"},
+		{blessings: bserver, errID: verror.ErrNoAccess, err: "bound to a different public key"},
+	}
+	for i, test := range tests {
+		name := fmt.Sprintf("%d: Client RPCing with blessings %v", i, test.blessings)
+		pclient.BlessingStore().Set(test.blessings, "root")
+		if err := b.client.Call(ctx, "mountpoint/server/suffix", "Closure", nil, nil); !matchesErrorPattern(err, test.errID, test.err) {
+			t.Errorf("%v: client.Call returned error %v", name, err)
+			continue
+		}
+	}
+}
+
+func TestServerLocalBlessings(t *testing.T) {
+	ctx, shutdown := initForTest()
+	defer shutdown()
+	var (
+		pprovider, pclient, pserver = testutil.NewPrincipal("root"), testutil.NewPrincipal("client"), testutil.NewPrincipal("server")
+		pdischarger                 = pprovider
+
+		mgr = imanager.InternalNew(ctx, naming.FixedRoutingID(0x1111111))
+		ns  = tnaming.NewSimpleNamespace()
+
+		tpCav = mkThirdPartyCaveat(pdischarger.PublicKey(), "mountpoint/dischargeserver", mkCaveat(security.NewExpiryCaveat(time.Now().Add(time.Hour))))
+
+		bserver = bless(pprovider, pserver, "server", tpCav)
+		bclient = bless(pprovider, pclient, "client")
+	)
+	// Make the client and server principals trust root certificates from
+	// pprovider.
+	pclient.AddToRoots(pprovider.BlessingStore().Default())
+	pserver.AddToRoots(pprovider.BlessingStore().Default())
+
+	// Make the server present bserver to all clients.
+	pserver.BlessingStore().SetDefault(bserver)
+
+	// Start the server and the discharger.
+	_, server := startServer(t, ctx, pserver, mgr, ns, "mountpoint/server", testServerDisp{&testServer{}})
+	defer stopServer(t, ctx, server, ns, "mountpoint/server")
+
+	_, dischargeServer := startServer(t, ctx, pdischarger, mgr, ns, "mountpoint/dischargeserver", testutil.LeafDispatcher(&dischargeServer{}, security.AllowEveryone()))
+	defer stopServer(t, ctx, dischargeServer, ns, "mountpoint/dischargeserver")
+
+	// Make the client present bclient to all servers that are blessed
+	// by pprovider.
+	pclient.BlessingStore().Set(bclient, "root")
+	client, err := InternalNewClient(mgr, ns)
+	if err != nil {
+		t.Fatalf("InternalNewClient failed: %v", err)
+	}
+	defer client.Close()
+
+	ctx, _ = v23.WithPrincipal(ctx, pclient)
+	var gotServer, gotClient string
+	if err := client.Call(ctx, "mountpoint/server/suffix", "EchoBlessings", nil, []interface{}{&gotServer, &gotClient}); err != nil {
+		t.Fatalf("Finish failed: %v", err)
+	}
+	if wantServer, wantClient := "[root/server]", "[root/client]"; gotServer != wantServer || gotClient != wantClient {
+		t.Fatalf("EchoBlessings: got %v, %v want %v, %v", gotServer, gotClient, wantServer, wantClient)
+	}
+}
+
+func TestDischargePurgeFromCache(t *testing.T) {
+	ctx, shutdown := initForTest()
+	defer shutdown()
+
+	var (
+		pserver     = testutil.NewPrincipal("server")
+		pdischarger = pserver // In general, the discharger can be a separate principal. In this test, it happens to be the server.
+		pclient     = testutil.NewPrincipal("client")
+		// Client is blessed with a third-party caveat. The discharger service issues discharges with a fakeTimeCaveat.
+		// This blessing is presented to "server".
+		bclient = bless(pserver, pclient, "client", mkThirdPartyCaveat(pdischarger.PublicKey(), "mountpoint/server/discharger", security.UnconstrainedUse()))
+
+		b = createBundle(t, ctx, pserver, &testServer{})
+	)
+	defer b.cleanup(t, ctx)
+	// Setup the client to recognize the server's blessing and present bclient to it.
+	pclient.AddToRoots(pserver.BlessingStore().Default())
+	pclient.BlessingStore().Set(bclient, "server")
+
+	var err error
+	if b.client, err = InternalNewClient(b.sm, b.ns); err != nil {
+		t.Fatalf("InternalNewClient failed: %v", err)
+	}
+	ctx, _ = v23.WithPrincipal(ctx, pclient)
+	call := func() error {
+		var got string
+		if err := b.client.Call(ctx, "mountpoint/server/aclAuth", "Echo", []interface{}{"batman"}, []interface{}{&got}); err != nil {
+			return err
+		}
+		if want := `method:"Echo",suffix:"aclAuth",arg:"batman"`; got != want {
+			return verror.Convert(verror.ErrBadArg, nil, fmt.Errorf("Got [%v] want [%v]", got, want))
+		}
+		return nil
+	}
+
+	// First call should succeed
+	if err := call(); err != nil {
+		t.Fatal(err)
+	}
+	// Advance virtual clock, which will invalidate the discharge
+	clock.Advance(1)
+	if err, want := call(), "not authorized"; !matchesErrorPattern(err, verror.ErrNoAccess, want) {
+		t.Errorf("Got error [%v] wanted to match pattern %q", err, want)
+	}
+	// But retrying will succeed since the discharge should be purged from cache and refreshed
+	if err := call(); err != nil {
+		t.Fatal(err)
+	}
+}
+
+type cancelTestServer struct {
+	started   chan struct{}
+	cancelled chan struct{}
+	t         *testing.T
+}
+
+func newCancelTestServer(t *testing.T) *cancelTestServer {
+	return &cancelTestServer{
+		started:   make(chan struct{}),
+		cancelled: make(chan struct{}),
+		t:         t,
+	}
+}
+
+func (s *cancelTestServer) CancelStreamReader(ctx *context.T, call rpc.StreamServerCall) error {
+	close(s.started)
+	var b []byte
+	if err := call.Recv(&b); err != io.EOF {
+		s.t.Errorf("Got error %v, want io.EOF", err)
+	}
+	<-ctx.Done()
+	close(s.cancelled)
+	return nil
+}
+
+// CancelStreamIgnorer doesn't read from it's input stream so all it's
+// buffers fill.  The intention is to show that call.Done() is closed
+// even when the stream is stalled.
+func (s *cancelTestServer) CancelStreamIgnorer(ctx *context.T, _ rpc.StreamServerCall) error {
+	close(s.started)
+	<-ctx.Done()
+	close(s.cancelled)
+	return nil
+}
+
+func waitForCancel(t *testing.T, ts *cancelTestServer, cancel context.CancelFunc) {
+	<-ts.started
+	cancel()
+	<-ts.cancelled
+}
+
+// TestCancel tests cancellation while the server is reading from a stream.
+func TestCancel(t *testing.T) {
+	ctx, shutdown := initForTest()
+	defer shutdown()
+	var (
+		ts               = newCancelTestServer(t)
+		pclient, pserver = newClientServerPrincipals()
+		b                = createBundle(t, ctx, pserver, ts)
+	)
+	defer b.cleanup(t, ctx)
+
+	ctx, _ = v23.WithPrincipal(ctx, pclient)
+	ctx, cancel := context.WithCancel(ctx)
+	_, err := b.client.StartCall(ctx, "mountpoint/server/suffix", "CancelStreamReader", []interface{}{})
+	if err != nil {
+		t.Fatalf("Start call failed: %v", err)
+	}
+	waitForCancel(t, ts, cancel)
+}
+
+// TestCancelWithFullBuffers tests that even if the writer has filled the buffers and
+// the server is not reading that the cancel message gets through.
+func TestCancelWithFullBuffers(t *testing.T) {
+	ctx, shutdown := initForTest()
+	defer shutdown()
+	var (
+		ts               = newCancelTestServer(t)
+		pclient, pserver = newClientServerPrincipals()
+		b                = createBundle(t, ctx, pserver, ts)
+	)
+	defer b.cleanup(t, ctx)
+
+	ctx, _ = v23.WithPrincipal(ctx, pclient)
+	ctx, cancel := context.WithCancel(ctx)
+	call, err := b.client.StartCall(ctx, "mountpoint/server/suffix", "CancelStreamIgnorer", []interface{}{})
+	if err != nil {
+		t.Fatalf("Start call failed: %v", err)
+	}
+	// Fill up all the write buffers to ensure that cancelling works even when the stream
+	// is blocked.
+	call.Send(make([]byte, vc.MaxSharedBytes))
+	call.Send(make([]byte, vc.DefaultBytesBufferedPerFlow))
+
+	waitForCancel(t, ts, cancel)
+}
+
+type streamRecvInGoroutineServer struct{ c chan error }
+
+func (s *streamRecvInGoroutineServer) RecvInGoroutine(_ *context.T, call rpc.StreamServerCall) error {
+	// Spawn a goroutine to read streaming data from the client.
+	go func() {
+		var i interface{}
+		for {
+			err := call.Recv(&i)
+			if err != nil {
+				s.c <- err
+				return
+			}
+		}
+	}()
+	// Imagine the server did some processing here and now that it is done,
+	// it does not care to see what else the client has to say.
+	return nil
+}
+
+func TestStreamReadTerminatedByServer(t *testing.T) {
+	ctx, shutdown := initForTest()
+	defer shutdown()
+	var (
+		pclient, pserver = newClientServerPrincipals()
+		s                = &streamRecvInGoroutineServer{c: make(chan error, 1)}
+		b                = createBundle(t, ctx, pserver, s)
+	)
+	defer b.cleanup(t, ctx)
+
+	ctx, _ = v23.WithPrincipal(ctx, pclient)
+	call, err := b.client.StartCall(ctx, "mountpoint/server/suffix", "RecvInGoroutine", []interface{}{})
+	if err != nil {
+		t.Fatalf("StartCall failed: %v", err)
+	}
+
+	c := make(chan error, 1)
+	go func() {
+		for i := 0; true; i++ {
+			if err := call.Send(i); err != nil {
+				c <- err
+				return
+			}
+		}
+	}()
+
+	// The goroutine at the server executing "Recv" should have terminated
+	// with EOF.
+	if err := <-s.c; err != io.EOF {
+		t.Errorf("Got %v at server, want io.EOF", err)
+	}
+	// The client Send should have failed since the RPC has been
+	// terminated.
+	if err := <-c; err == nil {
+		t.Errorf("Client Send should fail as the server should have closed the flow")
+	}
+}
+
+// TestConnectWithIncompatibleServers tests that clients ignore incompatible endpoints.
+func TestConnectWithIncompatibleServers(t *testing.T) {
+	ctx, shutdown := initForTest()
+	defer shutdown()
+	var (
+		pclient, pserver = newClientServerPrincipals()
+		b                = createBundle(t, ctx, pserver, &testServer{})
+	)
+	defer b.cleanup(t, ctx)
+
+	// Publish some incompatible endpoints.
+	publisher := publisher.New(ctx, b.ns, publishPeriod)
+	defer publisher.WaitForStop()
+	defer publisher.Stop()
+	publisher.AddName("incompatible", false, false)
+	publisher.AddServer("/@2@tcp@localhost:10000@@1000000@2000000@@")
+	publisher.AddServer("/@2@tcp@localhost:10001@@2000000@3000000@@")
+
+	ctx, _ = v23.WithPrincipal(ctx, pclient)
+	_, err := b.client.StartCall(ctx, "incompatible/suffix", "Echo", []interface{}{"foo"}, options.NoRetry{})
+	if verror.ErrorID(err) != verror.ErrNoServers.ID {
+		t.Errorf("Expected error %v, found: %v", verror.ErrNoServers, err)
+	}
+
+	// Now add a server with a compatible endpoint and try again.
+	publisher.AddServer("/" + b.ep.String())
+	publisher.AddName("incompatible", false, false)
+
+	call, err := b.client.StartCall(ctx, "incompatible/suffix", "Echo", []interface{}{"foo"})
+	if err != nil {
+		t.Fatal(err)
+	}
+	var result string
+	if err = call.Finish(&result); err != nil {
+		t.Errorf("Unexpected error finishing call %v", err)
+	}
+	expected := `method:"Echo",suffix:"suffix",arg:"foo"`
+	if result != expected {
+		t.Errorf("Wrong result returned.  Got %s, wanted %s", result, expected)
+	}
+}
+
+func TestPreferredAddress(t *testing.T) {
+	ctx, shutdown := initForTest()
+	defer shutdown()
+	sm := imanager.InternalNew(ctx, naming.FixedRoutingID(0x555555555))
+	defer sm.Shutdown()
+	ns := tnaming.NewSimpleNamespace()
+	pa := netstate.AddressChooserFunc(func(string, []net.Addr) ([]net.Addr, error) {
+		return []net.Addr{netstate.NewNetAddr("tcp", "1.1.1.1")}, nil
+	})
+	ctx, _ = v23.WithPrincipal(ctx, testutil.NewPrincipal("server"))
+	server, err := testInternalNewServer(ctx, sm, ns)
+	if err != nil {
+		t.Errorf("InternalNewServer failed: %v", err)
+	}
+	defer server.Stop()
+
+	spec := rpc.ListenSpec{
+		Addrs:          rpc.ListenAddrs{{"tcp", ":0"}},
+		AddressChooser: pa,
+	}
+	eps, err := server.Listen(spec)
+	if err != nil {
+		t.Errorf("unexpected error: %s", err)
+	}
+	iep := eps[0].(*inaming.Endpoint)
+	host, _, err := net.SplitHostPort(iep.Address)
+	if err != nil {
+		t.Errorf("unexpected error: %s", err)
+	}
+	if got, want := host, "1.1.1.1"; got != want {
+		t.Errorf("got %q, want %q", got, want)
+	}
+	// Won't override the specified address.
+	eps, err = server.Listen(listenSpec)
+	iep = eps[0].(*inaming.Endpoint)
+	host, _, err = net.SplitHostPort(iep.Address)
+	if err != nil {
+		t.Errorf("unexpected error: %s", err)
+	}
+	if got, want := host, "127.0.0.1"; got != want {
+		t.Errorf("got %q, want %q", got, want)
+	}
+}
+
+func TestPreferredAddressErrors(t *testing.T) {
+	ctx, shutdown := initForTest()
+	defer shutdown()
+	sm := imanager.InternalNew(ctx, naming.FixedRoutingID(0x555555555))
+	defer sm.Shutdown()
+	ns := tnaming.NewSimpleNamespace()
+	paerr := netstate.AddressChooserFunc(func(_ string, a []net.Addr) ([]net.Addr, error) {
+		return nil, fmt.Errorf("oops")
+	})
+	ctx, _ = v23.WithPrincipal(ctx, testutil.NewPrincipal("server"))
+	server, err := testInternalNewServer(ctx, sm, ns)
+	if err != nil {
+		t.Errorf("InternalNewServer failed: %v", err)
+	}
+	defer server.Stop()
+	spec := rpc.ListenSpec{
+		Addrs:          rpc.ListenAddrs{{"tcp", ":0"}},
+		AddressChooser: paerr,
+	}
+	eps, err := server.Listen(spec)
+
+	if got, want := len(eps), 0; got != want {
+		t.Errorf("got %q, want %q", got, want)
+	}
+	status := server.Status()
+	if got, want := len(status.Errors), 1; got != want {
+		t.Errorf("got %q, want %q", got, want)
+	}
+	if got, want := status.Errors[0].Error(), "oops"; got != want {
+		t.Errorf("got %q, want %q", got, want)
+	}
+}
+
+func TestSecurityNone(t *testing.T) {
+	ctx, shutdown := initForTest()
+	defer shutdown()
+	sm := imanager.InternalNew(ctx, naming.FixedRoutingID(0x66666666))
+	defer sm.Shutdown()
+	ns := tnaming.NewSimpleNamespace()
+	server, err := testInternalNewServer(ctx, sm, ns, nil, options.SecurityNone)
+	if err != nil {
+		t.Fatalf("InternalNewServer failed: %v", err)
+	}
+	if _, err = server.Listen(listenSpec); err != nil {
+		t.Fatalf("server.Listen failed: %v", err)
+	}
+	disp := &testServerDisp{&testServer{}}
+	if err := server.ServeDispatcher("mp/server", disp); err != nil {
+		t.Fatalf("server.Serve failed: %v", err)
+	}
+	client, err := InternalNewClient(sm, ns)
+	if err != nil {
+		t.Fatalf("InternalNewClient failed: %v", err)
+	}
+	// When using SecurityNone, all authorization checks should be skipped, so
+	// unauthorized methods should be callable.
+	var got string
+	if err := client.Call(ctx, "mp/server", "Unauthorized", nil, []interface{}{&got}, options.SecurityNone); err != nil {
+		t.Fatalf("client.Call failed: %v", err)
+	}
+	if want := "UnauthorizedResult"; got != want {
+		t.Errorf("got (%v), want (%v)", got, want)
+	}
+}
+
+func TestNoPrincipal(t *testing.T) {
+	ctx, shutdown := initForTest()
+	defer shutdown()
+	sm := imanager.InternalNew(ctx, naming.FixedRoutingID(0x66666666))
+	defer sm.Shutdown()
+	ns := tnaming.NewSimpleNamespace()
+	ctx, _ = v23.WithPrincipal(ctx, testutil.NewPrincipal("server"))
+	server, err := testInternalNewServer(ctx, sm, ns)
+	if err != nil {
+		t.Fatalf("InternalNewServer failed: %v", err)
+	}
+	if _, err = server.Listen(listenSpec); err != nil {
+		t.Fatalf("server.Listen failed: %v", err)
+	}
+	disp := &testServerDisp{&testServer{}}
+	if err := server.ServeDispatcher("mp/server", disp); err != nil {
+		t.Fatalf("server.Serve failed: %v", err)
+	}
+	client, err := InternalNewClient(sm, ns)
+	if err != nil {
+		t.Fatalf("InternalNewClient failed: %v", err)
+	}
+
+	// A call should fail if the principal in the ctx is nil and SecurityNone is not specified.
+	ctx, err = v23.WithPrincipal(ctx, nil)
+	if err != nil {
+		t.Fatalf("failed to set principal: %v", err)
+	}
+	_, err = client.StartCall(ctx, "mp/server", "Echo", []interface{}{"foo"})
+	if err == nil || verror.ErrorID(err) != errNoPrincipal.ID {
+		t.Fatalf("Expected errNoPrincipal, got %v", err)
+	}
+}
+
+func TestCallWithNilContext(t *testing.T) {
+	ctx, shutdown := initForTest()
+	defer shutdown()
+	sm := imanager.InternalNew(ctx, naming.FixedRoutingID(0x66666666))
+	defer sm.Shutdown()
+	ns := tnaming.NewSimpleNamespace()
+	client, err := InternalNewClient(sm, ns)
+	if err != nil {
+		t.Fatalf("InternalNewClient failed: %v", err)
+	}
+	call, err := client.StartCall(nil, "foo", "bar", []interface{}{}, options.SecurityNone)
+	if call != nil {
+		t.Errorf("Expected nil interface got: %#v", call)
+	}
+	if verror.ErrorID(err) != verror.ErrBadArg.ID {
+		t.Errorf("Expected a BadArg error, got: %s", err.Error())
+	}
+}
+
+func TestServerBlessingsOpt(t *testing.T) {
+	ctx, shutdown := initForTest()
+	defer shutdown()
+
+	var (
+		pserver   = testutil.NewPrincipal("server")
+		pclient   = testutil.NewPrincipal("client")
+		batman, _ = pserver.BlessSelf("batman")
+		cctx, _   = v23.WithPrincipal(ctx, pclient)
+		sctx, _   = v23.WithPrincipal(ctx, pserver)
+	)
+
+	// Client and server recognize the servers blessings
+	for _, p := range []security.Principal{pserver, pclient} {
+		if err := p.AddToRoots(pserver.BlessingStore().Default()); err != nil {
+			t.Fatal(err)
+		}
+		if err := p.AddToRoots(batman); err != nil {
+			t.Fatal(err)
+		}
+	}
+	// Start a server that uses the ServerBlessings option to configure itself
+	// to act as batman (as opposed to using the default blessing).
+	ns := tnaming.NewSimpleNamespace()
+
+	defer runServer(t, sctx, ns, "mountpoint/batman", &testServer{}, options.ServerBlessings{Blessings: batman}).Shutdown()
+	defer runServer(t, sctx, ns, "mountpoint/default", &testServer{}).Shutdown()
+
+	// And finally, make an RPC and see that the client sees "batman"
+	runClient := func(server string) ([]string, error) {
+		smc := imanager.InternalNew(ctx, naming.FixedRoutingID(0xc))
+		defer smc.Shutdown()
+		client, err := InternalNewClient(
+			smc,
+			ns)
+		if err != nil {
+			return nil, err
+		}
+		defer client.Close()
+		ctx, _ = v23.WithPrincipal(cctx, pclient)
+		call, err := client.StartCall(cctx, server, "Closure", nil)
+		if err != nil {
+			return nil, err
+		}
+		blessings, _ := call.RemoteBlessings()
+		return blessings, nil
+	}
+
+	// When talking to mountpoint/batman, should see "batman"
+	// When talking to mountpoint/default, should see "server"
+	if got, err := runClient("mountpoint/batman"); err != nil || len(got) != 1 || got[0] != "batman" {
+		t.Errorf("Got (%v, %v) wanted 'batman'", got, err)
+	}
+	if got, err := runClient("mountpoint/default"); err != nil || len(got) != 1 || got[0] != "server" {
+		t.Errorf("Got (%v, %v) wanted 'server'", got, err)
+	}
+}
+
+func TestNoDischargesOpt(t *testing.T) {
+	ctx, shutdown := initForTest()
+	defer shutdown()
+	var (
+		pdischarger = testutil.NewPrincipal("discharger")
+		pserver     = testutil.NewPrincipal("server")
+		pclient     = testutil.NewPrincipal("client")
+		cctx, _     = v23.WithPrincipal(ctx, pclient)
+		sctx, _     = v23.WithPrincipal(ctx, pserver)
+		pctx, _     = v23.WithPrincipal(ctx, pdischarger)
+	)
+
+	// Make the client recognize all server blessings
+	if err := pclient.AddToRoots(pserver.BlessingStore().Default()); err != nil {
+		t.Fatal(err)
+	}
+	if err := pclient.AddToRoots(pdischarger.BlessingStore().Default()); err != nil {
+		t.Fatal(err)
+	}
+
+	// Bless the client with a ThirdPartyCaveat.
+	tpcav := mkThirdPartyCaveat(pdischarger.PublicKey(), "mountpoint/discharger", mkCaveat(security.NewExpiryCaveat(time.Now().Add(time.Hour))))
+	blessings, err := pserver.Bless(pclient.PublicKey(), pserver.BlessingStore().Default(), "tpcav", tpcav)
+	if err != nil {
+		t.Fatalf("failed to create Blessings: %v", err)
+	}
+	if _, err = pclient.BlessingStore().Set(blessings, "server"); err != nil {
+		t.Fatalf("failed to set blessings: %v", err)
+	}
+
+	ns := tnaming.NewSimpleNamespace()
+
+	// Setup the disharger and test server.
+	discharger := &dischargeServer{}
+	defer runServer(t, pctx, ns, "mountpoint/discharger", discharger).Shutdown()
+	defer runServer(t, sctx, ns, "mountpoint/testServer", &testServer{}).Shutdown()
+
+	runClient := func(noDischarges bool) {
+		rid, err := naming.NewRoutingID()
+		if err != nil {
+			t.Fatal(err)
+		}
+		smc := imanager.InternalNew(ctx, rid)
+		defer smc.Shutdown()
+		client, err := InternalNewClient(smc, ns)
+		if err != nil {
+			t.Fatalf("failed to create client: %v", err)
+		}
+		defer client.Close()
+		var opts []rpc.CallOpt
+		if noDischarges {
+			opts = append(opts, NoDischarges{})
+		}
+		if _, err = client.StartCall(cctx, "mountpoint/testServer", "Closure", nil, opts...); err != nil {
+			t.Fatalf("failed to StartCall: %v", err)
+		}
+	}
+
+	// Test that when the NoDischarges option is set, dischargeServer does not get called.
+	if runClient(true); discharger.called {
+		t.Errorf("did not expect discharger to be called")
+	}
+	discharger.called = false
+	// Test that when the Nodischarges option is not set, dischargeServer does get called.
+	if runClient(false); !discharger.called {
+		t.Errorf("expected discharger to be called")
+	}
+}
+
+func TestNoImplicitDischargeFetching(t *testing.T) {
+	ctx, shutdown := initForTest()
+	defer shutdown()
+	// This test ensures that discharge clients only fetch discharges for the specified tp caveats and not its own.
+	var (
+		pdischarger1     = testutil.NewPrincipal("discharger1")
+		pdischarger2     = testutil.NewPrincipal("discharger2")
+		pdischargeClient = testutil.NewPrincipal("dischargeClient")
+		p1ctx, _         = v23.WithPrincipal(ctx, pdischarger1)
+		p2ctx, _         = v23.WithPrincipal(ctx, pdischarger2)
+		cctx, _          = v23.WithPrincipal(ctx, pdischargeClient)
+	)
+
+	// Bless the client with a ThirdPartyCaveat from discharger1.
+	tpcav1 := mkThirdPartyCaveat(pdischarger1.PublicKey(), "mountpoint/discharger1", mkCaveat(security.NewExpiryCaveat(time.Now().Add(time.Hour))))
+	blessings, err := pdischarger1.Bless(pdischargeClient.PublicKey(), pdischarger1.BlessingStore().Default(), "tpcav1", tpcav1)
+	if err != nil {
+		t.Fatalf("failed to create Blessings: %v", err)
+	}
+	if err = pdischargeClient.BlessingStore().SetDefault(blessings); err != nil {
+		t.Fatalf("failed to set blessings: %v", err)
+	}
+	// The client will only talk to the discharge services if it recognizes them.
+	pdischargeClient.AddToRoots(pdischarger1.BlessingStore().Default())
+	pdischargeClient.AddToRoots(pdischarger2.BlessingStore().Default())
+
+	ns := tnaming.NewSimpleNamespace()
+
+	// Setup the disharger and test server.
+	discharger1 := &dischargeServer{}
+	discharger2 := &dischargeServer{}
+	defer runServer(t, p1ctx, ns, "mountpoint/discharger1", discharger1).Shutdown()
+	defer runServer(t, p2ctx, ns, "mountpoint/discharger2", discharger2).Shutdown()
+
+	rid, err := naming.NewRoutingID()
+	if err != nil {
+		t.Fatal(err)
+	}
+	sm := imanager.InternalNew(ctx, rid)
+
+	c, err := InternalNewClient(sm, ns)
+	if err != nil {
+		t.Fatalf("failed to create client: %v", err)
+	}
+	dc := c.(*client).dc
+	tpcav2, err := security.NewPublicKeyCaveat(pdischarger2.PublicKey(), "mountpoint/discharger2", security.ThirdPartyRequirements{}, mkCaveat(security.NewExpiryCaveat(time.Now().Add(time.Hour))))
+	if err != nil {
+		t.Error(err)
+	}
+	dc.PrepareDischarges(cctx, []security.Caveat{tpcav2}, security.DischargeImpetus{})
+
+	// Ensure that discharger1 was not called and discharger2 was called.
+	if discharger1.called {
+		t.Errorf("discharge for caveat on discharge client should not have been fetched.")
+	}
+	if !discharger2.called {
+		t.Errorf("discharge for caveat passed to PrepareDischarges should have been fetched.")
+	}
+}
+
+// TestBlessingsCache tests that the VCCache is used to sucessfully used to cache duplicate
+// calls blessings.
+func TestBlessingsCache(t *testing.T) {
+	ctx, shutdown := initForTest()
+	defer shutdown()
+	var (
+		pserver = testutil.NewPrincipal("server")
+		pclient = testutil.NewPrincipal("client")
+		cctx, _ = v23.WithPrincipal(ctx, pclient)
+		sctx, _ = v23.WithPrincipal(ctx, pserver)
+	)
+
+	// Make the client recognize all server blessings
+	if err := pclient.AddToRoots(pserver.BlessingStore().Default()); err != nil {
+		t.Fatal(err)
+	}
+
+	ns := tnaming.NewSimpleNamespace()
+
+	serverSM := runServer(t, sctx, ns, "mountpoint/testServer", &testServer{})
+	defer serverSM.Shutdown()
+	rid := serverSM.RoutingID()
+
+	newClient := func() rpc.Client {
+		rid, err := naming.NewRoutingID()
+		if err != nil {
+			t.Fatal(err)
+		}
+		smc := imanager.InternalNew(sctx, rid)
+		defer smc.Shutdown()
+		client, err := InternalNewClient(smc, ns)
+		if err != nil {
+			t.Fatalf("failed to create client: %v", err)
+		}
+		return client
+	}
+
+	runClient := func(client rpc.Client) {
+		if err := client.Call(cctx, "mountpoint/testServer", "Closure", nil, nil); err != nil {
+			t.Fatalf("failed to Call: %v", err)
+		}
+	}
+
+	cachePrefix := naming.Join("rpc", "server", "routing-id", rid.String(), "security", "blessings", "cache")
+	cacheHits, err := stats.GetStatsObject(naming.Join(cachePrefix, "hits"))
+	if err != nil {
+		t.Fatal(err)
+	}
+	cacheAttempts, err := stats.GetStatsObject(naming.Join(cachePrefix, "attempts"))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// Check that the blessings cache is not used on the first call.
+	clientA := newClient()
+	runClient(clientA)
+	if gotAttempts, gotHits := cacheAttempts.Value().(int64), cacheHits.Value().(int64); gotAttempts != 1 || gotHits != 0 {
+		t.Errorf("got cacheAttempts(%v), cacheHits(%v), expected cacheAttempts(1), cacheHits(0)", gotAttempts, gotHits)
+	}
+	// Check that the cache is hit on the second call with the same blessings.
+	runClient(clientA)
+	if gotAttempts, gotHits := cacheAttempts.Value().(int64), cacheHits.Value().(int64); gotAttempts != 2 || gotHits != 1 {
+		t.Errorf("got cacheAttempts(%v), cacheHits(%v), expected cacheAttempts(2), cacheHits(1)", gotAttempts, gotHits)
+	}
+	clientA.Close()
+	// Check that the cache is not used with a different client.
+	clientB := newClient()
+	runClient(clientB)
+	if gotAttempts, gotHits := cacheAttempts.Value().(int64), cacheHits.Value().(int64); gotAttempts != 3 || gotHits != 1 {
+		t.Errorf("got cacheAttempts(%v), cacheHits(%v), expected cacheAttempts(3), cacheHits(1)", gotAttempts, gotHits)
+	}
+	// clientB changes its blessings, the cache should not be used.
+	blessings, err := pserver.Bless(pclient.PublicKey(), pserver.BlessingStore().Default(), "cav", mkCaveat(security.NewExpiryCaveat(time.Now().Add(time.Hour))))
+	if err != nil {
+		t.Fatalf("failed to create Blessings: %v", err)
+	}
+	if _, err = pclient.BlessingStore().Set(blessings, "server"); err != nil {
+		t.Fatalf("failed to set blessings: %v", err)
+	}
+	runClient(clientB)
+	if gotAttempts, gotHits := cacheAttempts.Value().(int64), cacheHits.Value().(int64); gotAttempts != 4 || gotHits != 1 {
+		t.Errorf("got cacheAttempts(%v), cacheHits(%v), expected cacheAttempts(4), cacheHits(1)", gotAttempts, gotHits)
+	}
+	clientB.Close()
+}
+
+var fakeTimeCaveat = security.CaveatDescriptor{
+	Id:        uniqueid.Id{0x18, 0xba, 0x6f, 0x84, 0xd5, 0xec, 0xdb, 0x9b, 0xf2, 0x32, 0x19, 0x5b, 0x53, 0x92, 0x80, 0x0},
+	ParamType: vdl.TypeOf(int64(0)),
+}
+
+func TestServerPublicKeyOpt(t *testing.T) {
+	ctx, shutdown := initForTest()
+	defer shutdown()
+	var (
+		pserver = testutil.NewPrincipal("server")
+		pother  = testutil.NewPrincipal("other")
+		pclient = testutil.NewPrincipal("client")
+		cctx, _ = v23.WithPrincipal(ctx, pclient)
+		sctx, _ = v23.WithPrincipal(ctx, pserver)
+	)
+
+	ns := tnaming.NewSimpleNamespace()
+	mountName := "mountpoint/default"
+
+	// Start a server with pserver.
+	defer runServer(t, sctx, ns, mountName, &testServer{}).Shutdown()
+
+	smc := imanager.InternalNew(sctx, naming.FixedRoutingID(0xc))
+	client, err := InternalNewClient(smc, ns)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer smc.Shutdown()
+	defer client.Close()
+
+	// The call should succeed when the server presents the same public as the opt...
+	if _, err = client.StartCall(cctx, mountName, "Closure", nil, options.SkipServerEndpointAuthorization{}, options.ServerPublicKey{
+		PublicKey: pserver.PublicKey(),
+	}); err != nil {
+		t.Errorf("Expected call to succeed but got %v", err)
+	}
+	// ...but fail if they differ.
+	if _, err = client.StartCall(cctx, mountName, "Closure", nil, options.SkipServerEndpointAuthorization{}, options.ServerPublicKey{
+		PublicKey: pother.PublicKey(),
+	}); verror.ErrorID(err) != verror.ErrNotTrusted.ID {
+		t.Errorf("got %v, want %v", verror.ErrorID(err), verror.ErrNotTrusted.ID)
+	}
+}
+
+type expiryDischarger struct {
+	called bool
+}
+
+func (ed *expiryDischarger) Discharge(ctx *context.T, call rpc.StreamServerCall, cav security.Caveat, _ security.DischargeImpetus) (security.Discharge, error) {
+	tp := cav.ThirdPartyDetails()
+	if tp == nil {
+		return security.Discharge{}, fmt.Errorf("discharger: %v does not represent a third-party caveat", cav)
+	}
+	if err := tp.Dischargeable(ctx, call.Security()); err != nil {
+		return security.Discharge{}, fmt.Errorf("third-party caveat %v cannot be discharged for this context: %v", cav, err)
+	}
+	expDur := 10 * time.Millisecond
+	if ed.called {
+		expDur = time.Second
+	}
+	expiry, err := security.NewExpiryCaveat(time.Now().Add(expDur))
+	if err != nil {
+		return security.Discharge{}, fmt.Errorf("failed to create an expiration on the discharge: %v", err)
+	}
+	d, err := call.Security().LocalPrincipal().MintDischarge(cav, expiry)
+	if err != nil {
+		return security.Discharge{}, err
+	}
+	ed.called = true
+	return d, nil
+}
+
+func TestDischargeClientFetchExpiredDischarges(t *testing.T) {
+	ctx, shutdown := initForTest()
+	defer shutdown()
+	var (
+		pclient, pdischarger = newClientServerPrincipals()
+		tpcav                = mkThirdPartyCaveat(pdischarger.PublicKey(), "mountpoint/discharger", mkCaveat(security.NewExpiryCaveat(time.Now().Add(time.Hour))))
+		ns                   = tnaming.NewSimpleNamespace()
+		discharger           = &expiryDischarger{}
+		pctx, _              = v23.WithPrincipal(ctx, pdischarger)
+	)
+	ctx, _ = v23.WithPrincipal(ctx, pclient)
+
+	// Setup the disharge server.
+	defer runServer(t, pctx, ns, "mountpoint/discharger", discharger).Shutdown()
+
+	// Create a discharge client.
+	rid, err := naming.NewRoutingID()
+	if err != nil {
+		t.Fatal(err)
+	}
+	smc := imanager.InternalNew(ctx, rid)
+	defer smc.Shutdown()
+	client, err := InternalNewClient(smc, ns)
+	if err != nil {
+		t.Fatalf("failed to create client: %v", err)
+	}
+	defer client.Close()
+
+	dc := InternalNewDischargeClient(ctx, client, 0)
+
+	// Fetch discharges for tpcav.
+	dis := dc.PrepareDischarges(ctx, []security.Caveat{tpcav}, security.DischargeImpetus{})[0]
+	// Check that the discharges is not yet expired, but is expired after 100 milliseconds.
+	expiry := dis.Expiry()
+	// The discharge should expire.
+	select {
+	case <-time.After(time.Now().Sub(expiry)):
+		break
+	case <-time.After(time.Second):
+		t.Fatalf("discharge didn't expire within a second")
+	}
+	// Preparing Discharges again to get fresh discharges.
+	now := time.Now()
+	dis = dc.PrepareDischarges(ctx, []security.Caveat{tpcav}, security.DischargeImpetus{})[0]
+	if expiry = dis.Expiry(); expiry.Before(now) {
+		t.Fatalf("discharge has expired %v, but should be fresh", dis)
+	}
+}
+
+// newClientServerPrincipals creates a pair of principals and sets them up to
+// recognize each others default blessings.
+//
+// If the client does not recognize the blessings presented by the server,
+// then it will not even send it the request.
+//
+// If the server does not recognize the blessings presented by the client,
+// it is likely to deny access (unless the server authorizes all principals).
+func newClientServerPrincipals() (client, server security.Principal) {
+	client = testutil.NewPrincipal("client")
+	server = testutil.NewPrincipal("server")
+	client.AddToRoots(server.BlessingStore().Default())
+	server.AddToRoots(client.BlessingStore().Default())
+	return
+}
+
+func init() {
+	rpc.RegisterUnknownProtocol("wsh", websocket.HybridDial, websocket.HybridResolve, websocket.HybridListener)
+	security.RegisterCaveatValidator(fakeTimeCaveat, func(_ *context.T, _ security.Call, t int64) error {
+		if now := clock.Now(); now > t {
+			return fmt.Errorf("fakeTimeCaveat expired: now=%d > then=%d", now, t)
+		}
+		return nil
+	})
+}
diff --git a/runtime/internal/rpc/init.go b/runtime/internal/rpc/init.go
new file mode 100644
index 0000000..6ab1428
--- /dev/null
+++ b/runtime/internal/rpc/init.go
@@ -0,0 +1,14 @@
+// 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 rpc
+
+import (
+	"math/rand"
+	"time"
+)
+
+func init() {
+	rand.Seed(time.Now().UnixNano())
+}
diff --git a/runtime/internal/rpc/options.go b/runtime/internal/rpc/options.go
new file mode 100644
index 0000000..4971e1c
--- /dev/null
+++ b/runtime/internal/rpc/options.go
@@ -0,0 +1,118 @@
+// 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 rpc
+
+import (
+	"time"
+
+	"v.io/v23/naming"
+	"v.io/v23/options"
+	"v.io/v23/rpc"
+
+	"v.io/x/ref/lib/apilog"
+	"v.io/x/ref/runtime/internal/rpc/stream"
+)
+
+// PreferredProtocols instructs the Runtime implementation to select
+// endpoints with the specified protocols when a Client makes a call
+// and to order them in the specified order.
+type PreferredProtocols []string
+
+func (PreferredProtocols) RPCClientOpt() {
+	defer apilog.LogCall(nil)(nil) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+}
+
+// This option is used to sort and filter the endpoints when resolving the
+// proxy name from a mounttable.
+type PreferredServerResolveProtocols []string
+
+func (PreferredServerResolveProtocols) RPCServerOpt() {
+	defer apilog.LogCall(nil)(nil) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+}
+
+// ReservedNameDispatcher specifies the dispatcher that controls access
+// to framework managed portion of the namespace.
+type ReservedNameDispatcher struct {
+	Dispatcher rpc.Dispatcher
+}
+
+func (ReservedNameDispatcher) RPCServerOpt() {
+	defer apilog.LogCall(nil)(nil) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+}
+
+func getRetryTimeoutOpt(opts []rpc.CallOpt) (time.Duration, bool) {
+	for _, o := range opts {
+		if r, ok := o.(options.RetryTimeout); ok {
+			return time.Duration(r), true
+		}
+	}
+	return 0, false
+}
+
+func getNoNamespaceOpt(opts []rpc.CallOpt) bool {
+	for _, o := range opts {
+		if _, ok := o.(options.NoResolve); ok {
+			return true
+		}
+	}
+	return false
+}
+
+func shouldNotFetchDischarges(opts []rpc.CallOpt) bool {
+	for _, o := range opts {
+		if _, ok := o.(NoDischarges); ok {
+			return true
+		}
+	}
+	return false
+}
+
+func noRetry(opts []rpc.CallOpt) bool {
+	for _, o := range opts {
+		if _, ok := o.(options.NoRetry); ok {
+			return true
+		}
+	}
+	return false
+}
+
+func translateVCOpts(opts []rpc.CallOpt) (vcOpts []stream.VCOpt) {
+	for _, o := range opts {
+		switch v := o.(type) {
+		case stream.VCOpt:
+			vcOpts = append(vcOpts, v)
+		case options.SecurityLevel:
+			switch v {
+			case options.SecurityNone:
+				vcOpts = append(vcOpts, stream.AuthenticatedVC(false))
+			case options.SecurityConfidential:
+				vcOpts = append(vcOpts, stream.AuthenticatedVC(true))
+			}
+		}
+	}
+	return
+}
+
+func getNamespaceOpts(opts []rpc.CallOpt) (resolveOpts []naming.NamespaceOpt) {
+	for _, o := range opts {
+		if r, ok := o.(naming.NamespaceOpt); ok {
+			resolveOpts = append(resolveOpts, r)
+		}
+	}
+	return
+}
+
+func callEncrypted(opts []rpc.CallOpt) bool {
+	encrypted := true
+	for _, o := range opts {
+		switch o {
+		case options.SecurityNone:
+			encrypted = false
+		case options.SecurityConfidential:
+			encrypted = true
+		}
+	}
+	return encrypted
+}
diff --git a/runtime/internal/rpc/protocols/tcp/init.go b/runtime/internal/rpc/protocols/tcp/init.go
new file mode 100644
index 0000000..a78117f
--- /dev/null
+++ b/runtime/internal/rpc/protocols/tcp/init.go
@@ -0,0 +1,76 @@
+// 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
new file mode 100644
index 0000000..5aac575
--- /dev/null
+++ b/runtime/internal/rpc/protocols/ws/init.go
@@ -0,0 +1,18 @@
+// 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
new file mode 100644
index 0000000..26cc680
--- /dev/null
+++ b/runtime/internal/rpc/protocols/wsh/init.go
@@ -0,0 +1,19 @@
+// 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
new file mode 100644
index 0000000..276a567
--- /dev/null
+++ b/runtime/internal/rpc/protocols/wsh_nacl/init.go
@@ -0,0 +1,21 @@
+// 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/pubsub.go b/runtime/internal/rpc/pubsub.go
new file mode 100644
index 0000000..6d2ad87
--- /dev/null
+++ b/runtime/internal/rpc/pubsub.go
@@ -0,0 +1,30 @@
+// 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 rpc
+
+import (
+	"net"
+
+	"v.io/x/ref/lib/pubsub"
+)
+
+// NewAddAddrsSetting creates the Setting to be sent to Listen to inform
+// it of new addresses that have become available since the last change.
+func NewAddAddrsSetting(a []net.Addr) pubsub.Setting {
+	return pubsub.NewAny(NewAddrsSetting, NewAddrsSettingDesc, a)
+}
+
+// NewRmAddrsSetting creates the Setting to be sent to Listen to inform
+// it of addresses that are no longer available.
+func NewRmAddrsSetting(a []net.Addr) pubsub.Setting {
+	return pubsub.NewAny(RmAddrsSetting, RmAddrsSettingDesc, a)
+}
+
+const (
+	NewAddrsSetting     = "NewAddrs"
+	NewAddrsSettingDesc = "New Addresses discovered since last change"
+	RmAddrsSetting      = "RmAddrs"
+	RmAddrsSettingDesc  = "Addresses that have been removed since last change"
+)
diff --git a/runtime/internal/rpc/reserved.go b/runtime/internal/rpc/reserved.go
new file mode 100644
index 0000000..e47f13b
--- /dev/null
+++ b/runtime/internal/rpc/reserved.go
@@ -0,0 +1,443 @@
+// 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 rpc
+
+import (
+	"fmt"
+	"strings"
+
+	"v.io/v23/context"
+	"v.io/v23/glob"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+	"v.io/v23/rpc/reserved"
+	"v.io/v23/security"
+	"v.io/v23/security/access"
+	"v.io/v23/vdl"
+	"v.io/v23/vdlroot/signature"
+	"v.io/v23/verror"
+	"v.io/x/ref/lib/apilog"
+)
+
+// reservedInvoker returns a special invoker for reserved methods.  This invoker
+// has access to the internal dispatchers, which allows it to perform special
+// handling for methods like Glob and Signature.
+func reservedInvoker(dispNormal, dispReserved rpc.Dispatcher) rpc.Invoker {
+	methods := &reservedMethods{dispNormal: dispNormal, dispReserved: dispReserved}
+	invoker := rpc.ReflectInvokerOrDie(methods)
+	methods.selfInvoker = invoker
+	return invoker
+}
+
+// reservedMethods is a regular server implementation object, which is passed to
+// the regular ReflectInvoker in order to implement reserved methods.  The
+// leading reserved "__" prefix is stripped before any methods are called.
+//
+// To add a new reserved method, simply add a method below, along with a
+// description of the method.
+type reservedMethods struct {
+	dispNormal   rpc.Dispatcher
+	dispReserved rpc.Dispatcher
+	selfInvoker  rpc.Invoker
+}
+
+func (r *reservedMethods) Describe__() []rpc.InterfaceDesc {
+	defer apilog.LogCall(nil)(nil) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	return []rpc.InterfaceDesc{{
+		Name: "__Reserved",
+		Doc:  `Reserved methods implemented by the RPC framework.  Each method name is prefixed with a double underscore "__".`,
+		Methods: []rpc.MethodDesc{
+			{
+				Name:      "Glob",
+				Doc:       "Glob returns all entries matching the pattern.",
+				InArgs:    []rpc.ArgDesc{{Name: "pattern", Doc: ""}},
+				OutStream: rpc.ArgDesc{Doc: "Streams matching entries back to the client."},
+			},
+			{
+				Name: "MethodSignature",
+				Doc:  "MethodSignature returns the signature for the given method.",
+				InArgs: []rpc.ArgDesc{{
+					Name: "method",
+					Doc:  "Method name whose signature will be returned.",
+				}},
+				OutArgs: []rpc.ArgDesc{{
+					Doc: "Method signature for the given method.",
+				}},
+			},
+			{
+				Name: "Signature",
+				Doc:  "Signature returns all interface signatures implemented by the object.",
+				OutArgs: []rpc.ArgDesc{{
+					Doc: "All interface signatures implemented by the object.",
+				}},
+			},
+		},
+	}}
+}
+
+func (r *reservedMethods) Signature(ctx *context.T, call rpc.ServerCall) ([]signature.Interface, error) {
+	suffix := call.Suffix()
+	disp := r.dispNormal
+	if naming.IsReserved(suffix) {
+		disp = r.dispReserved
+	}
+	if disp == nil {
+		return nil, verror.New(verror.ErrUnknownSuffix, ctx, suffix)
+	}
+	obj, _, err := disp.Lookup(ctx, suffix)
+	switch {
+	case err != nil:
+		return nil, err
+	case obj == nil:
+		return nil, verror.New(verror.ErrUnknownSuffix, ctx, suffix)
+	}
+	invoker, err := objectToInvoker(obj)
+	if err != nil {
+		return nil, err
+	}
+	sig, err := invoker.Signature(ctx, call)
+	if err != nil {
+		return nil, err
+	}
+	// Append the reserved methods.  We wait until now to add the "__" prefix to
+	// each method, so that we can use the regular ReflectInvoker.Signature logic.
+	rsig, err := r.selfInvoker.Signature(ctx, call)
+	if err != nil {
+		return nil, err
+	}
+	for i := range rsig {
+		for j := range rsig[i].Methods {
+			rsig[i].Methods[j].Name = "__" + rsig[i].Methods[j].Name
+		}
+	}
+	return signature.CleanInterfaces(append(sig, rsig...)), nil
+}
+
+func (r *reservedMethods) MethodSignature(ctx *context.T, call rpc.ServerCall, method string) (signature.Method, error) {
+	// Reserved methods use our self invoker, to describe our own methods,
+	if naming.IsReserved(method) {
+		return r.selfInvoker.MethodSignature(ctx, call, naming.StripReserved(method))
+	}
+
+	suffix := call.Suffix()
+	disp := r.dispNormal
+	if naming.IsReserved(suffix) {
+		disp = r.dispReserved
+	}
+	if disp == nil {
+		return signature.Method{}, verror.New(verror.ErrUnknownMethod, ctx, rpc.ReservedMethodSignature)
+	}
+	obj, _, err := disp.Lookup(ctx, suffix)
+	switch {
+	case err != nil:
+		return signature.Method{}, err
+	case obj == nil:
+		return signature.Method{}, verror.New(verror.ErrUnknownMethod, ctx, rpc.ReservedMethodSignature)
+	}
+	invoker, err := objectToInvoker(obj)
+	if err != nil {
+		return signature.Method{}, err
+	}
+	// TODO(toddw): Decide if we should hide the method signature if the
+	// caller doesn't have access to call it.
+	return invoker.MethodSignature(ctx, call, method)
+}
+
+func (r *reservedMethods) Glob(ctx *context.T, call rpc.StreamServerCall, pattern string) error {
+	// Copy the original call to shield ourselves from changes the flowServer makes.
+	glob := globInternal{r.dispNormal, r.dispReserved, call.Suffix()}
+	return glob.Glob(ctx, call, pattern)
+}
+
+// globInternal handles ALL the Glob requests received by a server and
+// constructs a response from the state of internal server objects and the
+// service objects.
+//
+// Internal objects exist only at the root of the server and have a name that
+// starts with a double underscore ("__"). They are only visible in the Glob
+// response if the double underscore is explicitly part of the pattern, e.g.
+// "".Glob("__*/*"), or "".Glob("__debug/...").
+//
+// Service objects may choose to implement either AllGlobber or ChildrenGlobber.
+// AllGlobber is more flexible, but ChildrenGlobber is simpler to implement and
+// less prone to errors.
+//
+// If objects implement AllGlobber, it must be able to handle recursive pattern
+// for the entire namespace below the receiver object, i.e. "a/b".Glob("...")
+// must return the name of all the objects under "a/b".
+//
+// If they implement ChildrenGlobber, it provides a list of the receiver's
+// immediate children names, or a non-nil error if the receiver doesn't exist.
+//
+// globInternal constructs the Glob response by internally accessing the
+// AllGlobber or ChildrenGlobber interface of objects as many times as needed.
+//
+// Before accessing an object, globInternal ensures that the requester is
+// authorized to access it. Internal objects require access.Debug. Service
+// objects require access.Resolve.
+type globInternal struct {
+	dispNormal   rpc.Dispatcher
+	dispReserved rpc.Dispatcher
+	receiver     string
+}
+
+// The maximum depth of recursion in Glob. We only count recursion levels
+// associated with a recursive glob pattern, e.g. a pattern like "..." will be
+// allowed to recurse up to 10 levels, but "*/*/*/*/..." will go up to 14
+// levels.
+const maxRecursiveGlobDepth = 10
+
+type gState struct {
+	name  string
+	glob  *glob.Glob
+	depth int
+}
+
+func (i *globInternal) Glob(ctx *context.T, call rpc.StreamServerCall, pattern string) error {
+	ctx.VI(3).Infof("rpc Glob: Incoming request: %q.Glob(%q)", i.receiver, pattern)
+	g, err := glob.Parse(pattern)
+	if err != nil {
+		return err
+	}
+	disp := i.dispNormal
+	tags := []*vdl.Value{vdl.ValueOf(access.Resolve)}
+	if naming.IsReserved(i.receiver) || (i.receiver == "" && naming.IsReserved(pattern)) {
+		disp = i.dispReserved
+		tags = []*vdl.Value{vdl.ValueOf(access.Debug)}
+	}
+	if disp == nil {
+		return reserved.NewErrGlobNotImplemented(ctx)
+	}
+	call = callWithMethodTags(ctx, call, tags)
+
+	queue := []gState{gState{glob: g}}
+
+	someMatchesOmitted := false
+	for len(queue) != 0 {
+		select {
+		case <-ctx.Done():
+			// RPC timed out or was canceled.
+			return nil
+		default:
+		}
+		state := queue[0]
+		queue = queue[1:]
+
+		subcall := callWithSuffix(ctx, call, naming.Join(i.receiver, state.name))
+		suffix := subcall.Suffix()
+		if state.depth > maxRecursiveGlobDepth {
+			ctx.Errorf("rpc Glob: exceeded recursion limit (%d): %q", maxRecursiveGlobDepth, suffix)
+			subcall.Send(naming.GlobReplyError{
+				Value: naming.GlobError{Name: state.name, Error: reserved.NewErrGlobMaxRecursionReached(ctx)},
+			})
+			continue
+		}
+		obj, auth, err := disp.Lookup(ctx, suffix)
+		if err != nil {
+			ctx.VI(3).Infof("rpc Glob: Lookup failed for %q: %v", suffix, err)
+			subcall.Send(naming.GlobReplyError{
+				Value: naming.GlobError{Name: state.name, Error: verror.Convert(verror.ErrNoExist, ctx, err)},
+			})
+			continue
+		}
+		if obj == nil {
+			ctx.VI(3).Infof("rpc Glob: object not found for %q", suffix)
+			subcall.Send(naming.GlobReplyError{
+				Value: naming.GlobError{Name: state.name, Error: verror.New(verror.ErrNoExist, ctx, "nil object")},
+			})
+			continue
+		}
+
+		// Verify that that requester is authorized for the current object.
+		if err := authorize(ctx, subcall.Security(), auth); err != nil {
+			someMatchesOmitted = true
+			ctx.VI(3).Infof("rpc Glob: client is not authorized for %q: %v", suffix, err)
+			continue
+		}
+
+		// If the object implements both AllGlobber and ChildrenGlobber, we'll
+		// use AllGlobber.
+		invoker, err := objectToInvoker(obj)
+		if err != nil {
+			ctx.VI(3).Infof("rpc Glob: object for %q cannot be converted to invoker: %v", suffix, err)
+			subcall.Send(naming.GlobReplyError{
+				Value: naming.GlobError{Name: state.name, Error: verror.Convert(verror.ErrInternal, ctx, err)},
+			})
+			continue
+		}
+		gs := invoker.Globber()
+		if gs == nil || (gs.AllGlobber == nil && gs.ChildrenGlobber == nil) {
+			if state.glob.Len() == 0 {
+				subcall.Send(naming.GlobReplyEntry{
+					Value: naming.MountEntry{Name: state.name, IsLeaf: true},
+				})
+			} else {
+				subcall.Send(naming.GlobReplyError{
+					Value: naming.GlobError{Name: state.name, Error: reserved.NewErrGlobNotImplemented(ctx)},
+				})
+			}
+			continue
+		}
+		if gs.AllGlobber != nil {
+			ctx.VI(3).Infof("rpc Glob: %q implements AllGlobber", suffix)
+			send := func(reply naming.GlobReply) error {
+				select {
+				case <-ctx.Done():
+					return verror.New(verror.ErrAborted, ctx)
+				default:
+				}
+				switch v := reply.(type) {
+				case naming.GlobReplyEntry:
+					v.Value.Name = naming.Join(state.name, v.Value.Name)
+					return subcall.Send(v)
+				case naming.GlobReplyError:
+					v.Value.Name = naming.Join(state.name, v.Value.Name)
+					return subcall.Send(v)
+				}
+				return nil
+			}
+			if err := gs.AllGlobber.Glob__(ctx, &globServerCall{subcall, send}, state.glob); err != nil {
+				ctx.VI(3).Infof("rpc Glob: %q.Glob(%q) failed: %v", suffix, state.glob, err)
+				subcall.Send(naming.GlobReplyError{
+					Value: naming.GlobError{Name: state.name, Error: verror.Convert(verror.ErrInternal, ctx, err)},
+				})
+			}
+			continue
+		}
+		if gs.ChildrenGlobber != nil {
+			ctx.VI(3).Infof("rpc Glob: %q implements ChildrenGlobber", suffix)
+			depth := state.depth
+			if state.glob.Len() == 0 {
+				// The glob pattern matches the current object.
+				subcall.Send(naming.GlobReplyEntry{
+					Value: naming.MountEntry{Name: state.name},
+				})
+				if state.glob.Recursive() {
+					// This is a recursive pattern. Make sure we don't recurse forever.
+					depth++
+				} else {
+					// The pattern can't possibly match any children of this node.
+					continue
+				}
+			}
+			matcher, tail := state.glob.Head(), state.glob.Tail()
+			send := func(reply naming.GlobChildrenReply) error {
+				select {
+				case <-ctx.Done():
+					return verror.New(verror.ErrAborted, ctx)
+				default:
+				}
+				switch v := reply.(type) {
+				case naming.GlobChildrenReplyName:
+					child := v.Value
+					if len(child) == 0 || strings.Contains(child, "/") {
+						return verror.New(verror.ErrBadArg, ctx, fmt.Sprintf("invalid child name: %q", child))
+					}
+					if !matcher.Match(child) {
+						return verror.New(verror.ErrBadArg, ctx, fmt.Sprintf("child name does not match: %q", child))
+					}
+					next := naming.Join(state.name, child)
+					queue = append(queue, gState{next, tail, depth})
+				case naming.GlobChildrenReplyError:
+					v.Value.Name = naming.Join(state.name, v.Value.Name)
+					return subcall.Send(naming.GlobReplyError{Value: v.Value})
+				}
+				return nil
+			}
+			if err := gs.ChildrenGlobber.GlobChildren__(ctx, &globChildrenServerCall{subcall, send}, matcher); err != nil {
+				subcall.Send(naming.GlobReplyError{
+					Value: naming.GlobError{Name: state.name, Error: verror.Convert(verror.ErrInternal, ctx, err)},
+				})
+			}
+			continue
+		}
+	}
+	if someMatchesOmitted {
+		call.Send(naming.GlobReplyError{
+			Value: naming.GlobError{Error: reserved.NewErrGlobMatchesOmitted(ctx)},
+		})
+	}
+	return nil
+}
+
+type globServerCall struct {
+	rpc.ServerCall
+	send func(reply naming.GlobReply) error
+}
+
+func (g *globServerCall) SendStream() interface {
+	Send(naming.GlobReply) error
+} {
+	return g
+}
+
+func (g *globServerCall) Send(reply naming.GlobReply) error {
+	return g.send(reply)
+}
+
+type globChildrenServerCall struct {
+	rpc.ServerCall
+	send func(reply naming.GlobChildrenReply) error
+}
+
+func (g *globChildrenServerCall) SendStream() interface {
+	Send(naming.GlobChildrenReply) error
+} {
+	return g
+}
+
+func (g *globChildrenServerCall) Send(reply naming.GlobChildrenReply) error {
+	return g.send(reply)
+}
+
+// derivedServerCall allows us to derive calls with slightly different properties,
+// useful for our various special-cased reserved methods.
+type derivedServerCall struct {
+	rpc.StreamServerCall
+	suffix   string
+	security security.Call
+}
+
+func callWithSuffix(ctx *context.T, src rpc.StreamServerCall, suffix string) rpc.StreamServerCall {
+	sec := securityCallWithSuffix(src.Security(), suffix)
+	return &derivedServerCall{src, suffix, sec}
+}
+
+func callWithMethodTags(ctx *context.T, src rpc.StreamServerCall, tags []*vdl.Value) rpc.StreamServerCall {
+	sec := securityCallWithMethodTags(src.Security(), tags)
+	return &derivedServerCall{src, src.Suffix(), sec}
+}
+
+func (c *derivedServerCall) Suffix() string {
+	defer apilog.LogCall(nil)(nil) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	return c.suffix
+}
+func (c *derivedServerCall) Security() security.Call {
+	defer apilog.LogCall(nil)(nil) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	return c.security
+}
+
+type derivedSecurityCall struct {
+	security.Call
+	suffix     string
+	methodTags []*vdl.Value
+}
+
+func securityCallWithSuffix(src security.Call, suffix string) security.Call {
+	return &derivedSecurityCall{src, suffix, src.MethodTags()}
+}
+
+func securityCallWithMethodTags(src security.Call, tags []*vdl.Value) security.Call {
+	return &derivedSecurityCall{src, src.Suffix(), tags}
+}
+
+func (c *derivedSecurityCall) Suffix() string {
+	defer apilog.LogCall(nil)(nil) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	return c.suffix
+}
+func (c *derivedSecurityCall) MethodTags() []*vdl.Value {
+	defer apilog.LogCall(nil)(nil) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	return c.methodTags
+}
diff --git a/runtime/internal/rpc/resolve_internal_test.go b/runtime/internal/rpc/resolve_internal_test.go
new file mode 100644
index 0000000..7236ceb
--- /dev/null
+++ b/runtime/internal/rpc/resolve_internal_test.go
@@ -0,0 +1,13 @@
+// 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 rpc
+
+import (
+	"v.io/v23/rpc"
+)
+
+func InternalServerResolveToEndpoint(s rpc.Server, name string) (string, error) {
+	return s.(*server).resolveToEndpoint(name)
+}
diff --git a/runtime/internal/rpc/resolve_test.go b/runtime/internal/rpc/resolve_test.go
new file mode 100644
index 0000000..da3649f
--- /dev/null
+++ b/runtime/internal/rpc/resolve_test.go
@@ -0,0 +1,154 @@
+// 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 rpc_test
+
+import (
+	"flag"
+	"fmt"
+	"os"
+	"testing"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/options"
+	"v.io/v23/rpc"
+
+	"v.io/x/ref/lib/flags"
+	"v.io/x/ref/lib/xrpc"
+	"v.io/x/ref/runtime/factories/fake"
+	"v.io/x/ref/runtime/internal"
+	"v.io/x/ref/runtime/internal/lib/appcycle"
+	inaming "v.io/x/ref/runtime/internal/naming"
+	irpc "v.io/x/ref/runtime/internal/rpc"
+	grt "v.io/x/ref/runtime/internal/rt"
+	"v.io/x/ref/services/mounttable/mounttablelib"
+	"v.io/x/ref/test/expect"
+	"v.io/x/ref/test/modules"
+)
+
+var commonFlags *flags.Flags
+
+func init() {
+	commonFlags = flags.CreateAndRegister(flag.CommandLine, flags.Runtime)
+	if err := internal.ParseFlags(commonFlags); err != nil {
+		panic(err)
+	}
+}
+
+func setupRuntime() {
+	ac := appcycle.New()
+
+	listenSpec := rpc.ListenSpec{Addrs: rpc.ListenAddrs{{"tcp", "127.0.0.1:0"}}}
+
+	rootctx, rootcancel := context.RootContext()
+	ctx, cancel := context.WithCancel(rootctx)
+	runtime, ctx, sd, err := grt.Init(ctx,
+		ac,
+		nil,
+		&listenSpec,
+		nil,
+		"",
+		commonFlags.RuntimeFlags(),
+		nil)
+	if err != nil {
+		panic(err)
+	}
+	shutdown := func() {
+		ac.Shutdown()
+		cancel()
+		sd()
+		rootcancel()
+	}
+	fake.InjectRuntime(runtime, ctx, shutdown)
+}
+
+var rootMT = modules.Register(func(env *modules.Env, args ...string) error {
+	setupRuntime()
+	ctx, shutdown := v23.Init()
+	defer shutdown()
+
+	mp := ""
+	mt, err := mounttablelib.NewMountTableDispatcher(ctx, "", "", "mounttable")
+	if err != nil {
+		return fmt.Errorf("mounttablelib.NewMountTableDispatcher failed: %s", err)
+	}
+	server, err := xrpc.NewDispatchingServer(ctx, mp, mt, options.ServesMountTable(true))
+	if err != nil {
+		return fmt.Errorf("root failed: %v", err)
+	}
+	fmt.Fprintf(env.Stdout, "PID=%d\n", os.Getpid())
+	for _, ep := range server.Status().Endpoints {
+		fmt.Fprintf(env.Stdout, "MT_NAME=%s\n", ep.Name())
+	}
+	modules.WaitForEOF(env.Stdin)
+	return nil
+}, "rootMT")
+
+func startMT(t *testing.T, sh *modules.Shell) string {
+	h, err := sh.Start(nil, rootMT)
+	if err != nil {
+		t.Fatalf("unexpected error for root mt: %s", err)
+	}
+	s := expect.NewSession(t, h.Stdout(), time.Minute)
+	s.ExpectVar("PID")
+	return s.ExpectVar("MT_NAME")
+}
+
+func TestResolveToEndpoint(t *testing.T) {
+	setupRuntime()
+	ctx, shutdown := v23.Init()
+	defer shutdown()
+	sh, err := modules.NewShell(ctx, nil, testing.Verbose(), t)
+	if err != nil {
+		t.Fatalf("modules.NewShell failed: %s", err)
+	}
+	defer sh.Cleanup(nil, nil)
+	root := startMT(t, sh)
+
+	ns := v23.GetNamespace(ctx)
+	ns.SetRoots(root)
+
+	proxyEp, _ := inaming.NewEndpoint("proxy.v.io:123")
+	proxyEpStr := proxyEp.String()
+	proxyAddr := naming.JoinAddressName(proxyEpStr, "")
+	if err := ns.Mount(ctx, "proxy", proxyAddr, time.Hour); err != nil {
+		t.Fatalf("ns.Mount failed: %s", err)
+	}
+
+	server, err := v23.NewServer(ctx)
+	if err != nil {
+		t.Fatalf("runtime.NewServer failed: %s", err)
+	}
+
+	notfound := fmt.Errorf("not found")
+	testcases := []struct {
+		address string
+		result  string
+		err     error
+	}{
+		{"/proxy.v.io:123", proxyEpStr, nil},
+		{"proxy.v.io:123", "", notfound},
+		{"proxy", proxyEpStr, nil},
+		{naming.JoinAddressName(root, "proxy"), proxyEpStr, nil},
+		{proxyAddr, proxyEpStr, nil},
+		{proxyEpStr, "", notfound},
+		{"unknown", "", notfound},
+	}
+	for _, tc := range testcases {
+		result, err := irpc.InternalServerResolveToEndpoint(server, tc.address)
+		if (err == nil) != (tc.err == nil) {
+			t.Errorf("Unexpected err for %q. Got %v, expected %v", tc.address, err, tc.err)
+		}
+		if result != tc.result {
+			t.Errorf("Unexpected result for %q. Got %q, expected %q", tc.address, result, tc.result)
+		}
+	}
+	if t.Failed() {
+		t.Logf("proxyEpStr: %v", proxyEpStr)
+		t.Logf("proxyAddr: %v", proxyAddr)
+	}
+}
diff --git a/runtime/internal/rpc/results_store.go b/runtime/internal/rpc/results_store.go
new file mode 100644
index 0000000..1ecabd3
--- /dev/null
+++ b/runtime/internal/rpc/results_store.go
@@ -0,0 +1,126 @@
+// 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 rpc
+
+import (
+	"container/heap"
+	"sync"
+)
+
+const (
+	// TODO(cnicolaou): what are good initial values? Large servers want
+	// large values, most won't.
+	initialResults           = 1000
+	initialOutOfOrderResults = 100
+)
+
+type results []interface{}
+
+// Implement heap.Interface to maintain an ordered min-heap of uint64s.
+type intHeap []uint64
+
+func (h intHeap) Len() int           { return len(h) }
+func (h intHeap) Less(i, j int) bool { return h[i] < h[j] }
+func (h intHeap) Swap(i, j int)      { h[i], h[j] = h[j], h[i] }
+
+func (h *intHeap) Push(x interface{}) {
+	// Push and Pop use pointer receivers because they modify the slice's length,
+	// not just its contents.
+	*h = append(*h, x.(uint64))
+}
+
+func (h *intHeap) Pop() interface{} {
+	old := *h
+	n := len(old)
+	x := old[n-1]
+	*h = old[0 : n-1]
+	return x
+}
+
+// resultStore is used to store the results of previously exited RPCs
+// until the client indicates that it has received those results and hence
+// the server no longer needs to store them. Store entries are added
+// one at a time, but the client indicates that it has received entries up to
+// given value and that all entries with key lower than that can be deleted.
+// Retrieving values is complicated by the fact that requests may arrive
+// out of order and hence one RPC may have to wait for another to complete
+// in order to access its stored results. A separate map of channels is
+// used to implement this synchronization.
+// TODO(cnicolaou): Servers protect themselves from badly behaved clients by
+// refusing to allocate beyond a certain number of results.
+type resultsStore struct {
+	sync.Mutex
+	store map[uint64]results
+	chans map[uint64]chan struct{}
+	keys  intHeap
+	// TODO(cnicolaou): Should addEntry/waitForEntry return an error when
+	// the calls do not match the frontier?
+	frontier uint64 // results with index less than this have been removed.
+}
+
+func newStore() *resultsStore {
+	r := &resultsStore{
+		store: make(map[uint64]results, initialResults),
+		chans: make(map[uint64]chan struct{}, initialOutOfOrderResults),
+	}
+	heap.Init(&r.keys)
+	return r
+}
+
+func (rs *resultsStore) addEntry(key uint64, data results) {
+	rs.Lock()
+	if _, present := rs.store[key]; !present && rs.frontier <= key {
+		rs.store[key] = data
+		heap.Push(&rs.keys, key)
+	}
+	if ch, present := rs.chans[key]; present {
+		close(ch)
+		delete(rs.chans, key)
+	}
+	rs.Unlock()
+}
+
+func (rs *resultsStore) removeEntriesTo(to uint64) {
+	rs.Lock()
+	if rs.frontier > to {
+		rs.Unlock()
+		return
+	}
+	rs.frontier = to + 1
+	for rs.keys.Len() > 0 && to >= rs.keys[0] {
+		k := heap.Pop(&rs.keys).(uint64)
+		delete(rs.store, k)
+		if ch, present := rs.chans[k]; present {
+			close(ch)
+			delete(rs.chans, k)
+		}
+	}
+	rs.Unlock()
+}
+
+func (rs *resultsStore) waitForEntry(key uint64) results {
+	rs.Lock()
+	if r, present := rs.store[key]; present {
+		rs.Unlock()
+		return r
+	}
+	if key < rs.frontier {
+		rs.Unlock()
+		return nil
+	}
+	// entry is not present, need to wait for it.
+	ch, present := rs.chans[key]
+	if !present {
+		heap.Push(&rs.keys, key)
+		ch = make(chan struct{}, 1)
+		rs.chans[key] = ch
+	}
+	rs.Unlock()
+	<-ch
+	rs.Lock()
+	defer rs.Unlock()
+	delete(rs.chans, key) // Allow the channel to be GC'ed.
+	return rs.store[key]  // This may be nil if the entry has been removed
+}
diff --git a/runtime/internal/rpc/results_store_test.go b/runtime/internal/rpc/results_store_test.go
new file mode 100644
index 0000000..7578acf
--- /dev/null
+++ b/runtime/internal/rpc/results_store_test.go
@@ -0,0 +1,148 @@
+// 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 rpc
+
+import (
+	"sort"
+	"sync"
+	"testing"
+
+	"v.io/x/ref/test/testutil"
+)
+
+func randomKeys() []uint64 {
+	n := (testutil.RandomIntn(256*10) / 10) + 256
+	k := make([]uint64, n)
+	for i := 0; i < n; i++ {
+		k[i] = uint64(testutil.RandomInt63())
+	}
+	return k
+}
+
+type keySlice []uint64
+
+func (p keySlice) Len() int           { return len(p) }
+func (p keySlice) Less(i, j int) bool { return p[i] < p[j] }
+func (p keySlice) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
+func (p keySlice) Sort()              { sort.Sort(p) }
+
+func TestStoreRandom(t *testing.T) {
+	testutil.InitRandGenerator(t.Logf)
+	store := newStore()
+	keys := randomKeys()
+
+	for i := 0; i < len(keys); i++ {
+		r := []interface{}{i}
+		store.addEntry(keys[i], r)
+	}
+	if len(store.store) != len(keys) {
+		t.Errorf("num stored entries: got %d, want %d", len(store.store), len(keys))
+	}
+	for i := 0; i < len(keys); i++ {
+		// Each call to removeEntries will remove an unknown number of entries
+		// depending on the original randomised value of the ints.
+		store.removeEntriesTo(keys[i])
+	}
+	if len(store.store) != 0 {
+		t.Errorf("store is not empty: %d", len(store.store))
+	}
+}
+
+func TestStoreOrdered(t *testing.T) {
+	testutil.InitRandGenerator(t.Logf)
+	store := newStore()
+	keys := randomKeys()
+
+	for i := 0; i < len(keys); i++ {
+		r := []interface{}{i}
+		store.addEntry(keys[i], r)
+	}
+	if len(store.store) != len(keys) {
+		t.Errorf("num stored entries: got %d, want %d", len(store.store), len(keys))
+	}
+
+	(keySlice(keys)).Sort()
+	l := len(keys)
+	for i := 0; i < len(keys); i++ {
+		store.removeEntriesTo(keys[i])
+		l--
+		if len(store.store) != l {
+			t.Errorf("failed to remove a single item(%d): %d != %d", keys[i], len(store.store), l)
+		}
+	}
+	if len(store.store) != 0 {
+		t.Errorf("store is not empty: %d", len(store.store))
+	}
+}
+
+func TestStoreWaitForEntry(t *testing.T) {
+	store := newStore()
+	store.addEntry(1, []interface{}{"1"})
+	r := store.waitForEntry(1)
+	if r[0].(string) != "1" {
+		t.Errorf("Got: %q, Want: %q", r[0], "1")
+	}
+	ch := make(chan string)
+	go func(ch chan string) {
+		r := store.waitForEntry(2)
+		ch <- r[0].(string)
+	}(ch)
+	store.addEntry(2, []interface{}{"2"})
+	if result := <-ch; result != "2" {
+		t.Errorf("Got: %q, Want: %q", r[0], "2")
+	}
+}
+
+func TestStoreWaitForEntryRandom(t *testing.T) {
+	testutil.InitRandGenerator(t.Logf)
+	store := newStore()
+	keys := randomKeys()
+	var wg sync.WaitGroup
+	for _, k := range keys {
+		wg.Add(1)
+		go func(t *testing.T, id uint64) {
+			r := store.waitForEntry(id)
+			if r[0].(uint64) != id {
+				t.Errorf("Got: %d, Want: %d", r[0].(uint64), id)
+			}
+			wg.Done()
+		}(t, k)
+	}
+	(keySlice(keys)).Sort()
+	for _, k := range keys {
+		store.addEntry(k, []interface{}{k})
+	}
+	wg.Wait()
+}
+
+func TestStoreWaitForRemovedEntry(t *testing.T) {
+	testutil.InitRandGenerator(t.Logf)
+	store := newStore()
+	keys := randomKeys()
+	var wg sync.WaitGroup
+	for _, k := range keys {
+		wg.Add(1)
+		go func(t *testing.T, id uint64) {
+			if r := store.waitForEntry(id); r != nil {
+				t.Errorf("Got %v, want nil", r)
+			}
+			wg.Done()
+		}(t, k)
+	}
+	(keySlice(keys)).Sort()
+	for _, k := range keys {
+		store.removeEntriesTo(k)
+	}
+	wg.Wait()
+}
+
+func TestStoreWaitForOldEntry(t *testing.T) {
+	store := newStore()
+	store.addEntry(1, []interface{}{"result"})
+	store.removeEntriesTo(1)
+	if got := store.waitForEntry(1); got != nil {
+		t.Errorf("Got %T=%v, want nil", got, got)
+	}
+}
diff --git a/runtime/internal/rpc/server.go b/runtime/internal/rpc/server.go
new file mode 100644
index 0000000..9f97089
--- /dev/null
+++ b/runtime/internal/rpc/server.go
@@ -0,0 +1,1422 @@
+// 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 rpc
+
+import (
+	"errors"
+	"fmt"
+	"io"
+	"net"
+	"reflect"
+	"strings"
+	"sync"
+	"time"
+
+	"v.io/x/lib/netstate"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/i18n"
+	"v.io/v23/namespace"
+	"v.io/v23/naming"
+	"v.io/v23/options"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/security/access"
+	"v.io/v23/vdl"
+	"v.io/v23/verror"
+	"v.io/v23/vom"
+	"v.io/v23/vtrace"
+
+	"v.io/x/ref/lib/apilog"
+	"v.io/x/ref/lib/pubsub"
+	"v.io/x/ref/lib/stats"
+	"v.io/x/ref/runtime/internal/lib/publisher"
+	inaming "v.io/x/ref/runtime/internal/naming"
+	"v.io/x/ref/runtime/internal/rpc/stream"
+	"v.io/x/ref/runtime/internal/rpc/stream/manager"
+	"v.io/x/ref/runtime/internal/rpc/stream/vc"
+)
+
+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.
+	errResponseEncoding          = reg(".errResponseEncoding", "failed to encode RPC response {3} <-> {4}{:5}")
+	errResultEncoding            = reg(".errResultEncoding", "failed to encode result #{3} [{4}]{:5}")
+	errFailedToResolveToEndpoint = reg(".errFailedToResolveToEndpoint", "failed to resolve {3} to an endpoint")
+	errFailedToResolveProxy      = reg(".errFailedToResolveProxy", "failed to resolve proxy {3}{:4}")
+	errFailedToListenForProxy    = reg(".errFailedToListenForProxy", "failed to listen on {3}{:4}")
+	errInternalTypeConversion    = reg(".errInternalTypeConversion", "failed to convert {3} to v.io/x/ref/runtime/internal/naming.Endpoint")
+	errFailedToParseIP           = reg(".errFailedToParseIP", "failed to parse {3} as an IP host")
+	errUnexpectedSuffix          = reg(".errUnexpectedSuffix", "suffix {3} was not expected because either server has the option IsLeaf set to true or it served an object and not a dispatcher")
+	errNoListeners               = reg(".errNoListeners", "failed to ceate any listeners{:3}")
+)
+
+// state for each requested listen address
+type listenState struct {
+	protocol, address string
+	ln                stream.Listener
+	lep               naming.Endpoint
+	lnerr, eperr      error
+	roaming           bool
+	// We keep track of all of the endpoints, the port and a copy of
+	// the original listen endpoint for use with roaming network changes.
+	ieps     []*inaming.Endpoint // list of currently active eps
+	port     string              // port to use for creating new eps
+	protoIEP inaming.Endpoint    // endpoint to use as template for new eps (includes rid, versions etc)
+}
+
+// state for each requested proxy
+type proxyState struct {
+	endpoint naming.Endpoint
+	err      error
+}
+
+type dhcpState struct {
+	name      string
+	publisher *pubsub.Publisher
+	stream    *pubsub.Stream
+	ch        chan pubsub.Setting // channel to receive dhcp settings over
+	err       error               // error status.
+	watchers  map[chan<- rpc.NetworkChange]struct{}
+}
+
+type server struct {
+	sync.Mutex
+	// context used by the server to make internal RPCs, error messages etc.
+	ctx               *context.T
+	cancel            context.CancelFunc   // function to cancel the above context.
+	state             serverState          // track state of the server.
+	streamMgr         stream.Manager       // stream manager to listen for new flows.
+	publisher         publisher.Publisher  // publisher to publish mounttable mounts.
+	dc                vc.DischargeClient   // fetches discharges of blessings
+	listenerOpts      []stream.ListenerOpt // listener opts for Listen.
+	settingsPublisher *pubsub.Publisher    // pubsub publisher for dhcp
+	settingsName      string               // pubwsub stream name for dhcp
+	dhcpState         *dhcpState           // dhcpState, nil if not using dhcp
+	principal         security.Principal
+	blessings         security.Blessings
+
+	// maps that contain state on listeners.
+	listenState map[*listenState]struct{}
+	listeners   map[stream.Listener]struct{}
+
+	// state of proxies keyed by the name of the proxy
+	proxies map[string]proxyState
+
+	disp               rpc.Dispatcher // dispatcher to serve RPCs
+	dispReserved       rpc.Dispatcher // dispatcher for reserved methods
+	active             sync.WaitGroup // active goroutines we've spawned.
+	stoppedChan        chan struct{}  // closed when the server has been stopped.
+	preferredProtocols []string       // protocols to use when resolving proxy name to endpoint.
+	// We cache the IP networks on the device since it is not that cheap to read
+	// network interfaces through os syscall.
+	// TODO(jhahn): Add monitoring the network interface changes.
+	ipNets           []*net.IPNet
+	ns               namespace.T
+	servesMountTable bool
+	isLeaf           bool
+
+	// TODO(cnicolaou): add roaming stats to rpcStats
+	stats *rpcStats // stats for this server.
+}
+
+type serverState int
+
+const (
+	initialized serverState = iota
+	listening
+	serving
+	publishing
+	stopping
+	stopped
+)
+
+// Simple state machine for the server implementation.
+type next map[serverState]bool
+type transitions map[serverState]next
+
+var (
+	states = transitions{
+		initialized: next{listening: true, stopping: true},
+		listening:   next{listening: true, serving: true, stopping: true},
+		serving:     next{publishing: true, stopping: true},
+		publishing:  next{publishing: true, stopping: true},
+		stopping:    next{},
+		stopped:     next{},
+	}
+
+	externalStates = map[serverState]rpc.ServerState{
+		initialized: rpc.ServerInit,
+		listening:   rpc.ServerActive,
+		serving:     rpc.ServerActive,
+		publishing:  rpc.ServerActive,
+		stopping:    rpc.ServerStopping,
+		stopped:     rpc.ServerStopped,
+	}
+)
+
+func (s *server) allowed(next serverState, method string) error {
+	if states[s.state][next] {
+		s.state = next
+		return nil
+	}
+	return verror.New(verror.ErrBadState, s.ctx, fmt.Sprintf("%s called out of order or more than once", method))
+}
+
+func (s *server) isStopState() bool {
+	return s.state == stopping || s.state == stopped
+}
+
+var _ rpc.Server = (*server)(nil)
+
+func InternalNewServer(
+	ctx *context.T,
+	streamMgr stream.Manager,
+	ns namespace.T,
+	settingsPublisher *pubsub.Publisher,
+	settingsName string,
+	client rpc.Client,
+	opts ...rpc.ServerOpt) (rpc.Server, error) {
+	ctx, cancel := context.WithRootCancel(ctx)
+	ctx, _ = vtrace.WithNewSpan(ctx, "NewServer")
+	statsPrefix := naming.Join("rpc", "server", "routing-id", streamMgr.RoutingID().String())
+	s := &server{
+		ctx:         ctx,
+		cancel:      cancel,
+		streamMgr:   streamMgr,
+		publisher:   publisher.New(ctx, ns, publishPeriod),
+		listenState: make(map[*listenState]struct{}),
+		listeners:   make(map[stream.Listener]struct{}),
+		proxies:     make(map[string]proxyState),
+		stoppedChan: make(chan struct{}),
+
+		ns:                ns,
+		stats:             newRPCStats(statsPrefix),
+		settingsPublisher: settingsPublisher,
+		settingsName:      settingsName,
+	}
+	var (
+		dischargeExpiryBuffer = vc.DefaultServerDischargeExpiryBuffer
+		securityLevel         options.SecurityLevel
+	)
+	ipNets, err := ipNetworks()
+	if err != nil {
+		return nil, err
+	}
+	s.ipNets = ipNets
+
+	for _, opt := range opts {
+		switch opt := opt.(type) {
+		case stream.ListenerOpt:
+			// Collect all ServerOpts that are also ListenerOpts.
+			s.listenerOpts = append(s.listenerOpts, opt)
+			switch opt := opt.(type) {
+			case vc.DischargeExpiryBuffer:
+				dischargeExpiryBuffer = time.Duration(opt)
+			}
+		case options.ServerBlessings:
+			s.blessings = opt.Blessings
+		case options.ServesMountTable:
+			s.servesMountTable = bool(opt)
+		case options.IsLeaf:
+			s.isLeaf = bool(opt)
+		case ReservedNameDispatcher:
+			s.dispReserved = opt.Dispatcher
+		case PreferredServerResolveProtocols:
+			s.preferredProtocols = []string(opt)
+		case options.SecurityLevel:
+			securityLevel = opt
+
+		}
+	}
+
+	authenticateVC := true
+
+	if securityLevel == options.SecurityNone {
+		authenticateVC = false
+		s.blessings = security.Blessings{}
+		s.dispReserved = nil
+	}
+	if authenticateVC {
+		s.principal = v23.GetPrincipal(ctx)
+		if s.blessings.IsZero() && s.principal != nil {
+			s.blessings = s.principal.BlessingStore().Default()
+		}
+	}
+
+	// Make dischargeExpiryBuffer shorter than the VC discharge buffer to ensure we have fetched
+	// the discharges by the time the VC asks for them.
+	s.dc = InternalNewDischargeClient(ctx, client, dischargeExpiryBuffer-(5*time.Second))
+	s.listenerOpts = append(s.listenerOpts, s.dc)
+	s.listenerOpts = append(s.listenerOpts, stream.AuthenticatedVC(authenticateVC))
+	blessingsStatsName := naming.Join(statsPrefix, "security", "blessings")
+	// TODO(caprita): revist printing the blessings with %s, and
+	// instead expose them as a list.
+	stats.NewString(blessingsStatsName).Set(fmt.Sprintf("%s", s.blessings))
+	if s.principal != nil {
+		stats.NewStringFunc(blessingsStatsName, func() string {
+			return fmt.Sprintf("%s (default)", s.principal.BlessingStore().Default())
+		})
+	}
+	return s, nil
+}
+
+func (s *server) Status() rpc.ServerStatus {
+	defer apilog.LogCall(nil)(nil) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	status := rpc.ServerStatus{}
+	s.Lock()
+	defer s.Unlock()
+	status.State = externalStates[s.state]
+	status.ServesMountTable = s.servesMountTable
+	status.Mounts = s.publisher.Status()
+	status.Endpoints = []naming.Endpoint{}
+	for ls, _ := range s.listenState {
+		if ls.eperr != nil {
+			status.Errors = append(status.Errors, ls.eperr)
+		}
+		if ls.lnerr != nil {
+			status.Errors = append(status.Errors, ls.lnerr)
+		}
+		for _, iep := range ls.ieps {
+			status.Endpoints = append(status.Endpoints, iep)
+		}
+	}
+	status.Proxies = make([]rpc.ProxyStatus, 0, len(s.proxies))
+	for k, v := range s.proxies {
+		status.Proxies = append(status.Proxies, rpc.ProxyStatus{
+			Proxy:    k,
+			Endpoint: v.endpoint,
+			Error:    v.err,
+		})
+	}
+	return status
+}
+
+func (s *server) WatchNetwork(ch chan<- rpc.NetworkChange) {
+	defer apilog.LogCallf(nil, "ch=")(nil, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	s.Lock()
+	defer s.Unlock()
+	if s.dhcpState != nil {
+		s.dhcpState.watchers[ch] = struct{}{}
+	}
+}
+
+func (s *server) UnwatchNetwork(ch chan<- rpc.NetworkChange) {
+	defer apilog.LogCallf(nil, "ch=")(nil, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	s.Lock()
+	defer s.Unlock()
+	if s.dhcpState != nil {
+		delete(s.dhcpState.watchers, ch)
+	}
+}
+
+// resolveToEndpoint resolves an object name or address to an endpoint.
+func (s *server) resolveToEndpoint(address string) (string, error) {
+	var resolved *naming.MountEntry
+	var err error
+	if s.ns != nil {
+		if resolved, err = s.ns.Resolve(s.ctx, address); err != nil {
+			return "", err
+		}
+	} else {
+		// Fake a namespace resolution
+		resolved = &naming.MountEntry{Servers: []naming.MountedServer{
+			{Server: address},
+		}}
+	}
+	// An empty set of protocols means all protocols...
+	if resolved.Servers, err = filterAndOrderServers(resolved.Servers, s.preferredProtocols, s.ipNets); err != nil {
+		return "", err
+	}
+	for _, n := range resolved.Names() {
+		address, suffix := naming.SplitAddressName(n)
+		if suffix != "" {
+			continue
+		}
+		if ep, err := inaming.NewEndpoint(address); err == nil {
+			return ep.String(), nil
+		}
+	}
+	return "", verror.New(errFailedToResolveToEndpoint, s.ctx, address)
+}
+
+// createEndpoints creates appropriate inaming.Endpoint instances for
+// all of the externally accessible network addresses that can be used
+// to reach this server.
+func (s *server) createEndpoints(lep naming.Endpoint, chooser netstate.AddressChooser) ([]*inaming.Endpoint, string, bool, error) {
+	iep, ok := lep.(*inaming.Endpoint)
+	if !ok {
+		return nil, "", false, verror.New(errInternalTypeConversion, nil, fmt.Sprintf("%T", lep))
+	}
+	if !strings.HasPrefix(iep.Protocol, "tcp") &&
+		!strings.HasPrefix(iep.Protocol, "ws") {
+		// If not tcp, ws, or wsh, just return the endpoint we were given.
+		return []*inaming.Endpoint{iep}, "", false, nil
+	}
+	host, port, err := net.SplitHostPort(iep.Address)
+	if err != nil {
+		return nil, "", false, err
+	}
+	addrs, unspecified, err := netstate.PossibleAddresses(iep.Protocol, host, chooser)
+	if err != nil {
+		return nil, port, false, err
+	}
+
+	ieps := make([]*inaming.Endpoint, 0, len(addrs))
+	for _, addr := range addrs {
+		n, err := inaming.NewEndpoint(lep.String())
+		if err != nil {
+			return nil, port, false, err
+		}
+		n.IsMountTable = s.servesMountTable
+		n.Address = net.JoinHostPort(addr.String(), port)
+		ieps = append(ieps, n)
+	}
+	return ieps, port, unspecified, nil
+}
+
+func (s *server) Listen(listenSpec rpc.ListenSpec) ([]naming.Endpoint, error) {
+	defer apilog.LogCallf(nil, "listenSpec=")(nil, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	useProxy := len(listenSpec.Proxy) > 0
+	if !useProxy && len(listenSpec.Addrs) == 0 {
+		return nil, verror.New(verror.ErrBadArg, s.ctx, "ListenSpec contains no proxy or addresses to listen on")
+	}
+
+	s.Lock()
+	defer s.Unlock()
+
+	if err := s.allowed(listening, "Listen"); err != nil {
+		return nil, err
+	}
+
+	// Start the proxy as early as possible, ignore duplicate requests
+	// for the same proxy.
+	if _, inuse := s.proxies[listenSpec.Proxy]; useProxy && !inuse {
+		// Pre-emptively fetch discharges on the blessings (they will be cached
+		// within s.dc for future calls).
+		// This shouldn't be required, but is a hack to reduce flakiness in
+		// JavaScript browser integration tests.
+		// See https://v.io/i/392
+		s.dc.PrepareDischarges(s.ctx, s.blessings.ThirdPartyCaveats(), security.DischargeImpetus{})
+		// We have a goroutine for listening on proxy connections.
+		s.active.Add(1)
+		go func() {
+			s.proxyListenLoop(listenSpec.Proxy)
+			s.active.Done()
+		}()
+	}
+
+	roaming := false
+	lnState := make([]*listenState, 0, len(listenSpec.Addrs))
+	for _, addr := range listenSpec.Addrs {
+		if len(addr.Address) > 0 {
+			// Listen if we have a local address to listen on.
+			ls := &listenState{
+				protocol: addr.Protocol,
+				address:  addr.Address,
+			}
+			ls.ln, ls.lep, ls.lnerr = s.streamMgr.Listen(s.ctx, addr.Protocol, addr.Address, s.blessings, s.listenerOpts...)
+			lnState = append(lnState, ls)
+			if ls.lnerr != nil {
+				s.ctx.VI(2).Infof("Listen(%q, %q, ...) failed: %v", addr.Protocol, addr.Address, ls.lnerr)
+				continue
+			}
+			ls.ieps, ls.port, ls.roaming, ls.eperr = s.createEndpoints(ls.lep, listenSpec.AddressChooser)
+			if ls.roaming && ls.eperr == nil {
+				ls.protoIEP = *ls.lep.(*inaming.Endpoint)
+				roaming = true
+			}
+		}
+	}
+
+	found := false
+	var lastErr error
+	for _, ls := range lnState {
+		if ls.ln != nil {
+			found = true
+			break
+		}
+		if ls.lnerr != nil {
+			lastErr = ls.lnerr
+		}
+	}
+	if !found && !useProxy {
+		return nil, verror.New(verror.ErrBadArg, s.ctx, verror.New(errNoListeners, s.ctx, lastErr))
+	}
+
+	if roaming && s.dhcpState == nil && s.settingsPublisher != nil {
+		// Create a dhcp listener if we haven't already done so.
+		dhcp := &dhcpState{
+			name:      s.settingsName,
+			publisher: s.settingsPublisher,
+			watchers:  make(map[chan<- rpc.NetworkChange]struct{}),
+		}
+		s.dhcpState = dhcp
+		dhcp.ch = make(chan pubsub.Setting, 10)
+		dhcp.stream, dhcp.err = dhcp.publisher.ForkStream(dhcp.name, dhcp.ch)
+		if dhcp.err == nil {
+			// We have a goroutine to listen for dhcp changes.
+			s.active.Add(1)
+			go func() {
+				s.dhcpLoop(dhcp.ch)
+				s.active.Done()
+			}()
+		}
+	}
+
+	eps := make([]naming.Endpoint, 0, 10)
+	for _, ls := range lnState {
+		s.listenState[ls] = struct{}{}
+		if ls.ln != nil {
+			// We have a goroutine per listener to accept new flows.
+			// Each flow is served from its own goroutine.
+			s.active.Add(1)
+			go func(ln stream.Listener, ep naming.Endpoint) {
+				s.listenLoop(ln, ep)
+				s.active.Done()
+			}(ls.ln, ls.lep)
+		}
+
+		for _, iep := range ls.ieps {
+			eps = append(eps, iep)
+		}
+	}
+
+	return eps, nil
+}
+
+func (s *server) reconnectAndPublishProxy(proxy string) (*inaming.Endpoint, stream.Listener, error) {
+	resolved, err := s.resolveToEndpoint(proxy)
+	if err != nil {
+		return nil, nil, verror.New(errFailedToResolveProxy, s.ctx, proxy, err)
+	}
+	opts := append([]stream.ListenerOpt{proxyAuth{s}}, s.listenerOpts...)
+	ln, ep, err := s.streamMgr.Listen(s.ctx, inaming.Network, resolved, s.blessings, opts...)
+	if err != nil {
+		return nil, nil, verror.New(errFailedToListenForProxy, s.ctx, resolved, err)
+	}
+	iep, ok := ep.(*inaming.Endpoint)
+	if !ok {
+		ln.Close()
+		return nil, nil, verror.New(errInternalTypeConversion, s.ctx, fmt.Sprintf("%T", ep))
+	}
+	s.Lock()
+	s.proxies[proxy] = proxyState{iep, nil}
+	s.Unlock()
+	iep.IsMountTable = s.servesMountTable
+	iep.IsLeaf = s.isLeaf
+	s.publisher.AddServer(iep.String())
+	return iep, ln, nil
+}
+
+func (s *server) proxyListenLoop(proxy string) {
+	const (
+		min = 5 * time.Millisecond
+		max = 5 * time.Minute
+	)
+
+	iep, ln, err := s.reconnectAndPublishProxy(proxy)
+	if err != nil {
+		s.ctx.Errorf("Failed to connect to proxy: %s", err)
+	}
+	// the initial connection maybe have failed, but we enter the retry
+	// loop anyway so that we will continue to try and connect to the
+	// proxy.
+	s.Lock()
+	if s.isStopState() {
+		s.Unlock()
+		return
+	}
+	s.Unlock()
+
+	for {
+		if ln != nil && iep != nil {
+			err := s.listenLoop(ln, iep)
+			// The listener is done, so:
+			// (1) Unpublish its name
+			s.publisher.RemoveServer(iep.String())
+			s.Lock()
+			if err != nil {
+				s.proxies[proxy] = proxyState{iep, verror.New(verror.ErrNoServers, s.ctx, err)}
+			} else {
+				// err will be nil if we're stopping.
+				s.proxies[proxy] = proxyState{iep, nil}
+				s.Unlock()
+				return
+			}
+			s.Unlock()
+		}
+
+		s.Lock()
+		if s.isStopState() {
+			s.Unlock()
+			return
+		}
+		s.Unlock()
+
+		// (2) Reconnect to the proxy unless the server has been stopped
+		backoff := min
+		ln = nil
+		for {
+			select {
+			case <-time.After(backoff):
+				if backoff = backoff * 2; backoff > max {
+					backoff = max
+				}
+			case <-s.stoppedChan:
+				return
+			}
+			// (3) reconnect, publish new address
+			if iep, ln, err = s.reconnectAndPublishProxy(proxy); err != nil {
+				s.ctx.Errorf("Failed to reconnect to proxy %q: %s", proxy, err)
+			} else {
+				s.ctx.VI(1).Infof("Reconnected to proxy %q, %s", proxy, iep)
+				break
+			}
+		}
+	}
+}
+
+// addListener adds the supplied listener taking care to
+// check to see if we're already stopping. It returns true
+// if the listener was added.
+func (s *server) addListener(ln stream.Listener) bool {
+	s.Lock()
+	defer s.Unlock()
+	if s.isStopState() {
+		return false
+	}
+	s.listeners[ln] = struct{}{}
+	return true
+}
+
+// rmListener removes the supplied listener taking care to
+// check if we're already stopping. It returns true if the
+// listener was removed.
+func (s *server) rmListener(ln stream.Listener) bool {
+	s.Lock()
+	defer s.Unlock()
+	if s.isStopState() {
+		return false
+	}
+	delete(s.listeners, ln)
+	return true
+}
+
+func (s *server) listenLoop(ln stream.Listener, ep naming.Endpoint) error {
+	defer s.ctx.VI(1).Infof("rpc: Stopped listening on %s", ep)
+	var calls sync.WaitGroup
+
+	if !s.addListener(ln) {
+		// We're stopping.
+		return nil
+	}
+
+	defer func() {
+		calls.Wait()
+		s.rmListener(ln)
+	}()
+	for {
+		flow, err := ln.Accept()
+		if err != nil {
+			s.ctx.VI(10).Infof("rpc: Accept on %v failed: %v", ep, err)
+			return err
+		}
+		calls.Add(1)
+		go func(flow stream.Flow) {
+			defer calls.Done()
+			fs, err := newFlowServer(flow, s)
+			if err != nil {
+				s.ctx.VI(1).Infof("newFlowServer on %v failed: %v", ep, err)
+				return
+			}
+			if err := fs.serve(); err != nil {
+				// TODO(caprita): Logging errors here is too spammy. For example, "not
+				// authorized" errors shouldn't be logged as server errors.
+				// TODO(cnicolaou): revisit this when verror2 transition is
+				// done.
+				if err != io.EOF {
+					s.ctx.VI(2).Infof("Flow.serve on %v failed: %v", ep, err)
+				}
+			}
+		}(flow)
+	}
+}
+
+func (s *server) dhcpLoop(ch chan pubsub.Setting) {
+	defer s.ctx.VI(1).Infof("rpc: Stopped listen for dhcp changes")
+	s.ctx.VI(2).Infof("rpc: dhcp loop")
+	for setting := range ch {
+		if setting == nil {
+			return
+		}
+		switch v := setting.Value().(type) {
+		case []net.Addr:
+			s.Lock()
+			if s.isStopState() {
+				s.Unlock()
+				return
+			}
+			change := rpc.NetworkChange{
+				Time:  time.Now(),
+				State: externalStates[s.state],
+			}
+			switch setting.Name() {
+			case NewAddrsSetting:
+				change.Changed = s.addAddresses(v)
+				change.AddedAddrs = v
+			case RmAddrsSetting:
+				change.Changed, change.Error = s.removeAddresses(v)
+				change.RemovedAddrs = v
+			}
+			s.ctx.VI(2).Infof("rpc: dhcp: change %v", change)
+			for ch, _ := range s.dhcpState.watchers {
+				select {
+				case ch <- change:
+				default:
+				}
+			}
+			s.Unlock()
+		default:
+			s.ctx.Errorf("rpc: dhcpLoop: unhandled setting type %T", v)
+		}
+	}
+}
+
+func getHost(address net.Addr) string {
+	host, _, err := net.SplitHostPort(address.String())
+	if err == nil {
+		return host
+	}
+	return address.String()
+
+}
+
+// Remove all endpoints that have the same host address as the supplied
+// address parameter.
+func (s *server) removeAddresses(addrs []net.Addr) ([]naming.Endpoint, error) {
+	var removed []naming.Endpoint
+	for _, address := range addrs {
+		host := getHost(address)
+		for ls, _ := range s.listenState {
+			if ls != nil && ls.roaming && len(ls.ieps) > 0 {
+				remaining := make([]*inaming.Endpoint, 0, len(ls.ieps))
+				for _, iep := range ls.ieps {
+					lnHost, _, err := net.SplitHostPort(iep.Address)
+					if err != nil {
+						lnHost = iep.Address
+					}
+					if lnHost == host {
+						s.ctx.VI(2).Infof("rpc: dhcp removing: %s", iep)
+						removed = append(removed, iep)
+						s.publisher.RemoveServer(iep.String())
+						continue
+					}
+					remaining = append(remaining, iep)
+				}
+				ls.ieps = remaining
+			}
+		}
+	}
+	return removed, nil
+}
+
+// Add new endpoints for the new address. There is no way to know with
+// 100% confidence which new endpoints to publish without shutting down
+// all network connections and reinitializing everything from scratch.
+// Instead, we find all roaming listeners with at least one endpoint
+// and create a new endpoint with the same port as the existing ones
+// but with the new address supplied to us to by the dhcp code. As
+// an additional safeguard we reject the new address if it is not
+// externally accessible.
+// This places the onus on the dhcp/roaming code that sends us addresses
+// to ensure that those addresses are externally reachable.
+func (s *server) addAddresses(addrs []net.Addr) []naming.Endpoint {
+	var added []naming.Endpoint
+	for _, address := range netstate.ConvertToAddresses(addrs) {
+		if !netstate.IsAccessibleIP(address) {
+			return added
+		}
+		host := getHost(address)
+		for ls, _ := range s.listenState {
+			if ls != nil && ls.roaming {
+				niep := ls.protoIEP
+				niep.Address = net.JoinHostPort(host, ls.port)
+				niep.IsMountTable = s.servesMountTable
+				niep.IsLeaf = s.isLeaf
+				ls.ieps = append(ls.ieps, &niep)
+				s.ctx.VI(2).Infof("rpc: dhcp adding: %s", niep)
+				s.publisher.AddServer(niep.String())
+				added = append(added, &niep)
+			}
+		}
+	}
+	return added
+}
+
+type leafDispatcher struct {
+	invoker rpc.Invoker
+	auth    security.Authorizer
+}
+
+func (d leafDispatcher) Lookup(ctx *context.T, suffix string) (interface{}, security.Authorizer, error) {
+	defer apilog.LogCallf(nil, "suffix=%.10s...", suffix)(nil, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	if suffix != "" {
+		return nil, nil, verror.New(verror.ErrUnknownSuffix, nil, suffix)
+	}
+	return d.invoker, d.auth, nil
+}
+
+func (s *server) Serve(name string, obj interface{}, authorizer security.Authorizer) error {
+	defer apilog.LogCallf(nil, "name=%.10s...,obj=,authorizer=", name)(nil, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	if obj == nil {
+		return verror.New(verror.ErrBadArg, s.ctx, "nil object")
+	}
+	invoker, err := objectToInvoker(obj)
+	if err != nil {
+		return verror.New(verror.ErrBadArg, s.ctx, fmt.Sprintf("bad object: %v", err))
+	}
+	s.setLeaf(true)
+	return s.ServeDispatcher(name, &leafDispatcher{invoker, authorizer})
+}
+
+func (s *server) setLeaf(value bool) {
+	s.Lock()
+	defer s.Unlock()
+	s.isLeaf = value
+	for ls, _ := range s.listenState {
+		for i := range ls.ieps {
+			ls.ieps[i].IsLeaf = s.isLeaf
+		}
+	}
+}
+
+func (s *server) ServeDispatcher(name string, disp rpc.Dispatcher) error {
+	defer apilog.LogCallf(nil, "name=%.10s...,disp=", name)(nil, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	if disp == nil {
+		return verror.New(verror.ErrBadArg, s.ctx, "nil dispatcher")
+	}
+	s.Lock()
+	defer s.Unlock()
+	if err := s.allowed(serving, "Serve or ServeDispatcher"); err != nil {
+		return err
+	}
+	vtrace.GetSpan(s.ctx).Annotate("Serving under name: " + name)
+	s.disp = disp
+	if len(name) > 0 {
+		for ls, _ := range s.listenState {
+			for _, iep := range ls.ieps {
+				s.publisher.AddServer(iep.String())
+			}
+		}
+		s.publisher.AddName(name, s.servesMountTable, s.isLeaf)
+	}
+	return nil
+}
+
+func (s *server) AddName(name string) error {
+	defer apilog.LogCallf(nil, "name=%.10s...", name)(nil, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	if len(name) == 0 {
+		return verror.New(verror.ErrBadArg, s.ctx, "name is empty")
+	}
+	s.Lock()
+	defer s.Unlock()
+	if err := s.allowed(publishing, "AddName"); err != nil {
+		return err
+	}
+	vtrace.GetSpan(s.ctx).Annotate("Serving under name: " + name)
+	s.publisher.AddName(name, s.servesMountTable, s.isLeaf)
+	return nil
+}
+
+func (s *server) RemoveName(name string) {
+	defer apilog.LogCallf(nil, "name=%.10s...", name)(nil, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	s.Lock()
+	defer s.Unlock()
+	if err := s.allowed(publishing, "RemoveName"); err != nil {
+		return
+	}
+	vtrace.GetSpan(s.ctx).Annotate("Removed name: " + name)
+	s.publisher.RemoveName(name)
+}
+
+func (s *server) Stop() error {
+	defer apilog.LogCall(nil)(nil) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	serverDebug := fmt.Sprintf("Dispatcher: %T, Status:[%v]", s.disp, s.Status())
+	s.ctx.VI(1).Infof("Stop: %s", serverDebug)
+	defer s.ctx.VI(1).Infof("Stop done: %s", serverDebug)
+	s.Lock()
+	if s.isStopState() {
+		s.Unlock()
+		return nil
+	}
+	s.state = stopping
+	close(s.stoppedChan)
+	s.Unlock()
+
+	// Delete the stats object.
+	s.stats.stop()
+
+	// Note, It's safe to Stop/WaitForStop on the publisher outside of the
+	// server lock, since publisher is safe for concurrent access.
+
+	// Stop the publisher, which triggers unmounting of published names.
+	s.publisher.Stop()
+	// Wait for the publisher to be done unmounting before we can proceed to
+	// close the listeners (to minimize the number of mounted names pointing
+	// to endpoint that are no longer serving).
+	//
+	// TODO(caprita): See if make sense to fail fast on rejecting
+	// connections once listeners are closed, and parallelize the publisher
+	// and listener shutdown.
+	s.publisher.WaitForStop()
+
+	s.Lock()
+
+	// Close all listeners.  No new flows will be accepted, while in-flight
+	// flows will continue until they terminate naturally.
+	nListeners := len(s.listeners)
+	errCh := make(chan error, nListeners)
+
+	for ln, _ := range s.listeners {
+		go func(ln stream.Listener) {
+			errCh <- ln.Close()
+		}(ln)
+	}
+
+	drain := func(ch chan pubsub.Setting) {
+		for {
+			select {
+			case v := <-ch:
+				if v == nil {
+					return
+				}
+			default:
+				close(ch)
+				return
+			}
+		}
+	}
+
+	if dhcp := s.dhcpState; dhcp != nil {
+		// TODO(cnicolaou,caprita): investigate not having to close and drain
+		// the channel here. It's a little awkward right now since we have to
+		// be careful to not close the channel in two places, i.e. here and
+		// and from the publisher's Shutdown method.
+		if err := dhcp.publisher.CloseFork(dhcp.name, dhcp.ch); err == nil {
+			drain(dhcp.ch)
+		}
+	}
+
+	s.Unlock()
+
+	var firstErr error
+	for i := 0; i < nListeners; i++ {
+		if err := <-errCh; err != nil && firstErr == nil {
+			firstErr = err
+		}
+	}
+	// At this point, we are guaranteed that no new requests are going to be
+	// accepted.
+
+	// Wait for the publisher and active listener + flows to finish.
+	done := make(chan struct{}, 1)
+	go func() { s.active.Wait(); done <- struct{}{} }()
+
+	select {
+	case <-done:
+	case <-time.After(5 * time.Second):
+		s.ctx.Errorf("%s: Listener Close Error: %v", serverDebug, firstErr)
+		s.ctx.Errorf("%s: Timedout waiting for goroutines to stop: listeners: %d (currently: %d)", serverDebug, nListeners, len(s.listeners))
+		for ln, _ := range s.listeners {
+			s.ctx.Errorf("%s: Listener: %v", serverDebug, ln)
+		}
+		for ls, _ := range s.listenState {
+			s.ctx.Errorf("%s: ListenState: %v", serverDebug, ls)
+		}
+		<-done
+		s.ctx.Infof("%s: Done waiting.", serverDebug)
+	}
+
+	s.Lock()
+	defer s.Unlock()
+	s.disp = nil
+	if firstErr != nil {
+		return verror.New(verror.ErrInternal, s.ctx, firstErr)
+	}
+	s.state = stopped
+	s.cancel()
+	return nil
+}
+
+// flowServer implements the RPC server-side protocol for a single RPC, over a
+// flow that's already connected to the client.
+type flowServer struct {
+	ctx    *context.T     // context associated with the RPC
+	server *server        // rpc.Server that this flow server belongs to
+	disp   rpc.Dispatcher // rpc.Dispatcher that will serve RPCs on this flow
+	dec    *vom.Decoder   // to decode requests and args from the client
+	enc    *vom.Encoder   // to encode responses and results to the client
+	flow   stream.Flow    // underlying flow
+
+	// Fields filled in during the server invocation.
+	clientBlessings  security.Blessings
+	ackBlessings     bool
+	grantedBlessings security.Blessings
+	method, suffix   string
+	tags             []*vdl.Value
+	discharges       map[string]security.Discharge
+	starttime        time.Time
+	endStreamArgs    bool // are the stream args at EOF?
+}
+
+var (
+	_ rpc.StreamServerCall = (*flowServer)(nil)
+	_ security.Call        = (*flowServer)(nil)
+)
+
+func newFlowServer(flow stream.Flow, server *server) (*flowServer, error) {
+	server.Lock()
+	disp := server.disp
+	server.Unlock()
+
+	fs := &flowServer{
+		ctx:        server.ctx,
+		server:     server,
+		disp:       disp,
+		flow:       flow,
+		discharges: make(map[string]security.Discharge),
+	}
+	typeenc := flow.VCDataCache().Get(vc.TypeEncoderKey{})
+	if typeenc == nil {
+		fs.enc = vom.NewEncoder(flow)
+		fs.dec = vom.NewDecoder(flow)
+	} else {
+		fs.enc = vom.NewEncoderWithTypeEncoder(flow, typeenc.(*vom.TypeEncoder))
+		typedec := flow.VCDataCache().Get(vc.TypeDecoderKey{})
+		fs.dec = vom.NewDecoderWithTypeDecoder(flow, typedec.(*vom.TypeDecoder))
+	}
+	return fs, nil
+}
+
+// authorizeVtrace works by simulating a call to __debug/vtrace.Trace.  That
+// rpc is essentially equivalent in power to the data we are attempting to
+// attach here.
+func (fs *flowServer) authorizeVtrace(ctx *context.T) error {
+	// Set up a context as though we were calling __debug/vtrace.
+	params := &security.CallParams{}
+	params.Copy(fs)
+	params.Method = "Trace"
+	params.MethodTags = []*vdl.Value{vdl.ValueOf(access.Debug)}
+	params.Suffix = "__debug/vtrace"
+
+	var auth security.Authorizer
+	if fs.server.dispReserved != nil {
+		_, auth, _ = fs.server.dispReserved.Lookup(ctx, params.Suffix)
+	}
+	return authorize(fs.ctx, security.NewCall(params), auth)
+}
+
+func (fs *flowServer) serve() error {
+	defer fs.flow.Close()
+
+	results, err := fs.processRequest()
+
+	vtrace.GetSpan(fs.ctx).Finish()
+
+	var traceResponse vtrace.Response
+	// Check if the caller is permitted to view vtrace data.
+	if fs.authorizeVtrace(fs.ctx) == nil {
+		traceResponse = vtrace.GetResponse(fs.ctx)
+	}
+
+	// Respond to the client with the response header and positional results.
+	response := rpc.Response{
+		Error:            err,
+		EndStreamResults: true,
+		NumPosResults:    uint64(len(results)),
+		TraceResponse:    traceResponse,
+		AckBlessings:     fs.ackBlessings,
+	}
+	if err := fs.enc.Encode(response); err != nil {
+		if err == io.EOF {
+			return err
+		}
+		return verror.New(errResponseEncoding, fs.ctx, fs.LocalEndpoint().String(), fs.RemoteEndpoint().String(), err)
+	}
+	if response.Error != nil {
+		return response.Error
+	}
+	for ix, res := range results {
+		if err := fs.enc.Encode(res); err != nil {
+			if err == io.EOF {
+				return err
+			}
+			return verror.New(errResultEncoding, fs.ctx, ix, fmt.Sprintf("%T=%v", res, res), err)
+		}
+	}
+	// TODO(ashankar): Should unread data from the flow be drained?
+	//
+	// Reason to do so:
+	// The common stream.Flow implementation (v.io/x/ref/runtime/internal/rpc/stream/vc/reader.go)
+	// uses iobuf.Slices backed by an iobuf.Pool. If the stream is not drained, these
+	// slices will not be returned to the pool leading to possibly increased memory usage.
+	//
+	// Reason to not do so:
+	// Draining here will conflict with any Reads on the flow in a separate goroutine
+	// (for example, see TestStreamReadTerminatedByServer in full_test.go).
+	//
+	// For now, go with the reason to not do so as having unread data in the stream
+	// should be a rare case.
+	return nil
+}
+
+func (fs *flowServer) readRPCRequest() (*rpc.Request, error) {
+	// Set a default timeout before reading from the flow. Without this timeout,
+	// a client that sends no request or a partial request will retain the flow
+	// indefinitely (and lock up server resources).
+	initTimer := newTimer(defaultCallTimeout)
+	defer initTimer.Stop()
+	fs.flow.SetDeadline(initTimer.C)
+
+	// Decode the initial request.
+	var req rpc.Request
+	if err := fs.dec.Decode(&req); err != nil {
+		return nil, verror.New(verror.ErrBadProtocol, fs.ctx, newErrBadRequest(fs.ctx, err))
+	}
+	return &req, nil
+}
+
+func (fs *flowServer) processRequest() ([]interface{}, error) {
+	fs.starttime = time.Now()
+	req, err := fs.readRPCRequest()
+	if err != nil {
+		// We don't know what the rpc call was supposed to be, but we'll create
+		// a placeholder span so we can capture annotations.
+		fs.ctx, _ = vtrace.WithNewSpan(fs.ctx, fmt.Sprintf("\"%s\".UNKNOWN", fs.suffix))
+		return nil, err
+	}
+	// We must call fs.drainDecoderArgs for any error that occurs
+	// after this point, and before we actually decode the arguments.
+	fs.method = req.Method
+	fs.suffix = strings.TrimLeft(req.Suffix, "/")
+
+	if req.Language != "" {
+		fs.ctx = i18n.WithLangID(fs.ctx, i18n.LangID(req.Language))
+	}
+
+	// TODO(mattr): Currently this allows users to trigger trace collection
+	// on the server even if they will not be allowed to collect the
+	// results later.  This might be considered a DOS vector.
+	spanName := fmt.Sprintf("\"%s\".%s", fs.suffix, fs.method)
+	fs.ctx, _ = vtrace.WithContinuedTrace(fs.ctx, spanName, req.TraceRequest)
+
+	var cancel context.CancelFunc
+	if !req.Deadline.IsZero() {
+		fs.ctx, cancel = context.WithDeadline(fs.ctx, req.Deadline.Time)
+	} else {
+		fs.ctx, cancel = context.WithCancel(fs.ctx)
+	}
+	fs.flow.SetDeadline(fs.ctx.Done())
+	go fs.cancelContextOnClose(cancel)
+
+	// Initialize security: blessings, discharges, etc.
+	if err := fs.initSecurity(req); err != nil {
+		fs.drainDecoderArgs(int(req.NumPosArgs))
+		return nil, err
+	}
+	// Lookup the invoker.
+	invoker, auth, err := fs.lookup(fs.suffix, fs.method)
+	if err != nil {
+		fs.drainDecoderArgs(int(req.NumPosArgs))
+		return nil, err
+	}
+
+	// Note that we strip the reserved prefix when calling the invoker so
+	// that __Glob will call Glob.  Note that we've already assigned a
+	// special invoker so that we never call the wrong method by mistake.
+	strippedMethod := naming.StripReserved(fs.method)
+
+	// Prepare invoker and decode args.
+	numArgs := int(req.NumPosArgs)
+	argptrs, tags, err := invoker.Prepare(fs.ctx, strippedMethod, numArgs)
+	fs.tags = tags
+	if err != nil {
+		fs.drainDecoderArgs(numArgs)
+		return nil, err
+	}
+	if called, want := req.NumPosArgs, uint64(len(argptrs)); called != want {
+		fs.drainDecoderArgs(numArgs)
+		return nil, newErrBadNumInputArgs(fs.ctx, fs.suffix, fs.method, called, want)
+	}
+	for ix, argptr := range argptrs {
+		if err := fs.dec.Decode(argptr); err != nil {
+			return nil, newErrBadInputArg(fs.ctx, fs.suffix, fs.method, uint64(ix), err)
+		}
+	}
+
+	// Check application's authorization policy.
+	if err := authorize(fs.ctx, fs, auth); err != nil {
+		return nil, err
+	}
+
+	// Invoke the method.
+	results, err := invoker.Invoke(fs.ctx, fs, strippedMethod, argptrs)
+	fs.server.stats.record(fs.method, time.Since(fs.starttime))
+	return results, err
+}
+
+// drainDecoderArgs drains the next n arguments encoded onto the flows decoder.
+// This is needed to ensure that the client is able to encode all of its args
+// before the server closes its flow. This guarantees that the client will
+// consistently get the server's error response.
+// TODO(suharshs): Figure out a better way to solve this race condition without
+// unnecessarily reading all arguments.
+func (fs *flowServer) drainDecoderArgs(n int) error {
+	for i := 0; i < n; i++ {
+		if err := fs.dec.Ignore(); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func (fs *flowServer) cancelContextOnClose(cancel context.CancelFunc) {
+	// Ensure that the context gets cancelled if the flow is closed
+	// due to a network error, or client cancellation.
+	select {
+	case <-fs.flow.Closed():
+		// Here we remove the contexts channel as a deadline to the flow.
+		// We do this to ensure clients get a consistent error when they read/write
+		// after the flow is closed.  Since the flow is already closed, it doesn't
+		// matter that the context is also cancelled.
+		fs.flow.SetDeadline(nil)
+		cancel()
+	case <-fs.ctx.Done():
+	}
+}
+
+// lookup returns the invoker and authorizer responsible for serving the given
+// name and method.  The suffix is stripped of any leading slashes. If it begins
+// with rpc.DebugKeyword, we use the internal debug dispatcher to look up the
+// invoker. Otherwise, and we use the server's dispatcher. The suffix and method
+// value may be modified to match the actual suffix and method to use.
+func (fs *flowServer) lookup(suffix string, method string) (rpc.Invoker, security.Authorizer, error) {
+	if naming.IsReserved(method) {
+		return reservedInvoker(fs.disp, fs.server.dispReserved), security.AllowEveryone(), nil
+	}
+	disp := fs.disp
+	if naming.IsReserved(suffix) {
+		disp = fs.server.dispReserved
+	} else if fs.server.isLeaf && suffix != "" {
+		innerErr := verror.New(errUnexpectedSuffix, fs.ctx, suffix)
+		return nil, nil, verror.New(verror.ErrUnknownSuffix, fs.ctx, suffix, innerErr)
+	}
+	if disp != nil {
+		obj, auth, err := disp.Lookup(fs.ctx, suffix)
+		switch {
+		case err != nil:
+			return nil, nil, err
+		case obj != nil:
+			invoker, err := objectToInvoker(obj)
+			if err != nil {
+				return nil, nil, verror.New(verror.ErrInternal, fs.ctx, "invalid received object", err)
+			}
+			return invoker, auth, nil
+		}
+	}
+	return nil, nil, verror.New(verror.ErrUnknownSuffix, fs.ctx, suffix)
+}
+
+func objectToInvoker(obj interface{}) (rpc.Invoker, error) {
+	if obj == nil {
+		return nil, errors.New("nil object")
+	}
+	if invoker, ok := obj.(rpc.Invoker); ok {
+		return invoker, nil
+	}
+	return rpc.ReflectInvoker(obj)
+}
+
+func (fs *flowServer) initSecurity(req *rpc.Request) error {
+	// LocalPrincipal is nil which means we are operating under
+	// SecurityNone.
+	if fs.LocalPrincipal() == nil {
+		return nil
+	}
+
+	// If additional credentials are provided, make them available in the context
+	// Detect unusable blessings now, rather then discovering they are unusable on
+	// first use.
+	//
+	// TODO(ashankar,ataly): Potential confused deputy attack: The client provides
+	// the server's identity as the blessing. Figure out what we want to do about
+	// this - should servers be able to assume that a blessing is something that
+	// does not have the authorizations that the server's own identity has?
+	if got, want := req.GrantedBlessings.PublicKey(), fs.LocalPrincipal().PublicKey(); got != nil && !reflect.DeepEqual(got, want) {
+		return verror.New(verror.ErrNoAccess, fs.ctx, fmt.Sprintf("blessing granted not bound to this server(%v vs %v)", got, want))
+	}
+	fs.grantedBlessings = req.GrantedBlessings
+
+	var err error
+	if fs.clientBlessings, err = serverDecodeBlessings(fs.flow.VCDataCache(), req.Blessings, fs.server.stats); err != nil {
+		// When the server can't access the blessings cache, the client is not following
+		// protocol, so the server closes the VCs corresponding to the client endpoint.
+		// TODO(suharshs,toddw): Figure out a way to only shutdown the current VC, instead
+		// of all VCs connected to the RemoteEndpoint.
+		fs.server.streamMgr.ShutdownEndpoint(fs.RemoteEndpoint())
+		return verror.New(verror.ErrBadProtocol, fs.ctx, newErrBadBlessingsCache(fs.ctx, err))
+	}
+	// Verify that the blessings sent by the client in the request have the same public
+	// key as those sent by the client during VC establishment.
+	if got, want := fs.clientBlessings.PublicKey(), fs.flow.RemoteBlessings().PublicKey(); got != nil && !reflect.DeepEqual(got, want) {
+		return verror.New(verror.ErrNoAccess, fs.ctx, fmt.Sprintf("blessings sent with the request are bound to a different public key (%v) from the blessing used during VC establishment (%v)", got, want))
+	}
+	fs.ackBlessings = true
+
+	for _, d := range req.Discharges {
+		fs.discharges[d.ID()] = d
+	}
+	return nil
+}
+
+func authorize(ctx *context.T, call security.Call, auth security.Authorizer) error {
+	if call.LocalPrincipal() == nil {
+		// LocalPrincipal is nil means that the server wanted to avoid
+		// authentication, and thus wanted to skip authorization as well.
+		return nil
+	}
+	if auth == nil {
+		auth = security.DefaultAuthorizer()
+	}
+	if err := auth.Authorize(ctx, call); err != nil {
+		return verror.New(verror.ErrNoAccess, ctx, newErrBadAuth(ctx, call.Suffix(), call.Method(), err))
+	}
+	return nil
+}
+
+// Send implements the rpc.Stream method.
+func (fs *flowServer) Send(item interface{}) error {
+	defer apilog.LogCallf(nil, "item=")(nil, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	// The empty response header indicates what follows is a streaming result.
+	if err := fs.enc.Encode(rpc.Response{}); err != nil {
+		return err
+	}
+	return fs.enc.Encode(item)
+}
+
+// Recv implements the rpc.Stream method.
+func (fs *flowServer) Recv(itemptr interface{}) error {
+	defer apilog.LogCallf(nil, "itemptr=")(nil, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	var req rpc.Request
+	if err := fs.dec.Decode(&req); err != nil {
+		return err
+	}
+	if req.EndStreamArgs {
+		fs.endStreamArgs = true
+		return io.EOF
+	}
+	return fs.dec.Decode(itemptr)
+}
+
+// Implementations of rpc.ServerCall and security.Call methods.
+
+func (fs *flowServer) Security() security.Call {
+	//nologcall
+	return fs
+}
+func (fs *flowServer) LocalDischarges() map[string]security.Discharge {
+	//nologcall
+	return fs.flow.LocalDischarges()
+}
+func (fs *flowServer) RemoteDischarges() map[string]security.Discharge {
+	//nologcall
+	return fs.discharges
+}
+func (fs *flowServer) Server() rpc.Server {
+	//nologcall
+	return fs.server
+}
+func (fs *flowServer) Timestamp() time.Time {
+	//nologcall
+	return fs.starttime
+}
+func (fs *flowServer) Method() string {
+	//nologcall
+	return fs.method
+}
+func (fs *flowServer) MethodTags() []*vdl.Value {
+	//nologcall
+	return fs.tags
+}
+func (fs *flowServer) Suffix() string {
+	//nologcall
+	return fs.suffix
+}
+func (fs *flowServer) LocalPrincipal() security.Principal {
+	//nologcall
+	return fs.flow.LocalPrincipal()
+}
+func (fs *flowServer) LocalBlessings() security.Blessings {
+	//nologcall
+	return fs.flow.LocalBlessings()
+}
+func (fs *flowServer) RemoteBlessings() security.Blessings {
+	//nologcall
+	if !fs.clientBlessings.IsZero() {
+		return fs.clientBlessings
+	}
+	return fs.flow.RemoteBlessings()
+}
+func (fs *flowServer) GrantedBlessings() security.Blessings {
+	//nologcall
+	return fs.grantedBlessings
+}
+func (fs *flowServer) LocalEndpoint() naming.Endpoint {
+	//nologcall
+	return fs.flow.LocalEndpoint()
+}
+func (fs *flowServer) RemoteEndpoint() naming.Endpoint {
+	//nologcall
+	return fs.flow.RemoteEndpoint()
+}
+
+type proxyAuth struct {
+	s *server
+}
+
+func (a proxyAuth) RPCStreamListenerOpt() {}
+
+func (a proxyAuth) Login(proxy stream.Flow) (security.Blessings, []security.Discharge, error) {
+	var (
+		principal = a.s.principal
+		dc        = a.s.dc
+		ctx       = a.s.ctx
+	)
+	if principal == nil {
+		return security.Blessings{}, nil, nil
+	}
+	proxyNames, _ := security.RemoteBlessingNames(ctx, security.NewCall(&security.CallParams{
+		LocalPrincipal:   principal,
+		RemoteBlessings:  proxy.RemoteBlessings(),
+		RemoteDischarges: proxy.RemoteDischarges(),
+		RemoteEndpoint:   proxy.RemoteEndpoint(),
+		LocalEndpoint:    proxy.LocalEndpoint(),
+	}))
+	blessings := principal.BlessingStore().ForPeer(proxyNames...)
+	tpc := blessings.ThirdPartyCaveats()
+	if len(tpc) == 0 {
+		return blessings, nil, nil
+	}
+	// Set DischargeImpetus.Server = proxyNames.
+	// See https://v.io/i/392
+	discharges := dc.PrepareDischarges(ctx, tpc, security.DischargeImpetus{})
+	return blessings, discharges, nil
+}
+
+var _ manager.ProxyAuthenticator = proxyAuth{}
diff --git a/runtime/internal/rpc/server_authorizer.go b/runtime/internal/rpc/server_authorizer.go
new file mode 100644
index 0000000..86874ce
--- /dev/null
+++ b/runtime/internal/rpc/server_authorizer.go
@@ -0,0 +1,139 @@
+// 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 rpc
+
+import (
+	"reflect"
+
+	"v.io/v23/context"
+	"v.io/v23/options"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/verror"
+
+	"v.io/x/ref/lib/apilog"
+)
+
+// TODO(ribrdb): Flip this to true once everything is updated and also update
+// the server authorizer tests.
+const enableSecureServerAuth = false
+
+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.
+	errNoBlessingsFromServer      = reg(".errNoBlessingsFromServer", "server has not presented any blessings")
+	errAuthNoServerBlessingsMatch = reg(".errAuthNoServerBlessingsMatch",
+		"server blessings {3} do not match client expectations {4}, (rejected blessings: {5})")
+	errAuthServerNotAllowed = reg(".errAuthServerNotAllowed",
+		"server blessings {3} do not match any allowed server patterns {4}{:5}")
+	errAuthServerKeyNotAllowed = reg(".errAuthServerKeyNotAllowed",
+		"remote public key {3} not matched by server key {4}")
+	errMultiplePublicKeys = reg(".errMultiplePublicKeyOptions", "at most one ServerPublicKey options can be provided")
+)
+
+// serverAuthorizer implements security.Authorizer.
+type serverAuthorizer struct {
+	allowedServerPolicies     [][]security.BlessingPattern
+	serverPublicKey           security.PublicKey
+	ignoreBlessingsInEndpoint bool
+}
+
+// newServerAuthorizer returns a security.Authorizer for authorizing the server
+// during a flow. The authorization policy is based on options supplied to the
+// call that initiated the flow. Additionally, if pattern is non-empty then
+// the server will be authorized only if it presents at least one blessing
+// that matches pattern.
+//
+// This method assumes that canCreateServerAuthorizer(opts) is nil.
+func newServerAuthorizer(pattern security.BlessingPattern, opts ...rpc.CallOpt) security.Authorizer {
+	auth := &serverAuthorizer{}
+	for _, o := range opts {
+		switch v := o.(type) {
+		case options.ServerPublicKey:
+			auth.serverPublicKey = v.PublicKey
+		case options.AllowedServersPolicy:
+			auth.allowedServerPolicies = append(auth.allowedServerPolicies, v)
+		case options.SkipServerEndpointAuthorization:
+			auth.ignoreBlessingsInEndpoint = true
+		}
+	}
+	if len(pattern) > 0 {
+		auth.allowedServerPolicies = append(auth.allowedServerPolicies, []security.BlessingPattern{pattern})
+	}
+	return auth
+}
+
+func (a *serverAuthorizer) Authorize(ctx *context.T, call security.Call) error {
+	defer apilog.LogCallf(ctx, "call=")(ctx, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	if call.RemoteBlessings().IsZero() {
+		return verror.New(errNoBlessingsFromServer, ctx)
+	}
+	serverBlessings, rejectedBlessings := security.RemoteBlessingNames(ctx, call)
+
+	if epb := call.RemoteEndpoint().BlessingNames(); len(epb) > 0 && !a.ignoreBlessingsInEndpoint {
+		matched := false
+		for _, b := range epb {
+			// TODO(ashankar,ataly): Should this be
+			// security.BlessingPattern(b).MakeNonExtendable().MatchedBy()?
+			// Because, without that, a delegate of the real server
+			// can be a man-in-the-middle without failing
+			// authorization. Is that a desirable property?
+			if security.BlessingPattern(b).MatchedBy(serverBlessings...) {
+				matched = true
+				break
+			}
+		}
+		if !matched {
+			return verror.New(errAuthNoServerBlessingsMatch, ctx, serverBlessings, epb, rejectedBlessings)
+		}
+	} else if enableSecureServerAuth && len(epb) == 0 {
+		// No blessings in the endpoint to set expectations on the
+		// "identity" of the server.  Use the default authorization
+		// policy.
+		if err := security.DefaultAuthorizer().Authorize(ctx, call); err != nil {
+			return err
+		}
+	}
+
+	for _, patterns := range a.allowedServerPolicies {
+		if !matchedBy(patterns, serverBlessings) {
+			return verror.New(errAuthServerNotAllowed, ctx, serverBlessings, patterns, rejectedBlessings)
+		}
+	}
+
+	if remoteKey, key := call.RemoteBlessings().PublicKey(), a.serverPublicKey; key != nil && !reflect.DeepEqual(remoteKey, key) {
+		return verror.New(errAuthServerKeyNotAllowed, ctx, remoteKey, key)
+	}
+
+	return nil
+}
+
+func matchedBy(patterns []security.BlessingPattern, blessings []string) bool {
+	if patterns == nil {
+		return true
+	}
+	for _, p := range patterns {
+		if p.MatchedBy(blessings...) {
+			return true
+		}
+	}
+	return false
+}
+
+func canCreateServerAuthorizer(ctx *context.T, opts []rpc.CallOpt) error {
+	var pkey security.PublicKey
+	for _, o := range opts {
+		switch v := o.(type) {
+		case options.ServerPublicKey:
+			if pkey != nil && !reflect.DeepEqual(pkey, v.PublicKey) {
+				return verror.New(errMultiplePublicKeys, ctx)
+			}
+			pkey = v.PublicKey
+		}
+	}
+	return nil
+}
diff --git a/runtime/internal/rpc/server_authorizer_test.go b/runtime/internal/rpc/server_authorizer_test.go
new file mode 100644
index 0000000..be56861
--- /dev/null
+++ b/runtime/internal/rpc/server_authorizer_test.go
@@ -0,0 +1,150 @@
+// 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 rpc
+
+import (
+	"testing"
+
+	"v.io/v23"
+	"v.io/v23/options"
+	"v.io/v23/security"
+	"v.io/x/ref/runtime/internal/naming"
+
+	"v.io/x/ref/test/testutil"
+)
+
+func TestServerAuthorizer(t *testing.T) {
+	var (
+		pclient = testutil.NewPrincipal()
+		pserver = testutil.NewPrincipal()
+		pother  = testutil.NewPrincipal()
+
+		ali, _      = pserver.BlessSelf("ali")
+		bob, _      = pserver.BlessSelf("bob")
+		che, _      = pserver.BlessSelf("che")
+		otherAli, _ = pother.BlessSelf("ali")
+		zero        = security.Blessings{}
+
+		ctx, shutdown = initForTest()
+
+		U = func(blessings ...security.Blessings) security.Blessings {
+			u, err := security.UnionOfBlessings(blessings...)
+			if err != nil {
+				t.Fatal(err)
+			}
+			return u
+		}
+	)
+	defer shutdown()
+	ctx, _ = v23.WithPrincipal(ctx, pclient)
+	// Make client recognize ali, bob and otherAli blessings
+	for _, b := range []security.Blessings{ali, bob, otherAli} {
+		if err := pclient.AddToRoots(b); err != nil {
+			t.Fatal(err)
+		}
+	}
+	// All tests are run as if pclient is the client end and pserver is remote end.
+	tests := []struct {
+		serverBlessingNames []string
+		auth                security.Authorizer
+		authorizedServers   []security.Blessings
+		unauthorizedServers []security.Blessings
+	}{
+		{
+			// No blessings in the endpoint means that all servers are authorized.
+			nil,
+			newServerAuthorizer(""),
+			[]security.Blessings{ali, otherAli, bob, che},
+			[]security.Blessings{zero},
+		},
+		{
+			// Endpoint sets the expectations for "ali" and "bob".
+			[]string{"ali", "bob"},
+			newServerAuthorizer(""),
+			[]security.Blessings{ali, otherAli, bob, U(ali, che), U(bob, che)},
+			[]security.Blessings{che},
+		},
+		{
+			// Still only ali, otherAli and bob are authorized (che is not
+			// authorized since it is not recognized by the client)
+			[]string{"ali", "bob", "che"},
+			newServerAuthorizer(""),
+			[]security.Blessings{ali, otherAli, bob, U(ali, che), U(bob, che)},
+			[]security.Blessings{che},
+		},
+		{
+
+			// Only ali and otherAli are authorized (since there is an
+			// allowed-servers policy that does not allow "bob")
+			[]string{"ali", "bob", "che"},
+			newServerAuthorizer("", options.AllowedServersPolicy{"ali", "bob"}, options.AllowedServersPolicy{"ali"}),
+			[]security.Blessings{ali, otherAli, U(ali, che), U(ali, bob)},
+			[]security.Blessings{bob, che},
+		},
+		{
+			// Multiple AllowedServersPolicy are treated as an AND (and individual ones are "ORs")
+			nil,
+			newServerAuthorizer("", options.AllowedServersPolicy{"ali", "che"}, options.AllowedServersPolicy{"bob", "che"}),
+			[]security.Blessings{U(ali, bob)},
+			[]security.Blessings{ali, bob, che, U(ali, che), U(bob, che)},
+		},
+		{
+			// Only otherAli is authorized (since only pother's public key is
+			// authorized)
+			[]string{"ali"},
+			newServerAuthorizer("", options.ServerPublicKey{PublicKey: pother.PublicKey()}),
+			[]security.Blessings{otherAli},
+			[]security.Blessings{ali, bob, che},
+		},
+		{
+			// Blessings in endpoint can be ignored.
+			[]string{"ali"},
+			newServerAuthorizer("", options.SkipServerEndpointAuthorization{}),
+			[]security.Blessings{ali, bob, che, otherAli},
+			nil,
+		},
+		{
+			// Pattern specified is respected
+			nil,
+			newServerAuthorizer("bob"),
+			[]security.Blessings{bob, U(ali, bob)},
+			[]security.Blessings{ali, otherAli, che},
+		},
+		{
+			// And concatenated with any existing AllowedServersPolicy
+			[]string{"ali", "bob", "che"},
+			newServerAuthorizer("bob", options.AllowedServersPolicy{"bob", "che"}),
+			[]security.Blessings{bob, U(ali, bob), U(ali, bob, che)},
+			[]security.Blessings{ali, che},
+		},
+		{
+			// And if the intersection of AllowedServersPolicy and the pattern be empty, then so be it!
+			[]string{"ali", "bob", "che"},
+			newServerAuthorizer("bob", options.AllowedServersPolicy{"ali", "che"}),
+			[]security.Blessings{U(ali, bob), U(ali, bob, che)},
+			[]security.Blessings{ali, otherAli, bob, che, U(ali, che)},
+		},
+	}
+	for _, test := range tests {
+		for _, s := range test.authorizedServers {
+			if err := test.auth.Authorize(ctx, &mockCall{
+				p:   pclient,
+				r:   s,
+				rep: &naming.Endpoint{Blessings: test.serverBlessingNames},
+			}); err != nil {
+				t.Errorf("serverAuthorizer: %#v failed to authorize server: %v", test.auth, s)
+			}
+		}
+		for _, s := range test.unauthorizedServers {
+			if err := test.auth.Authorize(ctx, &mockCall{
+				p:   pclient,
+				r:   s,
+				rep: &naming.Endpoint{Blessings: test.serverBlessingNames},
+			}); err == nil {
+				t.Errorf("serverAuthorizer: %#v authorized server: %v", test.auth, s)
+			}
+		}
+	}
+}
diff --git a/runtime/internal/rpc/server_test.go b/runtime/internal/rpc/server_test.go
new file mode 100644
index 0000000..7263415
--- /dev/null
+++ b/runtime/internal/rpc/server_test.go
@@ -0,0 +1,687 @@
+// 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 rpc
+
+import (
+	"net"
+	"reflect"
+	"sort"
+	"testing"
+	"time"
+
+	"v.io/x/lib/netstate"
+	"v.io/x/lib/set"
+
+	"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/v23/verror"
+
+	"v.io/x/ref/lib/pubsub"
+	inaming "v.io/x/ref/runtime/internal/naming"
+	imanager "v.io/x/ref/runtime/internal/rpc/stream/manager"
+	tnaming "v.io/x/ref/runtime/internal/testing/mocks/naming"
+	"v.io/x/ref/test/testutil"
+)
+
+type noMethodsType struct{ Field string }
+
+type fieldType struct {
+	unexported string
+}
+type noExportedFieldsType struct{}
+
+func (noExportedFieldsType) F(_ *context.T, _ rpc.ServerCall, f fieldType) error { return nil }
+
+type badObjectDispatcher struct{}
+
+func (badObjectDispatcher) Lookup(_ *context.T, suffix string) (interface{}, security.Authorizer, error) {
+	return noMethodsType{}, nil, nil
+}
+
+// TestBadObject ensures that Serve handles bad receiver objects gracefully (in
+// particular, it doesn't panic).
+func TestBadObject(t *testing.T) {
+	ctx, shutdown := initForTest()
+	defer shutdown()
+	sm := imanager.InternalNew(ctx, naming.FixedRoutingID(0x555555555))
+	defer sm.Shutdown()
+	ns := tnaming.NewSimpleNamespace()
+	pclient, pserver := newClientServerPrincipals()
+	cctx, _ := v23.WithPrincipal(ctx, pclient)
+	sctx, _ := v23.WithPrincipal(ctx, pserver)
+	server, err := testInternalNewServer(sctx, sm, ns)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer server.Stop()
+
+	if _, err := server.Listen(listenSpec); err != nil {
+		t.Fatalf("Listen failed: %v", err)
+	}
+	if err := server.Serve("", nil, nil); err == nil {
+		t.Fatal("should have failed")
+	}
+	if err := server.Serve("", new(noMethodsType), nil); err == nil {
+		t.Fatal("should have failed")
+	}
+	if err := server.Serve("", new(noExportedFieldsType), nil); err == nil {
+		t.Fatal("should have failed")
+	}
+	if err := server.ServeDispatcher("servername", badObjectDispatcher{}); err != nil {
+		t.Fatalf("ServeDispatcher failed: %v", err)
+	}
+	client, err := InternalNewClient(sm, ns)
+	if err != nil {
+		t.Fatalf("InternalNewClient failed: %v", err)
+	}
+	ctx, _ = context.WithDeadline(cctx, time.Now().Add(10*time.Second))
+	var result string
+	if err := client.Call(cctx, "servername", "SomeMethod", nil, []interface{}{&result}); err == nil {
+		// TODO(caprita): Check the error type rather than
+		// merely ensuring the test doesn't panic.
+		t.Fatalf("Call should have failed")
+	}
+}
+
+func TestServerArgs(t *testing.T) {
+	ctx, shutdown := initForTest()
+	defer shutdown()
+	sm := imanager.InternalNew(ctx, naming.FixedRoutingID(0x555555555))
+	defer sm.Shutdown()
+	ns := tnaming.NewSimpleNamespace()
+	sctx, _ := v23.WithPrincipal(ctx, testutil.NewPrincipal("test"))
+	server, err := testInternalNewServer(sctx, sm, ns)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer server.Stop()
+	_, err = server.Listen(rpc.ListenSpec{})
+	if verror.ErrorID(err) != verror.ErrBadArg.ID {
+		t.Fatalf("expected a BadArg error: got %v", err)
+	}
+	_, err = server.Listen(rpc.ListenSpec{Addrs: rpc.ListenAddrs{{"tcp", "*:0"}}})
+	if verror.ErrorID(err) != verror.ErrBadArg.ID {
+		t.Fatalf("expected a BadArg error: got %v", err)
+	}
+	_, err = server.Listen(rpc.ListenSpec{
+		Addrs: rpc.ListenAddrs{
+			{"tcp", "*:0"},
+			{"tcp", "127.0.0.1:0"},
+		}})
+	if verror.ErrorID(err) == verror.ErrBadArg.ID {
+		t.Fatalf("expected a BadArg error: got %v", err)
+	}
+	status := server.Status()
+	if got, want := len(status.Errors), 1; got != want {
+		t.Fatalf("got %v, want %v", got, want)
+	}
+	_, err = server.Listen(rpc.ListenSpec{Addrs: rpc.ListenAddrs{{"tcp", "*:0"}}})
+	if verror.ErrorID(err) != verror.ErrBadArg.ID {
+		t.Fatalf("expected a BadArg error: got %v", err)
+	}
+	status = server.Status()
+	if got, want := len(status.Errors), 1; got != want {
+		t.Fatalf("got %v, want %v", got, want)
+	}
+}
+
+type statusServer struct{ ch chan struct{} }
+
+func (s *statusServer) Hang(*context.T, rpc.ServerCall) error {
+	s.ch <- struct{}{} // Notify the server has received a call.
+	<-s.ch             // Wait for the server to be ready to go.
+	return nil
+}
+
+func TestServerStatus(t *testing.T) {
+	ctx, shutdown := initForTest()
+	defer shutdown()
+	sm := imanager.InternalNew(ctx, naming.FixedRoutingID(0x555555555))
+	defer sm.Shutdown()
+	ns := tnaming.NewSimpleNamespace()
+	principal := testutil.NewPrincipal("testServerStatus")
+	ctx, _ = v23.WithPrincipal(ctx, principal)
+	server, err := testInternalNewServer(ctx, sm, ns)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer server.Stop()
+
+	status := server.Status()
+	if got, want := status.State, rpc.ServerInit; got != want {
+		t.Fatalf("got %s, want %s", got, want)
+	}
+	server.Listen(rpc.ListenSpec{Addrs: rpc.ListenAddrs{{"tcp", "127.0.0.1:0"}}})
+	status = server.Status()
+	if got, want := status.State, rpc.ServerActive; got != want {
+		t.Fatalf("got %s, want %s", got, want)
+	}
+	serverChan := make(chan struct{})
+	err = server.Serve("test", &statusServer{serverChan}, nil)
+	if err != nil {
+		t.Fatalf(err.Error())
+	}
+	status = server.Status()
+	if got, want := status.State, rpc.ServerActive; got != want {
+		t.Fatalf("got %s, want %s", got, want)
+	}
+
+	progress := make(chan error)
+
+	client, err := InternalNewClient(sm, ns)
+	makeCall := func(ctx *context.T) {
+		call, err := client.StartCall(ctx, "test", "Hang", nil)
+		progress <- err
+		progress <- call.Finish()
+	}
+	go makeCall(ctx)
+
+	// Wait for RPC to start and the server has received the call.
+	if err := <-progress; err != nil {
+		t.Fatalf(err.Error())
+	}
+	<-serverChan
+
+	// Stop server asynchronously
+	go func() {
+		err = server.Stop()
+		if err != nil {
+			t.Fatalf(err.Error())
+		}
+	}()
+
+	// Server should enter 'ServerStopping' state.
+	then := time.Now()
+	for {
+		status = server.Status()
+		if got, want := status.State, rpc.ServerStopping; got != want {
+			if time.Now().Sub(then) > time.Minute {
+				t.Fatalf("got %s, want %s", got, want)
+			}
+		} else {
+			break
+		}
+		time.Sleep(100 * time.Millisecond)
+	}
+	// Server won't stop until the statusServer's hung method completes.
+	close(serverChan)
+	// Wait for RPC to finish
+	if err := <-progress; err != nil {
+		t.Fatalf(err.Error())
+	}
+
+	// Now that the RPC is done, the server should be able to stop.
+	then = time.Now()
+	for {
+		status = server.Status()
+		if got, want := status.State, rpc.ServerStopped; got != want {
+			if time.Now().Sub(then) > time.Minute {
+				t.Fatalf("got %s, want %s", got, want)
+			}
+		} else {
+			break
+		}
+		time.Sleep(100 * time.Millisecond)
+	}
+}
+
+func TestServerStates(t *testing.T) {
+	ctx, shutdown := initForTest()
+	defer shutdown()
+	sm := imanager.InternalNew(ctx, naming.FixedRoutingID(0x555555555))
+	defer sm.Shutdown()
+	ns := tnaming.NewSimpleNamespace()
+	sctx, _ := v23.WithPrincipal(ctx, testutil.NewPrincipal("test"))
+	expectBadState := func(err error) {
+		if verror.ErrorID(err) != verror.ErrBadState.ID {
+			t.Fatalf("%s: unexpected error: %v", loc(1), err)
+		}
+	}
+
+	expectNoError := func(err error) {
+		if err != nil {
+			t.Fatalf("%s: unexpected error: %v", loc(1), err)
+		}
+	}
+
+	server, err := testInternalNewServer(sctx, sm, ns)
+	expectNoError(err)
+	defer server.Stop()
+
+	expectState := func(s rpc.ServerState) {
+		if got, want := server.Status().State, s; got != want {
+			t.Fatalf("%s: got %s, want %s", loc(1), got, want)
+		}
+	}
+
+	expectState(rpc.ServerInit)
+
+	// Need to call Listen first.
+	err = server.Serve("", &testServer{}, nil)
+	expectBadState(err)
+	err = server.AddName("a")
+	expectBadState(err)
+
+	_, err = server.Listen(rpc.ListenSpec{Addrs: rpc.ListenAddrs{{"tcp", "127.0.0.1:0"}}})
+	expectNoError(err)
+
+	expectState(rpc.ServerActive)
+
+	err = server.Serve("", &testServer{}, nil)
+	expectNoError(err)
+
+	err = server.Serve("", &testServer{}, nil)
+	expectBadState(err)
+
+	expectState(rpc.ServerActive)
+
+	err = server.AddName("a")
+	expectNoError(err)
+
+	expectState(rpc.ServerActive)
+
+	server.RemoveName("a")
+
+	expectState(rpc.ServerActive)
+
+	err = server.Stop()
+	expectNoError(err)
+	err = server.Stop()
+	expectNoError(err)
+
+	err = server.AddName("a")
+	expectBadState(err)
+}
+
+func TestMountStatus(t *testing.T) {
+	ctx, shutdown := initForTest()
+	defer shutdown()
+	sm := imanager.InternalNew(ctx, naming.FixedRoutingID(0x555555555))
+	defer sm.Shutdown()
+	ns := tnaming.NewSimpleNamespace()
+	sctx, _ := v23.WithPrincipal(ctx, testutil.NewPrincipal("test"))
+
+	server, err := testInternalNewServer(sctx, sm, ns)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer server.Stop()
+
+	eps, err := server.Listen(rpc.ListenSpec{
+		Addrs: rpc.ListenAddrs{
+			{"tcp", "127.0.0.1:0"},
+			{"tcp", "127.0.0.1:0"},
+		}})
+	if err != nil {
+		t.Fatal(err)
+	}
+	if got, want := len(eps), 2; got != want {
+		t.Fatalf("got %d, want %d", got, want)
+	}
+	if err = server.Serve("foo", &testServer{}, nil); err != nil {
+		t.Fatal(err)
+	}
+	setLeafEndpoints(eps)
+	status := server.Status()
+	if got, want := len(status.Mounts), 2; got != want {
+		t.Fatalf("got %d, want %d", got, want)
+	}
+	servers := status.Mounts.Servers()
+	if got, want := len(servers), 2; got != want {
+		t.Fatalf("got %d, want %d", got, want)
+	}
+	if got, want := servers, endpointToStrings(eps); !reflect.DeepEqual(got, want) {
+		t.Fatalf("got %v, want %v", got, want)
+	}
+
+	// Add a second name and we should now see 4 mounts, 2 for each name.
+	if err := server.AddName("bar"); err != nil {
+		t.Fatal(err)
+	}
+	status = server.Status()
+	if got, want := len(status.Mounts), 4; got != want {
+		t.Fatalf("got %d, want %d", got, want)
+	}
+	servers = status.Mounts.Servers()
+	if got, want := len(servers), 2; got != want {
+		t.Fatalf("got %d, want %d", got, want)
+	}
+	if got, want := servers, endpointToStrings(eps); !reflect.DeepEqual(got, want) {
+		t.Fatalf("got %v, want %v", got, want)
+	}
+	names := status.Mounts.Names()
+	if got, want := len(names), 2; got != want {
+		t.Fatalf("got %d, want %d", got, want)
+	}
+	serversPerName := map[string][]string{}
+	for _, ms := range status.Mounts {
+		serversPerName[ms.Name] = append(serversPerName[ms.Name], ms.Server)
+	}
+	if got, want := len(serversPerName), 2; got != want {
+		t.Fatalf("got %d, want %d", got, want)
+	}
+	for _, name := range []string{"foo", "bar"} {
+		if got, want := len(serversPerName[name]), 2; got != want {
+			t.Fatalf("got %d, want %d", got, want)
+		}
+	}
+}
+
+func updateHost(ep naming.Endpoint, address string) naming.Endpoint {
+	niep := *(ep).(*inaming.Endpoint)
+	niep.Address = address
+	return &niep
+}
+
+func getIPAddrs(eps []naming.Endpoint) []net.Addr {
+	hosts := map[string]struct{}{}
+	for _, ep := range eps {
+		iep := (ep).(*inaming.Endpoint)
+		h, _, _ := net.SplitHostPort(iep.Address)
+		if len(h) > 0 {
+			hosts[h] = struct{}{}
+		}
+	}
+	addrs := []net.Addr{}
+	for h, _ := range hosts {
+		addrs = append(addrs, netstate.NewNetAddr("ip", h))
+	}
+	return addrs
+}
+
+func endpointToStrings(eps []naming.Endpoint) []string {
+	r := []string{}
+	for _, ep := range eps {
+		r = append(r, ep.String())
+	}
+	sort.Strings(r)
+	return r
+}
+
+func cmpEndpoints(got, want []naming.Endpoint) bool {
+	if len(got) != len(want) {
+		return false
+	}
+	return reflect.DeepEqual(endpointToStrings(got), endpointToStrings(want))
+}
+
+func getUniqPorts(eps []naming.Endpoint) []string {
+	ports := map[string]struct{}{}
+	for _, ep := range eps {
+		iep := ep.(*inaming.Endpoint)
+		_, p, _ := net.SplitHostPort(iep.Address)
+		ports[p] = struct{}{}
+	}
+	return set.String.ToSlice(ports)
+}
+
+func TestRoaming(t *testing.T) {
+	ctx, shutdown := initForTest()
+	defer shutdown()
+	sm := imanager.InternalNew(ctx, naming.FixedRoutingID(0x555555555))
+	defer sm.Shutdown()
+	ns := tnaming.NewSimpleNamespace()
+
+	publisher := pubsub.NewPublisher()
+	roaming := make(chan pubsub.Setting)
+	stop, err := publisher.CreateStream("TestRoaming", "TestRoaming", roaming)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer func() { publisher.Shutdown(); <-stop }()
+
+	nctx, _ := v23.WithPrincipal(ctx, testutil.NewPrincipal("test"))
+	server, err := testInternalNewServerWithPubsub(nctx, sm, ns, publisher, "TestRoaming")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer server.Stop()
+
+	ipv4And6 := netstate.AddressChooserFunc(func(network string, addrs []net.Addr) ([]net.Addr, error) {
+		accessible := netstate.ConvertToAddresses(addrs)
+		ipv4 := accessible.Filter(netstate.IsUnicastIPv4)
+		ipv6 := accessible.Filter(netstate.IsUnicastIPv6)
+		return append(ipv4.AsNetAddrs(), ipv6.AsNetAddrs()...), nil
+	})
+	spec := rpc.ListenSpec{
+		Addrs: rpc.ListenAddrs{
+			{"tcp", "*:0"},
+			{"tcp", ":0"},
+			{"tcp", ":0"},
+		},
+		AddressChooser: ipv4And6,
+	}
+
+	eps, err := server.Listen(spec)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if len(eps) == 0 {
+		t.Fatal(err)
+	}
+
+	if err = server.Serve("foo", &testServer{}, nil); err != nil {
+		t.Fatal(err)
+	}
+	setLeafEndpoints(eps)
+	if err = server.AddName("bar"); err != nil {
+		t.Fatal(err)
+	}
+
+	status := server.Status()
+	if got, want := status.Endpoints, eps; !cmpEndpoints(got, want) {
+		t.Fatalf("got %v, want %v", got, want)
+	}
+
+	if got, want := len(status.Mounts), len(eps)*2; got != want {
+		t.Fatalf("got %d, want %d", got, want)
+	}
+
+	n1 := netstate.NewNetAddr("ip", "1.1.1.1")
+	n2 := netstate.NewNetAddr("ip", "2.2.2.2")
+
+	watcher := make(chan rpc.NetworkChange, 10)
+	server.WatchNetwork(watcher)
+	defer close(watcher)
+
+	roaming <- NewAddAddrsSetting([]net.Addr{n1, n2})
+
+	waitForChange := func() *rpc.NetworkChange {
+		ctx.Infof("Waiting on %p", watcher)
+		select {
+		case c := <-watcher:
+			return &c
+		case <-time.After(time.Minute):
+			t.Fatalf("timedout: %s", loc(1))
+		}
+		return nil
+	}
+
+	// We expect 4 changes, one for each IP per usable listen spec addr.
+	change := waitForChange()
+	if got, want := len(change.Changed), 4; got != want {
+		t.Fatalf("got %d, want %d", got, want)
+	}
+
+	nepsA := make([]naming.Endpoint, len(eps))
+	copy(nepsA, eps)
+	for _, p := range getUniqPorts(eps) {
+		nep1 := updateHost(eps[0], net.JoinHostPort("1.1.1.1", p))
+		nep2 := updateHost(eps[0], net.JoinHostPort("2.2.2.2", p))
+		nepsA = append(nepsA, []naming.Endpoint{nep1, nep2}...)
+	}
+
+	status = server.Status()
+	if got, want := status.Endpoints, nepsA; !cmpEndpoints(got, want) {
+		t.Fatalf("got %v, want %v [%d, %d]", got, want, len(got), len(want))
+	}
+
+	if got, want := len(status.Mounts), len(nepsA)*2; got != want {
+		t.Fatalf("got %d, want %d", got, want)
+	}
+	if got, want := len(status.Mounts.Servers()), len(nepsA); got != want {
+		t.Fatalf("got %d, want %d", got, want)
+	}
+
+	roaming <- NewRmAddrsSetting([]net.Addr{n1})
+
+	// We expect 2 changes, one for each usable listen spec addr.
+	change = waitForChange()
+	if got, want := len(change.Changed), 2; got != want {
+		t.Fatalf("got %d, want %d", got, want)
+	}
+
+	nepsR := make([]naming.Endpoint, len(eps))
+	copy(nepsR, eps)
+	for _, p := range getUniqPorts(eps) {
+		nep2 := updateHost(eps[0], net.JoinHostPort("2.2.2.2", p))
+		nepsR = append(nepsR, nep2)
+	}
+
+	status = server.Status()
+	if got, want := status.Endpoints, nepsR; !cmpEndpoints(got, want) {
+		t.Fatalf("got %v, want %v [%d, %d]", got, want, len(got), len(want))
+	}
+
+	// Remove all addresses to mimic losing all connectivity.
+	roaming <- NewRmAddrsSetting(getIPAddrs(nepsR))
+
+	// We expect changes for all of the current endpoints
+	change = waitForChange()
+	if got, want := len(change.Changed), len(nepsR); got != want {
+		t.Fatalf("got %d, want %d", got, want)
+	}
+
+	status = server.Status()
+	if got, want := len(status.Mounts), 0; got != want {
+		t.Fatalf("got %d, want %d: %v", got, want, status.Mounts)
+	}
+
+	roaming <- NewAddAddrsSetting([]net.Addr{n1})
+	// We expect 2 changes, one for each usable listen spec addr.
+	change = waitForChange()
+	if got, want := len(change.Changed), 2; got != want {
+		t.Fatalf("got %d, want %d", got, want)
+	}
+
+}
+
+func TestWatcherDeadlock(t *testing.T) {
+	ctx, shutdown := initForTest()
+	defer shutdown()
+	sm := imanager.InternalNew(ctx, naming.FixedRoutingID(0x555555555))
+	defer sm.Shutdown()
+	ns := tnaming.NewSimpleNamespace()
+
+	publisher := pubsub.NewPublisher()
+	roaming := make(chan pubsub.Setting)
+	stop, err := publisher.CreateStream("TestWatcherDeadlock", "TestWatcherDeadlock", roaming)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer func() { publisher.Shutdown(); <-stop }()
+
+	nctx, _ := v23.WithPrincipal(ctx, testutil.NewPrincipal("test"))
+	server, err := testInternalNewServerWithPubsub(nctx, sm, ns, publisher, "TestWatcherDeadlock")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer server.Stop()
+
+	spec := rpc.ListenSpec{
+		Addrs: rpc.ListenAddrs{
+			{"tcp", ":0"},
+		},
+	}
+	eps, err := server.Listen(spec)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if err = server.Serve("foo", &testServer{}, nil); err != nil {
+		t.Fatal(err)
+	}
+	setLeafEndpoints(eps)
+
+	// Set a watcher that we never read from - the intent is to make sure
+	// that the listener still listens to changes even though there is no
+	// goroutine to read from the watcher channel.
+	watcher := make(chan rpc.NetworkChange, 0)
+	server.WatchNetwork(watcher)
+	defer close(watcher)
+
+	// Remove all addresses to mimic losing all connectivity.
+	roaming <- NewRmAddrsSetting(getIPAddrs(eps))
+
+	// Add in two new addresses
+	n1 := netstate.NewNetAddr("ip", "1.1.1.1")
+	n2 := netstate.NewNetAddr("ip", "2.2.2.2")
+	roaming <- NewAddAddrsSetting([]net.Addr{n1, n2})
+
+	neps := make([]naming.Endpoint, 0, len(eps))
+	for _, p := range getUniqPorts(eps) {
+		nep1 := updateHost(eps[0], net.JoinHostPort("1.1.1.1", p))
+		nep2 := updateHost(eps[0], net.JoinHostPort("2.2.2.2", p))
+		neps = append(neps, []naming.Endpoint{nep1, nep2}...)
+	}
+	then := time.Now()
+	for {
+		status := server.Status()
+		if got, want := status.Endpoints, neps; cmpEndpoints(got, want) {
+			break
+		}
+		time.Sleep(100 * time.Millisecond)
+		if time.Now().Sub(then) > time.Minute {
+			t.Fatalf("timed out waiting for changes to take effect")
+		}
+	}
+}
+
+func TestIsLeafServerOption(t *testing.T) {
+	ctx, shutdown := initForTest()
+	defer shutdown()
+	sm := imanager.InternalNew(ctx, naming.FixedRoutingID(0x555555555))
+	defer sm.Shutdown()
+	ns := tnaming.NewSimpleNamespace()
+	pclient, pserver := newClientServerPrincipals()
+	cctx, _ := v23.WithPrincipal(ctx, pclient)
+	sctx, _ := v23.WithPrincipal(ctx, pserver)
+	server, err := testInternalNewServer(sctx, sm, ns, options.IsLeaf(true))
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer server.Stop()
+
+	disp := &testServerDisp{&testServer{}}
+
+	if _, err := server.Listen(listenSpec); err != nil {
+		t.Fatalf("Listen failed: %v", err)
+	}
+
+	if err := server.ServeDispatcher("leafserver", disp); err != nil {
+		t.Fatalf("ServeDispatcher failed: %v", err)
+	}
+	client, err := InternalNewClient(sm, ns)
+	if err != nil {
+		t.Fatalf("InternalNewClient failed: %v", err)
+	}
+	cctx, _ = context.WithDeadline(cctx, time.Now().Add(10*time.Second))
+	var result string
+	// we have set IsLeaf to true, sending any suffix to leafserver should result
+	// in an suffix was not expected error.
+	callErr := client.Call(cctx, "leafserver/unwantedSuffix", "Echo", []interface{}{"Mirror on the wall"}, []interface{}{&result})
+	if callErr == nil {
+		t.Fatalf("Call should have failed with suffix was not expected error")
+	}
+}
+
+func setLeafEndpoints(eps []naming.Endpoint) {
+	for i := range eps {
+		eps[i].(*inaming.Endpoint).IsLeaf = true
+	}
+}
diff --git a/runtime/internal/rpc/sort_endpoints.go b/runtime/internal/rpc/sort_endpoints.go
new file mode 100644
index 0000000..3b5b896
--- /dev/null
+++ b/runtime/internal/rpc/sort_endpoints.go
@@ -0,0 +1,207 @@
+// 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 rpc
+
+import (
+	"fmt"
+	"net"
+	"sort"
+
+	"v.io/v23/naming"
+	"v.io/v23/verror"
+
+	"v.io/x/lib/netstate"
+	inaming "v.io/x/ref/runtime/internal/naming"
+)
+
+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.
+	errMalformedEndpoint            = reg(".errMalformedEndpoint", "malformed endpoint{:3}")
+	errUndesiredProtocol            = reg(".errUndesiredProtocol", "undesired protocol{:3}")
+	errIncompatibleEndpointVersions = reg(".errIncompatibleEndpointVersions", "incompatible endpoint versions{:3}")
+	errNoCompatibleServers          = reg(".errNoComaptibleServers", "failed to find any compatible servers{:3}")
+)
+
+type serverLocality int
+
+const (
+	unknownNetwork serverLocality = iota
+	remoteNetwork
+	localNetwork
+)
+
+type sortableServer struct {
+	server       naming.MountedServer
+	protocolRank int            // larger values are preferred.
+	locality     serverLocality // larger values are preferred.
+}
+
+func (s *sortableServer) String() string {
+	return fmt.Sprintf("%v", s.server)
+}
+
+type sortableServerList []sortableServer
+
+func (l sortableServerList) Len() int      { return len(l) }
+func (l sortableServerList) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
+func (l sortableServerList) Less(i, j int) bool {
+	if l[i].protocolRank == l[j].protocolRank {
+		return l[i].locality > l[j].locality
+	}
+	return l[i].protocolRank > l[j].protocolRank
+}
+
+func mkProtocolRankMap(list []string) map[string]int {
+	if len(list) == 0 {
+		return nil
+	}
+	m := make(map[string]int)
+	for idx, protocol := range list {
+		m[protocol] = len(list) - idx
+	}
+	return m
+}
+
+var defaultPreferredProtocolOrder = mkProtocolRankMap([]string{"unixfd", "wsh", "tcp4", "tcp", "*"})
+
+// filterAndOrderServers returns a set of servers that are compatible with
+// the current client in order of 'preference' specified by the supplied
+// protocols and a notion of 'locality' according to the supplied protocol
+// list as follows:
+// - if the protocol parameter is non-empty, then only servers matching those
+// protocols are returned and the endpoints are ordered first by protocol
+// and then by locality within each protocol. If tcp4 and unixfd are requested
+// for example then only protocols that match tcp4 and unixfd will returned
+// with the tcp4 ones preceeding the unixfd ones.
+// - if the protocol parameter is empty, then a default protocol ordering
+// will be used, but unlike the previous case, any servers that don't support
+// these protocols will be returned also, but following the default
+// preferences.
+func filterAndOrderServers(servers []naming.MountedServer, protocols []string, ipnets []*net.IPNet) ([]naming.MountedServer, error) {
+	var (
+		errs       = verror.SubErrs{}
+		list       = make(sortableServerList, 0, len(servers))
+		protoRanks = mkProtocolRankMap(protocols)
+	)
+	if len(protoRanks) == 0 {
+		protoRanks = defaultPreferredProtocolOrder
+	}
+	adderr := func(name string, err error) {
+		errs = append(errs, verror.SubErr{Name: "server=" + name, Err: err, Options: verror.Print})
+	}
+	for _, server := range servers {
+		name := server.Server
+		ep, err := name2endpoint(name)
+		if err != nil {
+			adderr(name, verror.New(errMalformedEndpoint, nil, err))
+			continue
+		}
+		rank, err := protocol2rank(ep.Addr().Network(), protoRanks)
+		if err != nil {
+			adderr(name, err)
+			continue
+		}
+		list = append(list, sortableServer{
+			server:       server,
+			protocolRank: rank,
+			locality:     locality(ep, ipnets),
+		})
+	}
+	if len(list) == 0 {
+		return nil, verror.AddSubErrs(verror.New(errNoCompatibleServers, nil), nil, errs...)
+	}
+	// TODO(ashankar): Don't have to use stable sorting, could
+	// just use sort.Sort. The only problem with that is the
+	// unittest.
+	sort.Stable(list)
+	// Convert to []naming.MountedServer
+	ret := make([]naming.MountedServer, len(list))
+	for idx, item := range list {
+		ret[idx] = item.server
+	}
+	return ret, nil
+}
+
+// name2endpoint returns the naming.Endpoint encoded in a name.
+func name2endpoint(name string) (naming.Endpoint, error) {
+	addr := name
+	if naming.Rooted(name) {
+		addr, _ = naming.SplitAddressName(name)
+	}
+	return inaming.NewEndpoint(addr)
+}
+
+// protocol2rank returns the "rank" of a protocol (given a map of ranks).
+// The higher the rank, the more preferable the protocol.
+func protocol2rank(protocol string, ranks map[string]int) (int, error) {
+	if r, ok := ranks[protocol]; ok {
+		return r, nil
+	}
+	// Special case: if "wsh" has a rank but "wsh4"/"wsh6" don't,
+	// then they get the same rank as "wsh". Similar for "tcp" and "ws".
+	//
+	// TODO(jhahn): We have similar protocol equivalency checks at a few places.
+	// Figure out a way for this mapping to be shared.
+	if p := protocol; p == "wsh4" || p == "wsh6" || p == "tcp4" || p == "tcp6" || p == "ws4" || p == "ws6" {
+		if r, ok := ranks[p[:len(p)-1]]; ok {
+			return r, nil
+		}
+	}
+	// "*" means that any protocol is acceptable.
+	if r, ok := ranks["*"]; ok {
+		return r, nil
+	}
+	// UnknownProtocol should be rare, it typically happens when
+	// the endpoint is described in <host>:<port> format instead of
+	// the full fidelity description (@<version>@<protocol>@...).
+	if protocol == naming.UnknownProtocol {
+		return -1, nil
+	}
+	return 0, verror.New(errUndesiredProtocol, nil, protocol)
+}
+
+// locality returns the serverLocality to use given an endpoint and the
+// set of IP networks configured on this machine.
+func locality(ep naming.Endpoint, ipnets []*net.IPNet) serverLocality {
+	if len(ipnets) < 1 {
+		return unknownNetwork // 0 IP networks, locality doesn't matter.
+
+	}
+	host, _, err := net.SplitHostPort(ep.Addr().String())
+	if err != nil {
+		host = ep.Addr().String()
+	}
+	ip := net.ParseIP(host)
+	if ip == nil {
+		// Not an IP address (possibly not an IP network).
+		return unknownNetwork
+	}
+	for _, ipnet := range ipnets {
+		if ipnet.Contains(ip) {
+			return localNetwork
+		}
+	}
+	return remoteNetwork
+}
+
+// ipNetworks returns the IP networks on this machine.
+func ipNetworks() ([]*net.IPNet, error) {
+	ifcs, err := netstate.GetAllAddresses()
+	if err != nil {
+		return nil, err
+	}
+	ret := make([]*net.IPNet, 0, len(ifcs))
+	for _, a := range ifcs {
+		_, ipnet, err := net.ParseCIDR(a.String())
+		if err != nil {
+			return nil, err
+		}
+		ret = append(ret, ipnet)
+	}
+	return ret, nil
+}
diff --git a/runtime/internal/rpc/sort_internal_test.go b/runtime/internal/rpc/sort_internal_test.go
new file mode 100644
index 0000000..2d0d545
--- /dev/null
+++ b/runtime/internal/rpc/sort_internal_test.go
@@ -0,0 +1,211 @@
+// 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 rpc
+
+import (
+	"net"
+	"reflect"
+	"strings"
+	"testing"
+
+	"v.io/v23/naming"
+)
+
+func servers2names(servers []naming.MountedServer) []string {
+	e := naming.MountEntry{Servers: servers}
+	return e.Names()
+}
+
+func TestIncompatible(t *testing.T) {
+	servers := []naming.MountedServer{}
+
+	_, err := filterAndOrderServers(servers, []string{"tcp"}, nil)
+	if err == nil || err.Error() != "failed to find any compatible servers" {
+		t.Errorf("expected a different error: %v", err)
+	}
+
+	for _, a := range []string{"127.0.0.3", "127.0.0.4"} {
+		name := naming.JoinAddressName(naming.FormatEndpoint("tcp", a), "")
+		servers = append(servers, naming.MountedServer{Server: name})
+	}
+
+	_, err = filterAndOrderServers(servers, []string{"foobar"}, nil)
+	if err == nil || !strings.HasSuffix(err.Error(), "undesired protocol: tcp]") {
+		t.Errorf("expected a different error to: %v", err)
+	}
+}
+
+func TestOrderingByProtocol(t *testing.T) {
+	servers := []naming.MountedServer{}
+	_, ipnet, _ := net.ParseCIDR("127.0.0.0/8")
+	ipnets := []*net.IPNet{ipnet}
+
+	for _, a := range []string{"127.0.0.3", "127.0.0.4"} {
+		name := naming.JoinAddressName(naming.FormatEndpoint("tcp", a), "")
+		servers = append(servers, naming.MountedServer{Server: name})
+	}
+	for _, a := range []string{"127.0.0.1", "127.0.0.2"} {
+		name := naming.JoinAddressName(naming.FormatEndpoint("tcp4", a), "")
+		servers = append(servers, naming.MountedServer{Server: name})
+	}
+	for _, a := range []string{"127.0.0.10", "127.0.0.11"} {
+		name := naming.JoinAddressName(naming.FormatEndpoint("foobar", a), "")
+		servers = append(servers, naming.MountedServer{Server: name})
+	}
+	for _, a := range []string{"127.0.0.7", "127.0.0.8"} {
+		name := naming.JoinAddressName(naming.FormatEndpoint("tcp6", a), "")
+		servers = append(servers, naming.MountedServer{Server: name})
+	}
+	if _, err := filterAndOrderServers(servers, []string{"batman"}, ipnets); err == nil {
+		t.Fatalf("expected an error")
+	}
+
+	// Add a server with naming.UnknownProtocol. This typically happens
+	// when the endpoint is in <host>:<port> format. Currently, the sorting
+	// is setup to always allow UnknownProtocol, but put it in the end.
+	// May want to revisit this choice, but for now the test captures what
+	// the current state of the code intends.
+	servers = append(servers, naming.MountedServer{Server: "127.0.0.12:14141"})
+
+	// Just foobar and tcp4
+	want := []string{
+		"/@5@foobar@127.0.0.10@@@@@",
+		"/@5@foobar@127.0.0.11@@@@@",
+		"/@5@tcp4@127.0.0.1@@@@@",
+		"/@5@tcp4@127.0.0.2@@@@@",
+		"/127.0.0.12:14141",
+	}
+	result, err := filterAndOrderServers(servers, []string{"foobar", "tcp4"}, ipnets)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	if got := servers2names(result); !reflect.DeepEqual(got, want) {
+		t.Errorf("got: %v, want %v", got, want)
+	}
+
+	// Everything, since we didn't specify a protocol, but ordered by
+	// the internal metric - see defaultPreferredProtocolOrder.
+	// The order will be the default preferred order for protocols, the
+	// original ordering within each protocol, with protocols that
+	// are not in the default ordering list at the end.
+	want = []string{
+		"/@5@tcp4@127.0.0.1@@@@@",
+		"/@5@tcp4@127.0.0.2@@@@@",
+		"/@5@tcp@127.0.0.3@@@@@",
+		"/@5@tcp@127.0.0.4@@@@@",
+		"/@5@tcp6@127.0.0.7@@@@@",
+		"/@5@tcp6@127.0.0.8@@@@@",
+		"/@5@foobar@127.0.0.10@@@@@",
+		"/@5@foobar@127.0.0.11@@@@@",
+		"/127.0.0.12:14141",
+	}
+	if result, err = filterAndOrderServers(servers, nil, ipnets); err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	if got := servers2names(result); !reflect.DeepEqual(got, want) {
+		t.Errorf("got: %v, want %v", got, want)
+	}
+
+	if result, err = filterAndOrderServers(servers, []string{}, ipnets); err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	if got := servers2names(result); !reflect.DeepEqual(got, want) {
+		t.Errorf("got: %v, want %v", got, want)
+	}
+
+	// Just "tcp" implies tcp4 and tcp6 as well.
+	want = []string{
+		"/@5@tcp@127.0.0.3@@@@@",
+		"/@5@tcp@127.0.0.4@@@@@",
+		"/@5@tcp4@127.0.0.1@@@@@",
+		"/@5@tcp4@127.0.0.2@@@@@",
+		"/@5@tcp6@127.0.0.7@@@@@",
+		"/@5@tcp6@127.0.0.8@@@@@",
+		"/127.0.0.12:14141",
+	}
+	if result, err = filterAndOrderServers(servers, []string{"tcp"}, ipnets); err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	if got := servers2names(result); !reflect.DeepEqual(got, want) {
+		t.Errorf("got: %v, want %v", got, want)
+	}
+
+	// Ask for all protocols, with no ordering, except for locality
+	want = []string{
+		"/@5@tcp@127.0.0.3@@@@@",
+		"/@5@tcp@127.0.0.1@@@@@",
+		"/@5@tcp@74.125.69.139@@@@@",
+		"/@5@tcp@192.168.1.10@@@@@",
+		"/@5@tcp@74.125.142.83@@@@@",
+		"/127.0.0.12:14141",
+		"/@5@foobar@127.0.0.10@@@@@",
+		"/@5@foobar@127.0.0.11@@@@@",
+	}
+	servers = []naming.MountedServer{}
+	// naming.UnknownProtocol
+	servers = append(servers, naming.MountedServer{Server: "127.0.0.12:14141"})
+	for _, a := range []string{"74.125.69.139", "127.0.0.3", "127.0.0.1", "192.168.1.10", "74.125.142.83"} {
+		name := naming.JoinAddressName(naming.FormatEndpoint("tcp", a), "")
+		servers = append(servers, naming.MountedServer{Server: name})
+	}
+	for _, a := range []string{"127.0.0.10", "127.0.0.11"} {
+		name := naming.JoinAddressName(naming.FormatEndpoint("foobar", a), "")
+		servers = append(servers, naming.MountedServer{Server: name})
+	}
+	if result, err = filterAndOrderServers(servers, []string{}, ipnets); err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	if got := servers2names(result); !reflect.DeepEqual(got, want) {
+		t.Errorf("got: %v, want %v", got, want)
+	}
+}
+
+func TestOrderingByLocality(t *testing.T) {
+	servers := []naming.MountedServer{}
+	_, ipnet, _ := net.ParseCIDR("127.0.0.0/8")
+	ipnets := []*net.IPNet{ipnet}
+
+	for _, a := range []string{"74.125.69.139", "127.0.0.3", "127.0.0.1", "192.168.1.10", "74.125.142.83"} {
+		name := naming.JoinAddressName(naming.FormatEndpoint("tcp", a), "")
+		servers = append(servers, naming.MountedServer{Server: name})
+	}
+	result, err := filterAndOrderServers(servers, []string{"tcp"}, ipnets)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	want := []string{
+		"/@5@tcp@127.0.0.3@@@@@",
+		"/@5@tcp@127.0.0.1@@@@@",
+		"/@5@tcp@74.125.69.139@@@@@",
+		"/@5@tcp@192.168.1.10@@@@@",
+		"/@5@tcp@74.125.142.83@@@@@",
+	}
+	if got := servers2names(result); !reflect.DeepEqual(got, want) {
+		t.Errorf("got: %v, want %v", got, want)
+	}
+	for _, a := range []string{"74.125.69.139", "127.0.0.3:123", "127.0.0.1", "192.168.1.10", "74.125.142.83"} {
+		name := naming.JoinAddressName(naming.FormatEndpoint("ws", a), "")
+		servers = append(servers, naming.MountedServer{Server: name})
+	}
+
+	if result, err = filterAndOrderServers(servers, []string{"ws", "tcp"}, ipnets); err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	want = []string{
+		"/@5@ws@127.0.0.3:123@@@@@",
+		"/@5@ws@127.0.0.1@@@@@",
+		"/@5@ws@74.125.69.139@@@@@",
+		"/@5@ws@192.168.1.10@@@@@",
+		"/@5@ws@74.125.142.83@@@@@",
+		"/@5@tcp@127.0.0.3@@@@@",
+		"/@5@tcp@127.0.0.1@@@@@",
+		"/@5@tcp@74.125.69.139@@@@@",
+		"/@5@tcp@192.168.1.10@@@@@",
+		"/@5@tcp@74.125.142.83@@@@@",
+	}
+	if got := servers2names(result); !reflect.DeepEqual(got, want) {
+		t.Errorf("got: %v, want %v", got, want)
+	}
+}
diff --git a/runtime/internal/rpc/stats.go b/runtime/internal/rpc/stats.go
new file mode 100644
index 0000000..640618a
--- /dev/null
+++ b/runtime/internal/rpc/stats.go
@@ -0,0 +1,99 @@
+// 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 rpc
+
+import (
+	"sync"
+	"time"
+
+	"v.io/x/ref/lib/stats"
+	"v.io/x/ref/lib/stats/counter"
+	"v.io/x/ref/lib/stats/histogram"
+
+	"v.io/v23/naming"
+)
+
+type rpcStats struct {
+	mu                  sync.RWMutex
+	prefix              string
+	methods             map[string]*perMethodStats
+	blessingsCacheStats *blessingsCacheStats
+}
+
+func newRPCStats(prefix string) *rpcStats {
+	return &rpcStats{
+		prefix:              prefix,
+		methods:             make(map[string]*perMethodStats),
+		blessingsCacheStats: newBlessingsCacheStats(prefix),
+	}
+}
+
+type perMethodStats struct {
+	latency *histogram.Histogram
+}
+
+func (s *rpcStats) stop() {
+	stats.Delete(s.prefix)
+}
+
+func (s *rpcStats) record(method string, latency time.Duration) {
+	// Try first with a read lock. This will succeed in the most common
+	// case. If it fails, try again with a write lock and create the stats
+	// objects if they are still not there.
+	s.mu.RLock()
+	m, ok := s.methods[method]
+	s.mu.RUnlock()
+	if !ok {
+		m = s.newPerMethodStats(method)
+	}
+	m.latency.Add(int64(latency / time.Millisecond))
+}
+
+func (s *rpcStats) recordBlessingCache(hit bool) {
+	s.blessingsCacheStats.incr(hit)
+}
+
+// newPerMethodStats creates a new perMethodStats object if one doesn't exist
+// already. It returns the newly created object, or the already existing one.
+func (s *rpcStats) newPerMethodStats(method string) *perMethodStats {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+	m, ok := s.methods[method]
+	if !ok {
+		name := naming.Join(s.prefix, "methods", method, "latency-ms")
+		s.methods[method] = &perMethodStats{
+			latency: stats.NewHistogram(name, histogram.Options{
+				NumBuckets:         25,
+				GrowthFactor:       1,
+				SmallestBucketSize: 1,
+				MinValue:           0,
+			}),
+		}
+		m = s.methods[method]
+	}
+	return m
+}
+
+// blessingsCacheStats keeps blessing cache hits and total calls received to determine
+// how often the blessingCache is being used.
+type blessingsCacheStats struct {
+	callsReceived, cacheHits *counter.Counter
+}
+
+func newBlessingsCacheStats(prefix string) *blessingsCacheStats {
+	cachePrefix := naming.Join(prefix, "security", "blessings", "cache")
+	return &blessingsCacheStats{
+		callsReceived: stats.NewCounter(naming.Join(cachePrefix, "attempts")),
+		cacheHits:     stats.NewCounter(naming.Join(cachePrefix, "hits")),
+	}
+}
+
+// Incr increments the cache attempt counter and the cache hit counter if hit is true.
+func (s *blessingsCacheStats) incr(hit bool) {
+	s.callsReceived.Incr(1)
+	if hit {
+		s.cacheHits.Incr(1)
+	}
+}
diff --git a/runtime/internal/rpc/stream/benchmark/RESULTS.txt b/runtime/internal/rpc/stream/benchmark/RESULTS.txt
new file mode 100644
index 0000000..905f1db
--- /dev/null
+++ b/runtime/internal/rpc/stream/benchmark/RESULTS.txt
@@ -0,0 +1,82 @@
+Date: 01/30/2015
+Platform: Intel(R) Xeon(R) CPU E5-2689 0 @ 2.60GHz,  66114888KB Memory
+
+$ v23 go test -bench=. -cpu=1 -benchtime=5s \
+  v.io/x/ref/runtime/internal/rpc/stream/benchmark
+
+Benchmark_dial_VIF	  500000	     14292 ns/op
+--- Histogram (unit: s)
+	Count: 500000  Min: 4  Max: 16455  Avg: 13.58
+	------------------------------------------------------------
+	[    4,     5)  139232   27.8%   27.8%  ###
+	[    5,     6)  257818   51.6%   79.4%  #####
+	[    6,     9)   92644   18.5%   97.9%  ##
+	[    9,    15)    5963    1.2%   99.1%
+	[   15,    28)    3162    0.6%   99.8%
+	[   28,    53)     171    0.0%   99.8%
+	[   53,   101)      67    0.0%   99.8%
+	[  101,   193)       1    0.0%   99.8%
+	[  193,   370)       0    0.0%   99.8%
+	[  370,   708)       0    0.0%   99.8%
+	[  708,  1354)      57    0.0%   99.8%
+	[ 1354,  2589)     152    0.0%   99.9%
+	[ 2589,  4949)     393    0.1%   99.9%
+	[ 4949,  9457)     322    0.1%  100.0%
+	[ 9457, 18069)      18    0.0%  100.0%
+	[18069, 34520)       0    0.0%  100.0%
+	[34520,   inf)       0    0.0%  100.0%
+Benchmark_dial_VIF_TLS	     500	  12594281 ns/op
+--- Histogram (unit: ms)
+	Count: 500  Min: 12  Max: 14  Avg: 12.31
+	------------------------------------------------------------
+	[ 12,  13)  352   70.4%   70.4%  #######
+	[ 13,  14)  141   28.2%   98.6%  ###
+	[ 14, inf)    7    1.4%  100.0%
+Benchmark_dial_VC_TLS	     500	  16116072 ns/op
+--- Histogram (unit: ms)
+	Count: 500  Min: 15  Max: 22  Avg: 15.53
+	------------------------------------------------------------
+	[ 15,  16)  313   62.6%   62.6%  ######
+	[ 16,  17)  121   24.2%   86.8%  ##
+	[ 17,  18)   60   12.0%   98.8%  #
+	[ 18,  19)    3    0.6%   99.4%
+	[ 19,  20)    2    0.4%   99.8%
+	[ 20,  21)    0    0.0%   99.8%
+	[ 21,  23)    1    0.2%  100.0%
+	[ 23, inf)    0    0.0%  100.0%
+Benchmark_throughput_TCP_1Conn	 1000000	      9197 ns/op	5566.89 MB/s
+Benchmark_throughput_TCP_2Conns	 1000000	      9083 ns/op	5636.56 MB/s
+Benchmark_throughput_TCP_4Conns	 1000000	      9855 ns/op	5194.81 MB/s
+Benchmark_throughput_TCP_8Conns	  500000	     12541 ns/op	4082.43 MB/s
+Benchmark_throughput_WS_1Conn	   30000	    206804 ns/op	 247.58 MB/s
+Benchmark_throughput_WS_2Conns	   30000	    211842 ns/op	 241.69 MB/s
+Benchmark_throughput_WS_4Conns	   30000	    209994 ns/op	 243.82 MB/s
+Benchmark_throughput_WS_8Conns	   30000	    217110 ns/op	 235.83 MB/s
+Benchmark_throughput_WSH_TCP_1Conn	 1000000	      9322 ns/op	5491.85 MB/s
+Benchmark_throughput_WSH_TCP_2Conns	 1000000	      9370 ns/op	5463.77 MB/s
+Benchmark_throughput_WSH_TCP_4Conns	 1000000	      9466 ns/op	5408.50 MB/s
+Benchmark_throughput_WSH_TCP_8Conns	  500000	     12526 ns/op	4087.22 MB/s
+Benchmark_throughput_WSH_WS_1Conn	   30000	    207833 ns/op	 246.35 MB/s
+Benchmark_throughput_WSH_WS_2Conns	   30000	    208567 ns/op	 245.48 MB/s
+Benchmark_throughput_WSH_WS_4Conns	   30000	    211562 ns/op	 242.01 MB/s
+Benchmark_throughput_WSH_WS_8Conns	   30000	    216454 ns/op	 236.54 MB/s
+Benchmark_throughput_Pipe_1Conn	  500000	     20169 ns/op	2538.54 MB/s
+Benchmark_throughput_Pipe_2Conns	  500000	     19935 ns/op	2568.29 MB/s
+Benchmark_throughput_Pipe_4Conns	  300000	     19893 ns/op	2573.76 MB/s
+Benchmark_throughput_Pipe_8Conns	 1000000	     20235 ns/op	2530.22 MB/s
+Benchmark_throughput_Flow_1VIF_1VC_1Flow	  300000	     28014 ns/op	1827.66 MB/s
+Benchmark_throughput_Flow_1VIF_1VC_2Flow	  300000	     27495 ns/op	1862.09 MB/s
+Benchmark_throughput_Flow_1VIF_1VC_8Flow	  200000	     35584 ns/op	1438.84 MB/s
+Benchmark_throughput_Flow_1VIF_2VC_2Flow	  300000	     27665 ns/op	1850.66 MB/s
+Benchmark_throughput_Flow_1VIF_2VC_8Flow	  200000	     34974 ns/op	1463.94 MB/s
+Benchmark_throughput_Flow_2VIF_4VC_8Flow	  200000	     37642 ns/op	1360.15 MB/s
+Benchmark_throughput_TLS_1Conn	   20000	    415149 ns/op	 123.33 MB/s
+Benchmark_throughput_TLS_2Conns	   20000	    416008 ns/op	 123.07 MB/s
+Benchmark_throughput_TLS_4Conns	   20000	    421083 ns/op	 121.59 MB/s
+Benchmark_throughput_TLS_8Conns	   20000	    423079 ns/op	 121.02 MB/s
+Benchmark_throughput_Flow_1VIF_1VC_1FlowTLS	   20000	    466212 ns/op	 109.82 MB/s
+Benchmark_throughput_Flow_1VIF_1VC_2FlowTLS	   20000	    466104 ns/op	 109.85 MB/s
+Benchmark_throughput_Flow_1VIF_1VC_8FlowTLS	   20000	    476604 ns/op	 107.43 MB/s
+Benchmark_throughput_Flow_1VIF_2VC_2FlowTLS	   20000	    466818 ns/op	 109.68 MB/s
+Benchmark_throughput_Flow_1VIF_2VC_8FlowTLS	   20000	    477094 ns/op	 107.32 MB/s
+Benchmark_throughput_Flow_2VIF_4VC_8FlowTLS	   20000	    476370 ns/op	 107.48 MB/s
diff --git a/runtime/internal/rpc/stream/benchmark/benchmark_test.go b/runtime/internal/rpc/stream/benchmark/benchmark_test.go
new file mode 100644
index 0000000..2102047
--- /dev/null
+++ b/runtime/internal/rpc/stream/benchmark/benchmark_test.go
@@ -0,0 +1,21 @@
+// 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 benchmark
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test/benchmark"
+)
+
+// A single empty test to avoid:
+// testing: warning: no tests to run
+// from showing up when running benchmarks in this package via "go test"
+func TestNoOp(t *testing.T) {}
+
+func TestMain(m *testing.M) {
+	os.Exit(benchmark.RunTestMain(m))
+}
diff --git a/runtime/internal/rpc/stream/benchmark/dial_test.go b/runtime/internal/rpc/stream/benchmark/dial_test.go
new file mode 100644
index 0000000..789df5b
--- /dev/null
+++ b/runtime/internal/rpc/stream/benchmark/dial_test.go
@@ -0,0 +1,14 @@
+// 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 benchmark
+
+import "testing"
+
+func Benchmark_dial_VIF_NoSecurity(b *testing.B) { benchmarkDialVIF(b, securityNone) }
+func Benchmark_dial_VIF(b *testing.B)            { benchmarkDialVIF(b, securityDefault) }
+
+// Note: We don't benchmark SecurityNone VC Dial for now since it doesn't wait ack
+// from the server after sending "OpenVC".
+func Benchmark_dial_VC(b *testing.B) { benchmarkDialVC(b, securityDefault) }
diff --git a/runtime/internal/rpc/stream/benchmark/dial_vc.go b/runtime/internal/rpc/stream/benchmark/dial_vc.go
new file mode 100644
index 0000000..c376783
--- /dev/null
+++ b/runtime/internal/rpc/stream/benchmark/dial_vc.go
@@ -0,0 +1,76 @@
+// 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 benchmark
+
+import (
+	"testing"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/naming"
+	"v.io/v23/options"
+	"v.io/v23/security"
+
+	_ "v.io/x/ref/runtime/factories/static"
+	"v.io/x/ref/runtime/internal/rpc/stream/manager"
+	"v.io/x/ref/runtime/internal/rpc/stream/vc"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/benchmark"
+	"v.io/x/ref/test/testutil"
+)
+
+// benchmarkDialVC measures VC creation time over the underlying VIF.
+func benchmarkDialVC(b *testing.B, mode options.SecurityLevel) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	stats := benchmark.AddStats(b, 16)
+
+	server := manager.InternalNew(ctx, naming.FixedRoutingID(0x5))
+	client := manager.InternalNew(ctx, naming.FixedRoutingID(0xc))
+	var (
+		principal security.Principal
+		blessings security.Blessings
+	)
+	if mode == securityDefault {
+		principal = testutil.NewPrincipal("test")
+		blessings = principal.BlessingStore().Default()
+		ctx, _ = v23.WithPrincipal(ctx, principal)
+	}
+
+	_, ep, err := server.Listen(ctx, "tcp", "127.0.0.1:0", blessings)
+
+	if err != nil {
+		b.Fatal(err)
+	}
+
+	// Create one VC to prevent the underlying VIF from being closed.
+	_, err = client.Dial(ctx, ep, vc.IdleTimeout{Duration: 0})
+	if err != nil {
+		b.Fatal(err)
+	}
+
+	b.ResetTimer() // Exclude setup time from measurement.
+
+	for i := 0; i < b.N; i++ {
+		b.StartTimer()
+		start := time.Now()
+
+		VC, err := client.Dial(ctx, ep)
+		if err != nil {
+			b.Fatal(err)
+		}
+
+		duration := time.Since(start)
+		b.StopTimer()
+
+		stats.Add(duration)
+
+		VC.Close(nil)
+	}
+
+	client.Shutdown()
+	server.Shutdown()
+}
diff --git a/runtime/internal/rpc/stream/benchmark/dial_vif.go b/runtime/internal/rpc/stream/benchmark/dial_vif.go
new file mode 100644
index 0000000..20e6528
--- /dev/null
+++ b/runtime/internal/rpc/stream/benchmark/dial_vif.go
@@ -0,0 +1,68 @@
+// 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 benchmark
+
+import (
+	"net"
+	"testing"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/naming"
+	"v.io/v23/options"
+	"v.io/v23/security"
+
+	"v.io/x/ref/runtime/internal/rpc/stream/vif"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/benchmark"
+	"v.io/x/ref/test/testutil"
+)
+
+// benchmarkDialVIF measures VIF creation time over the underlying net connection.
+func benchmarkDialVIF(b *testing.B, mode options.SecurityLevel) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	stats := benchmark.AddStats(b, 16)
+	var (
+		principal security.Principal
+		blessings security.Blessings
+	)
+	if mode == securityDefault {
+		principal = testutil.NewPrincipal("test")
+		blessings = principal.BlessingStore().Default()
+		ctx, _ = v23.WithPrincipal(ctx, principal)
+	}
+
+	b.ResetTimer() // Exclude setup time from measurement.
+
+	for i := 0; i < b.N; i++ {
+		b.StopTimer()
+		nc, ns := net.Pipe()
+
+		serverch := make(chan *vif.VIF)
+		go func() {
+			server, _ := vif.InternalNewAcceptedVIF(ctx, ns, naming.FixedRoutingID(0x5), blessings, nil, nil)
+			serverch <- server
+		}()
+
+		b.StartTimer()
+		start := time.Now()
+
+		client, err := vif.InternalNewDialedVIF(ctx, nc, naming.FixedRoutingID(0xc), nil, nil)
+		if err != nil {
+			b.Fatal(err)
+		}
+
+		duration := time.Since(start)
+		b.StopTimer()
+
+		stats.Add(duration)
+
+		client.Close()
+		if server := <-serverch; server != nil {
+			server.Close()
+		}
+	}
+}
diff --git a/runtime/internal/rpc/stream/benchmark/doc.go b/runtime/internal/rpc/stream/benchmark/doc.go
new file mode 100644
index 0000000..ba50140
--- /dev/null
+++ b/runtime/internal/rpc/stream/benchmark/doc.go
@@ -0,0 +1,11 @@
+// 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 benchmark implements some benchmarks for comparing the
+// v.io/v23/x/ref/profiles/internal/rpc/stream implementation with raw TCP
+// connections and/or pipes.
+//
+// Sample usage:
+//	go test v.io/v23/x/ref/profiles/internal/rpc/stream/benchmark -bench=.
+package benchmark
diff --git a/runtime/internal/rpc/stream/benchmark/throughput.go b/runtime/internal/rpc/stream/benchmark/throughput.go
new file mode 100644
index 0000000..f8a2819
--- /dev/null
+++ b/runtime/internal/rpc/stream/benchmark/throughput.go
@@ -0,0 +1,73 @@
+// 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 benchmark
+
+import (
+	"crypto/rand"
+	"io"
+	"sync"
+	"testing"
+)
+
+const (
+	// Number of bytes to read/write
+	throughputBlockSize = 50 << 10 // 50 KB
+)
+
+type throughputTester struct {
+	b       *testing.B
+	writers []io.WriteCloser
+	readers []io.ReadCloser
+
+	data    []byte
+	pending sync.WaitGroup
+}
+
+func (t *throughputTester) Run() {
+	t.pending.Add(len(t.writers) + len(t.readers))
+	iters := t.b.N / len(t.writers)
+	t.data = make([]byte, throughputBlockSize)
+	if n, err := rand.Read(t.data); n != len(t.data) || err != nil {
+		t.b.Fatalf("Failed to fill write buffer with data: (%d, %v)", n, err)
+	}
+	t.b.ResetTimer()
+	for _, w := range t.writers {
+		go t.writeLoop(w, iters)
+	}
+	for _, r := range t.readers {
+		go t.readLoop(r)
+	}
+	t.pending.Wait()
+}
+
+func (t *throughputTester) writeLoop(w io.WriteCloser, N int) {
+	defer t.pending.Done()
+	defer w.Close()
+	size := len(t.data)
+	t.b.SetBytes(int64(size))
+	for i := 0; i < N; i++ {
+		if n, err := w.Write(t.data); err != nil || n != size {
+			t.b.Fatalf("Write error: %v", err)
+			return
+		}
+	}
+}
+
+func (t *throughputTester) readLoop(r io.ReadCloser) {
+	defer t.pending.Done()
+	defer r.Close()
+	var buf [throughputBlockSize]byte
+	total := 0
+	for {
+		n, err := r.Read(buf[:])
+		if err != nil {
+			if err != io.EOF {
+				t.b.Errorf("Read returned (%d, %v)", n, err)
+			}
+			break
+		}
+		total += n
+	}
+}
diff --git a/runtime/internal/rpc/stream/benchmark/throughput_flow.go b/runtime/internal/rpc/stream/benchmark/throughput_flow.go
new file mode 100644
index 0000000..20a715f
--- /dev/null
+++ b/runtime/internal/rpc/stream/benchmark/throughput_flow.go
@@ -0,0 +1,135 @@
+// 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 benchmark
+
+import (
+	"io"
+	"testing"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/options"
+	"v.io/v23/security"
+
+	"v.io/x/ref/runtime/internal/rpc/stream"
+	"v.io/x/ref/runtime/internal/rpc/stream/manager"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/testutil"
+)
+
+const (
+	// Shorthands
+	securityNone    = options.SecurityNone
+	securityDefault = options.SecurityConfidential
+)
+
+type listener struct {
+	ln stream.Listener
+	ep naming.Endpoint
+}
+
+// createListeners returns N (stream.Listener, naming.Endpoint) pairs, such
+// that calling stream.Manager.Dial to each of the endpoints will end up
+// creating a new VIF.
+func createListeners(ctx *context.T, mode options.SecurityLevel, m stream.Manager, N int) (servers []listener, err error) {
+	for i := 0; i < N; i++ {
+		var (
+			l         listener
+			principal security.Principal
+			blessings security.Blessings
+		)
+		if mode == securityDefault {
+			principal = testutil.NewPrincipal("test")
+			blessings = principal.BlessingStore().Default()
+		}
+		if l.ln, l.ep, err = m.Listen(ctx, "tcp", "127.0.0.1:0", blessings); err != nil {
+			return
+		}
+		servers = append(servers, l)
+	}
+	return
+}
+
+func benchmarkFlow(b *testing.B, mode options.SecurityLevel, nVIFs, nVCsPerVIF, nFlowsPerVC int) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	client := manager.InternalNew(ctx, naming.FixedRoutingID(0xcccccccc))
+	server := manager.InternalNew(ctx, naming.FixedRoutingID(0x55555555))
+
+	var principal security.Principal
+	if mode == securityDefault {
+		principal = testutil.NewPrincipal("test")
+		ctx, _ = v23.WithPrincipal(ctx, principal)
+	}
+
+	lns, err := createListeners(ctx, mode, server, nVIFs)
+	if err != nil {
+		b.Fatal(err)
+	}
+
+	nFlows := nVIFs * nVCsPerVIF * nFlowsPerVC
+	rchan := make(chan io.ReadCloser, nFlows)
+	wchan := make(chan io.WriteCloser, nFlows)
+
+	b.ResetTimer()
+
+	go func() {
+		defer close(wchan)
+		for i := 0; i < nVIFs; i++ {
+			ep := lns[i].ep
+			for j := 0; j < nVCsPerVIF; j++ {
+				vc, err := client.Dial(ctx, ep)
+				if err != nil {
+					b.Error(err)
+					return
+				}
+				for k := 0; k < nFlowsPerVC; k++ {
+					flow, err := vc.Connect()
+					if err != nil {
+						b.Error(err)
+						return
+					}
+					// Flows are "Accepted" by the remote
+					// end only on the first Write.
+					if _, err := flow.Write([]byte("hello")); err != nil {
+						b.Error(err)
+						return
+					}
+					wchan <- flow
+				}
+			}
+		}
+	}()
+
+	go func() {
+		defer close(rchan)
+		for i := 0; i < nVIFs; i++ {
+			ln := lns[i].ln
+			nFlowsPerVIF := nVCsPerVIF * nFlowsPerVC
+			for j := 0; j < nFlowsPerVIF; j++ {
+				flow, err := ln.Accept()
+				if err != nil {
+					b.Error(err)
+					return
+				}
+				rchan <- flow
+			}
+		}
+	}()
+
+	var readers []io.ReadCloser
+	for r := range rchan {
+		readers = append(readers, r)
+	}
+	var writers []io.WriteCloser
+	for w := range wchan {
+		writers = append(writers, w)
+	}
+	if b.Failed() {
+		return
+	}
+	(&throughputTester{b: b, readers: readers, writers: writers}).Run()
+}
diff --git a/runtime/internal/rpc/stream/benchmark/throughput_pipe.go b/runtime/internal/rpc/stream/benchmark/throughput_pipe.go
new file mode 100644
index 0000000..0a3d348
--- /dev/null
+++ b/runtime/internal/rpc/stream/benchmark/throughput_pipe.go
@@ -0,0 +1,32 @@
+// 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 benchmark
+
+import (
+	"io"
+	"os"
+	"testing"
+)
+
+// benchmarkPipe runs a benchmark to test the throughput when nPipes each are
+// reading and writing.
+func benchmarkPipe(b *testing.B, nPipes int) {
+	readers := make([]io.ReadCloser, nPipes)
+	writers := make([]io.WriteCloser, nPipes)
+	var err error
+	for i := 0; i < nPipes; i++ {
+		// Use os.Pipe and NOT net.Pipe.
+		// The latter (based on io.Pipe) doesn't really do any I/O
+		// on the Write, it just manipulates pointers (the slice)
+		// and thus isn't useful when benchmarking since that
+		// implementation is excessively cache friendly.
+		readers[i], writers[i], err = os.Pipe()
+		if err != nil {
+			b.Fatalf("Failed to create pipe #%d: %v", i, err)
+			return
+		}
+	}
+	(&throughputTester{b: b, readers: readers, writers: writers}).Run()
+}
diff --git a/runtime/internal/rpc/stream/benchmark/throughput_tcp.go b/runtime/internal/rpc/stream/benchmark/throughput_tcp.go
new file mode 100644
index 0000000..a4b54e1
--- /dev/null
+++ b/runtime/internal/rpc/stream/benchmark/throughput_tcp.go
@@ -0,0 +1,62 @@
+// 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 benchmark
+
+import (
+	"io"
+	"net"
+	"testing"
+)
+
+// benchmarkTCP sets up nConns TCP connections and measures throughput.
+func benchmarkTCP(b *testing.B, nConns int) {
+	rchan := make(chan net.Conn, nConns)
+	wchan := make(chan net.Conn, nConns)
+	ln, err := net.Listen("tcp", "127.0.0.1:0")
+	if err != nil {
+		b.Fatalf("net.Listen failed: %v", err)
+		return
+	}
+	defer ln.Close()
+	// One goroutine to dial nConns connections.
+	go func() {
+		for i := 0; i < nConns; i++ {
+			conn, err := net.Dial("tcp", ln.Addr().String())
+			if err != nil {
+				b.Fatalf("net.Dial(%q, %q) failed: %v", "tcp", ln.Addr(), err)
+				wchan <- nil
+				return
+			}
+			wchan <- conn
+		}
+		close(wchan)
+	}()
+	// One goroutine to accept nConns connections.
+	go func() {
+		for i := 0; i < nConns; i++ {
+			conn, err := ln.Accept()
+			if err != nil {
+				b.Fatalf("Accept failed: %v", err)
+				rchan <- nil
+				return
+			}
+			rchan <- conn
+		}
+		close(rchan)
+	}()
+
+	var readers []io.ReadCloser
+	var writers []io.WriteCloser
+	for r := range rchan {
+		readers = append(readers, r)
+	}
+	for w := range wchan {
+		writers = append(writers, w)
+	}
+	if b.Failed() {
+		return
+	}
+	(&throughputTester{b: b, readers: readers, writers: writers}).Run()
+}
diff --git a/runtime/internal/rpc/stream/benchmark/throughput_test.go b/runtime/internal/rpc/stream/benchmark/throughput_test.go
new file mode 100644
index 0000000..f84b6b1
--- /dev/null
+++ b/runtime/internal/rpc/stream/benchmark/throughput_test.go
@@ -0,0 +1,74 @@
+// 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 benchmark
+
+import "testing"
+
+func Benchmark_throughput_TCP_1Conn(b *testing.B)  { benchmarkTCP(b, 1) }
+func Benchmark_throughput_TCP_2Conns(b *testing.B) { benchmarkTCP(b, 2) }
+func Benchmark_throughput_TCP_4Conns(b *testing.B) { benchmarkTCP(b, 4) }
+func Benchmark_throughput_TCP_8Conns(b *testing.B) { benchmarkTCP(b, 8) }
+
+func Benchmark_throughput_WS_1Conn(b *testing.B)  { benchmarkWS(b, 1) }
+func Benchmark_throughput_WS_2Conns(b *testing.B) { benchmarkWS(b, 2) }
+func Benchmark_throughput_WS_4Conns(b *testing.B) { benchmarkWS(b, 4) }
+func Benchmark_throughput_WS_8Conns(b *testing.B) { benchmarkWS(b, 8) }
+
+func Benchmark_throughput_WSH_TCP_1Conn(b *testing.B)  { benchmarkWSH(b, "tcp", 1) }
+func Benchmark_throughput_WSH_TCP_2Conns(b *testing.B) { benchmarkWSH(b, "tcp", 2) }
+func Benchmark_throughput_WSH_TCP_4Conns(b *testing.B) { benchmarkWSH(b, "tcp", 4) }
+func Benchmark_throughput_WSH_TCP_8Conns(b *testing.B) { benchmarkWSH(b, "tcp", 8) }
+
+func Benchmark_throughput_WSH_WS_1Conn(b *testing.B)  { benchmarkWSH(b, "ws", 1) }
+func Benchmark_throughput_WSH_WS_2Conns(b *testing.B) { benchmarkWSH(b, "ws", 2) }
+func Benchmark_throughput_WSH_WS_4Conns(b *testing.B) { benchmarkWSH(b, "ws", 4) }
+func Benchmark_throughput_WSH_WS_8Conns(b *testing.B) { benchmarkWSH(b, "ws", 8) }
+
+func Benchmark_throughput_Pipe_1Conn(b *testing.B)  { benchmarkPipe(b, 1) }
+func Benchmark_throughput_Pipe_2Conns(b *testing.B) { benchmarkPipe(b, 2) }
+func Benchmark_throughput_Pipe_4Conns(b *testing.B) { benchmarkPipe(b, 4) }
+func Benchmark_throughput_Pipe_8Conns(b *testing.B) { benchmarkPipe(b, 8) }
+
+func Benchmark_throughput_Flow_1VIF_1VC_1Flow_NoSecurity(b *testing.B) {
+	benchmarkFlow(b, securityNone, 1, 1, 1)
+}
+func Benchmark_throughput_Flow_1VIF_1VC_2Flow_NoSecurity(b *testing.B) {
+	benchmarkFlow(b, securityNone, 1, 1, 2)
+}
+func Benchmark_throughput_Flow_1VIF_1VC_8Flow_NoSecurity(b *testing.B) {
+	benchmarkFlow(b, securityNone, 1, 1, 8)
+}
+
+func Benchmark_throughput_Flow_1VIF_2VC_2Flow_NoSecurity(b *testing.B) {
+	benchmarkFlow(b, securityNone, 1, 2, 1)
+}
+func Benchmark_throughput_Flow_1VIF_2VC_8Flow_NoSecurity(b *testing.B) {
+	benchmarkFlow(b, securityNone, 1, 2, 4)
+}
+
+func Benchmark_throughput_Flow_2VIF_4VC_8Flow_NoSecurity(b *testing.B) {
+	benchmarkFlow(b, securityNone, 2, 2, 2)
+}
+
+func Benchmark_throughput_Flow_1VIF_1VC_1Flow(b *testing.B) {
+	benchmarkFlow(b, securityDefault, 1, 1, 1)
+}
+func Benchmark_throughput_Flow_1VIF_1VC_2Flow(b *testing.B) {
+	benchmarkFlow(b, securityDefault, 1, 1, 2)
+}
+func Benchmark_throughput_Flow_1VIF_1VC_8Flow(b *testing.B) {
+	benchmarkFlow(b, securityDefault, 1, 1, 8)
+}
+
+func Benchmark_throughput_Flow_1VIF_2VC_2Flow(b *testing.B) {
+	benchmarkFlow(b, securityDefault, 1, 2, 1)
+}
+func Benchmark_throughput_Flow_1VIF_2VC_8Flow(b *testing.B) {
+	benchmarkFlow(b, securityDefault, 1, 2, 4)
+}
+
+func Benchmark_throughput_Flow_2VIF_4VC_8Flow(b *testing.B) {
+	benchmarkFlow(b, securityDefault, 2, 2, 2)
+}
diff --git a/runtime/internal/rpc/stream/benchmark/throughput_ws.go b/runtime/internal/rpc/stream/benchmark/throughput_ws.go
new file mode 100644
index 0000000..b72ed1b
--- /dev/null
+++ b/runtime/internal/rpc/stream/benchmark/throughput_ws.go
@@ -0,0 +1,67 @@
+// 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 benchmark
+
+import (
+	"io"
+	"net"
+	"testing"
+
+	"v.io/x/ref/runtime/internal/lib/websocket"
+
+	"v.io/v23/context"
+)
+
+// benchmarkWS sets up nConns WS connections and measures throughput.
+func benchmarkWS(b *testing.B, nConns int) {
+	ctx, _ := context.RootContext()
+	rchan := make(chan net.Conn, nConns)
+	wchan := make(chan net.Conn, nConns)
+	ln, err := websocket.Listener(ctx, "ws", "127.0.0.1:0")
+	if err != nil {
+		b.Fatalf("websocket.Listener failed: %v", err)
+		return
+	}
+	defer ln.Close()
+	// One goroutine to dial nConns connections.
+	go func() {
+		for i := 0; i < nConns; i++ {
+			conn, err := websocket.Dial(ctx, "ws", ln.Addr().String(), 0)
+			if err != nil {
+				b.Fatalf("websocket.Dial(%q, %q) failed: %v", "ws", ln.Addr(), err)
+				wchan <- nil
+				return
+			}
+			wchan <- conn
+		}
+		close(wchan)
+	}()
+	// One goroutine to accept nConns connections.
+	go func() {
+		for i := 0; i < nConns; i++ {
+			conn, err := ln.Accept()
+			if err != nil {
+				b.Fatalf("Accept failed: %v", err)
+				rchan <- nil
+				return
+			}
+			rchan <- conn
+		}
+		close(rchan)
+	}()
+
+	var readers []io.ReadCloser
+	var writers []io.WriteCloser
+	for r := range rchan {
+		readers = append(readers, r)
+	}
+	for w := range wchan {
+		writers = append(writers, w)
+	}
+	if b.Failed() {
+		return
+	}
+	(&throughputTester{b: b, readers: readers, writers: writers}).Run()
+}
diff --git a/runtime/internal/rpc/stream/benchmark/throughput_wsh.go b/runtime/internal/rpc/stream/benchmark/throughput_wsh.go
new file mode 100644
index 0000000..517fb2e
--- /dev/null
+++ b/runtime/internal/rpc/stream/benchmark/throughput_wsh.go
@@ -0,0 +1,82 @@
+// 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 benchmark
+
+import (
+	"io"
+	"net"
+	"testing"
+
+	"v.io/x/ref/runtime/internal/lib/websocket"
+
+	"v.io/v23/context"
+)
+
+// benchmarkWS sets up nConns WS connections and measures throughput.
+func benchmarkWSH(b *testing.B, protocol string, nConns int) {
+	ctx, _ := context.RootContext()
+	rchan := make(chan net.Conn, nConns)
+	wchan := make(chan net.Conn, nConns)
+	ln, err := websocket.HybridListener(ctx, "wsh", "127.0.0.1:0")
+	if err != nil {
+		b.Fatalf("websocket.HybridListener failed: %v", err)
+		return
+	}
+	defer ln.Close()
+	// One goroutine to dial nConns connections.
+	go func() {
+		for i := 0; i < nConns; i++ {
+			var conn net.Conn
+			var err error
+			switch protocol {
+			case "tcp":
+				conn, err = net.Dial("tcp", ln.Addr().String())
+			case "ws":
+				conn, err = websocket.Dial(ctx, "ws", ln.Addr().String(), 0)
+			}
+			if err != nil {
+				b.Fatalf("Dial(%q, %q) failed: %v", protocol, ln.Addr(), err)
+				wchan <- nil
+				return
+			}
+			if protocol == "tcp" {
+				// Write a dummy byte since wsh waits for magic byte forever.
+				conn.Write([]byte("."))
+			}
+			wchan <- conn
+		}
+		close(wchan)
+	}()
+	// One goroutine to accept nConns connections.
+	go func() {
+		for i := 0; i < nConns; i++ {
+			conn, err := ln.Accept()
+			if err != nil {
+				b.Fatalf("Accept failed: %v", err)
+				rchan <- nil
+				return
+			}
+			if protocol == "tcp" {
+				// Read a dummy byte.
+				conn.Read(make([]byte, 1))
+			}
+			rchan <- conn
+		}
+		close(rchan)
+	}()
+
+	var readers []io.ReadCloser
+	var writers []io.WriteCloser
+	for r := range rchan {
+		readers = append(readers, r)
+	}
+	for w := range wchan {
+		writers = append(writers, w)
+	}
+	if b.Failed() {
+		return
+	}
+	(&throughputTester{b: b, readers: readers, writers: writers}).Run()
+}
diff --git a/runtime/internal/rpc/stream/crypto/box.go b/runtime/internal/rpc/stream/crypto/box.go
new file mode 100644
index 0000000..54e31fb
--- /dev/null
+++ b/runtime/internal/rpc/stream/crypto/box.go
@@ -0,0 +1,131 @@
+// 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 crypto
+
+import (
+	"bytes"
+	"crypto/rand"
+	"encoding/binary"
+	"fmt"
+
+	"golang.org/x/crypto/nacl/box"
+
+	"v.io/v23/verror"
+	"v.io/x/ref/runtime/internal/lib/iobuf"
+	"v.io/x/ref/runtime/internal/rpc/stream"
+)
+
+const pkgPath = "v.io/x/ref/runtime/internal/rpc/stream/crypto"
+
+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.
+	errCipherTextTooShort     = reg(".errCipherTextTooShort", "ciphertext too short")
+	errRemotePublicKey        = reg(".errRemotePublicKey", "failed to get remote public key")
+	errMessageAuthFailed      = reg(".errMessageAuthFailed", "message authentication failed")
+	errUnrecognizedCipherText = reg(".errUnrecognizedCipherText", "CipherSuite {3} is not recognized. Must use one that uses Diffie-Hellman as the key exchange algorithm")
+)
+
+type boxcrypter struct {
+	alloc                 *iobuf.Allocator
+	sharedKey             [32]byte
+	sortedPubkeys         []byte
+	writeNonce, readNonce uint64
+}
+
+type BoxKey [32]byte
+
+// BoxKeyExchanger is used to communicate public keys between peers.
+type BoxKeyExchanger func(myPublicKey *BoxKey) (theirPublicKey *BoxKey, err error)
+
+// GenerateBoxKey generates a new public/private key pair for BoxCrypter.
+func GenerateBoxKey() (publicKey, privateKey *BoxKey, err error) {
+	pk, sk, err := box.GenerateKey(rand.Reader)
+	return (*BoxKey)(pk), (*BoxKey)(sk), err
+}
+
+// NewBoxCrypter uses Curve25519, XSalsa20 and Poly1305 to encrypt and
+// authenticate messages (as defined in http://nacl.cr.yp.to/box.html).
+// An ephemeral Diffie-Hellman key exchange is performed per invocation
+// of NewBoxCrypter; the data sent has forward security with connection
+// granularity. One round-trip is required before any data can be sent.
+// BoxCrypter does NOT do anything to verify the identity of the peer.
+func NewBoxCrypter(exchange BoxKeyExchanger, alloc *iobuf.Allocator) (Crypter, error) {
+	pk, sk, err := GenerateBoxKey()
+	if err != nil {
+		return nil, err
+	}
+	theirPK, err := exchange(pk)
+	if err != nil {
+		return nil, err
+	}
+	if theirPK == nil {
+		return nil, verror.New(errRemotePublicKey, nil)
+	}
+	return NewBoxCrypterWithKey(pk, sk, theirPK, alloc), nil
+}
+
+// NewBoxCrypterWithKey is used when public keys have been already exchanged between peers.
+func NewBoxCrypterWithKey(myPublicKey, myPrivateKey, theirPublicKey *BoxKey, alloc *iobuf.Allocator) Crypter {
+	c := boxcrypter{alloc: alloc}
+	box.Precompute(&c.sharedKey, (*[32]byte)(theirPublicKey), (*[32]byte)(myPrivateKey))
+	// Distinct messages between the same {sender, receiver} set are required
+	// to have distinct nonces. The server with the lexicographically smaller
+	// public key will be sending messages with 0, 2, 4... and the other will
+	// be using 1, 3, 5...
+	if bytes.Compare(myPublicKey[:], theirPublicKey[:]) < 0 {
+		c.writeNonce, c.readNonce = 0, 1
+		c.sortedPubkeys = append(myPublicKey[:], theirPublicKey[:]...)
+	} else {
+		c.writeNonce, c.readNonce = 1, 0
+		c.sortedPubkeys = append(theirPublicKey[:], myPublicKey[:]...)
+	}
+	return &c
+}
+
+func (c *boxcrypter) Encrypt(src *iobuf.Slice) (*iobuf.Slice, error) {
+	var nonce [24]byte
+	binary.LittleEndian.PutUint64(nonce[:], c.writeNonce)
+	c.writeNonce += 2
+	ret := c.alloc.Alloc(uint(len(src.Contents) + box.Overhead))
+	ret.Contents = box.SealAfterPrecomputation(ret.Contents[:0], src.Contents, &nonce, &c.sharedKey)
+	src.Release()
+	return ret, nil
+}
+
+func (c *boxcrypter) Decrypt(src *iobuf.Slice) (*iobuf.Slice, error) {
+	var nonce [24]byte
+	binary.LittleEndian.PutUint64(nonce[:], c.readNonce)
+	c.readNonce += 2
+	retLen := len(src.Contents) - box.Overhead
+	if retLen < 0 {
+		src.Release()
+		return nil, verror.New(stream.ErrNetwork, nil, verror.New(errCipherTextTooShort, nil))
+	}
+	ret := c.alloc.Alloc(uint(retLen))
+	var ok bool
+	ret.Contents, ok = box.OpenAfterPrecomputation(ret.Contents[:0], src.Contents, &nonce, &c.sharedKey)
+	if !ok {
+		src.Release()
+		ret.Release()
+		return nil, verror.New(stream.ErrSecurity, nil, verror.New(errMessageAuthFailed, nil))
+	}
+	src.Release()
+	return ret, nil
+}
+
+func (c *boxcrypter) ChannelBinding() []byte {
+	return c.sortedPubkeys
+}
+
+func (c *boxcrypter) String() string {
+	return fmt.Sprintf("%#v", *c)
+}
diff --git a/runtime/internal/rpc/stream/crypto/box_cipher.go b/runtime/internal/rpc/stream/crypto/box_cipher.go
new file mode 100644
index 0000000..45ab98a
--- /dev/null
+++ b/runtime/internal/rpc/stream/crypto/box_cipher.go
@@ -0,0 +1,171 @@
+// 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 crypto
+
+import (
+	"bytes"
+	"encoding/binary"
+
+	"golang.org/x/crypto/nacl/box"
+	"golang.org/x/crypto/salsa20/salsa"
+
+	"v.io/v23/verror"
+
+	"v.io/x/ref/runtime/internal/rpc/stream"
+)
+
+// cbox implements a ControlCipher using go.crypto/nacl/box.
+type cbox struct {
+	sharedKey      [32]byte
+	channelBinding []byte
+	enc            cboxStream
+	dec            cboxStream
+}
+
+// cboxStream implements one stream of encryption or decryption.
+type cboxStream struct {
+	counter uint64
+	nonce   [24]byte
+	// buffer is a temporary used for in-place crypto.
+	buffer []byte
+}
+
+const (
+	cboxMACSize = box.Overhead
+)
+
+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.
+	errMessageTooShort = reg(".errMessageTooShort", "control cipher: message is too short")
+)
+
+func (s *cboxStream) alloc(n int) []byte {
+	if len(s.buffer) < n {
+		s.buffer = make([]byte, n*2)
+	}
+	return s.buffer[:0]
+}
+
+func (s *cboxStream) currentNonce() *[24]byte {
+	return &s.nonce
+}
+
+func (s *cboxStream) advanceNonce() {
+	s.counter++
+	binary.LittleEndian.PutUint64(s.nonce[:], s.counter)
+}
+
+// setupXSalsa20 produces a sub-key and Salsa20 counter given a nonce and key.
+//
+// See, "Extending the Salsa20 nonce," by Daniel J. Bernsten, Department of
+// Computer Science, University of Illinois at Chicago, 2008.
+func setupXSalsa20(subKey *[32]byte, counter *[16]byte, nonce *[24]byte, key *[32]byte) {
+	// We use XSalsa20 for encryption so first we need to generate a
+	// key and nonce with HSalsa20.
+	var hNonce [16]byte
+	copy(hNonce[:], nonce[:])
+	salsa.HSalsa20(subKey, &hNonce, key, &salsa.Sigma)
+
+	// The final 8 bytes of the original nonce form the new nonce.
+	copy(counter[:], nonce[16:])
+}
+
+// NewControlCipher returns a ControlCipher for RPC versions from 6 to 10.
+func NewControlCipherRPC6(myPrivateKey, theirPublicKey *BoxKey, isServer bool) ControlCipher {
+	var c cbox
+	box.Precompute(&c.sharedKey, (*[32]byte)(theirPublicKey), (*[32]byte)(myPrivateKey))
+	// The stream is full-duplex, and we want the directions to use different
+	// nonces, so we set bit (1 << 64) in the server-to-client stream, and leave
+	// it cleared in the client-to-server stream.  advanceNone touches only the
+	// first 8 bytes, so this change is permanent for the duration of the
+	// stream.
+	if isServer {
+		c.enc.nonce[8] = 1
+	} else {
+		c.dec.nonce[8] = 1
+	}
+	return &c
+}
+
+// NewControlCipher returns a ControlCipher for RPC versions greater than or equal to 11.
+func NewControlCipherRPC11(myPublicKey, myPrivateKey, theirPublicKey *BoxKey) ControlCipher {
+	var c cbox
+	box.Precompute(&c.sharedKey, (*[32]byte)(theirPublicKey), (*[32]byte)(myPrivateKey))
+	// The stream is full-duplex, and we want the directions to use different
+	// nonces, so we set bit (1 << 64) in one stream, and leave it cleared in
+	// the other server stream. advanceNone touches only the first 8 bytes,
+	// so this change is permanent for the duration of the stream.
+	if bytes.Compare(myPublicKey[:], theirPublicKey[:]) < 0 {
+		c.enc.nonce[8] = 1
+		c.channelBinding = append(myPublicKey[:], theirPublicKey[:]...)
+	} else {
+		c.dec.nonce[8] = 1
+		c.channelBinding = append(theirPublicKey[:], myPublicKey[:]...)
+	}
+	return &c
+}
+
+// MACSize implements the ControlCipher method.
+func (c *cbox) MACSize() int {
+	return cboxMACSize
+}
+
+// Seal implements the ControlCipher method.
+func (c *cbox) Seal(data []byte) error {
+	n := len(data)
+	if n < cboxMACSize {
+		return verror.New(stream.ErrNetwork, nil, verror.New(errMessageTooShort, nil))
+	}
+	tmp := c.enc.alloc(n)
+	nonce := c.enc.currentNonce()
+	out := box.SealAfterPrecomputation(tmp, data[:n-cboxMACSize], nonce, &c.sharedKey)
+	c.enc.advanceNonce()
+	copy(data, out)
+	return nil
+}
+
+// Open implements the ControlCipher method.
+func (c *cbox) Open(data []byte) bool {
+	n := len(data)
+	if n < cboxMACSize {
+		return false
+	}
+	tmp := c.dec.alloc(n - cboxMACSize)
+	nonce := c.dec.currentNonce()
+	out, ok := box.OpenAfterPrecomputation(tmp, data, nonce, &c.sharedKey)
+	if !ok {
+		return false
+	}
+	c.dec.advanceNonce()
+	copy(data, out)
+	return true
+}
+
+// Encrypt implements the ControlCipher method.
+func (c *cbox) Encrypt(data []byte) {
+	var subKey [32]byte
+	var counter [16]byte
+	nonce := c.enc.currentNonce()
+	setupXSalsa20(&subKey, &counter, nonce, &c.sharedKey)
+	c.enc.advanceNonce()
+	salsa.XORKeyStream(data, data, &counter, &subKey)
+}
+
+// Decrypt implements the ControlCipher method.
+func (c *cbox) Decrypt(data []byte) {
+	var subKey [32]byte
+	var counter [16]byte
+	nonce := c.dec.currentNonce()
+	setupXSalsa20(&subKey, &counter, nonce, &c.sharedKey)
+	c.dec.advanceNonce()
+	salsa.XORKeyStream(data, data, &counter, &subKey)
+}
+
+func (c *cbox) ChannelBinding() []byte {
+	return c.channelBinding
+}
diff --git a/runtime/internal/rpc/stream/crypto/box_cipher_test.go b/runtime/internal/rpc/stream/crypto/box_cipher_test.go
new file mode 100644
index 0000000..a7c46ec
--- /dev/null
+++ b/runtime/internal/rpc/stream/crypto/box_cipher_test.go
@@ -0,0 +1,174 @@
+// 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 crypto_test
+
+import (
+	"bytes"
+	"crypto/rand"
+	"errors"
+	"testing"
+
+	"golang.org/x/crypto/nacl/box"
+
+	"v.io/x/ref/runtime/internal/rpc/stream/crypto"
+)
+
+// Add space for a MAC.
+func newMessage(s string) []byte {
+	b := make([]byte, len(s)+box.Overhead)
+	copy(b, []byte(s))
+	return b
+}
+
+type testCipherVersion int
+
+const (
+	cipherRPC6 testCipherVersion = iota
+	cipherRPC11
+)
+
+func newCipher(ver testCipherVersion) (c1, c2 crypto.ControlCipher, err error) {
+	pk1, sk1, err := box.GenerateKey(rand.Reader)
+	if err != nil {
+		return nil, nil, errors.New("can't generate key")
+	}
+	pk2, sk2, err := box.GenerateKey(rand.Reader)
+	if err != nil {
+		return nil, nil, errors.New("can't generate key")
+	}
+	switch ver {
+	case cipherRPC6:
+		c1 = crypto.NewControlCipherRPC6((*crypto.BoxKey)(sk1), (*crypto.BoxKey)(pk2), true)
+		c2 = crypto.NewControlCipherRPC6((*crypto.BoxKey)(sk2), (*crypto.BoxKey)(pk1), false)
+	case cipherRPC11:
+		c1 = crypto.NewControlCipherRPC11((*crypto.BoxKey)(pk1), (*crypto.BoxKey)(sk1), (*crypto.BoxKey)(pk2))
+		c2 = crypto.NewControlCipherRPC11((*crypto.BoxKey)(pk2), (*crypto.BoxKey)(sk2), (*crypto.BoxKey)(pk1))
+	}
+	return
+}
+
+func testCipherOpenSeal(t *testing.T, ver testCipherVersion) {
+	c1, c2, err := newCipher(ver)
+	if err != nil {
+		t.Fatalf("can't create cipher: %v", err)
+	}
+
+	msg1 := newMessage("hello")
+	if err := c1.Seal(msg1); err != nil {
+		t.Errorf("unexpected error: %s", err)
+	}
+	msg2 := newMessage("world")
+	if err := c1.Seal(msg2); err != nil {
+		t.Errorf("unexpected error: %s", err)
+	}
+	msg3 := newMessage("hello")
+	if err := c1.Seal(msg3); err != nil {
+		t.Errorf("unexpected error: %s", err)
+	}
+	if bytes.Compare(msg1, msg3) == 0 {
+		t.Errorf("message should differ: %q, %q", msg1, msg3)
+	}
+
+	// Check that the client does not encrypt the same.
+	msg4 := newMessage("hello")
+	if err := c2.Seal(msg4); err != nil {
+		t.Errorf("unexpected error: %s", err)
+	}
+	if bytes.Compare(msg4, msg1) == 0 {
+		t.Errorf("messages should differ %q vs. %q", msg4, msg1)
+	}
+
+	// Corrupted message should not decrypt.
+	msg1[0] ^= 1
+	if ok := c2.Open(msg1); ok {
+		t.Errorf("expected error")
+	}
+
+	// Fix the message and try again.
+	msg1[0] ^= 1
+	if ok := c2.Open(msg1); !ok {
+		t.Errorf("Open failed")
+	}
+	if bytes.Compare(msg1[:5], []byte("hello")) != 0 {
+		t.Errorf("got %q, expected %q", msg1[:5], "hello")
+	}
+
+	// msg3 should not decrypt.
+	if ok := c2.Open(msg3); ok {
+		t.Errorf("expected error")
+	}
+
+	// Resume.
+	if ok := c2.Open(msg2); !ok {
+		t.Errorf("Open failed")
+	}
+	if bytes.Compare(msg2[:5], []byte("world")) != 0 {
+		t.Errorf("got %q, expected %q", msg2[:5], "world")
+	}
+	if ok := c2.Open(msg3); !ok {
+		t.Errorf("Open failed")
+	}
+	if bytes.Compare(msg3[:5], []byte("hello")) != 0 {
+		t.Errorf("got %q, expected %q", msg3[:5], "hello")
+	}
+}
+func TestCipherOpenSealRPC6(t *testing.T)  { testCipherOpenSeal(t, cipherRPC6) }
+func TestCipherOpenSealRPC11(t *testing.T) { testCipherOpenSeal(t, cipherRPC11) }
+
+func testCipherXORKeyStream(t *testing.T, ver testCipherVersion) {
+	c1, c2, err := newCipher(ver)
+	if err != nil {
+		t.Fatalf("can't create cipher: %v", err)
+	}
+
+	msg1 := []byte("hello")
+	msg2 := []byte("world")
+	msg3 := []byte("hello")
+	c1.Encrypt(msg1)
+	c1.Encrypt(msg2)
+	c1.Encrypt(msg3)
+	if bytes.Compare(msg1, msg3) == 0 {
+		t.Errorf("messages should differ: %q, %q", msg1, msg3)
+	}
+
+	c2.Decrypt(msg1)
+	c2.Decrypt(msg2)
+	c2.Decrypt(msg3)
+	s1 := string(msg1)
+	s2 := string(msg2)
+	s3 := string(msg3)
+	if s1 != "hello" {
+		t.Errorf("got %q, expected 'hello'", s1)
+	}
+	if s2 != "world" {
+		t.Errorf("got %q, expected 'world'", s2)
+	}
+	if s3 != "hello" {
+		t.Errorf("got %q, expected 'hello'", s3)
+	}
+}
+func TestCipherXORKeyStreamRPC6(t *testing.T)  { testCipherXORKeyStream(t, cipherRPC6) }
+func TestCipherXORKeyStreamRPC11(t *testing.T) { testCipherXORKeyStream(t, cipherRPC11) }
+
+func TestCipherChannelBinding(t *testing.T) {
+	values := make([][]byte, 100)
+	for i := 0; i < len(values); i++ {
+		c1, c2, err := newCipher(cipherRPC11)
+		if err != nil {
+			t.Fatalf("can't create cipher: %v", err)
+		}
+		if !bytes.Equal(c1.ChannelBinding(), c2.ChannelBinding()) {
+			t.Fatalf("Two ends of the crypter ended up with different channel bindings (iteration #%d)", i)
+		}
+		values[i] = c1.ChannelBinding()
+	}
+	for i := 0; i < len(values); i++ {
+		for j := i + 1; j < len(values); j++ {
+			if bytes.Equal(values[i], values[j]) {
+				t.Fatalf("Same ChannelBinding seen on multiple channels (%d and %d)", i, j)
+			}
+		}
+	}
+}
diff --git a/runtime/internal/rpc/stream/crypto/control_cipher.go b/runtime/internal/rpc/stream/crypto/control_cipher.go
new file mode 100644
index 0000000..33c97df
--- /dev/null
+++ b/runtime/internal/rpc/stream/crypto/control_cipher.go
@@ -0,0 +1,34 @@
+// 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 crypto
+
+// ControlCipher provides the ciphers and MAC for control channel encryption.
+// Encryption and decryption are performed in place.
+type ControlCipher interface {
+	// MACSize returns the number of bytes in the MAC.
+	MACSize() int
+
+	// Seal replaces the message with an authenticated and encrypted version.
+	// The trailing MACSize bytes of the data are used for the MAC; they are
+	// discarded and overwritten.
+	Seal(data []byte) error
+
+	// Open authenticates and decrypts a box produced by Seal.  The trailing
+	// MACSize bytes are not changed.  Returns true on success.
+	Open(data []byte) bool
+
+	// Encrypt encrypts the data in place.  No MAC is added.
+	Encrypt(data []byte)
+
+	// Decrypt decrypts the data in place.  No MAC is verified.
+	Decrypt(data []byte)
+
+	// ChannelBinding Returns a byte slice that is unique for the the
+	// particular cipher (and the parties between which it is operating).
+	// Having both parties assert out of the band that they are indeed
+	// participating in a connection with that channel binding value is
+	// sufficient to authenticate the data received through the cipher.
+	ChannelBinding() []byte
+}
diff --git a/runtime/internal/rpc/stream/crypto/crypto.go b/runtime/internal/rpc/stream/crypto/crypto.go
new file mode 100644
index 0000000..aba2628
--- /dev/null
+++ b/runtime/internal/rpc/stream/crypto/crypto.go
@@ -0,0 +1,39 @@
+// 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 crypto implements encryption and decryption interfaces intended for
+// securing communication over VCs.
+package crypto
+
+import "v.io/x/ref/runtime/internal/lib/iobuf"
+
+type Encrypter interface {
+	// Encrypt encrypts the provided plaintext data and returns the
+	// corresponding ciphertext slice (or nil if an error is returned).
+	//
+	// It always calls Release on plaintext and thus plaintext should not
+	// be used after calling Encrypt.
+	Encrypt(plaintext *iobuf.Slice) (ciphertext *iobuf.Slice, err error)
+}
+
+type Decrypter interface {
+	// Decrypt decrypts the provided ciphertext slice and returns the
+	// corresponding plaintext (or nil if an error is returned).
+	//
+	// It always calls Release on ciphertext and thus ciphertext should not
+	// be used after calling Decrypt.
+	Decrypt(ciphertext *iobuf.Slice) (plaintext *iobuf.Slice, err error)
+}
+
+type Crypter interface {
+	Encrypter
+	Decrypter
+	// ChannelBinding returns a byte slice that is unique for the the
+	// particular crypter (and the parties between which it is operating).
+	// Having both parties assert out of the band that they are indeed
+	// participating in a connection with that channel binding value is
+	// sufficient to authenticate the data received through the crypter.
+	ChannelBinding() []byte
+	String() string
+}
diff --git a/runtime/internal/rpc/stream/crypto/crypto_test.go b/runtime/internal/rpc/stream/crypto/crypto_test.go
new file mode 100644
index 0000000..b44af89
--- /dev/null
+++ b/runtime/internal/rpc/stream/crypto/crypto_test.go
@@ -0,0 +1,200 @@
+// 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 crypto
+
+import (
+	"bytes"
+	"crypto/rand"
+	"net"
+	"testing"
+	"testing/quick"
+
+	"v.io/x/ref/runtime/internal/lib/iobuf"
+)
+
+func quickTest(t *testing.T, e Encrypter, d Decrypter) {
+	f := func(plaintext []byte) bool {
+		plainslice := iobuf.NewSlice(plaintext)
+		cipherslice, err := e.Encrypt(plainslice)
+		if err != nil {
+			t.Error(err)
+			return false
+		}
+		plainslice, err = d.Decrypt(cipherslice)
+		if err != nil {
+			t.Error(err)
+			return false
+		}
+		defer plainslice.Release()
+		return bytes.Equal(plainslice.Contents, plaintext)
+	}
+	if err := quick.Check(f, nil); err != nil {
+		t.Error(err)
+	}
+}
+
+func TestNull(t *testing.T) {
+	crypter := NewNullCrypter()
+	quickTest(t, crypter, crypter)
+	_ = crypter.String() // Only to test that String does not crash.
+}
+
+func TestNullWithChannelBinding(t *testing.T) {
+	cb := []byte{1, 2, 3, 5}
+	crypter := NewNullCrypterWithChannelBinding(cb)
+	quickTest(t, crypter, crypter)
+	if got := crypter.ChannelBinding(); !bytes.Equal(cb, got) {
+		t.Errorf("Unexpected channel binding; got %q, want %q", got, cb)
+	}
+	_ = crypter.String() // Only to test that String does not crash.
+}
+
+func testSimple(t *testing.T, c1, c2 Crypter) {
+	// Execute String just to check that it does not crash.
+	_ = c1.String()
+	_ = c2.String()
+	if t.Failed() {
+		return
+	}
+	quickTest(t, c1, c2)
+	quickTest(t, c2, c1)
+
+	// Log the byte overhead of encryption, just so that test output has a
+	// record.
+	var overhead [10]int
+	for i := 0; i < len(overhead); i++ {
+		size := 1 << uint(i)
+		slice, err := c1.Encrypt(iobuf.NewSlice(make([]byte, size)))
+		overhead[i] = slice.Size() - size
+		slice.Release()
+		if err != nil {
+			t.Fatalf("%d: %v", i, err)
+		}
+	}
+	t.Logf("Byte overhead of encryption: %v", overhead)
+}
+
+func TestBox(t *testing.T) {
+	c1, c2 := boxCrypters(t, nil, nil)
+	testSimple(t, c1, c2)
+}
+
+// testChannelBinding attempts to ensure that:
+// (a) ChannelBinding returns the same value for both ends of a Crypter
+// (b) ChannelBindings are unique
+// For (b), we simply test many times and check that no two instances have the same ChannelBinding value.
+// Yes, this test isn't exhaustive. If you have ideas, please share.
+func testChannelBinding(t *testing.T, factory func(testing.TB, net.Conn, net.Conn) (Crypter, Crypter)) {
+	values := make([][]byte, 100)
+	for i := 0; i < len(values); i++ {
+		conn1, conn2 := net.Pipe()
+		c1, c2 := factory(t, conn1, conn2)
+		if !bytes.Equal(c1.ChannelBinding(), c2.ChannelBinding()) {
+			t.Fatalf("Two ends of the crypter ended up with different channel bindings (iteration #%d)", i)
+		}
+		values[i] = c1.ChannelBinding()
+	}
+	for i := 0; i < len(values); i++ {
+		for j := i + 1; j < len(values); j++ {
+			if bytes.Equal(values[i], values[j]) {
+				t.Fatalf("Same ChannelBinding seen on multiple channels (%d and %d)", i, j)
+			}
+		}
+	}
+}
+
+func TestChannelBindingBox(t *testing.T) { testChannelBinding(t, boxCrypters) }
+
+type factory func(t testing.TB, server, client net.Conn) (Crypter, Crypter)
+
+func boxCrypters(t testing.TB, _, _ net.Conn) (Crypter, Crypter) {
+	server, client := make(chan *BoxKey, 1), make(chan *BoxKey, 1)
+	clientExchanger := func(pubKey *BoxKey) (*BoxKey, error) {
+		client <- pubKey
+		return <-server, nil
+	}
+	serverExchanger := func(pubKey *BoxKey) (*BoxKey, error) {
+		server <- pubKey
+		return <-client, nil
+	}
+	crypters := make(chan Crypter)
+	for _, ex := range []BoxKeyExchanger{clientExchanger, serverExchanger} {
+		go func(exchanger BoxKeyExchanger) {
+			crypter, err := NewBoxCrypter(exchanger, iobuf.NewAllocator(iobuf.NewPool(0), 0))
+			if err != nil {
+				t.Fatal(err)
+			}
+			crypters <- crypter
+		}(ex)
+	}
+	return <-crypters, <-crypters
+}
+
+func benchmarkEncrypt(b *testing.B, crypters factory, size int) {
+	plaintext := make([]byte, size)
+	if _, err := rand.Read(plaintext); err != nil {
+		b.Fatal(err)
+	}
+	conn1, conn2 := net.Pipe()
+	defer conn1.Close()
+	defer conn2.Close()
+	e, _ := crypters(b, conn1, conn2)
+	b.SetBytes(int64(size))
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		cipher, err := e.Encrypt(iobuf.NewSlice(plaintext))
+		if err != nil {
+			b.Fatal(err)
+		}
+		cipher.Release()
+	}
+}
+
+func BenchmarkBoxEncrypt_1B(b *testing.B)  { benchmarkEncrypt(b, boxCrypters, 1) }
+func BenchmarkBoxEncrypt_1K(b *testing.B)  { benchmarkEncrypt(b, boxCrypters, 1<<10) }
+func BenchmarkBoxEncrypt_10K(b *testing.B) { benchmarkEncrypt(b, boxCrypters, 10<<10) }
+func BenchmarkBoxEncrypt_1M(b *testing.B)  { benchmarkEncrypt(b, boxCrypters, 1<<20) }
+func BenchmarkBoxEncrypt_5M(b *testing.B)  { benchmarkEncrypt(b, boxCrypters, 5<<20) }
+
+func benchmarkRoundTrip(b *testing.B, crypters factory, size int) {
+	plaintext := make([]byte, size)
+	if _, err := rand.Read(plaintext); err != nil {
+		b.Fatal(err)
+	}
+	conn1, conn2 := net.Pipe()
+	defer conn1.Close()
+	defer conn2.Close()
+	e, d := crypters(b, conn1, conn2)
+	b.SetBytes(int64(size))
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		cipherslice, err := e.Encrypt(iobuf.NewSlice(plaintext))
+		if err != nil {
+			b.Fatal(err)
+		}
+		plainslice, err := d.Decrypt(cipherslice)
+		if err != nil {
+			b.Fatal(err)
+		}
+		plainslice.Release()
+	}
+}
+
+func BenchmarkBoxRoundTrip_1B(b *testing.B)  { benchmarkRoundTrip(b, boxCrypters, 1) }
+func BenchmarkBoxRoundTrip_1K(b *testing.B)  { benchmarkRoundTrip(b, boxCrypters, 1<<10) }
+func BenchmarkBoxRoundTrip_10K(b *testing.B) { benchmarkRoundTrip(b, boxCrypters, 10<<10) }
+func BenchmarkBoxRoundTrip_1M(b *testing.B)  { benchmarkRoundTrip(b, boxCrypters, 1<<20) }
+func BenchmarkBoxRoundTrip_5M(b *testing.B)  { benchmarkRoundTrip(b, boxCrypters, 5<<20) }
+
+func benchmarkSetup(b *testing.B, crypters factory) {
+	for i := 0; i < b.N; i++ {
+		conn1, conn2 := net.Pipe()
+		crypters(b, conn1, conn2)
+		conn1.Close()
+		conn2.Close()
+	}
+}
+
+func BenchmarkBoxSetup(b *testing.B) { benchmarkSetup(b, boxCrypters) }
diff --git a/runtime/internal/rpc/stream/crypto/null.go b/runtime/internal/rpc/stream/crypto/null.go
new file mode 100644
index 0000000..73110a6
--- /dev/null
+++ b/runtime/internal/rpc/stream/crypto/null.go
@@ -0,0 +1,29 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package crypto
+
+import "v.io/x/ref/runtime/internal/lib/iobuf"
+
+// NewNullCrypter returns a Crypter that does no encryption/decryption.
+func NewNullCrypter() Crypter { return null{} }
+
+// NewNullCrypterWithChannelBinding returns a null Crypter with a channel binding.
+func NewNullCrypterWithChannelBinding(channelBinding []byte) Crypter {
+	return null{channelBinding}
+}
+
+type null struct {
+	channelBinding []byte
+}
+
+func (null) Encrypt(src *iobuf.Slice) (*iobuf.Slice, error) { return src, nil }
+func (null) Decrypt(src *iobuf.Slice) (*iobuf.Slice, error) { return src, nil }
+func (n null) ChannelBinding() []byte                       { return n.channelBinding }
+func (n null) String() string {
+	if n.channelBinding == nil {
+		return "Null"
+	}
+	return "Null(ChannelBinding)"
+}
diff --git a/runtime/internal/rpc/stream/crypto/null_cipher.go b/runtime/internal/rpc/stream/crypto/null_cipher.go
new file mode 100644
index 0000000..96a642c
--- /dev/null
+++ b/runtime/internal/rpc/stream/crypto/null_cipher.go
@@ -0,0 +1,28 @@
+// 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 crypto
+
+// NullControlCipher is a cipher that does nothing.
+type NullControlCipher struct{}
+
+func (NullControlCipher) MACSize() int           { return 0 }
+func (NullControlCipher) Seal(data []byte) error { return nil }
+func (NullControlCipher) Open(data []byte) bool  { return true }
+func (NullControlCipher) Encrypt(data []byte)    {}
+func (NullControlCipher) Decrypt(data []byte)    {}
+func (NullControlCipher) ChannelBinding() []byte { return nil }
+
+type disabledControlCipher struct {
+	NullControlCipher
+	macSize int
+}
+
+func (c *disabledControlCipher) MACSize() int { return c.macSize }
+
+// NewDisabledControlCipher returns a cipher that has the correct MACSize, but
+// encryption and decryption are disabled.
+func NewDisabledControlCipher(c ControlCipher) ControlCipher {
+	return &disabledControlCipher{macSize: c.MACSize()}
+}
diff --git a/runtime/internal/rpc/stream/doc.go b/runtime/internal/rpc/stream/doc.go
new file mode 100644
index 0000000..509628a
--- /dev/null
+++ b/runtime/internal/rpc/stream/doc.go
@@ -0,0 +1,54 @@
+// 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 stream implements authenticated byte streams to vanadium endpoints.
+//
+// It is split into multiple sub-packages in an attempt to keep the code
+// healthier by limiting the dependencies between objects. Most users should not
+// need to use this package.
+//
+// Package contents and dependencies are as follows:
+//
+//      * manager provides a factory for Manager objects.
+//        It depends on the vif and proxy packages.
+//      * vif implements a VIF type that wraps over a net.Conn and enables the
+//        creation of VC objects over the underlying network connection.
+//        It depends on the id, message and vc packages.
+//      * message implements serialization and deserialization for messages
+//        exchanged over a VIF.
+//        It depends on the id package.
+//      * vc provides types implementing VC and Flow.
+//        It depends on the id and crypto packages.
+//      * crypto provides types to secure communication over VCs.
+//        It does not depend on any other package.
+//      * id defines identifier types used by other packages.
+//        It does not depend on any other package.
+package stream
+
+// A dump of some ideas/thoughts/TODOs arising from the first iteration of this
+// package. Big ticket items like proxying and TLS/authentication are obvious
+// and won't be missed. I just wanted to put some smaller items on record (in
+// no particular order).
+//
+// (1) Garbage collection of VIFs: Create a policy to close the underlying
+// network connection (and shutdown the VIF) when it is "inactive" (i.e., no VCs
+// have existed on it for a while).
+// (2) On the first write of a new flow, counters are stolen from a shared pool
+// (to avoid a round trip of a "create flow" message followed by a "here are
+// your counters" message). Currently, this happens on either end of the flow
+// (on both the remote and local process). This doesn't need to be the case,
+// the end that received the first message of the flow doesn't need to steal
+// on its first write.
+// (3) Should flow control counters be part of the Data message?
+// If so, maybe the flowQ should have a lower priority than that of Data
+// messages? At a higher level I'm thinking of ways to reduce the number
+// of messages sent per flow. Currently, just creating a flow results in
+// two messages - One where the initiator sends counters to the receiver
+// and one where the receiver does the same. The first write does not
+// block on receiving the counters because of the "steal from shared pool on
+// first write" scheme, but still, sounds like too much traffic.
+// (4) As an example of the above, consider the following code:
+//     vc.Connect().Close()
+// This will result in 3 messages. But ideally it should involve 0.
+// (5) Encryption of control messages to protect from network sniffers.
diff --git a/runtime/internal/rpc/stream/error_test.go b/runtime/internal/rpc/stream/error_test.go
new file mode 100644
index 0000000..fa0716f
--- /dev/null
+++ b/runtime/internal/rpc/stream/error_test.go
@@ -0,0 +1,36 @@
+// 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 stream_test
+
+import (
+	"net"
+	"testing"
+
+	"v.io/v23/verror"
+
+	"v.io/x/ref/runtime/internal/rpc/stream"
+)
+
+func TestTimeoutError(t *testing.T) {
+	e := verror.Register(".test", verror.NoRetry, "hello{:3}")
+	timeoutErr := stream.NewNetError(verror.New(e, nil, "world"), true, false)
+
+	// TimeoutError implements error & net.Error. We test that it
+	// implements error by assigning timeoutErr to err which is of type error.
+	var err error
+	err = timeoutErr
+
+	neterr, ok := err.(net.Error)
+	if !ok {
+		t.Fatalf("%T not a net.Error", err)
+	}
+
+	if got, want := neterr.Timeout(), true; got != want {
+		t.Fatalf("got %v, want %v", got, want)
+	}
+	if got, want := neterr.Error(), "hello: world"; got != want {
+		t.Fatalf("got %v, want %v", got, want)
+	}
+}
diff --git a/runtime/internal/rpc/stream/errors.go b/runtime/internal/rpc/stream/errors.go
new file mode 100644
index 0000000..2a676d4
--- /dev/null
+++ b/runtime/internal/rpc/stream/errors.go
@@ -0,0 +1,60 @@
+// 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 stream
+
+import (
+	"net"
+
+	"v.io/v23/verror"
+
+	"v.io/x/ref/lib/apilog"
+)
+
+const pkgPath = "v.io/x/ref/runtime/internal/rpc/stream"
+
+// The stream family of packages guarantee to return one of the verror codes defined
+// here, their messages are constructed so as to avoid embedding a component/method name
+// and are thus more suitable for inclusion in other verrors.
+// This practiced of omitting {1}{2} is used throughout the stream packages since all
+// of their errors are intended to be used as arguments to higher level errors.
+var (
+	// TODO(cnicolaou): rename ErrSecurity to ErrAuth
+	ErrSecurity      = verror.Register(pkgPath+".errSecurity", verror.NoRetry, "{:3}")
+	ErrNotTrusted    = verror.Register(pkgPath+".errNotTrusted", verror.NoRetry, "{:3}")
+	ErrNetwork       = verror.Register(pkgPath+".errNetwork", verror.NoRetry, "{:3}")
+	ErrDialFailed    = verror.Register(pkgPath+".errDialFailed", verror.NoRetry, "{:3}")
+	ErrResolveFailed = verror.Register(pkgPath+".errResolveFailed", verror.NoRetry, "{:3}")
+	ErrProxy         = verror.Register(pkgPath+".errProxy", verror.NoRetry, "{:3}")
+	ErrBadArg        = verror.Register(pkgPath+".errBadArg", verror.NoRetry, "{:3}")
+	ErrBadState      = verror.Register(pkgPath+".errBadState", verror.NoRetry, "{:3}")
+	ErrAborted       = verror.Register(pkgPath+".errAborted", verror.NoRetry, "{:3}")
+)
+
+// NetError implements net.Error
+type NetError struct {
+	err           error
+	timeout, temp bool
+}
+
+// TODO(cnicolaou): investigate getting rid of the use of net.Error
+// entirely. The rpc code can now test for a specific verror code and it's
+// not clear that the net.Conns we implement in Vanadium will ever be used
+// directly by code that expects them to return a net.Error when they
+// timeout.
+
+// NewNetError returns a new net.Error which will return the
+// supplied error, timeout and temporary parameters when the corresponding
+// methods are invoked.
+func NewNetError(err error, timeout, temporary bool) net.Error {
+	return &NetError{err, timeout, temporary}
+}
+
+func (t NetError) Err() error { return t.err }
+func (t NetError) Error() string {
+	defer apilog.LogCall(nil)(nil) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	return t.err.Error()
+}
+func (t NetError) Timeout() bool   { return t.timeout }
+func (t NetError) Temporary() bool { return t.temp }
diff --git a/runtime/internal/rpc/stream/id/id.go b/runtime/internal/rpc/stream/id/id.go
new file mode 100644
index 0000000..fd6a641
--- /dev/null
+++ b/runtime/internal/rpc/stream/id/id.go
@@ -0,0 +1,12 @@
+// 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 id provides types for identifying VCs and Flows over them.
+package id
+
+// VC identifies a VC over a VIF.
+type VC uint32
+
+// Flow identifies a Flow over a VC.
+type Flow uint32
diff --git a/runtime/internal/rpc/stream/manager/error_test.go b/runtime/internal/rpc/stream/manager/error_test.go
new file mode 100644
index 0000000..78d2fc0
--- /dev/null
+++ b/runtime/internal/rpc/stream/manager/error_test.go
@@ -0,0 +1,153 @@
+// 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 manager_test
+
+import (
+	"net"
+	"testing"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/verror"
+
+	_ "v.io/x/ref/runtime/factories/generic"
+	inaming "v.io/x/ref/runtime/internal/naming"
+	"v.io/x/ref/runtime/internal/rpc/stream"
+	"v.io/x/ref/runtime/internal/rpc/stream/manager"
+	"v.io/x/ref/runtime/internal/rpc/stream/message"
+	"v.io/x/ref/runtime/internal/testing/mocks/mocknet"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/testutil"
+)
+
+func TestListenErrors(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	server := manager.InternalNew(ctx, naming.FixedRoutingID(0x1))
+	pserver := testutil.NewPrincipal("server")
+	ctx, _ = v23.WithPrincipal(ctx, pserver)
+
+	// principal, no blessings
+	_, _, err := server.Listen(ctx, "tcp", "127.0.0.1:0", security.Blessings{}, nil)
+	if verror.ErrorID(err) != stream.ErrBadArg.ID {
+		t.Fatalf("wrong error: %s", err)
+	}
+	t.Log(err)
+
+	// blessings, no principal
+	nilctx, _ := v23.WithPrincipal(ctx, nil)
+	_, _, err = server.Listen(nilctx, "tcp", "127.0.0.1:0", pserver.BlessingStore().Default(), nil)
+	if verror.ErrorID(err) != stream.ErrBadArg.ID {
+		t.Fatalf("wrong error: %s", err)
+	}
+	t.Log(err)
+
+	// bad protocol
+	_, _, err = server.Listen(ctx, "foo", "127.0.0.1:0", pserver.BlessingStore().Default())
+	if verror.ErrorID(err) != stream.ErrBadArg.ID {
+		t.Fatalf("wrong error: %s", err)
+	}
+	t.Log(err)
+
+	// bad address
+	_, _, err = server.Listen(ctx, "tcp", "xx.0.0.1:0", pserver.BlessingStore().Default())
+	if verror.ErrorID(err) != stream.ErrNetwork.ID {
+		t.Fatalf("wrong error: %s", err)
+	}
+	t.Log(err)
+
+	// bad address for proxy
+	_, _, err = server.Listen(ctx, "v23", "127x.0.0.1", pserver.BlessingStore().Default())
+	if verror.ErrorID(err) != stream.ErrBadArg.ID {
+		t.Fatalf("wrong error: %s", err)
+	}
+	t.Log(err)
+}
+
+func acceptLoop(ln stream.Listener) {
+	for {
+		f, err := ln.Accept()
+		if err != nil {
+			break
+		}
+		f.Close()
+	}
+
+}
+func dropDataDialer(ctx *context.T, network, address string, timeout time.Duration) (net.Conn, error) {
+	matcher := func(read bool, msg message.T) bool {
+		switch msg.(type) {
+		case *message.Setup:
+			return true
+		}
+		return false
+	}
+	opts := mocknet.Opts{
+		Mode:              mocknet.V23CloseAtMessage,
+		V23MessageMatcher: matcher,
+	}
+	return mocknet.DialerWithOpts(opts, network, address, timeout)
+}
+
+func simpleResolver(ctx *context.T, network, address string) (string, string, error) {
+	return network, address, nil
+}
+
+func simpleListen(ctx *context.T, network, address string) (net.Listener, error) {
+	return net.Listen(network, address)
+}
+
+func TestDialErrors(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	server := manager.InternalNew(ctx, naming.FixedRoutingID(0x55555555))
+	client := manager.InternalNew(ctx, naming.FixedRoutingID(0xcccccccc))
+	pclient := testutil.NewPrincipal("client")
+	pserver := testutil.NewPrincipal("server")
+	cctx, _ := v23.WithPrincipal(ctx, pclient)
+	sctx, _ := v23.WithPrincipal(ctx, pserver)
+
+	// bad protocol
+	ep, _ := inaming.NewEndpoint(naming.FormatEndpoint("x", "127.0.0.1:2"))
+	_, err := client.Dial(cctx, ep)
+	// A bad protocol should result in a Resolve Error.
+	if verror.ErrorID(err) != stream.ErrResolveFailed.ID {
+		t.Errorf("wrong error: %v", err)
+	}
+	t.Log(err)
+
+	// no server
+	ep, _ = inaming.NewEndpoint(naming.FormatEndpoint("tcp", "127.0.0.1:2"))
+	_, err = client.Dial(cctx, ep)
+	if verror.ErrorID(err) != stream.ErrDialFailed.ID {
+		t.Errorf("wrong error: %v", err)
+	}
+	t.Log(err)
+
+	rpc.RegisterProtocol("dropData", dropDataDialer, simpleResolver, simpleListen)
+
+	ln, sep, err := server.Listen(sctx, "tcp", "127.0.0.1:0", pserver.BlessingStore().Default())
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer ln.Close()
+
+	// Server will just listen for flows and close them.
+	go acceptLoop(ln)
+
+	cep, err := mocknet.RewriteEndpointProtocol(sep.String(), "dropData")
+	if err != nil {
+		t.Fatal(err)
+	}
+	_, err = client.Dial(cctx, cep)
+	if verror.ErrorID(err) != stream.ErrNetwork.ID {
+		t.Errorf("wrong error: %v", err)
+	}
+	t.Log(err)
+}
diff --git a/runtime/internal/rpc/stream/manager/listener.go b/runtime/internal/rpc/stream/manager/listener.go
new file mode 100644
index 0000000..a7afc98
--- /dev/null
+++ b/runtime/internal/rpc/stream/manager/listener.go
@@ -0,0 +1,455 @@
+// 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 manager
+
+import (
+	"fmt"
+	"math/rand"
+	"net"
+	"strings"
+	"sync"
+	"syscall"
+	"time"
+
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/security"
+	"v.io/v23/verror"
+	"v.io/v23/vom"
+
+	"v.io/x/ref/runtime/internal/lib/upcqueue"
+	inaming "v.io/x/ref/runtime/internal/naming"
+	"v.io/x/ref/runtime/internal/rpc/stream"
+	"v.io/x/ref/runtime/internal/rpc/stream/proxy"
+	"v.io/x/ref/runtime/internal/rpc/stream/vc"
+	"v.io/x/ref/runtime/internal/rpc/stream/vif"
+)
+
+// ProxyAuthenticator is a stream.ListenerOpt that is used when listening via a
+// proxy to authenticate with the proxy.
+type ProxyAuthenticator interface {
+	stream.ListenerOpt
+	// Login returns the Blessings (and the set of Discharges to make them
+	// valid) to send to the proxy. Typically, the proxy uses these to
+	// determine whether it wants to authorize use.
+	Login(proxy stream.Flow) (security.Blessings, []security.Discharge, error)
+}
+
+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.
+	errVomEncodeRequest           = reg(".errVomEncodeRequest", "failed to encode request to proxy{:3}")
+	errVomDecodeResponse          = reg(".errVomDecodeRequest", "failed to decoded response from proxy{:3}")
+	errProxyError                 = reg(".errProxyError", "proxy error {:3}")
+	errProxyEndpointError         = reg(".errProxyEndpointError", "proxy returned an invalid endpoint {:3}{:4}")
+	errAlreadyConnected           = reg(".errAlreadyConnected", "already connected to proxy and accepting connections? VIF: {3}, StartAccepting{:_}")
+	errFailedToCreateLivenessFlow = reg(".errFailedToCreateLivenessFlow", "unable to create liveness check flow to proxy{:3}")
+	errAcceptFailed               = reg(".errAcceptFailed", "accept failed{:3}")
+	errFailedToEstablishVC        = reg(".errFailedToEstablishVC", "VC establishment with proxy failed{:_}")
+	errListenerAlreadyClosed      = reg(".errListenerAlreadyClosed", "listener already closed")
+	errRefusedProxyLogin          = reg(".errRefusedProxyLogin", "server did not want to listen via proxy{:_}")
+)
+
+// listener extends stream.Listener with a DebugString method.
+type listener interface {
+	stream.Listener
+	DebugString() string
+}
+
+// netListener implements the listener interface by accepting flows (and VCs)
+// over network connections accepted on an underlying net.Listener.
+type netListener struct {
+	q       *upcqueue.T
+	netLn   net.Listener
+	manager *manager
+	ctx     *context.T
+
+	connsMu sync.Mutex
+	conns   map[net.Conn]bool
+	vifs    *vif.Set
+
+	netLoop  sync.WaitGroup
+	vifLoops sync.WaitGroup
+}
+
+var _ stream.Listener = (*netListener)(nil)
+
+// proxyListener implements the listener interface by connecting to a remote
+// proxy (typically used to "listen" across network domains).
+type proxyListener struct {
+	q       *upcqueue.T
+	proxyEP naming.Endpoint
+	manager *manager
+	vif     *vif.VIF
+	ctx     *context.T
+
+	vifLoop sync.WaitGroup
+}
+
+var _ stream.Listener = (*proxyListener)(nil)
+
+func newNetListener(ctx *context.T, m *manager, netLn net.Listener, blessings security.Blessings, opts []stream.ListenerOpt) listener {
+	ln := &netListener{
+		q:       upcqueue.New(),
+		manager: m,
+		netLn:   netLn,
+		vifs:    vif.NewSet(),
+		conns:   make(map[net.Conn]bool),
+		ctx:     ctx,
+	}
+
+	// Set the default idle timeout for VC. But for "unixfd", we do not set
+	// the idle timeout since we cannot reconnect it.
+	if ln.netLn.Addr().Network() != "unixfd" {
+		opts = append([]stream.ListenerOpt{vc.IdleTimeout{Duration: defaultIdleTimeout}}, opts...)
+	}
+
+	ln.netLoop.Add(1)
+	go ln.netAcceptLoop(blessings, opts)
+	return ln
+}
+
+func isTemporaryError(err error) bool {
+	if oErr, ok := err.(*net.OpError); ok && oErr.Temporary() {
+		return true
+	}
+	return false
+}
+
+func isTooManyOpenFiles(err error) bool {
+	if oErr, ok := err.(*net.OpError); ok && strings.Contains(oErr.Err.Error(), syscall.EMFILE.Error()) {
+		return true
+	}
+	return false
+}
+
+func (ln *netListener) killConnections(n int) {
+	ln.connsMu.Lock()
+	if n > len(ln.conns) {
+		n = len(ln.conns)
+	}
+	remaining := make([]net.Conn, 0, len(ln.conns))
+	for c := range ln.conns {
+		remaining = append(remaining, c)
+	}
+	removed := remaining[:n]
+	ln.connsMu.Unlock()
+
+	ln.ctx.Infof("Killing %d Conns", n)
+
+	var wg sync.WaitGroup
+	wg.Add(n)
+	for i := 0; i < n; i++ {
+		idx := rand.Intn(len(remaining))
+		conn := remaining[idx]
+		go func(conn net.Conn) {
+			ln.ctx.Infof("Killing connection (%s, %s)", conn.LocalAddr(), conn.RemoteAddr())
+			conn.Close()
+			ln.manager.killedConns.Incr(1)
+			wg.Done()
+		}(conn)
+		remaining[idx], remaining[0] = remaining[0], remaining[idx]
+		remaining = remaining[1:]
+	}
+
+	ln.connsMu.Lock()
+	for _, conn := range removed {
+		delete(ln.conns, conn)
+	}
+	ln.connsMu.Unlock()
+
+	wg.Wait()
+}
+
+func (ln *netListener) netAcceptLoop(blessings security.Blessings, opts []stream.ListenerOpt) {
+	defer ln.netLoop.Done()
+	opts = append([]stream.ListenerOpt{vc.StartTimeout{Duration: defaultStartTimeout}}, opts...)
+	for {
+		conn, err := ln.netLn.Accept()
+		if isTemporaryError(err) {
+			// Use Info instead of Error to reduce the changes that
+			// the log library will cause the process to abort on
+			// failing to create a new file.
+			ln.ctx.Infof("net.Listener.Accept() failed on %v with %v", ln.netLn, err)
+			for tokill := 1; isTemporaryError(err); tokill *= 2 {
+				if isTooManyOpenFiles(err) {
+					ln.killConnections(tokill)
+				} else {
+					tokill = 1
+				}
+				time.Sleep(10 * time.Millisecond)
+				conn, err = ln.netLn.Accept()
+			}
+		}
+		if err != nil {
+			// TODO(cnicolaou): closeListener in manager.go writes to ln (by calling
+			// ln.Close()) and we read it here in the Infof output, so there is
+			// an unguarded read here that will fail under --race. This will only show
+			// itself if the Infof below is changed to always be printed (which is
+			// how I noticed). The right solution is to lock these datastructures, but
+			// that can wait until a bigger overhaul occurs. For now, we leave this at
+			// VI(1) knowing that it's basically harmless.
+			ln.ctx.VI(1).Infof("Exiting netAcceptLoop: net.Listener.Accept() failed on %v with %v", ln.netLn, err)
+			return
+		}
+		ln.connsMu.Lock()
+		ln.conns[conn] = true
+		ln.connsMu.Unlock()
+
+		ln.ctx.VI(1).Infof("New net.Conn accepted from %s (local address: %s)", conn.RemoteAddr(), conn.LocalAddr())
+
+		ln.vifLoops.Add(1)
+		go func() {
+			vf, err := vif.InternalNewAcceptedVIF(ln.ctx, conn, ln.manager.rid, blessings, nil, ln.deleteVIF, opts...)
+			if err != nil {
+				ln.ctx.Infof("Shutting down conn from %s (local address: %s) as a VIF could not be created: %v", conn.RemoteAddr(), conn.LocalAddr(), err)
+				conn.Close()
+				ln.vifLoops.Done()
+				return
+			}
+			ln.connsMu.Lock()
+			if ln.vifs == nil {
+				ln.connsMu.Unlock()
+				vf.Close()
+				ln.vifLoops.Done()
+				return
+			}
+			ln.vifs.Insert(vf, conn.RemoteAddr().Network(), conn.RemoteAddr().String())
+			ln.connsMu.Unlock()
+			ln.manager.vifs.Insert(vf, conn.RemoteAddr().Network(), conn.RemoteAddr().String())
+			vifLoop(ln.ctx, vf, ln.q, func() {
+				ln.connsMu.Lock()
+				delete(ln.conns, conn)
+				ln.connsMu.Unlock()
+				ln.vifLoops.Done()
+			})
+		}()
+	}
+}
+
+func (ln *netListener) deleteVIF(vf *vif.VIF) {
+	ln.ctx.VI(2).Infof("VIF %v is closed, removing from cache", vf)
+	ln.connsMu.Lock()
+	if ln.vifs != nil {
+		ln.vifs.Delete(vf)
+	}
+	ln.connsMu.Unlock()
+	ln.manager.vifs.Delete(vf)
+}
+
+func (ln *netListener) Accept() (stream.Flow, error) {
+	item, err := ln.q.Get(nil)
+	switch {
+	case err == upcqueue.ErrQueueIsClosed:
+		return nil, verror.New(stream.ErrNetwork, nil, verror.New(errListenerAlreadyClosed, nil))
+	case err != nil:
+		return nil, verror.New(stream.ErrNetwork, nil, verror.New(errAcceptFailed, nil, err))
+	default:
+		return item.(vif.ConnectorAndFlow).Flow, nil
+	}
+}
+
+func (ln *netListener) Close() error {
+	closeNetListener(ln.ctx, ln.netLn)
+	ln.netLoop.Wait()
+
+	ln.connsMu.Lock()
+	var vfs []*vif.VIF
+	if ln.vifs != nil {
+		vfs, ln.vifs = ln.vifs.List(), nil
+	}
+	ln.connsMu.Unlock()
+
+	for _, vf := range vfs {
+		// NOTE(caprita): We do not actually Close down the vifs, as
+		// that would require knowing when all outstanding requests are
+		// finished.  For now, do not worry about it, since we expect
+		// shut down to immediately precede process exit.
+		//v23.Logger().Infof("Close: stop accepting: %p", vif)
+		vf.StopAccepting()
+	}
+	ln.q.Shutdown()
+	ln.manager.removeListener(ln)
+	ln.vifLoops.Wait()
+	return nil
+}
+
+func (ln *netListener) String() string {
+	return fmt.Sprintf("%T: (%v, %v)", ln, ln.netLn.Addr().Network(), ln.netLn.Addr())
+}
+
+func (ln *netListener) DebugString() string {
+	ret := []string{
+		fmt.Sprintf("stream.Listener: net.Listener on (%q, %q)", ln.netLn.Addr().Network(), ln.netLn.Addr()),
+	}
+	ln.connsMu.Lock()
+	var vifs []*vif.VIF
+	if ln.vifs != nil {
+		vifs, ln.vifs = ln.vifs.List(), nil
+	}
+	ln.connsMu.Unlock()
+	if len(vifs) > 0 {
+		ret = append(ret, fmt.Sprintf("===Accepted VIFs(%d)===", len(vifs)))
+		for ix, vif := range vifs {
+			ret = append(ret, fmt.Sprintf("%4d) %v", ix, vif))
+		}
+	}
+	return strings.Join(ret, "\n")
+}
+
+func newProxyListener(ctx *context.T, m *manager, proxyEP naming.Endpoint, opts []stream.ListenerOpt) (listener, *inaming.Endpoint, error) {
+	ln := &proxyListener{
+		q:       upcqueue.New(),
+		proxyEP: proxyEP,
+		manager: m,
+		ctx:     ctx,
+	}
+	vf, ep, err := ln.connect(opts)
+	if err != nil {
+		return nil, nil, err
+	}
+	ln.vif = vf
+	ln.vifLoop.Add(1)
+	go vifLoop(ctx, ln.vif, ln.q, func() {
+		ln.vifLoop.Done()
+	})
+	return ln, ep, nil
+}
+
+func (ln *proxyListener) connect(opts []stream.ListenerOpt) (*vif.VIF, *inaming.Endpoint, error) {
+	ln.ctx.VI(1).Infof("Connecting to proxy at %v", ln.proxyEP)
+	// Requires dialing a VC to the proxy, need to extract options from ln.opts to do so.
+	var dialOpts []stream.VCOpt
+	var auth ProxyAuthenticator
+	for _, opt := range opts {
+		switch v := opt.(type) {
+		case stream.VCOpt:
+			dialOpts = append(dialOpts, v)
+		case ProxyAuthenticator:
+			auth = v
+		}
+	}
+	// TODO(cnicolaou, ashankar): probably want to set a timeout here. (is
+	// this covered by opts?)
+	// TODO(ashankar): Authorize the proxy server as well (similar to how
+	// clients authorize servers in RPCs).
+	vf, err := ln.manager.FindOrDialVIF(ln.ctx, ln.proxyEP, dialOpts...)
+	if err != nil {
+		return nil, nil, err
+	}
+	// Prepend the default idle timeout for VC.
+	opts = append([]stream.ListenerOpt{vc.IdleTimeout{Duration: defaultIdleTimeout}}, opts...)
+	if err := vf.StartAccepting(opts...); err != nil {
+		return nil, nil, verror.New(stream.ErrNetwork, nil, verror.New(errAlreadyConnected, nil, vf, err))
+	}
+	// Proxy protocol: See v.io/x/ref/runtime/internal/rpc/stream/proxy/protocol.vdl
+	//
+	// We don't need idle timeout for this VC, since one flow will be kept alive.
+	vc, err := vf.Dial(ln.ctx, ln.proxyEP, dialOpts...)
+	if err != nil {
+		vf.StopAccepting()
+		if verror.ErrorID(err) == verror.ErrAborted.ID {
+			ln.manager.vifs.Delete(vf)
+			return nil, nil, verror.New(stream.ErrAborted, nil, err)
+		}
+		return nil, nil, err
+	}
+	flow, err := vc.Connect()
+	if err != nil {
+		vf.StopAccepting()
+		return nil, nil, verror.New(stream.ErrNetwork, nil, verror.New(errFailedToCreateLivenessFlow, nil, err))
+	}
+	var request proxy.Request
+	var response proxy.Response
+	if auth != nil {
+		if request.Blessings, request.Discharges, err = auth.Login(flow); err != nil {
+			vf.StopAccepting()
+			return nil, nil, verror.New(stream.ErrSecurity, nil, verror.New(errRefusedProxyLogin, nil, err))
+		}
+	}
+	enc := vom.NewEncoder(flow)
+	if err := enc.Encode(request); err != nil {
+		flow.Close()
+		vf.StopAccepting()
+		return nil, nil, verror.New(stream.ErrNetwork, nil, verror.New(errVomEncodeRequest, nil, err))
+	}
+	dec := vom.NewDecoder(flow)
+	if err := dec.Decode(&response); err != nil {
+		flow.Close()
+		vf.StopAccepting()
+		return nil, nil, verror.New(stream.ErrNetwork, nil, verror.New(errVomDecodeResponse, nil, err))
+	}
+	if response.Error != nil {
+		flow.Close()
+		vf.StopAccepting()
+		return nil, nil, verror.New(stream.ErrProxy, nil, response.Error)
+	}
+	ep, err := inaming.NewEndpoint(response.Endpoint)
+	if err != nil {
+		flow.Close()
+		vf.StopAccepting()
+		return nil, nil, verror.New(stream.ErrProxy, nil, verror.New(errProxyEndpointError, nil, response.Endpoint, err))
+	}
+	go func(vf *vif.VIF, flow stream.Flow, q *upcqueue.T) {
+		<-flow.Closed()
+		vf.StopAccepting()
+		q.Close()
+	}(vf, flow, ln.q)
+	return vf, ep, nil
+}
+
+func (ln *proxyListener) Accept() (stream.Flow, error) {
+	item, err := ln.q.Get(nil)
+	switch {
+	case err == upcqueue.ErrQueueIsClosed:
+		return nil, verror.New(stream.ErrNetwork, nil, verror.New(errListenerAlreadyClosed, nil))
+	case err != nil:
+		return nil, verror.New(stream.ErrNetwork, nil, verror.New(errAcceptFailed, nil, err))
+	default:
+		return item.(vif.ConnectorAndFlow).Flow, nil
+	}
+}
+
+func (ln *proxyListener) Close() error {
+	ln.vif.StopAccepting()
+	ln.q.Shutdown()
+	ln.manager.removeListener(ln)
+	ln.vifLoop.Wait()
+	ln.ctx.VI(3).Infof("Closed stream.Listener %s", ln)
+	return nil
+}
+
+func (ln *proxyListener) String() string {
+	return ln.DebugString()
+}
+
+func (ln *proxyListener) DebugString() string {
+	return fmt.Sprintf("stream.Listener: PROXY:%v RoutingID:%v", ln.proxyEP, ln.manager.rid)
+}
+
+func vifLoop(ctx *context.T, vf *vif.VIF, q *upcqueue.T, cleanup func()) {
+	defer cleanup()
+	for {
+		cAndf, err := vf.Accept()
+		switch {
+		case err != nil:
+			ctx.VI(2).Infof("Shutting down listener on VIF %v: %v", vf, err)
+			return
+		case cAndf.Flow == nil:
+			ctx.VI(1).Infof("New VC %v on VIF %v", cAndf.Connector, vf)
+		default:
+			if err := q.Put(cAndf); err != nil {
+				ctx.VI(1).Infof("Closing new flow on VC %v (VIF %v) as Put failed in vifLoop: %v", cAndf.Connector, vf, err)
+				cAndf.Flow.Close()
+			}
+		}
+	}
+}
diff --git a/runtime/internal/rpc/stream/manager/manager.go b/runtime/internal/rpc/stream/manager/manager.go
new file mode 100644
index 0000000..4ddef8c
--- /dev/null
+++ b/runtime/internal/rpc/stream/manager/manager.go
@@ -0,0 +1,375 @@
+// 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 manager provides an implementation of the Manager interface defined in v.io/x/ref/runtime/internal/rpc/stream.
+package manager
+
+import (
+	"fmt"
+	"net"
+	"strings"
+	"sync"
+	"time"
+
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+
+	"v.io/v23/security"
+	"v.io/v23/verror"
+
+	"v.io/x/ref/lib/apilog"
+	"v.io/x/ref/lib/stats"
+	"v.io/x/ref/lib/stats/counter"
+	inaming "v.io/x/ref/runtime/internal/naming"
+	"v.io/x/ref/runtime/internal/rpc/stream"
+	"v.io/x/ref/runtime/internal/rpc/stream/vc"
+	"v.io/x/ref/runtime/internal/rpc/stream/vif"
+)
+
+const pkgPath = "v.io/x/ref/runtime/internal/rpc/stream/manager"
+
+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.
+	errUnknownNetwork                          = reg(".errUnknownNetwork", "unknown network{:3}")
+	errEndpointParseError                      = reg(".errEndpointParseError", "failed to parse endpoint {3}{:4}")
+	errAlreadyShutdown                         = reg(".errAlreadyShutdown", "already shutdown")
+	errProvidedServerBlessingsWithoutPrincipal = reg(".errServerBlessingsWithoutPrincipal", "blessings provided but with no principal")
+	errNoBlessingNames                         = reg(".errNoBlessingNames", "no blessing names could be extracted for the provided principal")
+)
+
+const (
+	// The default time after which an VIF is closed if no VC is opened.
+	defaultStartTimeout = 3 * time.Second
+	// The default time after which an idle VC is closed.
+	defaultIdleTimeout = 30 * time.Second
+)
+
+// InternalNew creates a new stream.Manager for managing streams where the local
+// process is identified by the provided RoutingID.
+//
+// As the name suggests, this method is intended for use only within packages
+// placed inside v.io/x/ref/runtime/internal. Code outside the
+// v.io/x/ref/runtime/internal/* packages should never call this method.
+func InternalNew(ctx *context.T, rid naming.RoutingID) stream.Manager {
+	statsPrefix := naming.Join("rpc", "stream", "routing-id", rid.String())
+	m := &manager{
+		ctx:         ctx,
+		rid:         rid,
+		vifs:        vif.NewSet(),
+		listeners:   make(map[listener]bool),
+		statsPrefix: statsPrefix,
+		killedConns: stats.NewCounter(naming.Join(statsPrefix, "killed-connections")),
+	}
+	stats.NewStringFunc(naming.Join(m.statsPrefix, "debug"), m.DebugString)
+	return m
+}
+
+type manager struct {
+	ctx  *context.T
+	rid  naming.RoutingID
+	vifs *vif.Set
+
+	muListeners sync.Mutex
+	listeners   map[listener]bool // GUARDED_BY(muListeners)
+	shutdown    bool              // GUARDED_BY(muListeners)
+
+	statsPrefix string
+	killedConns *counter.Counter
+}
+
+var _ stream.Manager = (*manager)(nil)
+
+type DialTimeout time.Duration
+
+func (DialTimeout) RPCStreamVCOpt() {}
+func (DialTimeout) RPCClientOpt() {
+	defer apilog.LogCall(nil)(nil) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+}
+
+func dial(ctx *context.T, d rpc.DialerFunc, network, address string, timeout time.Duration) (net.Conn, error) {
+	if d != nil {
+		conn, err := d(ctx, network, address, timeout)
+		if err != nil {
+			return nil, verror.New(stream.ErrDialFailed, ctx, err)
+		}
+		return conn, nil
+	}
+	return nil, verror.New(stream.ErrDialFailed, ctx, verror.New(errUnknownNetwork, ctx, network))
+}
+
+func resolve(ctx *context.T, r rpc.ResolverFunc, network, address string) (string, string, error) {
+	if r != nil {
+		net, addr, err := r(ctx, network, address)
+		if err != nil {
+			return "", "", verror.New(stream.ErrResolveFailed, ctx, err)
+		}
+		return net, addr, nil
+	}
+	return "", "", verror.New(stream.ErrResolveFailed, ctx, verror.New(errUnknownNetwork, ctx, network))
+}
+
+type dialResult struct {
+	conn net.Conn
+	err  error
+}
+
+// FindOrDialVIF returns the network connection (VIF) to the provided address
+// from the cache in the manager. If not already present in the cache, a new
+// connection will be created using net.Dial.
+func (m *manager) FindOrDialVIF(ctx *context.T, remote naming.Endpoint, opts ...stream.VCOpt) (*vif.VIF, error) {
+	// Extract options.
+	var timeout time.Duration
+	for _, o := range opts {
+		switch v := o.(type) {
+		case DialTimeout:
+			timeout = time.Duration(v)
+		}
+	}
+	addr := remote.Addr()
+	d, r, _, _ := rpc.RegisteredProtocol(addr.Network())
+	// (network, address) in the endpoint might not always match up
+	// with the key used in the vifs. For example:
+	// - conn, err := net.Dial("tcp", "www.google.com:80")
+	//   fmt.Println(conn.RemoteAddr()) // Might yield the corresponding IP address
+	// - Similarly, an unspecified IP address (net.IP.IsUnspecified) like "[::]:80"
+	//   might yield "[::1]:80" (loopback interface) in conn.RemoteAddr().
+	// Thus, look for VIFs with the resolved address.
+	network, address, err := resolve(ctx, r, addr.Network(), addr.String())
+	if err != nil {
+		return nil, err
+	}
+	vf, unblock := m.vifs.BlockingFind(network, address)
+	if vf != nil {
+		ctx.VI(1).Infof("(%q, %q) resolved to (%q, %q) which exists in the VIF cache.", addr.Network(), addr.String(), network, address)
+		return vf, nil
+	}
+	defer unblock()
+
+	ctx.VI(1).Infof("(%q, %q) not in VIF cache. Dialing", network, address)
+
+	ch := make(chan *dialResult)
+	go func() {
+		conn, err := dial(ctx, d, network, address, timeout)
+		ch <- &dialResult{conn, err}
+	}()
+
+	var conn net.Conn
+	select {
+	case result := <-ch:
+		conn, err = result.conn, result.err
+	case <-ctx.Done():
+		return nil, verror.New(stream.ErrDialFailed, ctx, err)
+	}
+	if err != nil {
+		return nil, err
+	}
+
+	opts = append([]stream.VCOpt{vc.StartTimeout{Duration: defaultStartTimeout}}, opts...)
+	vf, err = vif.InternalNewDialedVIF(ctx, conn, m.rid, nil, m.deleteVIF, opts...)
+	if err != nil {
+		conn.Close()
+		return nil, err
+	}
+	m.vifs.Insert(vf, network, address)
+	return vf, nil
+}
+
+func (m *manager) Dial(ctx *context.T, remote naming.Endpoint, opts ...stream.VCOpt) (stream.VC, error) {
+	// If vif.Dial fails because the cached network connection was broken, remove from
+	// the cache and try once more.
+	for retry := true; true; retry = false {
+		vf, err := m.FindOrDialVIF(ctx, remote, opts...)
+		if err != nil {
+			return nil, err
+		}
+		opts = append([]stream.VCOpt{vc.IdleTimeout{Duration: defaultIdleTimeout}}, opts...)
+		vc, err := vf.Dial(ctx, remote, opts...)
+		if !retry || verror.ErrorID(err) != stream.ErrAborted.ID {
+			return vc, err
+		}
+		vf.Close()
+	}
+	return nil, verror.NewErrInternal(nil) // Not reached
+}
+
+func (m *manager) deleteVIF(vf *vif.VIF) {
+	m.ctx.VI(2).Infof("%p: VIF %v is closed, removing from cache", m, vf)
+	m.vifs.Delete(vf)
+}
+
+func listen(ctx *context.T, protocol, address string) (net.Listener, error) {
+	if _, _, l, _ := rpc.RegisteredProtocol(protocol); l != nil {
+		ln, err := l(ctx, protocol, address)
+		if err != nil {
+			return nil, verror.New(stream.ErrNetwork, ctx, err)
+		}
+		return ln, nil
+	}
+	return nil, verror.New(stream.ErrBadArg, ctx, verror.New(errUnknownNetwork, ctx, protocol))
+}
+
+func (m *manager) Listen(ctx *context.T, protocol, address string, blessings security.Blessings, opts ...stream.ListenerOpt) (stream.Listener, naming.Endpoint, error) {
+	principal := stream.GetPrincipalListenerOpts(ctx, opts...)
+	bNames, err := extractBlessingNames(principal, blessings)
+	if err != nil {
+		return nil, nil, err
+	}
+	ln, ep, err := m.internalListen(ctx, protocol, address, blessings, opts...)
+	if err != nil {
+		return nil, nil, err
+	}
+	ep.Blessings = bNames
+	return ln, ep, nil
+}
+
+func (m *manager) internalListen(ctx *context.T, protocol, address string, blessings security.Blessings, opts ...stream.ListenerOpt) (stream.Listener, *inaming.Endpoint, error) {
+	m.muListeners.Lock()
+	if m.shutdown {
+		m.muListeners.Unlock()
+		return nil, nil, verror.New(stream.ErrBadState, nil, verror.New(errAlreadyShutdown, nil))
+	}
+	m.muListeners.Unlock()
+
+	if protocol == inaming.Network {
+		// Act as if listening on the address of a remote proxy.
+		ep, err := inaming.NewEndpoint(address)
+		if err != nil {
+			return nil, nil, verror.New(stream.ErrBadArg, nil, verror.New(errEndpointParseError, nil, address, err))
+		}
+		return m.remoteListen(ctx, ep, opts)
+	}
+	netln, err := listen(ctx, protocol, address)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	m.muListeners.Lock()
+	if m.shutdown {
+		m.muListeners.Unlock()
+		closeNetListener(ctx, netln)
+		return nil, nil, verror.New(stream.ErrBadState, nil, verror.New(errAlreadyShutdown, nil))
+	}
+
+	ln := newNetListener(ctx, m, netln, blessings, opts)
+	m.listeners[ln] = true
+	m.muListeners.Unlock()
+	ep := &inaming.Endpoint{
+		Protocol: protocol,
+		Address:  netln.Addr().String(),
+		RID:      m.rid,
+	}
+	return ln, ep, nil
+}
+
+func (m *manager) remoteListen(ctx *context.T, proxy naming.Endpoint, listenerOpts []stream.ListenerOpt) (stream.Listener, *inaming.Endpoint, error) {
+	ln, ep, err := newProxyListener(ctx, m, proxy, listenerOpts)
+	if err != nil {
+		return nil, nil, err
+	}
+	m.muListeners.Lock()
+	defer m.muListeners.Unlock()
+	if m.shutdown {
+		ln.Close()
+		return nil, nil, verror.New(stream.ErrBadState, nil, verror.New(errAlreadyShutdown, nil))
+	}
+	m.listeners[ln] = true
+	return ln, ep, nil
+}
+
+func (m *manager) ShutdownEndpoint(remote naming.Endpoint) {
+	vifs := m.vifs.List()
+	total := 0
+	for _, vf := range vifs {
+		total += vf.ShutdownVCs(remote)
+	}
+	m.ctx.VI(1).Infof("ShutdownEndpoint(%q) closed %d VCs", remote, total)
+}
+
+func closeNetListener(ctx *context.T, ln net.Listener) {
+	addr := ln.Addr()
+	err := ln.Close()
+	ctx.VI(1).Infof("Closed net.Listener on (%q, %q): %v", addr.Network(), addr, err)
+}
+
+func (m *manager) removeListener(ln listener) {
+	m.muListeners.Lock()
+	delete(m.listeners, ln)
+	m.muListeners.Unlock()
+}
+
+func (m *manager) Shutdown() {
+	stats.Delete(m.statsPrefix)
+	m.muListeners.Lock()
+	if m.shutdown {
+		m.muListeners.Unlock()
+		return
+	}
+	m.shutdown = true
+	var wg sync.WaitGroup
+	wg.Add(len(m.listeners))
+	for ln, _ := range m.listeners {
+		go func(ln stream.Listener) {
+			ln.Close()
+			wg.Done()
+		}(ln)
+	}
+	m.listeners = make(map[listener]bool)
+	m.muListeners.Unlock()
+	wg.Wait()
+
+	vifs := m.vifs.List()
+	for _, vf := range vifs {
+		vf.Close()
+	}
+}
+
+func (m *manager) RoutingID() naming.RoutingID {
+	return m.rid
+}
+
+func (m *manager) DebugString() string {
+	vifs := m.vifs.List()
+
+	m.muListeners.Lock()
+	defer m.muListeners.Unlock()
+
+	l := make([]string, 0)
+	l = append(l, fmt.Sprintf("Manager: RoutingID:%v #VIFs:%d #Listeners:%d Shutdown:%t", m.rid, len(vifs), len(m.listeners), m.shutdown))
+	if len(vifs) > 0 {
+		l = append(l, "============================VIFs================================================")
+		for ix, vif := range vifs {
+			l = append(l, fmt.Sprintf("%4d) %v", ix, vif.DebugString()))
+			l = append(l, "--------------------------------------------------------------------------------")
+		}
+	}
+	if len(m.listeners) > 0 {
+		l = append(l, "=======================================Listeners==================================================")
+		l = append(l, "  (stream listeners, their local network listeners (missing for proxied listeners), and VIFS")
+		for ln, _ := range m.listeners {
+			l = append(l, ln.DebugString())
+		}
+	}
+	return strings.Join(l, "\n")
+}
+
+func extractBlessingNames(p security.Principal, b security.Blessings) ([]string, error) {
+	if !b.IsZero() && p == nil {
+		return nil, verror.New(stream.ErrBadArg, nil, verror.New(errProvidedServerBlessingsWithoutPrincipal, nil))
+	}
+	if p == nil {
+		return nil, nil
+	}
+	var ret []string
+	for b, _ := range p.BlessingsInfo(b) {
+		ret = append(ret, b)
+	}
+	if len(ret) == 0 {
+		return nil, verror.New(stream.ErrBadArg, nil, verror.New(errNoBlessingNames, nil))
+	}
+	return ret, nil
+}
diff --git a/runtime/internal/rpc/stream/manager/manager_test.go b/runtime/internal/rpc/stream/manager/manager_test.go
new file mode 100644
index 0000000..12f7bd5
--- /dev/null
+++ b/runtime/internal/rpc/stream/manager/manager_test.go
@@ -0,0 +1,1071 @@
+// 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 manager
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"net"
+	"os"
+	"reflect"
+	"runtime"
+	"sort"
+	"strconv"
+	"strings"
+	"sync"
+	"syscall"
+	"testing"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+
+	inaming "v.io/x/ref/runtime/internal/naming"
+	_ "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/stream"
+	"v.io/x/ref/runtime/internal/rpc/stream/vc"
+	"v.io/x/ref/runtime/internal/rpc/stream/vif"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/expect"
+	"v.io/x/ref/test/modules"
+	"v.io/x/ref/test/testutil"
+)
+
+// We write our own TestMain here instead of relying on v23 test generate because
+// we need to set runtime.GOMAXPROCS.
+func TestMain(m *testing.M) {
+	test.Init()
+
+	// testutil.Init sets GOMAXPROCS to NumCPU.  We want to force
+	// GOMAXPFDROCS to remain at 1, in order to trigger a particular race
+	// condition that occurs when closing the server; also, using 1 cpu
+	// introduces less variance in the behavior of the test.
+	runtime.GOMAXPROCS(1)
+	modules.DispatchAndExitIfChild()
+	os.Exit(m.Run())
+}
+
+func testSimpleFlow(t *testing.T, protocol string) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	server := InternalNew(ctx, naming.FixedRoutingID(0x55555555))
+	client := InternalNew(ctx, naming.FixedRoutingID(0xcccccccc))
+	pclient := testutil.NewPrincipal("client")
+	pclient2 := testutil.NewPrincipal("client2")
+	pserver := testutil.NewPrincipal("server")
+	ctx, _ = v23.WithPrincipal(ctx, pserver)
+
+	ln, ep, err := server.Listen(ctx, protocol, "127.0.0.1:0", pserver.BlessingStore().Default())
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	data := "the dark knight rises"
+	var clientVC stream.VC
+	var clientF1 stream.Flow
+	go func() {
+		var err error
+		cctx, _ := v23.WithPrincipal(ctx, pclient)
+		if clientVC, err = client.Dial(cctx, ep); err != nil {
+			t.Errorf("Dial(%q) failed: %v", ep, err)
+			return
+		}
+		if clientF1, err = clientVC.Connect(); err != nil {
+			t.Errorf("Connect() failed: %v", err)
+			return
+		}
+		if err := writeLine(clientF1, data); err != nil {
+			t.Error(err)
+		}
+	}()
+	serverF, err := ln.Accept()
+	if err != nil {
+		t.Fatalf("Accept failed: %v", err)
+	}
+	if got, err := readLine(serverF); got != data || err != nil {
+		t.Errorf("Got (%q, %v), want (%q, nil)", got, err, data)
+	}
+	// By this point, the goroutine has passed the write call (or exited
+	// early) since the read has gotten through.  Check if the goroutine
+	// encountered any errors in creating the VC or flow and abort.
+	if t.Failed() {
+		return
+	}
+	defer clientF1.Close()
+
+	ln.Close()
+
+	// Writes on flows opened before the server listener was closed should
+	// still succeed.
+	data = "the dark knight goes to bed"
+	go func() {
+		if err := writeLine(clientF1, data); err != nil {
+			t.Error(err)
+		}
+	}()
+	if got, err := readLine(serverF); got != data || err != nil {
+		t.Errorf("Got (%q, %v), want (%q, nil)", got, err, data)
+	}
+
+	// Opening a new flow on an existing VC will succeed initially, but
+	// writes on the client end will eventually fail once the server has
+	// stopped listening.
+	//
+	// It will require a round-trip to the server to notice the failure,
+	// hence the client should write enough data to ensure that the Write
+	// call will not return before a round-trip.
+	//
+	// The length of the data is taken to exceed the queue buffer size
+	// (DefaultBytesBufferedPerFlow), the shared counters (MaxSharedBytes)
+	// and the per-flow counters (DefaultBytesBufferedPerFlow) that are
+	// given when the flow gets established.
+	//
+	// TODO(caprita): separate the constants for the queue buffer size and
+	// the default number of counters to avoid confusion.
+	lotsOfData := string(make([]byte, vc.DefaultBytesBufferedPerFlow*2+vc.MaxSharedBytes+1))
+	clientF2, err := clientVC.Connect()
+	if err != nil {
+		t.Fatalf("Connect() failed: %v", err)
+	}
+	defer clientF2.Close()
+	if err := writeLine(clientF2, lotsOfData); err == nil {
+		t.Errorf("Should not be able to Dial or Write after the Listener is closed")
+	}
+	// Opening a new VC should fail fast. Note that we need to use a different
+	// principal since the client doesn't expect a response from a server
+	// when re-using VIF authentication.
+	cctx, _ := v23.WithPrincipal(ctx, pclient2)
+	if _, err := client.Dial(cctx, ep); err == nil {
+		t.Errorf("Should not be able to Dial after listener is closed")
+	}
+}
+
+func TestSimpleFlow(t *testing.T) {
+	testSimpleFlow(t, "tcp")
+}
+
+func TestSimpleFlowWS(t *testing.T) {
+	testSimpleFlow(t, "ws")
+}
+
+func TestConnectionTimeout(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	client := InternalNew(ctx, naming.FixedRoutingID(0xcccccccc))
+
+	ch := make(chan error)
+	go func() {
+		// 203.0.113.0 is TEST-NET-3 from RFC5737
+		ep, _ := inaming.NewEndpoint(naming.FormatEndpoint("tcp", "203.0.113.10:80"))
+		nctx, _ := v23.WithPrincipal(ctx, testutil.NewPrincipal("client"))
+		_, err := client.Dial(nctx, ep, DialTimeout(time.Second))
+		ch <- err
+	}()
+
+	select {
+	case err := <-ch:
+		if err == nil {
+			t.Fatalf("expected an error")
+		}
+	case <-time.After(time.Minute):
+		t.Fatalf("timedout")
+	}
+}
+
+func testAuthenticatedByDefault(t *testing.T, protocol string) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	var (
+		server = InternalNew(ctx, naming.FixedRoutingID(0x55555555))
+		client = InternalNew(ctx, naming.FixedRoutingID(0xcccccccc))
+
+		clientPrincipal = testutil.NewPrincipal("client")
+		serverPrincipal = testutil.NewPrincipal("server")
+		clientKey       = clientPrincipal.PublicKey()
+		serverBlessings = serverPrincipal.BlessingStore().Default()
+		cctx, _         = v23.WithPrincipal(ctx, clientPrincipal)
+		sctx, _         = v23.WithPrincipal(ctx, serverPrincipal)
+	)
+
+	ln, ep, err := server.Listen(sctx, protocol, "127.0.0.1:0", serverPrincipal.BlessingStore().Default())
+	if err != nil {
+		t.Fatal(err)
+	}
+	// And the server blessing should be in the endpoint.
+	if got, want := ep.BlessingNames(), []string{"server"}; !reflect.DeepEqual(got, want) {
+		t.Errorf("Got blessings %v from endpoint, want %v", got, want)
+	}
+
+	errs := make(chan error)
+
+	testAuth := func(tag string, flow stream.Flow, wantServer security.Blessings, wantClientKey security.PublicKey) {
+		// Since the client's blessing is expected to be self-signed we only test
+		// its public key
+		gotServer := flow.RemoteBlessings()
+		gotClientKey := flow.LocalBlessings().PublicKey()
+		if tag == "server" {
+			gotServer = flow.LocalBlessings()
+			gotClientKey = flow.RemoteBlessings().PublicKey()
+		}
+		if !reflect.DeepEqual(gotServer, wantServer) || !reflect.DeepEqual(gotClientKey, wantClientKey) {
+			errs <- fmt.Errorf("%s: Server: Got Blessings %q, want %q. Server: Got Blessings %q, want %q", tag, gotServer, wantServer, gotClientKey, wantClientKey)
+			return
+		}
+		errs <- nil
+	}
+
+	go func() {
+		flow, err := ln.Accept()
+		if err != nil {
+			errs <- err
+			return
+		}
+		defer flow.Close()
+		testAuth("server", flow, serverBlessings, clientKey)
+	}()
+
+	go func() {
+		vc, err := client.Dial(cctx, ep)
+		if err != nil {
+			errs <- err
+			return
+		}
+		flow, err := vc.Connect()
+		if err != nil {
+			errs <- err
+			return
+		}
+		defer flow.Close()
+		testAuth("client", flow, serverBlessings, clientKey)
+	}()
+
+	if err := <-errs; err != nil {
+		t.Error(err)
+	}
+	if err := <-errs; err != nil {
+		t.Error(err)
+	}
+}
+
+func TestAuthenticatedByDefault(t *testing.T) {
+	testAuthenticatedByDefault(t, "tcp")
+}
+
+func TestAuthenticatedByDefaultWS(t *testing.T) {
+	testAuthenticatedByDefault(t, "ws")
+}
+
+func numListeners(m stream.Manager) int   { return len(m.(*manager).listeners) }
+func debugString(m stream.Manager) string { return m.(*manager).DebugString() }
+func numVIFs(m stream.Manager) int        { return len(m.(*manager).vifs.List()) }
+
+func TestListenEndpoints(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	server := InternalNew(ctx, naming.FixedRoutingID(0xcafe))
+	principal := testutil.NewPrincipal("test")
+	ctx, _ = v23.WithPrincipal(ctx, principal)
+	blessings := principal.BlessingStore().Default()
+	ln1, ep1, err1 := server.Listen(ctx, "tcp", "127.0.0.1:0", blessings)
+	ln2, ep2, err2 := server.Listen(ctx, "tcp", "127.0.0.1:0", blessings)
+	// Since "127.0.0.1:0" was used as the network address, a random port will be
+	// assigned in each case. The endpoint should include that random port.
+	if err1 != nil {
+		t.Error(err1)
+	}
+	if err2 != nil {
+		t.Error(err2)
+	}
+	if ep1.String() == ep2.String() {
+		t.Errorf("Both listeners got the same endpoint: %q", ep1)
+	}
+	if n, expect := numListeners(server), 2; n != expect {
+		t.Errorf("expecting %d listeners, got %d for %s", n, expect, debugString(server))
+	}
+	ln1.Close()
+	if n, expect := numListeners(server), 1; n != expect {
+		t.Errorf("expecting %d listeners, got %d for %s", n, expect, debugString(server))
+	}
+	ln2.Close()
+	if n, expect := numListeners(server), 0; n != expect {
+		t.Errorf("expecting %d listeners, got %d for %s", n, expect, debugString(server))
+	}
+}
+
+func acceptLoop(wg *sync.WaitGroup, ln stream.Listener) {
+	for {
+		f, err := ln.Accept()
+		if err != nil {
+			break
+		}
+		f.Close()
+	}
+	wg.Done()
+}
+
+func TestCloseListener(t *testing.T) {
+	testCloseListener(t, "tcp")
+}
+
+func TestCloseListenerWS(t *testing.T) {
+	testCloseListener(t, "ws")
+}
+
+func testCloseListener(t *testing.T, protocol string) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	server := InternalNew(ctx, naming.FixedRoutingID(0x5e97e9))
+	pclient := testutil.NewPrincipal("client")
+	pserver := testutil.NewPrincipal("server")
+	cctx, _ := v23.WithPrincipal(ctx, pclient)
+	sctx, _ := v23.WithPrincipal(ctx, pserver)
+	blessings := pserver.BlessingStore().Default()
+	ln, ep, err := server.Listen(sctx, protocol, "127.0.0.1:0", blessings)
+	if err != nil {
+		t.Fatal(err)
+	}
+	var wg sync.WaitGroup
+	wg.Add(1)
+	// Server will just listen for flows and close them.
+	go acceptLoop(&wg, ln)
+	client := InternalNew(ctx, naming.FixedRoutingID(0xc1e41))
+	if _, err = client.Dial(cctx, ep); err != nil {
+		t.Fatal(err)
+	}
+	ln.Close()
+	client = InternalNew(ctx, naming.FixedRoutingID(0xc1e42))
+	if _, err := client.Dial(cctx, ep); err == nil {
+		t.Errorf("client.Dial(%q) should have failed", ep)
+	}
+	time.Sleep(time.Second)
+	wg.Wait()
+}
+
+func TestShutdown(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	server := InternalNew(ctx, naming.FixedRoutingID(0x5e97e9))
+	principal := testutil.NewPrincipal("test")
+	ctx, _ = v23.WithPrincipal(ctx, principal)
+	blessings := principal.BlessingStore().Default()
+	ln, _, err := server.Listen(ctx, "tcp", "127.0.0.1:0", blessings)
+	if err != nil {
+		t.Fatal(err)
+	}
+	var wg sync.WaitGroup
+	wg.Add(1)
+	// Server will just listen for flows and close them.
+	go acceptLoop(&wg, ln)
+	if n, expect := numListeners(server), 1; n != expect {
+		t.Errorf("expecting %d listeners, got %d for %s", n, expect, debugString(server))
+	}
+	server.Shutdown()
+	if _, _, err := server.Listen(ctx, "tcp", "127.0.0.1:0", blessings); err == nil {
+		t.Error("server should have shut down")
+	}
+	if n, expect := numListeners(server), 0; n != expect {
+		t.Errorf("expecting %d listeners, got %d for %s", n, expect, debugString(server))
+	}
+	wg.Wait()
+	fmt.Fprintf(os.Stderr, "DONE\n")
+}
+
+func TestShutdownEndpoint(t *testing.T) {
+	testShutdownEndpoint(t, "tcp")
+}
+
+func TestShutdownEndpointWS(t *testing.T) {
+	testShutdownEndpoint(t, "ws")
+}
+
+func testShutdownEndpoint(t *testing.T, protocol string) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	server := InternalNew(ctx, naming.FixedRoutingID(0x55555555))
+	client := InternalNew(ctx, naming.FixedRoutingID(0xcccccccc))
+	principal := testutil.NewPrincipal("test")
+	ctx, _ = v23.WithPrincipal(ctx, principal)
+
+	ln, ep, err := server.Listen(ctx, protocol, "127.0.0.1:0", principal.BlessingStore().Default())
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	var wg sync.WaitGroup
+	wg.Add(1)
+	// Server will just listen for flows and close them.
+	go acceptLoop(&wg, ln)
+
+	cctx, _ := v23.WithPrincipal(ctx, testutil.NewPrincipal("client"))
+	vc, err := client.Dial(cctx, ep)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if f, err := vc.Connect(); f == nil || err != nil {
+		t.Errorf("vc.Connect failed: (%v, %v)", f, err)
+	}
+	client.ShutdownEndpoint(ep)
+	if f, err := vc.Connect(); f != nil || err == nil {
+		t.Errorf("vc.Connect unexpectedly succeeded: (%v, %v)", f, err)
+	}
+	ln.Close()
+	wg.Wait()
+}
+
+func TestStartTimeout(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	const (
+		startTime = 5 * time.Millisecond
+	)
+
+	var (
+		server  = InternalNew(ctx, naming.FixedRoutingID(0x55555555))
+		pserver = testutil.NewPrincipal("server")
+		lopts   = []stream.ListenerOpt{vc.StartTimeout{Duration: startTime}}
+	)
+
+	sctx, _ := v23.WithPrincipal(ctx, pserver)
+
+	// Pause the start timers.
+	triggerTimers := vif.SetFakeTimers()
+
+	ln, ep, err := server.Listen(sctx, "tcp", "127.0.0.1:0", pserver.BlessingStore().Default(), lopts...)
+	if err != nil {
+		t.Fatal(err)
+	}
+	go func() {
+		for {
+			_, err := ln.Accept()
+			if err != nil {
+				return
+			}
+		}
+	}()
+	// Arrange for the above goroutine to exit when the test finishes.
+	defer ln.Close()
+
+	_, err = net.Dial(ep.Addr().Network(), ep.Addr().String())
+	if err != nil {
+		t.Fatalf("net.Dial failed: %v", err)
+	}
+
+	// Trigger the start timers.
+	triggerTimers()
+
+	// No VC is opened. The VIF should be closed after start timeout.
+	for range time.Tick(startTime) {
+		if numVIFs(server) == 0 {
+			break
+		}
+	}
+}
+
+func testIdleTimeout(t *testing.T, testServer bool) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	const (
+		idleTime = 10 * time.Millisecond
+		// We use a long wait time here since it takes some time to handle VC close
+		// especially in race testing.
+		waitTime = 150 * time.Millisecond
+	)
+
+	var (
+		server  = InternalNew(ctx, naming.FixedRoutingID(0x55555555))
+		client  = InternalNew(ctx, naming.FixedRoutingID(0xcccccccc))
+		pclient = testutil.NewPrincipal("client")
+		pserver = testutil.NewPrincipal("server")
+		cctx, _ = v23.WithPrincipal(ctx, pclient)
+		sctx, _ = v23.WithPrincipal(ctx, pserver)
+
+		opts  []stream.VCOpt
+		lopts []stream.ListenerOpt
+	)
+
+	if testServer {
+		lopts = []stream.ListenerOpt{vc.IdleTimeout{Duration: idleTime}}
+	} else {
+		opts = []stream.VCOpt{vc.IdleTimeout{Duration: idleTime}}
+	}
+
+	// Pause the idle timers.
+	triggerTimers := vif.SetFakeTimers()
+
+	ln, ep, err := server.Listen(sctx, "tcp", "127.0.0.1:0", pserver.BlessingStore().Default(), lopts...)
+	if err != nil {
+		t.Fatal(err)
+	}
+	errch := make(chan error)
+	done := make(chan struct{})
+	go func() {
+		for {
+			_, err := ln.Accept()
+			select {
+			case <-done:
+				return
+			case errch <- err:
+			}
+		}
+	}()
+	// Arrange for the above goroutine to exit when the test finishes.
+	defer func() { ln.Close(); close(done) }()
+
+	vc, err := client.Dial(cctx, ep, opts...)
+	if err != nil {
+		t.Fatalf("client.Dial(%q) failed: %v", ep, err)
+	}
+	f, err := vc.Connect()
+	if f == nil || err != nil {
+		t.Fatalf("vc.Connect failed: (%v, %v)", f, err)
+	}
+	// Wait until the server accepts the flow or fails.
+	if err = <-errch; err != nil {
+		t.Fatalf("ln.Accept failed: %v", err)
+	}
+
+	// Trigger the idle timers.
+	triggerTimers()
+
+	// One active flow. The VIF should be kept open.
+	time.Sleep(waitTime)
+	if n := numVIFs(client); n != 1 {
+		t.Errorf("Client has %d VIFs; want 1\n%v", n, debugString(client))
+	}
+	if n := numVIFs(server); n != 1 {
+		t.Errorf("Server has %d VIFs; want 1\n%v", n, debugString(server))
+	}
+
+	f.Close()
+
+	// The flow has been closed. The VIF should be closed after idle timeout.
+	for range time.Tick(idleTime) {
+		if numVIFs(client) == 0 && numVIFs(server) == 0 {
+			break
+		}
+	}
+}
+
+func TestIdleTimeout(t *testing.T)       { testIdleTimeout(t, false) }
+func TestIdleTimeoutServer(t *testing.T) { testIdleTimeout(t, true) }
+
+/* TLS + resumption + channel bindings is broken: <https://secure-resumption.com/#channelbindings>.
+func TestSessionTicketCache(t *testing.T) {
+	server := InternalNew(naming.FixedRoutingID(0x55555555))
+	_, ep, err := server.Listen("tcp", "127.0.0.1:0", testutil.NewPrincipal("server"))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	client := InternalNew(naming.FixedRoutingID(0xcccccccc))
+	if _, err = client.Dial(ep, testutil.NewPrincipal("TestSessionTicketCacheClient")); err != nil {
+		t.Fatalf("Dial(%q) failed: %v", ep, err)
+	}
+
+	if _, ok := client.(*manager).sessionCache.Get(ep.String()); !ok {
+		t.Fatalf("SessionTicket from TLS handshake not cached")
+	}
+}
+*/
+
+func testMultipleVCs(t *testing.T, protocol string) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	server := InternalNew(ctx, naming.FixedRoutingID(0x55555555))
+	client := InternalNew(ctx, naming.FixedRoutingID(0xcccccccc))
+	principal := testutil.NewPrincipal("test")
+	sctx, _ := v23.WithPrincipal(ctx, principal)
+
+	const nVCs = 2
+	const data = "bugs bunny"
+
+	// Have the server read from each flow and write to rchan.
+	rchan := make(chan string)
+	ln, ep, err := server.Listen(sctx, protocol, "127.0.0.1:0", principal.BlessingStore().Default())
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	read := func(flow stream.Flow, c chan string) {
+		var buf bytes.Buffer
+		var tmp [1024]byte
+		for {
+			n, err := flow.Read(tmp[:])
+			buf.Write(tmp[:n])
+			if err == io.EOF {
+				c <- buf.String()
+				return
+			}
+			if err != nil {
+				t.Error(err)
+				return
+			}
+		}
+	}
+	go func() {
+		for i := 0; i < nVCs; i++ {
+			flow, err := ln.Accept()
+			if err != nil {
+				t.Error(err)
+				rchan <- ""
+				continue
+			}
+			go read(flow, rchan)
+		}
+	}()
+
+	// Have the client establish nVCs and a flow on each.
+	var vcs [nVCs]stream.VC
+	for i := 0; i < nVCs; i++ {
+		var err error
+		pclient := testutil.NewPrincipal("client")
+		cctx, _ := v23.WithPrincipal(ctx, pclient)
+		vcs[i], err = client.Dial(cctx, ep)
+		if err != nil {
+			t.Fatal(err)
+		}
+	}
+	write := func(vc stream.VC) {
+		if err != nil {
+			ln.Close()
+			t.Error(err)
+			return
+		}
+		flow, err := vc.Connect()
+		if err != nil {
+			ln.Close()
+			t.Error(err)
+			return
+		}
+		defer flow.Close()
+		if _, err := flow.Write([]byte(data)); err != nil {
+			ln.Close()
+			t.Error(err)
+			return
+		}
+	}
+	for _, vc := range vcs {
+		go write(vc)
+	}
+	for i := 0; i < nVCs; i++ {
+		if got := <-rchan; got != data {
+			t.Errorf("Got %q want %q", got, data)
+		}
+	}
+}
+
+func TestMultipleVCs(t *testing.T) {
+	testMultipleVCs(t, "tcp")
+}
+
+func TestMultipleVCsWS(t *testing.T) {
+	testMultipleVCs(t, "ws")
+}
+
+func TestAddressResolution(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	server := InternalNew(ctx, naming.FixedRoutingID(0x55555555))
+	client := InternalNew(ctx, naming.FixedRoutingID(0xcccccccc))
+	principal := testutil.NewPrincipal("test")
+	sctx, _ := v23.WithPrincipal(ctx, principal)
+
+	// Using "tcp4" instead of "tcp" because the latter can end up with IPv6
+	// addresses and our Google Compute Engine integration test machines cannot
+	// resolve IPv6 addresses.
+	// As of April 2014, https://developers.google.com/compute/docs/networking
+	// said that IPv6 is not yet supported.
+	ln, ep, err := server.Listen(sctx, "tcp4", "127.0.0.1:0", principal.BlessingStore().Default())
+	if err != nil {
+		t.Fatal(err)
+	}
+	var wg sync.WaitGroup
+	wg.Add(1)
+	go acceptLoop(&wg, ln)
+
+	// We'd like an endpoint that contains an address that's different than the
+	// one used for the connection. In practice this is awkward to achieve since
+	// we don't want to listen on ":0" since that will annoy firewalls. Instead we
+	// create a endpoint with "localhost", which will result in an endpoint that
+	// doesn't contain 127.0.0.1.
+	_, port, _ := net.SplitHostPort(ep.Addr().String())
+	nep := &inaming.Endpoint{
+		Protocol: ep.Addr().Network(),
+		Address:  net.JoinHostPort("localhost", port),
+		RID:      ep.RoutingID(),
+	}
+
+	// Dial multiple VCs
+	for i := 0; i < 2; i++ {
+		pclient := testutil.NewPrincipal("client")
+		cctx, _ := v23.WithPrincipal(ctx, pclient)
+		if _, err = client.Dial(cctx, nep); err != nil {
+			t.Fatalf("Dial #%d failed: %v", i, err)
+		}
+	}
+	// They should all be on the same VIF.
+	if n := numVIFs(client); n != 1 {
+		t.Errorf("Client has %d VIFs, want 1\n%v", n, debugString(client))
+	}
+	ln.Close()
+	wg.Wait()
+	// TODO(ashankar): While a VIF can be re-used to Dial from the server
+	// to the client, currently there is no way to have the client "listen"
+	// on the same VIF. It can listen on a VC for new flows, but it cannot
+	// listen on an established VIF for new VCs. Figure this out?
+}
+
+func TestServerRestartDuringClientLifetime(t *testing.T) {
+	testServerRestartDuringClientLifetime(t, "tcp")
+}
+
+func TestServerRestartDuringClientLifetimeWS(t *testing.T) {
+	testServerRestartDuringClientLifetime(t, "ws")
+}
+
+func testServerRestartDuringClientLifetime(t *testing.T, protocol string) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	client := InternalNew(ctx, naming.FixedRoutingID(0xcccccccc))
+	pclient := testutil.NewPrincipal("client")
+	pclient2 := testutil.NewPrincipal("client2")
+	ctx1, _ := v23.WithPrincipal(ctx, pclient)
+	ctx2, _ := v23.WithPrincipal(ctx, pclient2)
+
+	sh, err := modules.NewShell(nil, nil, testing.Verbose(), t)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	defer sh.Cleanup(nil, nil)
+	h, err := sh.Start(nil, runServer, protocol, "127.0.0.1:0")
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	epstr := expect.NewSession(t, h.Stdout(), time.Minute).ExpectVar("ENDPOINT")
+	ep, err := inaming.NewEndpoint(epstr)
+	if err != nil {
+		t.Fatalf("inaming.NewEndpoint(%q): %v", epstr, err)
+	}
+	if _, err := client.Dial(ctx1, ep); err != nil {
+		t.Fatal(err)
+	}
+	h.Shutdown(nil, os.Stderr)
+
+	// A new VC cannot be created since the server is dead. Note that we need to
+	// use a different principal since the client doesn't expect a response from
+	// a server when re-using VIF authentication.
+	if _, err := client.Dial(ctx2, ep); err == nil {
+		t.Fatal("Expected client.Dial to fail since server is dead")
+	}
+
+	h, err = sh.Start(nil, runServer, protocol, ep.Addr().String())
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	// Restarting the server, listening on the same address as before
+	ep2, err := inaming.NewEndpoint(expect.NewSession(t, h.Stdout(), time.Minute).ExpectVar("ENDPOINT"))
+	if err != nil {
+		t.Fatal(err)
+	}
+	if got, want := ep.Addr().String(), ep2.Addr().String(); got != want {
+		t.Fatalf("Got %q, want %q", got, want)
+	}
+	if _, err := client.Dial(ctx1, ep2); err != nil {
+		t.Fatal(err)
+	}
+}
+
+var runServer = modules.Register(runServerFunc, "runServer")
+
+func runServerFunc(env *modules.Env, args ...string) error {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	server := InternalNew(ctx, naming.FixedRoutingID(0x55555555))
+	principal := testutil.NewPrincipal("test")
+	ctx, _ = v23.WithPrincipal(ctx, principal)
+	_, ep, err := server.Listen(ctx, args[0], args[1], principal.BlessingStore().Default())
+	if err != nil {
+		fmt.Fprintln(env.Stderr, err)
+		return err
+	}
+	fmt.Fprintf(env.Stdout, "ENDPOINT=%v\n", ep)
+	// Live forever (till the process is explicitly killed)
+	modules.WaitForEOF(env.Stdin)
+	return nil
+}
+
+var runRLimitedServer = modules.Register(func(env *modules.Env, args ...string) error {
+	var rlimit syscall.Rlimit
+	if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rlimit); err != nil {
+		fmt.Fprintln(env.Stderr, err)
+		return err
+	}
+	rlimit.Cur = 9
+	if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rlimit); err != nil {
+		fmt.Fprintln(env.Stderr, err)
+		return err
+	}
+	fmt.Fprintf(env.Stdout, "RLIMIT_NOFILE=%d\n", rlimit.Cur)
+	return runServerFunc(env, args...)
+}, "runRLimitedServer")
+
+func readLine(f stream.Flow) (string, error) {
+	var result bytes.Buffer
+	var buf [5]byte
+	for {
+		n, err := f.Read(buf[:])
+		result.Write(buf[:n])
+		if err == io.EOF || buf[n-1] == '\n' {
+			return strings.TrimRight(result.String(), "\n"), nil
+		}
+		if err != nil {
+			return "", fmt.Errorf("Read returned (%d, %v)", n, err)
+		}
+	}
+}
+
+func writeLine(f stream.Flow, data string) error {
+	data = data + "\n"
+	if n, err := f.Write([]byte(data)); err != nil {
+		return fmt.Errorf("Write returned (%d, %v)", n, err)
+	}
+	return nil
+}
+
+func TestRegistration(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	server := InternalNew(ctx, naming.FixedRoutingID(0x55555555))
+	client := InternalNew(ctx, naming.FixedRoutingID(0xcccccccc))
+	principal := testutil.NewPrincipal("server")
+	blessings := principal.BlessingStore().Default()
+	ctx, _ = v23.WithPrincipal(ctx, principal)
+
+	dialer := func(_ *context.T, _, _ string, _ time.Duration) (net.Conn, error) {
+		return nil, fmt.Errorf("tn.Dial")
+	}
+	resolver := func(_ *context.T, _, _ string) (string, string, error) {
+		return "", "", fmt.Errorf("tn.Resolve")
+	}
+	listener := func(_ *context.T, _, _ string) (net.Listener, error) {
+		return nil, fmt.Errorf("tn.Listen")
+	}
+	rpc.RegisterProtocol("tn", dialer, resolver, listener)
+
+	_, _, err := server.Listen(ctx, "tnx", "127.0.0.1:0", blessings)
+	if err == nil || !strings.Contains(err.Error(), "unknown network: tnx") {
+		t.Fatalf("expected error is missing (%v)", err)
+	}
+
+	_, _, err = server.Listen(ctx, "tn", "127.0.0.1:0", blessings)
+	if err == nil || !strings.Contains(err.Error(), "tn.Listen") {
+		t.Fatalf("expected error is missing (%v)", err)
+	}
+
+	// Need a functional listener to test Dial.
+	listener = func(_ *context.T, _, addr string) (net.Listener, error) {
+		return net.Listen("tcp", addr)
+	}
+
+	if got, want := rpc.RegisterProtocol("tn", dialer, resolver, listener), true; got != want {
+		t.Errorf("got %t, want %t", got, want)
+	}
+
+	_, ep, err := server.Listen(ctx, "tn", "127.0.0.1:0", blessings)
+	if err != nil {
+		t.Errorf("unexpected error %s", err)
+	}
+
+	cctx, _ := v23.WithPrincipal(ctx, testutil.NewPrincipal("client"))
+	_, err = client.Dial(cctx, ep)
+	if err == nil || !strings.Contains(err.Error(), "tn.Resolve") {
+		t.Fatalf("expected error is missing (%v)", err)
+	}
+}
+
+func TestBlessingNamesInEndpoint(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	var (
+		p    = testutil.NewPrincipal("default")
+		b, _ = p.BlessSelf("dev.v.io/users/foo@bar.com/devices/desktop/app/myapp")
+
+		server = InternalNew(ctx, naming.FixedRoutingID(0x1))
+
+		tests = []struct {
+			principal     security.Principal
+			blessings     security.Blessings
+			blessingNames []string
+			err           bool
+		}{
+			{
+				// provided blessings should match returned output.
+				principal:     p,
+				blessings:     b,
+				blessingNames: []string{"dev.v.io/users/foo@bar.com/devices/desktop/app/myapp"},
+			},
+			{
+				// It is an error to provide a principal without providing blessings.
+				principal: p,
+				blessings: security.Blessings{},
+				err:       true,
+			},
+			{
+				// It is an error to provide inconsistent blessings and principal
+				principal: testutil.NewPrincipal("random"),
+				blessings: b,
+				err:       true,
+			},
+		}
+	)
+
+	// p must recognize its own blessings!
+	p.AddToRoots(b)
+	for idx, test := range tests {
+		sctx, _ := v23.WithPrincipal(ctx, test.principal)
+		ln, ep, err := server.Listen(sctx, "tcp", "127.0.0.1:0", test.blessings)
+		if (err != nil) != test.err {
+			t.Errorf("test #%d: Got error %v, wanted error: %v", idx, err, test.err)
+		}
+		if err != nil {
+			continue
+		}
+		ln.Close()
+		got, want := ep.BlessingNames(), test.blessingNames
+		sort.Strings(got)
+		sort.Strings(want)
+		if !reflect.DeepEqual(got, want) {
+			t.Errorf("test #%d: Got %v, want %v", idx, got, want)
+		}
+	}
+}
+
+func TestVIFCleanupWhenFDLimitIsReached(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	sh, err := modules.NewShell(nil, nil, testing.Verbose(), t)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer sh.Cleanup(nil, nil)
+	h, err := sh.Start(nil, runRLimitedServer, "--logtostderr=true", "tcp", "127.0.0.1:0")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer h.CloseStdin()
+	stdout := expect.NewSession(t, h.Stdout(), time.Minute)
+	nfiles, err := strconv.Atoi(stdout.ExpectVar("RLIMIT_NOFILE"))
+	if stdout.Error() != nil {
+		t.Fatal(stdout.Error())
+	}
+	if err != nil {
+		t.Fatal(err)
+	}
+	epstr := stdout.ExpectVar("ENDPOINT")
+	if stdout.Error() != nil {
+		t.Fatal(stdout.Error())
+	}
+	ep, err := inaming.NewEndpoint(epstr)
+	if err != nil {
+		t.Fatal(err)
+	}
+	// Different client processes (represented by different stream managers
+	// in this test) should be able to make progress, even if the server
+	// has reached its file descriptor limit.
+	nattempts := 0
+	for i := 0; i < 2*nfiles; i++ {
+		client := InternalNew(ctx, naming.FixedRoutingID(uint64(i)))
+		defer client.Shutdown()
+		principal := testutil.NewPrincipal(fmt.Sprintf("client%d", i))
+		cctx, _ := v23.WithPrincipal(ctx, principal)
+		connected := false
+		for !connected {
+			nattempts++
+			// If the client connection reached the server when it
+			// was at its limit, it might fail.  However, this
+			// failure will trigger the "kill connections" logic at
+			// the server and eventually the client should succeed.
+			vc, err := client.Dial(cctx, ep)
+			if err != nil {
+				continue
+			}
+			// Establish a flow to prevent the VC (and thus the
+			// underlying VIF) from being garbage collected as an
+			// "inactive" connection.
+			flow, err := vc.Connect()
+			if err != nil {
+				continue
+			}
+			defer flow.Close()
+			connected = true
+		}
+	}
+	var stderr bytes.Buffer
+	if err := h.Shutdown(nil, &stderr); err != nil {
+		t.Logf("%s", stderr.String())
+		t.Fatal(err)
+	}
+	fmt.Fprintf(os.Stderr, "11\n")
+	if log := expect.NewSession(t, bytes.NewReader(stderr.Bytes()), time.Minute).ExpectSetEventuallyRE("listener.go.*Killing [1-9][0-9]* Conns"); len(log) == 0 {
+		t.Errorf("Failed to find log message talking about killing Conns in:\n%v", stderr.String())
+	}
+	t.Logf("Server FD limit:%d", nfiles)
+	t.Logf("Client connection attempts: %d", nattempts)
+}
+
+func TestConcurrentDials(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	// Concurrent Dials to the same network, address should only result in one VIF.
+	server := InternalNew(ctx, naming.FixedRoutingID(0x55555555))
+	client := InternalNew(ctx, naming.FixedRoutingID(0xcccccccc))
+	principal := testutil.NewPrincipal("test")
+	ctx, _ = v23.WithPrincipal(ctx, principal)
+
+	// Using "tcp4" instead of "tcp" because the latter can end up with IPv6
+	// addresses and our Google Compute Engine integration test machines cannot
+	// resolve IPv6 addresses.
+	// As of April 2014, https://developers.google.com/compute/docs/networking
+	// said that IPv6 is not yet supported.
+	ln, ep, err := server.Listen(ctx, "tcp4", "127.0.0.1:0", principal.BlessingStore().Default())
+	if err != nil {
+		t.Fatal(err)
+	}
+	var wg sync.WaitGroup
+	wg.Add(1)
+	go acceptLoop(&wg, ln)
+
+	nep := &inaming.Endpoint{
+		Protocol: ep.Addr().Network(),
+		Address:  ep.Addr().String(),
+		RID:      ep.RoutingID(),
+	}
+
+	// Dial multiple VCs
+	errCh := make(chan error, 10)
+	for i := 0; i < 10; i++ {
+		go func() {
+			cctx, _ := v23.WithPrincipal(ctx, testutil.NewPrincipal("client"))
+			_, err := client.Dial(cctx, nep)
+			errCh <- err
+		}()
+	}
+	for i := 0; i < 10; i++ {
+		if err := <-errCh; err != nil {
+			t.Fatal(err)
+		}
+	}
+	// They should all be on the same VIF.
+	if n := numVIFs(client); n != 1 {
+		t.Errorf("Client has %d VIFs, want 1\n%v", n, debugString(client))
+	}
+	ln.Close()
+	wg.Wait()
+}
diff --git a/runtime/internal/rpc/stream/message/coding.go b/runtime/internal/rpc/stream/message/coding.go
new file mode 100644
index 0000000..ad028ba
--- /dev/null
+++ b/runtime/internal/rpc/stream/message/coding.go
@@ -0,0 +1,241 @@
+// 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 message
+
+import (
+	"encoding/binary"
+	"io"
+
+	"v.io/v23/verror"
+
+	"v.io/x/ref/runtime/internal/rpc/stream/id"
+)
+
+const pkgPath = "v.io/x/ref/runtime/internal/rpc/stream/message"
+
+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.
+	errLargerThan3ByteUint = reg(".errLargerThan3ByteUnit", "integer too large to represent in 3 bytes")
+	errReadWrongNumBytes   = reg(".errReadWrongNumBytes", "read {3} bytes, wanted to read {4}")
+
+	sizeOfSizeT int // How many bytes are used to encode the type sizeT.
+)
+
+type sizeT uint32
+
+func init() {
+	sizeOfSizeT = binary.Size(sizeT(0))
+}
+
+func write3ByteUint(dst []byte, n int) error {
+	if n >= (1<<24) || n < 0 {
+		return verror.New(errLargerThan3ByteUint, nil)
+	}
+	dst[0] = byte((n & 0xff0000) >> 16)
+	dst[1] = byte((n & 0x00ff00) >> 8)
+	dst[2] = byte(n & 0x0000ff)
+	return nil
+}
+
+func read3ByteUint(src []byte) int {
+	return int(src[0])<<16 | int(src[1])<<8 | int(src[2])
+}
+
+func write4ByteUint(dst []byte, n uint32) {
+	dst[0] = byte((n & 0xff000000) >> 24)
+	dst[1] = byte((n & 0x00ff0000) >> 16)
+	dst[2] = byte((n & 0x0000ff00) >> 8)
+	dst[3] = byte(n & 0x000000ff)
+}
+
+func read4ByteUint(src []byte) uint32 {
+	return uint32(src[0])<<24 | uint32(src[1])<<16 | uint32(src[2])<<8 | uint32(src[3])
+}
+
+func readInt(r io.Reader, i interface{}) error {
+	return binary.Read(r, binary.BigEndian, i)
+}
+
+func writeInt(w io.Writer, i interface{}) error {
+	return binary.Write(w, binary.BigEndian, i)
+}
+
+func readString(r io.Reader) (string, error) {
+	b, err := readBytes(r)
+	return string(b), err
+}
+
+func writeString(w io.Writer, s string) error {
+	return writeBytes(w, []byte(s))
+}
+
+func readBytes(r io.Reader) ([]byte, error) {
+	var size sizeT
+	if err := readInt(r, &size); err != nil {
+		return nil, err
+	}
+	b := make([]byte, size)
+	n, err := r.Read(b)
+	if err != nil {
+		return nil, err
+	}
+	if n != int(size) {
+		return nil, verror.New(errReadWrongNumBytes, nil, n, int(size))
+	}
+	return b, nil
+}
+
+func writeBytes(w io.Writer, b []byte) error {
+	size := sizeT(len(b))
+	if err := writeInt(w, size); err != nil {
+		return err
+	}
+	n, err := w.Write(b)
+	if err != nil {
+		return err
+	}
+	if n != int(size) {
+		return verror.New(errReadWrongNumBytes, nil, n, int(size))
+	}
+	return nil
+}
+
+// byteReader adapts an io.Reader to an io.ByteReader so that we can
+// use it with encoding/Binary for varint etc.
+type byteReader struct{ io.Reader }
+
+func (b byteReader) ReadByte() (byte, error) {
+	var buf [1]byte
+	n, err := b.Reader.Read(buf[:])
+	switch {
+	case n == 1:
+		return buf[0], err
+	case err != nil:
+		return 0, err
+	default:
+		return 0, verror.New(errReadWrongNumBytes, nil, n, 1)
+	}
+}
+
+func readCounters(r io.Reader) (Counters, error) {
+	var br io.ByteReader
+	var ok bool
+	if br, ok = r.(io.ByteReader); !ok {
+		br = byteReader{r}
+	}
+	size, err := binary.ReadUvarint(br)
+	if err != nil {
+		return nil, err
+	}
+	if size == 0 {
+		return nil, nil
+	}
+	c := Counters(make(map[CounterID]uint32, size))
+	for i := uint64(0); i < size; i++ {
+		vci, err := binary.ReadUvarint(br)
+		if err != nil {
+			return nil, err
+		}
+		fid, err := binary.ReadUvarint(br)
+		if err != nil {
+			return nil, err
+		}
+		bytes, err := binary.ReadUvarint(br)
+		if err != nil {
+			return nil, err
+		}
+		c.Add(id.VC(vci), id.Flow(fid), uint32(bytes))
+	}
+	return c, nil
+}
+
+func writeCounters(w io.Writer, c Counters) (err error) {
+	var vbuf [binary.MaxVarintLen64]byte
+	putUvarint := func(n uint64) {
+		if err == nil {
+			_, err = w.Write(vbuf[:binary.PutUvarint(vbuf[:], n)])
+		}
+	}
+	putUvarint(uint64(len(c)))
+	for cid, bytes := range c {
+		putUvarint(uint64(cid.VCI()))
+		putUvarint(uint64(cid.Flow()))
+		putUvarint(uint64(bytes))
+	}
+	return
+}
+
+func readSetupOptions(r io.Reader) ([]SetupOption, error) {
+	var opts []SetupOption
+	for {
+		var code setupOptionCode
+		switch err := readInt(r, &code); err {
+		case io.EOF:
+			return opts, nil
+		case nil:
+			break
+		default:
+			return nil, err
+		}
+		var size uint16
+		if err := readInt(r, &size); err != nil {
+			return nil, err
+		}
+		l := &io.LimitedReader{R: r, N: int64(size)}
+		switch code {
+		case naclBoxOptionCode:
+			var opt NaclBox
+			if err := opt.read(l); err != nil {
+				return nil, err
+			}
+			opts = append(opts, &opt)
+		case peerEndpointOptionCode:
+			var opt PeerEndpoint
+			if err := opt.read(l); err != nil {
+				return nil, err
+			}
+			opts = append(opts, &opt)
+		case useVIFAuthenticationOptionCode:
+			var opt UseVIFAuthentication
+			if err := opt.read(l); err != nil {
+				return nil, err
+			}
+			opts = append(opts, &opt)
+		}
+		// Consume any data remaining.
+		readAndDiscardToError(l)
+	}
+}
+
+func writeSetupOptions(w io.Writer, options []SetupOption) error {
+	for _, opt := range options {
+		if err := writeInt(w, opt.code()); err != nil {
+			return err
+		}
+		if err := writeInt(w, opt.size()); err != nil {
+			return err
+		}
+		if err := opt.write(w); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func readAndDiscardToError(r io.Reader) {
+	var data [1024]byte
+	for {
+		if _, err := r.Read(data[:]); err != nil {
+			return
+		}
+	}
+}
diff --git a/runtime/internal/rpc/stream/message/control.go b/runtime/internal/rpc/stream/message/control.go
new file mode 100644
index 0000000..8ccc494
--- /dev/null
+++ b/runtime/internal/rpc/stream/message/control.go
@@ -0,0 +1,434 @@
+// 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 message
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+
+	"v.io/v23/naming"
+	"v.io/v23/verror"
+
+	inaming "v.io/x/ref/runtime/internal/naming"
+	"v.io/x/ref/runtime/internal/rpc/stream/crypto"
+	"v.io/x/ref/runtime/internal/rpc/stream/id"
+	"v.io/x/ref/runtime/internal/rpc/version"
+)
+
+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.
+	errUnrecognizedVCControlMessageCommand = reg(".errUnrecognizedVCControlMessageCommand",
+		"unrecognized VC control message command({3})")
+	errUnrecognizedVCControlMessageType = reg(".errUnrecognizedVCControlMessageType",
+		"unrecognized VC control message type({3})")
+	errFailedToDeserializedVCControlMessage = reg(".errFailedToDeserializedVCControlMessage", "failed to deserialize control message {3}({4}): {5}")
+	errFailedToWriteHeader                  = reg(".errFailedToWriteHeader", "failed to write header. Wrote {3} bytes instead of {4}{:5}")
+)
+
+// Control is the interface implemented by all control messages.
+type Control interface {
+	readFrom(r *bytes.Buffer) error
+	writeTo(w io.Writer) error
+}
+
+// SetupVC is a Control implementation containing information to setup a new
+// virtual circuit.
+type SetupVC struct {
+	VCI            id.VC
+	LocalEndpoint  naming.Endpoint // Endpoint of the sender (as seen by the sender), can be nil.
+	RemoteEndpoint naming.Endpoint // Endpoint of the receiver (as seen by the sender), can be nil.
+	Counters       Counters
+	Setup          Setup // Negotiate versioning and encryption.
+}
+
+// CloseVC is a Control implementation notifying the closure of an established
+// virtual circuit, or failure to establish a virtual circuit.
+//
+// The Error string will be empty in case the close was the result of an
+// explicit close by the application (and not an error).
+type CloseVC struct {
+	VCI   id.VC
+	Error string
+}
+
+// AddReceiveBuffers is a Control implementation used by the sender of the
+// message to inform the other end of a virtual circuit that it is ready to
+// receive more bytes of data (specified per flow).
+type AddReceiveBuffers struct {
+	Counters Counters
+}
+
+// OpenFlow is a Control implementation notifying the senders intent to create
+// a new Flow. It also include the number of bytes the sender of this message
+// is willing to read.
+type OpenFlow struct {
+	VCI             id.VC
+	Flow            id.Flow
+	InitialCounters uint32
+}
+
+// Setup is a control message used to negotiate VIF/VC options.
+type Setup struct {
+	Versions version.Range
+	Options  []SetupOption
+}
+
+// SetupStream is a byte stream used to negotiate VIF setup. During VIF setup,
+// each party sends a Setup message to the other party containing their version
+// and options. If the version requires further negotiation (such as for
+// authentication), the SetupStream is used for the negotiation.
+//
+// The protocol used on the stream is version-specific, it is not specified here.
+// See vif/auth.go for an example.
+type SetupStream struct {
+	Data []byte
+}
+
+// Command enum.
+type command uint8
+
+const (
+	closeVCCommand           command = 1
+	addReceiveBuffersCommand command = 2
+	openFlowCommand          command = 3
+	setupCommand             command = 4
+	setupStreamCommand       command = 5
+	setupVCCommand           command = 6
+)
+
+// SetupOption is the base interface for optional Setup options.
+type SetupOption interface {
+	// code is the identifier for the option.
+	code() setupOptionCode
+
+	// size returns the number of bytes needed to represent the option.
+	size() uint16
+
+	// write the option to the writer.
+	write(w io.Writer) error
+
+	// read the option from the reader.
+	read(r io.Reader) error
+}
+
+// Setup option codes.
+type setupOptionCode uint16
+
+const (
+	naclBoxOptionCode              setupOptionCode = 0
+	peerEndpointOptionCode         setupOptionCode = 1
+	useVIFAuthenticationOptionCode setupOptionCode = 2
+)
+
+// NaclBox is a SetupOption that specifies the public key for the NaclBox
+// encryption protocol.
+type NaclBox struct {
+	PublicKey crypto.BoxKey
+}
+
+// PeerEndpoint is a SetupOption that exchanges the endpoints between peers.
+type PeerEndpoint struct {
+	LocalEndpoint naming.Endpoint // Endpoint of the sender (as seen by the sender).
+}
+
+// UseVIFAuthentication is a SetupOption that notifies the server to use
+// the VIF authentication for the new virtual circuit.
+type UseVIFAuthentication struct {
+	// Signature for binding a principal to a channel to make sure that the peer
+	// who requests to use VIF authentication is the same peer of the VIF.
+	Signature []byte
+}
+
+func writeControl(w io.Writer, m Control) error {
+	var command command
+	switch m.(type) {
+	case *CloseVC:
+		command = closeVCCommand
+	case *AddReceiveBuffers:
+		command = addReceiveBuffersCommand
+	case *OpenFlow:
+		command = openFlowCommand
+	case *Setup:
+		command = setupCommand
+	case *SetupStream:
+		command = setupStreamCommand
+	case *SetupVC:
+		command = setupVCCommand
+	default:
+		return verror.New(errUnrecognizedVCControlMessageType, nil, fmt.Sprintf("%T", m))
+	}
+	var header [1]byte
+	header[0] = byte(command)
+	if n, err := w.Write(header[:]); n != len(header) || err != nil {
+		return verror.New(errFailedToWriteHeader, nil, n, len(header), err)
+	}
+	if err := m.writeTo(w); err != nil {
+		return err
+	}
+	return nil
+}
+
+func readControl(r *bytes.Buffer) (Control, error) {
+	var header byte
+	var err error
+	if header, err = r.ReadByte(); err != nil {
+		return nil, err
+	}
+	command := command(header)
+	var m Control
+	switch command {
+	case closeVCCommand:
+		m = new(CloseVC)
+	case addReceiveBuffersCommand:
+		m = new(AddReceiveBuffers)
+	case openFlowCommand:
+		m = new(OpenFlow)
+	case setupCommand:
+		m = new(Setup)
+	case setupStreamCommand:
+		m = new(SetupStream)
+	case setupVCCommand:
+		m = new(SetupVC)
+	default:
+		return nil, verror.New(errUnrecognizedVCControlMessageCommand, nil, command)
+	}
+	if err := m.readFrom(r); err != nil {
+		return nil, verror.New(errFailedToDeserializedVCControlMessage, nil, command, fmt.Sprintf("%T", m), err)
+	}
+	return m, nil
+}
+
+func (m *CloseVC) writeTo(w io.Writer) (err error) {
+	if err = writeInt(w, m.VCI); err != nil {
+		return
+	}
+	err = writeString(w, m.Error)
+	return
+}
+
+func (m *CloseVC) readFrom(r *bytes.Buffer) (err error) {
+	if err = readInt(r, &m.VCI); err != nil {
+		return
+	}
+	m.Error, err = readString(r)
+	return
+}
+
+func (m *SetupVC) writeTo(w io.Writer) (err error) {
+	if err = writeInt(w, m.VCI); err != nil {
+		return
+	}
+	var ep string
+	if m.LocalEndpoint != nil {
+		ep = m.LocalEndpoint.String()
+	}
+	if err = writeString(w, ep); err != nil {
+		return
+	}
+	if m.RemoteEndpoint != nil {
+		ep = m.RemoteEndpoint.String()
+	}
+	if err = writeString(w, ep); err != nil {
+		return
+	}
+	if err = writeCounters(w, m.Counters); err != nil {
+		return
+	}
+	err = m.Setup.writeTo(w)
+	return
+}
+
+func (m *SetupVC) readFrom(r *bytes.Buffer) (err error) {
+	if err = readInt(r, &m.VCI); err != nil {
+		return
+	}
+	var ep string
+	if ep, err = readString(r); err != nil {
+		return
+	}
+	if len(ep) > 0 {
+		if m.LocalEndpoint, err = inaming.NewEndpoint(ep); err != nil {
+			return
+		}
+	}
+	if ep, err = readString(r); err != nil {
+		return
+	}
+	if len(ep) > 0 {
+		if m.RemoteEndpoint, err = inaming.NewEndpoint(ep); err != nil {
+			return
+		}
+	}
+	if m.Counters, err = readCounters(r); err != nil {
+		return
+	}
+	err = m.Setup.readFrom(r)
+	return
+}
+
+func (m *AddReceiveBuffers) writeTo(w io.Writer) error {
+	return writeCounters(w, m.Counters)
+}
+
+func (m *AddReceiveBuffers) readFrom(r *bytes.Buffer) (err error) {
+	m.Counters, err = readCounters(r)
+	return
+}
+
+func (m *OpenFlow) writeTo(w io.Writer) (err error) {
+	if err = writeInt(w, m.VCI); err != nil {
+		return
+	}
+	if err = writeInt(w, m.Flow); err != nil {
+		return
+	}
+	err = writeInt(w, m.InitialCounters)
+	return
+}
+
+func (m *OpenFlow) readFrom(r *bytes.Buffer) (err error) {
+	if err = readInt(r, &m.VCI); err != nil {
+		return
+	}
+	if err = readInt(r, &m.Flow); err != nil {
+		return
+	}
+	err = readInt(r, &m.InitialCounters)
+	return
+}
+
+func (m *Setup) writeTo(w io.Writer) (err error) {
+	if err = writeInt(w, m.Versions.Min); err != nil {
+		return
+	}
+	if err = writeInt(w, m.Versions.Max); err != nil {
+		return
+	}
+	err = writeSetupOptions(w, m.Options)
+	return
+}
+
+func (m *Setup) readFrom(r *bytes.Buffer) (err error) {
+	if err = readInt(r, &m.Versions.Min); err != nil {
+		return
+	}
+	if err = readInt(r, &m.Versions.Max); err != nil {
+		return
+	}
+	m.Options, err = readSetupOptions(r)
+	return
+}
+
+func (m *SetupStream) writeTo(w io.Writer) error {
+	_, err := w.Write(m.Data)
+	return err
+}
+
+func (m *SetupStream) readFrom(r *bytes.Buffer) error {
+	m.Data = r.Bytes()
+	return nil
+}
+
+// NaclBox returns the first NaclBox option, or nil if there is none.
+func (m *Setup) NaclBox() *NaclBox {
+	for _, opt := range m.Options {
+		if o, ok := opt.(*NaclBox); ok {
+			return o
+		}
+	}
+	return nil
+}
+
+func (*NaclBox) code() setupOptionCode {
+	return naclBoxOptionCode
+}
+
+func (m *NaclBox) size() uint16 {
+	return uint16(len(m.PublicKey))
+}
+
+func (m *NaclBox) write(w io.Writer) error {
+	_, err := w.Write(m.PublicKey[:])
+	return err
+}
+
+func (m *NaclBox) read(r io.Reader) error {
+	_, err := io.ReadFull(r, m.PublicKey[:])
+	return err
+}
+
+// PeerEndpoint returns the naming.Endpoint in the first PeerEndpoint
+// option, or nil if there is none.
+func (m *Setup) PeerEndpoint() naming.Endpoint {
+	for _, opt := range m.Options {
+		if o, ok := opt.(*PeerEndpoint); ok {
+			return o.LocalEndpoint
+		}
+	}
+	return nil
+}
+
+func (*PeerEndpoint) code() setupOptionCode {
+	return peerEndpointOptionCode
+}
+
+func (m *PeerEndpoint) size() uint16 {
+	var ep string
+	if m.LocalEndpoint != nil {
+		ep = m.LocalEndpoint.String()
+	}
+	return uint16(sizeOfSizeT + len(ep))
+}
+
+func (m *PeerEndpoint) write(w io.Writer) error {
+	var ep string
+	if m.LocalEndpoint != nil {
+		ep = m.LocalEndpoint.String()
+	}
+	return writeString(w, ep)
+}
+
+func (m *PeerEndpoint) read(r io.Reader) (err error) {
+	var ep string
+	if ep, err = readString(r); err != nil {
+		return
+	}
+	if len(ep) > 0 {
+		m.LocalEndpoint, err = inaming.NewEndpoint(ep)
+	}
+	return
+}
+
+// UseVIFAuthentication returns the signature of the first UseVIFAuthentication
+// option, or nil if there is none.
+func (m *Setup) UseVIFAuthentication() []byte {
+	for _, opt := range m.Options {
+		if o, ok := opt.(*UseVIFAuthentication); ok {
+			return o.Signature
+		}
+	}
+	return nil
+}
+
+func (*UseVIFAuthentication) code() setupOptionCode {
+	return useVIFAuthenticationOptionCode
+}
+
+func (m *UseVIFAuthentication) size() uint16 {
+	return uint16(sizeOfSizeT + len(m.Signature))
+}
+
+func (m *UseVIFAuthentication) write(w io.Writer) error {
+	return writeBytes(w, m.Signature)
+}
+
+func (m *UseVIFAuthentication) read(r io.Reader) (err error) {
+	m.Signature, err = readBytes(r)
+	return
+}
diff --git a/runtime/internal/rpc/stream/message/counters.go b/runtime/internal/rpc/stream/message/counters.go
new file mode 100644
index 0000000..f51074e
--- /dev/null
+++ b/runtime/internal/rpc/stream/message/counters.go
@@ -0,0 +1,57 @@
+// 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 message
+
+import (
+	"fmt"
+
+	"v.io/x/ref/runtime/internal/rpc/stream/id"
+)
+
+// CounterID encapsulates the VCI and Flow used for flow control counter
+// accounting.
+type CounterID uint64
+
+// VCI returns the VCI encoded within the CounterID
+func (c *CounterID) VCI() id.VC { return id.VC(*c >> 32) }
+
+// Flow returns the Flow identifier encoded within the CounterID
+func (c *CounterID) Flow() id.Flow { return id.Flow(*c & 0xffffffff) }
+
+func (c *CounterID) String() string { return fmt.Sprintf("Flow:%d/VCI:%d", c.Flow(), c.VCI()) }
+
+// MakeCounterID creates a CounterID from the provided (vci, fid) pair.
+func MakeCounterID(vci id.VC, fid id.Flow) CounterID {
+	return CounterID(uint64(vci)<<32 | uint64(fid))
+}
+
+// Counters is a map from (VCI, Flow) to the number of bytes for that (VCI,
+// Flow) pair that the receiver is willing to read.
+//
+// Counters are not safe for concurrent access from multiple goroutines.
+//
+// When received in Control messages, clients can iterate over the map:
+//	for cid, bytes := range counters {
+//		fmt.Println("VCI=%d Flow=%d Bytes=%d", cid.VCI(), cid.Flow(), bytes)
+//	}
+type Counters map[CounterID]uint32
+
+// NewCounters creates a new Counters object.
+func NewCounters() Counters { return Counters(make(map[CounterID]uint32)) }
+
+// Add should be called by the receiving end of a Flow to indicate that it is
+// ready to read 'bytes' more data for the flow identified by (vci, fid).
+func (c Counters) Add(vci id.VC, fid id.Flow, bytes uint32) {
+	c[MakeCounterID(vci, fid)] += bytes
+}
+
+func (c Counters) String() string {
+	ret := "map[ "
+	for cid, bytes := range c {
+		ret += fmt.Sprintf("%d@%d:%d ", cid.Flow(), cid.VCI(), bytes)
+	}
+	ret += "]"
+	return ret
+}
diff --git a/runtime/internal/rpc/stream/message/counters_test.go b/runtime/internal/rpc/stream/message/counters_test.go
new file mode 100644
index 0000000..d2ea0f5
--- /dev/null
+++ b/runtime/internal/rpc/stream/message/counters_test.go
@@ -0,0 +1,65 @@
+// 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 message
+
+import (
+	"testing"
+	"testing/quick"
+
+	"v.io/x/ref/runtime/internal/rpc/stream/id"
+)
+
+func TestCounterID(t *testing.T) {
+	tests := []struct {
+		vci id.VC
+		fid id.Flow
+	}{
+		{0, 0},
+		{1, 10},
+		{0xffeeddcc, 0xffaabbcc},
+	}
+	for _, test := range tests {
+		cid := MakeCounterID(test.vci, test.fid)
+		if g, w := cid.VCI(), test.vci; g != w {
+			t.Errorf("Got VCI %d want %d", g, w)
+		}
+		if g, w := cid.Flow(), test.fid; g != w {
+			t.Errorf("Got Flow %d want %d", g, w)
+		}
+	}
+}
+
+func TestCounterID_Random(t *testing.T) {
+	f := func(vci id.VC, fid id.Flow) bool {
+		cid := MakeCounterID(vci, fid)
+		return cid.VCI() == vci && cid.Flow() == fid
+	}
+	if err := quick.Check(f, nil); err != nil {
+		t.Error(err)
+	}
+}
+
+func TestCounters(t *testing.T) {
+	f := func(vci id.VC, fid id.Flow, bytes []uint32) bool {
+		c := NewCounters()
+		var sum uint32
+		for _, bin := range bytes {
+			c.Add(vci, fid, bin)
+			if len(c) != 1 {
+				return false
+			}
+			sum += bin
+			for cid, bout := range c {
+				if cid.VCI() != vci || cid.Flow() != fid || bout != sum {
+					return false
+				}
+			}
+		}
+		return true
+	}
+	if err := quick.Check(f, nil); err != nil {
+		t.Error(err)
+	}
+}
diff --git a/runtime/internal/rpc/stream/message/data.go b/runtime/internal/rpc/stream/message/data.go
new file mode 100644
index 0000000..784b603
--- /dev/null
+++ b/runtime/internal/rpc/stream/message/data.go
@@ -0,0 +1,45 @@
+// 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 message
+
+import (
+	"fmt"
+
+	"v.io/x/ref/runtime/internal/lib/iobuf"
+	"v.io/x/ref/runtime/internal/rpc/stream/id"
+)
+
+// Data encapsulates an application data message.
+type Data struct {
+	VCI     id.VC // Must be non-zero.
+	Flow    id.Flow
+	flags   uint8
+	Payload *iobuf.Slice
+}
+
+// Close returns true if the sender of the data message requested that the flow be closed.
+func (d *Data) Close() bool { return d.flags&0x1 == 1 }
+
+// SetClose sets the Close flag of the message.
+func (d *Data) SetClose() { d.flags |= 0x1 }
+
+// Release releases the Payload
+func (d *Data) Release() {
+	if d.Payload != nil {
+		d.Payload.Release()
+		d.Payload = nil
+	}
+}
+
+func (d *Data) PayloadSize() int {
+	if d.Payload == nil {
+		return 0
+	}
+	return d.Payload.Size()
+}
+
+func (d *Data) String() string {
+	return fmt.Sprintf("VCI:%d Flow:%d Flags:%02x Payload:(%d bytes)", d.VCI, d.Flow, d.flags, d.PayloadSize())
+}
diff --git a/runtime/internal/rpc/stream/message/message.go b/runtime/internal/rpc/stream/message/message.go
new file mode 100644
index 0000000..2e0b841
--- /dev/null
+++ b/runtime/internal/rpc/stream/message/message.go
@@ -0,0 +1,263 @@
+// 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 message provides data structures and serialization/deserialization
+// methods for messages exchanged by the implementation of the
+// v.io/x/ref/runtime/internal/rpc/stream interfaces.
+package message
+
+// This file contains methods to read and write messages sent over the VIF.
+// Every message has the following format:
+//
+// +-----------------------------------------+
+// | Type (1 byte) | PayloadSize (3 bytes)   |
+// +-----------------------------------------+
+// | Payload (PayloadSize bytes)             |
+// +-----------------------------------------+
+//
+// Currently, there are 2 valid types:
+// 0 (controlType)
+// 1 (dataType)
+//
+// When Type == controlType, the message is:
+// +---------------------------------------------+
+// |      0        | PayloadSize (3 bytes)       |
+// +---------------------------------------------+
+// | Cmd  (1 byte)                               |
+// +---------------------------------------------+
+// | Data (PayloadSize - MACSize - 1 bytes)      |
+// +---------------------------------------------+
+// | MAC (MACSize bytes)                         |
+// +---------------------------------------------+
+// Where Data is the serialized Control interface object.
+//
+// When Type == dataType, the message is:
+// +---------------------------------------------+
+// |      1        | PayloadSize (3 bytes)       |
+// +---------------------------------------------+
+// | id.VCI (4 bytes)                            |
+// +---------------------------------------------+
+// | id.Flow (4 bytes)                           |
+// +---------------------------------------------+
+// | Flags (1 byte)                              |
+// +---------------------------------------------+
+// | MAC (MACSize bytes)                         |
+// +---------------------------------------------+
+// | Data (PayloadSize - 9 - MACSize bytes)      |
+// +---------------------------------------------+
+// Where Data is the application data.  The Data is encrypted separately; it is
+// not included in the MAC.
+//
+// A crypto.ControlCipher is used to encrypt the control data.  The MACSize
+// comes from the ControlCipher.  When used, the first word of the header,
+// containing the Type and PayloadSize, is encrypted with the cipher's Encrypt
+// method.  The rest of the control data is encrypted with the cipher's Seal
+// method.  This means that none of the data is observable by an adversary, but
+// the type and length are subject to corruption (the rest of the data is not).
+// This doesn't matter -- if the Type or PayloadSize is corrupted by an
+// adversary, the payload will be misread, and will fail to validate.
+//
+// We could potentially pass the Type and PayloadSize in the clear, but then the
+// framing would be observable, a (probably minor) information leak.  There is
+// no reason to do so, we encrypt everything.
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+
+	"v.io/v23/verror"
+	"v.io/x/ref/internal/logger"
+	"v.io/x/ref/runtime/internal/lib/iobuf"
+	"v.io/x/ref/runtime/internal/rpc/stream/crypto"
+	"v.io/x/ref/runtime/internal/rpc/stream/id"
+)
+
+const (
+	// Size (in bytes) of headers appended to application data payload in
+	// Data messages.
+	HeaderSizeBytes = commonHeaderSizeBytes + dataHeaderSizeBytes
+
+	commonHeaderSizeBytes = 4 // 1 byte type + 3 bytes payload length
+	dataHeaderSizeBytes   = 9 // 4 byte id.VC + 4 byte id.Flow + 1 byte flags
+
+	// Make sure the first byte can't be ASCII to ensure that a VC
+	// header can never be confused with a web socket request.
+	// TODO(cnicolaou): remove the original controlType and dataType values
+	// when new binaries are pushed.
+	controlType   = 0
+	controlTypeWS = 0x80
+	dataType      = 1
+	dataTypeWS    = 0x81
+)
+
+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.
+	errEmptyMessage              = reg(".errEmptyMessage", "message is empty")
+	errCorruptedMessage          = reg(".errCorruptedMessage", "corrupted message")
+	errInvalidMessageType        = reg("errInvalidMessageType", "invalid message type {3}")
+	errUnrecognizedMessageType   = reg("errUrecognizedMessageType", "unrecognized message type {3}")
+	errFailedToReadMessageHeader = reg(".errFailedToReadMessageHeader", "failed to read message header{:3}")
+	errFailedToReadPayload       = reg(".errFailedToReadPayload", "failed to read payload of {3} bytes for type {4}{:5}")
+)
+
+// T is the interface implemented by all messages communicated over a VIF.
+type T interface {
+}
+
+// ReadFrom reads a message from the provided iobuf.Reader.
+//
+// Sample usage:
+//	msg, err := message.ReadFrom(r)
+//	switch m := msg.(type) {
+//		case *Data:
+//			notifyFlowOfReceivedData(m.VCI, m.Flow, m.Payload)
+//			if m.Closed() {
+//			   closeFlow(m.VCI, m.Flow)
+//			}
+//		case Control:
+//			handleControlMessage(m)
+//	}
+func ReadFrom(r *iobuf.Reader, c crypto.ControlCipher) (T, error) {
+	header, err := r.Read(commonHeaderSizeBytes)
+	if err != nil {
+		return nil, verror.New(errFailedToReadMessageHeader, nil, err)
+	}
+	c.Decrypt(header.Contents)
+	msgType := header.Contents[0]
+	msgPayloadSize := read3ByteUint(header.Contents[1:4])
+	header.Release()
+	macSize := c.MACSize()
+	switch msgType {
+	case controlType, controlTypeWS:
+		payload, err := r.Read(msgPayloadSize)
+		if err != nil {
+			return nil, verror.New(errFailedToReadPayload, nil, msgPayloadSize, msgType, err)
+		}
+		if !c.Open(payload.Contents) {
+			payload.Release()
+			return nil, verror.New(errCorruptedMessage, nil)
+		}
+		m, err := readControl(bytes.NewBuffer(payload.Contents[:msgPayloadSize-macSize]))
+		payload.Release()
+		return m, err
+	case dataType, dataTypeWS:
+		payload, err := r.Read(msgPayloadSize)
+		if err != nil {
+			return nil, verror.New(errFailedToReadPayload, nil, msgPayloadSize, msgType, err)
+		}
+		if !c.Open(payload.Contents[0 : dataHeaderSizeBytes+macSize]) {
+			payload.Release()
+			return nil, verror.New(errCorruptedMessage, nil)
+		}
+		m := &Data{
+			VCI:     id.VC(read4ByteUint(payload.Contents[0:4])),
+			Flow:    id.Flow(read4ByteUint(payload.Contents[4:8])),
+			flags:   payload.Contents[8],
+			Payload: payload,
+		}
+		m.Payload.TruncateFront(uint(dataHeaderSizeBytes + macSize))
+		return m, nil
+	default:
+		return nil, verror.New(errUnrecognizedMessageType, nil, msgType)
+	}
+}
+
+// WriteTo serializes message and makes a single call to w.Write.
+// It is the inverse of ReadFrom.
+//
+// By writing the message in a single call to w.Write, confusion is avoided in
+// case multiple goroutines are calling Write on w simultaneously.
+//
+// If message is a Data message, the Payload contents will be Released
+// irrespective of the return value of this method.
+func WriteTo(w io.Writer, message T, c crypto.ControlCipher) error {
+	macSize := c.MACSize()
+	switch m := message.(type) {
+	case *Data:
+		payloadSize := m.PayloadSize() + dataHeaderSizeBytes + macSize
+		msg := mkHeaderSpace(m.Payload, uint(HeaderSizeBytes+macSize))
+		header := msg.Contents[0 : HeaderSizeBytes+macSize]
+		header[0] = dataType
+		if err := write3ByteUint(header[1:4], payloadSize); err != nil {
+			return err
+
+		}
+		write4ByteUint(header[4:8], uint32(m.VCI))
+		write4ByteUint(header[8:12], uint32(m.Flow))
+		header[12] = m.flags
+		EncryptMessage(msg.Contents, c)
+		_, err := w.Write(msg.Contents)
+		msg.Release()
+		return err
+	case Control:
+		var buf bytes.Buffer
+		// Prevent a few memory allocations by presizing the buffer to
+		// something that is large enough for typical control messages.
+		buf.Grow(256)
+		// Reserve space for the header
+		if err := extendBuffer(&buf, commonHeaderSizeBytes); err != nil {
+			return err
+		}
+		if err := writeControl(&buf, m); err != nil {
+			return err
+		}
+		if err := extendBuffer(&buf, macSize); err != nil {
+			return err
+		}
+		msg := buf.Bytes()
+		msg[0] = controlType
+		if err := write3ByteUint(msg[1:4], buf.Len()-commonHeaderSizeBytes); err != nil {
+			return err
+		}
+		EncryptMessage(msg, c)
+		_, err := w.Write(msg)
+		return err
+	default:
+		return verror.New(errInvalidMessageType, nil, fmt.Sprintf("%T", m))
+	}
+}
+
+// EncryptMessage encrypts the message's control data in place.
+func EncryptMessage(msg []byte, c crypto.ControlCipher) error {
+	if len(msg) == 0 {
+		return verror.New(errEmptyMessage, nil)
+	}
+	n := len(msg)
+	switch msgType := msg[0]; msgType {
+	case controlType:
+		// skip
+	case dataType:
+		n = HeaderSizeBytes + c.MACSize()
+	default:
+		return verror.New(errUnrecognizedMessageType, nil, msgType)
+	}
+	c.Encrypt(msg[0:commonHeaderSizeBytes])
+	c.Seal(msg[commonHeaderSizeBytes:n])
+	return nil
+}
+
+func mkHeaderSpace(slice *iobuf.Slice, space uint) *iobuf.Slice {
+	if slice == nil {
+		return iobuf.NewSlice(make([]byte, space))
+	}
+	if slice.ExpandFront(space) {
+		return slice
+	}
+	logger.Global().VI(10).Infof("Failed to expand slice by %d bytes. Copying", space)
+	contents := make([]byte, slice.Size()+int(space))
+	copy(contents[space:], slice.Contents)
+	slice.Release()
+	return iobuf.NewSlice(contents)
+}
+
+var emptyBytes [256]byte
+
+func extendBuffer(buf *bytes.Buffer, size int) error {
+	_, err := buf.Write(emptyBytes[:size])
+	return err
+}
diff --git a/runtime/internal/rpc/stream/message/message_test.go b/runtime/internal/rpc/stream/message/message_test.go
new file mode 100644
index 0000000..72c108e
--- /dev/null
+++ b/runtime/internal/rpc/stream/message/message_test.go
@@ -0,0 +1,261 @@
+// 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 message
+
+import (
+	"bytes"
+	"encoding/binary"
+	"reflect"
+	"testing"
+
+	"v.io/v23/naming"
+	"v.io/x/ref/runtime/internal/lib/iobuf"
+	inaming "v.io/x/ref/runtime/internal/naming"
+	"v.io/x/ref/runtime/internal/rpc/stream/crypto"
+	iversion "v.io/x/ref/runtime/internal/rpc/version"
+)
+
+// testControlCipher is a super-simple cipher that xor's each byte of the
+// payload with 0xaa.
+type testControlCipher struct{}
+
+const testMACSize = 4
+
+func (*testControlCipher) MACSize() int {
+	return testMACSize
+}
+
+func testMAC(data []byte) []byte {
+	var h uint32
+	for _, b := range data {
+		h = (h << 1) ^ uint32(b)
+	}
+	var hash [4]byte
+	binary.BigEndian.PutUint32(hash[:], h)
+	return hash[:]
+}
+
+func (c *testControlCipher) Decrypt(data []byte) {
+	for i, _ := range data {
+		data[i] ^= 0xaa
+	}
+}
+
+func (c *testControlCipher) Encrypt(data []byte) {
+	for i, _ := range data {
+		data[i] ^= 0xaa
+	}
+}
+
+func (c *testControlCipher) Open(data []byte) bool {
+	mac := testMAC(data[:len(data)-testMACSize])
+	if bytes.Compare(mac, data[len(data)-testMACSize:]) != 0 {
+		return false
+	}
+	c.Decrypt(data[:len(data)-testMACSize])
+	return true
+}
+
+func (c *testControlCipher) Seal(data []byte) error {
+	c.Encrypt(data[:len(data)-testMACSize])
+	mac := testMAC(data[:len(data)-testMACSize])
+	copy(data[len(data)-testMACSize:], mac)
+	return nil
+}
+
+func (c *testControlCipher) ChannelBinding() []byte {
+	return nil
+}
+
+func TestControl(t *testing.T) {
+	counters := NewCounters()
+	counters.Add(12, 13, 10240)
+	tests := []Control{
+		&CloseVC{VCI: 1},
+		&CloseVC{VCI: 2, Error: "some error"},
+
+		&SetupVC{
+			VCI: 1,
+			LocalEndpoint: &inaming.Endpoint{
+				Protocol: "tcp",
+				Address:  "batman.com:1990",
+				RID:      naming.FixedRoutingID(0xba7),
+			},
+			RemoteEndpoint: &inaming.Endpoint{
+				Protocol: "tcp",
+				Address:  "bugsbunny.com:1940",
+				RID:      naming.FixedRoutingID(0xbb),
+			},
+			Counters: counters,
+			Setup: Setup{
+				Versions: iversion.Range{Min: 34, Max: 56},
+				Options: []SetupOption{
+					&NaclBox{PublicKey: crypto.BoxKey{'h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd'}},
+					&NaclBox{PublicKey: crypto.BoxKey{7, 67, 31}},
+				},
+			},
+		},
+		// SetupVC without endpoints
+		&SetupVC{
+			VCI:      1,
+			Counters: counters,
+			Setup: Setup{
+				Versions: iversion.Range{Min: 34, Max: 56},
+				Options: []SetupOption{
+					&NaclBox{PublicKey: crypto.BoxKey{'h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd'}},
+				},
+			},
+		},
+		// SetupVC with use-vif-authentication.
+		&SetupVC{
+			VCI: 1,
+			LocalEndpoint: &inaming.Endpoint{
+				Protocol: "tcp",
+				Address:  "batman.com:1990",
+				RID:      naming.FixedRoutingID(0xba7),
+			},
+			RemoteEndpoint: &inaming.Endpoint{
+				Protocol: "tcp",
+				Address:  "bugsbunny.com:1940",
+				RID:      naming.FixedRoutingID(0xbb),
+			},
+			Counters: counters,
+			Setup: Setup{
+				Versions: iversion.Range{Min: 34, Max: 56},
+				Options: []SetupOption{
+					&NaclBox{PublicKey: crypto.BoxKey{'h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd'}},
+					&UseVIFAuthentication{Signature: []byte{'s', 'i', 'g', 'n', 'a', 't', 'u', 'r', 'e'}},
+				},
+			},
+		},
+
+		&AddReceiveBuffers{},
+		&AddReceiveBuffers{Counters: counters},
+
+		&OpenFlow{VCI: 1, Flow: 10, InitialCounters: 1 << 24},
+
+		&Setup{
+			Versions: iversion.Range{Min: 21, Max: 71},
+			Options: []SetupOption{
+				&NaclBox{PublicKey: crypto.BoxKey{'h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd'}},
+			},
+		},
+		// Setup with peer endpoint.
+		&Setup{
+			Versions: iversion.Range{Min: 21, Max: 71},
+			Options: []SetupOption{
+				&NaclBox{PublicKey: crypto.BoxKey{'h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd'}},
+				&PeerEndpoint{
+					LocalEndpoint: &inaming.Endpoint{
+						Protocol: "tcp",
+						Address:  "batman.com:1990",
+						RID:      naming.FixedRoutingID(0xba7),
+					},
+				},
+				&PeerEndpoint{
+					LocalEndpoint: &inaming.Endpoint{
+						Protocol: "tcp",
+						Address:  "bugsbunny.com:1940",
+						RID:      naming.FixedRoutingID(0xbb),
+					},
+				},
+			},
+		},
+
+		&SetupStream{Data: []byte("HelloWorld")},
+	}
+
+	var c testControlCipher
+	pool := iobuf.NewPool(0)
+	for i, msg := range tests {
+		var buf bytes.Buffer
+		if err := WriteTo(&buf, msg, &c); err != nil {
+			t.Errorf("WriteTo(%T) (test #%d) failed: %v", msg, i, err)
+			continue
+		}
+		reader := iobuf.NewReader(pool, &buf)
+		read, err := ReadFrom(reader, &c)
+		reader.Close()
+		if err != nil {
+			t.Errorf("ReadFrom failed (test #%d): %v", i, err)
+			continue
+		}
+		if !reflect.DeepEqual(msg, read) {
+			t.Errorf("Test #%d: Got %T = %+v, want %T = %+v", i, read, read, msg, msg)
+		}
+	}
+}
+
+func TestData(t *testing.T) {
+	tests := []struct {
+		Header  Data
+		Payload string
+	}{
+		{Data{VCI: 10, Flow: 3}, "abcd"},
+		{Data{VCI: 10, Flow: 3, flags: 1}, "batman"},
+	}
+
+	var c testControlCipher
+	pool := iobuf.NewPool(0)
+	allocator := iobuf.NewAllocator(pool, HeaderSizeBytes+testMACSize)
+	for i, test := range tests {
+		var buf bytes.Buffer
+		msgW := test.Header
+		msgW.Payload = allocator.Copy([]byte(test.Payload))
+		if err := WriteTo(&buf, &msgW, &c); err != nil {
+			t.Errorf("WriteTo(%v) failed: %v", i, err)
+			continue
+		}
+		reader := iobuf.NewReader(pool, &buf)
+		read, err := ReadFrom(reader, &c)
+		if err != nil {
+			t.Errorf("ReadFrom(%v) failed: %v", i, err)
+			continue
+		}
+		msgR := read.(*Data)
+		// Must compare Payload and the rest of the message separately.
+		// reflect.DeepEqual(msgR, &msgW) will not cut it because the
+		// iobuf.Slice objects might not pass the DeepEqual test.  That
+		// is fine, the important thing is for iobuf.Slice.Content to
+		// match.
+		if g, w := string(msgR.Payload.Contents), test.Payload; g != w {
+			t.Errorf("Mismatched payloads in test #%d. Got %q want %q", i, g, w)
+		}
+		msgR.Release()
+		if !reflect.DeepEqual(&test.Header, msgR) {
+			t.Errorf("Mismatched headers in test #%d. Got %+v want %+v", i, msgR, &test.Header)
+		}
+	}
+}
+
+func TestDataNoPayload(t *testing.T) {
+	tests := []Data{
+		{VCI: 10, Flow: 3},
+		{VCI: 11, Flow: 4, flags: 10},
+	}
+	var c testControlCipher
+	pool := iobuf.NewPool(0)
+	for _, test := range tests {
+		var buf bytes.Buffer
+		if err := WriteTo(&buf, &test, &c); err != nil {
+			t.Errorf("WriteTo(%v) failed: %v", test, err)
+			continue
+		}
+		read, err := ReadFrom(iobuf.NewReader(pool, &buf), &c)
+		if err != nil {
+			t.Errorf("ReadFrom(%v) failed: %v", test, err)
+			continue
+		}
+		msgR := read.(*Data)
+		if msgR.PayloadSize() != 0 {
+			t.Errorf("ReadFrom(WriteTo(%v)) returned payload of %d bytes", test, msgR.PayloadSize())
+			continue
+		}
+		msgR.Payload = nil
+		if !reflect.DeepEqual(&test, msgR) {
+			t.Errorf("Wrote %v, Read %v", test, read)
+		}
+	}
+}
diff --git a/runtime/internal/rpc/stream/model.go b/runtime/internal/rpc/stream/model.go
new file mode 100644
index 0000000..aac6c1c
--- /dev/null
+++ b/runtime/internal/rpc/stream/model.go
@@ -0,0 +1,159 @@
+// 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 stream
+
+import (
+	"io"
+
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/security"
+)
+
+// Flow is the interface for a flow-controlled channel multiplexed on a Virtual
+// Circuit (VC) (and its underlying network connections).
+//
+// This allows for a single level of multiplexing and flow-control over
+// multiple concurrent streams (that may be used for RPCs) over multiple
+// VCs over a single underlying network connection.
+type Flow interface {
+	io.ReadWriteCloser
+
+	// LocalEndpoint returns the local vanadium Endpoint
+	LocalEndpoint() naming.Endpoint
+	// RemoteEndpoint returns the remote vanadium Endpoint
+	RemoteEndpoint() naming.Endpoint
+	// LocalPrincipal returns the Principal at the local end of the flow that has authenticated with the remote end.
+	LocalPrincipal() security.Principal
+	// LocalBlessings returns the blessings presented by the local end of the flow during authentication.
+	LocalBlessings() security.Blessings
+	// RemoteBlessings returns the blessings presented by the remote end of the flow during authentication.
+	RemoteBlessings() security.Blessings
+	// LocalDischarges returns the discharges presented by the local end of the flow during authentication.
+	//
+	// The discharges are organized in a map keyed by the discharge-identifier.
+	LocalDischarges() map[string]security.Discharge
+	// RemoteDischarges returns the discharges presented by the remote end of the flow during authentication.
+	//
+	// The discharges are organized in a map keyed by the discharge-identifier.
+	RemoteDischarges() map[string]security.Discharge
+	// Cancel, like Close, closes the Flow but unlike Close discards any queued writes.
+	Cancel()
+	// IsClosed returns true if the flow has been closed or cancelled.
+	IsClosed() bool
+	// Closed returns a channel that remains open until the flow has been closed.
+	Closed() <-chan struct{}
+
+	// SetDeadline causes reads and writes to the flow to be
+	// cancelled when the given channel is closed.
+	SetDeadline(deadline <-chan struct{})
+
+	// VCDataCache returns the stream.VCDataCache object that allows information to be
+	// shared across the Flow's parent VC.
+	VCDataCache() VCDataCache
+}
+
+// VCDataCache is a thread-safe store that allows data to be shared across a VC,
+// with the intention of caching data that reappears over multiple flows.
+type VCDataCache interface {
+	// Get returns the 'value' associated with 'key'.
+	Get(key interface{}) interface{}
+
+	// GetOrInsert returns the 'value' associated with 'key'. If an entry already exists in the
+	// cache with the 'key', the 'value' is returned, otherwise 'create' is called to create a new
+	// value N, the cache is updated, and N is returned.  GetOrInsert may be called from
+	// multiple goroutines concurrently.
+	GetOrInsert(key interface{}, create func() interface{}) interface{}
+}
+
+// FlowOpt is the interface for all Flow options.
+type FlowOpt interface {
+	RPCStreamFlowOpt()
+}
+
+// Listener is the interface for accepting Flows created by a remote process.
+type Listener interface {
+	// Accept blocks until a new Flow has been initiated by a remote process.
+	// TODO(toddw): This should be:
+	//   Accept() (Flow, Connector, error)
+	Accept() (Flow, error)
+
+	// Close prevents new Flows from being accepted on this Listener.
+	// Previously accepted Flows are not closed down.
+	Close() error
+}
+
+// ListenerOpt is the interface for all options that control the creation of a
+// Listener.
+type ListenerOpt interface {
+	RPCStreamListenerOpt()
+}
+
+// Connector is the interface for initiating Flows to a remote process over a
+// Virtual Circuit (VC).
+type Connector interface {
+	Connect(opts ...FlowOpt) (Flow, error)
+}
+
+// VC is the interface for creating authenticated and secure end-to-end
+// streams.
+//
+// VCs are multiplexed onto underlying network conections and can span
+// multiple hops. Authentication and encryption are end-to-end, even though
+// underlying network connections span a single hop.
+type VC interface {
+	Connector
+	Listen() (Listener, error)
+
+	// Close closes the VC and all flows on it, allowing any pending writes in
+	// flows to drain.
+	Close(reason error) error
+}
+
+// VCOpt is the interface for all VC options.
+type VCOpt interface {
+	RPCStreamVCOpt()
+}
+
+type AuthenticatedVC bool
+
+func (AuthenticatedVC) RPCStreamVCOpt()       {}
+func (AuthenticatedVC) RPCStreamListenerOpt() {}
+
+// Manager is the interface for managing the creation of VCs.
+type Manager interface {
+	// Listen creates a Listener that can be used to accept Flows initiated
+	// with the provided network address.
+	//
+	// For example:
+	//   ln, ep, err := Listen(ctx, "tcp", ":0")
+	//   for {
+	//     flow, err := ln.Accept()
+	//     // process flow
+	//   }
+	// can be used to accept Flows initiated by remote processes to the endpoint
+	// identified by the returned Endpoint.
+	//
+	// ctx contains the principal to be used during authentication and blessings the
+	// blessings to use.
+	Listen(ctx *context.T, protocol, address string, blessings security.Blessings, opts ...ListenerOpt) (Listener, naming.Endpoint, error)
+
+	// Dial creates a VC to the provided remote endpoint.
+	// ctx contains the principal to be used during authentication.
+	// Authentication may be disabled via the AuthenticatedVC option.
+	Dial(ctx *context.T, remote naming.Endpoint, opts ...VCOpt) (VC, error)
+
+	// ShutdownEndpoint closes all VCs (and Flows and Listeners over it)
+	// involving the provided remote endpoint.
+	ShutdownEndpoint(remote naming.Endpoint)
+
+	// Shutdown closes all VCs and Listeners (and Flows over them) and
+	// frees up internal data structures.
+	// The Manager is not usable after Shutdown has been called.
+	Shutdown()
+
+	// RoutingID returns the Routing ID associated with the VC.
+	RoutingID() naming.RoutingID
+}
diff --git a/runtime/internal/rpc/stream/proxy/debug.go b/runtime/internal/rpc/stream/proxy/debug.go
new file mode 100644
index 0000000..a5ae4c0
--- /dev/null
+++ b/runtime/internal/rpc/stream/proxy/debug.go
@@ -0,0 +1,41 @@
+// 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 proxy
+
+import (
+	"bytes"
+	"fmt"
+)
+
+// DebugString dumps out the routing table at the proxy in text format.
+// The format is meant for debugging purposes and may change without notice.
+func (p *Proxy) debugString() string {
+	var buf bytes.Buffer
+	servers := p.servers.List()
+	p.mu.RLock()
+	defer p.mu.RUnlock()
+	fmt.Fprintf(&buf, "Proxy with endpoint: %q. #Processes:%d #Servers:%d\n", p.endpoint(), len(p.processes), len(servers))
+	fmt.Fprintf(&buf, "=========\n")
+	fmt.Fprintf(&buf, "PROCESSES\n")
+	fmt.Fprintf(&buf, "=========\n")
+	index := 1
+	for process, _ := range p.processes {
+		fmt.Fprintf(&buf, "(%d) - %v", index, process)
+		index++
+		process.mu.RLock()
+		fmt.Fprintf(&buf, " NextVCI:%d #Severs:%d\n", process.nextVCI, len(process.servers))
+		for vci, d := range process.routingTable {
+			fmt.Fprintf(&buf, "    VCI %4d --> VCI %4d @ %s\n", vci, d.VCI, d.Process)
+		}
+		process.mu.RUnlock()
+	}
+	fmt.Fprintf(&buf, "=======\n")
+	fmt.Fprintf(&buf, "SERVERS\n")
+	fmt.Fprintf(&buf, "=======\n")
+	for ix, is := range servers {
+		fmt.Fprintf(&buf, "(%d) %v\n", ix+1, is)
+	}
+	return buf.String()
+}
diff --git a/runtime/internal/rpc/stream/proxy/doc.go b/runtime/internal/rpc/stream/proxy/doc.go
new file mode 100644
index 0000000..2e0d16c
--- /dev/null
+++ b/runtime/internal/rpc/stream/proxy/doc.go
@@ -0,0 +1,50 @@
+// 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 proxy implements a proxy for the stream layer.
+//
+// Each process in vanadium is uniquely identified by a routing id
+// (naming.RoutingID). A proxy routes messages
+// (v.io/x/ref/runtime/internal/rpc/stream/message) it receives on a network connection
+// (net.Conn) to the network connection on which the destination process
+// (identified by the routing id) is listening.
+//
+// Processes behind a NAT can use the proxy to export their services outside
+// the NAT.
+// Sample usage:
+//    var proxyEP naming.Endpoint  // Endpoint of the proxy server
+//    var manager stream.Manager   // Manager used to create and listen for VCs and Flows.
+//    ln, ep, err := manager.Listen(proxyEP.Network(), proxyEP.String())
+//    // Now ln.Accept() will return Flows initiated by remote processes through the proxy.
+//
+// The proxy implemented in this package operates as follows:
+// - When an OpenVC message is received at the proxy, the RoutingID(R)
+//   of the source endpoint is associated with the net.Conn the message
+//   was received on.
+// - This association is used to route messages destined for R to the
+//   corresponding net.Conn
+// - Servers can "listen" on the proxy's address by establishing a VC to the
+//   proxy. Once the VC is established, messages received at the proxy destined
+//   for the RoutingID of the server are forwarded to the net.Conn between the
+//   server and the proxy.
+//
+// For example, consider the following three processes:
+// - Proxy(P) with routing id Rp
+// - A server (S) wishing to listen on the proxy's address with routing id Rs
+// - A client (C) wishing to connect to S through the proxy with routing id Rc.
+//
+// Here is a valid sequence of events that makes that possible:
+// (1) S establishes a VC with P over a net.Conn c1
+//     As a result, P knows that any messages intended for Rs should be
+//     forwarded on c1
+// (2) C connects to P over a net.Conn c2 and attempts to establish a VC with S
+//     using an OpenVC message.
+//     The source endpoint of this message contains the routing id Rc while the
+//     destination endpoint contains the routing id Rs.
+// (3) The proxy sees this message and:
+//     (a) Forwards the message over c1 (since Rs is mapped to c1)
+//     (b) Updates its routing table so that messages intended for Rc are forwarded over c2
+// (4) Any messages from S intended for the client received on c1 are forwarded
+//     by the proxy over c2.
+package proxy
diff --git a/runtime/internal/rpc/stream/proxy/protocol.vdl b/runtime/internal/rpc/stream/proxy/protocol.vdl
new file mode 100644
index 0000000..da87aa0
--- /dev/null
+++ b/runtime/internal/rpc/stream/proxy/protocol.vdl
@@ -0,0 +1,36 @@
+// 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 proxy
+
+import "v.io/v23/security"
+
+// The proxy protocol is:
+// (1) Server establishes a VC to the proxy to register its routing id and authenticate.
+// (2) The server opens a flow and sends a "Request" message and waits for a "Response"
+//     message.
+// (3) This flow is then kept alive with no more data read/written.
+//     Closure of this flow indicates that proxying has (or should be) stopped.
+// (4) The proxy immediately closes any other flows on the VC.
+
+// Request is the message sent by a server to request that the proxy route
+// traffic intended for the server's RoutingId to the network connection
+// between the server and the proxy.
+type Request struct {
+  // Blessings of the server that wishes to be proxied.
+  // Used to authorize the use of the proxy.
+  Blessings security.WireBlessings
+  // Discharges required to make Blessings valid.
+  Discharges []security.WireDischarge
+}
+
+// Response is sent by the proxy to the server after processing Request.
+type Response struct {
+  // Error is a description of why the proxy refused to proxy the server.
+  // A nil error indicates that the proxy will route traffic to the server.
+  Error error
+  // Endpoint is the string representation of an endpoint that can be
+  // used to communicate with the server through the proxy.
+  Endpoint string
+}
diff --git a/runtime/internal/rpc/stream/proxy/protocol.vdl.go b/runtime/internal/rpc/stream/proxy/protocol.vdl.go
new file mode 100644
index 0000000..43f70a0
--- /dev/null
+++ b/runtime/internal/rpc/stream/proxy/protocol.vdl.go
@@ -0,0 +1,52 @@
+// 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 by the vanadium vdl tool.
+// Source: protocol.vdl
+
+package proxy
+
+import (
+	// VDL system imports
+	"v.io/v23/vdl"
+
+	// VDL user imports
+	"v.io/v23/security"
+)
+
+// Request is the message sent by a server to request that the proxy route
+// traffic intended for the server's RoutingId to the network connection
+// between the server and the proxy.
+type Request struct {
+	// Blessings of the server that wishes to be proxied.
+	// Used to authorize the use of the proxy.
+	Blessings security.Blessings
+	// Discharges required to make Blessings valid.
+	Discharges []security.Discharge
+}
+
+func (Request) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/runtime/internal/rpc/stream/proxy.Request"`
+}) {
+}
+
+// Response is sent by the proxy to the server after processing Request.
+type Response struct {
+	// Error is a description of why the proxy refused to proxy the server.
+	// A nil error indicates that the proxy will route traffic to the server.
+	Error error
+	// Endpoint is the string representation of an endpoint that can be
+	// used to communicate with the server through the proxy.
+	Endpoint string
+}
+
+func (Response) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/runtime/internal/rpc/stream/proxy.Response"`
+}) {
+}
+
+func init() {
+	vdl.Register((*Request)(nil))
+	vdl.Register((*Response)(nil))
+}
diff --git a/runtime/internal/rpc/stream/proxy/proxy.go b/runtime/internal/rpc/stream/proxy/proxy.go
new file mode 100644
index 0000000..ee68f73
--- /dev/null
+++ b/runtime/internal/rpc/stream/proxy/proxy.go
@@ -0,0 +1,843 @@
+// 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 proxy
+
+import (
+	"fmt"
+	"net"
+	"reflect"
+	"sync"
+	"time"
+
+	"v.io/x/lib/netstate"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/verror"
+	"v.io/v23/vom"
+
+	"v.io/x/ref/runtime/internal/lib/bqueue"
+	"v.io/x/ref/runtime/internal/lib/bqueue/drrqueue"
+	"v.io/x/ref/runtime/internal/lib/iobuf"
+	"v.io/x/ref/runtime/internal/lib/publisher"
+	"v.io/x/ref/runtime/internal/lib/upcqueue"
+	inaming "v.io/x/ref/runtime/internal/naming"
+	"v.io/x/ref/runtime/internal/rpc/stream"
+	"v.io/x/ref/runtime/internal/rpc/stream/crypto"
+	"v.io/x/ref/runtime/internal/rpc/stream/id"
+	"v.io/x/ref/runtime/internal/rpc/stream/message"
+	"v.io/x/ref/runtime/internal/rpc/stream/vc"
+	"v.io/x/ref/runtime/internal/rpc/stream/vif"
+	iversion "v.io/x/ref/runtime/internal/rpc/version"
+
+	"v.io/x/ref/lib/stats"
+)
+
+const pkgPath = "v.io/x/ref/runtime/proxy"
+
+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.
+	errNoRoutingTableEntry       = reg(".errNoRoutingTableEntry", "routing table has no entry for the VC")
+	errProcessVanished           = reg(".errProcessVanished", "remote process vanished")
+	errDuplicateSetupVC          = reg(".errDuplicateSetupVC", "duplicate SetupVC request")
+	errVomEncodeResponse         = reg(".errVomEncodeResponse", "failed to encode response from proxy{:3}")
+	errNoRequest                 = reg(".errNoRequest", "unable to read Request{:3}")
+	errServerClosedByProxy       = reg(".errServerClosedByProxy", "server closed by proxy")
+	errRemoveServerVC            = reg(".errRemoveServerVC", "failed to remove server VC {3}{:4}")
+	errNetConnClosing            = reg(".errNetConnClosing", "net.Conn is closing")
+	errFailedToAcceptHealthCheck = reg(".errFailedToAcceptHealthCheck", "failed to accept health check flow")
+	errIncompatibleVersions      = reg(".errIncompatibleVersions", "{:3}")
+	errAlreadyProxied            = reg(".errAlreadyProxied", "server with routing id {3} is already being proxied")
+	errUnknownNetwork            = reg(".errUnknownNetwork", "unknown network {3}")
+	errListenFailed              = reg(".errListenFailed", "net.Listen({3}, {4}) failed{:5}")
+	errFailedToForwardRxBufs     = reg(".errFailedToForwardRxBufs", "failed to forward receive buffers{:3}")
+	errFailedToFowardDataMsg     = reg(".errFailedToFowardDataMsg", "failed to forward data message{:3}")
+	errFailedToFowardOpenFlow    = reg(".errFailedToFowardOpenFlow", "failed to forward open flow{:3}")
+	errServerNotBeingProxied     = reg(".errServerNotBeingProxied", "no server with routing id {3} is being proxied")
+	errServerVanished            = reg(".errServerVanished", "server with routing id {3} vanished")
+	errAccessibleAddresses       = reg(".errAccessibleAddresses", "failed to obtain a set of accessible addresses{:3}")
+	errNoAccessibleAddresses     = reg(".errNoAccessibleAddresses", "no accessible addresses were available for {3}")
+	errEmptyListenSpec           = reg(".errEmptyListenSpec", "no addresses supplied in the listen spec")
+)
+
+// Proxy routes virtual circuit (VC) traffic between multiple underlying
+// network connections.
+type Proxy struct {
+	ctx        *context.T
+	ln         net.Listener
+	rid        naming.RoutingID
+	principal  security.Principal
+	blessings  security.Blessings
+	authorizer security.Authorizer
+	mu         sync.RWMutex
+	servers    *servermap
+	processes  map[*process]struct{}
+	pubAddress string
+	statsName  string
+}
+
+// process encapsulates the physical network connection and the routing table
+// associated with the process at the other end of the network connection.
+type process struct {
+	proxy        *Proxy
+	ctx          *context.T
+	conn         net.Conn
+	pool         *iobuf.Pool
+	reader       *iobuf.Reader
+	ctrlCipher   crypto.ControlCipher
+	queue        *upcqueue.T
+	mu           sync.RWMutex
+	routingTable map[id.VC]*destination
+	nextVCI      id.VC
+	servers      map[id.VC]*vc.VC // servers wishing to be proxied create a VC that terminates at the proxy
+	bq           bqueue.T         // Flow control for messages sent on behalf of servers.
+}
+
+// destination is an entry in the routingtable of a process.
+type destination struct {
+	VCI     id.VC
+	Process *process
+}
+
+// server encapsulates information stored about a server exporting itself via the proxy.
+type server struct {
+	Process *process
+	VC      *vc.VC
+}
+
+func (s *server) RoutingID() naming.RoutingID { return s.VC.RemoteEndpoint().RoutingID() }
+
+func (s *server) Close(err error) {
+	if vc := s.Process.RemoveServerVC(s.VC.VCI()); vc != nil {
+		if err != nil {
+			vc.Close(verror.New(stream.ErrProxy, nil, verror.New(errRemoveServerVC, nil, s.VC.VCI(), err)))
+		} else {
+			vc.Close(verror.New(stream.ErrProxy, nil, verror.New(errServerClosedByProxy, nil)))
+		}
+		s.Process.SendCloseVC(s.VC.VCI(), err)
+	}
+}
+
+func (s *server) String() string {
+	return fmt.Sprintf("RoutingID %v on process %v (VCI:%v Blessings:%v)", s.RoutingID(), s.Process, s.VC.VCI(), s.VC.RemoteBlessings())
+}
+
+// servermap is a concurrent-access safe map from the RoutingID of a server exporting itself
+// through the proxy to the underlying network connection that the server is found on.
+type servermap struct {
+	ctx *context.T
+	mu  sync.Mutex
+	m   map[naming.RoutingID]*server
+}
+
+func (m *servermap) Add(server *server) error {
+	key := server.RoutingID()
+	m.mu.Lock()
+	defer m.mu.Unlock()
+	if m.m[key] != nil {
+		return verror.New(stream.ErrProxy, nil, verror.New(errAlreadyProxied, nil, key))
+	}
+	m.m[key] = server
+	proxyLog(m.ctx, "Started proxying server: %v", server)
+	return nil
+}
+
+func (m *servermap) Remove(server *server) {
+	key := server.RoutingID()
+	m.mu.Lock()
+	if m.m[key] != nil {
+		delete(m.m, key)
+		proxyLog(m.ctx, "Stopped proxying server: %v", server)
+	}
+	m.mu.Unlock()
+}
+
+func (m *servermap) Process(rid naming.RoutingID) *process {
+	m.mu.Lock()
+	defer m.mu.Unlock()
+	if s := m.m[rid]; s != nil {
+		return s.Process
+	}
+	return nil
+}
+
+func (m *servermap) List() []string {
+	m.mu.Lock()
+	defer m.mu.Unlock()
+	ret := make([]string, 0, len(m.m))
+	for _, s := range m.m {
+		ret = append(ret, s.String())
+	}
+	return ret
+}
+
+// New creates a new Proxy that listens for network connections on the provided
+// ListenSpec and routes VC traffic between accepted connections.
+//
+// Servers wanting to "listen through the proxy" will only be allowed to do so
+// if the blessings they present are accepted to the provided authorization
+// policy (authorizer).
+func New(ctx *context.T, spec rpc.ListenSpec, authorizer security.Authorizer, names ...string) (shutdown func(), endpoint naming.Endpoint, err error) {
+	rid, err := naming.NewRoutingID()
+	if err != nil {
+		return nil, nil, err
+	}
+	proxy, err := internalNew(rid, ctx, spec, authorizer)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var pub publisher.Publisher
+	for _, name := range names {
+		if name == "" {
+			// Consistent with v23.rpc.Server.Serve(...)
+			// an empty name implies, "do not publish"
+			continue
+		}
+		if pub == nil {
+			pub = publisher.New(ctx, v23.GetNamespace(ctx), time.Minute)
+			pub.AddServer(proxy.endpoint().String())
+		}
+		pub.AddName(name, false, true)
+	}
+
+	shutdown = func() {
+		if pub != nil {
+			pub.Stop()
+			pub.WaitForStop()
+		}
+		proxy.shutdown()
+	}
+	return shutdown, proxy.endpoint(), nil
+}
+
+func internalNew(rid naming.RoutingID, ctx *context.T, spec rpc.ListenSpec, authorizer security.Authorizer) (*Proxy, error) {
+	if len(spec.Addrs) == 0 {
+		return nil, verror.New(stream.ErrProxy, nil, verror.New(errEmptyListenSpec, nil))
+	}
+	laddr := spec.Addrs[0]
+	network := laddr.Protocol
+	address := laddr.Address
+	_, _, listenFn, _ := rpc.RegisteredProtocol(network)
+	if listenFn == nil {
+		return nil, verror.New(stream.ErrProxy, nil, verror.New(errUnknownNetwork, nil, network))
+	}
+	ln, err := listenFn(ctx, network, address)
+	if err != nil {
+		return nil, verror.New(stream.ErrProxy, nil, verror.New(errListenFailed, nil, network, address, err))
+	}
+	pub, _, err := netstate.PossibleAddresses(ln.Addr().Network(), ln.Addr().String(), spec.AddressChooser)
+	if err != nil {
+		ln.Close()
+		return nil, verror.New(stream.ErrProxy, nil, verror.New(errAccessibleAddresses, nil, err))
+	}
+	if len(pub) == 0 {
+		ln.Close()
+		return nil, verror.New(stream.ErrProxy, nil, verror.New(errNoAccessibleAddresses, nil, ln.Addr().String()))
+	}
+	if authorizer == nil {
+		authorizer = security.DefaultAuthorizer()
+	}
+	proxy := &Proxy{
+		ctx:        ctx,
+		ln:         ln,
+		rid:        rid,
+		authorizer: authorizer,
+		servers:    &servermap{ctx: ctx, m: make(map[naming.RoutingID]*server)},
+		processes:  make(map[*process]struct{}),
+		// TODO(cnicolaou): should use all of the available addresses
+		pubAddress: pub[0].String(),
+		principal:  v23.GetPrincipal(ctx),
+		statsName:  naming.Join("rpc", "proxy", "routing-id", rid.String(), "debug"),
+	}
+	if proxy.principal != nil {
+		proxy.blessings = proxy.principal.BlessingStore().Default()
+	}
+	stats.NewStringFunc(proxy.statsName, proxy.debugString)
+
+	go proxy.listenLoop()
+	return proxy, nil
+}
+
+func (p *Proxy) listenLoop() {
+	proxyLog(p.ctx, "Proxy listening on (%q, %q): %v", p.ln.Addr().Network(), p.ln.Addr(), p.endpoint())
+	for {
+		conn, err := p.ln.Accept()
+		if err != nil {
+			proxyLog(p.ctx, "Exiting listenLoop of proxy %q: %v", p.endpoint(), err)
+			return
+		}
+		go p.acceptProcess(conn)
+	}
+}
+
+func (p *Proxy) acceptProcess(conn net.Conn) {
+	pool := iobuf.NewPool(0)
+	reader := iobuf.NewReader(pool, conn)
+
+	var blessings security.Blessings
+	if p.principal != nil {
+		blessings = p.principal.BlessingStore().Default()
+	}
+
+	cipher, _, err := vif.AuthenticateAsServer(conn, reader, nil, nil, p.principal, blessings, nil)
+	if err != nil {
+		processLog(p.ctx, "Process %v failed to authenticate: %s", p, err)
+		return
+	}
+
+	process := &process{
+		ctx:          p.ctx,
+		proxy:        p,
+		conn:         conn,
+		pool:         pool,
+		reader:       reader,
+		ctrlCipher:   cipher,
+		queue:        upcqueue.New(),
+		routingTable: make(map[id.VC]*destination),
+		servers:      make(map[id.VC]*vc.VC),
+		bq:           drrqueue.New(vc.MaxPayloadSizeBytes),
+	}
+
+	p.mu.Lock()
+	if p.processes == nil {
+		// The proxy has been shutdowned.
+		p.mu.Unlock()
+		return
+	}
+	p.processes[process] = struct{}{}
+	p.mu.Unlock()
+
+	go process.serverVCsLoop()
+	go process.writeLoop()
+	go process.readLoop()
+
+	processLog(p.ctx, "Started process %v", process)
+}
+
+func (p *Proxy) removeProcess(process *process) {
+	p.mu.Lock()
+	delete(p.processes, process)
+	p.mu.Unlock()
+}
+
+func (p *Proxy) runServer(server *server, c <-chan vc.HandshakeResult) {
+	hr := <-c
+	if hr.Error != nil {
+		server.Close(hr.Error)
+		return
+	}
+	// See comments in protocol.vdl for the protocol between servers and the proxy.
+	conn, err := hr.Listener.Accept()
+	if err != nil {
+		server.Close(verror.New(stream.ErrProxy, nil, verror.New(errFailedToAcceptHealthCheck, nil)))
+		return
+	}
+	server.Process.InitVCI(server.VC.VCI())
+	var request Request
+	var response Response
+	dec := vom.NewDecoder(conn)
+	if err := dec.Decode(&request); err != nil {
+		response.Error = verror.New(stream.ErrProxy, nil, verror.New(errNoRequest, nil, err))
+	} else if err := p.authorize(server.VC, request); err != nil {
+		response.Error = err
+	} else if err := p.servers.Add(server); err != nil {
+		response.Error = verror.Convert(verror.ErrUnknown, nil, err)
+	} else {
+		defer p.servers.Remove(server)
+		proxyEP := p.endpoint()
+		ep := &inaming.Endpoint{
+			Protocol: proxyEP.Protocol,
+			Address:  proxyEP.Address,
+			RID:      server.VC.RemoteEndpoint().RoutingID(),
+		}
+		response.Endpoint = ep.String()
+	}
+	enc := vom.NewEncoder(conn)
+	if err := enc.Encode(response); err != nil {
+		proxyLog(p.ctx, "Failed to encode response %#v for server %v", response, server)
+		server.Close(verror.New(stream.ErrProxy, nil, verror.New(errVomEncodeResponse, nil, err)))
+		return
+	}
+	// Reject all other flows
+	go func() {
+		for {
+			flow, err := hr.Listener.Accept()
+			if err != nil {
+				return
+			}
+			flow.Close()
+		}
+	}()
+	// Wait for this flow to be closed.
+	<-conn.Closed()
+	server.Close(nil)
+}
+
+func (p *Proxy) authorize(vc *vc.VC, request Request) error {
+	var dmap map[string]security.Discharge
+	if len(request.Discharges) > 0 {
+		dmap = make(map[string]security.Discharge)
+		for _, d := range request.Discharges {
+			dmap[d.ID()] = d
+		}
+	}
+	// Blessings must be bound to the same public key as the VC.
+	// (Repeating logic in the RPC server authorization code).
+	if got, want := request.Blessings.PublicKey(), vc.RemoteBlessings().PublicKey(); !request.Blessings.IsZero() && !reflect.DeepEqual(got, want) {
+		return verror.New(verror.ErrNoAccess, nil, fmt.Errorf("malformed request: Blessings sent in proxy.Request are bound to public key %v and not %v", got, want))
+	}
+	return p.authorizer.Authorize(p.ctx, security.NewCall(&security.CallParams{
+		LocalPrincipal:   vc.LocalPrincipal(),
+		LocalBlessings:   vc.LocalBlessings(),
+		RemoteBlessings:  request.Blessings,
+		LocalEndpoint:    vc.LocalEndpoint(),
+		RemoteEndpoint:   vc.RemoteEndpoint(),
+		LocalDischarges:  vc.LocalDischarges(),
+		RemoteDischarges: dmap,
+	}))
+}
+
+func (p *Proxy) routeCounters(process *process, counters message.Counters) {
+	// Since each VC can be routed to a different process, split up the
+	// Counters into one message per VC.
+	// Ideally, would split into one message per process (rather than per
+	// flow). This optimization is left an as excercise to the interested.
+	for cid, bytes := range counters {
+		srcVCI := cid.VCI()
+		if vc := process.ServerVC(srcVCI); vc != nil {
+			vc.ReleaseCounters(cid.Flow(), bytes)
+			continue
+		}
+		if d := process.Route(srcVCI); d != nil {
+			c := message.NewCounters()
+			c.Add(d.VCI, cid.Flow(), bytes)
+			if err := d.Process.queue.Put(&message.AddReceiveBuffers{Counters: c}); err != nil {
+				process.RemoveRoute(srcVCI)
+				process.SendCloseVC(srcVCI, verror.New(stream.ErrProxy, nil, verror.New(errFailedToForwardRxBufs, nil, err)))
+			}
+		}
+	}
+}
+
+func startRoutingVC(ctx *context.T, srcVCI, dstVCI id.VC, srcProcess, dstProcess *process) {
+	dstProcess.AddRoute(dstVCI, &destination{VCI: srcVCI, Process: srcProcess})
+	srcProcess.AddRoute(srcVCI, &destination{VCI: dstVCI, Process: dstProcess})
+	vcLog(ctx, "Routing (VCI %d @ [%s]) <-> (VCI %d @ [%s])", srcVCI, srcProcess, dstVCI, dstProcess)
+}
+
+// Endpoint returns the endpoint of the proxy service.  By Dialing a VC to this
+// endpoint, processes can have their services exported through the proxy.
+func (p *Proxy) endpoint() *inaming.Endpoint {
+
+	ep := &inaming.Endpoint{
+		Protocol: p.ln.Addr().Network(),
+		Address:  p.pubAddress,
+		RID:      p.rid,
+	}
+	if prncpl := p.principal; prncpl != nil {
+		for b, _ := range prncpl.BlessingsInfo(prncpl.BlessingStore().Default()) {
+			ep.Blessings = append(ep.Blessings, b)
+		}
+	}
+	return ep
+}
+
+// Shutdown stops the proxy service, closing all network connections.
+func (p *Proxy) shutdown() {
+	stats.Delete(p.statsName)
+	p.ln.Close()
+	p.mu.Lock()
+	processes := p.processes
+	p.processes = nil
+	p.mu.Unlock()
+	for process, _ := range processes {
+		process.Close()
+	}
+}
+
+func (p *process) serverVCsLoop() {
+	for {
+		w, bufs, err := p.bq.Get(nil)
+		if err != nil {
+			return
+		}
+		vci, fid := unpackIDs(w.ID())
+		if vc := p.ServerVC(vci); vc != nil {
+			queueDataMessages(p.ctx, bufs, vc, fid, p.queue)
+			if len(bufs) == 0 {
+				m := &message.Data{VCI: vci, Flow: fid}
+				m.SetClose()
+				p.queue.Put(m)
+				w.Shutdown(true)
+			}
+			continue
+		}
+		releaseBufs(0, bufs)
+	}
+}
+
+func releaseBufs(start int, bufs []*iobuf.Slice) {
+	for _, buf := range bufs[start:] {
+		buf.Release()
+	}
+}
+
+func queueDataMessages(ctx *context.T, bufs []*iobuf.Slice, vc *vc.VC, fid id.Flow, q *upcqueue.T) {
+	for ix, b := range bufs {
+		m := &message.Data{VCI: vc.VCI(), Flow: fid}
+		var err error
+		if m.Payload, err = vc.Encrypt(fid, b); err != nil {
+			msgLog(ctx, "vc.Encrypt failed. VC:%v Flow:%v Error:%v", vc, fid, err)
+			releaseBufs(ix+1, bufs)
+			return
+		}
+		if err = q.Put(m); err != nil {
+			msgLog(ctx, "Failed to enqueue data message %v: %v", m, err)
+			m.Release()
+			releaseBufs(ix+1, bufs)
+			return
+		}
+	}
+}
+
+func (p *process) writeLoop() {
+	defer processLog(p.ctx, "Exited writeLoop for %v", p)
+	defer p.Close()
+
+	for {
+		item, err := p.queue.Get(nil)
+		if err != nil {
+			if err != upcqueue.ErrQueueIsClosed {
+				processLog(p.ctx, "upcqueue.Get failed on %v: %v", p, err)
+			}
+			return
+		}
+		if err = message.WriteTo(p.conn, item.(message.T), p.ctrlCipher); err != nil {
+			processLog(p.ctx, "message.WriteTo on %v failed: %v", p, err)
+			return
+		}
+	}
+}
+
+func (p *process) readLoop() {
+	defer processLog(p.ctx, "Exited readLoop for %v", p)
+	defer p.Close()
+
+	for {
+		msg, err := message.ReadFrom(p.reader, p.ctrlCipher)
+		if err != nil {
+			processLog(p.ctx, "Read on %v failed: %v", p, err)
+			return
+		}
+		msgLog(p.ctx, "Received msg: %T = %v", msg, msg)
+		switch m := msg.(type) {
+		case *message.Data:
+			if vc := p.ServerVC(m.VCI); vc != nil {
+				if err := vc.DispatchPayload(m.Flow, m.Payload); err != nil {
+					processLog(p.ctx, "Ignoring data message %v from process %v: %v", m, p, err)
+				}
+				if m.Close() {
+					vc.ShutdownFlow(m.Flow)
+				}
+				break
+			}
+			srcVCI := m.VCI
+			if d := p.Route(srcVCI); d != nil {
+				m.VCI = d.VCI
+				if err := d.Process.queue.Put(m); err != nil {
+					m.Release()
+					p.RemoveRoute(srcVCI)
+					p.SendCloseVC(srcVCI, verror.New(stream.ErrProxy, nil, verror.New(errFailedToFowardDataMsg, nil, err)))
+				}
+				break
+			}
+			p.SendCloseVC(srcVCI, verror.New(stream.ErrProxy, nil, verror.New(errNoRoutingTableEntry, nil)))
+		case *message.OpenFlow:
+			if vc := p.ServerVC(m.VCI); vc != nil {
+				if err := vc.AcceptFlow(m.Flow); err != nil {
+					processLog(p.ctx, "OpenFlow %+v on process %v failed: %v", m, p, err)
+					cm := &message.Data{VCI: m.VCI, Flow: m.Flow}
+					cm.SetClose()
+					p.queue.Put(cm)
+				}
+				vc.ReleaseCounters(m.Flow, m.InitialCounters)
+				break
+			}
+			srcVCI := m.VCI
+			if d := p.Route(srcVCI); d != nil {
+				m.VCI = d.VCI
+				if err := d.Process.queue.Put(m); err != nil {
+					p.RemoveRoute(srcVCI)
+					p.SendCloseVC(srcVCI, verror.New(stream.ErrProxy, nil, verror.New(errFailedToFowardOpenFlow, nil, err)))
+				}
+				break
+			}
+			p.SendCloseVC(srcVCI, verror.New(stream.ErrProxy, nil, verror.New(errNoRoutingTableEntry, nil)))
+		case *message.CloseVC:
+			if vc := p.RemoveServerVC(m.VCI); vc != nil {
+				vc.Close(verror.New(stream.ErrProxy, nil, verror.New(errRemoveServerVC, nil, m.VCI, m.Error)))
+				break
+			}
+			srcVCI := m.VCI
+			if d := p.Route(srcVCI); d != nil {
+				m.VCI = d.VCI
+				d.Process.queue.Put(m)
+				d.Process.RemoveRoute(d.VCI)
+			}
+			p.RemoveRoute(srcVCI)
+		case *message.AddReceiveBuffers:
+			p.proxy.routeCounters(p, m.Counters)
+		case *message.SetupVC:
+			// First let's ensure that we can speak a common protocol verison.
+			intersection, err := iversion.SupportedRange.Intersect(&m.Setup.Versions)
+			if err != nil {
+				p.SendCloseVC(m.VCI, verror.New(stream.ErrProxy, nil,
+					verror.New(errIncompatibleVersions, nil, err)))
+				break
+			}
+
+			dstrid := m.RemoteEndpoint.RoutingID()
+			if naming.Compare(dstrid, p.proxy.rid) || naming.Compare(dstrid, naming.NullRoutingID) {
+				// VC that terminates at the proxy.
+				// See protocol.vdl for details on the protocol between the server and the proxy.
+				vcObj := p.NewServerVC(p.ctx, m)
+				// route counters after creating the VC so counters to vc are not lost.
+				p.proxy.routeCounters(p, m.Counters)
+				if vcObj != nil {
+					server := &server{Process: p, VC: vcObj}
+					keyExchanger := func(pubKey *crypto.BoxKey) (*crypto.BoxKey, error) {
+						p.queue.Put(&message.SetupVC{
+							VCI: m.VCI,
+							Setup: message.Setup{
+								// Note that servers send clients not their actual supported versions,
+								// but the intersected range of the server and client ranges.  This
+								// is important because proxies may have adjusted the version ranges
+								// along the way, and we should negotiate a version that is compatible
+								// with all intermediate hops.
+								Versions: *intersection,
+								Options:  []message.SetupOption{&message.NaclBox{PublicKey: *pubKey}},
+							},
+							RemoteEndpoint: m.LocalEndpoint,
+							LocalEndpoint:  p.proxy.endpoint(),
+							// TODO(mattr): Consider adding counters.  See associated comment
+							// in vc.go:VC.HandshakeAcceptedVC for more details.
+						})
+						var theirPK *crypto.BoxKey
+						box := m.Setup.NaclBox()
+						if box != nil {
+							theirPK = &box.PublicKey
+						}
+						return theirPK, nil
+					}
+					go p.proxy.runServer(server, vcObj.HandshakeAcceptedVCWithAuthentication(intersection.Max, p.proxy.principal, p.proxy.blessings, keyExchanger))
+				}
+				break
+			}
+
+			srcVCI := m.VCI
+
+			d := p.Route(srcVCI)
+			if d == nil {
+				// SetupVC involves two messages: One sent by the initiator
+				// and one by the acceptor. The routing table gets setup on
+				// the first message, so if there is no route -
+				// setup a routing table entry.
+				dstprocess := p.proxy.servers.Process(dstrid)
+				if dstprocess == nil {
+					p.SendCloseVC(m.VCI, verror.New(stream.ErrProxy, nil, verror.New(errServerNotBeingProxied, nil, dstrid)))
+					p.proxy.routeCounters(p, m.Counters)
+					break
+				}
+				dstVCI := dstprocess.AllocVCI()
+				startRoutingVC(p.ctx, srcVCI, dstVCI, p, dstprocess)
+				if d = p.Route(srcVCI); d == nil {
+					p.SendCloseVC(srcVCI, verror.New(stream.ErrProxy, nil, verror.New(errServerVanished, nil, dstrid)))
+					p.proxy.routeCounters(p, m.Counters)
+					break
+				}
+			}
+
+			// Forward the SetupVC message.
+			// Typically, a SetupVC message is accompanied with
+			// Counters for the new VC.  Keep that in the forwarded
+			// message and route the remaining counters separately.
+			counters := m.Counters
+			m.Counters = message.NewCounters()
+			dstVCI := d.VCI
+			for cid, bytes := range counters {
+				if cid.VCI() == srcVCI {
+					m.Counters.Add(dstVCI, cid.Flow(), bytes)
+					delete(counters, cid)
+				}
+			}
+			m.VCI = dstVCI
+			// Note that proxies rewrite the version range so that the final negotiated
+			// version will be compatible with all intermediate hops.
+			m.Setup.Versions = *intersection
+			d.Process.queue.Put(m)
+			p.proxy.routeCounters(p, counters)
+
+		default:
+			processLog(p.ctx, "Closing %v because of invalid message %T", p, m)
+			return
+		}
+	}
+}
+
+func (p *process) String() string {
+	r := p.conn.RemoteAddr()
+	return fmt.Sprintf("(%s, %s)", r.Network(), r)
+}
+func (p *process) Route(vci id.VC) *destination {
+	p.mu.RLock()
+	defer p.mu.RUnlock()
+	return p.routingTable[vci]
+}
+func (p *process) AddRoute(vci id.VC, d *destination) {
+	p.mu.Lock()
+	p.routingTable[vci] = d
+	p.mu.Unlock()
+}
+func (p *process) InitVCI(vci id.VC) {
+	p.mu.Lock()
+	if p.nextVCI <= vci {
+		p.nextVCI = vci + 1
+	}
+	p.mu.Unlock()
+}
+func (p *process) AllocVCI() id.VC {
+	p.mu.Lock()
+	ret := p.nextVCI
+	p.nextVCI += 2
+	p.mu.Unlock()
+	return ret
+}
+func (p *process) RemoveRoute(vci id.VC) {
+	p.mu.Lock()
+	delete(p.routingTable, vci)
+	p.mu.Unlock()
+}
+func (p *process) SendCloseVC(vci id.VC, err error) {
+	var estr string
+	if err != nil {
+		estr = err.Error()
+	}
+	p.queue.Put(&message.CloseVC{VCI: vci, Error: estr})
+}
+
+func (p *process) Close() {
+	p.mu.Lock()
+	if p.routingTable == nil {
+		p.mu.Unlock()
+		return
+	}
+	rt := p.routingTable
+	p.routingTable = nil
+	for _, vc := range p.servers {
+		vc.Close(verror.New(stream.ErrProxy, nil, verror.New(errNetConnClosing, nil)))
+	}
+	p.mu.Unlock()
+	for _, d := range rt {
+		d.Process.SendCloseVC(d.VCI, verror.New(stream.ErrProxy, nil, verror.New(errProcessVanished, nil)))
+	}
+	p.bq.Close()
+	p.queue.Close()
+	p.conn.Close()
+
+	p.proxy.removeProcess(p)
+}
+
+func (p *process) ServerVC(vci id.VC) *vc.VC {
+	p.mu.Lock()
+	defer p.mu.Unlock()
+	return p.servers[vci]
+}
+
+func (p *process) NewServerVC(ctx *context.T, m *message.SetupVC) *vc.VC {
+	p.mu.Lock()
+	defer p.mu.Unlock()
+	if vc := p.servers[m.VCI]; vc != nil {
+		vc.Close(verror.New(stream.ErrProxy, nil, verror.New(errDuplicateSetupVC, nil)))
+		return nil
+	}
+	vc := vc.InternalNew(ctx, vc.Params{
+		VCI:          m.VCI,
+		LocalEP:      m.RemoteEndpoint,
+		RemoteEP:     m.LocalEndpoint,
+		Pool:         p.pool,
+		ReserveBytes: message.HeaderSizeBytes,
+		Helper:       p,
+	})
+	p.servers[m.VCI] = vc
+	proxyLog(p.ctx, "Registered VC %v from server on process %v", vc, p)
+	return vc
+}
+
+func (p *process) RemoveServerVC(vci id.VC) *vc.VC {
+	p.mu.Lock()
+	defer p.mu.Unlock()
+	if vc := p.servers[vci]; vc != nil {
+		delete(p.servers, vci)
+		proxyLog(p.ctx, "Unregistered server VC %v from process %v", vc, p)
+		return vc
+	}
+	return nil
+}
+
+// Make process implement vc.Helper
+func (p *process) NotifyOfNewFlow(vci id.VC, fid id.Flow, bytes uint) {
+	msg := &message.OpenFlow{VCI: vci, Flow: fid, InitialCounters: uint32(bytes)}
+	if err := p.queue.Put(msg); err != nil {
+		processLog(p.ctx, "Failed to send OpenFlow(%+v) on process %v: %v", msg, p, err)
+	}
+}
+
+func (p *process) AddReceiveBuffers(vci id.VC, fid id.Flow, bytes uint) {
+	if bytes == 0 {
+		return
+	}
+	msg := &message.AddReceiveBuffers{Counters: message.NewCounters()}
+	msg.Counters.Add(vci, fid, uint32(bytes))
+	if err := p.queue.Put(msg); err != nil {
+		processLog(p.ctx, "Failed to send AddReceiveBuffers(%+v) on process %v: %v", msg, p, err)
+	}
+}
+
+func (p *process) NewWriter(vci id.VC, fid id.Flow, priority bqueue.Priority) (bqueue.Writer, error) {
+	return p.bq.NewWriter(packIDs(vci, fid), priority, vc.DefaultBytesBufferedPerFlow)
+}
+
+// Convenience functions to assist with the logging convention.
+func proxyLog(ctx *context.T, format string, args ...interface{}) {
+	ctx.VI(1).Infof(format, args...)
+}
+func processLog(ctx *context.T, format string, args ...interface{}) {
+	ctx.VI(2).Infof(format, args...)
+}
+func vcLog(ctx *context.T, format string, args ...interface{}) {
+	ctx.VI(3).Infof(format, args...)
+}
+func msgLog(ctx *context.T, format string, args ...interface{}) {
+	ctx.VI(4).Infof(format, args...)
+}
+
+func packIDs(vci id.VC, fid id.Flow) bqueue.ID {
+	return bqueue.ID(message.MakeCounterID(vci, fid))
+}
+func unpackIDs(b bqueue.ID) (id.VC, id.Flow) {
+	cid := message.CounterID(b)
+	return cid.VCI(), cid.Flow()
+}
diff --git a/runtime/internal/rpc/stream/proxy/proxy_test.go b/runtime/internal/rpc/stream/proxy/proxy_test.go
new file mode 100644
index 0000000..23fc033
--- /dev/null
+++ b/runtime/internal/rpc/stream/proxy/proxy_test.go
@@ -0,0 +1,515 @@
+// 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 proxy_test
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"reflect"
+	"strings"
+	"testing"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/security"
+	"v.io/v23/verror"
+
+	_ "v.io/x/ref/runtime/factories/generic"
+	inaming "v.io/x/ref/runtime/internal/naming"
+	"v.io/x/ref/runtime/internal/rpc/stream"
+	"v.io/x/ref/runtime/internal/rpc/stream/manager"
+	"v.io/x/ref/runtime/internal/rpc/stream/proxy"
+	"v.io/x/ref/runtime/internal/rpc/stream/vc"
+	"v.io/x/ref/runtime/internal/rpc/stream/vif"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/testutil"
+)
+
+//go:generate v23 test generate
+
+func TestProxy(t *testing.T) {
+	ctx, shutdown := v23Init()
+	defer shutdown()
+	_, shutdown, proxyEp, err := proxy.InternalNew(naming.FixedRoutingID(0xbbbbbbbbbbbbbbbb), ctx, security.AllowEveryone())
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer shutdown()
+	principal := testutil.NewPrincipal("test")
+	ctx, _ = v23.WithPrincipal(ctx, principal)
+	blessings := principal.BlessingStore().Default()
+
+	// Create the stream.Manager for the server.
+	server1 := manager.InternalNew(ctx, naming.FixedRoutingID(0x1111111111111111))
+	defer server1.Shutdown()
+	// Setup a stream.Listener that will accept VCs and Flows routed
+	// through the proxy.
+	ln1, ep1, err := server1.Listen(ctx, proxyEp.Network(), proxyEp.String(), blessings)
+	if err != nil {
+		t.Logf(verror.DebugString(err))
+		t.Fatal(err)
+	}
+	defer ln1.Close()
+
+	// Create the stream.Manager for a second server.
+	server2 := manager.InternalNew(ctx, naming.FixedRoutingID(0x2222222222222222))
+	defer server2.Shutdown()
+	// Setup a stream.Listener that will accept VCs and Flows routed
+	// through the proxy.
+	ln2, ep2, err := server2.Listen(ctx, proxyEp.Network(), proxyEp.String(), blessings)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer ln2.Close()
+
+	// Create the stream.Manager for a client.
+	client := manager.InternalNew(ctx, naming.FixedRoutingID(0xcccccccccccccccc))
+	defer client.Shutdown()
+
+	cases := []struct {
+		client stream.Manager
+		ln     stream.Listener
+		ep     naming.Endpoint
+	}{
+		{client, ln1, ep1},  // client writing to server1
+		{server1, ln2, ep2}, // server1 writing to server2
+		{server1, ln1, ep1}, // server1 writing to itself
+	}
+
+	const written = "the dough rises"
+	for i, c := range cases {
+		name := fmt.Sprintf("case #%d(write to %v):", i, c.ep)
+		// Accept a single flow and write out what is read to readChan
+		readChan := make(chan string)
+		go readFlow(t, c.ln, readChan)
+		if err := writeFlow(ctx, c.client, c.ep, written); err != nil {
+			t.Errorf("%s: %v", name, err)
+			continue
+		}
+		// Validate that the data read is the same as the data written.
+		if read := <-readChan; read != written {
+			t.Errorf("case #%d: Read %q, wrote %q", i, read, written)
+		}
+	}
+}
+
+func TestProxyAuthorization(t *testing.T) {
+	ctx, shutdown := v23Init()
+	defer shutdown()
+	_, shutdown, proxyEp, err := proxy.InternalNew(naming.FixedRoutingID(0xbbbbbbbbbbbbbbbb), ctx, testAuth{"alice", "carol"})
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer shutdown()
+
+	var (
+		alice = testutil.NewPrincipal("alice")
+		bob   = testutil.NewPrincipal("bob")
+		carol = testutil.NewPrincipal("carol")
+		dave  = testutil.NewPrincipal("dave")
+	)
+	// Make the proxy recognize "alice", "bob" and "carol", but not "dave"
+	v23.GetPrincipal(ctx).AddToRoots(alice.BlessingStore().Default())
+	v23.GetPrincipal(ctx).AddToRoots(bob.BlessingStore().Default())
+	v23.GetPrincipal(ctx).AddToRoots(carol.BlessingStore().Default())
+
+	testcases := []struct {
+		p  security.Principal
+		ok bool
+	}{
+		{alice, true}, // passes the auth policy
+		{bob, false},  // recognized, but not included in auth policy
+		{carol, true}, // passes the auth policy
+		{dave, false}, // not recognized, thus doesn't pass the auth policy
+	}
+	for idx, test := range testcases {
+		server := manager.InternalNew(ctx, naming.FixedRoutingID(uint64(idx)))
+		nctx, _ := v23.WithPrincipal(ctx, test.p)
+		_, ep, err := server.Listen(nctx, proxyEp.Network(), proxyEp.String(), test.p.BlessingStore().Default(), proxyAuth{test.p})
+		if (err == nil) != test.ok {
+			t.Errorf("Got ep=%v, err=%v - wanted error:%v", ep, err, !test.ok)
+		}
+		server.Shutdown()
+	}
+}
+
+type proxyAuth struct {
+	p security.Principal
+}
+
+func (proxyAuth) RPCStreamListenerOpt() {}
+func (a proxyAuth) Login(stream.Flow) (security.Blessings, []security.Discharge, error) {
+	return a.p.BlessingStore().Default(), nil, nil
+}
+
+func TestDuplicateRoutingID(t *testing.T) {
+	ctx, shutdown := v23Init()
+	defer shutdown()
+
+	_, shutdown, proxyEp, err := proxy.InternalNew(naming.FixedRoutingID(0xbbbbbbbbbbbbbbbb), ctx, security.AllowEveryone())
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer shutdown()
+
+	// Create the stream.Manager for server1 and server2, both with the same routing ID
+	serverRID := naming.FixedRoutingID(0x5555555555555555)
+	server1 := manager.InternalNew(ctx, serverRID)
+	server2 := manager.InternalNew(ctx, serverRID)
+	defer server1.Shutdown()
+	defer server2.Shutdown()
+
+	principal := testutil.NewPrincipal("test")
+	ctx, _ = v23.WithPrincipal(ctx, principal)
+	blessings := principal.BlessingStore().Default()
+
+	// First server to claim serverRID should win.
+	ln1, ep1, err := server1.Listen(ctx, proxyEp.Network(), proxyEp.String(), blessings)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer ln1.Close()
+
+	ln2, ep2, err := server2.Listen(ctx, proxyEp.Network(), proxyEp.String(), blessings)
+	if pattern := "routing id 00000000000000005555555555555555 is already being proxied"; err == nil || !strings.Contains(err.Error(), pattern) {
+		t.Errorf("Got (%v, %v, %v) want error \"...%v\" (ep1:%v)", ln2, ep2, err, pattern, ep1)
+	}
+}
+
+func TestProxyAuthentication(t *testing.T) {
+	ctx, shutdown := v23Init()
+	defer shutdown()
+
+	pproxy := v23.GetPrincipal(ctx)
+	_, shutdown, proxyEp, err := proxy.InternalNew(naming.FixedRoutingID(0xbbbbbbbbbbbbbbbb), ctx, security.AllowEveryone())
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer shutdown()
+	if got, want := proxyEp.BlessingNames(), []string{"proxy"}; !reflect.DeepEqual(got, want) {
+		t.Errorf("Proxy endpoint blessing names: got %v, want %v", got, want)
+	}
+
+	other := manager.InternalNew(ctx, naming.FixedRoutingID(0xcccccccccccccccc))
+	defer other.Shutdown()
+
+	nctx, _ := v23.WithPrincipal(ctx, testutil.NewPrincipal("other"))
+	vc, err := other.Dial(nctx, proxyEp)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	flow, err := vc.Connect()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if got, want := flow.RemoteBlessings(), pproxy.BlessingStore().Default(); !reflect.DeepEqual(got, want) {
+		t.Errorf("Proxy authenticated as [%v], want [%v]", got, want)
+	}
+}
+
+func TestServerBlessings(t *testing.T) {
+	ctx, shutdown := v23Init()
+	defer shutdown()
+
+	var (
+		pserver = testutil.NewPrincipal("server")
+		pclient = testutil.NewPrincipal("client")
+	)
+	cctx, _ := v23.WithPrincipal(ctx, pclient)
+	sctx, _ := v23.WithPrincipal(ctx, pserver)
+
+	_, shutdown, proxyEp, err := proxy.InternalNew(naming.FixedRoutingID(0xbbbbbbbbbbbbbbbb), ctx, security.AllowEveryone())
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer shutdown()
+	if got, want := proxyEp.BlessingNames(), []string{"proxy"}; !reflect.DeepEqual(got, want) {
+		t.Errorf("Proxy endpoint blessing names: got %v, want %v", got, want)
+	}
+
+	server := manager.InternalNew(ctx, naming.FixedRoutingID(0x5555555555555555))
+	defer server.Shutdown()
+
+	ln, ep, err := server.Listen(sctx, proxyEp.Network(), proxyEp.String(), pserver.BlessingStore().Default())
+	if err != nil {
+		t.Fatal(err)
+	}
+	if got, want := ep.BlessingNames(), []string{"server"}; !reflect.DeepEqual(got, want) {
+		t.Errorf("Server endpoint %q: Got BlessingNames %v, want %v", ep, got, want)
+	}
+	defer ln.Close()
+	go func() {
+		for {
+			if _, err := ln.Accept(); err != nil {
+				return
+			}
+		}
+	}()
+
+	client := manager.InternalNew(ctx, naming.FixedRoutingID(0xcccccccccccccccc))
+	defer client.Shutdown()
+	vc, err := client.Dial(cctx, ep)
+	if err != nil {
+		t.Fatal(err)
+	}
+	flow, err := vc.Connect()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if got, want := flow.RemoteBlessings(), pserver.BlessingStore().Default(); !reflect.DeepEqual(got, want) {
+		t.Errorf("Got [%v] want [%v]", got, want)
+	}
+}
+
+func TestHostPort(t *testing.T) {
+	ctx, shutdown := v23Init()
+	defer shutdown()
+
+	_, shutdown, proxyEp, err := proxy.InternalNew(naming.FixedRoutingID(0xbbbbbbbbbbbbbbbb), ctx, security.AllowEveryone())
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer shutdown()
+	server := manager.InternalNew(ctx, naming.FixedRoutingID(0x5555555555555555))
+	defer server.Shutdown()
+	addr := proxyEp.Addr().String()
+	port := addr[strings.LastIndex(addr, ":"):]
+	principal := testutil.NewPrincipal("test")
+	ctx, _ = v23.WithPrincipal(ctx, principal)
+	blessings := principal.BlessingStore().Default()
+	ln, _, err := server.Listen(ctx, inaming.Network, "127.0.0.1"+port, blessings)
+	if err != nil {
+		t.Fatal(err)
+	}
+	ln.Close()
+}
+
+func TestClientBecomesServer(t *testing.T) {
+	ctx, shutdown := v23Init()
+	defer shutdown()
+
+	_, shutdown, proxyEp, err := proxy.InternalNew(naming.FixedRoutingID(0xbbbbbbbbbbbbbbbb), ctx, security.AllowEveryone())
+	if err != nil {
+		t.Fatal(err)
+	}
+	server := manager.InternalNew(ctx, naming.FixedRoutingID(0x5555555555555555))
+	client1 := manager.InternalNew(ctx, naming.FixedRoutingID(0x1111111111111111))
+	client2 := manager.InternalNew(ctx, naming.FixedRoutingID(0x2222222222222222))
+	defer shutdown()
+	defer server.Shutdown()
+	defer client1.Shutdown()
+	defer client2.Shutdown()
+
+	principal := testutil.NewPrincipal("test")
+	sctx, _ := v23.WithPrincipal(ctx, principal)
+	blessings := principal.BlessingStore().Default()
+	lnS, epS, err := server.Listen(sctx, proxyEp.Network(), proxyEp.String(), blessings)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer lnS.Close()
+	rchan := make(chan string)
+
+	pclient1 := testutil.NewPrincipal("client1")
+	cctx, _ := v23.WithPrincipal(ctx, pclient1)
+
+	// client1 must connect to the proxy to speak to the server.
+	// Keep a VC and Flow open to the server, to ensure that the proxy
+	// maintains routing information (at some point, inactive VIFs
+	// should be garbage collected, so this ensures that the VIF
+	// is "active")
+	if vc, err := client1.Dial(cctx, epS); err != nil {
+		t.Fatal(err)
+	} else if flow, err := vc.Connect(); err != nil {
+		t.Fatal(err)
+	} else {
+		defer flow.Close()
+	}
+
+	// Now client1 becomes a server
+	lnC, epC, err := client1.Listen(cctx, proxyEp.Network(), proxyEp.String(), pclient1.BlessingStore().Default())
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer lnC.Close()
+	// client2 should be able to talk to client1 through the proxy
+	rchan = make(chan string)
+	go readFlow(t, lnC, rchan)
+	if err := writeFlow(ctx, client2, epC, "daffy duck"); err != nil {
+		t.Fatalf("client2 failed to chat with client1: %v", err)
+	}
+	if got, want := <-rchan, "daffy duck"; got != want {
+		t.Fatalf("client2->client1 got %q want %q", got, want)
+	}
+}
+
+func testProxyIdleTimeout(t *testing.T, testServer bool) {
+	ctx, shutdown := v23Init()
+	defer shutdown()
+
+	const (
+		idleTime = 10 * time.Millisecond
+		// We use a long wait time here since it takes some time to handle VC close
+		// especially in race testing.
+		waitTime = 150 * time.Millisecond
+	)
+
+	var (
+		pserver = testutil.NewPrincipal("server")
+		pclient = testutil.NewPrincipal("client")
+
+		opts  []stream.VCOpt
+		lopts []stream.ListenerOpt
+	)
+	if testServer {
+		lopts = []stream.ListenerOpt{vc.IdleTimeout{Duration: idleTime}}
+	} else {
+		opts = []stream.VCOpt{vc.IdleTimeout{Duration: idleTime}}
+	}
+	cctx, _ := v23.WithPrincipal(ctx, pclient)
+	sctx, _ := v23.WithPrincipal(ctx, pserver)
+
+	// Pause the idle timers.
+	triggerTimers := vif.SetFakeTimers()
+
+	Proxy, shutdown, proxyEp, err := proxy.InternalNew(naming.FixedRoutingID(0xbbbbbbbbbbbbbbbb), ctx, security.AllowEveryone())
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer shutdown()
+
+	// Create the stream.Manager for the server.
+	server := manager.InternalNew(ctx, naming.FixedRoutingID(0x1111111111111111))
+	defer server.Shutdown()
+	// Setup a stream.Listener that will accept VCs and Flows routed
+	// through the proxy.
+	ln, ep, err := server.Listen(sctx, proxyEp.Network(), proxyEp.String(), pserver.BlessingStore().Default(), lopts...)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer ln.Close()
+
+	// Create the stream.Manager for a client.
+	client := manager.InternalNew(ctx, naming.FixedRoutingID(0xcccccccccccccccc))
+	defer client.Shutdown()
+
+	// Open a VC and a Flow.
+	VC, err := client.Dial(cctx, ep, opts...)
+	if err != nil {
+		t.Fatal(err)
+	}
+	flow, err := VC.Connect()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if _, err = ln.Accept(); err != nil {
+		t.Fatal(err)
+	}
+
+	// Trigger the idle timers.
+	triggerTimers()
+
+	if numProcs := proxy.NumProcesses(Proxy); numProcs != 2 {
+		// There should be two processes at this point.
+		t.Fatal(fmt.Errorf("Unexpected number of processes: %d\n", numProcs))
+	}
+
+	// There is one active flow. The VC should be kept open.
+	time.Sleep(waitTime)
+	if numProcs := proxy.NumProcesses(Proxy); numProcs != 2 {
+		t.Errorf("Want VC is kept open, but closed: number of processes: %d", numProcs)
+	}
+
+	flow.Close()
+
+	// The flow has been closed. The VC should be closed after idle timeout.
+	for range time.Tick(idleTime) {
+		if proxy.NumProcesses(Proxy) == 1 {
+			break
+		}
+	}
+
+	client.ShutdownEndpoint(ep)
+
+	// Even when the idle timeout is set for VC in server, we still should be
+	// able to dial to the server through the proxy, since one VC between the
+	// server and the proxy should be kept alive as the proxy protocol.
+	//
+	// We use fake timers here again to avoid idle timeout during dialing.
+	defer vif.SetFakeTimers()()
+	if _, err := client.Dial(cctx, ep, opts...); err != nil {
+		t.Errorf("Want to dial to the server; can't dial: %v", err)
+	}
+}
+
+func TestProxyIdleTimeout(t *testing.T)       { testProxyIdleTimeout(t, false) }
+func TestProxyIdleTimeoutServer(t *testing.T) { testProxyIdleTimeout(t, true) }
+
+func writeFlow(ctx *context.T, mgr stream.Manager, ep naming.Endpoint, data string) error {
+	ctx, _ = v23.WithPrincipal(ctx, testutil.NewPrincipal("test"))
+	vc, err := mgr.Dial(ctx, ep)
+	if err != nil {
+		return fmt.Errorf("manager.Dial(%v) failed: %v", ep, err)
+	}
+	flow, err := vc.Connect()
+	if err != nil {
+		return fmt.Errorf("vc.Connect failed: %v", err)
+	}
+	defer flow.Close()
+	if _, err := flow.Write([]byte(data)); err != nil {
+		return fmt.Errorf("flow.Write failed: %v", err)
+	}
+	return nil
+}
+
+func readFlow(t *testing.T, ln stream.Listener, read chan<- string) {
+	defer close(read)
+	flow, err := ln.Accept()
+	if err != nil {
+		t.Error(err)
+		return
+	}
+	var tmp [1024]byte
+	var buf bytes.Buffer
+	for {
+		n, err := flow.Read(tmp[:])
+		if err == io.EOF {
+			read <- buf.String()
+			return
+		}
+		if err != nil {
+			t.Error(err)
+			return
+		}
+		buf.Write(tmp[:n])
+	}
+}
+
+func v23Init() (*context.T, func()) {
+	ctx, shutdown := test.V23Init()
+	ctx, err := v23.WithPrincipal(ctx, testutil.NewPrincipal("proxy"))
+	if err != nil {
+		panic(err)
+	}
+	return ctx, shutdown
+}
+
+type testAuth []string
+
+func (l testAuth) Authorize(ctx *context.T, call security.Call) error {
+	remote, rejected := security.RemoteBlessingNames(ctx, call)
+	for _, n := range remote {
+		for _, a := range l {
+			if n == a {
+				return nil
+			}
+		}
+	}
+	return fmt.Errorf("%v not in authorized set of %v (rejected: %v)", remote, l, rejected)
+}
diff --git a/runtime/internal/rpc/stream/proxy/testutil_test.go b/runtime/internal/rpc/stream/proxy/testutil_test.go
new file mode 100644
index 0000000..727b8a5
--- /dev/null
+++ b/runtime/internal/rpc/stream/proxy/testutil_test.go
@@ -0,0 +1,28 @@
+// 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 proxy
+
+import (
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/security"
+)
+
+// These are the internal functions only for use in the proxy_test package.
+
+func InternalNew(rid naming.RoutingID, ctx *context.T, auth security.Authorizer) (*Proxy, func(), naming.Endpoint, error) {
+	proxy, err := internalNew(rid, ctx, v23.GetListenSpec(ctx), auth)
+	if err != nil {
+		return nil, nil, nil, err
+	}
+	return proxy, proxy.shutdown, proxy.endpoint(), err
+}
+
+func NumProcesses(proxy *Proxy) int {
+	proxy.mu.Lock()
+	defer proxy.mu.Unlock()
+	return len(proxy.processes)
+}
diff --git a/runtime/internal/rpc/stream/proxy/v23_internal_test.go b/runtime/internal/rpc/stream/proxy/v23_internal_test.go
new file mode 100644
index 0000000..73903ec
--- /dev/null
+++ b/runtime/internal/rpc/stream/proxy/v23_internal_test.go
@@ -0,0 +1,20 @@
+// 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 proxy
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	os.Exit(m.Run())
+}
diff --git a/runtime/internal/rpc/stream/util.go b/runtime/internal/rpc/stream/util.go
new file mode 100644
index 0000000..e79cc3d
--- /dev/null
+++ b/runtime/internal/rpc/stream/util.go
@@ -0,0 +1,42 @@
+// 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 stream
+
+import (
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/security"
+)
+
+// IMPORTANT:
+// It's essential that the ctx not be accessed when authentication
+// is not requested. This is because the context initialization code
+// uses an unauthenticated connection to obtain a principal!
+
+func GetPrincipalVCOpts(ctx *context.T, opts ...VCOpt) security.Principal {
+	for _, opt := range opts {
+		switch v := opt.(type) {
+		case AuthenticatedVC:
+			if bool(v) == false {
+				return nil
+			}
+			return v23.GetPrincipal(ctx)
+		}
+	}
+	return v23.GetPrincipal(ctx)
+}
+
+func GetPrincipalListenerOpts(ctx *context.T, opts ...ListenerOpt) security.Principal {
+	for _, opt := range opts {
+		switch v := opt.(type) {
+		case AuthenticatedVC:
+			if bool(v) == false {
+				return nil
+			}
+			return v23.GetPrincipal(ctx)
+		}
+	}
+	return v23.GetPrincipal(ctx)
+}
diff --git a/runtime/internal/rpc/stream/vc/auth.go b/runtime/internal/rpc/stream/vc/auth.go
new file mode 100644
index 0000000..b85a4f1
--- /dev/null
+++ b/runtime/internal/rpc/stream/vc/auth.go
@@ -0,0 +1,207 @@
+// 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 vc
+
+import (
+	"bytes"
+	"io"
+
+	"v.io/v23/rpc/version"
+	"v.io/v23/security"
+	"v.io/v23/verror"
+	"v.io/v23/vom"
+
+	"v.io/x/ref/runtime/internal/lib/iobuf"
+	"v.io/x/ref/runtime/internal/rpc/stream"
+	"v.io/x/ref/runtime/internal/rpc/stream/crypto"
+)
+
+var (
+	authServerContextTag = []byte("VCauthS\x00")
+	authClientContextTag = []byte("VCauthC\x00")
+)
+
+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.
+	errVomEncodeBlessing            = reg(".errVomEncodeRequest", "failed to encode blessing{:3}")
+	errHandshakeMessage             = reg(".errHandshakeMessage", "failed to read hanshake message{:3}")
+	errInvalidSignatureInMessage    = reg(".errInvalidSignatureInMessage", "signature does not verify in authentication handshake message")
+	errFailedToCreateSelfBlessing   = reg(".errFailedToCreateSelfBlessing", "failed to create self blessing{:3}")
+	errNoBlessingsToPresentToServer = reg(".errerrNoBlessingsToPresentToServer ", "no blessings to present as a server")
+)
+
+// TODO(jhahn): Add len(ChannelBinding) > 0 check in writeBlessing/readBlessings
+// to make sure that the auth protocol only works when len(ChannelBinding) > 0
+// once we deprecate RPCv10.
+
+// AuthenticateAsServer executes the authentication protocol at the server.
+// It returns the blessings shared by the client, and the discharges shared
+// by the server.
+func AuthenticateAsServer(conn io.ReadWriteCloser, crypter crypto.Crypter, v version.RPCVersion, principal security.Principal, server security.Blessings, dc DischargeClient) (security.Blessings, map[string]security.Discharge, error) {
+	if server.IsZero() {
+		return security.Blessings{}, nil, verror.New(stream.ErrSecurity, nil, verror.New(errNoBlessingsToPresentToServer, nil))
+	}
+	var serverDischarges []security.Discharge
+	if tpcavs := server.ThirdPartyCaveats(); len(tpcavs) > 0 && dc != nil {
+		serverDischarges = dc.PrepareDischarges(nil, tpcavs, security.DischargeImpetus{})
+	}
+	if err := writeBlessings(conn, authServerContextTag, crypter, principal, server, serverDischarges, v); err != nil {
+		return security.Blessings{}, nil, err
+	}
+	// Note that since the client uses a self-signed blessing to authenticate
+	// during VC setup, it does not share any discharges.
+	client, _, err := readBlessings(conn, authClientContextTag, crypter, v)
+	if err != nil {
+		return security.Blessings{}, nil, err
+	}
+	return client, mkDischargeMap(serverDischarges), nil
+}
+
+// AuthenticateAsClient executes the authentication protocol at the client.
+// It returns the blessing shared by the client, and the blessings and any
+// discharges shared by the server.
+//
+// The client will only share its blessings if the server (who shares its
+// blessings first) is authorized as per the authorizer for this RPC.
+func AuthenticateAsClient(conn io.ReadWriteCloser, crypter crypto.Crypter, v version.RPCVersion, params security.CallParams, auth *ServerAuthorizer) (security.Blessings, security.Blessings, map[string]security.Discharge, error) {
+	server, serverDischarges, err := readBlessings(conn, authServerContextTag, crypter, v)
+	if err != nil {
+		return security.Blessings{}, security.Blessings{}, nil, err
+	}
+	// Authorize the server based on the provided authorizer.
+	if auth != nil {
+		params.RemoteBlessings = server
+		params.RemoteDischarges = serverDischarges
+		if err := auth.Authorize(params); err != nil {
+			// Note this error type should match with the one in HandshakeDialedVCPreAuthenticated().
+			return security.Blessings{}, security.Blessings{}, nil, verror.New(stream.ErrNotTrusted, nil, err)
+		}
+	}
+
+	// The client shares its blessings at RPC time (as the blessings may vary
+	// across RPCs). During VC handshake, the client simply sends a self-signed
+	// blessing in order to reveal its public key to the server.
+	principal := params.LocalPrincipal
+	client, err := principal.BlessSelf("vcauth")
+	if err != nil {
+		return security.Blessings{}, security.Blessings{}, nil, verror.New(stream.ErrSecurity, nil, verror.New(errFailedToCreateSelfBlessing, nil, err))
+	}
+	if err := writeBlessings(conn, authClientContextTag, crypter, principal, client, nil, v); err != nil {
+		return security.Blessings{}, security.Blessings{}, nil, err
+	}
+	return client, server, serverDischarges, nil
+}
+
+func writeBlessings(w io.Writer, tag []byte, crypter crypto.Crypter, p security.Principal, b security.Blessings, discharges []security.Discharge, v version.RPCVersion) error {
+	signature, err := p.Sign(append(tag, crypter.ChannelBinding()...))
+	if err != nil {
+		return err
+	}
+	var buf bytes.Buffer
+	enc := vom.NewEncoder(&buf)
+	if err := enc.Encode(signature); err != nil {
+		return verror.New(stream.ErrNetwork, nil, verror.New(errVomEncodeBlessing, nil, err))
+	}
+	if err := enc.Encode(b); err != nil {
+		return verror.New(stream.ErrNetwork, nil, verror.New(errVomEncodeBlessing, nil, err))
+	}
+	if err := enc.Encode(discharges); err != nil {
+		return verror.New(stream.ErrNetwork, nil, verror.New(errVomEncodeBlessing, nil, err))
+	}
+	msg, err := crypter.Encrypt(iobuf.NewSlice(buf.Bytes()))
+	if err != nil {
+		return err
+	}
+	defer msg.Release()
+	enc = vom.NewEncoder(w)
+	if err := enc.Encode(msg.Contents); err != nil {
+		return verror.New(stream.ErrNetwork, nil, verror.New(errVomEncodeBlessing, nil, err))
+	}
+	return nil
+}
+
+func readBlessings(r io.Reader, tag []byte, crypter crypto.Crypter, v version.RPCVersion) (security.Blessings, map[string]security.Discharge, error) {
+	var msg []byte
+	var noBlessings security.Blessings
+	dec := vom.NewDecoder(r)
+	if err := dec.Decode(&msg); err != nil {
+		return noBlessings, nil, verror.New(stream.ErrNetwork, nil, verror.New(errHandshakeMessage, nil, err))
+	}
+	buf, err := crypter.Decrypt(iobuf.NewSlice(msg))
+	if err != nil {
+		return noBlessings, nil, err
+	}
+	defer buf.Release()
+	dec = vom.NewDecoder(bytes.NewReader(buf.Contents))
+	var (
+		blessings security.Blessings
+		sig       security.Signature
+	)
+	if err = dec.Decode(&sig); err != nil {
+		return noBlessings, nil, verror.New(stream.ErrNetwork, nil, err)
+	}
+	if err = dec.Decode(&blessings); err != nil {
+		return noBlessings, nil, verror.New(stream.ErrNetwork, nil, err)
+	}
+	var discharges []security.Discharge
+	if err := dec.Decode(&discharges); err != nil {
+		return noBlessings, nil, verror.New(stream.ErrNetwork, nil, err)
+	}
+	if !sig.Verify(blessings.PublicKey(), append(tag, crypter.ChannelBinding()...)) {
+		return noBlessings, nil, verror.New(stream.ErrSecurity, nil, verror.New(errInvalidSignatureInMessage, nil))
+	}
+	return blessings, mkDischargeMap(discharges), nil
+}
+
+func mkDischargeMap(discharges []security.Discharge) map[string]security.Discharge {
+	if len(discharges) == 0 {
+		return nil
+	}
+	m := make(map[string]security.Discharge, len(discharges))
+	for _, d := range discharges {
+		m[d.ID()] = d
+	}
+	return m
+}
+
+func bindClientPrincipalToChannel(crypter crypto.Crypter, p security.Principal) ([]byte, error) {
+	sig, err := p.Sign(append(authClientContextTag, crypter.ChannelBinding()...))
+	if err != nil {
+		return nil, err
+	}
+	var buf bytes.Buffer
+	enc := vom.NewEncoder(&buf)
+	if err := enc.Encode(sig); err != nil {
+		return nil, verror.New(errVomEncodeBlessing, nil, err)
+	}
+	msg, err := crypter.Encrypt(iobuf.NewSlice(buf.Bytes()))
+	if err != nil {
+		return nil, err
+	}
+	defer msg.Release()
+	signature := make([]byte, len(msg.Contents))
+	copy(signature, msg.Contents)
+	return signature, nil
+}
+
+func verifyClientPrincipalBoundToChannel(signature []byte, crypter crypto.Crypter, publicKey security.PublicKey) error {
+	msg, err := crypter.Decrypt(iobuf.NewSlice(signature))
+	if err != nil {
+		return err
+	}
+	defer msg.Release()
+	dec := vom.NewDecoder(bytes.NewReader(msg.Contents))
+	var sig security.Signature
+	if err = dec.Decode(&sig); err != nil {
+		return verror.New(errHandshakeMessage, nil, err)
+	}
+	if !sig.Verify(publicKey, append(authClientContextTag, crypter.ChannelBinding()...)) {
+		return verror.New(errInvalidSignatureInMessage, nil)
+	}
+	return nil
+}
diff --git a/runtime/internal/rpc/stream/vc/data_cache.go b/runtime/internal/rpc/stream/vc/data_cache.go
new file mode 100644
index 0000000..6c5b56c
--- /dev/null
+++ b/runtime/internal/rpc/stream/vc/data_cache.go
@@ -0,0 +1,65 @@
+// 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 vc
+
+import (
+	"sync"
+)
+
+// dataCache is a thread-safe map for any two types.
+type dataCache struct {
+	sync.RWMutex
+	m map[interface{}]interface{}
+}
+
+func newDataCache() *dataCache {
+	return &dataCache{m: make(map[interface{}]interface{})}
+}
+
+// Get returns the value stored under the key.
+func (c *dataCache) Get(key interface{}) interface{} {
+	c.RLock()
+	value, _ := c.m[key]
+	c.RUnlock()
+	return value
+}
+
+// Insert the given key and value into the cache if and only if the given key
+// did not already exist in the cache. Returns true if the key-value pair was
+// inserted; otherwise returns false.
+func (c *dataCache) Insert(key interface{}, value interface{}) bool {
+	c.Lock()
+	defer c.Unlock()
+	if _, exists := c.m[key]; exists {
+		return false
+	}
+	c.m[key] = value
+	return true
+}
+
+// GetOrInsert first checks if the key exists in the cache with a reader lock.
+// If it doesn't exist, it instead acquires a writer lock, creates and stores the new value
+// with create and returns value.
+func (c *dataCache) GetOrInsert(key interface{}, create func() interface{}) interface{} {
+	// We use the read lock for the fastpath. This should be the more common case, so we rarely
+	// need a writer lock.
+	c.RLock()
+	value, exists := c.m[key]
+	c.RUnlock()
+	if exists {
+		return value
+	}
+	// We acquire the writer lock for the slowpath, and need to re-check if the key exists
+	// in the map, since other thread may have snuck in.
+	c.Lock()
+	defer c.Unlock()
+	value, exists = c.m[key]
+	if exists {
+		return value
+	}
+	value = create()
+	c.m[key] = value
+	return value
+}
diff --git a/runtime/internal/rpc/stream/vc/doc.go b/runtime/internal/rpc/stream/vc/doc.go
new file mode 100644
index 0000000..62c34df
--- /dev/null
+++ b/runtime/internal/rpc/stream/vc/doc.go
@@ -0,0 +1,6 @@
+// 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 vc provides implementations of the VC and Flow interfaces in v.io/x/ref/runtime/internal/rpc/stream.
+package vc
diff --git a/runtime/internal/rpc/stream/vc/flow.go b/runtime/internal/rpc/stream/vc/flow.go
new file mode 100644
index 0000000..d5f2d2f
--- /dev/null
+++ b/runtime/internal/rpc/stream/vc/flow.go
@@ -0,0 +1,58 @@
+// 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 vc
+
+import (
+	"v.io/v23/naming"
+	"v.io/v23/security"
+
+	"v.io/x/ref/runtime/internal/rpc/stream"
+)
+
+type flow struct {
+	backingVC
+	*reader
+	*writer
+}
+
+type backingVC interface {
+	LocalEndpoint() naming.Endpoint
+	RemoteEndpoint() naming.Endpoint
+
+	LocalPrincipal() security.Principal
+	LocalBlessings() security.Blessings
+	RemoteBlessings() security.Blessings
+	LocalDischarges() map[string]security.Discharge
+	RemoteDischarges() map[string]security.Discharge
+
+	VCDataCache() stream.VCDataCache
+}
+
+func (f *flow) Close() error {
+	f.reader.Close()
+	f.writer.Close()
+	return nil
+}
+
+// SetDeadline sets a deadline channel on the flow.  Reads and writes
+// will be cancelled if the channel is closed.
+func (f *flow) SetDeadline(deadline <-chan struct{}) {
+	f.reader.SetDeadline(deadline)
+	f.writer.SetDeadline(deadline)
+}
+
+// Shutdown closes the flow and discards any queued up write buffers.
+// This is appropriate when the flow has been closed by the remote end.
+func (f *flow) Shutdown() {
+	f.reader.Close()
+	f.writer.shutdown(true)
+}
+
+// Cancel closes the flow and discards any queued up write buffers.
+// This is appropriate when the flow is being cancelled locally.
+func (f *flow) Cancel() {
+	f.reader.Close()
+	f.writer.shutdown(false)
+}
diff --git a/runtime/internal/rpc/stream/vc/knobs.go b/runtime/internal/rpc/stream/vc/knobs.go
new file mode 100644
index 0000000..7271f7a
--- /dev/null
+++ b/runtime/internal/rpc/stream/vc/knobs.go
@@ -0,0 +1,35 @@
+// 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 vc
+
+const (
+	// Maximum size (in bytes) of application data to write out in a single message.
+	MaxPayloadSizeBytes = 1 << 16 // 64KB
+
+	// Number of bytes that a receiver is willing to buffer for a flow.
+	DefaultBytesBufferedPerFlow = 1 << 20 // 1MB
+
+	// Maximum number of bytes to steal from the shared pool of receive
+	// buffers for the first write of a new Flow.
+	MaxSharedBytes = 1 << 12 // 4KB
+
+	// Number of VC IDs reserved for special use.
+	NumReservedVCs = 10
+
+	// Number of Flow IDs reserved for possible future use.
+	NumReservedFlows = 10
+
+	// Special Flow ID used for information specific to the VC
+	// (and not any specific flow)
+	SharedFlowID = 0
+
+	// Special flow used for authenticating between VCs.
+	AuthFlowID = 2
+	// Special flow used for interchanging of VOM types between VCs.
+	TypeFlowID = 3
+	// Special flow over which discharges for third-party caveats
+	// on the server's blessings are sent.
+	DischargeFlowID = 4
+)
diff --git a/runtime/internal/rpc/stream/vc/listener.go b/runtime/internal/rpc/stream/vc/listener.go
new file mode 100644
index 0000000..820a3a0
--- /dev/null
+++ b/runtime/internal/rpc/stream/vc/listener.go
@@ -0,0 +1,56 @@
+// 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 vc
+
+import (
+	"v.io/v23/verror"
+
+	"v.io/x/ref/internal/logger"
+	"v.io/x/ref/runtime/internal/lib/upcqueue"
+	"v.io/x/ref/runtime/internal/rpc/stream"
+)
+
+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.
+	errListenerClosed = reg(".errListenerClosed", "listener has been closed")
+	errGetFromQueue   = reg(".errGetFromQueue", "upcqueue.Get failed{:3}")
+)
+
+type listener struct {
+	q *upcqueue.T
+}
+
+var _ stream.Listener = (*listener)(nil)
+
+func newListener() *listener { return &listener{q: upcqueue.New()} }
+
+func (l *listener) Enqueue(f stream.Flow) error {
+	err := l.q.Put(f)
+	if err == upcqueue.ErrQueueIsClosed {
+		logger.Global().VI(3).Infof("Listener closed: %p, %p", l, l.q)
+		return verror.New(stream.ErrBadState, nil, verror.New(errListenerClosed, nil))
+	}
+	return err
+}
+
+func (l *listener) Accept() (stream.Flow, error) {
+	item, err := l.q.Get(nil)
+	if err == upcqueue.ErrQueueIsClosed {
+		return nil, verror.New(stream.ErrBadState, nil, verror.New(errListenerClosed, nil))
+	}
+	if err != nil {
+		return nil, verror.New(stream.ErrNetwork, nil, verror.New(errGetFromQueue, nil, err))
+	}
+	return item.(stream.Flow), nil
+}
+
+func (l *listener) Close() error {
+	logger.Global().VI(3).Infof("Listener being closed: %p, %p", l, l.q)
+	l.q.Close()
+	return nil
+}
diff --git a/runtime/internal/rpc/stream/vc/listener_test.go b/runtime/internal/rpc/stream/vc/listener_test.go
new file mode 100644
index 0000000..4dbf4ad
--- /dev/null
+++ b/runtime/internal/rpc/stream/vc/listener_test.go
@@ -0,0 +1,68 @@
+// 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 vc
+
+import (
+	"strings"
+	"testing"
+
+	"v.io/v23/naming"
+	"v.io/v23/security"
+	"v.io/v23/verror"
+
+	"v.io/x/ref/runtime/internal/rpc/stream"
+)
+
+type noopFlow struct{}
+
+// net.Conn methods
+func (*noopFlow) Read([]byte) (int, error)        { return 0, nil }
+func (*noopFlow) Write([]byte) (int, error)       { return 0, nil }
+func (*noopFlow) Close() error                    { return nil }
+func (*noopFlow) IsClosed() bool                  { return false }
+func (*noopFlow) Closed() <-chan struct{}         { return nil }
+func (*noopFlow) Cancel()                         {}
+func (*noopFlow) LocalEndpoint() naming.Endpoint  { return nil }
+func (*noopFlow) RemoteEndpoint() naming.Endpoint { return nil }
+
+// Other stream.Flow methods
+func (*noopFlow) LocalPrincipal() security.Principal              { return nil }
+func (*noopFlow) LocalBlessings() security.Blessings              { return security.Blessings{} }
+func (*noopFlow) RemoteBlessings() security.Blessings             { return security.Blessings{} }
+func (*noopFlow) LocalDischarges() map[string]security.Discharge  { return nil }
+func (*noopFlow) RemoteDischarges() map[string]security.Discharge { return nil }
+func (*noopFlow) SetDeadline(<-chan struct{})                     {}
+func (*noopFlow) VCDataCache() stream.VCDataCache                 { return nil }
+
+func TestListener(t *testing.T) {
+	ln := newListener()
+	f1, f2 := &noopFlow{}, &noopFlow{}
+
+	if err := ln.Enqueue(f1); err != nil {
+		t.Error(err)
+	}
+	if err := ln.Enqueue(f2); err != nil {
+		t.Error(err)
+	}
+	if f, err := ln.Accept(); f != f1 || err != nil {
+		t.Errorf("Got (%v, %v) want (%v, nil)", f, err, f1)
+	}
+	if f, err := ln.Accept(); f != f2 || err != nil {
+		t.Errorf("Got (%v, %v) want (%v, nil)", f, err, f2)
+	}
+	if err := ln.Close(); err != nil {
+		t.Error(err)
+	}
+	// Close-ing multiple times is fine.
+	if err := ln.Close(); err != nil {
+		t.Error(err)
+	}
+	if err := ln.Enqueue(f1); verror.ErrorID(err) != stream.ErrBadState.ID || !strings.Contains(err.Error(), "closed") {
+		t.Error(err)
+	}
+	if f, err := ln.Accept(); f != nil || verror.ErrorID(err) != stream.ErrBadState.ID || !strings.Contains(err.Error(), "closed") {
+		t.Errorf("Accept returned (%v, %v) wanted (nil, %v)", f, err, errListenerClosed)
+	}
+}
diff --git a/runtime/internal/rpc/stream/vc/reader.go b/runtime/internal/rpc/stream/vc/reader.go
new file mode 100644
index 0000000..127d451
--- /dev/null
+++ b/runtime/internal/rpc/stream/vc/reader.go
@@ -0,0 +1,115 @@
+// 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 vc
+
+import (
+	"io"
+	"sync"
+	"sync/atomic"
+
+	"v.io/v23/verror"
+
+	"v.io/x/ref/runtime/internal/lib/iobuf"
+	vsync "v.io/x/ref/runtime/internal/lib/sync"
+	"v.io/x/ref/runtime/internal/lib/upcqueue"
+	"v.io/x/ref/runtime/internal/rpc/stream"
+)
+
+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.
+	errGetFailed = reg(".errGetFailed", "upcqueue.Get failed:{:3}")
+)
+
+// readHandler is the interface used by the reader to notify other components
+// of the number of bytes returned in Read calls.
+type readHandler interface {
+	HandleRead(bytes uint)
+}
+
+// reader implements the io.Reader and SetReadDeadline interfaces for a Flow,
+// backed by iobuf.Slice objects read from a upcqueue.
+type reader struct {
+	handler    readHandler
+	src        *upcqueue.T
+	mu         sync.Mutex
+	buf        *iobuf.Slice    // GUARDED_BY(mu)
+	deadline   <-chan struct{} // GUARDED_BY(mu)
+	totalBytes uint32
+}
+
+func newReader(h readHandler) *reader {
+	return &reader{handler: h, src: upcqueue.New()}
+}
+
+func (r *reader) Close() {
+	r.src.Close()
+}
+
+func (r *reader) Read(b []byte) (int, error) {
+	// net.Conn requires that all methods be invokable by multiple
+	// goroutines simultaneously. Read calls are serialized to ensure
+	// contiguous chunks of data are provided from each Read call.
+	r.mu.Lock()
+	n, err := r.readLocked(b)
+	r.mu.Unlock()
+	atomic.AddUint32(&r.totalBytes, uint32(n))
+	if n > 0 {
+		r.handler.HandleRead(uint(n))
+	}
+	return n, err
+}
+
+func (r *reader) readLocked(b []byte) (int, error) {
+	if r.buf == nil {
+		slice, err := r.src.Get(r.deadline)
+		if err != nil {
+			switch err {
+			case upcqueue.ErrQueueIsClosed:
+				return 0, io.EOF
+			case vsync.ErrCanceled:
+				// As per net.Conn.Read specification
+				return 0, stream.NewNetError(verror.New(stream.ErrNetwork, nil, verror.New(errCanceled, nil)), true, false)
+			default:
+				return 0, verror.New(stream.ErrNetwork, nil, verror.New(errGetFailed, nil, err))
+			}
+		}
+		r.buf = slice.(*iobuf.Slice)
+	}
+	copied := 0
+	for r.buf.Size() <= len(b) {
+		n := copy(b, r.buf.Contents)
+		copied += n
+		b = b[n:]
+		r.buf.Release()
+		r.buf = nil
+
+		slice, err := r.src.TryGet()
+		if err != nil {
+			return copied, nil
+		}
+		r.buf = slice.(*iobuf.Slice)
+	}
+	n := copy(b, r.buf.Contents)
+	r.buf.TruncateFront(uint(n))
+	copied += n
+	return copied, nil
+}
+
+func (r *reader) SetDeadline(deadline <-chan struct{}) {
+	r.mu.Lock()
+	r.deadline = deadline
+	r.mu.Unlock()
+}
+
+func (r *reader) BytesRead() uint32 {
+	return atomic.LoadUint32(&r.totalBytes)
+}
+
+func (r *reader) Put(slice *iobuf.Slice) error {
+	return r.src.Put(slice)
+}
diff --git a/runtime/internal/rpc/stream/vc/reader_test.go b/runtime/internal/rpc/stream/vc/reader_test.go
new file mode 100644
index 0000000..7fac16f
--- /dev/null
+++ b/runtime/internal/rpc/stream/vc/reader_test.go
@@ -0,0 +1,112 @@
+// 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 vc
+
+import (
+	"io"
+	"net"
+	"reflect"
+	"testing"
+	"testing/quick"
+
+	"v.io/x/ref/runtime/internal/lib/iobuf"
+)
+
+type testReadHandler struct{ items []uint }
+
+func (t *testReadHandler) HandleRead(bytes uint) {
+	t.items = append(t.items, bytes)
+}
+
+func TestRead(t *testing.T) {
+	l := &testReadHandler{}
+	r := newReader(l)
+	input := []byte("abcdefghijklmnopqrstuvwxyzABCDE") // 31 bytes total
+	start := 0
+	// Produce data to read, adding elements to the underlying upcqueue
+	// with a geometric progression of 2.
+	for n := 1; start < len(input); n *= 2 {
+		if err := r.Put(iobuf.NewSlice(input[start : start+n])); err != nil {
+			t.Fatalf("Put(start=%d, n=%d) failed: %v", start, n, err)
+		}
+		start = start + n
+	}
+
+	var output [31]byte
+	start = 0
+	// Read with geometric progression of 1/2.
+	for n := 16; start < len(output); n /= 2 {
+		if m, err := r.Read(output[start : start+n]); err != nil || m != n {
+			t.Errorf("Read returned (%d, %v) want (%d, nil)", m, err, n)
+		}
+		if m := l.items[len(l.items)-1]; m != uint(n) {
+			t.Errorf("Read notified %d but should have notified %d bytes", m, n)
+		}
+		start = start + n
+	}
+	if got, want := string(output[:]), string(input); got != want {
+		t.Errorf("Got %q want %q", got, want)
+	}
+
+	r.Close()
+	if n, err := r.Read(output[:]); n != 0 || err != io.EOF {
+		t.Errorf("Got (%d, %v) want (0, nil)", n, err)
+	}
+}
+
+func TestReadRandom(t *testing.T) {
+	f := func(data [][]byte) bool {
+		r := newReader(&testReadHandler{})
+		// Use an empty slice (as opposed to a nil-slice) so that the
+		// reflect.DeepEqual call below succeeds when data is
+		// [][]byte{}.
+		written := make([]byte, 0)
+		for _, d := range data {
+			if err := r.Put(iobuf.NewSlice(d)); err != nil {
+				t.Error(err)
+				return false
+			}
+			written = append(written, d...)
+		}
+		read := make([]byte, len(written))
+		buf := read
+		r.Close()
+		for {
+			n, err := r.Read(buf)
+			if err == io.EOF {
+				break
+			}
+			if err != nil {
+				t.Error(err)
+				return false
+			}
+			buf = buf[n:]
+		}
+		return reflect.DeepEqual(written, read) && int(r.BytesRead()) == len(written)
+	}
+	if err := quick.Check(f, nil); err != nil {
+		t.Error(err)
+	}
+}
+
+func TestReadDeadline(t *testing.T) {
+	l := &testReadHandler{}
+	r := newReader(l)
+	defer r.Close()
+
+	deadline := make(chan struct{}, 0)
+	r.SetDeadline(deadline)
+	close(deadline)
+
+	var buf [1]byte
+	n, err := r.Read(buf[:])
+	neterr, ok := err.(net.Error)
+	if n != 0 || err == nil || !ok || !neterr.Timeout() {
+		t.Errorf("Expected read to fail with net.Error.Timeout, got (%d, %v)", n, err)
+	}
+	if len(l.items) != 0 {
+		t.Errorf("Expected no reads, but notified of reads: %v", l.items)
+	}
+}
diff --git a/runtime/internal/rpc/stream/vc/v23_internal_test.go b/runtime/internal/rpc/stream/vc/v23_internal_test.go
new file mode 100644
index 0000000..a48d6f6
--- /dev/null
+++ b/runtime/internal/rpc/stream/vc/v23_internal_test.go
@@ -0,0 +1,20 @@
+// 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 vc
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	os.Exit(m.Run())
+}
diff --git a/runtime/internal/rpc/stream/vc/vc.go b/runtime/internal/rpc/stream/vc/vc.go
new file mode 100644
index 0000000..4a383aa
--- /dev/null
+++ b/runtime/internal/rpc/stream/vc/vc.go
@@ -0,0 +1,1134 @@
+// 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 vc
+
+// Logging guidelines:
+// Verbosity level 1 is for per-VC messages.
+// Verbosity level 2 is for per-Flow messages.
+
+import (
+	"fmt"
+	"io"
+	"sort"
+	"strings"
+	"sync"
+	"time"
+
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/rpc/version"
+	"v.io/v23/security"
+	"v.io/v23/verror"
+	"v.io/v23/vom"
+
+	"v.io/x/ref/lib/apilog"
+	"v.io/x/ref/runtime/internal/lib/bqueue"
+	"v.io/x/ref/runtime/internal/lib/iobuf"
+	vsync "v.io/x/ref/runtime/internal/lib/sync"
+	"v.io/x/ref/runtime/internal/rpc/stream"
+	"v.io/x/ref/runtime/internal/rpc/stream/crypto"
+	"v.io/x/ref/runtime/internal/rpc/stream/id"
+)
+
+const pkgPath = "v.io/x/ref/runtime/internal/rpc/stream/vc"
+
+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.
+	errAlreadyListening               = reg(".errAlreadyListening", "Listen has already been called")
+	errDuplicateFlow                  = reg(".errDuplicateFlow", "duplicate OpenFlow message")
+	errUnrecognizedFlow               = reg(".errUnrecognizedFlow", "unrecognized flow")
+	errFailedToCreateWriterForFlow    = reg(".errFailedToCreateWriterForFlow", "failed to create writer for Flow{:3}")
+	errConnectOnClosedVC              = reg(".errConnectOnClosedVC", "connect on closed VC{:3}")
+	errHandshakeNotInProgress         = reg(".errHandshakeNotInProgress", "Attempted to finish a VC handshake, but no handshake was in progress{:3}")
+	errClosedDuringHandshake          = reg(".errCloseDuringHandshake", "VC closed during handshake{:3}")
+	errFailedToDecryptPayload         = reg(".errFailedToDecryptPayload", "failed to decrypt payload{:3}")
+	errIgnoringMessageOnClosedVC      = reg(".errIgnoringMessageOnClosedVC", "ignoring message for Flow {3} on closed VC {4}")
+	errFailedToCreateFlowForAuth      = reg(".errFailedToCreateFlowForAuth", "failed to create a Flow for authentication{:3}")
+	errAuthFlowNotAccepted            = reg(".errAuthFlowNotAccepted", "authentication Flow not accepted{:3}")
+	errFailedToCreateFlowForWireType  = reg(".errFailedToCreateFlowForWireType", "fail to create a Flow for wire type{:3}")
+	errFlowForWireTypeNotAccepted     = reg(".errFlowForWireTypeNotAccepted", "Flow for wire type not accepted{:3}")
+	errFailedToCreateFlowForDischarge = reg(".errFailedToCreateFlowForDischarge", "fail to create a Flow for discharge{:3}")
+	errFlowForDischargeNotAccepted    = reg(".errFlowForDischargesNotAccepted", "Flow for discharge not accepted{:3}")
+	errFailedToSetupEncryption        = reg(".errFailedToSetupEncryption", "failed to setup channel encryption{:3}")
+	errAuthFailed                     = reg(".errAuthFailed", "authentication failed{:3}")
+	errNoActiveListener               = reg(".errNoActiveListener", "no active listener on VCI {3}")
+	errFailedToCreateWriterForNewFlow = reg(".errFailedToCreateWriterForNewFlow", "failed to create writer for new flow({3}){:4}")
+	errFailedToEnqueueFlow            = reg(".errFailedToEnqueueFlow", "failed to enqueue flow at listener{:3}")
+	errFailedToAcceptSystemFlows      = reg(".errFailedToAcceptSystemFlows", "failed to accept system flows{:3}")
+)
+
+// DischargeExpiryBuffer specifies how much before discharge expiration we should
+// refresh discharges.
+// Discharges will be refreshed DischargeExpiryBuffer before they expire.
+type DischargeExpiryBuffer time.Duration
+
+func (DischargeExpiryBuffer) RPCStreamListenerOpt() {}
+func (DischargeExpiryBuffer) RPCServerOpt() {
+	defer apilog.LogCall(nil)(nil) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+}
+
+const DefaultServerDischargeExpiryBuffer = 20 * time.Second
+
+// DataCache Keys for TypeEncoder/Decoder.
+type TypeEncoderKey struct{}
+type TypeDecoderKey struct{}
+
+// VC implements the stream.VC interface and exports additional methods to
+// manage Flows.
+//
+// stream.Flow objects created by this stream.VC implementation use a buffer
+// queue (v.io/x/ref/runtime/internal/lib/bqueue) to provide flow control on Write
+// operations.
+type VC struct {
+	ctx                             *context.T
+	vci                             id.VC
+	localEP, remoteEP               naming.Endpoint
+	localPrincipal                  security.Principal
+	localBlessings, remoteBlessings security.Blessings
+	localDischarges                 map[string]security.Discharge // Discharges shared by the local end of the VC.
+	remoteDischarges                map[string]security.Discharge // Discharges shared by the remote end of the VC.
+
+	pool           *iobuf.Pool
+	reserveBytes   uint
+	sharedCounters *vsync.Semaphore
+
+	mu                  sync.Mutex
+	flowMap             map[id.Flow]*flow // nil iff the VC is closed.
+	acceptHandshakeDone chan struct{}     // non-nil when HandshakeAcceptedVC begins the handshake, closed when handshake completes.
+	nextConnectFID      id.Flow
+	listener            *listener // non-nil iff Listen has been called and the VC has not been closed.
+	crypter             crypto.Crypter
+	closeReason         error // reason why the VC was closed, possibly nil
+	closeCh             chan struct{}
+	closed              bool
+	version             version.RPCVersion
+	remotePubKeyChan    chan *crypto.BoxKey // channel which will receive the remote public key during setup.
+
+	helper    Helper
+	dataCache *dataCache // dataCache contains information that can shared between Flows from this VC.
+	loopWG    sync.WaitGroup
+}
+
+// ServerAuthorizer encapsulates the policy used to authorize servers during VC
+// establishment.
+//
+// A client will first authorize a server before revealing any of its credentials
+// (public key, blessings etc.) to the server. Thus, if the authorization policy
+// calls for the server to be rejected, then the client will not have revealed
+// any of its credentials to the server.
+//
+// ServerAuthorizer in turn uses an authorization policy (security.Authorizer),
+// with the context matching the context of the RPC that caused the initiation
+// of the VC.
+type ServerAuthorizer struct {
+	Suffix, Method string
+	Policy         security.Authorizer
+}
+
+func (a *ServerAuthorizer) RPCStreamVCOpt() {}
+func (a *ServerAuthorizer) Authorize(params security.CallParams) error {
+	params.Suffix = a.Suffix
+	params.Method = a.Method
+	ctx, cancel := context.RootContext()
+	defer cancel()
+	return a.Policy.Authorize(ctx, security.NewCall(&params))
+}
+
+// StartTimeout specifies the time after which the underlying VIF is closed
+// if no VC is opened.
+type StartTimeout struct{ time.Duration }
+
+func (StartTimeout) RPCStreamVCOpt()       {}
+func (StartTimeout) RPCStreamListenerOpt() {}
+
+// IdleTimeout specifies the time after which an idle VC is closed.
+type IdleTimeout struct{ time.Duration }
+
+func (IdleTimeout) RPCStreamVCOpt()       {}
+func (IdleTimeout) RPCStreamListenerOpt() {}
+
+var _ stream.VC = (*VC)(nil)
+
+// Helper is the interface for functionality required by the stream.VC
+// implementation in this package.
+type Helper interface {
+	// NotifyOfNewFlow notifies the remote end of a VC that the caller intends to
+	// establish a new flow to it and that the caller is ready to receive bytes
+	// data from the remote end.
+	NotifyOfNewFlow(vci id.VC, fid id.Flow, bytes uint)
+
+	// AddReceiveBuffers notifies the remote end of a VC that it is read to receive
+	// bytes more data on the flow identified by fid over the VC identified by vci.
+	//
+	// Unlike NotifyOfNewFlow, this call does not let the remote end know of the
+	// intent to establish a new flow.
+	AddReceiveBuffers(vci id.VC, fid id.Flow, bytes uint)
+
+	// NewWriter creates a buffer queue for Write operations on the
+	// stream.Flow implementation.
+	NewWriter(vci id.VC, fid id.Flow, priority bqueue.Priority) (bqueue.Writer, error)
+}
+
+// Priorities of flows.
+const (
+	systemFlowPriority bqueue.Priority = iota
+	normalFlowPriority
+
+	NumFlowPriorities
+)
+
+// DischargeClient is an interface for obtaining discharges for a set of third-party
+// caveats.
+//
+// TODO(ataly, ashankar): What should be the impetus for obtaining the discharges?
+type DischargeClient interface {
+	PrepareDischarges(ctx *context.T, forcaveats []security.Caveat, impetus security.DischargeImpetus) []security.Discharge
+	// Invalidate marks the provided discharges as invalid, and therefore unfit
+	// for being returned by a subsequent PrepareDischarges call.
+	Invalidate(ctx *context.T, discharges ...security.Discharge)
+	RPCStreamListenerOpt()
+}
+
+// Params encapsulates the set of parameters needed to create a new VC.
+type Params struct {
+	VCI          id.VC           // Identifier of the VC
+	Dialed       bool            // True if the VC was initiated by the local process.
+	LocalEP      naming.Endpoint // Endpoint of the local end of the VC.
+	RemoteEP     naming.Endpoint // Endpoint of the remote end of the VC.
+	Pool         *iobuf.Pool     // Byte pool used for read and write buffer allocations.
+	ReserveBytes uint            // Number of padding bytes to reserve for headers.
+	Helper       Helper
+}
+
+// InternalNew creates a new VC, which implements the stream.VC interface.
+//
+// As the name suggests, this method is intended for use only within packages
+// placed inside v.io/x/ref/runtime/internal. Code outside the
+// v.io/x/ref/runtime/internal/* packages should never call this method.
+func InternalNew(ctx *context.T, p Params) *VC {
+	fidOffset := 1
+	if p.Dialed {
+		fidOffset = 0
+	}
+	return &VC{
+		ctx:            ctx,
+		vci:            p.VCI,
+		localEP:        p.LocalEP,
+		remoteEP:       p.RemoteEP,
+		pool:           p.Pool,
+		reserveBytes:   p.ReserveBytes,
+		sharedCounters: vsync.NewSemaphore(),
+		flowMap:        make(map[id.Flow]*flow),
+		// Reserve flow IDs 0 thru NumReservedFlows for
+		// possible future use.
+		// Furthermore, flows created by Connect have an even
+		// id if the VC was initiated by the local process,
+		// and have an odd id if the VC was initiated by the
+		// remote process.
+		nextConnectFID: id.Flow(NumReservedFlows + fidOffset),
+		crypter:        crypto.NewNullCrypter(),
+		closeCh:        make(chan struct{}),
+		helper:         p.Helper,
+		dataCache:      newDataCache(),
+	}
+}
+
+// Connect implements the stream.Connector.Connect method.
+func (vc *VC) Connect(opts ...stream.FlowOpt) (stream.Flow, error) {
+	return vc.connectFID(vc.allocFID(), normalFlowPriority, opts...)
+}
+
+func (vc *VC) Version() version.RPCVersion {
+	vc.mu.Lock()
+	defer vc.mu.Unlock()
+	return vc.version
+}
+
+func (vc *VC) connectFID(fid id.Flow, priority bqueue.Priority, opts ...stream.FlowOpt) (stream.Flow, error) {
+	writer, err := vc.newWriter(fid, priority)
+	if err != nil {
+		return nil, verror.New(stream.ErrNetwork, nil, verror.New(errFailedToCreateWriterForFlow, nil, err))
+	}
+	f := &flow{
+		backingVC: vc,
+		reader:    newReader(readHandlerImpl{vc, fid}),
+		writer:    writer,
+	}
+	vc.mu.Lock()
+	if vc.flowMap == nil {
+		vc.mu.Unlock()
+		f.Shutdown()
+		return nil, verror.New(stream.ErrNetwork, nil, verror.New(errConnectOnClosedVC, nil, vc.closeReason))
+	}
+	vc.flowMap[fid] = f
+	vc.mu.Unlock()
+	// New flow created, inform remote end that data can be received on it.
+	vc.helper.NotifyOfNewFlow(vc.vci, fid, DefaultBytesBufferedPerFlow)
+	return f, nil
+}
+
+// Listen implements the stream.VC.Listen method.
+func (vc *VC) Listen() (stream.Listener, error) {
+	vc.mu.Lock()
+	defer vc.mu.Unlock()
+	if vc.listener != nil {
+		return nil, verror.New(stream.ErrBadState, nil, verror.New(errAlreadyListening, nil))
+	}
+	vc.listener = newListener()
+	return vc.listener, nil
+}
+
+// DispatchPayload makes payload.Contents available to Read operations on the
+// Flow identified by fid.
+//
+// Assumes ownership of payload, i.e., payload should not be used by the caller
+// after this method returns (irrespective of the return value).
+func (vc *VC) DispatchPayload(fid id.Flow, payload *iobuf.Slice) error {
+	if payload.Size() == 0 {
+		payload.Release()
+		return nil
+	}
+	vc.mu.Lock()
+	if vc.flowMap == nil {
+		vc.mu.Unlock()
+		payload.Release()
+		return verror.New(stream.ErrNetwork, nil, verror.New(errIgnoringMessageOnClosedVC, nil, fid, vc.VCI()))
+	}
+	// Authentication is done by encrypting/decrypting its payload by itself,
+	// so we do not go through with the decryption for auth flow.
+	if fid != AuthFlowID {
+		vc.waitForHandshakeLocked()
+		var err error
+		if payload, err = vc.crypter.Decrypt(payload); err != nil {
+			vc.mu.Unlock()
+			return verror.New(stream.ErrSecurity, nil, verror.New(errFailedToDecryptPayload, nil, err))
+		}
+	}
+	if payload.Size() == 0 {
+		vc.mu.Unlock()
+		payload.Release()
+		return nil
+	}
+	f := vc.flowMap[fid]
+	if f == nil {
+		vc.mu.Unlock()
+		payload.Release()
+		return verror.New(stream.ErrNetwork, nil, verror.New(errDuplicateFlow, nil))
+	}
+	vc.mu.Unlock()
+	if err := f.reader.Put(payload); err != nil {
+		payload.Release()
+		return verror.New(stream.ErrNetwork, nil, err)
+	}
+	return nil
+}
+
+// AcceptFlow enqueues a new Flow for acceptance by the listener on the VC.
+// Returns an error if the VC is not accepting flows initiated by the remote
+// end.
+func (vc *VC) AcceptFlow(fid id.Flow) error {
+	vc.mu.Lock()
+	defer vc.mu.Unlock()
+	if vc.listener == nil {
+		return verror.New(stream.ErrBadState, nil, vc.vci)
+	}
+	if _, exists := vc.flowMap[fid]; exists {
+		return verror.New(stream.ErrNetwork, nil, verror.New(errDuplicateFlow, nil))
+	}
+	priority := normalFlowPriority
+	// We use the same high priority for all reserved flows including handshake and
+	// authentication flows. This is because client may open a new system flow before
+	// authentication finishes in server side and then vc.DispatchPayload() can be
+	// stuck in waiting for authentication to finish.
+	if fid < NumReservedFlows {
+		priority = systemFlowPriority
+	}
+	writer, err := vc.newWriter(fid, priority)
+	if err != nil {
+		return verror.New(stream.ErrNetwork, nil, verror.New(errFailedToCreateWriterForNewFlow, nil, fid, err))
+	}
+	f := &flow{
+		backingVC: vc,
+		reader:    newReader(readHandlerImpl{vc, fid}),
+		writer:    writer,
+	}
+	if err = vc.listener.Enqueue(f); err != nil {
+		f.Shutdown()
+		return verror.New(stream.ErrNetwork, nil, verror.New(errFailedToEnqueueFlow, nil, err))
+	}
+	vc.flowMap[fid] = f
+	// New flow accepted, notify remote end that it can send over data.
+	// Do it in a goroutine in case the implementation of AddReceiveBuffers
+	// ends up attempting to lock vc.mu
+	go vc.helper.AddReceiveBuffers(vc.vci, fid, DefaultBytesBufferedPerFlow)
+	vc.ctx.VI(2).Infof("Added flow %d@%d to listener", fid, vc.vci)
+	return nil
+}
+
+// ShutdownFlow closes the Flow identified by fid and discards any pending
+// writes.
+func (vc *VC) ShutdownFlow(fid id.Flow) {
+	vc.mu.Lock()
+	f := vc.flowMap[fid]
+	if f == nil {
+		vc.mu.Unlock()
+		return
+	}
+	delete(vc.flowMap, fid)
+	vc.mu.Unlock()
+	f.Shutdown()
+	vc.ctx.VI(2).Infof("Shutdown flow %d@%d", fid, vc.vci)
+}
+
+// ReleaseCounters informs the Flow (identified by fid) that the remote end is
+// ready to receive up to 'bytes' more bytes of data.
+func (vc *VC) ReleaseCounters(fid id.Flow, bytes uint32) {
+	if fid == SharedFlowID {
+		vc.sharedCounters.IncN(uint(bytes))
+		return
+	}
+	var f *flow
+	vc.mu.Lock()
+	if vc.flowMap != nil {
+		f = vc.flowMap[fid]
+	}
+	vc.mu.Unlock()
+	if f == nil {
+		vc.ctx.VI(2).Infof("Ignoring ReleaseCounters(%d, %d) on VCI %d as the flow does not exist", fid, bytes, vc.vci)
+		return
+	}
+	f.Release(int(bytes))
+}
+
+func (vc *VC) Close(reason error) error {
+	vc.ctx.VI(1).Infof("Closing VC %v. Reason:%q", vc, reason)
+	vc.mu.Lock()
+	if vc.closed {
+		vc.mu.Unlock()
+		return nil
+	}
+	flows := vc.flowMap
+	vc.flowMap = nil
+	if vc.listener != nil {
+		vc.listener.Close()
+		vc.listener = nil
+	}
+	vc.closeReason = reason
+	vc.closed = true
+	close(vc.closeCh)
+	vc.mu.Unlock()
+
+	vc.sharedCounters.Close()
+	for fid, flow := range flows {
+		vc.ctx.VI(2).Infof("Closing flow %d on VC %v as VC is being closed(%q)", fid, vc, reason)
+		flow.Close()
+	}
+
+	vc.loopWG.Wait()
+	return nil
+}
+
+// appendCloseReason adds a closeReason, if any, as a sub error to err.
+func (vc *VC) appendCloseReason(err error) error {
+	vc.mu.Lock()
+	defer vc.mu.Unlock()
+	if vc.closeReason != nil {
+		return verror.AddSubErrs(err, nil, verror.SubErr{
+			Name:    "remote=" + vc.RemoteEndpoint().String(),
+			Err:     vc.closeReason,
+			Options: verror.Print,
+		})
+	}
+	return err
+}
+
+// FinishHandshakeDialedVC should be called from another goroutine after
+// HandshakeDialedVCWithAuthentication or HandshakeDialedVCNoAuthentication
+// is called, when version/encryption negotiation is complete.
+func (vc *VC) FinishHandshakeDialedVC(vers version.RPCVersion, remotePubKey *crypto.BoxKey) error {
+	vc.mu.Lock()
+	defer vc.mu.Unlock()
+	if vc.remotePubKeyChan == nil {
+		return verror.New(errHandshakeNotInProgress, nil, vc.VCI)
+	}
+	vc.remotePubKeyChan <- remotePubKey
+	vc.remotePubKeyChan = nil
+	vc.version = vers
+	return nil
+}
+
+// HandshakeDialedVCWithAuthentication completes initialization of the VC (setting
+// up encryption, authentication etc.) under the assumption that the VC was initiated
+// by the local process (i.e., the local process "Dial"ed to create the VC).
+// HandshakeDialedVCWithAuthentication will not return until FinishHandshakeDialedVC
+// is called from another goroutine.
+func (vc *VC) HandshakeDialedVCWithAuthentication(principal security.Principal, sendSetupVC func(*crypto.BoxKey) error, opts ...stream.VCOpt) error {
+	remotePubKeyChan := make(chan *crypto.BoxKey, 1)
+	vc.mu.Lock()
+	vc.remotePubKeyChan = remotePubKeyChan
+	vc.mu.Unlock()
+	exchange := func(pubKey *crypto.BoxKey) (*crypto.BoxKey, error) {
+		if err := sendSetupVC(pubKey); err != nil {
+			return nil, err
+		}
+		// TODO(mattr): Error on some timeout so a non-responding server doesn't
+		// cause this to hang forever.
+		select {
+		case remotePublicKey := <-remotePubKeyChan:
+			return remotePublicKey, nil
+		case <-vc.closeCh:
+			return nil, verror.New(stream.ErrNetwork, nil, verror.New(errClosedDuringHandshake, nil, vc.VCI))
+		}
+	}
+	crypter, err := crypto.NewBoxCrypter(exchange, iobuf.NewAllocator(vc.pool, vc.reserveBytes))
+	if err != nil {
+		return vc.appendCloseReason(verror.New(stream.ErrSecurity, nil, verror.New(errFailedToSetupEncryption, nil, err)))
+	}
+
+	// The version is set by FinishHandshakeDialedVC and exchange (called by
+	// NewBoxCrypter) will block until FinishHandshakeDialedVC is called, so we
+	// should look up the version now.
+	ver := vc.Version()
+
+	// Authenticate (exchange blessings).
+	authConn, err := vc.connectFID(AuthFlowID, systemFlowPriority)
+	if err != nil {
+		return vc.appendCloseReason(verror.New(stream.ErrSecurity, nil, verror.New(errFailedToCreateFlowForAuth, nil, err)))
+	}
+	auth := serverAuthorizer(opts)
+	params := security.CallParams{LocalPrincipal: principal, LocalEndpoint: vc.localEP, RemoteEndpoint: vc.remoteEP}
+	lBlessings, rBlessings, rDischarges, err := AuthenticateAsClient(authConn, crypter, ver, params, auth)
+	authConn.Close()
+	if err != nil {
+		return vc.appendCloseReason(err)
+	}
+
+	vc.mu.Lock()
+	vc.crypter = crypter
+	vc.localPrincipal = principal
+	vc.localBlessings = lBlessings
+	vc.remoteBlessings = rBlessings
+	vc.remoteDischarges = rDischarges
+	vc.mu.Unlock()
+
+	// Open system flows.
+	if err = vc.connectSystemFlows(); err != nil {
+		return vc.appendCloseReason(err)
+	}
+	vc.ctx.VI(1).Infof("Client VC %v authenticated. RemoteBlessings:%v, LocalBlessings:%v", vc, rBlessings, lBlessings)
+	return nil
+}
+
+// HandshakeDialedVCPreAuthenticated completes initialization of the VC
+// under the assumption that authentication happened out of band.
+// Authentication results are provided via params. Unlike
+// HandshakeDialedVCWithAuthentication or HandshakeDialedVCNoAuthentication,
+// this doesn't wait for FinishHandshakeDialedVC.
+func (vc *VC) HandshakeDialedVCPreAuthenticated(ver version.RPCVersion, params security.CallParams, remotePublicKeyPreauth *crypto.BoxKey, sendSetupVC func(*crypto.BoxKey, []byte) error, opts ...stream.VCOpt) error {
+	pk, sk, err := crypto.GenerateBoxKey()
+	if err != nil {
+		return vc.appendCloseReason(verror.New(stream.ErrSecurity, nil, verror.New(errFailedToSetupEncryption, nil, err)))
+	}
+	crypter := crypto.NewBoxCrypterWithKey(pk, sk, remotePublicKeyPreauth, iobuf.NewAllocator(vc.pool, vc.reserveBytes))
+	sigPreauth, err := bindClientPrincipalToChannel(crypter, params.LocalPrincipal)
+	if err != nil {
+		return vc.appendCloseReason(verror.New(stream.ErrSecurity, nil, err))
+	}
+
+	if err := sendSetupVC(pk, sigPreauth); err != nil {
+		return vc.appendCloseReason(err)
+	}
+
+	// Authorize the server.
+	if auth := serverAuthorizer(opts); auth != nil {
+		if err := auth.Authorize(params); err != nil {
+			// Note this error type should match with the one in AuthenticateAsClient().
+			return vc.appendCloseReason(verror.New(stream.ErrNotTrusted, nil, err))
+		}
+	}
+
+	vc.mu.Lock()
+	vc.version = ver
+	vc.crypter = crypter
+	vc.localPrincipal = params.LocalPrincipal
+	vc.localBlessings = params.LocalBlessings
+	vc.remoteBlessings = params.RemoteBlessings
+	vc.remoteDischarges = params.RemoteDischarges
+	vc.mu.Unlock()
+
+	// Open system flows.
+	if err := vc.connectSystemFlows(); err != nil {
+		return vc.appendCloseReason(err)
+	}
+	vc.ctx.VI(1).Infof("Client VC %v authenticated. RemoteBlessings:%v, LocalBlessings:%v", vc, params.RemoteBlessings, params.LocalBlessings)
+	return nil
+}
+
+// HandshakeDialedVCNoAuthentication completes initialization of the VC
+// without authentication. Blocks until FinishHandshakeVC is called.
+func (vc *VC) HandshakeDialedVCNoAuthentication(sendSetupVC func() error, opts ...stream.VCOpt) error {
+	remotePubKeyChan := make(chan *crypto.BoxKey, 1)
+	vc.mu.Lock()
+	vc.remotePubKeyChan = remotePubKeyChan
+	vc.mu.Unlock()
+	// We are running in SecurityNone and we don't need to authenticate the VC. But
+	// we still need to negotiate versioning information, so we call sendSetupVC.
+	if err := sendSetupVC(); err != nil {
+		return err
+	}
+	// TODO(mattr): Error on some timeout so a non-responding server doesn't
+	// cause this to hang forever.
+	select {
+	case <-remotePubKeyChan:
+	case <-vc.closeCh:
+		return verror.New(stream.ErrNetwork, nil, verror.New(errClosedDuringHandshake, nil, vc.VCI))
+	}
+
+	ver := vc.Version()
+
+	// Open system flows.
+	//
+	// For backward compatibility, we do not establish any system flow in old RPC versions.
+	// TODO(jhahn): Clean up this once we deprecate RPCVersion10.
+	if ver >= version.RPCVersion11 {
+		if err := vc.connectSystemFlows(); err != nil {
+			return vc.appendCloseReason(err)
+		}
+	}
+	vc.ctx.VI(1).Infof("Client VC %v handshaked with no authentication.", vc)
+	return nil
+}
+
+// HandshakeResult is sent by HandshakeAcceptedVC over the channel returned by it.
+type HandshakeResult struct {
+	Listener stream.Listener // Listener for accepting new Flows on the VC.
+	Error    error           // Error, if any, during the handshake.
+}
+
+// HandshakeAcceptedVCWithAuthentication completes initialization of the VC
+// (setting up encryption, authentication etc.) under the assumption that the VC
+// was initiated by a remote process (and the local process wishes to "accept" it).
+//
+// Since the handshaking process might involve several round trips, a bulk of the work
+// is done asynchronously and the result of the handshake is written to the
+// channel returned by this method.
+//
+// 'principal' is the principal used by the server used during authentication.
+// If principal is nil, then the VC expects to be used for unauthenticated,
+// unencrypted communication. 'lBlessings' is presented to the client during
+// authentication.
+func (vc *VC) HandshakeAcceptedVCWithAuthentication(ver version.RPCVersion, principal security.Principal, lBlessings security.Blessings, exchange crypto.BoxKeyExchanger, opts ...stream.ListenerOpt) <-chan HandshakeResult {
+	result := make(chan HandshakeResult, 1)
+	ln, err := vc.initHandshakeAcceptedVC()
+	if err != nil {
+		result <- HandshakeResult{nil, err}
+		return result
+	}
+
+	go func() {
+		crypter, err := crypto.NewBoxCrypter(exchange, iobuf.NewAllocator(vc.pool, vc.reserveBytes))
+		if err != nil {
+			vc.abortHandshakeAcceptedVC(verror.New(stream.ErrSecurity, nil, verror.New(errFailedToSetupEncryption, nil, err)), ln, result)
+			return
+		}
+
+		// Authenticate (exchange blessings).
+		authConn, err := ln.Accept()
+		if err != nil {
+			vc.abortHandshakeAcceptedVC(verror.New(stream.ErrNetwork, nil, verror.New(errAuthFlowNotAccepted, nil, err)), ln, result)
+			return
+		}
+		if vc.findFlow(authConn) != AuthFlowID {
+			// This should have been an auth flow. We can't establish security so send an error.
+			authConn.Close()
+			vc.abortHandshakeAcceptedVC(verror.New(stream.ErrSecurity, nil, verror.New(errFailedToCreateFlowForAuth, nil, err)), ln, result)
+			return
+		}
+		dischargeClient, dischargeExpiryBuffer := dischargeOptions(opts)
+		rBlessings, lDischarges, err := AuthenticateAsServer(authConn, crypter, ver, principal, lBlessings, dischargeClient)
+		authConn.Close()
+		if err != nil {
+			vc.abortHandshakeAcceptedVC(verror.New(stream.ErrSecurity, nil, verror.New(errAuthFailed, nil, err)), ln, result)
+			return
+		}
+
+		vc.mu.Lock()
+		vc.version = ver
+		vc.crypter = crypter
+		vc.localPrincipal = principal
+		vc.localBlessings = lBlessings
+		vc.remoteBlessings = rBlessings
+		vc.localDischarges = lDischarges
+		close(vc.acceptHandshakeDone)
+		vc.acceptHandshakeDone = nil
+		vc.mu.Unlock()
+
+		// Accept system flows.
+		if err = vc.acceptSystemFlows(ln, dischargeClient, dischargeExpiryBuffer); err != nil {
+			vc.abortHandshakeAcceptedVC(verror.New(stream.ErrNetwork, nil, verror.New(errFailedToAcceptSystemFlows, nil, err)), ln, result)
+			return
+		}
+		vc.ctx.VI(1).Infof("Server VC %v authenticated. RemoteBlessings:%v, LocalBlessings:%v", vc, rBlessings, lBlessings)
+		result <- HandshakeResult{ln, nil}
+	}()
+	return result
+}
+
+// HandshakeAcceptedVCPreAuthenticated completes initialization of the VC
+// under the assumption that authentication happened out of band. Unlike
+// HandshakeAcceptedVCWithAuthentication or HandshakeAcceptedVCNoAuthentication,
+// this doesn't send a SetupVC response.
+func (vc *VC) HandshakeAcceptedVCPreAuthenticated(ver version.RPCVersion, params security.CallParams, sigPreauth []byte, lPublicKeyPreauth, lPrivateKeyPreauth, rPublicKey *crypto.BoxKey, opts ...stream.ListenerOpt) <-chan HandshakeResult {
+	result := make(chan HandshakeResult, 1)
+	ln, err := vc.initHandshakeAcceptedVC()
+	if err != nil {
+		result <- HandshakeResult{nil, err}
+		return result
+	}
+
+	go func() {
+		crypter := crypto.NewBoxCrypterWithKey(lPublicKeyPreauth, lPrivateKeyPreauth, rPublicKey, iobuf.NewAllocator(vc.pool, vc.reserveBytes))
+		if err := verifyClientPrincipalBoundToChannel(sigPreauth, crypter, params.RemoteBlessings.PublicKey()); err != nil {
+			vc.abortHandshakeAcceptedVC(verror.New(stream.ErrSecurity, nil, verror.New(errAuthFailed, nil, err)), ln, result)
+			return
+		}
+
+		vc.mu.Lock()
+		vc.version = ver
+		vc.crypter = crypter
+		vc.localPrincipal = params.LocalPrincipal
+		vc.localBlessings = params.LocalBlessings
+		vc.remoteBlessings = params.RemoteBlessings
+		vc.localDischarges = params.LocalDischarges
+		close(vc.acceptHandshakeDone)
+		vc.acceptHandshakeDone = nil
+		vc.mu.Unlock()
+
+		// Accept system flows.
+		dischargeClient, dischargeExpiryBuffer := dischargeOptions(opts)
+		if err = vc.acceptSystemFlows(ln, dischargeClient, dischargeExpiryBuffer); err != nil {
+			vc.abortHandshakeAcceptedVC(verror.New(stream.ErrNetwork, nil, verror.New(errFailedToAcceptSystemFlows, nil, err)), ln, result)
+			return
+		}
+		vc.ctx.VI(1).Infof("Server VC %v authenticated. RemoteBlessings:%v, LocalBlessings:%v", vc, params.RemoteBlessings, params.LocalBlessings)
+		result <- HandshakeResult{ln, nil}
+	}()
+	return result
+}
+
+// HandshakeAcceptedVCNoAuthentication completes initialization of the VC
+// without authentication.
+func (vc *VC) HandshakeAcceptedVCNoAuthentication(ver version.RPCVersion, sendSetupVC func() error, opts ...stream.ListenerOpt) <-chan HandshakeResult {
+	result := make(chan HandshakeResult, 1)
+	ln, err := vc.initHandshakeAcceptedVC()
+	if err != nil {
+		result <- HandshakeResult{nil, err}
+		return result
+	}
+
+	go func() {
+		// We don't need to authenticate the VC, but we still need to negotiate
+		// versioning information. So we call sendSetupVC.
+		if err := sendSetupVC(); err != nil {
+			vc.abortHandshakeAcceptedVC(verror.New(stream.ErrNetwork, nil, err), ln, result)
+			return
+		}
+
+		vc.mu.Lock()
+		vc.version = ver
+		close(vc.acceptHandshakeDone)
+		vc.acceptHandshakeDone = nil
+		vc.mu.Unlock()
+
+		// Accept system flows.
+		//
+		// For backward compatibility, we do not establish any system flow in old RPC versions.
+		// TODO(jhahn): Clean up this once we deprecate RPCVersion10.
+		if ver >= version.RPCVersion11 {
+			if err = vc.acceptSystemFlows(ln, nil, 0); err != nil {
+				vc.abortHandshakeAcceptedVC(verror.New(stream.ErrNetwork, nil, verror.New(errFailedToAcceptSystemFlows, nil, err)), ln, result)
+				return
+			}
+		}
+		vc.ctx.VI(1).Infof("Server VC %v handshaked with no authentication.", vc)
+		result <- HandshakeResult{ln, err}
+	}()
+	return result
+}
+
+// initHandshakeAcceptedVC setups a listener and a notification channel
+// synchronously to avoid races.
+//
+// If the listener was setup asynchronously, there is a race between
+// the listener being setup and the caller of this method trying to
+// dispatch messages.
+//
+// If the channel was setup after starting the handshake, there is a race
+// where DispatchPayload() may handle messages before the handshake has been
+// completed.
+func (vc *VC) initHandshakeAcceptedVC() (stream.Listener, error) {
+	ln, err := vc.Listen()
+	if err != nil {
+		return nil, err
+	}
+	// TODO(mattr): We could instead send counters in the return SetupVC message
+	// and avoid this extra message.  It probably doesn't make much difference
+	// so for now I'll leave it.  May be a nice cleanup after we are always
+	// using SetupVC.
+	vc.helper.AddReceiveBuffers(vc.VCI(), SharedFlowID, DefaultBytesBufferedPerFlow)
+
+	vc.mu.Lock()
+	vc.acceptHandshakeDone = make(chan struct{})
+	vc.mu.Unlock()
+	return ln, nil
+}
+
+func (vc *VC) abortHandshakeAcceptedVC(reason error, ln stream.Listener, result chan<- HandshakeResult) {
+	ln.Close()
+	if vc.acceptHandshakeDone != nil {
+		close(vc.acceptHandshakeDone)
+		vc.acceptHandshakeDone = nil
+	}
+	result <- HandshakeResult{nil, vc.appendCloseReason(reason)}
+}
+
+func (vc *VC) sendDischargesLoop(conn io.WriteCloser, dc DischargeClient, tpCavs []security.Caveat, dischargeExpiryBuffer time.Duration) {
+	defer conn.Close()
+	defer vc.loopWG.Done()
+	if dc == nil {
+		return
+	}
+	enc := vom.NewEncoder(conn)
+	discharges := dc.PrepareDischarges(nil, tpCavs, security.DischargeImpetus{})
+	for expiry := minExpiryTime(discharges, tpCavs); !expiry.IsZero(); expiry = minExpiryTime(discharges, tpCavs) {
+		select {
+		case <-time.After(fetchDuration(expiry, dischargeExpiryBuffer)):
+			discharges = dc.PrepareDischarges(nil, tpCavs, security.DischargeImpetus{})
+			if err := enc.Encode(discharges); err != nil {
+				vc.ctx.Errorf("encoding discharges on VC %v failed: %v", vc, err)
+				return
+			}
+			if len(discharges) == 0 {
+				continue
+			}
+			vc.mu.Lock()
+			if vc.localDischarges == nil {
+				vc.localDischarges = make(map[string]security.Discharge)
+			}
+			for _, d := range discharges {
+				vc.localDischarges[d.ID()] = d
+			}
+			vc.mu.Unlock()
+		case <-vc.closeCh:
+			vc.ctx.VI(3).Infof("closing sendDischargesLoop on VC %v", vc)
+			return
+		}
+	}
+}
+
+func fetchDuration(expiry time.Time, buffer time.Duration) time.Duration {
+	// Fetch the discharge earlier than the actual expiry to factor in for clock
+	// skew and RPC time.
+	return expiry.Sub(time.Now().Add(buffer))
+}
+
+func minExpiryTime(discharges []security.Discharge, tpCavs []security.Caveat) time.Time {
+	var min time.Time
+	// If some discharges were not fetched, retry again in a minute.
+	if len(discharges) < len(tpCavs) {
+		min = time.Now().Add(time.Minute)
+	}
+	for _, d := range discharges {
+		if exp := d.Expiry(); min.IsZero() || (!exp.IsZero() && exp.Before(min)) {
+			min = exp
+		}
+	}
+	return min
+}
+
+func (vc *VC) recvDischargesLoop(conn io.ReadCloser) {
+	defer conn.Close()
+	defer vc.loopWG.Done()
+	dec := vom.NewDecoder(conn)
+	for {
+		var discharges []security.Discharge
+		if err := dec.Decode(&discharges); err != nil {
+			vc.ctx.VI(3).Infof("decoding discharges on %v failed: %v", vc, err)
+			return
+		}
+		if len(discharges) == 0 {
+			continue
+		}
+		vc.mu.Lock()
+		if vc.remoteDischarges == nil {
+			vc.remoteDischarges = make(map[string]security.Discharge)
+		}
+		for _, d := range discharges {
+			vc.remoteDischarges[d.ID()] = d
+		}
+		vc.mu.Unlock()
+	}
+}
+
+func (vc *VC) connectSystemFlows() error {
+	conn, err := vc.connectFID(TypeFlowID, systemFlowPriority)
+	if err != nil {
+		return verror.New(stream.ErrSecurity, nil, verror.New(errFailedToCreateFlowForWireType, nil, err))
+	}
+	vc.dataCache.Insert(TypeEncoderKey{}, vom.NewTypeEncoder(conn))
+	vc.dataCache.Insert(TypeDecoderKey{}, vom.NewTypeDecoder(conn))
+
+	vc.mu.Lock()
+	rBlessings := vc.remoteBlessings
+	vc.mu.Unlock()
+	if len(rBlessings.ThirdPartyCaveats()) > 0 {
+		conn, err = vc.connectFID(DischargeFlowID, systemFlowPriority)
+		if err != nil {
+			return verror.New(stream.ErrSecurity, nil, verror.New(errFailedToCreateFlowForDischarge, nil, err))
+		}
+		vc.loopWG.Add(1)
+		go vc.recvDischargesLoop(conn)
+	}
+
+	return nil
+}
+
+func (vc *VC) acceptSystemFlows(ln stream.Listener, dischargeClient DischargeClient, dischargeExpiryBuffer time.Duration) error {
+	conn, err := ln.Accept()
+	if err != nil {
+		return verror.New(errFlowForWireTypeNotAccepted, nil, err)
+	}
+	if vc.findFlow(conn) != TypeFlowID {
+		// This should have been a type flow.
+		return verror.New(errFailedToCreateFlowForWireType, nil, err)
+	}
+	vc.dataCache.Insert(TypeEncoderKey{}, vom.NewTypeEncoder(conn))
+	vc.dataCache.Insert(TypeDecoderKey{}, vom.NewTypeDecoder(conn))
+
+	vc.mu.Lock()
+	lBlessings := vc.localBlessings
+	vc.mu.Unlock()
+	tpCaveats := lBlessings.ThirdPartyCaveats()
+	if len(tpCaveats) > 0 {
+		conn, err := ln.Accept()
+		if err != nil {
+			return verror.New(errFlowForDischargeNotAccepted, nil, err)
+		}
+		vc.loopWG.Add(1)
+		go vc.sendDischargesLoop(conn, dischargeClient, tpCaveats, dischargeExpiryBuffer)
+	}
+	return nil
+}
+
+// Encrypt uses the VC's encryption scheme to encrypt the provided data payload.
+// Always takes ownership of plaintext.
+func (vc *VC) Encrypt(fid id.Flow, plaintext *iobuf.Slice) (cipherslice *iobuf.Slice, err error) {
+	if plaintext == nil {
+		return nil, nil
+	}
+	vc.mu.Lock()
+	if fid == AuthFlowID {
+		cipherslice = plaintext
+	} else {
+		cipherslice, err = vc.crypter.Encrypt(plaintext)
+	}
+	vc.mu.Unlock()
+	return
+}
+
+func (vc *VC) allocFID() id.Flow {
+	vc.mu.Lock()
+	ret := vc.nextConnectFID
+	vc.nextConnectFID += 2
+	vc.mu.Unlock()
+	return ret
+}
+
+// findFlow finds the flow id for the provided flow.
+// Returns 0 if there is none.
+func (vc *VC) findFlow(flow interface{}) id.Flow {
+	vc.mu.Lock()
+	defer vc.mu.Unlock()
+
+	const invalidFlowID = 0
+	// This operation is rare and early enough (called when there are <= 2
+	// flows over the VC) that iteration to the map should be fine.
+	for fid, f := range vc.flowMap {
+		if f == flow {
+			return fid
+		}
+	}
+	return invalidFlowID
+}
+
+// VCI returns the identifier of this VC.
+func (vc *VC) VCI() id.VC { return vc.vci }
+
+// RemoteEndpoint returns the remote endpoint for this VC.
+func (vc *VC) RemoteEndpoint() naming.Endpoint { return vc.remoteEP }
+
+// LocalEndpoint returns the local endpoint for this VC.
+func (vc *VC) LocalEndpoint() naming.Endpoint { return vc.localEP }
+
+// VCDataCache returns the VCDataCache that allows information to be
+// shared across the VC.
+func (vc *VC) VCDataCache() stream.VCDataCache { return vc.dataCache }
+
+// LocalPrincipal returns the principal that authenticated with the remote end of the VC.
+func (vc *VC) LocalPrincipal() security.Principal {
+	vc.mu.Lock()
+	defer vc.mu.Unlock()
+	vc.waitForHandshakeLocked()
+	return vc.localPrincipal
+}
+
+// LocalBlessings returns the blessings (bound to LocalPrincipal) presented to the
+// remote end of the VC during authentication.
+func (vc *VC) LocalBlessings() security.Blessings {
+	vc.mu.Lock()
+	defer vc.mu.Unlock()
+	vc.waitForHandshakeLocked()
+	return vc.localBlessings
+}
+
+// RemoteBlessings returns the blessings presented by the remote end of the VC during
+// authentication.
+func (vc *VC) RemoteBlessings() security.Blessings {
+	vc.mu.Lock()
+	defer vc.mu.Unlock()
+	vc.waitForHandshakeLocked()
+	return vc.remoteBlessings
+}
+
+// LocalDischarges returns the discharges presented by the local end of the VC during
+// authentication.
+func (vc *VC) LocalDischarges() map[string]security.Discharge {
+	vc.mu.Lock()
+	defer vc.mu.Unlock()
+	vc.waitForHandshakeLocked()
+	if len(vc.localDischarges) == 0 {
+		return nil
+	}
+	// Return a copy of the map to prevent racy reads.
+	return copyDischargeMap(vc.localDischarges)
+}
+
+// RemoteDischarges returns the discharges presented by the remote end of the VC during
+// authentication.
+func (vc *VC) RemoteDischarges() map[string]security.Discharge {
+	vc.mu.Lock()
+	defer vc.mu.Unlock()
+	vc.waitForHandshakeLocked()
+	if len(vc.remoteDischarges) == 0 {
+		return nil
+	}
+	// Return a copy of the map to prevent racy reads.
+	return copyDischargeMap(vc.remoteDischarges)
+}
+
+// waitForHandshakeLocked blocks until an in-progress handshake (encryption
+// setup and authentication) completes.
+// REQUIRES: vc.mu is held.
+func (vc *VC) waitForHandshakeLocked() {
+	if hsd := vc.acceptHandshakeDone; hsd != nil {
+		vc.mu.Unlock()
+		<-hsd
+		vc.mu.Lock()
+	}
+}
+
+func (vc *VC) String() string {
+	return fmt.Sprintf("VCI:%d (%v<->%v)", vc.vci, vc.localEP, vc.remoteEP)
+}
+
+// DebugString returns a string representation of the state of a VC.
+//
+// The format of the returned string is meant to be human-friendly and the
+// specific format should not be relied upon for automated processing.
+func (vc *VC) DebugString() string {
+	vc.mu.Lock()
+	l := make([]string, 0, len(vc.flowMap)+1)
+	l = append(l, fmt.Sprintf("VCI:%d -- Endpoints:(Local:%q Remote:%q) #Flows:%d NextConnectFID:%d",
+		vc.vci,
+		vc.localEP,
+		vc.remoteEP,
+		len(vc.flowMap),
+		vc.nextConnectFID))
+	if vc.crypter == nil {
+		l = append(l, "Handshake not completed yet")
+	} else {
+		l = append(l, "Encryption: "+vc.crypter.String())
+		if vc.localPrincipal != nil {
+			l = append(l, fmt.Sprintf("LocalPrincipal:%v LocalBlessings:%v RemoteBlessings:%v", vc.localPrincipal.PublicKey(), vc.localBlessings, vc.remoteBlessings))
+		}
+	}
+	for fid, f := range vc.flowMap {
+		l = append(l, fmt.Sprintf("  Flow:%3d BytesRead:%7d BytesWritten:%7d", fid, f.BytesRead(), f.BytesWritten()))
+	}
+	vc.mu.Unlock()
+	sort.Strings(l[1:])
+	return strings.Join(l, "\n")
+}
+
+func (vc *VC) newWriter(fid id.Flow, priority bqueue.Priority) (*writer, error) {
+	bq, err := vc.helper.NewWriter(vc.vci, fid, priority)
+	if err != nil {
+		return nil, err
+	}
+	alloc := iobuf.NewAllocator(vc.pool, 0)
+	return newWriter(MaxPayloadSizeBytes, bq, alloc, vc.sharedCounters), nil
+}
+
+// readHandlerImpl is an adapter for the readHandler interface required by
+// the reader type.
+type readHandlerImpl struct {
+	vc  *VC
+	fid id.Flow
+}
+
+func (r readHandlerImpl) HandleRead(bytes uint) {
+	r.vc.helper.AddReceiveBuffers(r.vc.vci, r.fid, bytes)
+}
+
+func copyDischargeMap(m map[string]security.Discharge) map[string]security.Discharge {
+	ret := make(map[string]security.Discharge)
+	for id, d := range m {
+		ret[id] = d
+	}
+	return ret
+}
+
+func serverAuthorizer(opts []stream.VCOpt) *ServerAuthorizer {
+	for _, o := range opts {
+		switch auth := o.(type) {
+		case *ServerAuthorizer:
+			return auth
+		}
+	}
+	return nil
+}
+
+func dischargeOptions(opts []stream.ListenerOpt) (DischargeClient, time.Duration) {
+	var (
+		dischargeClient       DischargeClient
+		dischargeExpiryBuffer = DefaultServerDischargeExpiryBuffer
+	)
+	for _, o := range opts {
+		switch v := o.(type) {
+		case DischargeClient:
+			dischargeClient = v
+		case DischargeExpiryBuffer:
+			dischargeExpiryBuffer = time.Duration(v)
+		}
+	}
+	return dischargeClient, dischargeExpiryBuffer
+}
diff --git a/runtime/internal/rpc/stream/vc/vc_cache.go b/runtime/internal/rpc/stream/vc/vc_cache.go
new file mode 100644
index 0000000..d962cfa
--- /dev/null
+++ b/runtime/internal/rpc/stream/vc/vc_cache.go
@@ -0,0 +1,114 @@
+// 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 vc
+
+import (
+	"sync"
+
+	"v.io/v23/naming"
+	"v.io/v23/security"
+	"v.io/v23/verror"
+)
+
+var errVCCacheClosed = reg(".errVCCacheClosed", "vc cache has been closed")
+
+// VCCache implements a set of VIFs keyed by the endpoint of the remote end and the
+// local principal. Multiple goroutines can invoke methods on the VCCache simultaneously.
+type VCCache struct {
+	mu      sync.Mutex
+	cache   map[vcKey]*VC  // GUARDED_BY(mu)
+	started map[vcKey]bool // GUARDED_BY(mu)
+	cond    *sync.Cond
+}
+
+// NewVCCache returns a new cache for VCs.
+func NewVCCache() *VCCache {
+	c := &VCCache{
+		cache:   make(map[vcKey]*VC),
+		started: make(map[vcKey]bool),
+	}
+	c.cond = sync.NewCond(&c.mu)
+	return c
+}
+
+// ReservedFind returns a VC where the remote end of the underlying connection
+// is identified by the provided (ep, p.PublicKey). Returns nil if there is no
+// such VC.
+//
+// Iff the cache is closed, ReservedFind will return an error.
+// If ReservedFind has no error, the caller is required to call Unreserve, to avoid deadlock.
+// The ep, and p.PublicKey in Unreserve must be the same as used in the ReservedFind call.
+// During this time, all new ReservedFind calls for this ep and p will Block until
+// the corresponding Unreserve call is made.
+func (c *VCCache) ReservedFind(ep naming.Endpoint, p security.Principal) (*VC, error) {
+	k := c.key(ep, p)
+	c.mu.Lock()
+	defer c.mu.Unlock()
+	for c.started[k] {
+		c.cond.Wait()
+	}
+	if c.cache == nil {
+		return nil, verror.New(errVCCacheClosed, nil)
+	}
+	c.started[k] = true
+	return c.cache[k], nil
+}
+
+// Unreserve marks the status of the ep, p as no longer started, and
+// broadcasts waiting threads.
+func (c *VCCache) Unreserve(ep naming.Endpoint, p security.Principal) {
+	c.mu.Lock()
+	delete(c.started, c.key(ep, p))
+	c.cond.Broadcast()
+	c.mu.Unlock()
+}
+
+// Insert adds vc to the cache and returns an error iff the cache has been closed.
+func (c *VCCache) Insert(vc *VC) error {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+	if c.cache == nil {
+		return verror.New(errVCCacheClosed, nil)
+	}
+	c.cache[c.key(vc.RemoteEndpoint(), vc.LocalPrincipal())] = vc
+	return nil
+}
+
+// Close marks the VCCache as closed and returns the VCs remaining in the cache.
+func (c *VCCache) Close() []*VC {
+	c.mu.Lock()
+	vcs := make([]*VC, 0, len(c.cache))
+	for _, vc := range c.cache {
+		vcs = append(vcs, vc)
+	}
+	c.cache = nil
+	c.started = nil
+	c.mu.Unlock()
+	return vcs
+}
+
+// Delete removes vc from the cache, returning an error iff the cache has been closed.
+func (c *VCCache) Delete(vc *VC) error {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+	if c.cache == nil {
+		return verror.New(errVCCacheClosed, nil)
+	}
+	delete(c.cache, c.key(vc.RemoteEndpoint(), vc.LocalPrincipal()))
+	return nil
+}
+
+type vcKey struct {
+	remoteEP       string
+	localPublicKey string // localPublicKey = "" means we are running unencrypted (i.e. SecurityNone)
+}
+
+func (c *VCCache) key(ep naming.Endpoint, p security.Principal) vcKey {
+	k := vcKey{remoteEP: ep.String()}
+	if p != nil {
+		k.localPublicKey = p.PublicKey().String()
+	}
+	return k
+}
diff --git a/runtime/internal/rpc/stream/vc/vc_cache_test.go b/runtime/internal/rpc/stream/vc/vc_cache_test.go
new file mode 100644
index 0000000..b096e21
--- /dev/null
+++ b/runtime/internal/rpc/stream/vc/vc_cache_test.go
@@ -0,0 +1,123 @@
+// 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 vc
+
+import (
+	"testing"
+
+	inaming "v.io/x/ref/runtime/internal/naming"
+	"v.io/x/ref/test/testutil"
+)
+
+func TestInsertDelete(t *testing.T) {
+	cache := NewVCCache()
+	ep, err := inaming.NewEndpoint("foo:8888")
+	if err != nil {
+		t.Fatal(err)
+	}
+	p := testutil.NewPrincipal("test")
+	vc := &VC{remoteEP: ep, localPrincipal: p}
+	otherEP, err := inaming.NewEndpoint("foo:8888")
+	if err != nil {
+		t.Fatal(err)
+	}
+	otherP := testutil.NewPrincipal("test")
+	otherVC := &VC{remoteEP: otherEP, localPrincipal: otherP}
+
+	cache.Insert(vc)
+	cache.Insert(otherVC)
+	cache.Delete(vc)
+	if got, want := cache.Close(), []*VC{otherVC}; !vcsEqual(got, want) {
+		t.Errorf("got %v, want %v", got, want)
+	}
+}
+
+func TestInsertClose(t *testing.T) {
+	cache := NewVCCache()
+	ep, err := inaming.NewEndpoint("foo:8888")
+	if err != nil {
+		t.Fatal(err)
+	}
+	p := testutil.NewPrincipal("test")
+	vc := &VC{remoteEP: ep, localPrincipal: p}
+
+	if err := cache.Insert(vc); err != nil {
+		t.Errorf("the cache is not closed yet")
+	}
+	if got, want := cache.Close(), []*VC{vc}; !vcsEqual(got, want) {
+		t.Errorf("got %v, want %v", got, want)
+	}
+	if err := cache.Insert(vc); err == nil {
+		t.Errorf("the cache has been closed")
+	}
+}
+
+func TestReservedFind(t *testing.T) {
+	cache := NewVCCache()
+	ep, err := inaming.NewEndpoint("foo:8888")
+	if err != nil {
+		t.Fatal(err)
+	}
+	p := testutil.NewPrincipal("test")
+	vc := &VC{remoteEP: ep, localPrincipal: p}
+	cache.Insert(vc)
+
+	// We should be able to find the vc in the cache.
+	if got, err := cache.ReservedFind(ep, p); err != nil || got != vc {
+		t.Errorf("got %v, want %v, err: %v", got, vc, err)
+	}
+
+	// If we change the endpoint or the principal, we should get nothing.
+	otherEP, err := inaming.NewEndpoint("bar: 7777")
+	if err != nil {
+		t.Fatal(err)
+	}
+	if got, err := cache.ReservedFind(otherEP, p); err != nil || got != nil {
+		t.Errorf("got %v, want <nil>, err: %v", got, err)
+	}
+	if got, err := cache.ReservedFind(ep, testutil.NewPrincipal("wrong")); err != nil || got != nil {
+		t.Errorf("got %v, want <nil>, err: %v", got, err)
+	}
+
+	// A subsequent ReservedFind call that matches a previous failed ReservedFind
+	// should block until a matching Unreserve call is made.
+	ch := make(chan *VC, 1)
+	go func(ch chan *VC) {
+		vc, err := cache.ReservedFind(otherEP, p)
+		if err != nil {
+			t.Fatal(err)
+		}
+		ch <- vc
+	}(ch)
+
+	// We insert the otherEP into the cache.
+	otherVC := &VC{remoteEP: otherEP, localPrincipal: p}
+	cache.Insert(otherVC)
+	cache.Unreserve(otherEP, p)
+
+	// Now the cache.BlcokingFind should have returned the correct otherVC.
+	if cachedVC := <-ch; cachedVC != otherVC {
+		t.Errorf("got %v, want %v", cachedVC, otherVC)
+	}
+}
+
+func vcsEqual(a, b []*VC) bool {
+	if len(a) != len(b) {
+		return false
+	}
+	m := make(map[*VC]int)
+	for _, v := range a {
+		m[v]++
+	}
+	for _, v := range b {
+		m[v]--
+	}
+	for _, i := range m {
+		if i != 0 {
+			return false
+		}
+	}
+	return true
+}
diff --git a/runtime/internal/rpc/stream/vc/vc_test.go b/runtime/internal/rpc/stream/vc/vc_test.go
new file mode 100644
index 0000000..452801a
--- /dev/null
+++ b/runtime/internal/rpc/stream/vc/vc_test.go
@@ -0,0 +1,724 @@
+// 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.
+
+// Use a different package for the tests to ensure that only the exported API is used.
+
+package vc_test
+
+import (
+	"bytes"
+	"errors"
+	"fmt"
+	"io"
+	"net"
+	"reflect"
+	"runtime"
+	"strings"
+	"sync"
+	"testing"
+
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/rpc/version"
+	"v.io/v23/security"
+	"v.io/v23/verror"
+
+	_ "v.io/x/ref/runtime/factories/generic"
+	"v.io/x/ref/runtime/internal/lib/bqueue"
+	"v.io/x/ref/runtime/internal/lib/bqueue/drrqueue"
+	"v.io/x/ref/runtime/internal/lib/iobuf"
+	"v.io/x/ref/runtime/internal/rpc/stream"
+	"v.io/x/ref/runtime/internal/rpc/stream/crypto"
+	"v.io/x/ref/runtime/internal/rpc/stream/id"
+	"v.io/x/ref/runtime/internal/rpc/stream/vc"
+	iversion "v.io/x/ref/runtime/internal/rpc/version"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/testutil"
+)
+
+var (
+	clientEP = endpoint(naming.FixedRoutingID(0xcccccccccccccccc))
+	serverEP = endpoint(naming.FixedRoutingID(0x5555555555555555))
+)
+
+//go:generate v23 test generate
+
+const (
+	// Convenience alias to avoid conflicts between the package name "vc" and variables called "vc".
+	DefaultBytesBufferedPerFlow = vc.DefaultBytesBufferedPerFlow
+)
+
+var LatestVersion = iversion.SupportedRange.Max
+
+type testSecurityLevel int
+
+const (
+	SecurityDefault testSecurityLevel = iota
+	SecurityPreAuthenticated
+	SecurityNone
+)
+
+// testFlowEcho writes a random string of 'size' bytes on the flow and then
+// ensures that the same string is read back.
+func testFlowEcho(t *testing.T, flow stream.Flow, size int) {
+	testutil.InitRandGenerator(t.Logf)
+	defer flow.Close()
+	wrote := testutil.RandomBytes(size)
+	go func() {
+		buf := wrote
+		for len(buf) > 0 {
+			limit := 1 + testutil.RandomIntn(len(buf)) // Random number in [1, n]
+			n, err := flow.Write(buf[:limit])
+			if n != limit || err != nil {
+				t.Errorf("Write returned (%d, %v) want (%d, nil)", n, err, limit)
+			}
+			buf = buf[limit:]
+		}
+	}()
+
+	total := 0
+	read := make([]byte, size)
+	buf := read
+	for total < size {
+		n, err := flow.Read(buf)
+		if err != nil {
+			t.Error(err)
+			return
+		}
+		total += n
+		buf = buf[n:]
+	}
+	if bytes.Compare(read, wrote) != 0 {
+		t.Errorf("Data read != data written")
+	}
+}
+
+func TestHandshakeNoSecurity(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	// When the principals are nil, no blessings should be sent over the wire.
+	clientH, serverH := newVC(ctx)
+	if err := handshakeVCNoAuthentication(LatestVersion, clientH.VC, serverH.VC); err != nil {
+		t.Fatal(err)
+	}
+	defer clientH.Close()
+	flow, err := clientH.VC.Connect()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !flow.RemoteBlessings().IsZero() {
+		t.Errorf("Server sent blessing %v over insecure transport", flow.RemoteBlessings())
+	}
+	if !flow.LocalBlessings().IsZero() {
+		t.Errorf("Client sent blessing %v over insecure transport", flow.LocalBlessings())
+	}
+}
+
+func testFlowAuthN(flow stream.Flow, serverBlessings security.Blessings, serverDischarges map[string]security.Discharge, clientPublicKey security.PublicKey) error {
+	if got, want := flow.RemoteBlessings(), serverBlessings; !reflect.DeepEqual(got, want) {
+		return fmt.Errorf("Server shared blessings %v, want %v", got, want)
+	}
+	if got, want := flow.RemoteDischarges(), serverDischarges; !reflect.DeepEqual(got, want) {
+		return fmt.Errorf("Server shared discharges %#v, want %#v", got, want)
+	}
+	if got, want := flow.LocalBlessings().PublicKey(), clientPublicKey; !reflect.DeepEqual(got, want) {
+		return fmt.Errorf("Client shared %v, want %v", got, want)
+	}
+	return nil
+}
+
+// auth implements security.Authorizer.
+type auth struct {
+	localPrincipal   security.Principal
+	remoteBlessings  security.Blessings
+	remoteDischarges map[string]security.Discharge
+	suffix, method   string
+	err              error
+}
+
+// Authorize tests that the context passed to the authorizer is the expected one.
+func (a *auth) Authorize(ctx *context.T, call security.Call) error {
+	if a.err != nil {
+		return a.err
+	}
+	if got, want := call.LocalPrincipal(), a.localPrincipal; !reflect.DeepEqual(got, want) {
+		return fmt.Errorf("ctx.LocalPrincipal: got %v, want %v", got, want)
+	}
+	if got, want := call.RemoteBlessings(), a.remoteBlessings; !reflect.DeepEqual(got, want) {
+		return fmt.Errorf("ctx.RemoteBlessings: got %v, want %v", got, want)
+	}
+	if got, want := call.RemoteDischarges(), a.remoteDischarges; !reflect.DeepEqual(got, want) {
+		return fmt.Errorf("ctx.RemoteDischarges: got %v, want %v", got, want)
+	}
+	if got, want := call.LocalEndpoint(), clientEP; !reflect.DeepEqual(got, want) {
+		return fmt.Errorf("ctx.LocalEndpoint: got %v, want %v", got, want)
+	}
+	if got, want := call.RemoteEndpoint(), serverEP; !reflect.DeepEqual(got, want) {
+		return fmt.Errorf("ctx.RemoteEndpoint: got %v, want %v", got, want)
+	}
+	if got, want := call.Suffix(), a.suffix; got != want {
+		return fmt.Errorf("ctx.RemoteEndpoint: got %v, want %v", got, want)
+	}
+	if got, want := call.Method(), a.method; got != want {
+		return fmt.Errorf("ctx.RemoteEndpoint: got %v, want %v", got, want)
+	}
+	return nil
+}
+
+// mockDischargeClient implements vc.DischargeClient.
+type mockDischargeClient []security.Discharge
+
+func (m mockDischargeClient) PrepareDischarges(_ *context.T, forcaveats []security.Caveat, impetus security.DischargeImpetus) []security.Discharge {
+	return m
+}
+func (mockDischargeClient) Invalidate(*context.T, ...security.Discharge) {}
+func (mockDischargeClient) RPCStreamListenerOpt()                        {}
+func (mockDischargeClient) RPCStreamVCOpt()                              {}
+
+// Test that mockDischargeClient implements vc.DischargeClient.
+var _ vc.DischargeClient = (mockDischargeClient)(nil)
+
+func testHandshake(t *testing.T, securityLevel testSecurityLevel) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	matchesError := func(got error, want string) error {
+		if (got == nil) && len(want) == 0 {
+			return nil
+		}
+		if got == nil && !strings.Contains(got.Error(), want) {
+			return fmt.Errorf("got error %q, wanted to match %q", got, want)
+		}
+		return nil
+	}
+	var (
+		root       = testutil.NewIDProvider("root")
+		discharger = testutil.NewPrincipal("discharger")
+		pclient    = testutil.NewPrincipal()
+		pserver    = testutil.NewPrincipal()
+	)
+	tpcav, err := security.NewPublicKeyCaveat(discharger.PublicKey(), "irrelevant", security.ThirdPartyRequirements{}, security.UnconstrainedUse())
+	if err != nil {
+		t.Fatal(err)
+	}
+	dis, err := discharger.MintDischarge(tpcav, security.UnconstrainedUse())
+	if err != nil {
+		t.Fatal(err)
+	}
+	// Root blesses the client
+	if err := root.Bless(pclient, "client"); err != nil {
+		t.Fatal(err)
+	}
+	// Root blesses the server with a third-party caveat
+	if err := root.Bless(pserver, "server", tpcav); err != nil {
+		t.Fatal(err)
+	}
+
+	testdata := []struct {
+		dischargeClient      vc.DischargeClient
+		auth                 *vc.ServerAuthorizer
+		dialErr              string
+		flowRemoteBlessings  security.Blessings
+		flowRemoteDischarges map[string]security.Discharge
+	}{
+		{
+			flowRemoteBlessings: pserver.BlessingStore().Default(),
+		},
+		{
+			dischargeClient:      mockDischargeClient([]security.Discharge{dis}),
+			flowRemoteBlessings:  pserver.BlessingStore().Default(),
+			flowRemoteDischarges: map[string]security.Discharge{dis.ID(): dis},
+		},
+		{
+			dischargeClient: mockDischargeClient([]security.Discharge{dis}),
+			auth: &vc.ServerAuthorizer{
+				Suffix: "suffix",
+				Method: "method",
+				Policy: &auth{
+					localPrincipal:   pclient,
+					remoteBlessings:  pserver.BlessingStore().Default(),
+					remoteDischarges: map[string]security.Discharge{dis.ID(): dis},
+					suffix:           "suffix",
+					method:           "method",
+				},
+			},
+			flowRemoteBlessings:  pserver.BlessingStore().Default(),
+			flowRemoteDischarges: map[string]security.Discharge{dis.ID(): dis},
+		},
+		{
+			dischargeClient: mockDischargeClient([]security.Discharge{dis}),
+			auth: &vc.ServerAuthorizer{
+				Suffix: "suffix",
+				Method: "method",
+				Policy: &auth{
+					err: errors.New("authorization error"),
+				},
+			},
+			dialErr: "authorization error",
+		},
+	}
+	for i, d := range testdata {
+		clientH, serverH := newVC(ctx)
+		var err error
+		switch securityLevel {
+		case SecurityPreAuthenticated:
+			var serverPK, serverSK *crypto.BoxKey
+			if serverPK, serverSK, err = crypto.GenerateBoxKey(); err != nil {
+				t.Fatal(err)
+			}
+			err = handshakeVCPreAuthenticated(LatestVersion, clientH.VC, serverH.VC, pclient, pserver, serverPK, serverSK, d.flowRemoteDischarges, d.dischargeClient, d.auth)
+		case SecurityDefault:
+			err = handshakeVCWithAuthentication(LatestVersion, clientH.VC, serverH.VC, pclient, pserver, d.flowRemoteDischarges, d.dischargeClient, d.auth)
+		}
+		if merr := matchesError(err, d.dialErr); merr != nil {
+			t.Errorf("Test #%d: HandshakeDialedVC with server authorizer %#v:: %v", i, d.auth.Policy, merr)
+		}
+		if err != nil {
+			continue
+		}
+		flow, err := clientH.VC.Connect()
+		if err != nil {
+			clientH.Close()
+			t.Errorf("Unable to create flow: %v", err)
+			continue
+		}
+		if err := testFlowAuthN(flow, d.flowRemoteBlessings, d.flowRemoteDischarges, pclient.PublicKey()); err != nil {
+			clientH.Close()
+			t.Error(err)
+			continue
+		}
+		clientH.Close()
+	}
+}
+func TestHandshakePreAuthenticated(t *testing.T) { testHandshake(t, SecurityPreAuthenticated) }
+func TestHandshake(t *testing.T)                 { testHandshake(t, SecurityDefault) }
+
+func testConnect_Small(t *testing.T, version version.RPCVersion, securityLevel testSecurityLevel) {
+	h, vc, err := NewSimple(version, securityLevel)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer h.Close()
+	flow, err := vc.Connect()
+	if err != nil {
+		t.Fatal(err)
+	}
+	testFlowEcho(t, flow, 10)
+}
+func TestConnect_SmallNoSecurity(t *testing.T) { testConnect_Small(t, LatestVersion, SecurityNone) }
+func TestConnect_SmallPreAuthenticated(t *testing.T) {
+	testConnect_Small(t, LatestVersion, SecurityPreAuthenticated)
+}
+func TestConnect_Small(t *testing.T) { testConnect_Small(t, LatestVersion, SecurityDefault) }
+
+func testConnect(t *testing.T, securityLevel testSecurityLevel) {
+	h, vc, err := NewSimple(LatestVersion, securityLevel)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer h.Close()
+	flow, err := vc.Connect()
+	if err != nil {
+		t.Fatal(err)
+	}
+	testFlowEcho(t, flow, 10*DefaultBytesBufferedPerFlow)
+}
+func TestConnectNoSecurity(t *testing.T)       { testConnect(t, SecurityNone) }
+func TestConnectPreAuthenticated(t *testing.T) { testConnect(t, SecurityPreAuthenticated) }
+func TestConnect(t *testing.T)                 { testConnect(t, SecurityDefault) }
+
+// helper function for testing concurrent operations on multiple flows over the
+// same VC.  Such tests are most useful when running the race detector.
+// (go test -race ...)
+func testConcurrentFlows(t *testing.T, securityLevel testSecurityLevel, flows, gomaxprocs int) {
+	mp := runtime.GOMAXPROCS(gomaxprocs)
+	defer runtime.GOMAXPROCS(mp)
+	h, vc, err := NewSimple(LatestVersion, securityLevel)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer h.Close()
+
+	var wg sync.WaitGroup
+	wg.Add(flows)
+	for i := 0; i < flows; i++ {
+		go func(n int) {
+			defer wg.Done()
+			flow, err := vc.Connect()
+			if err != nil {
+				t.Error(err)
+			} else {
+				testFlowEcho(t, flow, (n+1)*DefaultBytesBufferedPerFlow)
+			}
+		}(i)
+	}
+	wg.Wait()
+}
+
+func TestConcurrentFlows_1NOSecurity(t *testing.T) { testConcurrentFlows(t, SecurityNone, 10, 1) }
+func TestConcurrentFlows_1PreAuthenticated(t *testing.T) {
+	testConcurrentFlows(t, SecurityPreAuthenticated, 10, 1)
+}
+func TestConcurrentFlows_1(t *testing.T) { testConcurrentFlows(t, SecurityDefault, 10, 1) }
+
+func TestConcurrentFlows_10NoSecurity(t *testing.T) { testConcurrentFlows(t, SecurityNone, 10, 10) }
+func TestConcurrentFlows_10PreAuthenticated(t *testing.T) {
+	testConcurrentFlows(t, SecurityPreAuthenticated, 10, 10)
+}
+func TestConcurrentFlows_10(t *testing.T) { testConcurrentFlows(t, SecurityDefault, 10, 10) }
+
+func testListen(t *testing.T, securityLevel testSecurityLevel) {
+	h, vc, err := NewSimple(LatestVersion, securityLevel)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer h.Close()
+	if err := h.VC.AcceptFlow(id.Flow(21)); err == nil {
+		t.Errorf("Expected AcceptFlow on a new flow to fail as Listen was not called")
+	}
+
+	ln, err := vc.Listen()
+	if err != nil {
+		t.Fatalf("vc.Listen failed: %v", err)
+		return
+	}
+	_, err = vc.Listen()
+	if err == nil {
+		t.Fatalf("Second call to vc.Listen should have failed")
+		return
+	}
+	if err := h.VC.AcceptFlow(id.Flow(23)); err != nil {
+		t.Fatal(err)
+	}
+
+	data := "the dark knight"
+	cipherdata, err := h.otherEnd.VC.Encrypt(id.Flow(23), iobuf.NewSlice([]byte(data)))
+	if err != nil {
+		t.Fatal(err)
+	}
+	if err := h.VC.DispatchPayload(id.Flow(23), cipherdata); err != nil {
+		t.Fatal(err)
+	}
+	flow, err := ln.Accept()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if err := ln.Close(); err != nil {
+		t.Error(err)
+	}
+	flow.Close()
+	var buf [4096]byte
+	if n, err := flow.Read(buf[:]); n != len(data) || err != nil || string(buf[:n]) != data {
+		t.Errorf("Got (%d, %v) = %q, want (%d, nil) = %q", n, err, string(buf[:n]), len(data), data)
+	}
+	if n, err := flow.Read(buf[:]); n != 0 || err != io.EOF {
+		t.Errorf("Got (%d, %v) want (0, %v)", n, err, io.EOF)
+	}
+}
+func TestListenNoSecurity(t *testing.T)       { testListen(t, SecurityNone) }
+func TestListenPreAuthenticated(t *testing.T) { testListen(t, SecurityPreAuthenticated) }
+func TestListen(t *testing.T)                 { testListen(t, SecurityDefault) }
+
+func testNewFlowAfterClose(t *testing.T, securityLevel testSecurityLevel) {
+	h, _, err := NewSimple(LatestVersion, securityLevel)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer h.Close()
+	h.VC.Close(fmt.Errorf("reason"))
+	if err := h.VC.AcceptFlow(id.Flow(10)); err == nil {
+		t.Fatalf("New flows should not be accepted once the VC is closed")
+	}
+}
+func TestNewFlowAfterCloseNoSecurity(t *testing.T) { testNewFlowAfterClose(t, SecurityNone) }
+func TestNewFlowAfterClosePreAuthenticated(t *testing.T) {
+	testNewFlowAfterClose(t, SecurityPreAuthenticated)
+}
+func TestNewFlowAfterClose(t *testing.T) { testNewFlowAfterClose(t, SecurityDefault) }
+
+func testConnectAfterClose(t *testing.T, securityLevel testSecurityLevel) {
+	h, vc, err := NewSimple(LatestVersion, securityLevel)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer h.Close()
+	h.VC.Close(fmt.Errorf("myerr"))
+	if f, err := vc.Connect(); f != nil || err == nil || !strings.Contains(err.Error(), "myerr") {
+		t.Fatalf("Got (%v, %v), want (nil, %q)", f, err, "myerr")
+	}
+}
+func TestConnectAfterCloseNoSecurity(t *testing.T) { testConnectAfterClose(t, SecurityNone) }
+func TestConnectAfterClosePreAuthenticated(t *testing.T) {
+	testConnectAfterClose(t, SecurityPreAuthenticated)
+}
+func TestConnectAfterClose(t *testing.T) { testConnectAfterClose(t, SecurityDefault) }
+
+// helper implements vc.Helper and also sets up a single VC.
+type helper struct {
+	VC *vc.VC
+	bq bqueue.T
+
+	mu       sync.Mutex
+	otherEnd *helper // GUARDED_BY(mu)
+}
+
+// NewSimple creates both ends of a VC but returns only the "client" end (i.e.,
+// the one that initiated the VC). The "server" end (the one that "accepted" the
+// VC) listens for flows and simply echoes data read.
+func NewSimple(v version.RPCVersion, securityLevel testSecurityLevel) (*helper, stream.VC, error) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	clientH, serverH := newVC(ctx)
+	pclient := testutil.NewPrincipal("client")
+	pserver := testutil.NewPrincipal("server")
+	var err error
+	switch securityLevel {
+	case SecurityNone:
+		err = handshakeVCNoAuthentication(v, clientH.VC, serverH.VC)
+	case SecurityPreAuthenticated:
+		serverPK, serverSK, _ := crypto.GenerateBoxKey()
+		err = handshakeVCPreAuthenticated(v, clientH.VC, serverH.VC, pclient, pserver, serverPK, serverSK, nil, nil, nil)
+	case SecurityDefault:
+		err = handshakeVCWithAuthentication(v, clientH.VC, serverH.VC, pclient, pserver, nil, nil, nil)
+	}
+	if err != nil {
+		clientH.Close()
+		return nil, nil, err
+	}
+	return clientH, clientH.VC, err
+}
+
+func newVC(ctx *context.T) (clientH, serverH *helper) {
+	clientH = &helper{bq: drrqueue.New(vc.MaxPayloadSizeBytes)}
+	serverH = &helper{bq: drrqueue.New(vc.MaxPayloadSizeBytes)}
+	clientH.otherEnd = serverH
+	serverH.otherEnd = clientH
+
+	vci := id.VC(1234)
+
+	clientParams := vc.Params{
+		VCI:      vci,
+		Dialed:   true,
+		LocalEP:  clientEP,
+		RemoteEP: serverEP,
+		Pool:     iobuf.NewPool(0),
+		Helper:   clientH,
+	}
+	serverParams := vc.Params{
+		VCI:      vci,
+		LocalEP:  serverEP,
+		RemoteEP: clientEP,
+		Pool:     iobuf.NewPool(0),
+		Helper:   serverH,
+	}
+
+	clientH.VC = vc.InternalNew(ctx, clientParams)
+	serverH.VC = vc.InternalNew(ctx, serverParams)
+	clientH.AddReceiveBuffers(vci, vc.SharedFlowID, vc.DefaultBytesBufferedPerFlow)
+
+	go clientH.pipeLoop(ctx, serverH.VC)
+	go serverH.pipeLoop(ctx, clientH.VC)
+	return
+}
+
+func handshakeVCWithAuthentication(v version.RPCVersion, client, server *vc.VC, pclient, pserver security.Principal, discharges map[string]security.Discharge, dischargeClient vc.DischargeClient, auth *vc.ServerAuthorizer) error {
+	var lopts []stream.ListenerOpt
+	if dischargeClient != nil {
+		lopts = append(lopts, dischargeClient)
+	}
+	var vcopts []stream.VCOpt
+	if auth != nil {
+		vcopts = append(vcopts, auth)
+	}
+
+	clientPK, serverPK := make(chan *crypto.BoxKey, 1), make(chan *crypto.BoxKey, 1)
+	clientSendSetupVC := func(pubKey *crypto.BoxKey) error {
+		clientPK <- pubKey
+		return client.FinishHandshakeDialedVC(v, <-serverPK)
+	}
+	serverExchange := func(pubKey *crypto.BoxKey) (*crypto.BoxKey, error) {
+		serverPK <- pubKey
+		return <-clientPK, nil
+	}
+
+	hrCH := server.HandshakeAcceptedVCWithAuthentication(v, pserver, pserver.BlessingStore().Default(), serverExchange, lopts...)
+	if err := client.HandshakeDialedVCWithAuthentication(pclient, clientSendSetupVC, vcopts...); err != nil {
+		go func() { <-hrCH }()
+		return err
+	}
+	hr := <-hrCH
+	if hr.Error != nil {
+		return hr.Error
+	}
+	go acceptLoop(hr.Listener)
+	return nil
+}
+
+func handshakeVCPreAuthenticated(v version.RPCVersion, client, server *vc.VC, pclient, pserver security.Principal, serverPK, serverSK *crypto.BoxKey, discharges map[string]security.Discharge, dischargeClient vc.DischargeClient, auth *vc.ServerAuthorizer) error {
+	var lopts []stream.ListenerOpt
+	if dischargeClient != nil {
+		lopts = append(lopts, dischargeClient)
+	}
+	var vcopts []stream.VCOpt
+	if auth != nil {
+		vcopts = append(vcopts, auth)
+	}
+	bserver := pserver.BlessingStore().Default()
+	bclient, _ := pclient.BlessSelf("vcauth")
+
+	clientPK, clientSig := make(chan *crypto.BoxKey, 1), make(chan []byte, 1)
+	serverAccepted := make(chan struct{})
+	sendSetupVC := func(pubKey *crypto.BoxKey, signature []byte) error {
+		clientPK <- pubKey
+		clientSig <- signature
+		// Unlike the real world (in VIF), a message can be delivered to a server before
+		// it handles SetupVC message. So we explictly sync in this test.
+		<-serverAccepted
+		return nil
+	}
+
+	var hrCH <-chan vc.HandshakeResult
+	go func() {
+		params := security.CallParams{LocalPrincipal: pserver, LocalBlessings: bserver, RemoteBlessings: bclient, LocalDischarges: discharges}
+		hrCH = server.HandshakeAcceptedVCPreAuthenticated(v, params, <-clientSig, serverPK, serverSK, <-clientPK, lopts...)
+		close(serverAccepted)
+	}()
+	params := security.CallParams{LocalPrincipal: pclient, LocalBlessings: bclient, RemoteBlessings: bserver, RemoteDischarges: discharges}
+	if err := client.HandshakeDialedVCPreAuthenticated(v, params, serverPK, sendSetupVC, vcopts...); err != nil {
+		go func() { <-hrCH }()
+		return err
+	}
+	hr := <-hrCH
+	if hr.Error != nil {
+		return hr.Error
+	}
+	go acceptLoop(hr.Listener)
+	return nil
+}
+
+func handshakeVCNoAuthentication(v version.RPCVersion, client, server *vc.VC) error {
+	clientCH, serverCH := make(chan struct{}), make(chan struct{})
+	clientSendSetupVC := func() error {
+		close(clientCH)
+		return client.FinishHandshakeDialedVC(v, nil)
+	}
+	serverSendSetupVC := func() error {
+		close(serverCH)
+		return nil
+	}
+
+	hrCH := server.HandshakeAcceptedVCNoAuthentication(v, serverSendSetupVC)
+	if err := client.HandshakeDialedVCNoAuthentication(clientSendSetupVC); err != nil {
+		go func() { <-hrCH }()
+		return err
+	}
+	hr := <-hrCH
+	if hr.Error != nil {
+		return hr.Error
+	}
+	go acceptLoop(hr.Listener)
+	return nil
+}
+
+// pipeLoop forwards slices written to h.bq to dst.
+func (h *helper) pipeLoop(ctx *context.T, dst *vc.VC) {
+	for {
+		w, bufs, err := h.bq.Get(nil)
+		if err != nil {
+			return
+		}
+		fid := id.Flow(w.ID())
+		for _, b := range bufs {
+			cipher, err := h.VC.Encrypt(fid, b)
+			if err != nil {
+				ctx.Infof("vc encrypt failed: %v", err)
+			}
+			if err := dst.DispatchPayload(fid, cipher); err != nil {
+				ctx.Infof("dispatch payload failed: %v", err)
+				return
+			}
+		}
+		if w.IsDrained() {
+			h.VC.ShutdownFlow(fid)
+			dst.ShutdownFlow(fid)
+		}
+	}
+}
+
+func acceptLoop(ln stream.Listener) {
+	for {
+		f, err := ln.Accept()
+		if err != nil {
+			return
+		}
+		go echoLoop(f)
+	}
+}
+
+func echoLoop(flow stream.Flow) {
+	var buf [vc.DefaultBytesBufferedPerFlow * 20]byte
+	for {
+		n, err := flow.Read(buf[:])
+		if err == io.EOF {
+			return
+		}
+		if err == nil {
+			_, err = flow.Write(buf[:n])
+		}
+		if err != nil {
+			panic(err)
+		}
+	}
+}
+
+func (h *helper) NotifyOfNewFlow(vci id.VC, fid id.Flow, bytes uint) {
+	h.mu.Lock()
+	defer h.mu.Unlock()
+	if h.otherEnd != nil {
+		if err := h.otherEnd.VC.AcceptFlow(fid); err != nil {
+			panic(verror.DebugString(err))
+		}
+		h.otherEnd.VC.ReleaseCounters(fid, uint32(bytes))
+	}
+}
+
+func (h *helper) AddReceiveBuffers(vci id.VC, fid id.Flow, bytes uint) {
+	h.mu.Lock()
+	defer h.mu.Unlock()
+	if h.otherEnd != nil {
+		h.otherEnd.VC.ReleaseCounters(fid, uint32(bytes))
+	}
+}
+
+func (h *helper) NewWriter(vci id.VC, fid id.Flow, priority bqueue.Priority) (bqueue.Writer, error) {
+	return h.bq.NewWriter(bqueue.ID(fid), priority, DefaultBytesBufferedPerFlow)
+}
+
+func (h *helper) Close() {
+	h.VC.Close(fmt.Errorf("helper closed"))
+	h.bq.Close()
+	h.mu.Lock()
+	otherEnd := h.otherEnd
+	h.otherEnd = nil
+	h.mu.Unlock()
+	if otherEnd != nil {
+		otherEnd.mu.Lock()
+		otherEnd.otherEnd = nil
+		otherEnd.mu.Unlock()
+		otherEnd.Close()
+	}
+}
+
+type endpoint naming.RoutingID
+
+func (e endpoint) Network() string                          { return "test" }
+func (e endpoint) VersionedString(int) string               { return e.String() }
+func (e endpoint) String() string                           { return naming.RoutingID(e).String() }
+func (e endpoint) Name() string                             { return naming.JoinAddressName(e.String(), "") }
+func (e endpoint) RoutingID() naming.RoutingID              { return naming.RoutingID(e) }
+func (e endpoint) Routes() []string                         { return nil }
+func (e endpoint) Addr() net.Addr                           { return nil }
+func (e endpoint) ServesMountTable() bool                   { return false }
+func (e endpoint) ServesLeaf() bool                         { return false }
+func (e endpoint) BlessingNames() []string                  { return nil }
+func (e endpoint) RPCVersionRange() version.RPCVersionRange { return version.RPCVersionRange{} }
diff --git a/runtime/internal/rpc/stream/vc/writer.go b/runtime/internal/rpc/stream/vc/writer.go
new file mode 100644
index 0000000..684a357
--- /dev/null
+++ b/runtime/internal/rpc/stream/vc/writer.go
@@ -0,0 +1,200 @@
+// 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 vc
+
+import (
+	"io"
+	"sync"
+	"sync/atomic"
+
+	"v.io/v23/verror"
+
+	"v.io/x/ref/runtime/internal/lib/bqueue"
+	"v.io/x/ref/runtime/internal/lib/iobuf"
+	vsync "v.io/x/ref/runtime/internal/lib/sync"
+	"v.io/x/ref/runtime/internal/rpc/stream"
+)
+
+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.
+	errWriterClosed     = reg(".errWriterClosed", "attempt to call Write on Flow that has been Closed")
+	errBQueuePutFailed  = reg(".errBqueuePutFailed", "bqueue.Writer.Put failed{:3}")
+	errFailedToGetQuota = reg(".errFailedToGetQuota", "failed to get quota from receive buffers shared by all new flows on a VC{:3}")
+	errCanceled         = reg(".errCanceled", "underlying queues canceled")
+)
+
+// writer implements the io.Writer and SetWriteDeadline interfaces for Flow.
+type writer struct {
+	MTU            int              // Maximum size (in bytes) of each slice Put into Sink.
+	Sink           bqueue.Writer    // Buffer queue writer where data from Write is sent as iobuf.Slice objects.
+	Alloc          *iobuf.Allocator // Allocator for iobuf.Slice objects. GUARDED_BY(mu)
+	SharedCounters *vsync.Semaphore // Semaphore hosting counters shared by all flows over a VC.
+
+	mu         sync.Mutex      // Guards call to Writes
+	wroteOnce  bool            // GUARDED_BY(mu)
+	isClosed   bool            // GUARDED_BY(mu)
+	closeError error           // GUARDED_BY(mu)
+	closed     chan struct{}   // GUARDED_BY(mu)
+	deadline   <-chan struct{} // GUARDED_BY(mu)
+
+	// Total number of bytes filled in by all Write calls on this writer.
+	// Atomic operations are used to manipulate it.
+	totalBytes uint32
+
+	// Accounting for counters borrowed from the shared pool.
+	muSharedCountersBorrowed sync.Mutex
+	sharedCountersBorrowed   int // GUARDED_BY(muSharedCountersBorrowed)
+}
+
+func newWriter(mtu int, sink bqueue.Writer, alloc *iobuf.Allocator, counters *vsync.Semaphore) *writer {
+	return &writer{
+		MTU:            mtu,
+		Sink:           sink,
+		Alloc:          alloc,
+		SharedCounters: counters,
+		closed:         make(chan struct{}),
+		closeError:     verror.New(errWriterClosed, nil),
+	}
+}
+
+// Shutdown closes the writer and discards any queued up write buffers, i.e.,
+// the bqueue.Get call will not see the buffers queued up at this writer.
+// If removeWriter is true the writer will also be removed entirely from the
+// bqueue, otherwise the now empty writer will eventually be returned by
+// bqueue.Get.
+func (w *writer) shutdown(removeWriter bool) {
+	w.Sink.Shutdown(removeWriter)
+	w.finishClose(true)
+}
+
+// Close closes the writer without discarding any queued up write buffers.
+func (w *writer) Close() {
+	w.Sink.Close()
+	w.finishClose(false)
+}
+
+func (w *writer) IsClosed() bool {
+	w.mu.Lock()
+	defer w.mu.Unlock()
+	return w.isClosed
+}
+
+func (w *writer) Closed() <-chan struct{} {
+	return w.closed
+}
+
+func (w *writer) finishClose(remoteShutdown bool) {
+	// IsClosed() and Closed() indicate that the writer is closed before
+	// finishClose() completes. This is safe because Alloc and shared counters
+	// are guarded, and are not accessed elsewhere after w.closed is closed.
+	w.mu.Lock()
+	// finishClose() is idempotent, but Go's builtin close is not.
+	if !w.isClosed {
+		w.isClosed = true
+		if remoteShutdown {
+			w.closeError = io.EOF
+		}
+		close(w.closed)
+	}
+
+	w.Alloc.Release()
+	w.mu.Unlock()
+
+	w.muSharedCountersBorrowed.Lock()
+	w.SharedCounters.IncN(uint(w.sharedCountersBorrowed))
+	w.sharedCountersBorrowed = 0
+	w.muSharedCountersBorrowed.Unlock()
+}
+
+// Write implements the Write call for a Flow.
+//
+// Flow control is achieved using receive buffers (aka counters), wherein the
+// receiving end sends out the number of bytes that it is willing to read. To
+// avoid an additional round-trip for the creation of new flows, the very first
+// write of a new flow borrows counters from a shared pool.
+func (w *writer) Write(b []byte) (int, error) {
+	written := 0
+	// net.Conn requires that multiple goroutines be able to invoke methods
+	// simulatenously.
+	w.mu.Lock()
+	defer w.mu.Unlock()
+	if w.isClosed {
+		if w.closeError == io.EOF {
+			return 0, io.EOF
+		}
+		return 0, verror.New(stream.ErrBadState, nil, w.closeError)
+	}
+
+	for len(b) > 0 {
+		n := len(b)
+		if n > w.MTU {
+			n = w.MTU
+		}
+		if !w.wroteOnce && w.SharedCounters != nil {
+			w.wroteOnce = true
+			if n > MaxSharedBytes {
+				n = MaxSharedBytes
+			}
+			if err := w.SharedCounters.DecN(uint(n), w.deadline); err != nil {
+				if err == vsync.ErrCanceled {
+					return 0, stream.NewNetError(verror.New(stream.ErrNetwork, nil, verror.New(errCanceled, nil)), true, false)
+				}
+				return 0, verror.New(stream.ErrNetwork, nil, verror.New(errFailedToGetQuota, nil, err))
+			}
+			w.muSharedCountersBorrowed.Lock()
+			w.sharedCountersBorrowed = n
+			w.muSharedCountersBorrowed.Unlock()
+			w.Sink.Release(n)
+		}
+		slice := w.Alloc.Copy(b[:n])
+		if err := w.Sink.Put(slice, w.deadline); err != nil {
+			slice.Release()
+			atomic.AddUint32(&w.totalBytes, uint32(written))
+			switch err {
+			case bqueue.ErrCancelled, vsync.ErrCanceled:
+				return written, stream.NewNetError(verror.New(stream.ErrNetwork, nil, verror.New(errCanceled, nil)), true, false)
+			case bqueue.ErrWriterIsClosed:
+				return written, verror.New(stream.ErrBadState, nil, verror.New(errWriterClosed, nil))
+			default:
+				return written, verror.New(stream.ErrNetwork, nil, verror.New(errBQueuePutFailed, nil, err))
+			}
+		}
+		written += n
+		b = b[n:]
+	}
+	atomic.AddUint32(&w.totalBytes, uint32(written))
+	return written, nil
+}
+
+func (w *writer) SetDeadline(deadline <-chan struct{}) {
+	w.mu.Lock()
+	w.deadline = deadline
+	w.mu.Unlock()
+}
+
+// Release allows the next 'bytes' of data to be removed from the buffer queue
+// writer and passed to bqueue.Get.
+func (w *writer) Release(bytes int) {
+	w.muSharedCountersBorrowed.Lock()
+	switch {
+	case w.sharedCountersBorrowed == 0:
+		w.Sink.Release(bytes)
+	case w.sharedCountersBorrowed >= bytes:
+		w.SharedCounters.IncN(uint(bytes))
+		w.sharedCountersBorrowed -= bytes
+	default:
+		w.SharedCounters.IncN(uint(w.sharedCountersBorrowed))
+		w.Sink.Release(bytes - w.sharedCountersBorrowed)
+		w.sharedCountersBorrowed = 0
+	}
+	w.muSharedCountersBorrowed.Unlock()
+}
+
+func (w *writer) BytesWritten() uint32 {
+	return atomic.LoadUint32(&w.totalBytes)
+}
diff --git a/runtime/internal/rpc/stream/vc/writer_test.go b/runtime/internal/rpc/stream/vc/writer_test.go
new file mode 100644
index 0000000..baaebfd
--- /dev/null
+++ b/runtime/internal/rpc/stream/vc/writer_test.go
@@ -0,0 +1,223 @@
+// 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 vc
+
+import (
+	"bytes"
+	"io"
+	"net"
+	"reflect"
+	"testing"
+
+	"v.io/v23/verror"
+
+	"v.io/x/ref/runtime/internal/lib/bqueue"
+	"v.io/x/ref/runtime/internal/lib/bqueue/drrqueue"
+	"v.io/x/ref/runtime/internal/lib/iobuf"
+	"v.io/x/ref/runtime/internal/lib/sync"
+	"v.io/x/ref/runtime/internal/rpc/stream"
+)
+
+// TestWrite is a very basic, easy to follow, but not very thorough test of the
+// writer.  More thorough testing of flows (and implicitly the writer) is in
+// vc_test.go.
+func TestWrite(t *testing.T) {
+	bq := drrqueue.New(128)
+	defer bq.Close()
+
+	bw, err := bq.NewWriter(0, 0, 10)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	shared := sync.NewSemaphore()
+	shared.IncN(4)
+
+	w := newTestWriter(bw, shared)
+
+	if n, err := w.Write([]byte("abcd")); n != 4 || err != nil {
+		t.Errorf("Got (%d, %v) want (4, nil)", n, err)
+	}
+
+	// Should have used up 4 shared counters
+	if err := shared.TryDecN(1); err != sync.ErrTryAgain {
+		t.Errorf("Got %v want %v", err, sync.ErrTryAgain)
+	}
+
+	// Further Writes will block until some space has been released.
+	w.Release(10)
+	if n, err := w.Write([]byte("efghij")); n != 6 || err != nil {
+		t.Errorf("Got (%d, %v) want (5, nil)", n, err)
+	}
+	// And the release should have returned to the shared counters set
+	if err := shared.TryDecN(4); err != nil {
+		t.Errorf("Got %v want %v", err, nil)
+	}
+
+	// Further writes will block since all 10 bytes (provided to NewWriter)
+	// have been exhausted and Get hasn't been called on bq yet.
+	deadline := make(chan struct{}, 0)
+	w.SetDeadline(deadline)
+	close(deadline)
+
+	w.SetDeadline(deadline)
+	if n, err := w.Write([]byte("k")); n != 0 || !isTimeoutError(err) {
+		t.Errorf("Got (%d, %v) want (0, timeout error)", n, err)
+	}
+
+	w.Close()
+	if w.BytesWritten() != 10 {
+		t.Errorf("Got %d want %d", w.BytesWritten(), 10)
+	}
+
+	_, bufs, err := bq.Get(nil)
+	var read bytes.Buffer
+	for _, b := range bufs {
+		read.Write(b.Contents)
+		b.Release()
+	}
+	if g, w := read.String(), "abcdefghij"; g != w {
+		t.Errorf("Got %q want %q", g, w)
+	}
+}
+
+func TestCloseBeforeWrite(t *testing.T) {
+	bq := drrqueue.New(128)
+	defer bq.Close()
+
+	bw, err := bq.NewWriter(0, 0, 10)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	shared := sync.NewSemaphore()
+	shared.IncN(4)
+
+	w := newTestWriter(bw, shared)
+	w.Close()
+
+	if n, err := w.Write([]byte{1, 2}); n != 0 || verror.ErrorID(err) != stream.ErrBadState.ID {
+		t.Errorf("Got (%v, %v) want (0, %v)", n, err, stream.ErrBadState)
+	}
+}
+
+func TestShutdownBeforeWrite(t *testing.T) {
+	bq := drrqueue.New(128)
+	defer bq.Close()
+
+	bw, err := bq.NewWriter(0, 0, 10)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	shared := sync.NewSemaphore()
+	shared.IncN(4)
+
+	w := newTestWriter(bw, shared)
+	w.shutdown(true)
+
+	if n, err := w.Write([]byte{1, 2}); n != 0 || err != io.EOF {
+		t.Errorf("Got (%v, %v) want (0, %v)", n, err, io.EOF)
+	}
+}
+
+func TestCloseDoesNotDiscardPendingWrites(t *testing.T) {
+	bq := drrqueue.New(128)
+	defer bq.Close()
+
+	bw, err := bq.NewWriter(0, 0, 10)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	shared := sync.NewSemaphore()
+	shared.IncN(2)
+
+	w := newTestWriter(bw, shared)
+	data := []byte{1, 2}
+	if n, err := w.Write(data); n != len(data) || err != nil {
+		t.Fatalf("Got (%d, %v) want (%d, nil)", n, err, len(data))
+	}
+	w.Close()
+
+	gbw, bufs, err := bq.Get(nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if gbw != bw {
+		t.Fatalf("Got %v want %v", gbw, bw)
+	}
+	if len(bufs) != 1 {
+		t.Fatalf("Got %d bufs, want 1", len(bufs))
+	}
+	if !reflect.DeepEqual(bufs[0].Contents, data) {
+		t.Fatalf("Got %v want %v", bufs[0].Contents, data)
+	}
+	if !gbw.IsDrained() {
+		t.Fatal("Expected bqueue.Writer to be drained")
+	}
+}
+
+func TestWriterCloseIsIdempotent(t *testing.T) {
+	bq := drrqueue.New(128)
+	defer bq.Close()
+
+	bw, err := bq.NewWriter(0, 0, 10)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	shared := sync.NewSemaphore()
+	shared.IncN(1)
+	w := newTestWriter(bw, shared)
+	if n, err := w.Write([]byte{1}); n != 1 || err != nil {
+		t.Fatalf("Got (%d, %v) want (1, nil)", n, err)
+	}
+	// Should have used up the shared counter.
+	if err := shared.TryDec(); err != sync.ErrTryAgain {
+		t.Fatalf("Got %v want %v", err, sync.ErrTryAgain)
+	}
+	w.Close()
+	// The shared counter should have been returned
+	if err := shared.TryDec(); err != nil {
+		t.Fatalf("Got %v want nil", err)
+	}
+	// Closing again shouldn't affect the shared counters
+	w.Close()
+	if err := shared.TryDec(); err != sync.ErrTryAgain {
+		t.Fatalf("Got %v want %v", err, sync.ErrTryAgain)
+	}
+}
+
+func TestClosedChannel(t *testing.T) {
+	bq := drrqueue.New(128)
+	defer bq.Close()
+
+	bw, err := bq.NewWriter(0, 0, 10)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	shared := sync.NewSemaphore()
+	shared.IncN(4)
+
+	w := newTestWriter(bw, shared)
+	go w.Close()
+	<-w.Closed()
+
+	if n, err := w.Write([]byte{1, 2}); n != 0 || verror.ErrorID(err) != stream.ErrBadState.ID {
+		t.Errorf("Got (%v, %v) want (0, %v)", n, err, stream.ErrBadState.ID)
+	}
+}
+
+func newTestWriter(bqw bqueue.Writer, shared *sync.Semaphore) *writer {
+	alloc := iobuf.NewAllocator(iobuf.NewPool(0), 0)
+	return newWriter(16, bqw, alloc, shared)
+}
+
+func isTimeoutError(err error) bool {
+	neterr, ok := err.(net.Error)
+	return ok && neterr.Timeout()
+}
diff --git a/runtime/internal/rpc/stream/vif/auth.go b/runtime/internal/rpc/stream/vif/auth.go
new file mode 100644
index 0000000..8cdcaa5
--- /dev/null
+++ b/runtime/internal/rpc/stream/vif/auth.go
@@ -0,0 +1,287 @@
+// 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 vif
+
+import (
+	"io"
+
+	"v.io/v23/naming"
+	"v.io/v23/rpc/version"
+	"v.io/v23/security"
+	"v.io/v23/verror"
+
+	"v.io/x/ref/runtime/internal/lib/iobuf"
+	"v.io/x/ref/runtime/internal/rpc/stream"
+	"v.io/x/ref/runtime/internal/rpc/stream/crypto"
+	"v.io/x/ref/runtime/internal/rpc/stream/message"
+	"v.io/x/ref/runtime/internal/rpc/stream/vc"
+	iversion "v.io/x/ref/runtime/internal/rpc/version"
+)
+
+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.
+	errAuthFailed                      = reg(".errAuthFailed", "authentication failed{:3}")
+	errUnsupportedEncryptVersion       = reg(".errUnsupportedEncryptVersion", "unsupported encryption version {4} < {5}")
+	errNaclBoxVersionNegotiationFailed = reg(".errNaclBoxVersionNegotiationFailed", "nacl box encryption version negotiation failed")
+	errVersionNegotiationFailed        = reg(".errVersionNegotiationFailed", "encryption version negotiation failed")
+	nullCipher                         crypto.NullControlCipher
+)
+
+// AuthenticationResult includes the result of the VIF authentication.
+type AuthenticationResult struct {
+	Dialed           bool
+	Version          version.RPCVersion
+	RemoteEndpoint   naming.Endpoint
+	SessionKeys      SessionKeys
+	LocalBlessings   security.Blessings
+	RemoteBlessings  security.Blessings
+	LocalDischarges  map[string]security.Discharge
+	RemoteDischarges map[string]security.Discharge
+}
+
+// Public/private keys used to establish an encrypted communication channel
+// (and not the keys corresponding to the principal used in authentication).
+type SessionKeys struct {
+	LocalPublic, LocalPrivate, RemotePublic crypto.BoxKey
+}
+
+// AuthenticateAsClient sends a Setup message if possible. If so, it chooses
+// encryption based on the max supported version.
+//
+// The sequence is initiated by the client.
+//
+//    - The client sends a Setup message to the server, containing the client's
+//      supported versions, and the client's crypto options. The Setup message
+//      is sent in the clear.
+//
+//    - When the server receives the Setup message, it calls
+//      AuthenticateAsServer, which constructs a response Setup containing
+//      the server's version range, and any crypto options.
+//
+//    - The client and server use the public/private key pairs
+//      generated for the Setup messages to create an encrypted stream
+//      of SetupStream messages for the remainder of the authentication
+//      setup. The encryption uses NewControlCipherRPC11, which is based
+//      on code.google.com/p/go.crypto/nacl/box.
+//
+//    - Once the encrypted SetupStream channel is setup, the client and
+//      server authenticate using the vc.AuthenticateAs{Client,Server} protocol.
+//
+// Note that the Setup messages are sent in the clear, so they are subject to
+// modification by a man-in-the-middle, which can currently force a downgrade by
+// modifying the acceptable version ranges downward.  This can be addressed by
+// including a hash of the Setup message in the encrypted stream.  It is
+// likely that this will be addressed in subsequent protocol versions.
+func AuthenticateAsClient(writer io.Writer, reader *iobuf.Reader, localEP naming.Endpoint, versions *iversion.Range, principal security.Principal, auth *vc.ServerAuthorizer) (crypto.ControlCipher, *AuthenticationResult, error) {
+	if versions == nil {
+		versions = iversion.SupportedRange
+	}
+
+	// Send the client's public data.
+	setup, pk, sk, err := makeSetup(versions, localEP)
+	if err != nil {
+		return nil, nil, verror.New(stream.ErrSecurity, nil, err)
+	}
+
+	errch := make(chan error, 1)
+	go func() {
+		errch <- message.WriteTo(writer, setup, nullCipher)
+	}()
+
+	remoteMsg, err := message.ReadFrom(reader, nullCipher)
+	if err != nil {
+		return nil, nil, verror.New(stream.ErrNetwork, nil, err)
+	}
+	remoteSetup, ok := remoteMsg.(*message.Setup)
+	if !ok {
+		return nil, nil, verror.New(stream.ErrSecurity, nil, verror.New(errVersionNegotiationFailed, nil))
+	}
+
+	// Wait for the write to succeed.
+	if err := <-errch; err != nil {
+		return nil, nil, verror.New(stream.ErrNetwork, nil, err)
+	}
+
+	// Choose the max version in the intersection.
+	vrange, err := setup.Versions.Intersect(&remoteSetup.Versions)
+	if err != nil {
+		return nil, nil, verror.New(stream.ErrNetwork, nil, err)
+	}
+
+	if principal == nil {
+		return nullCipher, nil, nil
+	}
+
+	// Perform the authentication.
+	ver := vrange.Max
+	remoteBox := remoteSetup.NaclBox()
+	if remoteBox == nil {
+		return nil, nil, verror.New(errNaclBoxVersionNegotiationFailed, nil)
+	}
+	remoteEP := remoteSetup.PeerEndpoint()
+
+	var cipher crypto.ControlCipher
+	switch {
+	case ver < version.RPCVersion11:
+		cipher = crypto.NewControlCipherRPC6(sk, &remoteBox.PublicKey, false)
+	default:
+		cipher = crypto.NewControlCipherRPC11(pk, sk, &remoteBox.PublicKey)
+	}
+	sconn := newSetupConn(writer, reader, cipher)
+	crypter := crypto.NewNullCrypterWithChannelBinding(cipher.ChannelBinding())
+	params := security.CallParams{LocalPrincipal: principal, LocalEndpoint: localEP}
+	// TODO(jyh): act upon the authentication results.
+	lBlessings, rBlessings, rDischarges, err := vc.AuthenticateAsClient(sconn, crypter, ver, params, auth)
+	if err != nil {
+		return nil, nil, err
+	}
+	if ver < version.RPCVersion11 || remoteEP == nil {
+		// We do not return AuthenticationResult for old versions due to a channel binding bug.
+		return cipher, nil, nil
+	}
+
+	authr := AuthenticationResult{
+		Dialed:         true,
+		Version:        ver,
+		RemoteEndpoint: remoteEP,
+		SessionKeys: SessionKeys{
+			RemotePublic: remoteBox.PublicKey,
+		},
+		LocalBlessings:   lBlessings,
+		RemoteBlessings:  rBlessings,
+		RemoteDischarges: rDischarges,
+	}
+	return cipher, &authr, nil
+}
+
+// AuthenticateAsServer handles a Setup message, choosing authentication
+// based on the max common version.
+//
+// See AuthenticateAsClient for a description of the negotiation.
+func AuthenticateAsServer(writer io.Writer, reader *iobuf.Reader, localEP naming.Endpoint, versions *iversion.Range, principal security.Principal, lBlessings security.Blessings, dc vc.DischargeClient) (crypto.ControlCipher, *AuthenticationResult, error) {
+	if versions == nil {
+		versions = iversion.SupportedRange
+	}
+
+	// Send server's public data.
+	setup, pk, sk, err := makeSetup(versions, localEP)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	errch := make(chan error, 1)
+	readch := make(chan struct{})
+	go func() {
+		// TODO(mattr,ribrdb): In the case of the agent, which is
+		// currently the only user of insecure connections, we need to
+		// wait for the client to initiate the communication.  The agent
+		// sends an extra first byte to clients, which clients read before
+		// dialing their side of the vif.  If we send this message before
+		// the magic byte has been sent the client will use the first
+		// byte of this message instead rendering the remainder of the
+		// stream uninterpretable.
+		if principal == nil {
+			<-readch
+		}
+		errch <- message.WriteTo(writer, setup, nullCipher)
+	}()
+
+	// Read client's public data.
+	remoteMsg, err := message.ReadFrom(reader, nullCipher)
+	close(readch) // Note: we need to close this whether we get an error or not.
+	if err != nil {
+		<-errch
+		return nil, nil, verror.New(stream.ErrNetwork, nil, err)
+	}
+	remoteSetup, ok := remoteMsg.(*message.Setup)
+	if !ok {
+		<-errch
+		return nil, nil, verror.New(stream.ErrSecurity, nil, verror.New(errVersionNegotiationFailed, nil))
+	}
+
+	// Wait for the write to succeed.
+	if err := <-errch; err != nil {
+		return nil, nil, err
+	}
+
+	// Choose the max version in the intersection.
+	vrange, err := versions.Intersect(&remoteSetup.Versions)
+	if err != nil {
+		return nil, nil, verror.New(stream.ErrNetwork, nil, err)
+	}
+
+	if principal == nil {
+		return nullCipher, nil, nil
+	}
+
+	// Perform authentication.
+	ver := vrange.Max
+	remoteBox := remoteSetup.NaclBox()
+	if remoteBox == nil {
+		return nil, nil, verror.New(errNaclBoxVersionNegotiationFailed, nil)
+	}
+	remoteEP := remoteSetup.PeerEndpoint()
+
+	var cipher crypto.ControlCipher
+	switch {
+	case ver < version.RPCVersion11:
+		cipher = crypto.NewControlCipherRPC6(sk, &remoteBox.PublicKey, true)
+	default:
+		cipher = crypto.NewControlCipherRPC11(pk, sk, &remoteBox.PublicKey)
+	}
+	sconn := newSetupConn(writer, reader, cipher)
+	crypter := crypto.NewNullCrypterWithChannelBinding(cipher.ChannelBinding())
+	// TODO(jyh): act upon authentication results.
+	rBlessings, lDischarges, err := vc.AuthenticateAsServer(sconn, crypter, ver, principal, lBlessings, dc)
+	if err != nil {
+		return nil, nil, verror.New(errAuthFailed, nil, err)
+	}
+	if ver < version.RPCVersion11 || remoteEP == nil {
+		// We do not return AuthenticationResult for old versions due to a channel binding bug.
+		return cipher, nil, nil
+	}
+
+	authr := AuthenticationResult{
+		Version:        ver,
+		RemoteEndpoint: remoteEP,
+		SessionKeys: SessionKeys{
+			LocalPublic:  *pk,
+			LocalPrivate: *sk,
+		},
+		LocalBlessings:  lBlessings,
+		RemoteBlessings: rBlessings,
+		LocalDischarges: lDischarges,
+	}
+	return cipher, &authr, nil
+}
+
+// makeSetup constructs the options that this process can support.
+func makeSetup(versions *iversion.Range, localEP naming.Endpoint) (setup *message.Setup, publicKey, privateKey *crypto.BoxKey, err error) {
+	publicKey, privateKey, err = crypto.GenerateBoxKey()
+	if err != nil {
+		return nil, nil, nil, err
+	}
+	options := []message.SetupOption{&message.NaclBox{PublicKey: *publicKey}}
+	if localEP != nil {
+		options = append(options, &message.PeerEndpoint{LocalEndpoint: localEP})
+	}
+	setup = &message.Setup{Versions: *versions, Options: options}
+	return
+}
+
+// getDischargeClient returns the dischargeClient needed to fetch server discharges for this call.
+// TODO(suharshs): Perhaps we should pass dischargeClient explicitly?
+func getDischargeClient(lopts []stream.ListenerOpt) vc.DischargeClient {
+	for _, o := range lopts {
+		switch v := o.(type) {
+		case vc.DischargeClient:
+			return v
+		}
+	}
+	return nil
+}
diff --git a/runtime/internal/rpc/stream/vif/doc.go b/runtime/internal/rpc/stream/vif/doc.go
new file mode 100644
index 0000000..9f00db4
--- /dev/null
+++ b/runtime/internal/rpc/stream/vif/doc.go
@@ -0,0 +1,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.
+
+// Package vif implements a virtual network interface that wraps over a
+// net.Conn and provides the ability to Dial and Listen for virtual circuits
+// (v.io/x/ref/runtime/internal/rpc/stream.VC)
+package vif
diff --git a/runtime/internal/rpc/stream/vif/faketimer.go b/runtime/internal/rpc/stream/vif/faketimer.go
new file mode 100644
index 0000000..d257a85
--- /dev/null
+++ b/runtime/internal/rpc/stream/vif/faketimer.go
@@ -0,0 +1,93 @@
+// 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 vif
+
+import (
+	"sync"
+	"time"
+)
+
+// Since an idle timer with a short timeout can expire before establishing a VC,
+// we provide a fake timer to reduce dependence on real time in unittests.
+type fakeTimer struct {
+	mu          sync.Mutex
+	timeout     time.Duration
+	timeoutFunc func()
+	timer       timer
+	stopped     bool
+}
+
+func newFakeTimer(d time.Duration, f func()) *fakeTimer {
+	return &fakeTimer{
+		timeout:     d,
+		timeoutFunc: f,
+		timer:       noopTimer{},
+	}
+}
+
+func (t *fakeTimer) Stop() bool {
+	t.mu.Lock()
+	defer t.mu.Unlock()
+	t.stopped = true
+	return t.timer.Stop()
+}
+
+func (t *fakeTimer) Reset(d time.Duration) bool {
+	t.mu.Lock()
+	defer t.mu.Unlock()
+	active := t.timer.Stop()
+	t.timeout = d
+	t.stopped = false
+	if t.timeout > 0 {
+		t.timer = newTimer(t.timeout, t.timeoutFunc)
+	} else {
+		t.timer = noopTimer{}
+	}
+	return active
+}
+
+func (t *fakeTimer) run(release <-chan struct{}, wg *sync.WaitGroup) {
+	defer wg.Done()
+	<-release // Wait until notified to run.
+	t.mu.Lock()
+	defer t.mu.Unlock()
+	if t.timeout > 0 && !t.stopped {
+		t.timer = newTimer(t.timeout, t.timeoutFunc)
+	}
+}
+
+// SetFakeTimers causes the idle timers to use a fake timer instead of one
+// based on real time. The timers will be triggered when the returned function
+// is invoked. (at which point the timer setup will be restored to what it was
+// before calling this function)
+//
+// Usage:
+//   triggerTimers := SetFakeTimers()
+//   ...
+//   triggerTimers()
+//
+// This function cannot be called concurrently.
+func SetFakeTimers() func() {
+	backup := newTimer
+
+	var mu sync.Mutex
+	var wg sync.WaitGroup
+	release := make(chan struct{})
+	newTimer = func(d time.Duration, f func()) timer {
+		mu.Lock()
+		defer mu.Unlock()
+		wg.Add(1)
+		t := newFakeTimer(d, f)
+		go t.run(release, &wg)
+		return t
+	}
+	return func() {
+		mu.Lock()
+		defer mu.Unlock()
+		newTimer = backup
+		close(release)
+		wg.Wait()
+	}
+}
diff --git a/runtime/internal/rpc/stream/vif/idletimer.go b/runtime/internal/rpc/stream/vif/idletimer.go
new file mode 100644
index 0000000..a9910c7
--- /dev/null
+++ b/runtime/internal/rpc/stream/vif/idletimer.go
@@ -0,0 +1,139 @@
+// 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 vif
+
+import (
+	"sync"
+	"time"
+
+	"v.io/x/ref/runtime/internal/rpc/stream/id"
+	"v.io/x/ref/runtime/internal/rpc/stream/vc"
+)
+
+// idleTimerMap keeps track of all the flows of each VC and then calls the notify
+// function in its own goroutine if there is no flow in a VC for some duration.
+type idleTimerMap struct {
+	mu         sync.Mutex
+	m          map[id.VC]*idleTimer
+	notifyFunc func(id.VC)
+	stopped    bool
+}
+
+type idleTimer struct {
+	set     map[id.Flow]struct{}
+	timeout time.Duration
+	timer   timer
+	stopped bool
+}
+
+type timer interface {
+	// Stop prevents the Timer from firing.
+	Stop() bool
+	// Reset changes the timer to expire after duration d.
+	Reset(d time.Duration) bool
+}
+
+// newIdleTimerMap returns a new idle timer map.
+func newIdleTimerMap(f func(id.VC)) *idleTimerMap {
+	return &idleTimerMap{
+		m:          make(map[id.VC]*idleTimer),
+		notifyFunc: f,
+	}
+}
+
+// Stop stops idle timers for all VC.
+func (m *idleTimerMap) Stop() {
+	m.mu.Lock()
+	defer m.mu.Unlock()
+	if m.stopped {
+		return
+	}
+	for _, t := range m.m {
+		if !t.stopped {
+			t.timer.Stop()
+			t.stopped = true
+		}
+	}
+	m.stopped = true
+}
+
+// Insert starts the idle timer for the given VC. If there is no active flows
+// in the VC for the duration d, the notify function will be called in its own
+// goroutine. If d is zero, the idle timer is disabled.
+func (m *idleTimerMap) Insert(vci id.VC, d time.Duration) bool {
+	m.mu.Lock()
+	defer m.mu.Unlock()
+	if m.stopped {
+		return false
+	}
+	if _, exists := m.m[vci]; exists {
+		return false
+	}
+	t := &idleTimer{
+		set:     make(map[id.Flow]struct{}),
+		timeout: d,
+	}
+	if t.timeout > 0 {
+		t.timer = newTimer(t.timeout, func() { m.notifyFunc(vci) })
+	} else {
+		t.timer = noopTimer{}
+	}
+	m.m[vci] = t
+	return true
+}
+
+// Delete deletes the idle timer for the given VC.
+func (m *idleTimerMap) Delete(vci id.VC) {
+	m.mu.Lock()
+	if t, exists := m.m[vci]; exists {
+		if !t.stopped {
+			t.timer.Stop()
+		}
+		delete(m.m, vci)
+	}
+	m.mu.Unlock()
+}
+
+// InsertFlow inserts the given flow to the given VC. All system flows will be ignored.
+func (m *idleTimerMap) InsertFlow(vci id.VC, fid id.Flow) {
+	if fid < vc.NumReservedFlows {
+		return
+	}
+	m.mu.Lock()
+	if t, exists := m.m[vci]; exists {
+		t.set[fid] = struct{}{}
+		if !t.stopped {
+			t.timer.Stop()
+			t.stopped = true
+		}
+	}
+	m.mu.Unlock()
+}
+
+// DeleteFlow deletes the given flow from the VC vci.
+func (m *idleTimerMap) DeleteFlow(vci id.VC, fid id.Flow) {
+	m.mu.Lock()
+	if t, exists := m.m[vci]; exists {
+		delete(t.set, fid)
+		if len(t.set) == 0 && t.stopped && !m.stopped {
+			t.timer.Reset(t.timeout)
+			t.stopped = false
+		}
+	}
+	m.mu.Unlock()
+}
+
+// To avoid dependence on real times in unittests, the factory function for timers
+// can be overridden (with SetFakeTimers). This factory function should only be
+// overridden for unittests.
+var newTimer = defaultTimerFactory
+
+func defaultTimerFactory(d time.Duration, f func()) timer { return time.AfterFunc(d, f) }
+
+// A noop timer.
+type noopTimer struct{}
+
+func (t noopTimer) Stop() bool                 { return false }
+func (t noopTimer) Reset(d time.Duration) bool { return false }
diff --git a/runtime/internal/rpc/stream/vif/idletimer_test.go b/runtime/internal/rpc/stream/vif/idletimer_test.go
new file mode 100644
index 0000000..d5bd6f3
--- /dev/null
+++ b/runtime/internal/rpc/stream/vif/idletimer_test.go
@@ -0,0 +1,130 @@
+// 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 vif
+
+import (
+	"testing"
+	"time"
+
+	"v.io/x/ref/runtime/internal/rpc/stream/id"
+	"v.io/x/ref/runtime/internal/rpc/stream/vc"
+)
+
+func TestIdleTimer(t *testing.T) {
+	const (
+		idleTime = 5 * time.Millisecond
+		waitTime = idleTime * 2
+
+		vc1 id.VC = 1
+		vc2 id.VC = 2
+
+		flow1        id.Flow = vc.NumReservedFlows
+		flow2        id.Flow = vc.NumReservedFlows + 1
+		flowReserved id.Flow = vc.NumReservedFlows - 1
+	)
+
+	notify := make(chan interface{})
+	notifyFunc := func(vci id.VC) { notify <- vci }
+
+	m := newIdleTimerMap(notifyFunc)
+
+	// An empty map. Should not be notified.
+	if err := WaitWithTimeout(notify, waitTime); err != nil {
+		t.Error(err)
+	}
+
+	m.Insert(vc1, idleTime)
+
+	// A new empty VC. Should be notified.
+	if err := WaitForNotifications(notify, vc1); err != nil {
+		t.Error(err)
+	}
+
+	m.Delete(vc1)
+	m.Insert(vc1, idleTime)
+
+	// A VC with one flow. Should not be notified.
+	m.InsertFlow(vc1, flow1)
+	if err := WaitWithTimeout(notify, waitTime); err != nil {
+		t.Error(err)
+	}
+
+	// Try to delete non-existent flow. Should not be notified.
+	m.DeleteFlow(vc1, flow2)
+	if err := WaitWithTimeout(notify, waitTime); err != nil {
+		t.Error(err)
+	}
+
+	// Delete the flow. Should be notified.
+	m.DeleteFlow(vc1, flow1)
+	if err := WaitForNotifications(notify, vc1); err != nil {
+		t.Error(err)
+	}
+
+	// Try to delete the deleted flow again. Should not be notified.
+	m.DeleteFlow(vc1, flow1)
+	if err := WaitWithTimeout(notify, waitTime); err != nil {
+		t.Error(err)
+	}
+
+	m.Delete(vc1)
+	m.Insert(vc1, idleTime)
+
+	// Delete an empty VC. Should not be notified.
+	m.Delete(vc1)
+	if err := WaitWithTimeout(notify, waitTime); err != nil {
+		t.Error(err)
+	}
+
+	m.Insert(vc1, idleTime)
+
+	// A VC with two flows.
+	m.InsertFlow(vc1, flow1)
+	m.InsertFlow(vc1, flow2)
+
+	// Delete the first flow twice. Should not be notified.
+	m.DeleteFlow(vc1, flow1)
+	m.DeleteFlow(vc1, flow1)
+	if err := WaitWithTimeout(notify, waitTime); err != nil {
+		t.Error(err)
+	}
+
+	// Delete the second flow. Should be notified.
+	m.DeleteFlow(vc1, flow2)
+	if err := WaitForNotifications(notify, vc1); err != nil {
+		t.Error(err)
+	}
+
+	m.Delete(vc1)
+	m.Insert(vc1, idleTime)
+
+	// Insert a reserved flow. Should be notified.
+	m.InsertFlow(vc1, flowReserved)
+	if err := WaitForNotifications(notify, vc1); err != nil {
+		t.Error(err)
+	}
+
+	m.Delete(vc1)
+	m.Insert(vc1, idleTime)
+	m.Insert(vc2, idleTime)
+
+	// Multiple VCs. Should be notified for each.
+	if err := WaitForNotifications(notify, vc1, vc2); err != nil {
+		t.Error(err)
+	}
+
+	m.Delete(vc1)
+	m.Delete(vc2)
+	m.Insert(vc1, idleTime)
+
+	// Stop the timer. Should not be notified.
+	m.Stop()
+	if m.Insert(vc1, idleTime) {
+		t.Fatal("timer has been stopped, but can insert a vc")
+	}
+	if err := WaitWithTimeout(notify, waitTime); err != nil {
+		t.Error(err)
+	}
+}
diff --git a/runtime/internal/rpc/stream/vif/set.go b/runtime/internal/rpc/stream/vif/set.go
new file mode 100644
index 0000000..3032dfc
--- /dev/null
+++ b/runtime/internal/rpc/stream/vif/set.go
@@ -0,0 +1,146 @@
+// 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 vif
+
+import (
+	"math/rand"
+	"net"
+	"runtime"
+	"sync"
+
+	"v.io/v23/rpc"
+)
+
+// Set implements a set of VIFs keyed by (network, address) of the underlying
+// connection.  Multiple goroutines can invoke methods on the Set
+// simultaneously.
+type Set struct {
+	mu      sync.RWMutex
+	set     map[string][]*VIF // GUARDED_BY(mu)
+	started map[string]bool   // GUARDED_BY(mu)
+	keys    map[*VIF]string   // GUARDED_BY(mu)
+	cond    *sync.Cond
+}
+
+// NewSet returns a new Set of VIFs.
+func NewSet() *Set {
+	s := &Set{
+		set:     make(map[string][]*VIF),
+		started: make(map[string]bool),
+		keys:    make(map[*VIF]string),
+	}
+	s.cond = sync.NewCond(&s.mu)
+	return s
+}
+
+// BlockingFind returns a VIF where the remote end of the underlying network connection
+// is identified by the provided (network, address). Returns nil if there is no
+// such VIF.
+//
+// The caller is required to call the returned unblock function, to avoid deadlock.
+// Until the returned function is called, all new BlockingFind calls for this
+// network and address will block.
+func (s *Set) BlockingFind(network, address string) (*VIF, func()) {
+	if isNonDistinctConn(network, address) {
+		return nil, func() {}
+	}
+
+	k := key(network, address)
+
+	s.mu.Lock()
+	defer s.mu.Unlock()
+
+	for s.started[k] {
+		s.cond.Wait()
+	}
+
+	_, _, _, p := rpc.RegisteredProtocol(network)
+	for _, n := range p {
+		if vifs := s.set[key(n, address)]; len(vifs) > 0 {
+			return vifs[rand.Intn(len(vifs))], func() {}
+		}
+	}
+
+	s.started[k] = true
+	return nil, func() { s.unblock(network, address) }
+}
+
+// unblock marks the status of the network, address as no longer started, and
+// broadcasts waiting threads.
+func (s *Set) unblock(network, address string) {
+	s.mu.Lock()
+	delete(s.started, key(network, address))
+	s.cond.Broadcast()
+	s.mu.Unlock()
+}
+
+// Insert adds a VIF to the set.
+func (s *Set) Insert(vif *VIF, network, address string) {
+	k := key(network, address)
+	s.mu.Lock()
+	defer s.mu.Unlock()
+	s.keys[vif] = k
+	vifs := s.set[k]
+	for _, v := range vifs {
+		if v == vif {
+			return
+		}
+	}
+	s.set[k] = append(vifs, vif)
+}
+
+// Delete removes a VIF from the set.
+func (s *Set) Delete(vif *VIF) {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+	k := s.keys[vif]
+	vifs := s.set[k]
+	for i, v := range vifs {
+		if v == vif {
+			if len(vifs) == 1 {
+				delete(s.set, k)
+			} else {
+				s.set[k] = append(vifs[:i], vifs[i+1:]...)
+			}
+			delete(s.keys, vif)
+			return
+		}
+	}
+}
+
+// List returns the elements in the set as a slice.
+func (s *Set) List() []*VIF {
+	s.mu.RLock()
+	defer s.mu.RUnlock()
+	l := make([]*VIF, 0, len(s.set))
+	for _, vifs := range s.set {
+		l = append(l, vifs...)
+	}
+	return l
+}
+
+func key(network, address string) string {
+	if network == "tcp" || network == "ws" {
+		host, _, _ := net.SplitHostPort(address)
+		switch ip := net.ParseIP(host); {
+		case ip == nil:
+			// This may happen when address is a hostname. But we do not care
+			// about it, since vif cannot be found with a hostname anyway.
+		case ip.To4() != nil:
+			network += "4"
+		default:
+			network += "6"
+		}
+	}
+	return network + ":" + address
+}
+
+// Some network connections (like those created with net.Pipe or Unix sockets)
+// do not end up with distinct net.Addrs on distinct net.Conns.
+func isNonDistinctConn(network, address string) bool {
+	return len(address) == 0 ||
+		(network == "pipe" && address == "pipe") ||
+		(runtime.GOOS == "linux" && network == "unix" && address == "@")
+}
diff --git a/runtime/internal/rpc/stream/vif/set_test.go b/runtime/internal/rpc/stream/vif/set_test.go
new file mode 100644
index 0000000..b79d8d5
--- /dev/null
+++ b/runtime/internal/rpc/stream/vif/set_test.go
@@ -0,0 +1,357 @@
+// 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 vif_test
+
+import (
+	"fmt"
+	"io/ioutil"
+	"net"
+	"os"
+	"path"
+	"testing"
+	"time"
+
+	"v.io/x/lib/set"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+	"v.io/v23/verror"
+
+	_ "v.io/x/ref/runtime/factories/generic"
+	"v.io/x/ref/runtime/internal/rpc/stream/vif"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/testutil"
+)
+
+var supportsIPv6 bool
+
+func init() {
+	simpleResolver := func(ctx *context.T, network, address string) (string, string, error) {
+		return network, address, nil
+	}
+	simpleDial := func(ctx *context.T, p, a string, timeout time.Duration) (net.Conn, error) {
+		return net.DialTimeout(p, a, timeout)
+	}
+	simpleListen := func(ctx *context.T, p, a string) (net.Listener, error) {
+		return net.Listen(p, a)
+	}
+	rpc.RegisterProtocol("unix", simpleDial, simpleResolver, simpleListen)
+
+	// Check whether the platform supports IPv6.
+	ln, err := net.Listen("tcp6", "[::1]:0")
+	defer ln.Close()
+	if err == nil {
+		supportsIPv6 = true
+	}
+}
+
+func newConn(network, address string) (net.Conn, net.Conn, error) {
+	dfunc, _, lfunc, _ := rpc.RegisteredProtocol(network)
+	ln, err := lfunc(nil, network, address)
+	if err != nil {
+		return nil, nil, err
+	}
+	defer ln.Close()
+
+	done := make(chan net.Conn)
+	go func() {
+		conn, err := ln.Accept()
+		if err != nil {
+			panic(err)
+		}
+		conn.Read(make([]byte, 1)) // Read a dummy byte.
+		done <- conn
+	}()
+
+	conn, err := dfunc(nil, ln.Addr().Network(), ln.Addr().String(), 1*time.Second)
+	if err != nil {
+		return nil, nil, err
+	}
+	// Write a dummy byte since wsh listener waits for the magic bytes for ws.
+	conn.Write([]byte("."))
+	return conn, <-done, nil
+}
+
+func newVIF(ctx *context.T, c, s net.Conn) (*vif.VIF, *vif.VIF, error) {
+	done := make(chan *vif.VIF)
+	go func() {
+		principal := testutil.NewPrincipal("accepted")
+		ctx, _ = v23.WithPrincipal(ctx, principal)
+		blessings := principal.BlessingStore().Default()
+		vf, err := vif.InternalNewAcceptedVIF(ctx, s, naming.FixedRoutingID(0x5), blessings, nil, nil)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "ERR 2: %s\n", verror.DebugString(err))
+			panic(err)
+		}
+		done <- vf
+	}()
+
+	ctx, _ = v23.WithPrincipal(ctx, testutil.NewPrincipal("dialed"))
+	vf, err := vif.InternalNewDialedVIF(ctx, c, naming.FixedRoutingID(0xc), nil, nil)
+	if err != nil {
+		return nil, nil, err
+	}
+	return vf, <-done, nil
+}
+
+func diff(a, b []string) []string {
+	s1, s2 := set.String.FromSlice(a), set.String.FromSlice(b)
+	set.String.Difference(s1, s2)
+	return set.String.ToSlice(s1)
+}
+
+func find(set *vif.Set, n, a string) *vif.VIF {
+	found, unblock := set.BlockingFind(n, a)
+	unblock()
+	return found
+}
+
+func TestSetBasic(t *testing.T) {
+	ctx, shutdown := test.V23InitAnon()
+	defer shutdown()
+	sockdir, err := ioutil.TempDir("", "TestSetBasic")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(sockdir)
+
+	all := rpc.RegisteredProtocols()
+	unknown := naming.UnknownProtocol
+	tests := []struct {
+		network, address string
+		compatibles      []string
+	}{
+		{"tcp", "127.0.0.1:0", []string{"tcp", "tcp4", "wsh", "wsh4", unknown}},
+		{"tcp4", "127.0.0.1:0", []string{"tcp", "tcp4", "wsh", "wsh4", unknown}},
+		{"tcp", "[::1]:0", []string{"tcp", "tcp6", "wsh", "wsh6", unknown}},
+		{"tcp6", "[::1]:0", []string{"tcp", "tcp6", "wsh", "wsh6", unknown}},
+		{"ws", "127.0.0.1:0", []string{"ws", "ws4", "wsh", "wsh4", unknown}},
+		{"ws4", "127.0.0.1:0", []string{"ws", "ws4", "wsh", "wsh4", unknown}},
+		{"ws", "[::1]:0", []string{"ws", "ws6", "wsh", "wsh6", unknown}},
+		{"ws6", "[::1]:0", []string{"ws", "ws6", "wsh", "wsh6", unknown}},
+		// wsh dial always uses tcp.
+		{"wsh", "127.0.0.1:0", []string{"tcp", "tcp4", "wsh", "wsh4", unknown}},
+		{"wsh4", "127.0.0.1:0", []string{"tcp", "tcp4", "wsh", "wsh4", unknown}},
+		{"wsh", "[::1]:0", []string{"tcp", "tcp6", "wsh", "wsh6", unknown}},
+		{"wsh6", "[::1]:0", []string{"tcp", "tcp6", "wsh", "wsh6", unknown}},
+		{unknown, "127.0.0.1:0", []string{"tcp", "tcp4", "wsh", "wsh4", unknown}},
+		{unknown, "[::1]:0", []string{"tcp", "tcp6", "wsh", "wsh6", unknown}},
+		{"unix", path.Join(sockdir, "socket"), []string{"unix"}},
+	}
+
+	set := vif.NewSet()
+	for _, test := range tests {
+		if test.address == "[::1]:0" && !supportsIPv6 {
+			continue
+		}
+
+		name := fmt.Sprintf("(%q, %q)", test.network, test.address)
+
+		c, s, err := newConn(test.network, test.address)
+		if err != nil {
+			t.Fatal(err)
+		}
+		vf, _, err := newVIF(ctx, c, s)
+		if err != nil {
+			t.Fatal(err)
+		}
+		a := c.RemoteAddr()
+
+		set.Insert(vf, a.Network(), a.String())
+		for _, n := range test.compatibles {
+			if found := find(set, n, a.String()); found == nil {
+				t.Fatalf("%s: Got nil, but want [%v] on find(%q, %q))", name, vf, n, a)
+			}
+		}
+
+		for _, n := range diff(all, test.compatibles) {
+			if v := find(set, n, a.String()); v != nil {
+				t.Fatalf("%s: Got [%v], but want nil on find(%q, %q))", name, v, n, a)
+			}
+		}
+
+		set.Delete(vf)
+		for _, n := range all {
+			if v := find(set, n, a.String()); v != nil {
+				t.Fatalf("%s: Got [%v], but want nil on find(%q, %q))", name, v, n, a)
+			}
+		}
+	}
+}
+
+func TestSetWithPipes(t *testing.T) {
+	ctx, shutdown := test.V23InitAnon()
+	defer shutdown()
+	c1, s1 := net.Pipe()
+	c2, s2 := net.Pipe()
+	a1 := c1.RemoteAddr()
+	a2 := c2.RemoteAddr()
+	if a1.Network() != a2.Network() || a1.String() != a2.String() {
+		t.Fatalf("This test was intended for distinct connections that have duplicate RemoteAddrs. "+
+			"That does not seem to be the case with (%q, %q) and (%q, %q)",
+			a1.Network(), a1, a2.Network(), a2)
+	}
+
+	vf1, _, err := newVIF(ctx, c1, s1)
+	if err != nil {
+		t.Fatal(err)
+	}
+	vf2, _, err := newVIF(ctx, c2, s2)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	set := vif.NewSet()
+	set.Insert(vf1, a1.Network(), a1.String())
+	if v := find(set, a1.Network(), a1.String()); v != nil {
+		t.Fatalf("Got [%v], but want nil on find(%q, %q))", v, a1.Network(), a1)
+	}
+	if l := set.List(); len(l) != 1 || l[0] != vf1 {
+		t.Errorf("Unexpected list of VIFs: %v", l)
+	}
+
+	set.Insert(vf2, a2.Network(), a2.String())
+	if v := find(set, a2.Network(), a2.String()); v != nil {
+		t.Fatalf("Got [%v], but want nil on find(%q, %q))", v, a2.Network(), a2)
+	}
+	if l := set.List(); len(l) != 2 || l[0] != vf1 || l[1] != vf2 {
+		t.Errorf("Unexpected list of VIFs: %v", l)
+	}
+
+	set.Delete(vf1)
+	if l := set.List(); len(l) != 1 || l[0] != vf2 {
+		t.Errorf("Unexpected list of VIFs: %v", l)
+	}
+	set.Delete(vf2)
+	if l := set.List(); len(l) != 0 {
+		t.Errorf("Unexpected list of VIFs: %v", l)
+	}
+}
+
+func TestSetWithUnixSocket(t *testing.T) {
+	ctx, shutdown := test.V23InitAnon()
+	defer shutdown()
+	dir, err := ioutil.TempDir("", "TestSetWithUnixSocket")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(dir)
+
+	c1, s1, err := newConn("unix", path.Join(dir, "socket1"))
+	if err != nil {
+		t.Fatal(err)
+	}
+	c2, s2, err := newConn("unix", path.Join(dir, "socket2"))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// The client side address is always unix:@ regardless of socket name.
+	a1 := s1.RemoteAddr()
+	a2 := s2.RemoteAddr()
+	if a1.Network() != a2.Network() || a1.String() != a2.String() {
+		t.Fatalf("This test was intended for distinct connections that have duplicate RemoteAddrs. "+
+			"That does not seem to be the case with (%q, %q) and (%q, %q)",
+			a1.Network(), a1, a2.Network(), a2)
+	}
+
+	_, vf1, err := newVIF(ctx, c1, s1)
+	if err != nil {
+		t.Fatal(err)
+	}
+	_, vf2, err := newVIF(ctx, c2, s2)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	set := vif.NewSet()
+	set.Insert(vf1, a1.Network(), a1.String())
+	if v := find(set, a1.Network(), a1.String()); v != nil {
+		t.Fatalf("Got [%v], but want nil on find(%q, %q))", v, a1.Network(), a1)
+	}
+	if l := set.List(); len(l) != 1 || l[0] != vf1 {
+		t.Errorf("Unexpected list of VIFs: %v", l)
+	}
+
+	set.Insert(vf2, a2.Network(), a2.String())
+	if v := find(set, a2.Network(), a2.String()); v != nil {
+		t.Fatalf("Got [%v], but want nil on find(%q, %q))", v, a2.Network(), a2)
+	}
+	if l := set.List(); len(l) != 2 || l[0] != vf1 || l[1] != vf2 {
+		t.Errorf("Unexpected list of VIFs: %v", l)
+	}
+
+	set.Delete(vf1)
+	if l := set.List(); len(l) != 1 || l[0] != vf2 {
+		t.Errorf("Unexpected list of VIFs: %v", l)
+	}
+	set.Delete(vf2)
+	if l := set.List(); len(l) != 0 {
+		t.Errorf("Unexpected list of VIFs: %v", l)
+	}
+}
+
+func TestSetInsertDelete(t *testing.T) {
+	ctx, shutdown := test.V23InitAnon()
+	defer shutdown()
+	c1, s1 := net.Pipe()
+	vf1, _, err := newVIF(ctx, c1, s1)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	set1 := vif.NewSet()
+
+	n1, a1 := c1.RemoteAddr().Network(), c1.RemoteAddr().String()
+	set1.Insert(vf1, n1, a1)
+	if l := set1.List(); len(l) != 1 || l[0] != vf1 {
+		t.Errorf("Unexpected list of VIFs: %v", l)
+	}
+
+	set1.Delete(vf1)
+	if l := set1.List(); len(l) != 0 {
+		t.Errorf("Unexpected list of VIFs: %v", l)
+	}
+}
+
+func TestBlockingFind(t *testing.T) {
+	ctx, shutdown := test.V23InitAnon()
+	defer shutdown()
+	network, address := "tcp", "127.0.0.1:1234"
+	set := vif.NewSet()
+
+	_, unblock := set.BlockingFind(network, address)
+
+	ch := make(chan *vif.VIF, 1)
+
+	// set.BlockingFind should block until set.Unblock is called with the corresponding VIF,
+	// since set.BlockingFind was called earlier.
+	go func(ch chan *vif.VIF) {
+		vf, _ := set.BlockingFind(network, address)
+		ch <- vf
+	}(ch)
+
+	// set.BlockingFind for a different network and address should not block.
+	set.BlockingFind("network", "address")
+
+	// Create and insert the VIF.
+	c, s, err := newConn(network, address)
+	if err != nil {
+		t.Fatal(err)
+	}
+	vf, _, err := newVIF(ctx, c, s)
+	if err != nil {
+		t.Fatal(err)
+	}
+	set.Insert(vf, network, address)
+	unblock()
+
+	// Now the set.BlockingFind should have returned the correct vif.
+	if cachedVif := <-ch; cachedVif != vf {
+		t.Errorf("got %v, want %v", cachedVif, vf)
+	}
+}
diff --git a/runtime/internal/rpc/stream/vif/setup_conn.go b/runtime/internal/rpc/stream/vif/setup_conn.go
new file mode 100644
index 0000000..c038ef3
--- /dev/null
+++ b/runtime/internal/rpc/stream/vif/setup_conn.go
@@ -0,0 +1,71 @@
+// 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 vif
+
+import (
+	"io"
+
+	"v.io/v23/verror"
+
+	"v.io/x/ref/runtime/internal/lib/iobuf"
+	"v.io/x/ref/runtime/internal/rpc/stream"
+	"v.io/x/ref/runtime/internal/rpc/stream/crypto"
+	"v.io/x/ref/runtime/internal/rpc/stream/message"
+)
+
+// setupConn writes the data to the net.Conn using SetupStream messages.
+type setupConn struct {
+	writer  io.Writer
+	reader  *iobuf.Reader
+	cipher  crypto.ControlCipher
+	rbuffer []byte // read buffer
+}
+
+var _ io.ReadWriteCloser = (*setupConn)(nil)
+
+const maxFrameSize = 8192
+
+func newSetupConn(writer io.Writer, reader *iobuf.Reader, c crypto.ControlCipher) *setupConn {
+	return &setupConn{writer: writer, reader: reader, cipher: c}
+}
+
+// Read implements the method from net.Conn.
+func (s *setupConn) Read(buf []byte) (int, error) {
+	for len(s.rbuffer) == 0 {
+		msg, err := message.ReadFrom(s.reader, s.cipher)
+		if err != nil {
+			return 0, err
+		}
+		emsg, ok := msg.(*message.SetupStream)
+		if !ok {
+			return 0, verror.New(stream.ErrSecurity, nil, verror.New(errVersionNegotiationFailed, nil))
+		}
+		s.rbuffer = emsg.Data
+	}
+	n := copy(buf, s.rbuffer)
+	s.rbuffer = s.rbuffer[n:]
+	return n, nil
+}
+
+// Write implements the method from net.Conn.
+func (s *setupConn) Write(buf []byte) (int, error) {
+	amount := 0
+	for len(buf) > 0 {
+		n := len(buf)
+		if n > maxFrameSize {
+			n = maxFrameSize
+		}
+		emsg := message.SetupStream{Data: buf[:n]}
+		if err := message.WriteTo(s.writer, &emsg, s.cipher); err != nil {
+			return 0, err
+		}
+		buf = buf[n:]
+		amount += n
+	}
+	return amount, nil
+}
+
+// Close does nothing.
+func (s *setupConn) Close() error { return nil }
diff --git a/runtime/internal/rpc/stream/vif/setup_conn_test.go b/runtime/internal/rpc/stream/vif/setup_conn_test.go
new file mode 100644
index 0000000..7f96b82
--- /dev/null
+++ b/runtime/internal/rpc/stream/vif/setup_conn_test.go
@@ -0,0 +1,168 @@
+// 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 vif
+
+import (
+	"bytes"
+	"encoding/binary"
+	"io"
+	"net"
+	"sync"
+	"testing"
+
+	"v.io/x/ref/runtime/internal/lib/iobuf"
+)
+
+const (
+	text = `Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.`
+)
+
+func min(i, j int) int {
+	if i < j {
+		return i
+	}
+	return j
+}
+
+// testControlCipher is a super-simple cipher that xor's each byte of the
+// payload with 0xaa.
+type testControlCipher struct{}
+
+const testMACSize = 4
+
+func (*testControlCipher) MACSize() int {
+	return testMACSize
+}
+
+func testMAC(data []byte) []byte {
+	var h uint32
+	for _, b := range data {
+		h = (h << 1) ^ uint32(b)
+	}
+	var hash [4]byte
+	binary.BigEndian.PutUint32(hash[:], h)
+	return hash[:]
+}
+
+func (c *testControlCipher) Decrypt(data []byte) {
+	for i, _ := range data {
+		data[i] ^= 0xaa
+	}
+}
+
+func (c *testControlCipher) Encrypt(data []byte) {
+	for i, _ := range data {
+		data[i] ^= 0xaa
+	}
+}
+
+func (c *testControlCipher) Open(data []byte) bool {
+	mac := testMAC(data[:len(data)-testMACSize])
+	if bytes.Compare(mac, data[len(data)-testMACSize:]) != 0 {
+		return false
+	}
+	c.Decrypt(data[:len(data)-testMACSize])
+	return true
+}
+
+func (c *testControlCipher) Seal(data []byte) error {
+	c.Encrypt(data[:len(data)-testMACSize])
+	mac := testMAC(data[:len(data)-testMACSize])
+	copy(data[len(data)-testMACSize:], mac)
+	return nil
+}
+
+func (c *testControlCipher) ChannelBinding() []byte { return nil }
+
+// shortConn performs at most 3 bytes of IO at a time.
+type shortConn struct {
+	io.ReadWriteCloser
+}
+
+func (s *shortConn) Read(data []byte) (int, error) {
+	if len(data) > 3 {
+		data = data[:3]
+	}
+	return s.ReadWriteCloser.Read(data)
+}
+
+func (s *shortConn) Write(data []byte) (int, error) {
+	n := len(data)
+	for i := 0; i < n; i += 3 {
+		j := min(n, i+3)
+		m, err := s.ReadWriteCloser.Write(data[i:j])
+		if err != nil {
+			return i + m, err
+		}
+	}
+	return n, nil
+}
+
+func TestConn(t *testing.T) {
+	p1, p2 := net.Pipe()
+	pool := iobuf.NewPool(0)
+	r1 := iobuf.NewReader(pool, p1)
+	r2 := iobuf.NewReader(pool, p2)
+	f1 := newSetupConn(p1, r1, &testControlCipher{})
+	f2 := newSetupConn(p2, r2, &testControlCipher{})
+	testConn(t, f1, f2)
+}
+
+func TestShortInnerConn(t *testing.T) {
+	p1, p2 := net.Pipe()
+	s1 := &shortConn{p1}
+	s2 := &shortConn{p2}
+	pool := iobuf.NewPool(0)
+	r1 := iobuf.NewReader(pool, s1)
+	r2 := iobuf.NewReader(pool, s2)
+	f1 := newSetupConn(s1, r1, &testControlCipher{})
+	f2 := newSetupConn(s2, r2, &testControlCipher{})
+	testConn(t, f1, f2)
+}
+
+func TestShortOuterConn(t *testing.T) {
+	p1, p2 := net.Pipe()
+	pool := iobuf.NewPool(0)
+	r1 := iobuf.NewReader(pool, p1)
+	r2 := iobuf.NewReader(pool, p2)
+	e1 := newSetupConn(p1, r1, &testControlCipher{})
+	e2 := newSetupConn(p2, r2, &testControlCipher{})
+	f1 := &shortConn{e1}
+	f2 := &shortConn{e2}
+	testConn(t, f1, f2)
+}
+
+// Write prefixes of the text onto the framed pipe and verify the frame content.
+func testConn(t *testing.T, f1, f2 io.ReadWriteCloser) {
+	// Reader loop.
+	var pending sync.WaitGroup
+	pending.Add(1)
+	go func() {
+		var buf [1024]byte
+		for i := 1; i != len(text); i++ {
+			n, err := io.ReadFull(f1, buf[:i])
+			if err != nil {
+				t.Errorf("bad read: %s", err)
+			}
+			if n != i {
+				t.Errorf("bad read: got %d bytes, expected %d bytes", n, i)
+			}
+			actual := string(buf[:n])
+			expected := string(text[:n])
+			if actual != expected {
+				t.Errorf("got %q, expected %q", actual, expected)
+			}
+		}
+		pending.Done()
+	}()
+
+	// Writer.
+	for i := 1; i != len(text); i++ {
+		if n, err := f2.Write([]byte(text[:i])); err != nil || n != i {
+			t.Errorf("bad write: i=%d n=%d err=%s", i, n, err)
+		}
+	}
+	pending.Wait()
+}
diff --git a/runtime/internal/rpc/stream/vif/testutil_test.go b/runtime/internal/rpc/stream/vif/testutil_test.go
new file mode 100644
index 0000000..9d35a6a
--- /dev/null
+++ b/runtime/internal/rpc/stream/vif/testutil_test.go
@@ -0,0 +1,40 @@
+// 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 vif
+
+import (
+	"fmt"
+	"time"
+)
+
+// WaitForNotifications waits till all notifications in 'wants' have been received.
+func WaitForNotifications(notify <-chan interface{}, wants ...interface{}) error {
+	expected := make(map[interface{}]struct{})
+	for _, w := range wants {
+		expected[w] = struct{}{}
+	}
+	for len(expected) > 0 {
+		n := <-notify
+		if _, exists := expected[n]; !exists {
+			return fmt.Errorf("unexpected notification %v", n)
+		}
+		delete(expected, n)
+	}
+	return nil
+}
+
+// WaitWithTimeout returns error if any notification has been received before
+// the timeout expires.
+func WaitWithTimeout(notify <-chan interface{}, timeout time.Duration) error {
+	timer := time.After(timeout)
+	for {
+		select {
+		case n := <-notify:
+			return fmt.Errorf("unexpected notification %v", n)
+		case <-timer:
+			return nil
+		}
+	}
+}
diff --git a/runtime/internal/rpc/stream/vif/v23_internal_test.go b/runtime/internal/rpc/stream/vif/v23_internal_test.go
new file mode 100644
index 0000000..70e47de
--- /dev/null
+++ b/runtime/internal/rpc/stream/vif/v23_internal_test.go
@@ -0,0 +1,20 @@
+// 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 vif
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	os.Exit(m.Run())
+}
diff --git a/runtime/internal/rpc/stream/vif/vcmap.go b/runtime/internal/rpc/stream/vif/vcmap.go
new file mode 100644
index 0000000..bc6aa28
--- /dev/null
+++ b/runtime/internal/rpc/stream/vif/vcmap.go
@@ -0,0 +1,107 @@
+// 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 vif
+
+import (
+	"sort"
+	"sync"
+
+	"v.io/x/ref/runtime/internal/lib/pcqueue"
+	"v.io/x/ref/runtime/internal/rpc/stream/id"
+	"v.io/x/ref/runtime/internal/rpc/stream/vc"
+)
+
+// vcMap implements a thread-safe map of vc.VC objects (vcInfo) keyed by their VCI.
+type vcMap struct {
+	mu     sync.Mutex
+	m      map[id.VC]vcInfo
+	frozen bool
+}
+
+// vcInfo represents per-VC information maintained by a VIF.
+type vcInfo struct {
+	VC *vc.VC
+	// Queues used to dispatch work to per-VC goroutines.
+	// RQ is where vif.readLoop can dispatch work to.
+	// WQ is where vif.writeLoop can dispatch work to.
+	RQ, WQ *pcqueue.T
+}
+
+func newVCMap() *vcMap { return &vcMap{m: make(map[id.VC]vcInfo)} }
+
+func (m *vcMap) Insert(c *vc.VC) (inserted bool, rq, wq *pcqueue.T) {
+	m.mu.Lock()
+	defer m.mu.Unlock()
+	if m.frozen {
+		return false, nil, nil
+	}
+	if _, exists := m.m[c.VCI()]; exists {
+		return false, nil, nil
+	}
+	info := vcInfo{
+		VC: c,
+		RQ: pcqueue.New(100),
+		WQ: pcqueue.New(100),
+	}
+	m.m[c.VCI()] = info
+	return true, info.RQ, info.WQ
+}
+
+func (m *vcMap) Find(vci id.VC) (vc *vc.VC, rq, wq *pcqueue.T) {
+	m.mu.Lock()
+	info := m.m[vci]
+	m.mu.Unlock()
+	return info.VC, info.RQ, info.WQ
+}
+
+// Delete deletes the given VC and returns true if the map is empty after deletion.
+func (m *vcMap) Delete(vci id.VC) bool {
+	m.mu.Lock()
+	if info, exists := m.m[vci]; exists {
+		info.RQ.Close()
+		info.WQ.Close()
+		delete(m.m, vci)
+	}
+	empty := len(m.m) == 0
+	m.mu.Unlock()
+	return empty
+}
+
+func (m *vcMap) Size() int {
+	m.mu.Lock()
+	defer m.mu.Unlock()
+	return len(m.m)
+}
+
+// Freeze causes all subsequent Inserts to fail.
+// Returns a list of all the VCs that are in the map.
+func (m *vcMap) Freeze() []vcInfo {
+	m.mu.Lock()
+	m.frozen = true
+	l := make([]vcInfo, 0, len(m.m))
+	for _, info := range m.m {
+		l = append(l, info)
+	}
+	m.mu.Unlock()
+	return l
+}
+
+type vcSlice []*vc.VC
+
+func (s vcSlice) Len() int           { return len(s) }
+func (s vcSlice) Less(i, j int) bool { return s[i].VCI() < s[j].VCI() }
+func (s vcSlice) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
+
+// List returns the list of all VCs currently in the map, sorted by VCI
+func (m *vcMap) List() []*vc.VC {
+	m.mu.Lock()
+	l := make([]*vc.VC, 0, len(m.m))
+	for _, info := range m.m {
+		l = append(l, info.VC)
+	}
+	m.mu.Unlock()
+	sort.Sort(vcSlice(l))
+	return l
+}
diff --git a/runtime/internal/rpc/stream/vif/vcmap_test.go b/runtime/internal/rpc/stream/vif/vcmap_test.go
new file mode 100644
index 0000000..350dd40
--- /dev/null
+++ b/runtime/internal/rpc/stream/vif/vcmap_test.go
@@ -0,0 +1,93 @@
+// 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 vif
+
+import (
+	"reflect"
+	"testing"
+
+	"v.io/x/ref/runtime/internal/rpc/stream/vc"
+	"v.io/x/ref/test"
+)
+
+func TestVCMap(t *testing.T) {
+	ctx, cancel := test.TestContext()
+	defer cancel()
+	m := newVCMap()
+
+	vc12 := vc.InternalNew(ctx, vc.Params{VCI: 12})
+	vc34 := vc.InternalNew(ctx, vc.Params{VCI: 34})
+	vc45 := vc.InternalNew(ctx, vc.Params{VCI: 45})
+
+	if vc, _, _ := m.Find(12); vc != nil {
+		t.Errorf("Unexpected VC found: %+v", vc)
+	}
+	if ok, _, _ := m.Insert(vc34); !ok {
+		t.Errorf("Insert should have returned true on first insert")
+	}
+	if ok, _, _ := m.Insert(vc34); ok {
+		t.Errorf("Insert should have returned false on second insert")
+	}
+	if ok, _, _ := m.Insert(vc12); !ok {
+		t.Errorf("Insert should have returned true on first insert")
+	}
+	if ok, _, _ := m.Insert(vc45); !ok {
+		t.Errorf("Insert should have returned true on the first insert")
+	}
+	if g, w := m.List(), []*vc.VC{vc12, vc34, vc45}; !reflect.DeepEqual(g, w) {
+		t.Errorf("Did not get all VCs in expected order. Got %v, want %v", g, w)
+	}
+	m.Delete(vc34.VCI())
+	if g, w := m.List(), []*vc.VC{vc12, vc45}; !reflect.DeepEqual(g, w) {
+		t.Errorf("Did not get all VCs in expected order. Got %v, want %v", g, w)
+	}
+}
+
+func TestVCMapFreeze(t *testing.T) {
+	ctx, cancel := test.TestContext()
+	defer cancel()
+	m := newVCMap()
+	vc1 := vc.InternalNew(ctx, vc.Params{VCI: 1})
+	vc2 := vc.InternalNew(ctx, vc.Params{VCI: 2})
+	if ok, _, _ := m.Insert(vc1); !ok {
+		t.Fatal("Should be able to insert the VC")
+	}
+	m.Freeze()
+	if ok, _, _ := m.Insert(vc2); ok {
+		t.Errorf("Should not be able to insert a VC after Freeze")
+	}
+	if vc, _, _ := m.Find(1); vc != vc1 {
+		t.Errorf("Got %v want %v", vc, vc1)
+	}
+	m.Delete(vc1.VCI())
+	if vc, _, _ := m.Find(1); vc != nil {
+		t.Errorf("Got %v want nil", vc)
+	}
+}
+
+func TestVCMapDelete(t *testing.T) {
+	ctx, cancel := test.TestContext()
+	defer cancel()
+	m := newVCMap()
+
+	vc1 := vc.InternalNew(ctx, vc.Params{VCI: 1})
+	vc2 := vc.InternalNew(ctx, vc.Params{VCI: 2})
+
+	m.Insert(vc1)
+	if empty := m.Delete(vc1.VCI()); !empty {
+		t.Error("Want empty; got false")
+	}
+
+	m.Insert(vc1)
+	m.Insert(vc2)
+
+	m.Delete(vc1.VCI())
+	if empty := m.Delete(vc1.VCI()); empty {
+		t.Error("Want not empty; got true")
+	}
+	if empty := m.Delete(vc2.VCI()); !empty {
+		t.Error("Want empty; got false")
+	}
+}
diff --git a/runtime/internal/rpc/stream/vif/vif.go b/runtime/internal/rpc/stream/vif/vif.go
new file mode 100644
index 0000000..d8fd6d7
--- /dev/null
+++ b/runtime/internal/rpc/stream/vif/vif.go
@@ -0,0 +1,1246 @@
+// 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 vif
+
+// Logging guidelines:
+// .VI(1) for per-net.Conn information
+// .VI(2) for per-VC information
+// .VI(3) for per-Flow information
+
+import (
+	"bytes"
+	"errors"
+	"fmt"
+	"net"
+	"reflect"
+	"sort"
+	"strings"
+	"sync"
+	"time"
+
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/rpc/version"
+	"v.io/v23/security"
+	"v.io/v23/verror"
+	"v.io/v23/vtrace"
+
+	"v.io/x/ref/runtime/internal/lib/bqueue"
+	"v.io/x/ref/runtime/internal/lib/bqueue/drrqueue"
+	"v.io/x/ref/runtime/internal/lib/iobuf"
+	"v.io/x/ref/runtime/internal/lib/pcqueue"
+	vsync "v.io/x/ref/runtime/internal/lib/sync"
+	"v.io/x/ref/runtime/internal/lib/upcqueue"
+	inaming "v.io/x/ref/runtime/internal/naming"
+	"v.io/x/ref/runtime/internal/rpc/stream"
+	"v.io/x/ref/runtime/internal/rpc/stream/crypto"
+	"v.io/x/ref/runtime/internal/rpc/stream/id"
+	"v.io/x/ref/runtime/internal/rpc/stream/message"
+	"v.io/x/ref/runtime/internal/rpc/stream/vc"
+	iversion "v.io/x/ref/runtime/internal/rpc/version"
+)
+
+const pkgPath = "v.io/x/ref/runtime/internal/rpc/stream/vif"
+
+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.
+	errShuttingDown             = reg(".errShuttingDown", "underlying network connection({3}) shutting down")
+	errVCHandshakeFailed        = reg(".errVCHandshakeFailed", "VC handshake failed{:3}")
+	errSendOnExpressQFailed     = reg(".errSendOnExpressQFailed", "vif.sendOnExpressQ(OpenVC) failed{:3}")
+	errVIFIsBeingClosed         = reg(".errVIFIsBeingClosed", "VIF is being closed")
+	errVIFAlreadyAcceptingFlows = reg(".errVIFAlreadyAcceptingFlows", "already accepting flows on VIF {3}")
+	errVCsNotAcceptedOnVIF      = reg(".errVCsNotAcceptedOnVIF", "VCs not accepted on VIF {3}")
+	errAcceptFailed             = reg(".errAcceptFailed", "Accept failed{:3}")
+	errRemoteEndClosedVC        = reg(".errRemoteEndClosedVC", "remote end closed VC{:3}")
+	errFlowsNoLongerAccepted    = reg(".errFlowsNowLongerAccepted", "Flows no longer being accepted")
+	errVCAcceptFailed           = reg(".errVCAcceptFailed", "VC accept failed{:3}")
+	errIdleTimeout              = reg(".errIdleTimeout", "idle timeout")
+	errVIFAlreadySetup          = reg(".errVIFAlreadySetupt", "VIF is already setup")
+	errBqueueWriterForXpress    = reg(".errBqueueWriterForXpress", "failed to create bqueue.Writer for express messages{:3}")
+	errBqueueWriterForControl   = reg(".errBqueueWriterForControl", "failed to create bqueue.Writer for flow control counters{:3}")
+	errBqueueWriterForStopping  = reg(".errBqueueWriterForStopping", "failed to create bqueue.Writer for stopping the write loop{:3}")
+	errWriteFailed              = reg(".errWriteFailed", "write failed: got ({3}, {4}) for {5} byte message)")
+)
+
+// VIF implements a "virtual interface" over an underlying network connection
+// (net.Conn). Just like multiple network connections can be established over a
+// single physical interface, multiple Virtual Circuits (VCs) can be
+// established over a single VIF.
+type VIF struct {
+	ctx *context.T
+
+	// All reads must be performed through reader, and not directly through conn.
+	conn    net.Conn
+	pool    *iobuf.Pool
+	reader  *iobuf.Reader
+	localEP naming.Endpoint
+
+	// ctrlCipher is normally guarded by writeMu, however see the exception in
+	// readLoop.
+	ctrlCipher crypto.ControlCipher
+	writeMu    sync.Mutex
+
+	muStartTimer sync.Mutex
+	startTimer   timer
+
+	vcMap              *vcMap
+	idleTimerMap       *idleTimerMap
+	wpending, rpending vsync.WaitGroup
+
+	muListen     sync.Mutex
+	acceptor     *upcqueue.T          // GUARDED_BY(muListen)
+	listenerOpts []stream.ListenerOpt // GUARDED_BY(muListen)
+	principal    security.Principal
+	// TODO(jhahn): Merge this blessing with the one in authResult once
+	// we fixed to pass blessings to StartAccepting().
+	blessings  security.Blessings
+	authResult *AuthenticationResult
+
+	muNextVCI sync.Mutex
+	nextVCI   id.VC
+
+	outgoing bqueue.T
+	expressQ bqueue.Writer
+
+	flowQ        bqueue.Writer
+	flowMu       sync.Mutex
+	flowCounters message.Counters
+
+	stopQ bqueue.Writer
+
+	// The RPC version range supported by this VIF.  In practice this is
+	// non-nil only in testing.  nil is equivalent to using the versions
+	// actually supported by this RPC implementation (which is always
+	// what you want outside of tests).
+	versions *iversion.Range
+
+	isClosedMu sync.Mutex
+	isClosed   bool // GUARDED_BY(isClosedMu)
+	onClose    func(*VIF)
+
+	muStats sync.Mutex
+	stats   Stats
+
+	loopWG sync.WaitGroup
+}
+
+// ConnectorAndFlow represents a Flow and the Connector that can be used to
+// create another Flow over the same underlying VC.
+type ConnectorAndFlow struct {
+	Connector stream.Connector
+	Flow      stream.Flow
+}
+
+// Stats holds stats for a VIF.
+type Stats struct {
+	SendMsgCounter map[reflect.Type]uint64
+	RecvMsgCounter map[reflect.Type]uint64
+
+	NumDialedVCs        uint
+	NumAcceptedVCs      uint
+	NumPreAuthenticated uint
+}
+
+// Separate out constants that are not exported so that godoc looks nicer for
+// the exported ones.
+const (
+	// Priorities of the buffered queues used for flow control of writes.
+	expressPriority bqueue.Priority = iota
+	controlPriority
+	// The range of flow priorities is [flowPriority, flowPriority + NumFlowPriorities)
+	flowPriority
+	stopPriority = flowPriority + vc.NumFlowPriorities
+)
+
+const (
+	// Convenience aliases so that the package name "vc" does not
+	// conflict with the variables named "vc".
+	defaultBytesBufferedPerFlow = vc.DefaultBytesBufferedPerFlow
+	sharedFlowID                = vc.SharedFlowID
+)
+
+type vifSide bool
+
+const (
+	dialedVIF   vifSide = true
+	acceptedVIF vifSide = false
+)
+
+// InternalNewDialedVIF creates a new virtual interface over the provided
+// network connection, under the assumption that the conn object was created
+// using net.Dial. If onClose is given, it is run in its own goroutine when
+// the vif has been closed.
+//
+// As the name suggests, this method is intended for use only within packages
+// placed inside v.io/x/ref/runtime/internal. Code outside the
+// v.io/x/ref/runtime/internal/* packages should never call this method.
+func InternalNewDialedVIF(ctx *context.T, conn net.Conn, rid naming.RoutingID, versions *iversion.Range, onClose func(*VIF), opts ...stream.VCOpt) (*VIF, error) {
+	if ctx != nil {
+		var span vtrace.Span
+		ctx, span = vtrace.WithNewSpan(ctx, "InternalNewDialedVIF")
+		span.Annotatef("(%v, %v)", conn.RemoteAddr().Network(), conn.RemoteAddr())
+		defer span.Finish()
+	}
+	principal := stream.GetPrincipalVCOpts(ctx, opts...)
+	pool := iobuf.NewPool(0)
+	reader := iobuf.NewReader(pool, conn)
+	localEP := localEndpoint(conn, rid, versions)
+
+	// TODO(ataly, ashankar, suharshs): Figure out what authorization policy to use
+	// for authenticating the server during VIF establishment. Note that we cannot
+	// use the VC.ServerAuthorizer available in 'opts' as that applies to the end
+	// server and not the remote endpoint of the VIF.
+	c, authr, err := AuthenticateAsClient(conn, reader, localEP, versions, principal, nil)
+	if err != nil {
+		return nil, verror.New(stream.ErrNetwork, ctx, err)
+	}
+	var blessings security.Blessings
+	if principal != nil {
+		blessings = principal.BlessingStore().Default()
+	}
+	var startTimeout time.Duration
+	for _, o := range opts {
+		switch v := o.(type) {
+		case vc.StartTimeout:
+			startTimeout = v.Duration
+		}
+	}
+	return internalNew(ctx, conn, pool, reader, localEP, id.VC(vc.NumReservedVCs), versions, principal, blessings, startTimeout, onClose, nil, nil, c, authr)
+}
+
+// InternalNewAcceptedVIF creates a new virtual interface over the provided
+// network connection, under the assumption that the conn object was created
+// using an Accept call on a net.Listener object. If onClose is given, it is
+// run in its own goroutine when the vif has been closed.
+//
+// The returned VIF is also setup for accepting new VCs and Flows with the provided
+// ListenerOpts.
+//
+// As the name suggests, this method is intended for use only within packages
+// placed inside v.io/x/ref/runtime/internal. Code outside the
+// v.io/x/ref/runtime/internal/* packages should never call this method.
+func InternalNewAcceptedVIF(ctx *context.T, conn net.Conn, rid naming.RoutingID, blessings security.Blessings, versions *iversion.Range, onClose func(*VIF), lopts ...stream.ListenerOpt) (*VIF, error) {
+	pool := iobuf.NewPool(0)
+	reader := iobuf.NewReader(pool, conn)
+	localEP := localEndpoint(conn, rid, versions)
+	dischargeClient := getDischargeClient(lopts)
+	principal := stream.GetPrincipalListenerOpts(ctx, lopts...)
+	c, authr, err := AuthenticateAsServer(conn, reader, localEP, versions, principal, blessings, dischargeClient)
+	if err != nil {
+		return nil, err
+	}
+
+	var startTimeout time.Duration
+	for _, o := range lopts {
+		switch v := o.(type) {
+		case vc.StartTimeout:
+			startTimeout = v.Duration
+		}
+	}
+	return internalNew(ctx, conn, pool, reader, localEP, id.VC(vc.NumReservedVCs)+1, versions, principal, blessings, startTimeout, onClose, upcqueue.New(), lopts, c, authr)
+}
+
+func internalNew(ctx *context.T, conn net.Conn, pool *iobuf.Pool, reader *iobuf.Reader, localEP naming.Endpoint, initialVCI id.VC, versions *iversion.Range, principal security.Principal, blessings security.Blessings, startTimeout time.Duration, onClose func(*VIF), acceptor *upcqueue.T, listenerOpts []stream.ListenerOpt, c crypto.ControlCipher, authr *AuthenticationResult) (*VIF, error) {
+	var (
+		// Choose IDs that will not conflict with any other (VC, Flow)
+		// pairs.  VCI 0 is never used by the application (it is
+		// reserved for control messages), so steal from the Flow space
+		// there.
+		expressID bqueue.ID = packIDs(0, 0)
+		flowID    bqueue.ID = packIDs(0, 1)
+		stopID    bqueue.ID = packIDs(0, 2)
+	)
+	outgoing := drrqueue.New(vc.MaxPayloadSizeBytes)
+
+	expressQ, err := outgoing.NewWriter(expressID, expressPriority, defaultBytesBufferedPerFlow)
+	if err != nil {
+		return nil, verror.New(stream.ErrNetwork, nil, verror.New(errBqueueWriterForXpress, nil, err))
+	}
+	expressQ.Release(-1) // Disable flow control
+
+	flowQ, err := outgoing.NewWriter(flowID, controlPriority, flowToken.Size())
+	if err != nil {
+		return nil, verror.New(stream.ErrNetwork, nil, verror.New(errBqueueWriterForControl, nil, err))
+	}
+	flowQ.Release(-1) // Disable flow control
+
+	stopQ, err := outgoing.NewWriter(stopID, stopPriority, 1)
+	if err != nil {
+		return nil, verror.New(stream.ErrNetwork, nil, verror.New(errBqueueWriterForStopping, nil, err))
+	}
+	stopQ.Release(-1) // Disable flow control
+
+	if versions == nil {
+		versions = iversion.SupportedRange
+	}
+
+	vif := &VIF{
+		ctx:          ctx,
+		conn:         conn,
+		pool:         pool,
+		reader:       reader,
+		localEP:      localEP,
+		ctrlCipher:   c,
+		vcMap:        newVCMap(),
+		acceptor:     acceptor,
+		listenerOpts: listenerOpts,
+		principal:    principal,
+		blessings:    blessings,
+		authResult:   authr,
+		nextVCI:      initialVCI,
+		outgoing:     outgoing,
+		expressQ:     expressQ,
+		flowQ:        flowQ,
+		flowCounters: message.NewCounters(),
+		stopQ:        stopQ,
+		versions:     versions,
+		onClose:      onClose,
+		stats:        Stats{SendMsgCounter: make(map[reflect.Type]uint64), RecvMsgCounter: make(map[reflect.Type]uint64)},
+	}
+	if startTimeout > 0 {
+		vif.startTimer = newTimer(startTimeout, vif.Close)
+	}
+	vif.idleTimerMap = newIdleTimerMap(func(vci id.VC) {
+		vc, _, _ := vif.vcMap.Find(vci)
+		if vc != nil {
+			vif.closeVCAndSendMsg(vc, false, verror.New(errIdleTimeout, nil))
+		}
+	})
+	vif.loopWG.Add(2)
+	go vif.readLoop()
+	go vif.writeLoop()
+	return vif, nil
+}
+
+// Dial creates a new VC to the provided remote identity, authenticating the VC
+// with the provided local identity.
+func (vif *VIF) Dial(ctx *context.T, remoteEP naming.Endpoint, opts ...stream.VCOpt) (stream.VC, error) {
+	var idleTimeout time.Duration
+	for _, o := range opts {
+		switch v := o.(type) {
+		case vc.IdleTimeout:
+			idleTimeout = v.Duration
+		}
+	}
+	principal := stream.GetPrincipalVCOpts(ctx, opts...)
+	vc, err := vif.newVC(ctx, vif.allocVCI(), vif.localEP, remoteEP, idleTimeout, true)
+	if err != nil {
+		return nil, err
+	}
+	counters := message.NewCounters()
+	counters.Add(vc.VCI(), sharedFlowID, defaultBytesBufferedPerFlow)
+
+	usePreauth := vif.useVIFAuthForVC(vif.versions.Max, vif.localEP, remoteEP, dialedVIF) &&
+		principal != nil &&
+		reflect.DeepEqual(principal.PublicKey(), vif.principal.PublicKey())
+	switch {
+	case usePreauth:
+		preauth := vif.authResult
+		params := security.CallParams{
+			LocalPrincipal:   principal,
+			LocalEndpoint:    vif.localEP,
+			RemoteEndpoint:   preauth.RemoteEndpoint,
+			LocalBlessings:   preauth.LocalBlessings,
+			RemoteBlessings:  preauth.RemoteBlessings,
+			RemoteDischarges: preauth.RemoteDischarges,
+		}
+		sendSetupVC := func(pubKey *crypto.BoxKey, sigPreauth []byte) error {
+			err := vif.sendOnExpressQ(&message.SetupVC{
+				VCI:            vc.VCI(),
+				RemoteEndpoint: remoteEP,
+				LocalEndpoint:  vif.localEP,
+				Counters:       counters,
+				Setup: message.Setup{
+					Versions: iversion.Range{Min: preauth.Version, Max: preauth.Version},
+					Options: []message.SetupOption{
+						&message.NaclBox{PublicKey: *pubKey},
+						&message.UseVIFAuthentication{Signature: sigPreauth},
+					},
+				},
+			})
+			if err != nil {
+				return verror.New(stream.ErrNetwork, nil, verror.New(errSendOnExpressQFailed, nil, err))
+			}
+			return nil
+		}
+		err = vc.HandshakeDialedVCPreAuthenticated(preauth.Version, params, &preauth.SessionKeys.RemotePublic, sendSetupVC, opts...)
+	case principal == nil:
+		sendSetupVC := func() error {
+			err := vif.sendOnExpressQ(&message.SetupVC{
+				VCI:            vc.VCI(),
+				RemoteEndpoint: remoteEP,
+				LocalEndpoint:  vif.localEP,
+				Counters:       counters,
+				Setup:          message.Setup{Versions: *vif.versions},
+			})
+			if err != nil {
+				return verror.New(stream.ErrNetwork, nil, verror.New(errSendOnExpressQFailed, nil, err))
+			}
+			return nil
+		}
+		err = vc.HandshakeDialedVCNoAuthentication(sendSetupVC, opts...)
+	default:
+		sendSetupVC := func(pubKey *crypto.BoxKey) error {
+			err := vif.sendOnExpressQ(&message.SetupVC{
+				VCI:            vc.VCI(),
+				RemoteEndpoint: remoteEP,
+				LocalEndpoint:  vif.localEP,
+				Counters:       counters,
+				Setup: message.Setup{
+					Versions: *vif.versions,
+					Options:  []message.SetupOption{&message.NaclBox{PublicKey: *pubKey}},
+				},
+			})
+			if err != nil {
+				return verror.New(stream.ErrNetwork, nil, verror.New(errSendOnExpressQFailed, nil, err))
+			}
+			return nil
+		}
+		err = vc.HandshakeDialedVCWithAuthentication(principal, sendSetupVC, opts...)
+	}
+	if err != nil {
+		vif.deleteVC(vc.VCI())
+		vc.Close(err)
+		return nil, err
+	}
+
+	vif.muStats.Lock()
+	vif.stats.NumDialedVCs++
+	if usePreauth {
+		vif.stats.NumPreAuthenticated++
+	}
+	vif.muStats.Unlock()
+	return vc, nil
+}
+
+func (vif *VIF) acceptVC(ctx *context.T, m *message.SetupVC) error {
+	vrange, err := vif.versions.Intersect(&m.Setup.Versions)
+	if err != nil {
+		ctx.VI(2).Infof("SetupVC message %+v to VIF %s did not present compatible versions: %v", m, vif, err)
+		return err
+	}
+	vif.muListen.Lock()
+	closed := vif.acceptor == nil || vif.acceptor.IsClosed()
+	lopts := vif.listenerOpts
+	vif.muListen.Unlock()
+	if closed {
+		ctx.VI(2).Infof("Ignoring SetupVC message %+v as VIF %s does not accept VCs", m, vif)
+		return errors.New("VCs not accepted")
+	}
+	var idleTimeout time.Duration
+	for _, o := range lopts {
+		switch v := o.(type) {
+		case vc.IdleTimeout:
+			idleTimeout = v.Duration
+		}
+	}
+	vcobj, err := vif.newVC(ctx, m.VCI, m.RemoteEndpoint, m.LocalEndpoint, idleTimeout, false)
+	if err != nil {
+		return err
+	}
+	vif.distributeCounters(m.Counters)
+
+	var remotePK *crypto.BoxKey
+	if box := m.Setup.NaclBox(); box != nil {
+		remotePK = &box.PublicKey
+	}
+	sigPreauth := m.Setup.UseVIFAuthentication()
+	var hrCH <-chan vc.HandshakeResult
+	switch {
+	case len(sigPreauth) > 0:
+		if !vif.useVIFAuthForVC(vrange.Max, m.RemoteEndpoint, m.LocalEndpoint, acceptedVIF) {
+			ctx.VI(2).Infof("Ignoring SetupVC message %+v as VIF %s does not allow re-using VIF authentication for this VC", m, vif)
+			return errors.New("VCs not accepted: cannot re-use VIF authentication for this VC")
+		}
+		preauth := vif.authResult
+		params := security.CallParams{
+			LocalPrincipal:  vif.principal,
+			LocalBlessings:  vif.blessings,
+			RemoteBlessings: preauth.RemoteBlessings,
+			LocalDischarges: preauth.LocalDischarges,
+		}
+		hrCH = vcobj.HandshakeAcceptedVCPreAuthenticated(preauth.Version, params, sigPreauth, &preauth.SessionKeys.LocalPublic, &preauth.SessionKeys.LocalPrivate, remotePK, lopts...)
+	case vif.principal == nil:
+		sendSetupVC := func() error {
+			err = vif.sendOnExpressQ(&message.SetupVC{
+				VCI:            m.VCI,
+				Setup:          message.Setup{Versions: *vrange},
+				RemoteEndpoint: m.LocalEndpoint,
+				LocalEndpoint:  vif.localEP,
+			})
+			return err
+		}
+		hrCH = vcobj.HandshakeAcceptedVCNoAuthentication(vrange.Max, sendSetupVC, lopts...)
+	default:
+		exchanger := func(pubKey *crypto.BoxKey) (*crypto.BoxKey, error) {
+			err = vif.sendOnExpressQ(&message.SetupVC{
+				VCI: m.VCI,
+				Setup: message.Setup{
+					// Note that servers send clients not their actual supported versions,
+					// but the intersected range of the server and client ranges. This
+					// is important because proxies may have adjusted the version ranges
+					// along the way, and we should negotiate a version that is compatible
+					// with all intermediate hops.
+					Versions: *vrange,
+					Options:  []message.SetupOption{&message.NaclBox{PublicKey: *pubKey}},
+				},
+				RemoteEndpoint: m.LocalEndpoint,
+				LocalEndpoint:  vif.localEP,
+				// TODO(mattr): Consider adding counters. See associated comment in
+				// vc.initHandshakeAcceptedVC for more details. Note that we need to send
+				// AddReceiveBuffers message when reusing VIF authentication since servers
+				// doesn't send a reply for SetupVC.
+			})
+			return remotePK, err
+		}
+		hrCH = vcobj.HandshakeAcceptedVCWithAuthentication(vrange.Max, vif.principal, vif.blessings, exchanger, lopts...)
+	}
+	go vif.acceptFlowsLoop(vcobj, hrCH)
+
+	vif.muStats.Lock()
+	vif.stats.NumAcceptedVCs++
+	if len(sigPreauth) > 0 {
+		vif.stats.NumPreAuthenticated++
+	}
+	vif.muStats.Unlock()
+	return nil
+}
+
+func (vif *VIF) useVIFAuthForVC(ver version.RPCVersion, localEP, remoteEP naming.Endpoint, side vifSide) bool {
+	dialed := side == dialedVIF
+	if vif.authResult == nil || vif.authResult.Dialed != dialed || vif.authResult.Version != ver {
+		return false
+	}
+	// We allow to use the VIF authentication when the routing ID is null, since it
+	// means that this VIF is connected to the peer directly with a hostname and port.
+	if dialed {
+		return naming.Compare(vif.authResult.RemoteEndpoint.RoutingID(), remoteEP.RoutingID()) ||
+			naming.Compare(remoteEP.RoutingID(), naming.NullRoutingID)
+	}
+	return naming.Compare(vif.authResult.RemoteEndpoint.RoutingID(), remoteEP.RoutingID()) &&
+		(naming.Compare(vif.localEP.RoutingID(), localEP.RoutingID()) || naming.Compare(localEP.RoutingID(), naming.NullRoutingID))
+}
+
+// Close closes all VCs (and thereby Flows) over the VIF and then closes the
+// underlying network connection after draining all pending writes on those
+// VCs.
+func (vif *VIF) Close() {
+	vif.isClosedMu.Lock()
+	if vif.isClosed {
+		vif.isClosedMu.Unlock()
+		return
+	}
+	vif.isClosed = true
+	vif.isClosedMu.Unlock()
+
+	vif.ctx.VI(1).Infof("Closing VIF %s", vif)
+	// Stop accepting new VCs.
+	vif.StopAccepting()
+	// Close local datastructures for all existing VCs.
+	vcs := vif.vcMap.Freeze()
+	// Stop the idle timers.
+	vif.idleTimerMap.Stop()
+	for _, vc := range vcs {
+		vc.VC.Close(verror.New(stream.ErrNetwork, nil, verror.New(errVIFIsBeingClosed, nil)))
+	}
+	// Wait for the vcWriteLoops to exit (after draining queued up messages).
+	vif.stopQ.Close()
+	vif.wpending.Wait()
+	// Close the underlying network connection.
+	// No need to send individual messages to close all pending VCs since
+	// the remote end should know to close all VCs when the VIF's
+	// connection breaks.
+	if err := vif.conn.Close(); err != nil {
+		vif.ctx.VI(1).Infof("net.Conn.Close failed on VIF %s: %v", vif, err)
+	}
+
+	// Notify that the VIF has been closed.
+	if vif.onClose != nil {
+		go vif.onClose(vif)
+	}
+
+	vif.loopWG.Wait()
+}
+
+// StartAccepting begins accepting Flows (and VCs) initiated by the remote end
+// of a VIF. opts is used to setup the listener on newly established VCs.
+func (vif *VIF) StartAccepting(opts ...stream.ListenerOpt) error {
+	vif.muListen.Lock()
+	defer vif.muListen.Unlock()
+	if vif.acceptor != nil {
+		return verror.New(stream.ErrNetwork, nil, verror.New(errVIFIsBeingClosed, nil, vif))
+	}
+	vif.acceptor = upcqueue.New()
+	vif.listenerOpts = opts
+	return nil
+}
+
+// StopAccepting prevents any Flows initiated by the remote end of a VIF from
+// being accepted and causes any existing and future calls to Accept to fail
+// immediately.
+func (vif *VIF) StopAccepting() {
+	vif.muListen.Lock()
+	defer vif.muListen.Unlock()
+	if vif.acceptor != nil {
+		vif.acceptor.Shutdown()
+		vif.acceptor = nil
+		vif.listenerOpts = nil
+	}
+}
+
+// Accept returns the (stream.Connector, stream.Flow) pair of a newly
+// established VC and/or Flow.
+//
+// Sample usage:
+//	for {
+//		cAndf, err := vif.Accept()
+//		switch {
+//		case err != nil:
+//			fmt.Println("Accept error:", err)
+//			return
+//		case cAndf.Flow == nil:
+//			fmt.Println("New VC established:", cAndf.Connector)
+//		default:
+//			fmt.Println("New flow established")
+//			go handleFlow(cAndf.Flow)
+//		}
+//	}
+func (vif *VIF) Accept() (ConnectorAndFlow, error) {
+	vif.muListen.Lock()
+	acceptor := vif.acceptor
+	vif.muListen.Unlock()
+	if acceptor == nil {
+		return ConnectorAndFlow{}, verror.New(stream.ErrNetwork, nil, verror.New(errVCsNotAcceptedOnVIF, nil, vif))
+	}
+	item, err := acceptor.Get(nil)
+	if err != nil {
+		return ConnectorAndFlow{}, verror.New(stream.ErrNetwork, nil, verror.New(errAcceptFailed, nil, err))
+	}
+	return item.(ConnectorAndFlow), nil
+}
+
+func (vif *VIF) String() string {
+	l := vif.conn.LocalAddr()
+	r := vif.conn.RemoteAddr()
+	return fmt.Sprintf("(%s, %s) <-> (%s, %s)", l.Network(), l, r.Network(), r)
+}
+
+func (vif *VIF) readLoop() {
+	defer vif.Close()
+	defer vif.loopWG.Done()
+	defer vif.stopVCDispatchLoops()
+	for {
+		// vif.ctrlCipher is guarded by vif.writeMu.  However, the only mutation
+		// to it is in handleMessage, which runs in the same goroutine, so a
+		// lock is not required here.
+		msg, err := message.ReadFrom(vif.reader, vif.ctrlCipher)
+		if err != nil {
+			vif.ctx.VI(1).Infof("Exiting readLoop of VIF %s because of read error: %v", vif, err)
+			return
+		}
+		vif.ctx.VI(3).Infof("Received %T = [%v] on VIF %s", msg, msg, vif)
+		if err := vif.handleMessage(msg); err != nil {
+			vif.ctx.VI(1).Infof("Exiting readLoop of VIF %s because of message error: %v", vif, err)
+			return
+		}
+	}
+}
+
+// handleMessage handles a single incoming message.  Any error returned is
+// fatal, causing the VIF to close.
+func (vif *VIF) handleMessage(msg message.T) error {
+	mtype := reflect.TypeOf(msg)
+	vif.muStats.Lock()
+	vif.stats.RecvMsgCounter[mtype]++
+	vif.muStats.Unlock()
+
+	switch m := msg.(type) {
+
+	case *message.Data:
+		_, rq, _ := vif.vcMap.Find(m.VCI)
+		if rq == nil {
+			vif.ctx.VI(2).Infof("Ignoring message of %d bytes for unrecognized VCI %d on VIF %s", m.Payload.Size(), m.VCI, vif)
+			m.Release()
+			return nil
+		}
+		if err := rq.Put(m, nil); err != nil {
+			vif.ctx.VI(2).Infof("Failed to put message(%v) on VC queue on VIF %v: %v", m, vif, err)
+			m.Release()
+		}
+
+	case *message.AddReceiveBuffers:
+		vif.distributeCounters(m.Counters)
+
+	case *message.OpenFlow:
+		if vc, _, _ := vif.vcMap.Find(m.VCI); vc != nil {
+			if err := vc.AcceptFlow(m.Flow); err != nil {
+				vif.ctx.VI(3).Infof("OpenFlow %+v on VIF %v failed:%v", m, vif, err)
+				cm := &message.Data{VCI: m.VCI, Flow: m.Flow}
+				cm.SetClose()
+				vif.sendOnExpressQ(cm)
+				return nil
+			}
+			vc.ReleaseCounters(m.Flow, m.InitialCounters)
+			return nil
+		}
+		vif.ctx.VI(2).Infof("Ignoring OpenFlow(%+v) for unrecognized VCI on VIF %s", m, m, vif)
+
+	case *message.SetupVC:
+		// If we dialed this VC, then this is a response and we should finish
+		// the vc handshake. Otherwise, this message is opening a new VC.
+		if vif.dialedVCI(m.VCI) {
+			vif.distributeCounters(m.Counters)
+			vc, _, _ := vif.vcMap.Find(m.VCI)
+			if vc == nil {
+				vif.ctx.VI(2).Infof("Ignoring SetupVC message %+v for unknown dialed VC", m)
+				return nil
+			}
+			vrange, err := vif.versions.Intersect(&m.Setup.Versions)
+			if err != nil {
+				vif.closeVCAndSendMsg(vc, false, err)
+				return nil
+			}
+			var remotePK *crypto.BoxKey
+			if box := m.Setup.NaclBox(); box != nil {
+				remotePK = &box.PublicKey
+			}
+			if err := vc.FinishHandshakeDialedVC(vrange.Max, remotePK); err != nil {
+				vif.closeVCAndSendMsg(vc, false, err)
+			}
+			return nil
+		}
+		// This is an accepted VC.
+		if err := vif.acceptVC(vif.ctx, m); err != nil {
+			vif.sendOnExpressQ(&message.CloseVC{VCI: m.VCI, Error: err.Error()})
+		}
+		return nil
+
+	case *message.CloseVC:
+		if vc, _, _ := vif.vcMap.Find(m.VCI); vc != nil {
+			vif.deleteVC(vc.VCI())
+			vif.ctx.VI(2).Infof("CloseVC(%+v) on VIF %s", m, vif)
+			// TODO(cnicolaou): it would be nice to have a method on VC
+			// to indicate a 'remote close' rather than a 'local one'. This helps
+			// with error reporting since we expect reads/writes to occur
+			// after a remote close, but not after a local close.
+			vc.Close(verror.New(stream.ErrNetwork, nil, verror.New(errRemoteEndClosedVC, nil, m.Error)))
+			return nil
+		}
+		vif.ctx.VI(2).Infof("Ignoring CloseVC(%+v) for unrecognized VCI on VIF %s", m, vif)
+
+	case *message.Setup:
+		vif.ctx.Infof("Ignoring redundant Setup message %T on VIF %s", m, vif)
+
+	default:
+		vif.ctx.Infof("Ignoring unrecognized message %T on VIF %s", m, vif)
+	}
+	return nil
+}
+
+func (vif *VIF) vcDispatchLoop(ctx *context.T, vc *vc.VC, messages *pcqueue.T) {
+	defer ctx.VI(2).Infof("Exiting vcDispatchLoop(%v) on VIF %v", vc, vif)
+	defer vif.rpending.Done()
+	for {
+		qm, err := messages.Get(nil)
+		if err != nil {
+			return
+		}
+		m := qm.(*message.Data)
+		if err := vc.DispatchPayload(m.Flow, m.Payload); err != nil {
+			ctx.VI(2).Infof("Ignoring data message %v for on VIF %s: %v", m, vif, err)
+		}
+		if m.Close() {
+			vif.shutdownFlow(vc, m.Flow)
+		}
+	}
+}
+
+func (vif *VIF) stopVCDispatchLoops() {
+	vcs := vif.vcMap.Freeze()
+	for _, v := range vcs {
+		v.RQ.Close()
+	}
+	vif.rpending.Wait()
+}
+
+func clientVCClosed(err error) bool {
+	// If we've encountered a networking error, then all likelihood the
+	// connection to the client is closed.
+	return verror.ErrorID(err) == stream.ErrNetwork.ID
+}
+
+func (vif *VIF) acceptFlowsLoop(vc *vc.VC, c <-chan vc.HandshakeResult) {
+	hr := <-c
+	if hr.Error != nil {
+		vif.closeVCAndSendMsg(vc, clientVCClosed(hr.Error), hr.Error)
+		return
+	}
+
+	vif.muListen.Lock()
+	acceptor := vif.acceptor
+	vif.muListen.Unlock()
+	if acceptor == nil {
+		vif.closeVCAndSendMsg(vc, false, verror.New(errFlowsNoLongerAccepted, nil))
+		return
+	}
+
+	// Notify any listeners that a new VC has been established
+	if err := acceptor.Put(ConnectorAndFlow{vc, nil}); err != nil {
+		vif.closeVCAndSendMsg(vc, clientVCClosed(err), verror.New(errVCAcceptFailed, nil, err))
+		return
+	}
+
+	vif.ctx.VI(2).Infof("Running acceptFlowsLoop for VC %v on VIF %v", vc, vif)
+	for {
+		f, err := hr.Listener.Accept()
+		if err != nil {
+			vif.ctx.VI(2).Infof("Accept failed on VC %v on VIF %v: %v", vc, vif, err)
+			return
+		}
+		if err := acceptor.Put(ConnectorAndFlow{vc, f}); err != nil {
+			vif.ctx.VI(2).Infof("vif.acceptor.Put(%v, %T) on VIF %v failed: %v", vc, f, vif, err)
+			f.Close()
+			return
+		}
+	}
+}
+
+func (vif *VIF) distributeCounters(counters message.Counters) {
+	for cid, bytes := range counters {
+		vc, _, _ := vif.vcMap.Find(cid.VCI())
+		if vc == nil {
+			vif.ctx.VI(2).Infof("Ignoring counters for non-existent VCI %d on VIF %s", cid.VCI(), vif)
+			continue
+		}
+		vc.ReleaseCounters(cid.Flow(), bytes)
+	}
+}
+
+func (vif *VIF) writeLoop() {
+	defer vif.loopWG.Done()
+	defer vif.outgoing.Close()
+	defer vif.stopVCWriteLoops()
+	for {
+		writer, bufs, err := vif.outgoing.Get(nil)
+		if err != nil {
+			vif.ctx.VI(1).Infof("Exiting writeLoop of VIF %s because of bqueue.Get error: %v", vif, err)
+			return
+		}
+		wtype := reflect.TypeOf(writer)
+		vif.muStats.Lock()
+		vif.stats.SendMsgCounter[wtype]++
+		vif.muStats.Unlock()
+		switch writer {
+		case vif.expressQ:
+			for _, b := range bufs {
+				if err := vif.writeSerializedMessage(b.Contents); err != nil {
+					vif.ctx.VI(1).Infof("Exiting writeLoop of VIF %s because Control message write failed: %s", vif, err)
+					releaseBufs(bufs)
+					return
+				}
+				b.Release()
+			}
+		case vif.flowQ:
+			msg := &message.AddReceiveBuffers{}
+			// No need to call releaseBufs(bufs) as all bufs are
+			// the exact same value: flowToken.
+			vif.flowMu.Lock()
+			if len(vif.flowCounters) > 0 {
+				msg.Counters = vif.flowCounters
+				vif.flowCounters = message.NewCounters()
+			}
+			vif.flowMu.Unlock()
+			if len(msg.Counters) > 0 {
+				vif.ctx.VI(3).Infof("Sending counters %v on VIF %s", msg.Counters, vif)
+				if err := vif.writeMessage(msg); err != nil {
+					vif.ctx.VI(1).Infof("Exiting writeLoop of VIF %s because AddReceiveBuffers message write failed: %v", vif, err)
+					return
+				}
+			}
+		case vif.stopQ:
+			// Lowest-priority queue which will never have any
+			// buffers, Close is the only method called on it.
+			return
+		default:
+			vif.writeDataMessages(writer, bufs)
+		}
+	}
+}
+
+func (vif *VIF) vcWriteLoop(ctx *context.T, vc *vc.VC, messages *pcqueue.T) {
+	defer ctx.VI(2).Infof("Exiting vcWriteLoop(%v) on VIF %v", vc, vif)
+	defer vif.wpending.Done()
+	for {
+		qm, err := messages.Get(nil)
+		if err != nil {
+			return
+		}
+		m := qm.(*message.Data)
+		m.Payload, err = vc.Encrypt(m.Flow, m.Payload)
+		if err != nil {
+			ctx.Infof("Encryption failed. Flow:%v VC:%v Error:%v", m.Flow, vc, err)
+		}
+		if m.Close() {
+			// The last bytes written on the flow will be sent out
+			// on vif.conn. Local datastructures for the flow can
+			// be cleaned up now.
+			vif.shutdownFlow(vc, m.Flow)
+		}
+		if err == nil {
+			err = vif.writeMessage(m)
+		}
+		if err != nil {
+			// TODO(caprita): Calling closeVCAndSendMsg below causes
+			// a race as described in:
+			// https://docs.google.com/a/google.com/document/d/1C0kxfYhuOcStdV7tnLZELZpUhfQCZj47B0JrzbE29h8/edit
+			//
+			// There should be a finer grained way to fix this, and
+			// there are likely other instances where we should not
+			// be closing the VC.
+			//
+			// For now, commenting out the line below removes the
+			// flakiness from our existing unit tests, but this
+			// needs to be revisited and fixed correctly.
+			//
+			//   vif.closeVCAndSendMsg(vc, fmt.Sprintf("write failure: %v", err))
+
+			// Drain the queue and exit.
+			for {
+				qm, err := messages.Get(nil)
+				if err != nil {
+					return
+				}
+				qm.(*message.Data).Release()
+			}
+		}
+	}
+}
+
+func (vif *VIF) stopVCWriteLoops() {
+	vcs := vif.vcMap.Freeze()
+	vif.idleTimerMap.Stop()
+	for _, v := range vcs {
+		v.WQ.Close()
+	}
+}
+
+// sendOnExpressQ adds 'msg' to the expressQ (highest priority queue) of messages to write on the wire.
+func (vif *VIF) sendOnExpressQ(msg message.T) error {
+	vif.ctx.VI(2).Infof("sendOnExpressQ(%T = %+v) on VIF %s", msg, msg, vif)
+	var buf bytes.Buffer
+	// Don't encrypt yet, because the message ordering isn't yet determined.
+	// Encryption is performed by vif.writeSerializedMessage() when the
+	// message is actually written to vif.conn.
+	vif.writeMu.Lock()
+	c := vif.ctrlCipher
+	vif.writeMu.Unlock()
+	if err := message.WriteTo(&buf, msg, crypto.NewDisabledControlCipher(c)); err != nil {
+		return err
+	}
+	return vif.expressQ.Put(iobuf.NewSlice(buf.Bytes()), nil)
+}
+
+// writeMessage writes the message to the channel.  Writes must be serialized so
+// that the control channel can be encrypted, so we acquire the writeMu.
+func (vif *VIF) writeMessage(msg message.T) error {
+	vif.writeMu.Lock()
+	defer vif.writeMu.Unlock()
+	return message.WriteTo(vif.conn, msg, vif.ctrlCipher)
+}
+
+// Write writes the message to the channel, encrypting the control data.  Writes
+// must be serialized so that the control channel can be encrypted, so we
+// acquire the writeMu.
+func (vif *VIF) writeSerializedMessage(msg []byte) error {
+	vif.writeMu.Lock()
+	defer vif.writeMu.Unlock()
+	if err := message.EncryptMessage(msg, vif.ctrlCipher); err != nil {
+		return err
+	}
+	if n, err := vif.conn.Write(msg); err != nil {
+		return verror.New(stream.ErrNetwork, nil, verror.New(errWriteFailed, nil, n, err, len(msg)))
+	}
+	return nil
+}
+
+func (vif *VIF) writeDataMessages(writer bqueue.Writer, bufs []*iobuf.Slice) {
+	vci, fid := unpackIDs(writer.ID())
+	// iobuf.Coalesce will coalesce buffers only if they are adjacent to
+	// each other.  In the worst case, each buf will be non-adjacent to the
+	// others and the code below will end up with multiple small writes
+	// instead of a single big one.
+	// Might want to investigate this and see if this needs to be
+	// revisited.
+	bufs = iobuf.Coalesce(bufs, uint(vc.MaxPayloadSizeBytes))
+	_, _, wq := vif.vcMap.Find(vci)
+	if wq == nil {
+		// VC has been removed, stop sending messages
+		vif.ctx.VI(2).Infof("VCI %d on VIF %s was shutdown, dropping %d messages that were pending a write", vci, vif, len(bufs))
+		releaseBufs(bufs)
+		return
+	}
+	last := len(bufs) - 1
+	drained := writer.IsDrained()
+	for i, b := range bufs {
+		d := &message.Data{VCI: vci, Flow: fid, Payload: b}
+		if drained && i == last {
+			d.SetClose()
+		}
+		if err := wq.Put(d, nil); err != nil {
+			releaseBufs(bufs[i:])
+			return
+		}
+	}
+	if len(bufs) == 0 && drained {
+		d := &message.Data{VCI: vci, Flow: fid}
+		d.SetClose()
+		if err := wq.Put(d, nil); err != nil {
+			d.Release()
+		}
+	}
+}
+
+func (vif *VIF) dialedVCI(VCI id.VC) bool {
+	vif.muNextVCI.Lock()
+	dialed := vif.nextVCI%2 == VCI%2
+	vif.muNextVCI.Unlock()
+	return dialed
+}
+
+func (vif *VIF) allocVCI() id.VC {
+	vif.muNextVCI.Lock()
+	ret := vif.nextVCI
+	vif.nextVCI += 2
+	vif.muNextVCI.Unlock()
+	return ret
+}
+
+func (vif *VIF) newVC(ctx *context.T, vci id.VC, localEP, remoteEP naming.Endpoint, idleTimeout time.Duration, side vifSide) (*vc.VC, error) {
+	vif.muStartTimer.Lock()
+	if vif.startTimer != nil {
+		vif.startTimer.Stop()
+		vif.startTimer = nil
+	}
+	vif.muStartTimer.Unlock()
+	vc := vc.InternalNew(ctx, vc.Params{
+		VCI:          vci,
+		Dialed:       side == dialedVIF,
+		LocalEP:      localEP,
+		RemoteEP:     remoteEP,
+		Pool:         vif.pool,
+		ReserveBytes: uint(message.HeaderSizeBytes + vif.ctrlCipher.MACSize()),
+		Helper:       vcHelper{vif},
+	})
+	added, rq, wq := vif.vcMap.Insert(vc)
+	if added {
+		vif.idleTimerMap.Insert(vc.VCI(), idleTimeout)
+	}
+	// Start vcWriteLoop
+	if added = added && vif.wpending.TryAdd(); added {
+		go vif.vcWriteLoop(ctx, vc, wq)
+	}
+	// Start vcDispatchLoop
+	if added = added && vif.rpending.TryAdd(); added {
+		go vif.vcDispatchLoop(ctx, vc, rq)
+	}
+	if !added {
+		if rq != nil {
+			rq.Close()
+		}
+		if wq != nil {
+			wq.Close()
+		}
+		vc.Close(verror.New(stream.ErrAborted, nil, verror.New(errShuttingDown, nil, vif)))
+		vif.deleteVC(vci)
+		return nil, verror.New(stream.ErrAborted, nil, verror.New(errShuttingDown, nil, vif))
+	}
+	return vc, nil
+}
+
+func (vif *VIF) deleteVC(vci id.VC) {
+	vif.idleTimerMap.Delete(vci)
+	if vif.vcMap.Delete(vci) {
+		vif.Close()
+	}
+}
+
+func (vif *VIF) closeVCAndSendMsg(vc *vc.VC, clientVCClosed bool, errMsg error) {
+	vif.ctx.VI(2).Infof("Shutting down VCI %d on VIF %v due to: %v", vc.VCI(), vif, errMsg)
+	vif.deleteVC(vc.VCI())
+	vc.Close(errMsg)
+	if clientVCClosed {
+		// No point in sending to the client if the VC is closed, or otherwise broken.
+		return
+	}
+	msg := ""
+	if errMsg != nil {
+		msg = errMsg.Error()
+	}
+	if err := vif.sendOnExpressQ(&message.CloseVC{
+		VCI:   vc.VCI(),
+		Error: msg,
+	}); err != nil {
+		vif.ctx.VI(2).Infof("sendOnExpressQ(CloseVC{VCI:%d,...}) on VIF %v failed: %v", vc.VCI(), vif, err)
+	}
+}
+
+// shutdownFlow clears out all the datastructures associated with fid.
+func (vif *VIF) shutdownFlow(vc *vc.VC, fid id.Flow) {
+	vc.ShutdownFlow(fid)
+	vif.flowMu.Lock()
+	delete(vif.flowCounters, message.MakeCounterID(vc.VCI(), fid))
+	vif.flowMu.Unlock()
+	vif.idleTimerMap.DeleteFlow(vc.VCI(), fid)
+}
+
+// ShutdownVCs closes all VCs established to the provided remote endpoint.
+// Returns the number of VCs that were closed.
+func (vif *VIF) ShutdownVCs(remote naming.Endpoint) int {
+	vcs := vif.vcMap.List()
+	n := 0
+	for _, vc := range vcs {
+		if naming.Compare(vc.RemoteEndpoint().RoutingID(), remote.RoutingID()) {
+			vif.ctx.VI(1).Infof("VCI %d on VIF %s being closed because of ShutdownVCs call", vc.VCI(), vif)
+			vif.closeVCAndSendMsg(vc, false, nil)
+			n++
+		}
+	}
+	return n
+}
+
+// NumVCs returns the number of VCs established over this VIF.
+func (vif *VIF) NumVCs() int { return vif.vcMap.Size() }
+
+// Stats returns the current stats of this VIF.
+func (vif *VIF) Stats() Stats {
+	stats := Stats{SendMsgCounter: make(map[reflect.Type]uint64), RecvMsgCounter: make(map[reflect.Type]uint64)}
+	vif.muStats.Lock()
+	for k, v := range vif.stats.SendMsgCounter {
+		stats.SendMsgCounter[k] = v
+	}
+	for k, v := range vif.stats.RecvMsgCounter {
+		stats.RecvMsgCounter[k] = v
+	}
+	stats.NumDialedVCs = vif.stats.NumDialedVCs
+	stats.NumAcceptedVCs = vif.stats.NumAcceptedVCs
+	stats.NumPreAuthenticated = vif.stats.NumPreAuthenticated
+	vif.muStats.Unlock()
+	return stats
+}
+
+// DebugString returns a descriptive state of the VIF.
+//
+// The returned string is meant for consumptions by humans. The specific format
+// should not be relied upon by any automated processing.
+func (vif *VIF) DebugString() string {
+	vif.muNextVCI.Lock()
+	nextVCI := vif.nextVCI
+	vif.muNextVCI.Unlock()
+	vif.isClosedMu.Lock()
+	isClosed := vif.isClosed
+	vif.isClosedMu.Unlock()
+	vcs := vif.vcMap.List()
+	stats := vif.Stats()
+
+	l := make([]string, 0, 2+len(vcs)+len(stats.SendMsgCounter)+len(stats.RecvMsgCounter))
+	l = append(l, fmt.Sprintf("VIF:[%s] -- #VCs:%d NextVCI:%d ControlChannelEncryption:%t IsClosed:%t #Dialed:%d #Accepted:%d #PreAuthenticated:%d",
+		vif, len(vcs), nextVCI, vif.ctrlCipher != nullCipher, isClosed, stats.NumDialedVCs, stats.NumAcceptedVCs, stats.NumPreAuthenticated))
+
+	l = append(l, "Message Counters:")
+	msgStats := make([]string, 0, len(stats.SendMsgCounter)+len(stats.RecvMsgCounter))
+	for k, v := range stats.SendMsgCounter {
+		msgStats = append(msgStats, fmt.Sprintf(" %-32s %10d", "Send("+k.String()+")", v))
+	}
+	for k, v := range stats.RecvMsgCounter {
+		msgStats = append(msgStats, fmt.Sprintf(" %-32s %10d", "Recv("+k.String()+")", v))
+	}
+	sort.Strings(msgStats)
+	l = append(l, msgStats...)
+
+	for _, vc := range vcs {
+		l = append(l, vc.DebugString())
+	}
+	return strings.Join(l, "\n")
+}
+
+// Methods and type that implement vc.Helper
+//
+// We create a separate type for vc.Helper to hide the vc.Helper methods
+// from the exported method set of VIF.
+type vcHelper struct{ vif *VIF }
+
+func (h vcHelper) NotifyOfNewFlow(vci id.VC, fid id.Flow, bytes uint) {
+	h.vif.sendOnExpressQ(&message.OpenFlow{VCI: vci, Flow: fid, InitialCounters: uint32(bytes)})
+}
+
+func (h vcHelper) AddReceiveBuffers(vci id.VC, fid id.Flow, bytes uint) {
+	if bytes == 0 {
+		return
+	}
+	h.vif.flowMu.Lock()
+	h.vif.flowCounters.Add(vci, fid, uint32(bytes))
+	h.vif.flowMu.Unlock()
+	h.vif.flowQ.TryPut(flowToken)
+}
+
+func (h vcHelper) NewWriter(vci id.VC, fid id.Flow, priority bqueue.Priority) (bqueue.Writer, error) {
+	h.vif.idleTimerMap.InsertFlow(vci, fid)
+	return h.vif.outgoing.NewWriter(packIDs(vci, fid), flowPriority+priority, defaultBytesBufferedPerFlow)
+}
+
+// The token added to vif.flowQ.
+var flowToken *iobuf.Slice
+
+func init() {
+	// flowToken must be non-empty otherwise bqueue.Writer.Put will ignore it.
+	flowToken = iobuf.NewSlice(make([]byte, 1))
+}
+
+func packIDs(vci id.VC, fid id.Flow) bqueue.ID {
+	return bqueue.ID(message.MakeCounterID(vci, fid))
+}
+
+func unpackIDs(b bqueue.ID) (id.VC, id.Flow) {
+	cid := message.CounterID(b)
+	return cid.VCI(), cid.Flow()
+}
+
+func releaseBufs(bufs []*iobuf.Slice) {
+	for _, b := range bufs {
+		b.Release()
+	}
+}
+
+// localEndpoint creates a naming.Endpoint from the provided parameters.
+//
+// It intentionally does not include any blessings (present in endpoints in the
+// v5 format). At this point it is not clear whether the endpoint is being
+// created for a "client" or a "server". If the endpoint is used for clients
+// (i.e., for those sending an OpenVC message for example), then we do NOT want
+// to include the blessings in the endpoint to ensure client privacy.
+//
+// Servers should be happy to let anyone with access to their endpoint string
+// know their blessings, because they are willing to share those with anyone
+// that connects to them.
+//
+// The addition of the endpoints is left as an excercise to higher layers of
+// the stack, where the desire to share or hide blessings from the endpoint is
+// clearer.
+func localEndpoint(conn net.Conn, rid naming.RoutingID, versions *iversion.Range) naming.Endpoint {
+	localAddr := conn.LocalAddr()
+	ep := &inaming.Endpoint{
+		Protocol: localAddr.Network(),
+		Address:  localAddr.String(),
+		RID:      rid,
+	}
+	return ep
+}
diff --git a/runtime/internal/rpc/stream/vif/vif_test.go b/runtime/internal/rpc/stream/vif/vif_test.go
new file mode 100644
index 0000000..a67b396
--- /dev/null
+++ b/runtime/internal/rpc/stream/vif/vif_test.go
@@ -0,0 +1,1064 @@
+// 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.
+
+// Tests in a separate package to ensure that only the exported API is used in the tests.
+//
+// All tests are run with the default security level on VCs (SecurityConfidential).
+
+package vif_test
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"net"
+	"reflect"
+	"runtime"
+	"sort"
+	"sync"
+	"testing"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/rpc/version"
+
+	inaming "v.io/x/ref/runtime/internal/naming"
+	"v.io/x/ref/runtime/internal/rpc/stream"
+	"v.io/x/ref/runtime/internal/rpc/stream/vc"
+	"v.io/x/ref/runtime/internal/rpc/stream/vif"
+	iversion "v.io/x/ref/runtime/internal/rpc/version"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/testutil"
+)
+
+//go:generate v23 test generate
+
+func TestSingleFlowCreatedAtClient(t *testing.T) {
+	ctx, shutdown := test.V23InitAnon()
+	defer shutdown()
+	pclient := testutil.NewPrincipal("client")
+	pserver := testutil.NewPrincipal("server")
+	cctx, _ := v23.WithPrincipal(ctx, pclient)
+	sctx, _ := v23.WithPrincipal(ctx, pserver)
+	client, server := NewClientServer(cctx, sctx)
+	defer client.Close()
+
+	clientVC, _, err := createVC(cctx, client, server, makeEP(0x5))
+	if err != nil {
+		t.Fatal(err)
+	}
+	writer, err := clientVC.Connect()
+	if err != nil {
+		t.Fatal(err)
+	}
+	// Test with an empty message to ensure that we correctly
+	// handle closing empty flows.
+	rwSingleFlow(t, writer, acceptFlowAtServer(server), "")
+	writer, err = clientVC.Connect()
+	if err != nil {
+		t.Fatal(err)
+	}
+	rwSingleFlow(t, writer, acceptFlowAtServer(server), "the dark knight")
+}
+
+func TestSingleFlowCreatedAtServer(t *testing.T) {
+	ctx, shutdown := test.V23InitAnon()
+	defer shutdown()
+	pclient := testutil.NewPrincipal("client")
+	pserver := testutil.NewPrincipal("server")
+	cctx, _ := v23.WithPrincipal(ctx, pclient)
+	sctx, _ := v23.WithPrincipal(ctx, pserver)
+	client, server := NewClientServer(cctx, sctx)
+	defer client.Close()
+
+	clientVC, serverConnector, err := createVC(cctx, client, server, makeEP(0x5))
+	if err != nil {
+		t.Fatal(err)
+	}
+	ln, err := clientVC.Listen()
+	if err != nil {
+		t.Fatal(err)
+	}
+	writer, err := serverConnector.Connect()
+	if err != nil {
+		t.Fatal(err)
+	}
+	reader, err := ln.Accept()
+	if err != nil {
+		t.Fatal(err)
+	}
+	rwSingleFlow(t, writer, reader, "the dark knight")
+	ln.Close()
+}
+
+func testMultipleVCsAndMultipleFlows(t *testing.T, gomaxprocs int) {
+	testutil.InitRandGenerator(t.Logf)
+	ctx, shutdown := test.V23InitAnon()
+	defer shutdown()
+	// This test dials multiple VCs from the client to the server.
+	// On each VC, it creates multiple flows, writes to them and verifies
+	// that the other process received what was written.
+
+	// Knobs configuring this test
+	//
+	// In case the test breaks, the knobs can be tuned down to isolate the problem.
+	// In normal circumstances, the knows should be tuned up to stress test the code.
+	const (
+		nVCs                  = 6 // Number of VCs created by the client process Dialing.
+		nFlowsFromClientPerVC = 3 // Number of flows initiated by the client process, per VC
+		nFlowsFromServerPerVC = 4 // Number of flows initiated by the server process, per VC
+
+		// Maximum number of bytes to write and read per flow.
+		// The actual size is selected randomly.
+		maxBytesPerFlow = 512 << 10 // 512KB
+	)
+
+	mp := runtime.GOMAXPROCS(gomaxprocs)
+	defer runtime.GOMAXPROCS(mp)
+
+	pclient := testutil.NewPrincipal("client")
+	pserver := testutil.NewPrincipal("server")
+	cctx, _ := v23.WithPrincipal(ctx, pclient)
+	sctx, _ := v23.WithPrincipal(ctx, pserver)
+	client, server := NewClientServer(cctx, sctx)
+	defer client.Close()
+
+	// Create all the VCs
+	// clientVCs[i] is the VC at the client process
+	// serverConnectors[i] is the corresponding VC at the server process.
+	clientVCs, serverConnectors, err := createNVCs(cctx, client, server, 0, nVCs)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// Create listeners for flows on the client VCs.
+	// Flows are implicitly being listened to at the server (available through server.Accept())
+	clientLNs, err := createListeners(clientVCs)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// Create flows:
+	// Over each VC, create nFlowsFromClientPerVC initiated by the client
+	// and nFlowsFromServerPerVC initiated by the server.
+	nFlows := nVCs * (nFlowsFromClientPerVC + nFlowsFromServerPerVC)
+
+	// Fill in random strings that will be written over the Flows.
+	dataWritten := make([]string, nFlows)
+	for i := 0; i < nFlows; i++ {
+		dataWritten[i] = string(testutil.RandomBytes(maxBytesPerFlow))
+	}
+
+	// write writes data to flow in randomly sized chunks.
+	write := func(flow stream.Flow, data string) {
+		defer flow.Close()
+		buf := []byte(data)
+		// Split into a random number of Write calls.
+		for len(buf) > 0 {
+			size := 1 + testutil.RandomIntn(len(buf)) // Random number in [1, len(buf)]
+			n, err := flow.Write(buf[:size])
+			if err != nil {
+				t.Errorf("Write failed: (%d, %v)", n, err)
+				return
+			}
+			buf = buf[size:]
+		}
+	}
+
+	dataReadChan := make(chan string, nFlows)
+	// read reads from a flow and writes out the data to dataReadChan
+	read := func(flow stream.Flow) {
+		var buf bytes.Buffer
+		var tmp [1024]byte
+		for {
+			n, err := flow.Read(tmp[:testutil.RandomIntn(len(tmp))])
+			buf.Write(tmp[:n])
+			if err == io.EOF {
+				break
+			}
+			if err != nil {
+				t.Errorf("Read error: %v", err)
+				break
+			}
+		}
+		dataReadChan <- buf.String()
+	}
+
+	index := 0
+	for i := 0; i < len(clientVCs); i++ {
+		for j := 0; j < nFlowsFromClientPerVC; j++ {
+			// Flow initiated by client, read by server
+			writer, err := clientVCs[i].Connect()
+			if err != nil {
+				t.Errorf("clientVCs[%d], flow %d: %v", i, j, err)
+				continue
+			}
+			go write(writer, dataWritten[index])
+			go read(acceptFlowAtServer(server))
+			index++
+		}
+	}
+	for i := 0; i < len(serverConnectors); i++ {
+		for j := 0; j < nFlowsFromServerPerVC; j++ {
+			// Flow initiated by server, read by client
+			writer, err := serverConnectors[i].Connect()
+			if err != nil {
+				t.Errorf("serverConnectors[%d], flow %d: %v", i, j, err)
+				continue
+			}
+			go write(writer, dataWritten[index])
+			go read(acceptFlowAtClient(clientLNs[i]))
+			index++
+		}
+	}
+	if index != nFlows {
+		t.Errorf("Created %d flows, wanted %d", index, nFlows)
+	}
+
+	// Collect all data read and compare against the data written.
+	// Since flows might be accepted in arbitrary order, sort the data before comparing.
+	dataRead := make([]string, index)
+	for i := 0; i < index; i++ {
+		dataRead[i] = <-dataReadChan
+	}
+	sort.Strings(dataWritten)
+	sort.Strings(dataRead)
+	if !reflect.DeepEqual(dataRead, dataWritten) {
+		// Since the strings can be very large, only print out the first few diffs.
+		nDiffs := 0
+		for i := 0; i < len(dataRead); i++ {
+			if dataRead[i] != dataWritten[i] {
+				nDiffs++
+				t.Errorf("Diff %d out of %d items: Got %q want %q", nDiffs, i, atmostNbytes(dataRead[i], 20), atmostNbytes(dataWritten[i], 20))
+			}
+		}
+		if nDiffs > 0 {
+			t.Errorf("#Mismatches:%d #ReadSamples:%d #WriteSamples:%d", nDiffs, len(dataRead), len(dataWritten))
+		}
+	}
+}
+
+func TestMultipleVCsAndMultipleFlows_1(t *testing.T) {
+	// Test with a single goroutine since that is typically easier to debug
+	// in case of problems.
+	testMultipleVCsAndMultipleFlows(t, 1)
+}
+
+func TestMultipleVCsAndMultipleFlows_5(t *testing.T) {
+	// Test with multiple goroutines, particularly useful for checking
+	// races with
+	// go test -race
+	testMultipleVCsAndMultipleFlows(t, 5)
+}
+
+func TestClose(t *testing.T) {
+	ctx, shutdown := test.V23InitAnon()
+	defer shutdown()
+	pclient := testutil.NewPrincipal("client")
+	pserver := testutil.NewPrincipal("server")
+	cctx, _ := v23.WithPrincipal(ctx, pclient)
+	sctx, _ := v23.WithPrincipal(ctx, pserver)
+	client, server := NewClientServer(cctx, sctx)
+	vc, _, err := createVC(cctx, client, server, makeEP(0x5))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	clientFlow, err := vc.Connect()
+	if err != nil {
+		t.Fatal(err)
+	}
+	serverFlow := acceptFlowAtServer(server)
+
+	var message = []byte("bugs bunny")
+	go func() {
+		if n, err := clientFlow.Write(message); n != len(message) || err != nil {
+			t.Fatalf("Wrote (%d, %v), want (%d, nil)", n, err, len(message))
+		}
+		client.Close()
+	}()
+
+	buf := make([]byte, 1024)
+	// client.Close should drain all pending writes first.
+	if n, err := serverFlow.Read(buf); n != len(message) || err != nil {
+		t.Fatalf("Got (%d, %v) = %q, want (%d, nil) = %q", n, err, buf[:n], len(message), message)
+	}
+	// subsequent reads should fail, since the VIF should be closed.
+	if n, err := serverFlow.Read(buf); n != 0 || err == nil {
+		t.Fatalf("Got (%d, %v) = %q, want (0, nil)", n, err, buf[:n])
+	}
+	server.Close()
+}
+
+func TestOnClose(t *testing.T) {
+	ctx, shutdown := test.V23InitAnon()
+	defer shutdown()
+	pclient := testutil.NewPrincipal("client")
+	pserver := testutil.NewPrincipal("server")
+	cctx, _ := v23.WithPrincipal(ctx, pclient)
+	sctx, _ := v23.WithPrincipal(ctx, pserver)
+	notifyC, notifyS := make(chan *vif.VIF), make(chan *vif.VIF)
+	notifyFuncC := func(vf *vif.VIF) { notifyC <- vf }
+	notifyFuncS := func(vf *vif.VIF) { notifyS <- vf }
+
+	// Close the client VIF. Both client and server should be notified.
+	client, server, err := New(nil, nil, cctx, sctx, notifyFuncC, notifyFuncS, nil, nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	client.Close()
+	if got := <-notifyC; got != client {
+		t.Errorf("Want notification for %v; got %v", client, got)
+	}
+	if got := <-notifyS; got != server {
+		t.Errorf("Want notification for %v; got %v", server, got)
+	}
+
+	// Same as above, but close the server VIF at this time.
+	client, server, err = New(nil, nil, cctx, sctx, notifyFuncC, notifyFuncS, nil, nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	server.Close()
+	if got := <-notifyC; got != client {
+		t.Errorf("Want notification for %v; got %v", client, got)
+	}
+	if got := <-notifyS; got != server {
+		t.Errorf("Want notification for %v; got %v", server, got)
+	}
+}
+
+func testCloseWhenEmpty(t *testing.T, testServer bool) {
+	const (
+		waitTime = 5 * time.Millisecond
+	)
+	ctx, shutdown := test.V23InitAnon()
+	defer shutdown()
+	pclient := testutil.NewPrincipal("client")
+	pserver := testutil.NewPrincipal("server")
+	cctx, _ := v23.WithPrincipal(ctx, pclient)
+	sctx, _ := v23.WithPrincipal(ctx, pserver)
+	notify := make(chan interface{})
+	notifyFunc := func(vf *vif.VIF) { notify <- vf }
+
+	newVIF := func() (vf, remote *vif.VIF) {
+		var err error
+		vf, remote, err = New(nil, nil, cctx, sctx, notifyFunc, notifyFunc, nil, nil)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if err = vf.StartAccepting(); err != nil {
+			t.Fatal(err)
+		}
+		if testServer {
+			vf, remote = remote, vf
+		}
+		return
+	}
+
+	// Initially empty. Should not be closed.
+	vf, remote := newVIF()
+	if err := vif.WaitWithTimeout(notify, waitTime); err != nil {
+		t.Error(err)
+	}
+
+	// Open one VC. Should not be closed.
+	vf, remote = newVIF()
+	if _, _, err := createVC(cctx, vf, remote, makeEP(0x10)); err != nil {
+		t.Fatal(err)
+	}
+	if err := vif.WaitWithTimeout(notify, waitTime); err != nil {
+		t.Error(err)
+	}
+
+	// Close the VC. Should be closed.
+	vf.ShutdownVCs(makeEP(0x10))
+	if err := vif.WaitForNotifications(notify, vf, remote); err != nil {
+		t.Error(err)
+	}
+
+	// Same as above, but open a VC from the remote side.
+	vf, remote = newVIF()
+	_, _, err := createVC(cctx, remote, vf, makeEP(0x10))
+	if err != nil {
+		t.Fatal(err)
+	}
+	if err := vif.WaitWithTimeout(notify, waitTime); err != nil {
+		t.Error(err)
+	}
+	remote.ShutdownVCs(makeEP(0x10))
+	if err := vif.WaitForNotifications(notify, vf, remote); err != nil {
+		t.Error(err)
+	}
+
+	// Create two VCs.
+	vf, remote = newVIF()
+	if _, _, err := createNVCs(cctx, vf, remote, 0x10, 2); err != nil {
+		t.Fatal(err)
+	}
+
+	// Close the first VC twice. Should not be closed.
+	vf.ShutdownVCs(makeEP(0x10))
+	vf.ShutdownVCs(makeEP(0x10))
+	if err := vif.WaitWithTimeout(notify, waitTime); err != nil {
+		t.Error(err)
+	}
+
+	// Close the second VC. Should be closed.
+	vf.ShutdownVCs(makeEP(0x10 + 1))
+	if err := vif.WaitForNotifications(notify, vf, remote); err != nil {
+		t.Error(err)
+	}
+}
+
+func TestCloseWhenEmpty(t *testing.T)       { testCloseWhenEmpty(t, false) }
+func TestCloseWhenEmptyServer(t *testing.T) { testCloseWhenEmpty(t, true) }
+
+func testStartTimeout(t *testing.T, testServer bool) {
+	const (
+		startTime = 5 * time.Millisecond
+		// We use a long wait time here since it takes some time for the underlying network
+		// connection of the other side to be closed especially in race testing.
+		waitTime = 150 * time.Millisecond
+	)
+	ctx, shutdown := test.V23InitAnon()
+	defer shutdown()
+	pclient := testutil.NewPrincipal("client")
+	pserver := testutil.NewPrincipal("server")
+	cctx, _ := v23.WithPrincipal(ctx, pclient)
+	sctx, _ := v23.WithPrincipal(ctx, pserver)
+	notify := make(chan interface{})
+	notifyFunc := func(vf *vif.VIF) { notify <- vf }
+
+	newVIF := func() (vf, remote *vif.VIF, triggerTimers func()) {
+		triggerTimers = vif.SetFakeTimers()
+		var vfStartTime, remoteStartTime time.Duration = startTime, 0
+		if testServer {
+			vfStartTime, remoteStartTime = remoteStartTime, vfStartTime
+		}
+		var err error
+		vf, remote, err = New(nil, nil, cctx, sctx, notifyFunc, notifyFunc, []stream.VCOpt{vc.StartTimeout{
+			Duration: vfStartTime,
+		}}, []stream.ListenerOpt{vc.StartTimeout{
+			Duration: remoteStartTime,
+		}})
+		if err != nil {
+			t.Fatal(err)
+		}
+		if err = vf.StartAccepting(); err != nil {
+			t.Fatal(err)
+		}
+		if testServer {
+			vf, remote = remote, vf
+		}
+		return
+	}
+
+	// No VC opened. Should be closed after the start timeout.
+	vf, remote, triggerTimers := newVIF()
+	triggerTimers()
+	if err := vif.WaitForNotifications(notify, vf, remote); err != nil {
+		t.Error(err)
+	}
+
+	// Open one VC. Should not be closed.
+	vf, remote, triggerTimers = newVIF()
+	if _, _, err := createVC(cctx, vf, remote, makeEP(0x10)); err != nil {
+		t.Fatal(err)
+	}
+	triggerTimers()
+	if err := vif.WaitWithTimeout(notify, waitTime); err != nil {
+		t.Error(err)
+	}
+
+	// Close the VC. Should be closed.
+	vf.ShutdownVCs(makeEP(0x10))
+	if err := vif.WaitForNotifications(notify, vf, remote); err != nil {
+		t.Error(err)
+	}
+}
+
+func TestStartTimeout(t *testing.T)       { testStartTimeout(t, false) }
+func TestStartTimeoutServer(t *testing.T) { testStartTimeout(t, true) }
+
+func testIdleTimeout(t *testing.T, testServer bool) {
+	const (
+		idleTime = 10 * time.Millisecond
+		waitTime = idleTime * 2
+	)
+	ctx, shutdown := test.V23InitAnon()
+	defer shutdown()
+	pclient := testutil.NewPrincipal("client")
+	pserver := testutil.NewPrincipal("server")
+	cctx, _ := v23.WithPrincipal(ctx, pclient)
+	sctx, _ := v23.WithPrincipal(ctx, pserver)
+	notify := make(chan interface{})
+	notifyFunc := func(vf *vif.VIF) { notify <- vf }
+
+	newVIF := func() (vf, remote *vif.VIF) {
+		var err error
+		if vf, remote, err = New(nil, nil, cctx, sctx, notifyFunc, notifyFunc, nil, nil); err != nil {
+			t.Fatal(err)
+		}
+		if err = vf.StartAccepting(); err != nil {
+			t.Fatal(err)
+		}
+		if testServer {
+			vf, remote = remote, vf
+		}
+		return
+	}
+	newVC := func(vf, remote *vif.VIF) (VC stream.VC, ln stream.Listener, remoteVC stream.Connector, triggerTimers func()) {
+		triggerTimers = vif.SetFakeTimers()
+		var err error
+		VC, remoteVC, err = createVC(cctx, vf, remote, makeEP(0x10), vc.IdleTimeout{Duration: idleTime})
+		if err != nil {
+			t.Fatal(err)
+		}
+		if ln, err = VC.Listen(); err != nil {
+			t.Fatal(err)
+		}
+		return
+	}
+	newFlow := func(vc stream.VC, remote *vif.VIF) stream.Flow {
+		f, err := vc.Connect()
+		if err != nil {
+			t.Fatal(err)
+		}
+		acceptFlowAtServer(remote)
+		return f
+	}
+
+	// No active flow. Should be notified.
+	vf, remote := newVIF()
+	_, _, _, triggerTimers := newVC(vf, remote)
+	triggerTimers()
+	if err := vif.WaitForNotifications(notify, vf, remote); err != nil {
+		t.Error(err)
+	}
+
+	// Same as above, but with multiple VCs.
+	vf, remote = newVIF()
+	triggerTimers = vif.SetFakeTimers()
+	if _, _, err := createNVCs(cctx, vf, remote, 0x10, 5, vc.IdleTimeout{Duration: idleTime}); err != nil {
+		t.Fatal(err)
+	}
+	triggerTimers()
+	if err := vif.WaitForNotifications(notify, vf, remote); err != nil {
+		t.Error(err)
+	}
+
+	// Open one flow. Should not be notified.
+	vf, remote = newVIF()
+	vc, _, _, triggerTimers := newVC(vf, remote)
+	f1 := newFlow(vc, remote)
+	triggerTimers()
+	if err := vif.WaitWithTimeout(notify, waitTime); err != nil {
+		t.Fatal(err)
+	}
+
+	// Close the flow. Should be notified.
+	f1.Close()
+	if err := vif.WaitForNotifications(notify, vf, remote); err != nil {
+		t.Error(err)
+	}
+
+	// Open two flows.
+	vf, remote = newVIF()
+	vc, _, _, triggerTimers = newVC(vf, remote)
+	f1 = newFlow(vc, remote)
+	f2 := newFlow(vc, remote)
+	triggerTimers()
+
+	// Close the first flow twice. Should not be notified.
+	f1.Close()
+	f1.Close()
+	if err := vif.WaitWithTimeout(notify, waitTime); err != nil {
+		t.Fatal(err)
+	}
+	// Close the second flow. Should be notified now.
+	f2.Close()
+	if err := vif.WaitForNotifications(notify, vf, remote); err != nil {
+		t.Error(err)
+	}
+
+	// Same as above, but open a flow from the remote side.
+	vf, remote = newVIF()
+	_, ln, remoteVC, triggerTimers := newVC(vf, remote)
+	f1, err := remoteVC.Connect()
+	if err != nil {
+		t.Fatal(err)
+	}
+	acceptFlowAtClient(ln)
+	triggerTimers()
+	if err := vif.WaitWithTimeout(notify, waitTime); err != nil {
+		t.Fatal(err)
+	}
+	f1.Close()
+	if err := vif.WaitForNotifications(notify, vf, remote); err != nil {
+		t.Error(err)
+	}
+}
+
+func TestIdleTimeout(t *testing.T)       { testIdleTimeout(t, false) }
+func TestIdleTimeoutServer(t *testing.T) { testIdleTimeout(t, true) }
+
+func TestShutdownVCs(t *testing.T) {
+	ctx, shutdown := test.V23InitAnon()
+	defer shutdown()
+	pclient := testutil.NewPrincipal("client")
+	pserver := testutil.NewPrincipal("server")
+	cctx, _ := v23.WithPrincipal(ctx, pclient)
+	sctx, _ := v23.WithPrincipal(ctx, pserver)
+	client, server := NewClientServer(cctx, sctx)
+	defer server.Close()
+	defer client.Close()
+
+	testN := func(N int) error {
+		c := client.NumVCs()
+		if c != N {
+			return fmt.Errorf("%d VCs on client VIF, expected %d", c, N)
+		}
+		return nil
+	}
+
+	if _, _, err := createVC(cctx, client, server, makeEP(0x5)); err != nil {
+		t.Fatal(err)
+	}
+	if err := testN(1); err != nil {
+		t.Error(err)
+	}
+	if _, _, err := createVC(cctx, client, server, makeEP(0x5)); err != nil {
+		t.Fatal(err)
+	}
+	if err := testN(2); err != nil {
+		t.Error(err)
+	}
+	if _, _, err := createVC(cctx, client, server, makeEP(0x7)); err != nil {
+		t.Fatal(err)
+	}
+	if err := testN(3); err != nil {
+		t.Error(err)
+	}
+	// Client does not have any VCs to the endpoint with routing id 0x9,
+	// so nothing should be closed
+	if n := client.ShutdownVCs(makeEP(0x9)); n != 0 {
+		t.Errorf("Expected 0 VCs to be closed, closed %d", n)
+	}
+	if err := testN(3); err != nil {
+		t.Error(err)
+	}
+	// But it does have to 0x5
+	if n := client.ShutdownVCs(makeEP(0x5)); n != 2 {
+		t.Errorf("Expected 2 VCs to be closed, closed %d", n)
+	}
+	if err := testN(1); err != nil {
+		t.Error(err)
+	}
+	// And 0x7
+	if n := client.ShutdownVCs(makeEP(0x7)); n != 1 {
+		t.Errorf("Expected 2 VCs to be closed, closed %d", n)
+	}
+	if err := testN(0); err != nil {
+		t.Error(err)
+	}
+}
+
+type versionTestCase struct {
+	client, server, ep *iversion.Range
+	expectError        bool
+	expectVIFError     bool
+}
+
+func (tc *versionTestCase) Run(t *testing.T) {
+	ctx, shutdown := test.V23InitAnon()
+	defer shutdown()
+	pclient := testutil.NewPrincipal("client")
+	pserver := testutil.NewPrincipal("server")
+	cctx, _ := v23.WithPrincipal(ctx, pclient)
+	sctx, _ := v23.WithPrincipal(ctx, pserver)
+	client, server, err := NewVersionedClientServer(tc.client, tc.server, cctx, sctx)
+	if (err != nil) != tc.expectVIFError {
+		t.Errorf("Error mismatch.  Wanted error: %v, got %v; client: %v, server: %v", tc.expectVIFError, err, tc.client, tc.server)
+	}
+	if err != nil {
+		return
+	}
+	defer client.Close()
+
+	ep := &inaming.Endpoint{
+		Protocol: "test",
+		Address:  "addr",
+		RID:      naming.FixedRoutingID(0x5),
+	}
+	clientVC, _, err := createVC(cctx, client, server, ep)
+	if (err != nil) != tc.expectError {
+		t.Errorf("Error mismatch.  Wanted error: %v, got %v (client:%v, server:%v ep:%v)", tc.expectError, err, tc.client, tc.server, tc.ep)
+
+	}
+	if err != nil {
+		return
+	}
+
+	writer, err := clientVC.Connect()
+	if err != nil {
+		t.Errorf("Unexpected error on case %+v: %v", tc, err)
+		return
+	}
+
+	rwSingleFlow(t, writer, acceptFlowAtServer(server), "the dark knight")
+}
+
+// TestIncompatibleVersions tests many cases where the client and server
+// have compatible or incompatible supported version ranges.  It ensures
+// that overlapping ranges work properly, but non-overlapping ranges generate
+// errors.
+func TestIncompatibleVersions(t *testing.T) {
+	unknown := &iversion.Range{Min: version.UnknownRPCVersion, Max: version.UnknownRPCVersion}
+	tests := []versionTestCase{
+		{&iversion.Range{Min: 2, Max: 2}, &iversion.Range{Min: 2, Max: 2}, &iversion.Range{Min: 2, Max: 2}, false, false},
+		{&iversion.Range{Min: 2, Max: 3}, &iversion.Range{Min: 3, Max: 5}, &iversion.Range{Min: 3, Max: 5}, false, false},
+		{&iversion.Range{Min: 2, Max: 3}, &iversion.Range{Min: 3, Max: 5}, unknown, false, false},
+
+		// VIF error since there are no versions in common.
+		{&iversion.Range{Min: 2, Max: 3}, &iversion.Range{Min: 4, Max: 5}, &iversion.Range{Min: 4, Max: 5}, true, true},
+		{&iversion.Range{Min: 2, Max: 3}, &iversion.Range{Min: 4, Max: 5}, unknown, true, true},
+	}
+
+	for _, tc := range tests {
+		tc.Run(t)
+	}
+}
+
+func TestNetworkFailure(t *testing.T) {
+	ctx, shutdown := test.V23InitAnon()
+	defer shutdown()
+	pclient := testutil.NewPrincipal("client")
+	pserver := testutil.NewPrincipal("server")
+	cctx, _ := v23.WithPrincipal(ctx, pclient)
+	sctx, _ := v23.WithPrincipal(ctx, pserver)
+	c1, c2 := pipe()
+	result := make(chan *vif.VIF)
+	closed := make(chan struct{})
+	go func() {
+		client, err := vif.InternalNewDialedVIF(sctx, c1, naming.FixedRoutingID(0xc), nil, func(vf *vif.VIF) { close(closed) })
+		if err != nil {
+			t.Fatal(err)
+		}
+		result <- client
+	}()
+	server, err := vif.InternalNewAcceptedVIF(sctx, c2, naming.FixedRoutingID(0x5), pserver.BlessingStore().Default(), nil, nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	client := <-result
+	// If the network connection dies, Dial and Accept should fail.
+	c1.Close()
+	// Wait until the VIF is closed, since Dial() may run before the underlying VC is closed.
+	<-closed
+	if _, err := client.Dial(cctx, makeEP(0x5)); err == nil {
+		t.Errorf("Expected client.Dial to fail")
+	}
+	if _, err := server.Accept(); err == nil {
+		t.Errorf("Expected server.Accept to fail")
+	}
+}
+
+func TestPreAuthentication(t *testing.T) {
+	ctx, shutdown := test.V23InitAnon()
+	defer shutdown()
+	pclient := testutil.NewPrincipal("client")
+	pserver := testutil.NewPrincipal("server")
+	cctx, _ := v23.WithPrincipal(ctx, pclient)
+	sctx, _ := v23.WithPrincipal(ctx, pserver)
+	client, server := NewClientServer(cctx, sctx)
+	defer client.Close()
+
+	check := func(numVCs, numPreAuth uint) error {
+		stats := client.Stats()
+		if stats.NumDialedVCs != numVCs {
+			return fmt.Errorf("Unexpected NumDialedVCs in client; got %d want %d", stats.NumDialedVCs, numVCs)
+		}
+		if stats.NumPreAuthenticated != numPreAuth {
+			return fmt.Errorf("Unexpected NumPreAuthenticated in client; got %d want %d", stats.NumPreAuthenticated, numPreAuth)
+		}
+		stats = server.Stats()
+		if stats.NumAcceptedVCs != numVCs {
+			return fmt.Errorf("Unexpected NumAcceptedVCs in server; got %d want %d", stats.NumAcceptedVCs, numVCs)
+		}
+		if stats.NumPreAuthenticated != numPreAuth {
+			return fmt.Errorf("Unexpected PreAuthUsed in server; got %d want %d", stats.NumPreAuthenticated, numPreAuth)
+		}
+		return nil
+	}
+
+	// Use a different routing ID. Should not use pre-auth.
+	_, _, err := createVC(cctx, client, server, makeEP(0x55))
+	if err != nil {
+		t.Fatal(err)
+	}
+	if err := check(1, 0); err != nil {
+		t.Error(err)
+	}
+
+	// Use the same routing ID. Should use pre-auth.
+	_, _, err = createVC(cctx, client, server, makeEP(0x5))
+	if err != nil {
+		t.Fatal(err)
+	}
+	if err := check(2, 1); err != nil {
+		t.Error(err)
+	}
+
+	// Use the null routing ID. Should use VIF pre-auth.
+	_, _, err = createVC(cctx, client, server, makeEP(0x0))
+	if err != nil {
+		t.Fatal(err)
+	}
+	if err := check(3, 2); err != nil {
+		t.Error(err)
+	}
+
+	// Use a different principal. Should not use pre-auth.
+	nctx, _ := v23.WithPrincipal(ctx, testutil.NewPrincipal("client2"))
+	_, _, err = createVC(nctx, client, server, makeEP(0x5))
+	if err != nil {
+		t.Fatal(err)
+	}
+	if err := check(4, 2); err != nil {
+		t.Error(err)
+	}
+}
+
+func makeEP(rid uint64) naming.Endpoint {
+	return &inaming.Endpoint{
+		Protocol: "test",
+		Address:  "addr",
+		RID:      naming.FixedRoutingID(rid),
+	}
+}
+
+// pipeAddr provides a more descriptive String implementation than provided by net.Pipe.
+type pipeAddr struct{ name string }
+
+func (a pipeAddr) Network() string { return "pipe" }
+func (a pipeAddr) String() string  { return a.name }
+
+// pipeConn provides a buffered net.Conn, with pipeAddr addressing.
+type pipeConn struct {
+	lock sync.Mutex
+	// w is guarded by lock, to prevent Close from racing with Write.  This is a
+	// quick way to prevent the race, but it allows a Write to block the Close.
+	// This isn't a problem in the tests currently.
+	w            chan<- []byte
+	r            <-chan []byte
+	rdata        []byte
+	laddr, raddr pipeAddr
+}
+
+func (c *pipeConn) Read(data []byte) (int, error) {
+	for len(c.rdata) == 0 {
+		d, ok := <-c.r
+		if !ok {
+			return 0, io.EOF
+		}
+		c.rdata = d
+	}
+	n := copy(data, c.rdata)
+	c.rdata = c.rdata[n:]
+	return n, nil
+}
+
+func (c *pipeConn) Write(data []byte) (int, error) {
+	c.lock.Lock()
+	defer c.lock.Unlock()
+	if c.w == nil {
+		return 0, io.EOF
+	}
+	d := make([]byte, len(data))
+	copy(d, data)
+	c.w <- d
+	return len(data), nil
+}
+
+func (c *pipeConn) Close() error {
+	c.lock.Lock()
+	defer c.lock.Unlock()
+	if c.w == nil {
+		return io.EOF
+	}
+	close(c.w)
+	c.w = nil
+	return nil
+}
+
+func (c *pipeConn) LocalAddr() net.Addr                { return c.laddr }
+func (c *pipeConn) RemoteAddr() net.Addr               { return c.raddr }
+func (c *pipeConn) SetDeadline(t time.Time) error      { return nil }
+func (c *pipeConn) SetReadDeadline(t time.Time) error  { return nil }
+func (c *pipeConn) SetWriteDeadline(t time.Time) error { return nil }
+
+func pipe() (net.Conn, net.Conn) {
+	clientAddr := pipeAddr{"client"}
+	serverAddr := pipeAddr{"server"}
+	c1 := make(chan []byte, 10)
+	c2 := make(chan []byte, 10)
+	p1 := &pipeConn{w: c1, r: c2, laddr: clientAddr, raddr: serverAddr}
+	p2 := &pipeConn{w: c2, r: c1, laddr: serverAddr, raddr: clientAddr}
+	return p1, p2
+}
+
+func NewClientServer(cctx, sctx *context.T) (client, server *vif.VIF) {
+	var err error
+	client, server, err = New(nil, nil, cctx, sctx, nil, nil, nil, nil)
+	if err != nil {
+		panic(err)
+	}
+	return
+}
+
+func NewVersionedClientServer(clientVersions, serverVersions *iversion.Range, cctx, sctx *context.T) (client, server *vif.VIF, verr error) {
+	return New(clientVersions, serverVersions, cctx, sctx, nil, nil, nil, nil)
+}
+
+func New(clientVersions, serverVersions *iversion.Range, cctx, sctx *context.T, clientOnClose, serverOnClose func(*vif.VIF), opts []stream.VCOpt, lopts []stream.ListenerOpt) (client, server *vif.VIF, verr error) {
+	c1, c2 := pipe()
+	var cerr error
+	cl := make(chan *vif.VIF)
+	go func() {
+		c, err := vif.InternalNewDialedVIF(cctx, c1, naming.FixedRoutingID(0xc), clientVersions, clientOnClose, opts...)
+		if err != nil {
+			cerr = err
+			close(cl)
+		} else {
+			cl <- c
+		}
+	}()
+	blessings := v23.GetPrincipal(sctx).BlessingStore().Default()
+	s, err := vif.InternalNewAcceptedVIF(sctx, c2, naming.FixedRoutingID(0x5), blessings, serverVersions, serverOnClose, lopts...)
+	c, ok := <-cl
+	if err != nil {
+		verr = err
+		return
+	}
+	if !ok {
+		verr = cerr
+		return
+	}
+	server = s
+	client = c
+	return
+}
+
+// rwSingleFlow writes out data on writer and ensures that the reader sees the same string.
+func rwSingleFlow(t *testing.T, writer io.WriteCloser, reader io.Reader, data string) {
+	go func() {
+		if n, err := writer.Write([]byte(data)); n != len(data) || err != nil {
+			t.Errorf("Write failure. Got (%d, %v) want (%d, nil)", n, err, len(data))
+		}
+		writer.Close()
+	}()
+
+	var buf bytes.Buffer
+	var tmp [4096]byte
+	for {
+		n, err := reader.Read(tmp[:])
+		buf.Write(tmp[:n])
+		if err == io.EOF {
+			break
+		}
+		if err != nil {
+			t.Errorf("Read error: %v", err)
+		}
+	}
+	if buf.String() != data {
+		t.Errorf("Wrote %q but read %q", data, buf.String())
+	}
+}
+
+// createVC creates a VC by dialing from the client process to the server
+// process.  It returns the VC at the client and the Connector at the server
+// (which the server can use to create flows over the VC)).
+func createVC(ctx *context.T, client, server *vif.VIF, ep naming.Endpoint, opts ...stream.VCOpt) (clientVC stream.VC, serverConnector stream.Connector, err error) {
+	vcChan := make(chan stream.VC)
+	scChan := make(chan stream.Connector)
+	errChan := make(chan error)
+	go func() {
+		vc, err := client.Dial(ctx, ep, opts...)
+		errChan <- err
+		vcChan <- vc
+	}()
+	go func() {
+		cAndf, err := server.Accept()
+		errChan <- err
+		if err == nil {
+			scChan <- cAndf.Connector
+		}
+	}()
+	if err = <-errChan; err != nil {
+		return
+	}
+	if err = <-errChan; err != nil {
+		return
+	}
+	clientVC = <-vcChan
+	serverConnector = <-scChan
+	return
+}
+
+func createNVCs(ctx *context.T, client, server *vif.VIF, startRID uint64, N int, opts ...stream.VCOpt) (clientVCs []stream.VC, serverConnectors []stream.Connector, err error) {
+	var c stream.VC
+	var s stream.Connector
+	for i := 0; i < N; i++ {
+		c, s, err = createVC(ctx, client, server, makeEP(startRID+uint64(i)), opts...)
+		if err != nil {
+			return
+		}
+		clientVCs = append(clientVCs, c)
+		serverConnectors = append(serverConnectors, s)
+	}
+	return
+}
+
+func createListeners(vcs []stream.VC) ([]stream.Listener, error) {
+	var ret []stream.Listener
+	for _, vc := range vcs {
+		ln, err := vc.Listen()
+		if err != nil {
+			return nil, err
+		}
+		ret = append(ret, ln)
+	}
+	return ret, nil
+}
+
+func acceptFlowAtServer(vf *vif.VIF) stream.Flow {
+	for {
+		cAndf, err := vf.Accept()
+		if err != nil {
+			panic(err)
+		}
+		if cAndf.Flow != nil {
+			return cAndf.Flow
+		}
+	}
+}
+
+func acceptFlowAtClient(ln stream.Listener) stream.Flow {
+	f, err := ln.Accept()
+	if err != nil {
+		panic(err)
+	}
+	return f
+}
+
+func atmostNbytes(s string, n int) string {
+	if n > len(s) {
+		return s
+	}
+	b := []byte(s)
+	return string(b[:n/2]) + "..." + string(b[len(s)-n/2:])
+}
diff --git a/runtime/internal/rpc/stress/internal/client.go b/runtime/internal/rpc/stress/internal/client.go
new file mode 100644
index 0000000..2263c50
--- /dev/null
+++ b/runtime/internal/rpc/stress/internal/client.go
@@ -0,0 +1,128 @@
+// 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
+
+import (
+	"bytes"
+	"fmt"
+	"math/rand"
+	"time"
+
+	"v.io/v23/context"
+
+	"v.io/x/ref/runtime/internal/rpc/stress"
+)
+
+// CallEcho calls 'Echo' method with the given payload size for the given time
+// duration and returns the number of iterations.
+func CallEcho(ctx *context.T, server string, payloadSize int, duration time.Duration) uint64 {
+	stub := stress.StressClient(server)
+	payload := make([]byte, payloadSize)
+	for i := range payload {
+		payload[i] = byte(i & 0xff)
+	}
+
+	var iterations uint64
+	start := time.Now()
+	for {
+		got, err := stub.Echo(ctx, payload)
+		if err != nil {
+			ctx.Fatalf("Echo failed: %v", err)
+		}
+		if !bytes.Equal(got, payload) {
+			ctx.Fatalf("Echo returned %v, but expected %v", got, payload)
+		}
+		iterations++
+
+		if time.Since(start) >= duration {
+			break
+		}
+	}
+	return iterations
+}
+
+// CallSum calls 'Sum' method with a randomly generated payload.
+func CallSum(ctx *context.T, server string, maxPayloadSize int, stats *stress.SumStats) {
+	stub := stress.StressClient(server)
+	arg, err := newSumArg(maxPayloadSize)
+	if err != nil {
+		ctx.Fatalf("new arg failed: %v", err)
+	}
+
+	got, err := stub.Sum(ctx, arg)
+	if err != nil {
+		ctx.Fatalf("Sum failed: %v", err)
+	}
+
+	wanted, _ := doSum(&arg)
+	if !bytes.Equal(got, wanted) {
+		ctx.Fatalf("Sum returned %v, but expected %v", got, wanted)
+	}
+	stats.SumCount++
+	stats.BytesSent += uint64(lenSumArg(&arg))
+	stats.BytesRecv += uint64(len(got))
+}
+
+// CallSumStream calls 'SumStream' method. Each iteration sends up to
+// 'maxChunkCnt' chunks on the stream and receives the same number of
+// sums back.
+func CallSumStream(ctx *context.T, server string, maxChunkCnt, maxPayloadSize int, stats *stress.SumStats) {
+	stub := stress.StressClient(server)
+	stream, err := stub.SumStream(ctx)
+	if err != nil {
+		ctx.Fatalf("Stream failed: %v", err)
+	}
+
+	chunkCnt := rand.Intn(maxChunkCnt) + 1
+	args := make([]stress.SumArg, chunkCnt)
+	done := make(chan error, 1)
+	go func() {
+		defer close(done)
+
+		recvS := stream.RecvStream()
+		i := 0
+		for ; recvS.Advance(); i++ {
+			got := recvS.Value()
+			wanted, _ := doSum(&args[i])
+			if !bytes.Equal(got, wanted) {
+				done <- fmt.Errorf("RecvStream returned %v, but expected %v", got, wanted)
+				return
+			}
+			stats.BytesRecv += uint64(len(got))
+		}
+		switch err := recvS.Err(); {
+		case err != nil:
+			done <- err
+		case i != chunkCnt:
+			done <- fmt.Errorf("RecvStream returned %d chunks, but expected %d", i, chunkCnt)
+		default:
+			done <- nil
+		}
+	}()
+
+	sendS := stream.SendStream()
+	for i := 0; i < chunkCnt; i++ {
+		arg, err := newSumArg(maxPayloadSize)
+		if err != nil {
+			ctx.Fatalf("new arg failed: %v", err)
+		}
+		args[i] = arg
+
+		if err = sendS.Send(arg); err != nil {
+			ctx.Fatalf("SendStream failed to send: %v", err)
+		}
+		stats.BytesSent += uint64(lenSumArg(&arg))
+	}
+	if err = sendS.Close(); err != nil {
+		ctx.Fatalf("SendStream failed to close: %v", err)
+	}
+	if err = <-done; err != nil {
+		ctx.Fatalf("%v", err)
+	}
+	if err = stream.Finish(); err != nil {
+		ctx.Fatalf("Stream failed to finish: %v", err)
+	}
+	stats.SumStreamCount++
+}
diff --git a/runtime/internal/rpc/stress/internal/server.go b/runtime/internal/rpc/stress/internal/server.go
new file mode 100644
index 0000000..563491a
--- /dev/null
+++ b/runtime/internal/rpc/stress/internal/server.go
@@ -0,0 +1,82 @@
+// 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
+
+import (
+	"sync"
+
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/x/ref/runtime/internal/rpc/stress"
+)
+
+type impl struct {
+	statsMu sync.Mutex
+	stats   stress.SumStats // GUARDED_BY(statsMu)
+
+	stop chan struct{}
+}
+
+func NewService() (stress.StressServerStub, <-chan struct{}) {
+	s := &impl{stop: make(chan struct{})}
+	return stress.StressServer(s), s.stop
+}
+
+func (s *impl) Echo(_ *context.T, _ rpc.ServerCall, payload []byte) ([]byte, error) {
+	return payload, nil
+}
+
+func (s *impl) Sum(_ *context.T, _ rpc.ServerCall, arg stress.SumArg) ([]byte, error) {
+	sum, err := doSum(&arg)
+	if err != nil {
+		return nil, err
+	}
+	s.addSumStats(false, uint64(lenSumArg(&arg)), uint64(len(sum)))
+	return sum, nil
+}
+
+func (s *impl) SumStream(_ *context.T, call stress.StressSumStreamServerCall) error {
+	rStream := call.RecvStream()
+	sStream := call.SendStream()
+	var bytesRecv, bytesSent uint64
+	for rStream.Advance() {
+		arg := rStream.Value()
+		sum, err := doSum(&arg)
+		if err != nil {
+			return err
+		}
+		sStream.Send(sum)
+		bytesRecv += uint64(lenSumArg(&arg))
+		bytesSent += uint64(len(sum))
+	}
+	if err := rStream.Err(); err != nil {
+		return err
+	}
+	s.addSumStats(true, bytesRecv, bytesSent)
+	return nil
+}
+
+func (s *impl) addSumStats(stream bool, bytesRecv, bytesSent uint64) {
+	s.statsMu.Lock()
+	if stream {
+		s.stats.SumStreamCount++
+	} else {
+		s.stats.SumCount++
+	}
+	s.stats.BytesRecv += bytesRecv
+	s.stats.BytesSent += bytesSent
+	s.statsMu.Unlock()
+}
+
+func (s *impl) GetSumStats(*context.T, rpc.ServerCall) (stress.SumStats, error) {
+	s.statsMu.Lock()
+	defer s.statsMu.Unlock()
+	return s.stats, nil
+}
+
+func (s *impl) Stop(*context.T, rpc.ServerCall) error {
+	s.stop <- struct{}{}
+	return nil
+}
diff --git a/runtime/internal/rpc/stress/internal/util.go b/runtime/internal/rpc/stress/internal/util.go
new file mode 100644
index 0000000..adf15e2
--- /dev/null
+++ b/runtime/internal/rpc/stress/internal/util.go
@@ -0,0 +1,44 @@
+// 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
+
+import (
+	"crypto/md5"
+	crand "crypto/rand"
+	"encoding/binary"
+	"math/rand"
+
+	"v.io/x/ref/runtime/internal/rpc/stress"
+)
+
+// newSumArg returns a randomly generated SumArg.
+func newSumArg(maxPayloadSize int) (stress.SumArg, error) {
+	var arg stress.SumArg
+	arg.ABool = rand.Intn(2) == 0
+	arg.AInt64 = rand.Int63()
+	arg.AListOfBytes = make([]byte, rand.Intn(maxPayloadSize)+1)
+	_, err := crand.Read(arg.AListOfBytes)
+	return arg, err
+}
+
+// lenSumArg returns the length of the SumArg in bytes.
+func lenSumArg(arg *stress.SumArg) int {
+	// bool + uint64 + []byte
+	return 1 + 4 + len(arg.AListOfBytes)
+}
+
+// doSum returns the MD5 checksum of the SumArg.
+func doSum(arg *stress.SumArg) ([]byte, error) {
+	h := md5.New()
+	if arg.ABool {
+		if err := binary.Write(h, binary.LittleEndian, arg.AInt64); err != nil {
+			return nil, err
+		}
+	}
+	if _, err := h.Write(arg.AListOfBytes); err != nil {
+		return nil, err
+	}
+	return h.Sum(nil), nil
+}
diff --git a/runtime/internal/rpc/stress/mtstress/doc.go b/runtime/internal/rpc/stress/mtstress/doc.go
new file mode 100644
index 0000000..75a4ac4
--- /dev/null
+++ b/runtime/internal/rpc/stress/mtstress/doc.go
@@ -0,0 +1,115 @@
+// 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
+
+/*
+Usage:
+   mtstress <command>
+
+The mtstress commands are:
+   mount       Measure latency of the Mount RPC at a fixed request rate
+   resolve     Measure latency of the Resolve RPC at a fixed request rate
+   help        Display help for commands or topics
+
+The global flags are:
+ -duration=10s
+   Duration for sending test traffic and measuring latency
+ -rate=1
+   Rate, in RPCs per second, to send to the test server
+ -reauthenticate=false
+   If true, establish a new authenticated connection for each RPC, simulating
+   load from a distinct process
+
+ -alsologtostderr=true
+   log to standard error as well as files
+ -log_backtrace_at=:0
+   when logging hits line file:N, emit a stack trace
+ -log_dir=
+   if non-empty, write log files to this directory
+ -logtostderr=false
+   log to standard error instead of files
+ -max_stack_buf_size=4292608
+   max size in bytes of the buffer to use for logging stack traces
+ -metadata=<just specify -metadata to activate>
+   Displays metadata for the program and exits.
+ -stderrthreshold=2
+   logs at or above this threshold go to stderr
+ -v=0
+   log level for V logs
+ -v23.credentials=
+   directory to use for storing security credentials
+ -v23.i18n-catalogue=
+   18n catalogue files to load, comma separated
+ -v23.namespace.root=[/(dev.v.io/role/vprod/service/mounttabled)@ns.dev.v.io:8101]
+   local namespace root; can be repeated to provided multiple roots
+ -v23.proxy=
+   object name of proxy service to use to export services across network
+   boundaries
+ -v23.tcp.address=
+   address to listen on
+ -v23.tcp.protocol=wsh
+   protocol to listen with
+ -v23.vtrace.cache-size=1024
+   The number of vtrace traces to store in memory.
+ -v23.vtrace.collect-regexp=
+   Spans and annotations that match this regular expression will trigger trace
+   collection.
+ -v23.vtrace.dump-on-shutdown=true
+   If true, dump all stored traces on runtime shutdown.
+ -v23.vtrace.sample-rate=0
+   Rate (from 0.0 to 1.0) to sample vtrace traces.
+ -vmodule=
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
+
+Mtstress mount - Measure latency of the Mount RPC at a fixed request rate
+
+Repeatedly issues a Mount request (at --rate) and measures latency
+
+Usage:
+   mtstress mount <mountpoint> <ttl>
+
+<mountpoint> defines the name to be mounted
+
+<ttl> specfies the time-to-live of the mount point. For example: 5s for 5
+seconds, 1m for 1 minute etc. Valid time units are "ms", "s", "m", "h".
+
+Mtstress resolve - Measure latency of the Resolve RPC at a fixed request rate
+
+Repeatedly issues a Resolve request (at --rate) to a name and measures latency
+
+Usage:
+   mtstress resolve <name>
+
+<name> the object name to resolve
+
+Mtstress help - Display help for commands or topics
+
+Help with no args displays the usage of the parent command.
+
+Help with args displays the usage of the specified sub-command or help topic.
+
+"help ..." recursively displays help for all commands and topics.
+
+Usage:
+   mtstress help [flags] [command/topic ...]
+
+[command/topic ...] optionally identifies a specific sub-command or help topic.
+
+The mtstress help flags are:
+ -style=compact
+   The formatting style for help output:
+      compact - Good for compact cmdline output.
+      full    - Good for cmdline output, shows all global flags.
+      godoc   - Good for godoc processing.
+   Override the default by setting the CMDLINE_STYLE environment variable.
+ -width=<terminal width>
+   Format output to this target width in runes, or unlimited if width < 0.
+   Defaults to the terminal width if available.  Override the default by setting
+   the CMDLINE_WIDTH environment variable.
+*/
+package main
diff --git a/runtime/internal/rpc/stress/mtstress/main.go b/runtime/internal/rpc/stress/mtstress/main.go
new file mode 100644
index 0000000..b00d77e
--- /dev/null
+++ b/runtime/internal/rpc/stress/mtstress/main.go
@@ -0,0 +1,151 @@
+// 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.
+
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go .
+
+package main
+
+import (
+	"flag"
+	"net"
+	"regexp"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/options"
+	"v.io/v23/verror"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/v23cmd"
+	_ "v.io/x/ref/runtime/factories/generic"
+)
+
+func init() {
+	cmdline.HideGlobalFlagsExcept(regexp.MustCompile(`^((rate)|(duration)|(reauthenticate))$`))
+}
+
+var (
+	rate     = flag.Float64("rate", 1, "Rate, in RPCs per second, to send to the test server")
+	duration = flag.Duration("duration", 10*time.Second, "Duration for sending test traffic and measuring latency")
+	reauth   = flag.Bool("reauthenticate", false, "If true, establish a new authenticated connection for each RPC, simulating load from a distinct process")
+
+	cmdMount = &cmdline.Command{
+		Name:  "mount",
+		Short: "Measure latency of the Mount RPC at a fixed request rate",
+		Long: `
+Repeatedly issues a Mount request (at --rate) and measures latency
+`,
+		ArgsName: "<mountpoint> <ttl>",
+		ArgsLong: `
+<mountpoint> defines the name to be mounted
+
+<ttl> specfies the time-to-live of the mount point. For example: 5s for 5
+seconds, 1m for 1 minute etc.
+Valid time units are "ms", "s", "m", "h".
+`,
+		Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline.Env, args []string) error {
+			if got, want := len(args), 2; got != want {
+				return env.UsageErrorf("mount: got %d arguments, want %d", got, want)
+			}
+			mountpoint := args[0]
+			ttl, err := time.ParseDuration(args[1])
+			if err != nil {
+				return env.UsageErrorf("invalid TTL: %v", err)
+			}
+			// Make up a random server to mount
+			ep := naming.FormatEndpoint("tcp", "127.0.0.1:14141")
+			mount := func(ctx *context.T) (time.Duration, error) {
+				// Currently this is a simple call, but at some
+				// point should generate random test data -
+				// mountpoints at different depths and the like
+				start := time.Now()
+				if err := v23.GetClient(ctx).Call(ctx, mountpoint, "Mount", []interface{}{ep, uint32(ttl / time.Second), 0}, nil, options.NoResolve{}); err != nil {
+					return 0, err
+				}
+				return time.Since(start), nil
+			}
+			p, err := paramsFromFlags(ctx, mountpoint)
+			if err != nil {
+				return err
+			}
+			return run(mount, p)
+		}),
+	}
+
+	cmdResolve = &cmdline.Command{
+		Name:  "resolve",
+		Short: "Measure latency of the Resolve RPC at a fixed request rate",
+		Long: `
+Repeatedly issues a Resolve request (at --rate) to a name and measures latency
+`,
+		ArgsName: "<name>",
+		ArgsLong: `
+<name> the object name to resolve
+`,
+		Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline.Env, args []string) error {
+			if got, want := len(args), 1; got != want {
+				return env.UsageErrorf("resolve: got %d arguments, want %d", got, want)
+			}
+			name := args[0]
+			resolve := func(ctx *context.T) (time.Duration, error) {
+				var entry naming.MountEntry
+				start := time.Now()
+				if err := v23.GetClient(ctx).Call(ctx, name, "ResolveStep", nil, []interface{}{&entry}, options.NoResolve{}); err != nil && verror.ErrorID(err) != naming.ErrNoSuchName.ID {
+					// ErrNoSuchName is fine, it just means
+					// that the mounttable server did not
+					// find an entry in its tables.
+					return 0, err
+				}
+				return time.Since(start), nil
+			}
+			p, err := paramsFromFlags(ctx, name)
+			if err != nil {
+				return err
+			}
+			return run(resolve, p)
+		}),
+	}
+)
+
+func paramsFromFlags(ctx *context.T, objectName string) (params, error) {
+	// Measure network distance to objectName
+	const iters = 5
+	addr, _ := naming.SplitAddressName(objectName)
+	if len(addr) == 0 {
+		addr, _ = naming.SplitAddressName(v23.GetNamespace(ctx).Roots()[0])
+	}
+	ep, err := v23.NewEndpoint(addr)
+	if err != nil {
+		return params{}, err
+	}
+	// Assume TCP
+	var total time.Duration
+	for i := 0; i < iters; i++ {
+		start := time.Now()
+		conn, err := net.Dial("tcp", ep.Addr().String())
+		if err != nil {
+			return params{}, err
+		}
+		total += time.Since(start)
+		conn.Close()
+	}
+	return params{
+		Rate:            *rate,
+		Duration:        *duration,
+		NetworkDistance: time.Duration(total.Nanoseconds() / iters),
+		Context:         ctx,
+		Reauthenticate:  *reauth,
+	}, nil
+}
+
+func main() {
+	root := &cmdline.Command{
+		Name:     "mtstress",
+		Short:    "Tool to stress test a mounttable service by issuing a fixed rate of requests per second and measuring latency",
+		Children: []*cmdline.Command{cmdMount, cmdResolve},
+	}
+	cmdline.Main(root)
+}
diff --git a/runtime/internal/rpc/stress/mtstress/run.go b/runtime/internal/rpc/stress/mtstress/run.go
new file mode 100644
index 0000000..c357311
--- /dev/null
+++ b/runtime/internal/rpc/stress/mtstress/run.go
@@ -0,0 +1,157 @@
+// 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 (
+	"fmt"
+	"os"
+	"os/signal"
+	"sync"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+
+	"v.io/x/ref/lib/stats/histogram"
+)
+
+// params encapsulates "input" information to the loadtester.
+type params struct {
+	NetworkDistance time.Duration // Distance (in time) of the server from this driver
+	Rate            float64       // Desired rate of sending RPCs (RPC/sec)
+	Duration        time.Duration // Duration over which loadtest traffic should be sent
+	Context         *context.T
+	Reauthenticate  bool // If true, each RPC should establish a network connection (and authenticate)
+}
+
+// report is generated by a run of the loadtest.
+type report struct {
+	Count      int64         // Count of RPCs sent
+	Elapsed    time.Duration // Time period over which Count RPCs were sent
+	AvgLatency time.Duration
+	HistMS     *histogram.Histogram // Histogram of latencies in milliseconds
+}
+
+func (r *report) Print(params params) error {
+	if r.HistMS != nil {
+		fmt.Println("RPC latency histogram (in ms):")
+		fmt.Println(r.HistMS.Value())
+	}
+	actualRate := float64(r.Count*int64(time.Second)) / float64(r.Elapsed)
+	fmt.Printf("Network Distance: %v\n", params.NetworkDistance)
+	fmt.Printf("#RPCs sent:       %v\n", r.Count)
+	fmt.Printf("RPCs/sec sent:    %.2f (%.2f%% of the desired rate of %v)\n", actualRate, actualRate*100/params.Rate, params.Rate)
+	fmt.Printf("Avg. Latency:     %v\n", r.AvgLatency)
+	// Mark the results are tainted if the deviation from the desired rate is too large
+	if 0.9*params.Rate > actualRate {
+		return fmt.Errorf("TAINTED RESULTS: drove less traffic than desired: either server or loadtester had a bottleneck")
+	}
+	return nil
+}
+
+func run(f func(*context.T) (time.Duration, error), p params) error {
+	var (
+		ticker    = time.NewTicker(time.Duration(float64(time.Second) / p.Rate))
+		latency   = make(chan time.Duration, 1000)
+		started   = time.Now()
+		stop      = time.After(p.Duration)
+		interrupt = make(chan os.Signal)
+		ret       report
+	)
+	defer ticker.Stop()
+	warmup(p.Context, f)
+	signal.Notify(interrupt, os.Interrupt)
+	defer signal.Stop(interrupt)
+
+	stopped := false
+	var sumMS int64
+	var lastInterrupt time.Time
+	for !stopped {
+		select {
+		case <-ticker.C:
+			go call(p.Context, f, p.Reauthenticate, latency)
+		case d := <-latency:
+			if ret.HistMS != nil {
+				ret.HistMS.Add(int64(d / time.Millisecond))
+			}
+			ret.Count++
+			sumMS += int64(d / time.Millisecond)
+			// Use 10 samples to determine how the histogram should be setup
+			if ret.Count == 10 {
+				avgms := sumMS / ret.Count
+				opts := histogram.Options{
+					NumBuckets: 32,
+					// Mostly interested in tail latencies,
+					// so have the histogram start close to
+					// the current average.
+					MinValue:     int64(float64(avgms) * 0.95),
+					GrowthFactor: 0.20,
+				}
+				p.Context.Infof("Creating histogram after %d samples (%vms avg latency): %+v", ret.Count, avgms, opts)
+				ret.HistMS = histogram.New(opts)
+			}
+		case sig := <-interrupt:
+			if time.Since(lastInterrupt) < time.Second {
+				p.Context.Infof("Multiple %v signals received, aborting test", sig)
+				stopped = true
+				break
+			}
+			lastInterrupt = time.Now()
+			// Print a temporary report
+			fmt.Println("INTERMEDIATE REPORT:")
+			ret.Elapsed = time.Since(started)
+			ret.AvgLatency = time.Duration(float64(sumMS)/float64(ret.Count)) * time.Millisecond
+			if err := ret.Print(p); err != nil {
+				fmt.Println(err)
+			}
+			fmt.Println("--------------------------------------------------------------------------------")
+		case <-stop:
+			stopped = true
+		}
+	}
+	ret.Elapsed = time.Since(started)
+	ret.AvgLatency = time.Duration(float64(sumMS)/float64(ret.Count)) * time.Millisecond
+	return ret.Print(p)
+}
+
+func warmup(ctx *context.T, f func(*context.T) (time.Duration, error)) {
+	const nWarmup = 10
+	ctx.Infof("Sending %d requests as warmup", nWarmup)
+	var wg sync.WaitGroup
+	for i := 0; i < nWarmup; i++ {
+		wg.Add(1)
+		go func() {
+			f(ctx)
+			wg.Done()
+		}()
+	}
+	wg.Wait()
+	ctx.Infof("Done with warmup")
+}
+
+func call(ctx *context.T, f func(*context.T) (time.Duration, error), reauth bool, d chan<- time.Duration) {
+	client := v23.GetClient(ctx)
+	if reauth {
+		// HACK ALERT: At the time the line below was written, it was
+		// known that the implementation would cause 'ctx' to be setup
+		// such that any subsequent RPC will establish a network
+		// connection from scratch (a new VIF, new VC etc.) If that
+		// implementation changes, then this line below will have to
+		// change!
+		var err error
+		if ctx, err = v23.WithPrincipal(ctx, v23.GetPrincipal(ctx)); err != nil {
+			ctx.Infof("%v", err)
+			return
+		}
+		client = v23.GetClient(ctx)
+		defer client.Close()
+	}
+	sample, err := f(ctx)
+	if err != nil {
+		ctx.Infof("%v", err)
+		return
+	}
+	d <- sample
+}
diff --git a/runtime/internal/rpc/stress/stress.vdl b/runtime/internal/rpc/stress/stress.vdl
new file mode 100644
index 0000000..1ae6c51
--- /dev/null
+++ b/runtime/internal/rpc/stress/stress.vdl
@@ -0,0 +1,39 @@
+// 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 stress
+
+import (
+	"v.io/v23/security/access"
+)
+
+type SumArg struct {
+	ABool        bool
+	AInt64       int64
+	AListOfBytes []byte
+}
+
+type SumStats struct {
+	SumCount       uint64
+	SumStreamCount uint64
+	BytesRecv      uint64
+	BytesSent      uint64
+}
+
+type Stress interface {
+	// Echo returns the payload that it receives.
+	Echo(Payload []byte) ([]byte | error) {access.Read}
+
+	// Do returns the checksum of the payload that it receives.
+	Sum(arg SumArg) ([]byte | error) {access.Read}
+
+	// DoStream returns the checksum of the payload that it receives via the stream.
+	SumStream() stream<SumArg,[]byte> error {access.Read}
+
+	// GetSumStats returns the stats on the Sum calls that the server received.
+	GetSumStats() (SumStats | error) {access.Read}
+
+	// Stop stops the server.
+	Stop() error {access.Admin}
+}
diff --git a/runtime/internal/rpc/stress/stress.vdl.go b/runtime/internal/rpc/stress/stress.vdl.go
new file mode 100644
index 0000000..15e2787
--- /dev/null
+++ b/runtime/internal/rpc/stress/stress.vdl.go
@@ -0,0 +1,434 @@
+// 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 by the vanadium vdl tool.
+// Source: stress.vdl
+
+package stress
+
+import (
+	// VDL system imports
+	"io"
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/vdl"
+
+	// VDL user imports
+	"v.io/v23/security/access"
+)
+
+type SumArg struct {
+	ABool        bool
+	AInt64       int64
+	AListOfBytes []byte
+}
+
+func (SumArg) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/runtime/internal/rpc/stress.SumArg"`
+}) {
+}
+
+type SumStats struct {
+	SumCount       uint64
+	SumStreamCount uint64
+	BytesRecv      uint64
+	BytesSent      uint64
+}
+
+func (SumStats) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/runtime/internal/rpc/stress.SumStats"`
+}) {
+}
+
+func init() {
+	vdl.Register((*SumArg)(nil))
+	vdl.Register((*SumStats)(nil))
+}
+
+// StressClientMethods is the client interface
+// containing Stress methods.
+type StressClientMethods interface {
+	// Echo returns the payload that it receives.
+	Echo(ctx *context.T, Payload []byte, opts ...rpc.CallOpt) ([]byte, error)
+	// Do returns the checksum of the payload that it receives.
+	Sum(ctx *context.T, arg SumArg, opts ...rpc.CallOpt) ([]byte, error)
+	// DoStream returns the checksum of the payload that it receives via the stream.
+	SumStream(*context.T, ...rpc.CallOpt) (StressSumStreamClientCall, error)
+	// GetSumStats returns the stats on the Sum calls that the server received.
+	GetSumStats(*context.T, ...rpc.CallOpt) (SumStats, error)
+	// Stop stops the server.
+	Stop(*context.T, ...rpc.CallOpt) error
+}
+
+// StressClientStub adds universal methods to StressClientMethods.
+type StressClientStub interface {
+	StressClientMethods
+	rpc.UniversalServiceMethods
+}
+
+// StressClient returns a client stub for Stress.
+func StressClient(name string) StressClientStub {
+	return implStressClientStub{name}
+}
+
+type implStressClientStub struct {
+	name string
+}
+
+func (c implStressClientStub) Echo(ctx *context.T, i0 []byte, opts ...rpc.CallOpt) (o0 []byte, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "Echo", []interface{}{i0}, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implStressClientStub) Sum(ctx *context.T, i0 SumArg, opts ...rpc.CallOpt) (o0 []byte, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "Sum", []interface{}{i0}, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implStressClientStub) SumStream(ctx *context.T, opts ...rpc.CallOpt) (ocall StressSumStreamClientCall, err error) {
+	var call rpc.ClientCall
+	if call, err = v23.GetClient(ctx).StartCall(ctx, c.name, "SumStream", nil, opts...); err != nil {
+		return
+	}
+	ocall = &implStressSumStreamClientCall{ClientCall: call}
+	return
+}
+
+func (c implStressClientStub) GetSumStats(ctx *context.T, opts ...rpc.CallOpt) (o0 SumStats, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "GetSumStats", nil, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implStressClientStub) Stop(ctx *context.T, opts ...rpc.CallOpt) (err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "Stop", nil, nil, opts...)
+	return
+}
+
+// StressSumStreamClientStream is the client stream for Stress.SumStream.
+type StressSumStreamClientStream interface {
+	// RecvStream returns the receiver side of the Stress.SumStream client stream.
+	RecvStream() interface {
+		// Advance stages an item so that it may be retrieved via Value.  Returns
+		// true iff there is an item to retrieve.  Advance must be called before
+		// Value is called.  May block if an item is not available.
+		Advance() bool
+		// Value returns the item that was staged by Advance.  May panic if Advance
+		// returned false or was not called.  Never blocks.
+		Value() []byte
+		// Err returns any error encountered by Advance.  Never blocks.
+		Err() error
+	}
+	// SendStream returns the send side of the Stress.SumStream client stream.
+	SendStream() interface {
+		// Send places the item onto the output stream.  Returns errors
+		// encountered while sending, or if Send is called after Close or
+		// the stream has been canceled.  Blocks if there is no buffer
+		// space; will unblock when buffer space is available or after
+		// the stream has been canceled.
+		Send(item SumArg) error
+		// Close indicates to the server that no more items will be sent;
+		// server Recv calls will receive io.EOF after all sent items.
+		// This is an optional call - e.g. a client might call Close if it
+		// needs to continue receiving items from the server after it's
+		// done sending.  Returns errors encountered while closing, or if
+		// Close is called after the stream has been canceled.  Like Send,
+		// blocks if there is no buffer space available.
+		Close() error
+	}
+}
+
+// StressSumStreamClientCall represents the call returned from Stress.SumStream.
+type StressSumStreamClientCall interface {
+	StressSumStreamClientStream
+	// Finish performs the equivalent of SendStream().Close, then blocks until
+	// the server is done, and returns the positional return values for the call.
+	//
+	// Finish returns immediately if the call has been canceled; depending on the
+	// timing the output could either be an error signaling cancelation, or the
+	// valid positional return values from the server.
+	//
+	// Calling Finish is mandatory for releasing stream resources, unless the call
+	// has been canceled or any of the other methods return an error.  Finish should
+	// be called at most once.
+	Finish() error
+}
+
+type implStressSumStreamClientCall struct {
+	rpc.ClientCall
+	valRecv []byte
+	errRecv error
+}
+
+func (c *implStressSumStreamClientCall) RecvStream() interface {
+	Advance() bool
+	Value() []byte
+	Err() error
+} {
+	return implStressSumStreamClientCallRecv{c}
+}
+
+type implStressSumStreamClientCallRecv struct {
+	c *implStressSumStreamClientCall
+}
+
+func (c implStressSumStreamClientCallRecv) Advance() bool {
+	c.c.errRecv = c.c.Recv(&c.c.valRecv)
+	return c.c.errRecv == nil
+}
+func (c implStressSumStreamClientCallRecv) Value() []byte {
+	return c.c.valRecv
+}
+func (c implStressSumStreamClientCallRecv) Err() error {
+	if c.c.errRecv == io.EOF {
+		return nil
+	}
+	return c.c.errRecv
+}
+func (c *implStressSumStreamClientCall) SendStream() interface {
+	Send(item SumArg) error
+	Close() error
+} {
+	return implStressSumStreamClientCallSend{c}
+}
+
+type implStressSumStreamClientCallSend struct {
+	c *implStressSumStreamClientCall
+}
+
+func (c implStressSumStreamClientCallSend) Send(item SumArg) error {
+	return c.c.Send(item)
+}
+func (c implStressSumStreamClientCallSend) Close() error {
+	return c.c.CloseSend()
+}
+func (c *implStressSumStreamClientCall) Finish() (err error) {
+	err = c.ClientCall.Finish()
+	return
+}
+
+// StressServerMethods is the interface a server writer
+// implements for Stress.
+type StressServerMethods interface {
+	// Echo returns the payload that it receives.
+	Echo(ctx *context.T, call rpc.ServerCall, Payload []byte) ([]byte, error)
+	// Do returns the checksum of the payload that it receives.
+	Sum(ctx *context.T, call rpc.ServerCall, arg SumArg) ([]byte, error)
+	// DoStream returns the checksum of the payload that it receives via the stream.
+	SumStream(*context.T, StressSumStreamServerCall) error
+	// GetSumStats returns the stats on the Sum calls that the server received.
+	GetSumStats(*context.T, rpc.ServerCall) (SumStats, error)
+	// Stop stops the server.
+	Stop(*context.T, rpc.ServerCall) error
+}
+
+// StressServerStubMethods is the server interface containing
+// Stress methods, as expected by rpc.Server.
+// The only difference between this interface and StressServerMethods
+// is the streaming methods.
+type StressServerStubMethods interface {
+	// Echo returns the payload that it receives.
+	Echo(ctx *context.T, call rpc.ServerCall, Payload []byte) ([]byte, error)
+	// Do returns the checksum of the payload that it receives.
+	Sum(ctx *context.T, call rpc.ServerCall, arg SumArg) ([]byte, error)
+	// DoStream returns the checksum of the payload that it receives via the stream.
+	SumStream(*context.T, *StressSumStreamServerCallStub) error
+	// GetSumStats returns the stats on the Sum calls that the server received.
+	GetSumStats(*context.T, rpc.ServerCall) (SumStats, error)
+	// Stop stops the server.
+	Stop(*context.T, rpc.ServerCall) error
+}
+
+// StressServerStub adds universal methods to StressServerStubMethods.
+type StressServerStub interface {
+	StressServerStubMethods
+	// Describe the Stress interfaces.
+	Describe__() []rpc.InterfaceDesc
+}
+
+// StressServer returns a server stub for Stress.
+// It converts an implementation of StressServerMethods into
+// an object that may be used by rpc.Server.
+func StressServer(impl StressServerMethods) StressServerStub {
+	stub := implStressServerStub{
+		impl: impl,
+	}
+	// Initialize GlobState; always check the stub itself first, to handle the
+	// case where the user has the Glob method defined in their VDL source.
+	if gs := rpc.NewGlobState(stub); gs != nil {
+		stub.gs = gs
+	} else if gs := rpc.NewGlobState(impl); gs != nil {
+		stub.gs = gs
+	}
+	return stub
+}
+
+type implStressServerStub struct {
+	impl StressServerMethods
+	gs   *rpc.GlobState
+}
+
+func (s implStressServerStub) Echo(ctx *context.T, call rpc.ServerCall, i0 []byte) ([]byte, error) {
+	return s.impl.Echo(ctx, call, i0)
+}
+
+func (s implStressServerStub) Sum(ctx *context.T, call rpc.ServerCall, i0 SumArg) ([]byte, error) {
+	return s.impl.Sum(ctx, call, i0)
+}
+
+func (s implStressServerStub) SumStream(ctx *context.T, call *StressSumStreamServerCallStub) error {
+	return s.impl.SumStream(ctx, call)
+}
+
+func (s implStressServerStub) GetSumStats(ctx *context.T, call rpc.ServerCall) (SumStats, error) {
+	return s.impl.GetSumStats(ctx, call)
+}
+
+func (s implStressServerStub) Stop(ctx *context.T, call rpc.ServerCall) error {
+	return s.impl.Stop(ctx, call)
+}
+
+func (s implStressServerStub) Globber() *rpc.GlobState {
+	return s.gs
+}
+
+func (s implStressServerStub) Describe__() []rpc.InterfaceDesc {
+	return []rpc.InterfaceDesc{StressDesc}
+}
+
+// StressDesc describes the Stress interface.
+var StressDesc rpc.InterfaceDesc = descStress
+
+// descStress hides the desc to keep godoc clean.
+var descStress = rpc.InterfaceDesc{
+	Name:    "Stress",
+	PkgPath: "v.io/x/ref/runtime/internal/rpc/stress",
+	Methods: []rpc.MethodDesc{
+		{
+			Name: "Echo",
+			Doc:  "// Echo returns the payload that it receives.",
+			InArgs: []rpc.ArgDesc{
+				{"Payload", ``}, // []byte
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"", ``}, // []byte
+			},
+			Tags: []*vdl.Value{vdl.ValueOf(access.Tag("Read"))},
+		},
+		{
+			Name: "Sum",
+			Doc:  "// Do returns the checksum of the payload that it receives.",
+			InArgs: []rpc.ArgDesc{
+				{"arg", ``}, // SumArg
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"", ``}, // []byte
+			},
+			Tags: []*vdl.Value{vdl.ValueOf(access.Tag("Read"))},
+		},
+		{
+			Name: "SumStream",
+			Doc:  "// DoStream returns the checksum of the payload that it receives via the stream.",
+			Tags: []*vdl.Value{vdl.ValueOf(access.Tag("Read"))},
+		},
+		{
+			Name: "GetSumStats",
+			Doc:  "// GetSumStats returns the stats on the Sum calls that the server received.",
+			OutArgs: []rpc.ArgDesc{
+				{"", ``}, // SumStats
+			},
+			Tags: []*vdl.Value{vdl.ValueOf(access.Tag("Read"))},
+		},
+		{
+			Name: "Stop",
+			Doc:  "// Stop stops the server.",
+			Tags: []*vdl.Value{vdl.ValueOf(access.Tag("Admin"))},
+		},
+	},
+}
+
+// StressSumStreamServerStream is the server stream for Stress.SumStream.
+type StressSumStreamServerStream interface {
+	// RecvStream returns the receiver side of the Stress.SumStream server stream.
+	RecvStream() interface {
+		// Advance stages an item so that it may be retrieved via Value.  Returns
+		// true iff there is an item to retrieve.  Advance must be called before
+		// Value is called.  May block if an item is not available.
+		Advance() bool
+		// Value returns the item that was staged by Advance.  May panic if Advance
+		// returned false or was not called.  Never blocks.
+		Value() SumArg
+		// Err returns any error encountered by Advance.  Never blocks.
+		Err() error
+	}
+	// SendStream returns the send side of the Stress.SumStream server stream.
+	SendStream() interface {
+		// Send places the item onto the output stream.  Returns errors encountered
+		// while sending.  Blocks if there is no buffer space; will unblock when
+		// buffer space is available.
+		Send(item []byte) error
+	}
+}
+
+// StressSumStreamServerCall represents the context passed to Stress.SumStream.
+type StressSumStreamServerCall interface {
+	rpc.ServerCall
+	StressSumStreamServerStream
+}
+
+// StressSumStreamServerCallStub is a wrapper that converts rpc.StreamServerCall into
+// a typesafe stub that implements StressSumStreamServerCall.
+type StressSumStreamServerCallStub struct {
+	rpc.StreamServerCall
+	valRecv SumArg
+	errRecv error
+}
+
+// Init initializes StressSumStreamServerCallStub from rpc.StreamServerCall.
+func (s *StressSumStreamServerCallStub) Init(call rpc.StreamServerCall) {
+	s.StreamServerCall = call
+}
+
+// RecvStream returns the receiver side of the Stress.SumStream server stream.
+func (s *StressSumStreamServerCallStub) RecvStream() interface {
+	Advance() bool
+	Value() SumArg
+	Err() error
+} {
+	return implStressSumStreamServerCallRecv{s}
+}
+
+type implStressSumStreamServerCallRecv struct {
+	s *StressSumStreamServerCallStub
+}
+
+func (s implStressSumStreamServerCallRecv) Advance() bool {
+	s.s.valRecv = SumArg{}
+	s.s.errRecv = s.s.Recv(&s.s.valRecv)
+	return s.s.errRecv == nil
+}
+func (s implStressSumStreamServerCallRecv) Value() SumArg {
+	return s.s.valRecv
+}
+func (s implStressSumStreamServerCallRecv) Err() error {
+	if s.s.errRecv == io.EOF {
+		return nil
+	}
+	return s.s.errRecv
+}
+
+// SendStream returns the send side of the Stress.SumStream server stream.
+func (s *StressSumStreamServerCallStub) SendStream() interface {
+	Send(item []byte) error
+} {
+	return implStressSumStreamServerCallSend{s}
+}
+
+type implStressSumStreamServerCallSend struct {
+	s *StressSumStreamServerCallStub
+}
+
+func (s implStressSumStreamServerCallSend) Send(item []byte) error {
+	return s.s.Send(item)
+}
diff --git a/runtime/internal/rpc/stress/stress/doc.go b/runtime/internal/rpc/stress/stress/doc.go
new file mode 100644
index 0000000..04e1957
--- /dev/null
+++ b/runtime/internal/rpc/stress/stress/doc.go
@@ -0,0 +1,158 @@
+// 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
+
+/*
+Command stress is a tool to stress/load test RPC by issuing randomly generated
+requests.
+
+Usage:
+   stress <command>
+
+The stress commands are:
+   stress      Run stress test
+   stats       Print out stress stats of servers
+   load        Run load test
+   stop        Stop servers
+   help        Display help for commands or topics
+
+The global flags are:
+ -alsologtostderr=true
+   log to standard error as well as files
+ -log_backtrace_at=:0
+   when logging hits line file:N, emit a stack trace
+ -log_dir=
+   if non-empty, write log files to this directory
+ -logtostderr=false
+   log to standard error instead of files
+ -max_stack_buf_size=4292608
+   max size in bytes of the buffer to use for logging stack traces
+ -metadata=<just specify -metadata to activate>
+   Displays metadata for the program and exits.
+ -stderrthreshold=2
+   logs at or above this threshold go to stderr
+ -v=0
+   log level for V logs
+ -v23.credentials=
+   directory to use for storing security credentials
+ -v23.i18n-catalogue=
+   18n catalogue files to load, comma separated
+ -v23.namespace.root=[/(dev.v.io/role/vprod/service/mounttabled)@ns.dev.v.io:8101]
+   local namespace root; can be repeated to provided multiple roots
+ -v23.permissions.file=map[]
+   specify a perms file as <name>:<permsfile>
+ -v23.permissions.literal=
+   explicitly specify the runtime perms as a JSON-encoded access.Permissions.
+   Overrides all --v23.permissions.file flags.
+ -v23.proxy=
+   object name of proxy service to use to export services across network
+   boundaries
+ -v23.tcp.address=
+   address to listen on
+ -v23.tcp.protocol=wsh
+   protocol to listen with
+ -v23.vtrace.cache-size=1024
+   The number of vtrace traces to store in memory.
+ -v23.vtrace.collect-regexp=
+   Spans and annotations that match this regular expression will trigger trace
+   collection.
+ -v23.vtrace.dump-on-shutdown=true
+   If true, dump all stored traces on runtime shutdown.
+ -v23.vtrace.sample-rate=0
+   Rate (from 0.0 to 1.0) to sample vtrace traces.
+ -vmodule=
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
+
+Stress stress - Run stress test
+
+Run stress test
+
+Usage:
+   stress stress [flags] <server> ...
+
+<server> ... A list of servers to connect to.
+
+The stress stress flags are:
+ -duration=1m0s
+   duration of the test to run
+ -format=text
+   Stats output format; either text or json
+ -max-chunk-count=1000
+   maximum number of chunks to send per streaming RPC
+ -max-payload-size=10000
+   maximum size of payload in bytes
+ -workers=1
+   number of test workers to run
+
+Stress stats - Print out stress stats of servers
+
+Print out stress stats of servers
+
+Usage:
+   stress stats [flags] <server> ...
+
+<server> ... A list of servers to connect to.
+
+The stress stats flags are:
+ -format=text
+   Stats output format; either text or json
+
+Stress load - Run load test
+
+Run load test
+
+Usage:
+   stress load [flags] <server> ...
+
+<server> ... A list of servers to connect to.
+
+The stress load flags are:
+ -cpu=0
+   number of cpu cores to use; if zero, use the number of servers to test
+ -duration=1m0s
+   duration of the test to run
+ -format=text
+   Stats output format; either text or json
+ -payload-size=1000
+   size of payload in bytes
+
+Stress stop - Stop servers
+
+Stop servers
+
+Usage:
+   stress stop <server> ...
+
+<server> ... A list of servers to stop.
+
+Stress help - Display help for commands or topics
+
+Help with no args displays the usage of the parent command.
+
+Help with args displays the usage of the specified sub-command or help topic.
+
+"help ..." recursively displays help for all commands and topics.
+
+Usage:
+   stress help [flags] [command/topic ...]
+
+[command/topic ...] optionally identifies a specific sub-command or help topic.
+
+The stress help flags are:
+ -style=compact
+   The formatting style for help output:
+      compact - Good for compact cmdline output.
+      full    - Good for cmdline output, shows all global flags.
+      godoc   - Good for godoc processing.
+   Override the default by setting the CMDLINE_STYLE environment variable.
+ -width=<terminal width>
+   Format output to this target width in runes, or unlimited if width < 0.
+   Defaults to the terminal width if available.  Override the default by setting
+   the CMDLINE_WIDTH environment variable.
+*/
+package main
diff --git a/runtime/internal/rpc/stress/stress/load.go b/runtime/internal/rpc/stress/stress/load.go
new file mode 100644
index 0000000..befbf21
--- /dev/null
+++ b/runtime/internal/rpc/stress/stress/load.go
@@ -0,0 +1,112 @@
+// 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 (
+	"encoding/json"
+	"fmt"
+	"io"
+	"runtime"
+	"time"
+
+	"v.io/v23/context"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/v23cmd"
+	"v.io/x/ref/runtime/internal/rpc/stress/internal"
+)
+
+var (
+	cpus        int
+	payloadSize int
+)
+
+func init() {
+	cmdLoadTest.Flags.DurationVar(&duration, "duration", 1*time.Minute, "duration of the test to run")
+	cmdLoadTest.Flags.IntVar(&cpus, "cpu", 0, "number of cpu cores to use; if zero, use the number of servers to test")
+	cmdLoadTest.Flags.IntVar(&payloadSize, "payload-size", 1000, "size of payload in bytes")
+	cmdLoadTest.Flags.StringVar(&outFormat, "format", "text", "Stats output format; either text or json")
+}
+
+type loadStats struct {
+	Iterations uint64
+	MsecPerRpc float64
+	Qps        float64
+	QpsPerCore float64
+}
+
+var cmdLoadTest = &cmdline.Command{
+	Runner:   v23cmd.RunnerFunc(runLoadTest),
+	Name:     "load",
+	Short:    "Run load test",
+	Long:     "Run load test",
+	ArgsName: "<server> ...",
+	ArgsLong: "<server> ... A list of servers to connect to.",
+}
+
+func runLoadTest(ctx *context.T, env *cmdline.Env, args []string) error {
+	if len(args) == 0 {
+		return env.UsageErrorf("no server specified")
+	}
+	if outFormat != "text" && outFormat != "json" {
+		return env.UsageErrorf("invalid output format: %s\n", outFormat)
+	}
+
+	cores := cpus
+	if cores == 0 {
+		cores = len(args)
+	}
+	runtime.GOMAXPROCS(cores)
+
+	fmt.Fprintf(env.Stdout, "starting load test against %d server(s) using %d core(s)...\n", len(args), cores)
+	fmt.Fprintf(env.Stdout, "payloadSize: %d, duration: %v\n", payloadSize, duration)
+
+	start := time.Now()
+	done := make(chan loadStats)
+	for _, server := range args {
+		go func(server string) {
+			var stats loadStats
+
+			start := time.Now()
+			stats.Iterations = internal.CallEcho(ctx, server, payloadSize, duration)
+			elapsed := time.Since(start)
+
+			stats.Qps = float64(stats.Iterations) / elapsed.Seconds()
+			stats.MsecPerRpc = 1000 / stats.Qps
+			done <- stats
+		}(server)
+	}
+	var merged loadStats
+	for i := 0; i < len(args); i++ {
+		stats := <-done
+		merged.Iterations += stats.Iterations
+		merged.MsecPerRpc += stats.MsecPerRpc
+		merged.Qps += stats.Qps
+	}
+	merged.MsecPerRpc /= float64(len(args))
+	merged.QpsPerCore = merged.Qps / float64(cores)
+	elapsed := time.Since(start)
+	fmt.Printf("done after %v\n", elapsed)
+	return outLoadStats(env.Stdout, outFormat, "load stats:", &merged)
+}
+
+func outLoadStats(w io.Writer, format, title string, stats *loadStats) error {
+	switch format {
+	case "text":
+		fmt.Fprintf(w, "%s\n", title)
+		fmt.Fprintf(w, "\tnumber of RPCs:\t\t%d\n", stats.Iterations)
+		fmt.Fprintf(w, "\tlatency (msec/rpc):\t%.2f\n", stats.MsecPerRpc)
+		fmt.Fprintf(w, "\tqps:\t\t\t%.2f\n", stats.Qps)
+		fmt.Fprintf(w, "\tqps/core:\t\t%.2f\n", stats.QpsPerCore)
+	case "json":
+		b, err := json.Marshal(stats)
+		if err != nil {
+			return err
+		}
+		fmt.Fprintf(w, "%s%s\n", title, b)
+	default:
+		return fmt.Errorf("invalid output format: %s\n", format)
+	}
+	return nil
+}
diff --git a/runtime/internal/rpc/stress/stress/main.go b/runtime/internal/rpc/stress/stress/main.go
new file mode 100644
index 0000000..03d3950
--- /dev/null
+++ b/runtime/internal/rpc/stress/stress/main.go
@@ -0,0 +1,53 @@
+// 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.
+
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go .
+
+package main
+
+import (
+	"v.io/v23/context"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/v23cmd"
+	_ "v.io/x/ref/runtime/factories/static"
+	"v.io/x/ref/runtime/internal/rpc/stress"
+)
+
+var cmdStopServers = &cmdline.Command{
+	Runner:   v23cmd.RunnerFunc(runStopServers),
+	Name:     "stop",
+	Short:    "Stop servers",
+	Long:     "Stop servers",
+	ArgsName: "<server> ...",
+	ArgsLong: "<server> ... A list of servers to stop.",
+}
+
+func runStopServers(ctx *context.T, env *cmdline.Env, args []string) error {
+	if len(args) == 0 {
+		return env.UsageErrorf("no server specified")
+	}
+	for _, server := range args {
+		if err := stress.StressClient(server).Stop(ctx); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func main() {
+	cmdRoot := &cmdline.Command{
+		Name:  "stress",
+		Short: "Tool to stress/load test RPC",
+		Long:  "Command stress is a tool to stress/load test RPC by issuing randomly generated requests.",
+		Children: []*cmdline.Command{
+			cmdStressTest,
+			cmdStressStats,
+			cmdLoadTest,
+			cmdStopServers,
+		},
+	}
+	cmdline.HideGlobalFlagsExcept()
+	cmdline.Main(cmdRoot)
+}
diff --git a/runtime/internal/rpc/stress/stress/stress.go b/runtime/internal/rpc/stress/stress/stress.go
new file mode 100644
index 0000000..41b6238
--- /dev/null
+++ b/runtime/internal/rpc/stress/stress/stress.go
@@ -0,0 +1,149 @@
+// 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 (
+	"encoding/json"
+	"fmt"
+	"io"
+	"math/rand"
+	"runtime"
+	"time"
+
+	"v.io/v23/context"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/v23cmd"
+	"v.io/x/ref/runtime/internal/rpc/stress"
+	"v.io/x/ref/runtime/internal/rpc/stress/internal"
+)
+
+var (
+	duration time.Duration
+
+	workers        int
+	maxChunkCnt    int
+	maxPayloadSize int
+
+	outFormat string
+)
+
+func init() {
+	cmdStressTest.Flags.DurationVar(&duration, "duration", 1*time.Minute, "duration of the test to run")
+	cmdStressTest.Flags.IntVar(&workers, "workers", 1, "number of test workers to run")
+	cmdStressTest.Flags.IntVar(&maxChunkCnt, "max-chunk-count", 1000, "maximum number of chunks to send per streaming RPC")
+	cmdStressTest.Flags.IntVar(&maxPayloadSize, "max-payload-size", 10000, "maximum size of payload in bytes")
+	cmdStressTest.Flags.StringVar(&outFormat, "format", "text", "Stats output format; either text or json")
+
+	cmdStressStats.Flags.StringVar(&outFormat, "format", "text", "Stats output format; either text or json")
+}
+
+var cmdStressTest = &cmdline.Command{
+	Runner:   v23cmd.RunnerFunc(runStressTest),
+	Name:     "stress",
+	Short:    "Run stress test",
+	Long:     "Run stress test",
+	ArgsName: "<server> ...",
+	ArgsLong: "<server> ... A list of servers to connect to.",
+}
+
+func runStressTest(ctx *context.T, env *cmdline.Env, args []string) error {
+	if len(args) == 0 {
+		return env.UsageErrorf("no server specified")
+	}
+	if outFormat != "text" && outFormat != "json" {
+		return env.UsageErrorf("invalid output format: %s\n", outFormat)
+	}
+
+	cores := runtime.NumCPU()
+	runtime.GOMAXPROCS(cores)
+	rand.Seed(time.Now().UnixNano())
+	fmt.Fprintf(env.Stdout, "starting stress test against %d server(s) using %d core(s)...\n", len(args), cores)
+	fmt.Fprintf(env.Stdout, "workers: %d, maxChunkCnt: %d, maxPayloadSize: %d, duration: %v\n", workers, maxChunkCnt, maxPayloadSize, duration)
+
+	start := time.Now()
+	done := make(chan stress.SumStats)
+	for i := 0; i < workers; i++ {
+		go func() {
+			var stats stress.SumStats
+			timeout := time.After(duration)
+		done:
+			for {
+				server := args[rand.Intn(len(args))]
+				if rand.Intn(2) == 0 {
+					internal.CallSum(ctx, server, maxPayloadSize, &stats)
+				} else {
+					internal.CallSumStream(ctx, server, maxChunkCnt, maxPayloadSize, &stats)
+				}
+
+				select {
+				case <-timeout:
+					break done
+				default:
+				}
+			}
+			done <- stats
+		}()
+	}
+	var merged stress.SumStats
+	for i := 0; i < workers; i++ {
+		stats := <-done
+		merged.SumCount += stats.SumCount
+		merged.SumStreamCount += stats.SumStreamCount
+		merged.BytesRecv += stats.BytesRecv
+		merged.BytesSent += stats.BytesSent
+	}
+	elapsed := time.Since(start)
+	fmt.Printf("done after %v\n", elapsed)
+	return outSumStats(env.Stdout, outFormat, "client stats:", &merged)
+}
+
+var cmdStressStats = &cmdline.Command{
+	Runner:   v23cmd.RunnerFunc(runStressStats),
+	Name:     "stats",
+	Short:    "Print out stress stats of servers",
+	Long:     "Print out stress stats of servers",
+	ArgsName: "<server> ...",
+	ArgsLong: "<server> ... A list of servers to connect to.",
+}
+
+func runStressStats(ctx *context.T, env *cmdline.Env, args []string) error {
+	if len(args) == 0 {
+		return env.UsageErrorf("no server specified")
+	}
+	if outFormat != "text" && outFormat != "json" {
+		return env.UsageErrorf("invalid output format: %s\n", outFormat)
+	}
+	for _, server := range args {
+		stats, err := stress.StressClient(server).GetSumStats(ctx)
+		if err != nil {
+			return err
+		}
+		title := fmt.Sprintf("server stats(%s):", server)
+		if err := outSumStats(env.Stdout, outFormat, title, &stats); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func outSumStats(w io.Writer, format, title string, stats *stress.SumStats) error {
+	switch format {
+	case "text":
+		fmt.Fprintf(w, "%s\n", title)
+		fmt.Fprintf(w, "\tnumber of non-streaming RPCs:\t%d\n", stats.SumCount)
+		fmt.Fprintf(w, "\tnumber of streaming RPCs:\t%d\n", stats.SumStreamCount)
+		fmt.Fprintf(w, "\tnumber of bytes received:\t%d\n", stats.BytesRecv)
+		fmt.Fprintf(w, "\tnumber of bytes sent:\t\t%d\n", stats.BytesSent)
+	case "json":
+		b, err := json.Marshal(stats)
+		if err != nil {
+			return err
+		}
+		fmt.Fprintf(w, "%s%s\n", title, b)
+	default:
+		return fmt.Errorf("invalid output format: %s\n", format)
+	}
+	return nil
+}
diff --git a/runtime/internal/rpc/stress/stressd/doc.go b/runtime/internal/rpc/stress/stressd/doc.go
new file mode 100644
index 0000000..ece1767
--- /dev/null
+++ b/runtime/internal/rpc/stress/stressd/doc.go
@@ -0,0 +1,67 @@
+// 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
+
+/*
+Command stressd runs the stress-test server.
+
+Usage:
+   stressd [flags]
+
+The stressd flags are:
+ -duration=0
+   Duration of the stress test to run; if zero, there is no limit.
+
+The global flags are:
+ -alsologtostderr=true
+   log to standard error as well as files
+ -log_backtrace_at=:0
+   when logging hits line file:N, emit a stack trace
+ -log_dir=
+   if non-empty, write log files to this directory
+ -logtostderr=false
+   log to standard error instead of files
+ -max_stack_buf_size=4292608
+   max size in bytes of the buffer to use for logging stack traces
+ -metadata=<just specify -metadata to activate>
+   Displays metadata for the program and exits.
+ -stderrthreshold=2
+   logs at or above this threshold go to stderr
+ -v=0
+   log level for V logs
+ -v23.credentials=
+   directory to use for storing security credentials
+ -v23.i18n-catalogue=
+   18n catalogue files to load, comma separated
+ -v23.namespace.root=[/(dev.v.io/role/vprod/service/mounttabled)@ns.dev.v.io:8101]
+   local namespace root; can be repeated to provided multiple roots
+ -v23.permissions.file=map[]
+   specify a perms file as <name>:<permsfile>
+ -v23.permissions.literal=
+   explicitly specify the runtime perms as a JSON-encoded access.Permissions.
+   Overrides all --v23.permissions.file flags.
+ -v23.proxy=
+   object name of proxy service to use to export services across network
+   boundaries
+ -v23.tcp.address=
+   address to listen on
+ -v23.tcp.protocol=wsh
+   protocol to listen with
+ -v23.vtrace.cache-size=1024
+   The number of vtrace traces to store in memory.
+ -v23.vtrace.collect-regexp=
+   Spans and annotations that match this regular expression will trigger trace
+   collection.
+ -v23.vtrace.dump-on-shutdown=true
+   If true, dump all stored traces on runtime shutdown.
+ -v23.vtrace.sample-rate=0
+   Rate (from 0.0 to 1.0) to sample vtrace traces.
+ -vmodule=
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
+*/
+package main
diff --git a/runtime/internal/rpc/stress/stressd/main.go b/runtime/internal/rpc/stress/stressd/main.go
new file mode 100644
index 0000000..1adbceb
--- /dev/null
+++ b/runtime/internal/rpc/stress/stressd/main.go
@@ -0,0 +1,59 @@
+// 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.
+
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go . -help
+
+package main
+
+import (
+	"runtime"
+	"time"
+
+	"v.io/v23/context"
+	"v.io/v23/security"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/signals"
+	"v.io/x/ref/lib/v23cmd"
+	"v.io/x/ref/lib/xrpc"
+	_ "v.io/x/ref/runtime/factories/static"
+	"v.io/x/ref/runtime/internal/rpc/stress/internal"
+)
+
+var duration time.Duration
+
+func main() {
+	cmdRoot.Flags.DurationVar(&duration, "duration", 0, "Duration of the stress test to run; if zero, there is no limit.")
+	cmdline.HideGlobalFlagsExcept()
+	cmdline.Main(cmdRoot)
+}
+
+var cmdRoot = &cmdline.Command{
+	Runner: v23cmd.RunnerFunc(runStressD),
+	Name:   "stressd",
+	Short:  "Run the stress-test server",
+	Long:   "Command stressd runs the stress-test server.",
+}
+
+func runStressD(ctx *context.T, env *cmdline.Env, args []string) error {
+	runtime.GOMAXPROCS(runtime.NumCPU())
+
+	service, stop := internal.NewService()
+	server, err := xrpc.NewServer(ctx, "", service, security.AllowEveryone())
+	if err != nil {
+		ctx.Fatalf("NewServer failed: %v", err)
+	}
+	ctx.Infof("listening on %s", server.Status().Endpoints[0].Name())
+
+	var timeout <-chan time.Time
+	if duration > 0 {
+		timeout = time.After(duration)
+	}
+	select {
+	case <-timeout:
+	case <-stop:
+	case <-signals.ShutdownOnSignals(ctx):
+	}
+	return nil
+}
diff --git a/runtime/internal/rpc/test/client_test.go b/runtime/internal/rpc/test/client_test.go
new file mode 100644
index 0000000..3f1c875
--- /dev/null
+++ b/runtime/internal/rpc/test/client_test.go
@@ -0,0 +1,976 @@
+// 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 test
+
+import (
+	"fmt"
+	"net"
+	"os"
+	"path/filepath"
+	"runtime"
+	"strings"
+	"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/v23/vdlroot/signature"
+	"v.io/v23/verror"
+
+	"v.io/x/ref"
+	"v.io/x/ref/internal/logger"
+	"v.io/x/ref/lib/xrpc"
+	_ "v.io/x/ref/runtime/factories/generic"
+	inaming "v.io/x/ref/runtime/internal/naming"
+	irpc "v.io/x/ref/runtime/internal/rpc"
+	"v.io/x/ref/runtime/internal/rpc/stream/message"
+	"v.io/x/ref/runtime/internal/testing/mocks/mocknet"
+	"v.io/x/ref/services/mounttable/mounttablelib"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/expect"
+	"v.io/x/ref/test/modules"
+	"v.io/x/ref/test/testutil"
+)
+
+//go:generate v23 test generate .
+
+var rootMT = modules.Register(func(env *modules.Env, args ...string) error {
+	seclevel := options.SecurityConfidential
+	if len(args) == 1 && args[0] == "nosec" {
+		seclevel = options.SecurityNone
+	}
+	return runRootMT(seclevel, env, args...)
+}, "rootMT")
+
+func runRootMT(seclevel options.SecurityLevel, env *modules.Env, args ...string) error {
+	ctx, shutdown := v23.Init()
+	defer shutdown()
+	mt, err := mounttablelib.NewMountTableDispatcher(ctx, "", "", "mounttable")
+	if err != nil {
+		return fmt.Errorf("mounttablelib.NewMountTableDispatcher failed: %s", err)
+	}
+	server, err := xrpc.NewDispatchingServer(ctx, "", mt, options.ServesMountTable(true), seclevel)
+	if err != nil {
+		return fmt.Errorf("root failed: %v", err)
+	}
+	fmt.Fprintf(env.Stdout, "PID=%d\n", os.Getpid())
+	for _, ep := range server.Status().Endpoints {
+		fmt.Fprintf(env.Stdout, "MT_NAME=%s\n", ep.Name())
+	}
+	modules.WaitForEOF(env.Stdin)
+	return nil
+}
+
+type treeDispatcher struct{ id string }
+
+func (d treeDispatcher) Lookup(_ *context.T, suffix string) (interface{}, security.Authorizer, error) {
+	return &echoServerObject{d.id, suffix}, nil, nil
+}
+
+type echoServerObject struct {
+	id, suffix string
+}
+
+func (es *echoServerObject) Echo(_ *context.T, _ rpc.ServerCall, m string) (string, error) {
+	if len(es.suffix) > 0 {
+		return fmt.Sprintf("%s.%s: %s\n", es.id, es.suffix, m), nil
+	}
+	return fmt.Sprintf("%s: %s\n", es.id, m), nil
+}
+
+func (es *echoServerObject) Sleep(_ *context.T, _ rpc.ServerCall, d string) error {
+	duration, err := time.ParseDuration(d)
+	if err != nil {
+		return err
+	}
+	time.Sleep(duration)
+	return nil
+}
+
+var echoServer = modules.Register(func(env *modules.Env, args ...string) error {
+	ctx, shutdown := v23.Init()
+	defer shutdown()
+
+	id, mp := args[0], args[1]
+	disp := &treeDispatcher{id: id}
+	server, err := xrpc.NewDispatchingServer(ctx, mp, disp)
+	if err != nil {
+		return err
+	}
+	fmt.Fprintf(env.Stdout, "PID=%d\n", os.Getpid())
+	for _, ep := range server.Status().Endpoints {
+		fmt.Fprintf(env.Stdout, "NAME=%s\n", ep.Name())
+	}
+	modules.WaitForEOF(env.Stdin)
+	return nil
+}, "echoServer")
+
+var echoClient = modules.Register(func(env *modules.Env, args ...string) error {
+	ctx, shutdown := v23.Init()
+	defer shutdown()
+
+	name := args[0]
+	args = args[1:]
+	client := v23.GetClient(ctx)
+	for _, a := range args {
+		var r string
+		if err := client.Call(ctx, name, "Echo", []interface{}{a}, []interface{}{&r}); err != nil {
+			return err
+		}
+		fmt.Fprintf(env.Stdout, r)
+	}
+	return nil
+}, "echoClient")
+
+func runMountTable(t *testing.T, ctx *context.T, args ...string) (*modules.Shell, func()) {
+	sh, err := modules.NewShell(ctx, nil, testing.Verbose(), t)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	root, err := sh.Start(nil, rootMT, args...)
+	if err != nil {
+		t.Fatalf("unexpected error for root mt: %s", err)
+	}
+	deferFn := func() {
+		sh.Cleanup(os.Stderr, os.Stderr)
+	}
+
+	root.ExpectVar("PID")
+	rootName := root.ExpectVar("MT_NAME")
+	if len(rootName) == 0 {
+		root.Shutdown(nil, os.Stderr)
+	}
+
+	sh.SetVar(ref.EnvNamespacePrefix, rootName)
+	if err = v23.GetNamespace(ctx).SetRoots(rootName); err != nil {
+		t.Fatalf("unexpected error setting namespace roots: %s", err)
+	}
+
+	return sh, deferFn
+}
+
+func runClient(t *testing.T, sh *modules.Shell) error {
+	clt, err := sh.Start(nil, echoClient, "echoServer", "a message")
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	s := expect.NewSession(t, clt.Stdout(), 30*time.Second)
+	s.Expect("echoServer: a message")
+	if s.Failed() {
+		return s.Error()
+	}
+	return nil
+}
+
+func numServers(t *testing.T, ctx *context.T, name string, expected int) int {
+	for {
+		me, err := v23.GetNamespace(ctx).Resolve(ctx, name)
+		if err == nil && len(me.Servers) == expected {
+			return expected
+		}
+		time.Sleep(10 * time.Millisecond)
+	}
+}
+
+// TODO(cnicolaou): figure out how to test and see what the internals
+// of tryCall are doing - e.g. using stats counters.
+func TestMultipleEndpoints(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	sh, fn := runMountTable(t, ctx)
+	defer fn()
+	srv, err := sh.Start(nil, echoServer, "echoServer", "echoServer")
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	s := expect.NewSession(t, srv.Stdout(), time.Minute)
+	s.ExpectVar("PID")
+	s.ExpectVar("NAME")
+
+	// Verify that there are 1 entries for echoServer in the mount table.
+	if got, want := numServers(t, ctx, "echoServer", 1), 1; got != want {
+		t.Fatalf("got: %d, want: %d", got, want)
+	}
+
+	runClient(t, sh)
+
+	// Create a fake set of 100 entries in the mount table
+	for i := 0; i < 100; i++ {
+		// 203.0.113.0 is TEST-NET-3 from RFC5737
+		ep := naming.FormatEndpoint("tcp", fmt.Sprintf("203.0.113.%d:443", i))
+		n := naming.JoinAddressName(ep, "")
+		if err := v23.GetNamespace(ctx).Mount(ctx, "echoServer", n, time.Hour); err != nil {
+			t.Fatalf("unexpected error: %s", err)
+		}
+	}
+
+	// Verify that there are 101 entries for echoServer in the mount table.
+	if got, want := numServers(t, ctx, "echoServer", 101), 101; got != want {
+		t.Fatalf("got: %q, want: %q", got, want)
+	}
+
+	// TODO(cnicolaou): ok, so it works, but I'm not sure how
+	// long it should take or if the parallel connection code
+	// really works. Use counters to inspect it for example.
+	if err := runClient(t, sh); err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+
+	srv.CloseStdin()
+	srv.Shutdown(nil, nil)
+
+	// Verify that there are 100 entries for echoServer in the mount table.
+	if got, want := numServers(t, ctx, "echoServer", 100), 100; got != want {
+		t.Fatalf("got: %d, want: %d", got, want)
+	}
+}
+
+func TestTimeout(t *testing.T) {
+	t.Skip("https://github.com/vanadium/issues/issues/650")
+
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	client := v23.GetClient(ctx)
+	ctx, _ = context.WithTimeout(ctx, 100*time.Millisecond)
+	name := naming.JoinAddressName(naming.FormatEndpoint("tcp", "203.0.113.10:443"), "")
+	_, err := client.StartCall(ctx, name, "echo", []interface{}{"args don't matter"})
+	t.Log(err)
+	if verror.ErrorID(err) != verror.ErrTimeout.ID {
+		t.Fatalf("wrong error: %s", err)
+	}
+}
+
+func logErrors(t *testing.T, msg string, logerr, logstack, debugString bool, err error) {
+	_, file, line, _ := runtime.Caller(2)
+	loc := fmt.Sprintf("%s:%d", filepath.Base(file), line)
+	if logerr {
+		t.Logf("%s: %s: %v", loc, msg, err)
+	}
+	if logstack {
+		t.Logf("%s: %s: %v", loc, msg, verror.Stack(err).String())
+	}
+	if debugString {
+		t.Logf("%s: %s: %v", loc, msg, verror.DebugString(err))
+	}
+}
+
+func TestStartCallErrors(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	client := v23.GetClient(ctx)
+
+	ns := v23.GetNamespace(ctx)
+
+	logErr := func(msg string, err error) {
+		logErrors(t, msg, true, false, false, err)
+	}
+
+	emptyCtx := &context.T{}
+	emptyCtx = context.WithLogger(emptyCtx, logger.Global())
+	_, err := client.StartCall(emptyCtx, "noname", "nomethod", nil)
+	if verror.ErrorID(err) != verror.ErrBadArg.ID {
+		t.Fatalf("wrong error: %s", err)
+	}
+	logErr("no context", err)
+
+	// TODO(mattr): This test doesn't pass because xclient doesn't look for this
+	// error.
+	// p1 := options.ServerPublicKey{PublicKey: testutil.NewPrincipal().PublicKey()}
+	// p2 := options.ServerPublicKey{PublicKey: testutil.NewPrincipal().PublicKey()}
+	// _, err = client.StartCall(ctx, "noname", "nomethod", nil, p1, p2)
+	// if verror.ErrorID(err) != verror.ErrBadArg.ID {
+	// 	t.Fatalf("wrong error: %s", err)
+	// }
+	// logErr("too many public keys", err)
+
+	// This will fail with NoServers, but because there is no mount table
+	// to communicate with. The error message should include a
+	// 'connection refused' string.
+	ns.SetRoots("/127.0.0.1:8101")
+	_, err = client.StartCall(ctx, "noname", "nomethod", nil, options.NoRetry{})
+	if verror.ErrorID(err) != verror.ErrNoServers.ID {
+		t.Fatalf("wrong error: %s", err)
+	}
+	if want := "connection refused"; !strings.Contains(verror.DebugString(err), want) {
+		t.Fatalf("wrong error: %s - doesn't contain %q", err, want)
+	}
+	logErr("no mount table", err)
+
+	// This will fail with NoServers, but because there really is no
+	// name registered with the mount table.
+	_, shutdown = runMountTable(t, ctx)
+	defer shutdown()
+	_, err = client.StartCall(ctx, "noname", "nomethod", nil, options.NoRetry{})
+	if verror.ErrorID(err) != verror.ErrNoServers.ID {
+		t.Fatalf("wrong error: %s", err)
+	}
+	if unwanted := "connection refused"; strings.Contains(err.Error(), unwanted) {
+		t.Fatalf("wrong error: %s - does contain %q", err, unwanted)
+	}
+	logErr("no name registered", err)
+
+	// The following tests will fail with NoServers, but because there are
+	// no protocols that the client and servers (mount table, and "name") share.
+	nctx, nclient, err := v23.WithNewClient(ctx, irpc.PreferredProtocols([]string{"wsh"}))
+
+	addr := naming.FormatEndpoint("nope", "127.0.0.1:1081")
+	if err := ns.Mount(ctx, "name", addr, time.Minute); err != nil {
+		t.Fatal(err)
+	}
+
+	// This will fail in its attempt to call ResolveStep to the mount table
+	// because we are using both the new context and the new client.
+	_, err = nclient.StartCall(nctx, "name", "nomethod", nil, options.NoRetry{})
+	if verror.ErrorID(err) != verror.ErrNoServers.ID {
+		t.Fatalf("wrong error: %s", err)
+	}
+	if want := "ResolveStep"; !strings.Contains(err.Error(), want) {
+		t.Fatalf("wrong error: %s - doesn't contain %q", err, want)
+	}
+	logErr("mismatched protocols", err)
+
+	// This will fail in its attempt to invoke the actual RPC because
+	// we are using the old context (which supplies the context for the calls
+	// to ResolveStep) and the new client.
+	_, err = nclient.StartCall(ctx, "name", "nomethod", nil, options.NoRetry{})
+	if verror.ErrorID(err) != verror.ErrNoServers.ID {
+		t.Fatalf("wrong error: %s", err)
+	}
+	if want := "nope"; !strings.Contains(err.Error(), want) {
+		t.Fatalf("wrong error: %s - doesn't contain %q", err, want)
+	}
+	if unwanted := "ResolveStep"; strings.Contains(err.Error(), unwanted) {
+		t.Fatalf("wrong error: %s - does contain %q", err, unwanted)
+
+	}
+	logErr("mismatched protocols", err)
+
+	// The following two tests will fail due to a timeout.
+	ns.SetRoots("/203.0.113.10:8101")
+	nctx, _ = context.WithTimeout(ctx, 100*time.Millisecond)
+	// This call will timeout talking to the mount table.
+	call, err := client.StartCall(nctx, "name", "noname", nil, options.NoRetry{})
+	if verror.ErrorID(err) != verror.ErrTimeout.ID {
+		t.Fatalf("wrong error: %s", err)
+	}
+	if call != nil {
+		t.Fatalf("expected call to be nil")
+	}
+	logErr("timeout to mount table", err)
+
+	// TODO(mattr): This test is temporarily disabled.  It's very flaky
+	// and we're about to move to a new version of the client.  We will
+	// fix it in the new client.
+	// This, second test, will fail due a timeout contacting the server itself.
+	// addr = naming.FormatEndpoint("tcp", "203.0.113.10:8101")
+
+	// nctx, _ = context.WithTimeout(ctx, 100*time.Millisecond)
+	// new_name := naming.JoinAddressName(addr, "")
+	// call, err = client.StartCall(nctx, new_name, "noname", nil, options.NoRetry{})
+	// if verror.ErrorID(err) != verror.ErrTimeout.ID {
+	// 	t.Fatalf("wrong error: %s", err)
+	// }
+	// if call != nil {
+	// 	t.Fatalf("expected call to be nil")
+	// }
+	// logErr("timeout to server", err)
+}
+
+func dropDataDialer(ctx *context.T, network, address string, timeout time.Duration) (net.Conn, error) {
+	matcher := func(read bool, msg message.T) bool {
+		// Drop and close the connection when reading the first data message.
+		if _, ok := msg.(*message.Data); ok && read {
+			return true
+		}
+		return false
+	}
+	opts := mocknet.Opts{
+		Mode:              mocknet.V23CloseAtMessage,
+		V23MessageMatcher: matcher,
+	}
+	return mocknet.DialerWithOpts(opts, network, address, timeout)
+}
+
+func simpleResolver(ctx *context.T, network, address string) (string, string, error) {
+	return network, address, nil
+}
+
+func TestStartCallBadProtocol(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	client := v23.GetClient(ctx)
+
+	logErr := func(msg string, err error) {
+		logErrors(t, msg, true, false, false, err)
+	}
+
+	// TODO(mattr): This test is temporarily disabled.  Mocknet doesn't understand the
+	// new protocol yet, and the new protocol doesn't have a SecurityNone option.
+	// We will need to remedy both issues.
+	// ns := v23.GetNamespace(ctx)
+	// simpleListen := func(ctx *context.T, protocol, address string) (net.Listener, error) {
+	// 	return net.Listen(protocol, address)
+	// }
+	// rpc.RegisterProtocol("dropData", dropDataDialer, simpleResolver, simpleListen)
+	// // The following test will fail due to a broken connection.
+	// // We need to run mount table and servers with no security to use
+	// // the V23CloseAtMessage net.Conn mock.
+	// _, shutdown = runMountTable(t, ctx, "nosec")
+	// defer shutdown()
+	// roots := ns.Roots()
+	// brkRoot, err := mocknet.RewriteEndpointProtocol(roots[0], "dropData")
+	// if err != nil {
+	// 	t.Fatal(err)
+	// }
+	// ns.SetRoots(brkRoot.Name())
+	// nctx, _ := context.WithTimeout(ctx, 5*time.Second)
+	// call, err := client.StartCall(nctx, "name", "noname", nil, options.NoRetry{}, options.SecurityNone)
+	// if verror.ErrorID(err) != verror.ErrNoServers.ID {
+	// 	t.Errorf("wrong error: %s", verror.DebugString(err))
+	// }
+	// if call != nil {
+	// 	t.Errorf("expected call to be nil")
+	// }
+	// logErr("broken connection", err)
+
+	// The following test will fail with because the client will set up
+	// a secure connection to a server that isn't expecting one.
+	nctx, cancel := context.WithTimeout(ctx, 5*time.Second)
+	defer cancel()
+	name, fn := initServer(t, ctx, options.SecurityNone)
+	defer fn()
+	call, err := client.StartCall(nctx, name, "noname", nil, options.NoRetry{})
+	if verror.ErrorID(err) != verror.ErrBadProtocol.ID {
+		t.Errorf("wrong error: %s", err)
+	}
+	if call != nil {
+		t.Errorf("expected call to be nil")
+	}
+	logErr("insecure server", err)
+
+	// This is the inverse, secure server, insecure client
+	name, fn = initServer(t, ctx)
+	defer fn()
+	call, err = client.StartCall(nctx, name, "noname", nil, options.NoRetry{}, options.SecurityNone)
+	if verror.ErrorID(err) != verror.ErrBadProtocol.ID {
+		t.Errorf("wrong error: %s", err)
+	}
+	if call != nil {
+		t.Errorf("expected call to be nil")
+	}
+	logErr("insecure client", err)
+}
+
+func TestStartCallSecurity(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	client := v23.GetClient(ctx)
+
+	logErr := func(msg string, err error) {
+		logErrors(t, msg, true, false, false, err)
+	}
+
+	name, fn := initServer(t, ctx)
+	defer fn()
+
+	// Create a context with a new principal that doesn't match the server,
+	// so that the client will not trust the server.
+	ctx1, err := v23.WithPrincipal(ctx, testutil.NewPrincipal("test-blessing"))
+	if err != nil {
+		t.Fatal(err)
+	}
+	call, err := client.StartCall(ctx1, name, "noname", nil, options.NoRetry{})
+	if verror.ErrorID(err) != verror.ErrNotTrusted.ID {
+		t.Fatalf("wrong error: %s", err)
+	}
+	if call != nil {
+		t.Fatalf("expected call to be nil")
+	}
+	logErr("client does not trust server", err)
+}
+
+var childPing = modules.Register(func(env *modules.Env, args ...string) error {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	name := args[0]
+	got := ""
+	if err := v23.GetClient(ctx).Call(ctx, name, "Ping", nil, []interface{}{&got}); err != nil {
+		return fmt.Errorf("unexpected error: %s", err)
+	}
+	fmt.Fprintf(env.Stdout, "RESULT=%s\n", got)
+	return nil
+}, "childPing")
+
+func initServer(t *testing.T, ctx *context.T, opts ...rpc.ServerOpt) (string, func()) {
+	done := make(chan struct{})
+	server, err := xrpc.NewServer(ctx, "", &simple{done}, nil, opts...)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	return server.Status().Endpoints[0].Name(), func() { close(done) }
+}
+
+func TestTimeoutResponse(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	name, fn := initServer(t, ctx)
+	defer fn()
+
+	ctx, cancel := context.WithTimeout(ctx, time.Millisecond)
+	err := v23.GetClient(ctx).Call(ctx, name, "Sleep", nil, nil)
+	if got, want := verror.ErrorID(err), verror.ErrTimeout.ID; got != want {
+		t.Fatalf("got %v, want %v", got, want)
+	}
+	cancel()
+}
+
+func TestArgsAndResponses(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	name, fn := initServer(t, ctx)
+	defer fn()
+
+	call, err := v23.GetClient(ctx).StartCall(ctx, name, "Sleep", []interface{}{"too many args"})
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	err = call.Finish()
+	if got, want := verror.ErrorID(err), verror.ErrBadProtocol.ID; got != want {
+		t.Fatalf("got %v, want %v", got, want)
+	}
+
+	call, err = v23.GetClient(ctx).StartCall(ctx, name, "Ping", nil)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	pong := ""
+	dummy := ""
+	err = call.Finish(&pong, &dummy)
+	if got, want := verror.ErrorID(err), verror.ErrBadProtocol.ID; got != want {
+		t.Fatalf("got %v, want %v", got, want)
+	}
+}
+
+func TestAccessDenied(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	name, fn := initServer(t, ctx)
+	defer fn()
+
+	ctx1, err := v23.WithPrincipal(ctx, testutil.NewPrincipal("test-blessing"))
+	// Client must recognize the server, otherwise it won't even send the request.
+	v23.GetPrincipal(ctx1).AddToRoots(v23.GetPrincipal(ctx).BlessingStore().Default())
+	if err != nil {
+		t.Fatal(err)
+	}
+	call, err := v23.GetClient(ctx1).StartCall(ctx1, name, "Sleep", nil)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	err = call.Finish()
+	if got, want := verror.ErrorID(err), verror.ErrNoAccess.ID; got != want {
+		t.Fatalf("got %v, want %v", got, want)
+	}
+}
+
+func TestCanceledBeforeFinish(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	name, fn := initServer(t, ctx)
+	defer fn()
+
+	ctx, cancel := context.WithCancel(ctx)
+	call, err := v23.GetClient(ctx).StartCall(ctx, name, "Sleep", nil)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	// Cancel before we call finish.
+	cancel()
+	err = call.Finish()
+	if got, want := verror.ErrorID(err), verror.ErrCanceled.ID; got != want {
+		t.Fatalf("got %v, want %v", got, want)
+	}
+}
+
+func TestCanceledDuringFinish(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	name, fn := initServer(t, ctx)
+	defer fn()
+
+	ctx, cancel := context.WithCancel(ctx)
+	call, err := v23.GetClient(ctx).StartCall(ctx, name, "Sleep", nil)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	// Cancel whilst the RPC is running.
+	go func() {
+		time.Sleep(100 * time.Millisecond)
+		cancel()
+	}()
+	err = call.Finish()
+	if got, want := verror.ErrorID(err), verror.ErrCanceled.ID; got != want {
+		t.Fatalf("got %v, want %v", got, want)
+	}
+}
+
+func TestRendezvous(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	sh, fn := runMountTable(t, ctx)
+	defer fn()
+
+	name := "echoServer"
+
+	// We start the client before we start the server, StartCall will reresolve
+	// the name until it finds an entry or times out after an exponential
+	// backoff of some minutes.
+	startServer := func() {
+		time.Sleep(100 * time.Millisecond)
+		srv, _ := sh.Start(nil, echoServer, "message", name)
+		s := expect.NewSession(t, srv.Stdout(), time.Minute)
+		s.ExpectVar("PID")
+		s.ExpectVar("NAME")
+	}
+	go startServer()
+
+	call, err := v23.GetClient(ctx).StartCall(ctx, name, "Echo", []interface{}{"hello"})
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+
+	response := ""
+	if err := call.Finish(&response); err != nil {
+		if got, want := verror.ErrorID(err), verror.ErrCanceled.ID; got != want {
+			t.Fatalf("got %v, want %v", got, want)
+		}
+	}
+	if got, want := response, "message: hello\n"; got != want {
+		t.Errorf("got %q, want %q", got, want)
+	}
+}
+
+func TestCallback(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	sh, fn := runMountTable(t, ctx)
+	defer fn()
+
+	name, fn := initServer(t, ctx)
+	defer fn()
+
+	srv, err := sh.Start(nil, childPing, name)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	s := expect.NewSession(t, srv.Stdout(), time.Minute)
+	if got, want := s.ExpectVar("RESULT"), "pong"; got != want {
+		t.Errorf("got %q, want %q", got, want)
+	}
+}
+
+func TestStreamTimeout(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	name, fn := initServer(t, ctx)
+	defer fn()
+
+	want := 10
+	ctx, _ = context.WithTimeout(ctx, 300*time.Millisecond)
+	call, err := v23.GetClient(ctx).StartCall(ctx, name, "Source", []interface{}{want})
+	if err != nil {
+		if verror.ErrorID(err) != verror.ErrTimeout.ID {
+			t.Fatalf("verror should be a timeout not %s: stack %s",
+				err, verror.Stack(err))
+		}
+		return
+	}
+
+	for {
+		got := 0
+		err := call.Recv(&got)
+		if err == nil {
+			if got != want {
+				t.Fatalf("got %d, want %d", got, want)
+			}
+			want++
+			continue
+		}
+		if got, want := verror.ErrorID(err), verror.ErrTimeout.ID; got != want {
+			t.Fatalf("got %v, want %v", got, want)
+		}
+		break
+	}
+	err = call.Finish()
+	if got, want := verror.ErrorID(err), verror.ErrTimeout.ID; got != want {
+		t.Fatalf("got %v, want %v", got, want)
+	}
+}
+
+func TestStreamAbort(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	name, fn := initServer(t, ctx)
+	defer fn()
+
+	call, err := v23.GetClient(ctx).StartCall(ctx, name, "Sink", nil)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+
+	want := 10
+	for i := 0; i <= want; i++ {
+		if err := call.Send(i); err != nil {
+			t.Fatalf("unexpected error: %s", err)
+		}
+	}
+	call.CloseSend()
+	err = call.Send(100)
+	if got, want := verror.ErrorID(err), verror.ErrAborted.ID; got != want {
+		t.Fatalf("got %v, want %v", got, want)
+	}
+
+	result := 0
+	err = call.Finish(&result)
+	if err != nil {
+		t.Errorf("unexpected error: %#v", err)
+	}
+	if got := result; got != want {
+		t.Errorf("got %d, want %d", got, want)
+	}
+}
+
+func TestNoServersAvailable(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	_, fn := runMountTable(t, ctx)
+	defer fn()
+	name := "noservers"
+	call, err := v23.GetClient(ctx).StartCall(ctx, name, "Sleep", nil, options.NoRetry{})
+	if err != nil {
+		if got, want := verror.ErrorID(err), verror.ErrNoServers.ID; got != want {
+			t.Fatalf("got %v, want %v", got, want)
+		}
+		return
+	}
+	err = call.Finish()
+	if got, want := verror.ErrorID(err), verror.ErrNoServers.ID; got != want {
+		t.Fatalf("got %v, want %v", got, want)
+	}
+
+}
+
+func TestNoMountTable(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	v23.GetNamespace(ctx).SetRoots()
+	name := "a_mount_table_entry"
+
+	// If there is no mount table, then we'll get a NoServers error message.
+	ctx, _ = context.WithTimeout(ctx, 300*time.Millisecond)
+	_, err := v23.GetClient(ctx).StartCall(ctx, name, "Sleep", nil)
+	if got, want := verror.ErrorID(err), verror.ErrNoServers.ID; got != want {
+		t.Fatalf("got %v, want %v", got, want)
+	}
+}
+
+// TestReconnect verifies that the client transparently re-establishes the
+// connection to the server if the server dies and comes back (on the same
+// endpoint).
+func TestReconnect(t *testing.T) {
+	t.Skip()
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	sh, err := modules.NewShell(ctx, v23.GetPrincipal(ctx), testing.Verbose(), t)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	defer sh.Cleanup(os.Stderr, os.Stderr)
+	server, err := sh.Start(nil, echoServer, "--v23.tcp.address=127.0.0.1:0", "mymessage", "")
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	server.ReadLine()
+	serverName := server.ExpectVar("NAME")
+	serverEP, _ := naming.SplitAddressName(serverName)
+	ep, _ := inaming.NewEndpoint(serverEP)
+
+	makeCall := func(ctx *context.T, opts ...rpc.CallOpt) (string, error) {
+		ctx, _ = context.WithDeadline(ctx, time.Now().Add(10*time.Second))
+		call, err := v23.GetClient(ctx).StartCall(ctx, serverName, "Echo", []interface{}{"bratman"}, opts...)
+		if err != nil {
+			return "", fmt.Errorf("START: %s", err)
+		}
+		var result string
+		if err := call.Finish(&result); err != nil {
+			return "", err
+		}
+		return result, nil
+	}
+
+	expected := "mymessage: bratman\n"
+	if result, err := makeCall(ctx); err != nil || result != expected {
+		t.Errorf("Got (%q, %v) want (%q, nil)", result, err, expected)
+	}
+	// Kill the server, verify client can't talk to it anymore.
+	if err := server.Shutdown(os.Stderr, os.Stderr); err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+
+	if _, err := makeCall(ctx, options.NoRetry{}); err == nil || (!strings.HasPrefix(err.Error(), "START") && !strings.Contains(err.Error(), "EOF")) {
+		t.Fatalf(`Got (%v) want ("START: <err>" or "EOF") as server is down`, err)
+	}
+
+	// Resurrect the server with the same address, verify client
+	// re-establishes the connection. This is racy if another
+	// process grabs the port.
+	server, err = sh.Start(nil, echoServer, "--v23.tcp.address="+ep.Address, "mymessage again", "")
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	defer server.Shutdown(os.Stderr, os.Stderr)
+	expected = "mymessage again: bratman\n"
+	if result, err := makeCall(ctx); err != nil || result != expected {
+		t.Errorf("Got (%q, %v) want (%q, nil)", result, err, expected)
+	}
+}
+
+func TestMethodErrors(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	clt := v23.GetClient(ctx)
+
+	name, fn := initServer(t, ctx)
+	defer fn()
+
+	var (
+		i, j int
+		s    string
+	)
+
+	testCases := []struct {
+		testName, objectName, method string
+		args, results                []interface{}
+		wantID                       verror.ID
+		wantMessage                  string
+	}{
+		{
+			testName:   "unknown method",
+			objectName: name,
+			method:     "NoMethod",
+			wantID:     verror.ErrUnknownMethod.ID,
+		},
+		{
+			testName:   "unknown suffix",
+			objectName: name + "/NoSuffix",
+			method:     "Ping",
+			wantID:     verror.ErrUnknownSuffix.ID,
+		},
+		{
+			testName:    "too many args",
+			objectName:  name,
+			method:      "Ping",
+			args:        []interface{}{1, 2},
+			results:     []interface{}{&i},
+			wantID:      verror.ErrBadProtocol.ID,
+			wantMessage: "wrong number of input arguments",
+		},
+		{
+			testName:    "wrong number of results",
+			objectName:  name,
+			method:      "Ping",
+			results:     []interface{}{&i, &j},
+			wantID:      verror.ErrBadProtocol.ID,
+			wantMessage: "results, but want",
+		},
+		{
+			testName:    "wrong number of results",
+			objectName:  name,
+			method:      "Ping",
+			results:     []interface{}{&i, &j},
+			wantID:      verror.ErrBadProtocol.ID,
+			wantMessage: "results, but want",
+		},
+		{
+			testName:    "mismatched arg types",
+			objectName:  name,
+			method:      "Echo",
+			args:        []interface{}{1},
+			results:     []interface{}{&s},
+			wantID:      verror.ErrBadProtocol.ID,
+			wantMessage: "aren't compatible",
+		},
+		{
+			testName:    "mismatched result types",
+			objectName:  name,
+			method:      "Ping",
+			results:     []interface{}{&i},
+			wantID:      verror.ErrBadProtocol.ID,
+			wantMessage: "aren't compatible",
+		},
+	}
+
+	for _, test := range testCases {
+		testPrefix := fmt.Sprintf("test(%s) failed", test.testName)
+		call, err := clt.StartCall(ctx, test.objectName, test.method, test.args)
+		if err != nil {
+			t.Fatalf("%s: %v", testPrefix, err)
+		}
+		verr := call.Finish(test.results...)
+		if verror.ErrorID(verr) != test.wantID {
+			t.Errorf("%s: wrong error: %v", testPrefix, verr)
+		} else if got, want := verr.Error(), test.wantMessage; !strings.Contains(got, want) {
+			t.Errorf("%s: want %q to contain %q", testPrefix, got, want)
+		}
+		logErrors(t, test.testName, false, false, false, verr)
+	}
+}
+
+func TestReservedMethodErrors(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	clt := v23.GetClient(ctx)
+
+	name, fn := initServer(t, ctx)
+	defer fn()
+
+	logErr := func(msg string, err error) {
+		logErrors(t, msg, true, false, false, err)
+	}
+
+	// This call will fail because the __xx suffix is not supported by
+	// the dispatcher implementing Signature.
+	call, err := clt.StartCall(ctx, name+"/__xx", "__Signature", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	sig := []signature.Interface{}
+	verr := call.Finish(&sig)
+	if verror.ErrorID(verr) != verror.ErrUnknownSuffix.ID {
+		t.Fatalf("wrong error: %s", verr)
+	}
+	logErr("unknown suffix", verr)
+
+	// This call will fail for the same reason, but with a different error,
+	// saying that MethodSignature is an unknown method.
+	call, err = clt.StartCall(ctx, name+"/__xx", "__MethodSignature", []interface{}{"dummy"})
+	if err != nil {
+		t.Fatal(err)
+	}
+	verr = call.Finish(&sig)
+	if verror.ErrorID(verr) != verror.ErrUnknownMethod.ID {
+		t.Fatalf("wrong error: %s", verr)
+	}
+	logErr("unknown method", verr)
+}
diff --git a/runtime/internal/rpc/test/doc.go b/runtime/internal/rpc/test/doc.go
new file mode 100644
index 0000000..df643b7
--- /dev/null
+++ b/runtime/internal/rpc/test/doc.go
@@ -0,0 +1,6 @@
+// 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 test contains test for rpc code that do not rely on unexposed rpc declarations.
+package test
diff --git a/runtime/internal/rpc/test/glob_test.go b/runtime/internal/rpc/test/glob_test.go
new file mode 100644
index 0000000..d881b67
--- /dev/null
+++ b/runtime/internal/rpc/test/glob_test.go
@@ -0,0 +1,363 @@
+// 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 test
+
+import (
+	"errors"
+	"fmt"
+	"reflect"
+	"strings"
+	"testing"
+
+	"v.io/v23/context"
+	"v.io/v23/glob"
+	"v.io/v23/i18n"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+	"v.io/v23/rpc/reserved"
+	"v.io/v23/security"
+	"v.io/v23/verror"
+	"v.io/x/ref/lib/xrpc"
+	_ "v.io/x/ref/runtime/factories/generic"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/testutil"
+)
+
+func TestGlob(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	namespace := []string{
+		"a/b/c1/d1",
+		"a/b/c1/d2",
+		"a/b/c2/d1",
+		"a/b/c2/d2",
+		"a/x/y/z",
+		"leaf",
+	}
+	tree := newNode()
+	for _, p := range namespace {
+		tree.find(strings.Split(p, "/"), true)
+	}
+
+	server, err := xrpc.NewDispatchingServer(ctx, "", &disp{tree})
+	if err != nil {
+		t.Fatalf("failed to start debug server: %v", err)
+	}
+	ep := server.Status().Endpoints[0].String()
+
+	var (
+		noExist        = verror.New(verror.ErrNoExist, ctx, "")
+		notImplemented = reserved.NewErrGlobNotImplemented(ctx)
+		maxRecursion   = reserved.NewErrGlobMaxRecursionReached(ctx)
+	)
+
+	testcases := []struct {
+		name, pattern string
+		expected      []string
+		errors        []naming.GlobError
+	}{
+		{"", "...", []string{
+			"",
+			"a",
+			"a/b",
+			"a/b/c1",
+			"a/b/c1/d1",
+			"a/b/c1/d2",
+			"a/b/c2",
+			"a/b/c2/d1",
+			"a/b/c2/d2",
+			"a/x",
+			"a/x/y",
+			"a/x/y/z",
+			"leaf",
+		}, nil},
+		{"a", "...", []string{
+			"",
+			"b",
+			"b/c1",
+			"b/c1/d1",
+			"b/c1/d2",
+			"b/c2",
+			"b/c2/d1",
+			"b/c2/d2",
+			"x",
+			"x/y",
+			"x/y/z",
+		}, nil},
+		{"a/b", "...", []string{
+			"",
+			"c1",
+			"c1/d1",
+			"c1/d2",
+			"c2",
+			"c2/d1",
+			"c2/d2",
+		}, nil},
+		{"a/b/c1", "...", []string{
+			"",
+			"d1",
+			"d2",
+		}, nil},
+		{"a/b/c1/d1", "...", []string{
+			"",
+		}, nil},
+		{"a/x", "...", []string{
+			"",
+			"y",
+			"y/z",
+		}, nil},
+		{"a/x/y", "...", []string{
+			"",
+			"z",
+		}, nil},
+		{"a/x/y/z", "...", []string{
+			"",
+		}, nil},
+		{"", "", []string{""}, nil},
+		{"", "*", []string{"a", "leaf"}, nil},
+		{"a", "", []string{""}, nil},
+		{"a", "*", []string{"b", "x"}, nil},
+		{"a/b", "", []string{""}, nil},
+		{"a/b", "*", []string{"c1", "c2"}, nil},
+		{"a/b/c1", "", []string{""}, nil},
+		{"a/b/c1", "*", []string{"d1", "d2"}, nil},
+		{"a/b/c1/d1", "*", []string{}, nil},
+		{"a/b/c1/d1", "", []string{""}, nil},
+		{"a/b/c2", "", []string{""}, nil},
+		{"a/b/c2", "*", []string{"d1", "d2"}, nil},
+		{"a/b/c2/d1", "*", []string{}, nil},
+		{"a/b/c2/d1", "", []string{""}, nil},
+		{"a", "*/c?", []string{"b/c1", "b/c2"}, nil},
+		{"a", "*/*", []string{"b/c1", "b/c2", "x/y"}, nil},
+		{"a", "*/*/*", []string{"b/c1/d1", "b/c1/d2", "b/c2/d1", "b/c2/d2", "x/y/z"}, nil},
+		{"a/x", "*/*", []string{"y/z"}, nil},
+		{"bad", "", []string{}, []naming.GlobError{{Name: "", Error: noExist}}},
+		{"bad/foo", "", []string{}, []naming.GlobError{{Name: "", Error: noExist}}},
+		{"a/bad", "", []string{}, []naming.GlobError{{Name: "", Error: noExist}}},
+		{"a/b/bad", "", []string{}, []naming.GlobError{{Name: "", Error: noExist}}},
+		{"a/b/c1/bad", "", []string{}, []naming.GlobError{{Name: "", Error: noExist}}},
+		{"a/x/bad", "", []string{}, []naming.GlobError{{Name: "", Error: noExist}}},
+		{"a/x/y/bad", "", []string{}, []naming.GlobError{{Name: "", Error: noExist}}},
+		{"leaf", "", []string{""}, nil},
+		{"leaf", "*", []string{}, []naming.GlobError{{Name: "", Error: notImplemented}}},
+		{"leaf/foo", "", []string{}, []naming.GlobError{{Name: "", Error: noExist}}},
+		// muah is an infinite space to test rescursion limit.
+		{"muah", "*", []string{"ha"}, nil},
+		{"muah", "*/*", []string{"ha/ha"}, nil},
+		{"muah", "*/*/*/*/*/*/*/*/*/*/*/*", []string{"ha/ha/ha/ha/ha/ha/ha/ha/ha/ha/ha/ha"}, nil},
+		{"muah", "...", []string{
+			"",
+			"ha",
+			"ha/ha",
+			"ha/ha/ha",
+			"ha/ha/ha/ha",
+			"ha/ha/ha/ha/ha",
+			"ha/ha/ha/ha/ha/ha",
+			"ha/ha/ha/ha/ha/ha/ha",
+			"ha/ha/ha/ha/ha/ha/ha/ha",
+			"ha/ha/ha/ha/ha/ha/ha/ha/ha",
+			"ha/ha/ha/ha/ha/ha/ha/ha/ha/ha",
+		}, []naming.GlobError{{Name: "ha/ha/ha/ha/ha/ha/ha/ha/ha/ha/ha", Error: maxRecursion}}},
+	}
+	for _, tc := range testcases {
+		name := naming.JoinAddressName(ep, tc.name)
+		results, globErrors, err := testutil.GlobName(ctx, name, tc.pattern)
+		if err != nil {
+			t.Errorf("unexpected Glob error for (%q, %q): %v", tc.name, tc.pattern, err)
+			continue
+		}
+		if !reflect.DeepEqual(results, tc.expected) {
+			t.Errorf("unexpected result for (%q, %q). Got %q, want %q", tc.name, tc.pattern, results, tc.expected)
+		}
+		if len(globErrors) != len(tc.errors) {
+			t.Errorf("unexpected number of glob errors for (%q, %q): got %#v, expected %#v", tc.name, tc.pattern, globErrors, tc.errors)
+		}
+		for i, e := range globErrors {
+			if i >= len(tc.errors) {
+				t.Errorf("unexpected glob error for (%q, %q): %#v", tc.name, tc.pattern, e.Error)
+				continue
+			}
+			if e.Name != tc.errors[i].Name {
+				t.Errorf("unexpected glob error for (%q, %q): %v", tc.name, tc.pattern, e)
+			}
+			if got, expected := verror.ErrorID(e.Error), verror.ErrorID(tc.errors[i].Error); got != expected {
+				t.Errorf("unexpected error ID for (%q, %q): Got %v, expected %v", tc.name, tc.pattern, got, expected)
+			}
+		}
+	}
+}
+
+func TestGlobDeny(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	tree := newNode()
+	tree.find([]string{"a", "b"}, true)
+	tree.find([]string{"a", "deny", "x"}, true)
+
+	server, err := xrpc.NewDispatchingServer(ctx, "", &disp{tree})
+	if err != nil {
+		t.Fatalf("failed to start debug server: %v", err)
+	}
+	ep := server.Status().Endpoints[0].String()
+
+	testcases := []struct {
+		name, pattern string
+		results       []string
+		numErrors     int
+	}{
+		{"", "*", []string{"a"}, 0},
+		{"", "*/*", []string{"a/b"}, 1},
+		{"a", "*", []string{"b"}, 1},
+		{"a/deny", "*", []string{}, 1},
+		{"a/deny/x", "*", []string{}, 1},
+		{"a/deny/y", "*", []string{}, 1},
+	}
+
+	// Ensure that we're getting the english error message.
+	ctx = i18n.WithLangID(ctx, i18n.LangID("en-US"))
+
+	for _, tc := range testcases {
+		name := naming.JoinAddressName(ep, tc.name)
+		results, globErrors, err := testutil.GlobName(ctx, name, tc.pattern)
+		if err != nil {
+			t.Errorf("unexpected Glob error for (%q, %q): %v", tc.name, tc.pattern, err)
+		}
+		if !reflect.DeepEqual(results, tc.results) {
+			t.Errorf("unexpected results for (%q, %q). Got %v, expected %v", tc.name, tc.pattern, results, tc.results)
+		}
+		if len(globErrors) != tc.numErrors {
+			t.Errorf("unexpected number of errors for (%q, %q). Got %v, expected %v", tc.name, tc.pattern, len(globErrors), tc.numErrors)
+		}
+		for _, gerr := range globErrors {
+			if got, expected := verror.ErrorID(gerr.Error), reserved.ErrGlobMatchesOmitted.ID; got != expected {
+				t.Errorf("unexpected error for (%q, %q): Got %v, expected %v", tc.name, tc.pattern, got, expected)
+			}
+			// This error message is purposely vague to avoid leaking information that
+			// the user doesn't have access to.
+			// We check the actual error string to make sure that we don't start
+			// leaking new information by accident.
+			expectedStr := fmt.Sprintf(
+				`test.test:"%s".__Glob: some matches might have been omitted`,
+				tc.name)
+			if got := gerr.Error.Error(); got != expectedStr {
+				t.Errorf("unexpected error string: Got %q, expected %q", got, expectedStr)
+			}
+		}
+	}
+}
+
+type disp struct {
+	tree *node
+}
+
+func (d *disp) Lookup(ctx *context.T, suffix string) (interface{}, security.Authorizer, error) {
+	elems := strings.Split(suffix, "/")
+	var auth security.Authorizer
+	for _, e := range elems {
+		if e == "deny" {
+			return &leafObject{}, &denyAllAuthorizer{}, nil
+		}
+	}
+	if len(elems) != 0 && elems[0] == "muah" {
+		// Infinite space. Each node has one child named "ha".
+		return rpc.ChildrenGlobberInvoker("ha"), auth, nil
+
+	}
+	if len(elems) != 0 && elems[len(elems)-1] == "leaf" {
+		return &leafObject{}, auth, nil
+	}
+	n := d.tree.find(elems, false)
+	if n == nil {
+		return nil, nil, verror.New(verror.ErrNoExist, ctx, suffix)
+	}
+	if len(elems) < 2 || (elems[0] == "a" && elems[1] == "x") {
+		return &vChildrenObject{n}, auth, nil
+	}
+	return &globObject{n}, auth, nil
+}
+
+type denyAllAuthorizer struct{}
+
+func (denyAllAuthorizer) Authorize(*context.T, security.Call) error {
+	return errors.New("no access")
+}
+
+type globObject struct {
+	n *node
+}
+
+func (o *globObject) Glob__(ctx *context.T, call rpc.GlobServerCall, g *glob.Glob) error {
+	o.globLoop(call, "", g, o.n)
+	return nil
+}
+
+func (o *globObject) globLoop(call rpc.GlobServerCall, name string, g *glob.Glob, n *node) {
+	if g.Len() == 0 {
+		call.SendStream().Send(naming.GlobReplyEntry{Value: naming.MountEntry{Name: name}})
+	}
+	if g.Empty() {
+		return
+	}
+	matcher, left := g.Head(), g.Tail()
+	for leaf, child := range n.children {
+		if matcher.Match(leaf) {
+			o.globLoop(call, naming.Join(name, leaf), left, child)
+		}
+	}
+}
+
+type vChildrenObject struct {
+	n *node
+}
+
+func (o *vChildrenObject) GlobChildren__(ctx *context.T, call rpc.GlobChildrenServerCall, m *glob.Element) error {
+	sender := call.SendStream()
+	for child, _ := range o.n.children {
+		if m.Match(child) {
+			sender.Send(naming.GlobChildrenReplyName{Value: child})
+		}
+	}
+	return nil
+}
+
+type node struct {
+	children map[string]*node
+}
+
+func newNode() *node {
+	return &node{make(map[string]*node)}
+}
+
+func (n *node) find(names []string, create bool) *node {
+	if len(names) == 1 && names[0] == "" {
+		return n
+	}
+	for {
+		if len(names) == 0 {
+			return n
+		}
+		if next, ok := n.children[names[0]]; ok {
+			n = next
+			names = names[1:]
+			continue
+		}
+		if create {
+			nn := newNode()
+			n.children[names[0]] = nn
+			n = nn
+			names = names[1:]
+			continue
+		}
+		return nil
+	}
+}
+
+type leafObject struct{}
+
+func (leafObject) Func(*context.T, rpc.ServerCall) error {
+	return nil
+}
diff --git a/runtime/internal/rpc/test/proxy_test.go b/runtime/internal/rpc/test/proxy_test.go
new file mode 100644
index 0000000..46c5078
--- /dev/null
+++ b/runtime/internal/rpc/test/proxy_test.go
@@ -0,0 +1,406 @@
+// 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 test
+
+import (
+	"fmt"
+	"os"
+	"reflect"
+	"sort"
+	"strings"
+	"testing"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/namespace"
+	"v.io/v23/naming"
+	"v.io/v23/options"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/verror"
+	"v.io/v23/vtrace"
+	"v.io/x/ref/lib/flags"
+	_ "v.io/x/ref/runtime/factories/generic"
+	"v.io/x/ref/runtime/internal/lib/publisher"
+	inaming "v.io/x/ref/runtime/internal/naming"
+	irpc "v.io/x/ref/runtime/internal/rpc"
+	imanager "v.io/x/ref/runtime/internal/rpc/stream/manager"
+	"v.io/x/ref/runtime/internal/rpc/stream/proxy"
+	tnaming "v.io/x/ref/runtime/internal/testing/mocks/naming"
+	ivtrace "v.io/x/ref/runtime/internal/vtrace"
+	"v.io/x/ref/test/modules"
+	"v.io/x/ref/test/testutil"
+)
+
+func testContext() (*context.T, func()) {
+	ctx, shutdown := v23.Init()
+	ctx, _ = context.WithTimeout(ctx, 20*time.Second)
+	var err error
+	if ctx, err = ivtrace.Init(ctx, flags.VtraceFlags{}); err != nil {
+		panic(err)
+	}
+	ctx, _ = vtrace.WithNewTrace(ctx)
+	return ctx, shutdown
+}
+
+var proxyServer = modules.Register(func(env *modules.Env, args ...string) error {
+	ctx, shutdown := v23.Init()
+	defer shutdown()
+
+	expected := len(args)
+
+	listenSpec := rpc.ListenSpec{Addrs: rpc.ListenAddrs{{"tcp", "127.0.0.1:0"}}}
+	proxyShutdown, proxyEp, err := proxy.New(ctx, listenSpec, security.AllowEveryone())
+	if err != nil {
+		fmt.Fprintf(env.Stderr, "%s\n", verror.DebugString(err))
+		return err
+	}
+	defer proxyShutdown()
+	fmt.Fprintf(env.Stdout, "PID=%d\n", os.Getpid())
+	if expected > 0 {
+		pub := publisher.New(ctx, v23.GetNamespace(ctx), time.Minute)
+		defer pub.WaitForStop()
+		defer pub.Stop()
+		pub.AddServer(proxyEp.String())
+		for _, name := range args {
+			if len(name) == 0 {
+				return fmt.Errorf("empty name specified on the command line")
+			}
+			pub.AddName(name, false, false)
+		}
+		// Wait for all the entries to be published.
+		for {
+			pubState := pub.Status()
+			if expected == len(pubState) {
+				break
+			}
+			delay := time.Second
+			time.Sleep(delay)
+		}
+	}
+	fmt.Fprintf(env.Stdout, "PROXY_NAME=%s\n", proxyEp.Name())
+	modules.WaitForEOF(env.Stdin)
+	fmt.Fprintf(env.Stdout, "DONE\n")
+	return nil
+}, "")
+
+type testServer struct{}
+
+func (*testServer) Echo(_ *context.T, call rpc.ServerCall, arg string) (string, error) {
+	return fmt.Sprintf("method:%q,suffix:%q,arg:%q", "Echo", call.Suffix(), arg), nil
+}
+
+type testServerAuthorizer struct{}
+
+func (testServerAuthorizer) Authorize(*context.T, security.Call) error {
+	return nil
+}
+
+type testServerDisp struct{ server interface{} }
+
+func (t testServerDisp) Lookup(_ *context.T, suffix string) (interface{}, security.Authorizer, error) {
+	return t.server, testServerAuthorizer{}, nil
+}
+
+type proxyHandle struct {
+	ns    namespace.T
+	sh    *modules.Shell
+	proxy modules.Handle
+	name  string
+}
+
+func (h *proxyHandle) Start(t *testing.T, ctx *context.T, args ...string) error {
+	sh, err := modules.NewShell(nil, nil, testing.Verbose(), t)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	h.sh = sh
+	p, err := sh.Start(nil, proxyServer, args...)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	h.proxy = p
+	p.ReadLine()
+	h.name = p.ExpectVar("PROXY_NAME")
+	if len(h.name) == 0 {
+		h.proxy.Shutdown(os.Stderr, os.Stderr)
+		t.Fatalf("failed to get PROXY_NAME from proxyd")
+	}
+	return h.ns.Mount(ctx, "proxy", h.name, time.Hour)
+}
+
+func (h *proxyHandle) Stop(ctx *context.T) error {
+	defer h.sh.Cleanup(os.Stderr, os.Stderr)
+	if err := h.proxy.Shutdown(os.Stderr, os.Stderr); err != nil {
+		return err
+	}
+	if len(h.name) == 0 {
+		return nil
+	}
+	return h.ns.Unmount(ctx, "proxy", h.name)
+}
+
+func TestProxyOnly(t *testing.T) {
+	listenSpec := rpc.ListenSpec{Proxy: "proxy"}
+	testProxy(t, listenSpec)
+}
+
+func TestProxy(t *testing.T) {
+	proxyListenSpec := rpc.ListenSpec{
+		Addrs: rpc.ListenAddrs{{"tcp", "127.0.0.1:0"}},
+		Proxy: "proxy",
+	}
+	testProxy(t, proxyListenSpec)
+}
+
+func TestWSProxy(t *testing.T) {
+	proxyListenSpec := rpc.ListenSpec{
+		Addrs: rpc.ListenAddrs{{"tcp", "127.0.0.1:0"}},
+		Proxy: "proxy",
+	}
+	// The proxy uses websockets only, but the server is using tcp.
+	testProxy(t, proxyListenSpec, "--v23.tcp.protocol=ws")
+}
+
+func testProxy(t *testing.T, spec rpc.ListenSpec, args ...string) {
+	ctx, shutdown := testContext()
+	defer shutdown()
+
+	var (
+		pserver   = testutil.NewPrincipal("server")
+		pclient   = testutil.NewPrincipal("client")
+		serverKey = pserver.PublicKey()
+		// We use different stream managers for the client and server
+		// to prevent VIF re-use (in other words, we want to test VIF
+		// creation from both the client and server end).
+		smserver = imanager.InternalNew(ctx, naming.FixedRoutingID(0x555555555))
+		smclient = imanager.InternalNew(ctx, naming.FixedRoutingID(0x444444444))
+		ns       = tnaming.NewSimpleNamespace()
+	)
+	defer smserver.Shutdown()
+	defer smclient.Shutdown()
+	client, err := irpc.InternalNewClient(smserver, ns)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer client.Close()
+	serverCtx, _ := v23.WithPrincipal(ctx, pserver)
+	server, err := irpc.InternalNewServer(serverCtx, smserver, ns, nil, "", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer server.Stop()
+
+	// The client must recognize the server's blessings, otherwise it won't
+	// communicate with it.
+	pclient.AddToRoots(pserver.BlessingStore().Default())
+
+	// If no address is specified then we'll only 'listen' via
+	// the proxy.
+	hasLocalListener := len(spec.Addrs) > 0 && len(spec.Addrs[0].Address) != 0
+
+	name := "mountpoint/server/suffix"
+	makeCall := func(opts ...rpc.CallOpt) (string, error) {
+		clientCtx, _ := v23.WithPrincipal(ctx, pclient)
+		clientCtx, _ = context.WithDeadline(clientCtx, time.Now().Add(5*time.Second))
+		call, err := client.StartCall(clientCtx, name, "Echo", []interface{}{"batman"}, opts...)
+		if err != nil {
+			// proxy is down, we should return here/.... prepend
+			// the error with a well known string so that we can test for that.
+			return "", fmt.Errorf("RESOLVE: %s", err)
+		}
+		var result string
+		if err = call.Finish(&result); err != nil {
+			return "", err
+		}
+		return result, nil
+	}
+	proxy := &proxyHandle{ns: ns}
+	if err := proxy.Start(t, ctx, args...); err != nil {
+		t.Fatal(err)
+	}
+	defer proxy.Stop(ctx)
+	addrs := verifyMount(t, ctx, ns, spec.Proxy)
+	if len(addrs) != 1 {
+		t.Fatalf("failed to lookup proxy")
+	}
+
+	eps, err := server.Listen(spec)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if err := server.ServeDispatcher("mountpoint/server", testServerDisp{&testServer{}}); err != nil {
+		t.Fatal(err)
+	}
+
+	// Proxy connections are started asynchronously, so we need to wait..
+	waitForMountTable := func(ch chan int, expect int) {
+		then := time.Now().Add(time.Minute)
+		for {
+			me, err := ns.Resolve(ctx, name)
+			if err != nil {
+				continue
+			}
+			for i, s := range me.Servers {
+				ctx.Infof("%d: %s", i, s)
+			}
+			if err == nil && len(me.Servers) == expect {
+				ch <- 1
+				return
+			}
+			if time.Now().After(then) {
+				t.Fatalf("timed out waiting for %d servers, found %d", expect, len(me.Servers))
+			}
+			time.Sleep(100 * time.Millisecond)
+		}
+	}
+	waitForServerStatus := func(ch chan int, proxy string) {
+		then := time.Now().Add(time.Minute)
+		for {
+			status := server.Status()
+			if len(status.Proxies) == 1 && status.Proxies[0].Proxy == proxy {
+				ch <- 2
+				return
+			}
+			if time.Now().After(then) {
+				t.Fatalf("timed out")
+			}
+			time.Sleep(100 * time.Millisecond)
+		}
+	}
+	proxyEP, _ := naming.SplitAddressName(addrs[0])
+	proxiedEP, err := inaming.NewEndpoint(proxyEP)
+	if err != nil {
+		t.Fatalf("unexpected error for %q: %s", proxyEP, err)
+	}
+	proxiedEP.RID = naming.FixedRoutingID(0x555555555)
+	proxiedEP.Blessings = []string{"server"}
+	expectedNames := []string{naming.JoinAddressName(proxiedEP.String(), "suffix")}
+	if hasLocalListener {
+		expectedNames = append(expectedNames, naming.JoinAddressName(eps[0].String(), "suffix"))
+	}
+
+	// Proxy connetions are created asynchronously, so we wait for the
+	// expected number of endpoints to appear for the specified service name.
+	ch := make(chan int, 2)
+	go waitForMountTable(ch, len(expectedNames))
+	go waitForServerStatus(ch, spec.Proxy)
+	select {
+	case <-time.After(time.Minute):
+		t.Fatalf("timedout waiting for two entries in the mount table and server status")
+	case i := <-ch:
+		select {
+		case <-time.After(time.Minute):
+			t.Fatalf("timedout waiting for two entries in the mount table or server status")
+		case j := <-ch:
+			if !((i == 1 && j == 2) || (i == 2 && j == 1)) {
+				t.Fatalf("unexpected return values from waiters")
+			}
+		}
+	}
+
+	status := server.Status()
+	if got, want := status.Proxies[0].Endpoint, proxiedEP; !reflect.DeepEqual(got, want) {
+		t.Fatalf("got %q, want %q", got, want)
+	}
+
+	got := []string{}
+	for _, s := range verifyMount(t, ctx, ns, name) {
+		got = append(got, s)
+	}
+	sort.Strings(got)
+	sort.Strings(expectedNames)
+	if !reflect.DeepEqual(got, expectedNames) {
+		t.Errorf("got %v, want %v", got, expectedNames)
+	}
+
+	if hasLocalListener {
+		// Listen will publish both the local and proxied endpoint with the
+		// mount table, given that we're trying to test the proxy, we remove
+		// the local endpoint from the mount table entry!  We have to remove both
+		// the tcp and the websocket address.
+		sep := eps[0].String()
+		ns.Unmount(ctx, "mountpoint/server", sep)
+	}
+
+	addrs = verifyMount(t, ctx, ns, name)
+	if len(addrs) != 1 {
+		t.Fatalf("failed to lookup proxy: addrs %v", addrs)
+	}
+
+	// Proxied endpoint should be published and RPC should succeed (through proxy).
+	// Additionally, any server authorizaton options must only apply to the end server
+	// and not the proxy.
+	const expected = `method:"Echo",suffix:"suffix",arg:"batman"`
+	if result, err := makeCall(options.ServerPublicKey{PublicKey: serverKey}); result != expected || err != nil {
+		t.Fatalf("Got (%v, %v) want (%v, nil)", result, err, expected)
+	}
+
+	// Proxy dies, calls should fail and the name should be unmounted.
+	if err := proxy.Stop(ctx); err != nil {
+		t.Fatal(err)
+	}
+
+	if result, err := makeCall(options.NoRetry{}); err == nil || (!strings.HasPrefix(err.Error(), "RESOLVE") && !strings.Contains(err.Error(), "EOF")) {
+		t.Fatalf(`Got (%v, %v) want ("", "RESOLVE: <err>" or "EOF") as proxy is down`, result, err)
+	}
+
+	for {
+		if _, err := ns.Resolve(ctx, name); err != nil {
+			break
+		}
+		time.Sleep(10 * time.Millisecond)
+	}
+	verifyMountMissing(t, ctx, ns, name)
+
+	status = server.Status()
+	if got, want := len(status.Proxies), 1; got != want {
+		t.Logf("Proxies: %v", status.Proxies)
+		t.Fatalf("got %v, want %v", got, want)
+	}
+	if got, want := status.Proxies[0].Proxy, spec.Proxy; got != want {
+		t.Fatalf("got %v, want %v", got, want)
+	}
+	if got, want := verror.ErrorID(status.Proxies[0].Error), verror.ErrNoServers.ID; got != want {
+		t.Fatalf("got %v, want %v", got, want)
+	}
+
+	// Proxy restarts, calls should eventually start succeeding.
+	if err := proxy.Start(t, ctx, args...); err != nil {
+		t.Fatal(err)
+	}
+
+	retries := 0
+	for {
+		if result, err := makeCall(); err == nil {
+			if result != expected {
+				t.Errorf("Got (%v, %v) want (%v, nil)", result, err, expected)
+			}
+			break
+		} else {
+			retries++
+			if retries > 10 {
+				t.Fatalf("Failed after 10 attempts: err: %s", err)
+			}
+		}
+	}
+}
+
+func verifyMount(t *testing.T, ctx *context.T, ns namespace.T, name string) []string {
+	me, err := ns.Resolve(ctx, name)
+	if err != nil {
+		t.Errorf("%s not found in mounttable", name)
+		return nil
+	}
+	return me.Names()
+}
+
+func verifyMountMissing(t *testing.T, ctx *context.T, ns namespace.T, name string) {
+	if me, err := ns.Resolve(ctx, name); err == nil {
+		names := me.Names()
+		t.Errorf("%s not supposed to be found in mounttable; got %d servers instead: %v", name, len(names), names)
+	}
+}
diff --git a/runtime/internal/rpc/test/retry_test.go b/runtime/internal/rpc/test/retry_test.go
new file mode 100644
index 0000000..736f45e
--- /dev/null
+++ b/runtime/internal/rpc/test/retry_test.go
@@ -0,0 +1,80 @@
+// 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 test
+
+import (
+	"testing"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/options"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/verror"
+	"v.io/x/ref/lib/xrpc"
+)
+
+var errRetryThis = verror.Register("retry_test.retryThis", verror.RetryBackoff, "retryable error")
+
+type retryServer struct {
+	called int // number of times TryAgain has been called
+}
+
+func (s *retryServer) TryAgain(ctx *context.T, _ rpc.ServerCall) error {
+	// If this is the second time this method is being called, return success.
+	if s.called > 0 {
+		s.called++
+		return nil
+	}
+	s.called++
+	// otherwise, return a verror with action code RetryBackoff.
+	return verror.New(errRetryThis, ctx)
+}
+
+func TestRetryCall(t *testing.T) {
+	ctx, shutdown := v23.Init()
+	defer shutdown()
+
+	// Start the server.
+	rs := retryServer{}
+	server, err := xrpc.NewServer(ctx, "", &rs, security.AllowEveryone())
+	if err != nil {
+		t.Fatal(err)
+	}
+	name := server.Status().Endpoints[0].Name()
+
+	client := v23.GetClient(ctx)
+	// A traditional client.StartCall/call.Finish sequence should fail at
+	// call.Finish since the error returned by the server won't be retried.
+	call, err := client.StartCall(ctx, name, "TryAgain", nil)
+	if err != nil {
+		t.Errorf("client.StartCall failed: %v", err)
+	}
+	if err := call.Finish(); err == nil {
+		t.Errorf("call.Finish should have failed")
+	}
+	rs.called = 0
+
+	// A call to client.Call should succeed because the error return by the
+	// server should be retried.
+	if err := client.Call(ctx, name, "TryAgain", nil, nil); err != nil {
+		t.Errorf("client.Call failed: %v", err)
+	}
+	// Ensure that client.Call retried the call exactly once.
+	if rs.called != 2 {
+		t.Errorf("retryServer should have been called twice, instead called %d times", rs.called)
+	}
+	rs.called = 0
+
+	// The options.NoRetry option should be honored by client.Call, so the following
+	// call should fail.
+	if err := client.Call(ctx, name, "TryAgain", nil, nil, options.NoRetry{}); err == nil {
+		t.Errorf("client.Call(..., options.NoRetry{}) should have failed")
+	}
+	// Ensure that client.Call did not retry the call.
+	if rs.called != 1 {
+		t.Errorf("retryServer have been called once, instead called %d times", rs.called)
+	}
+}
diff --git a/runtime/internal/rpc/test/signature_test.go b/runtime/internal/rpc/test/signature_test.go
new file mode 100644
index 0000000..8c3c492
--- /dev/null
+++ b/runtime/internal/rpc/test/signature_test.go
@@ -0,0 +1,141 @@
+// 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 test
+
+import (
+	"reflect"
+	"testing"
+
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/rpc/reserved"
+	"v.io/v23/vdl"
+	"v.io/v23/vdlroot/signature"
+
+	"v.io/x/ref/lib/xrpc"
+	_ "v.io/x/ref/runtime/factories/generic"
+	"v.io/x/ref/test"
+)
+
+type sigImpl struct{}
+
+func (sigImpl) NonStreaming0(*context.T, rpc.ServerCall) error                   { panic("X") }
+func (sigImpl) NonStreaming1(*context.T, rpc.ServerCall, string) (int64, error)  { panic("X") }
+func (sigImpl) Streaming0(*context.T, *streamStringBool) error                   { panic("X") }
+func (sigImpl) Streaming1(*context.T, *streamStringBool, int64) (float64, error) { panic("X") }
+
+type streamStringBool struct{ rpc.StreamServerCall }
+
+func (*streamStringBool) Init(rpc.StreamServerCall) { panic("X") }
+func (*streamStringBool) RecvStream() interface {
+	Advance() bool
+	Value() string
+	Err() error
+} {
+	panic("X")
+}
+func (*streamStringBool) SendStream() interface {
+	Send(_ bool) error
+} {
+	panic("X")
+}
+
+func TestMethodSignature(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	server, err := xrpc.NewServer(ctx, "", sigImpl{}, nil)
+	if err != nil {
+		t.Fatalf("failed to start sig server: %v", err)
+	}
+	name := server.Status().Endpoints[0].Name()
+
+	tests := []struct {
+		Method string
+		Want   signature.Method
+	}{
+		{"NonStreaming0", signature.Method{
+			Name: "NonStreaming0",
+		}},
+		{"NonStreaming1", signature.Method{
+			Name:    "NonStreaming1",
+			InArgs:  []signature.Arg{{Type: vdl.StringType}},
+			OutArgs: []signature.Arg{{Type: vdl.Int64Type}},
+		}},
+		{"Streaming0", signature.Method{
+			Name:      "Streaming0",
+			InStream:  &signature.Arg{Type: vdl.StringType},
+			OutStream: &signature.Arg{Type: vdl.BoolType},
+		}},
+		{"Streaming1", signature.Method{
+			Name:      "Streaming1",
+			InArgs:    []signature.Arg{{Type: vdl.Int64Type}},
+			OutArgs:   []signature.Arg{{Type: vdl.Float64Type}},
+			InStream:  &signature.Arg{Type: vdl.StringType},
+			OutStream: &signature.Arg{Type: vdl.BoolType},
+		}},
+	}
+	for _, test := range tests {
+		sig, err := reserved.MethodSignature(ctx, name, test.Method)
+		if err != nil {
+			t.Errorf("call failed: %v", err)
+		}
+		if got, want := sig, test.Want; !reflect.DeepEqual(got, want) {
+			t.Errorf("%s got %#v, want %#v", test.Method, got, want)
+		}
+	}
+}
+
+func TestSignature(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	server, err := xrpc.NewServer(ctx, "", sigImpl{}, nil)
+	if err != nil {
+		t.Fatalf("failed to start sig server: %v", err)
+	}
+	name := server.Status().Endpoints[0].Name()
+	sig, err := reserved.Signature(ctx, name)
+	if err != nil {
+		t.Errorf("call failed: %v", err)
+	}
+	if got, want := len(sig), 2; got != want {
+		t.Fatalf("got sig %#v len %d, want %d", sig, got, want)
+	}
+	// Check expected methods.
+	methods := signature.Interface{
+		Doc: "The empty interface contains methods not attached to any interface.",
+		Methods: []signature.Method{
+			{
+				Name: "NonStreaming0",
+			},
+			{
+				Name:    "NonStreaming1",
+				InArgs:  []signature.Arg{{Type: vdl.StringType}},
+				OutArgs: []signature.Arg{{Type: vdl.Int64Type}},
+			},
+			{
+				Name:      "Streaming0",
+				InStream:  &signature.Arg{Type: vdl.StringType},
+				OutStream: &signature.Arg{Type: vdl.BoolType},
+			},
+			{
+				Name:      "Streaming1",
+				InArgs:    []signature.Arg{{Type: vdl.Int64Type}},
+				OutArgs:   []signature.Arg{{Type: vdl.Float64Type}},
+				InStream:  &signature.Arg{Type: vdl.StringType},
+				OutStream: &signature.Arg{Type: vdl.BoolType},
+			},
+		},
+	}
+	if got, want := sig[0], methods; !reflect.DeepEqual(got, want) {
+		t.Errorf("got sig[0] %#v, want %#v", got, want)
+	}
+	// Check reserved methods.
+	if got, want := sig[1].Name, "__Reserved"; got != want {
+		t.Errorf("got sig[1].Name %q, want %q", got, want)
+	}
+	if got, want := signature.MethodNames(sig[1:2]), []string{"__Glob", "__MethodSignature", "__Signature"}; !reflect.DeepEqual(got, want) {
+		t.Fatalf("got sig[1] methods %v, want %v", got, want)
+	}
+}
diff --git a/runtime/internal/rpc/test/simple_test.go b/runtime/internal/rpc/test/simple_test.go
new file mode 100644
index 0000000..4167007
--- /dev/null
+++ b/runtime/internal/rpc/test/simple_test.go
@@ -0,0 +1,132 @@
+// 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 test
+
+import (
+	"io"
+	"testing"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/x/ref/test"
+)
+
+type simple struct {
+	done <-chan struct{}
+}
+
+func (s *simple) Sleep(*context.T, rpc.ServerCall) error {
+	select {
+	case <-s.done:
+	case <-time.After(time.Hour):
+	}
+	return nil
+}
+
+func (s *simple) Ping(_ *context.T, _ rpc.ServerCall) (string, error) {
+	return "pong", nil
+}
+
+func (s *simple) Echo(_ *context.T, _ rpc.ServerCall, arg string) (string, error) {
+	return arg, nil
+}
+
+func (s *simple) Source(_ *context.T, call rpc.StreamServerCall, start int) error {
+	i := start
+	backoff := 25 * time.Millisecond
+	for {
+		select {
+		case <-s.done:
+			return nil
+		case <-time.After(backoff):
+			call.Send(i)
+			i++
+		}
+		backoff *= 2
+	}
+}
+
+func (s *simple) Sink(_ *context.T, call rpc.StreamServerCall) (int, error) {
+	i := 0
+	for {
+		if err := call.Recv(&i); err != nil {
+			if err == io.EOF {
+				return i, nil
+			}
+			return 0, err
+		}
+	}
+}
+
+func (s *simple) Inc(_ *context.T, call rpc.StreamServerCall, inc int) (int, error) {
+	i := 0
+	for {
+		if err := call.Recv(&i); err != nil {
+			if err == io.EOF {
+				return i, nil
+			}
+			return 0, err
+		}
+		call.Send(i + inc)
+	}
+}
+
+func TestSimpleRPC(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	name, fn := initServer(t, ctx)
+	defer fn()
+
+	client := v23.GetClient(ctx)
+	call, err := client.StartCall(ctx, name, "Ping", nil)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	response := ""
+	if err := call.Finish(&response); err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	if got, want := response, "pong"; got != want {
+		t.Fatalf("got %q, want %q", got, want)
+	}
+}
+
+func TestSimpleStreaming(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	name, fn := initServer(t, ctx)
+	defer fn()
+
+	inc := 1
+	call, err := v23.GetClient(ctx).StartCall(ctx, name, "Inc", []interface{}{inc})
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+
+	want := 10
+	for i := 0; i <= want; i++ {
+		if err := call.Send(i); err != nil {
+			t.Fatalf("unexpected error: %s", err)
+		}
+		got := -1
+		if err = call.Recv(&got); err != nil {
+			t.Fatalf("unexpected error: %s", err)
+		}
+		if want := i + inc; got != want {
+			t.Fatalf("got %d, want %d", got, want)
+		}
+	}
+	call.CloseSend()
+	final := -1
+	err = call.Finish(&final)
+	if err != nil {
+		t.Errorf("unexpected error: %#v", err)
+	}
+	if got := final; got != want {
+		t.Fatalf("got %d, want %d", got, want)
+	}
+}
diff --git a/runtime/internal/rpc/test/v23_internal_test.go b/runtime/internal/rpc/test/v23_internal_test.go
new file mode 100644
index 0000000..d7274da
--- /dev/null
+++ b/runtime/internal/rpc/test/v23_internal_test.go
@@ -0,0 +1,22 @@
+// 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 test
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	modules.DispatchAndExitIfChild()
+	os.Exit(m.Run())
+}
diff --git a/runtime/internal/rpc/testutil_test.go b/runtime/internal/rpc/testutil_test.go
new file mode 100644
index 0000000..105638d
--- /dev/null
+++ b/runtime/internal/rpc/testutil_test.go
@@ -0,0 +1,124 @@
+// 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 rpc
+
+import (
+	"reflect"
+	"testing"
+	"time"
+
+	"v.io/v23/vtrace"
+	"v.io/x/ref/lib/flags"
+	ivtrace "v.io/x/ref/runtime/internal/vtrace"
+	"v.io/x/ref/test"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/security"
+	"v.io/v23/vdl"
+	"v.io/v23/verror"
+)
+
+func makeResultPtrs(ins []interface{}) []interface{} {
+	outs := make([]interface{}, len(ins))
+	for ix, in := range ins {
+		typ := reflect.TypeOf(in)
+		if typ == nil {
+			// Nil indicates interface{}.
+			var empty interface{}
+			typ = reflect.ValueOf(&empty).Elem().Type()
+		}
+		outs[ix] = reflect.New(typ).Interface()
+	}
+	return outs
+}
+
+func checkResultPtrs(t *testing.T, name string, gotptrs, want []interface{}) {
+	for ix, res := range gotptrs {
+		got := reflect.ValueOf(res).Elem().Interface()
+		want := want[ix]
+		switch g := got.(type) {
+		case verror.E:
+			w, ok := want.(verror.E)
+			// don't use reflect deep equal on verror's since they contain
+			// a list of stack PCs which will be different.
+			if !ok {
+				t.Errorf("%s result %d got type %T, want %T", name, ix, g, w)
+			}
+			if verror.ErrorID(g) != w.ID {
+				t.Errorf("%s result %d got %v, want %v", name, ix, g, w)
+			}
+		default:
+			if !reflect.DeepEqual(got, want) {
+				t.Errorf("%s result %d got %v, want %v", name, ix, got, want)
+			}
+		}
+
+	}
+}
+
+func mkCaveat(cav security.Caveat, err error) security.Caveat {
+	if err != nil {
+		panic(err)
+	}
+	return cav
+}
+
+func bless(blesser, blessed security.Principal, extension string, caveats ...security.Caveat) security.Blessings {
+	if len(caveats) == 0 {
+		caveats = append(caveats, security.UnconstrainedUse())
+	}
+	b, err := blesser.Bless(blessed.PublicKey(), blesser.BlessingStore().Default(), extension, caveats[0], caveats[1:]...)
+	if err != nil {
+		panic(err)
+	}
+	return b
+}
+
+func initForTest() (*context.T, v23.Shutdown) {
+	ctx, shutdown := test.V23InitAnon()
+	ctx, err := ivtrace.Init(ctx, flags.VtraceFlags{})
+	if err != nil {
+		panic(err)
+	}
+	ctx, _ = vtrace.WithNewTrace(ctx)
+	return ctx, shutdown
+}
+
+func mkThirdPartyCaveat(discharger security.PublicKey, location string, c security.Caveat) security.Caveat {
+	tpc, err := security.NewPublicKeyCaveat(discharger, location, security.ThirdPartyRequirements{}, c)
+	if err != nil {
+		panic(err)
+	}
+	return tpc
+}
+
+// mockCall implements security.Call
+type mockCall struct {
+	p        security.Principal
+	l, r     security.Blessings
+	m        string
+	ld, rd   security.Discharge
+	lep, rep naming.Endpoint
+}
+
+var _ security.Call = (*mockCall)(nil)
+
+func (c *mockCall) Timestamp() (t time.Time) { return }
+func (c *mockCall) Method() string           { return c.m }
+func (c *mockCall) MethodTags() []*vdl.Value { return nil }
+func (c *mockCall) Suffix() string           { return "" }
+func (c *mockCall) LocalDischarges() map[string]security.Discharge {
+	return map[string]security.Discharge{c.ld.ID(): c.ld}
+}
+func (c *mockCall) RemoteDischarges() map[string]security.Discharge {
+	return map[string]security.Discharge{c.rd.ID(): c.rd}
+}
+func (c *mockCall) LocalEndpoint() naming.Endpoint      { return c.lep }
+func (c *mockCall) RemoteEndpoint() naming.Endpoint     { return c.rep }
+func (c *mockCall) LocalPrincipal() security.Principal  { return c.p }
+func (c *mockCall) LocalBlessings() security.Blessings  { return c.l }
+func (c *mockCall) RemoteBlessings() security.Blessings { return c.r }
diff --git a/runtime/internal/rpc/timer.go b/runtime/internal/rpc/timer.go
new file mode 100644
index 0000000..986124d
--- /dev/null
+++ b/runtime/internal/rpc/timer.go
@@ -0,0 +1,36 @@
+// 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 rpc
+
+import (
+	"time"
+)
+
+// timer is a replacement for time.Timer, the only difference is that
+// its channel is type chan struct{} and it will be closed when the timer expires,
+// which we need in some places.
+type timer struct {
+	base *time.Timer
+	C    <-chan struct{}
+}
+
+func newTimer(d time.Duration) *timer {
+	c := make(chan struct{}, 0)
+	base := time.AfterFunc(d, func() {
+		close(c)
+	})
+	return &timer{
+		base: base,
+		C:    c,
+	}
+}
+
+func (t *timer) Stop() bool {
+	return t.base.Stop()
+}
+
+func (t *timer) Reset(d time.Duration) bool {
+	return t.base.Reset(d)
+}
diff --git a/runtime/internal/rpc/timer_test.go b/runtime/internal/rpc/timer_test.go
new file mode 100644
index 0000000..60f9866
--- /dev/null
+++ b/runtime/internal/rpc/timer_test.go
@@ -0,0 +1,35 @@
+// 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 rpc
+
+import (
+	"testing"
+	"time"
+)
+
+func TestTimer(t *testing.T) {
+	test := newTimer(time.Millisecond)
+	if _, ok := <-test.C; ok {
+		t.Errorf("Expected the channel to be closed.")
+	}
+
+	// Test resetting.
+	test = newTimer(time.Hour)
+	if reset := test.Reset(time.Millisecond); !reset {
+		t.Errorf("Expected to successfully reset.")
+	}
+	if _, ok := <-test.C; ok {
+		t.Errorf("Expected the channel to be closed.")
+	}
+
+	// Test stop.
+	test = newTimer(100 * time.Millisecond)
+	test.Stop()
+	select {
+	case <-test.C:
+		t.Errorf("the test timer should have been stopped.")
+	case <-time.After(200 * time.Millisecond):
+	}
+}
diff --git a/runtime/internal/rpc/transition/dummy.go b/runtime/internal/rpc/transition/dummy.go
new file mode 100644
index 0000000..607072b
--- /dev/null
+++ b/runtime/internal/rpc/transition/dummy.go
@@ -0,0 +1,7 @@
+// 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 transition
+
+// This file allows the package to build despite the fact that it only contains tests.
diff --git a/runtime/internal/rpc/transition/transition_test.go b/runtime/internal/rpc/transition/transition_test.go
new file mode 100644
index 0000000..d50c239
--- /dev/null
+++ b/runtime/internal/rpc/transition/transition_test.go
@@ -0,0 +1,62 @@
+// 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 transition
+
+import (
+	"testing"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+	_ "v.io/x/ref/runtime/factories/generic"
+	irpc "v.io/x/ref/runtime/internal/rpc"
+	"v.io/x/ref/runtime/internal/rpc/stream/manager"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/testutil"
+)
+
+type example struct{}
+
+func (e *example) Echo(ctx *context.T, call rpc.ServerCall, arg string) (string, error) {
+	return arg, nil
+}
+
+func TestTransitionToNew(t *testing.T) {
+	// TODO(mattr): Make a test showing the transition client
+	// connecting to a new server once the new server is available.
+}
+
+func TestTransitionToOld(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	sm := manager.InternalNew(ctx, naming.FixedRoutingID(0x555555555))
+	defer sm.Shutdown()
+
+	sp := testutil.NewPrincipal()
+	testutil.IDProviderFromPrincipal(v23.GetPrincipal(ctx)).Bless(sp, "server")
+	server, err := irpc.InternalNewServer(ctx, sm, v23.GetNamespace(ctx),
+		nil, "", v23.GetClient(ctx))
+	if err != nil {
+		t.Fatal(err)
+	}
+	if _, err = server.Listen(v23.GetListenSpec(ctx)); err != nil {
+		t.Fatal(err)
+	}
+	if err = server.Serve("echo", &example{}, nil); err != nil {
+		t.Fatal(err)
+	}
+
+	var result string
+	err = v23.GetClient(ctx).Call(ctx, "echo", "Echo",
+		[]interface{}{"hello"}, []interface{}{&result})
+	if err != nil {
+		t.Fatal(err)
+	}
+	if result != "hello" {
+		t.Errorf("got %s, wanted hello", result)
+	}
+}
diff --git a/runtime/internal/rpc/transitionclient.go b/runtime/internal/rpc/transitionclient.go
new file mode 100644
index 0000000..41f675e
--- /dev/null
+++ b/runtime/internal/rpc/transitionclient.go
@@ -0,0 +1,72 @@
+// 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 rpc
+
+import (
+	"strings"
+
+	"v.io/v23/context"
+	"v.io/v23/flow/message"
+	"v.io/v23/namespace"
+	"v.io/v23/rpc"
+	"v.io/v23/verror"
+	"v.io/x/ref/runtime/internal/rpc/stream"
+)
+
+type transitionClient struct {
+	c, xc rpc.Client
+}
+
+var _ = rpc.Client((*transitionClient)(nil))
+
+func NewTransitionClient(ctx *context.T, streamMgr stream.Manager, ns namespace.T, opts ...rpc.ClientOpt) (rpc.Client, error) {
+	var err error
+	ret := &transitionClient{}
+	// TODO(mattr): Un-comment this once servers are sending setups before closing
+	// connections in error cases.
+	// if ret.xc, err = InternalNewXClient(ctx, opts...); err != nil {
+	// 	return nil, err
+	// }
+	if ret.c, err = InternalNewClient(streamMgr, ns, opts...); err != nil {
+		ret.xc.Close()
+		return nil, err
+	}
+	return ret, nil
+}
+
+func (t *transitionClient) StartCall(ctx *context.T, name, method string, args []interface{}, opts ...rpc.CallOpt) (rpc.ClientCall, error) {
+	// The agent cannot reconnect, and it's never going to transition to the new
+	// rpc system.  Instead it's moving off of rpc entirely.  For now we detect
+	// and send it to the old rpc system.
+	if t.xc == nil || strings.HasPrefix(name, "/@5@unixfd@") || strings.HasPrefix(name, "/@6@unixfd@") {
+		return t.c.StartCall(ctx, name, method, args, opts...)
+	}
+	call, err := t.xc.StartCall(ctx, name, method, args, opts...)
+	if verror.ErrorID(err) == message.ErrWrongProtocol.ID {
+		call, err = t.c.StartCall(ctx, name, method, args, opts...)
+	}
+	return call, err
+}
+
+func (t *transitionClient) Call(ctx *context.T, name, method string, in, out []interface{}, opts ...rpc.CallOpt) error {
+	// The agent cannot reconnect, and it's never going to transition to the new
+	// rpc system.  Instead it's moving off of rpc entirely.  For now we detect
+	// and send it to the old rpc system.
+	if t.xc == nil || strings.HasPrefix(name, "/@5@unixfd@") || strings.HasPrefix(name, "/@6@unixfd@") {
+		return t.c.Call(ctx, name, method, in, out, opts...)
+	}
+	err := t.xc.Call(ctx, name, method, in, out, opts...)
+	if verror.ErrorID(err) == message.ErrWrongProtocol.ID {
+		err = t.c.Call(ctx, name, method, in, out, opts...)
+	}
+	return err
+}
+
+func (t *transitionClient) Close() {
+	if t.xc != nil {
+		t.xc.Close()
+	}
+	t.c.Close()
+}
diff --git a/runtime/internal/rpc/v23_test.go b/runtime/internal/rpc/v23_test.go
new file mode 100644
index 0000000..fac4210
--- /dev/null
+++ b/runtime/internal/rpc/v23_test.go
@@ -0,0 +1,22 @@
+// 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 rpc_test
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	modules.DispatchAndExitIfChild()
+	os.Exit(m.Run())
+}
diff --git a/runtime/internal/rpc/version/version.go b/runtime/internal/rpc/version/version.go
new file mode 100644
index 0000000..06cc3cd
--- /dev/null
+++ b/runtime/internal/rpc/version/version.go
@@ -0,0 +1,101 @@
+// 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 (
+	"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
+// implementation.
+//
+// Max is incremented whenever we make a protocol change that's not both forward
+// and backward compatible.
+//
+// Min is incremented whenever we want to remove support for old protocol
+// versions.
+var SupportedRange = &Range{Min: version.RPCVersion10, Max: version.RPCVersion11}
+var Supported = version.RPCVersionRange{Min: version.RPCVersion10, Max: version.RPCVersion11}
+
+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
+}
diff --git a/runtime/internal/rpc/version/version_test.go b/runtime/internal/rpc/version/version_test.go
new file mode 100644
index 0000000..652baf8
--- /dev/null
+++ b/runtime/internal/rpc/version/version_test.go
@@ -0,0 +1,51 @@
+// 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/rpc/xclient.go b/runtime/internal/rpc/xclient.go
new file mode 100644
index 0000000..1f5dcb5
--- /dev/null
+++ b/runtime/internal/rpc/xclient.go
@@ -0,0 +1,684 @@
+// 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 rpc
+
+import (
+	"fmt"
+	"io"
+	"net"
+	"sync"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/flow"
+	"v.io/v23/flow/message"
+	"v.io/v23/i18n"
+	"v.io/v23/namespace"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	vtime "v.io/v23/vdlroot/time"
+	"v.io/v23/verror"
+	"v.io/v23/vom"
+	"v.io/v23/vtrace"
+	"v.io/x/ref/lib/apilog"
+	inaming "v.io/x/ref/runtime/internal/naming"
+)
+
+type xclient struct {
+	flowMgr            flow.Manager
+	ns                 namespace.T
+	preferredProtocols []string
+
+	// We cache the IP networks on the device since it is not that cheap to read
+	// network interfaces through os syscall.
+	// TODO(toddw): this can be removed since netstate now implements caching
+	// directly.
+	ipNets []*net.IPNet
+
+	wg     sync.WaitGroup
+	mu     sync.Mutex
+	closed bool
+}
+
+var _ rpc.Client = (*xclient)(nil)
+
+func InternalNewXClient(ctx *context.T, opts ...rpc.ClientOpt) (rpc.Client, error) {
+	c := &xclient{
+		flowMgr: v23.ExperimentalGetFlowManager(ctx),
+		ns:      v23.GetNamespace(ctx),
+	}
+	ipNets, err := ipNetworks()
+	if err != nil {
+		return nil, err
+	}
+	c.ipNets = ipNets
+	for _, opt := range opts {
+		switch v := opt.(type) {
+		case PreferredProtocols:
+			c.preferredProtocols = v
+		}
+	}
+	return c, nil
+}
+
+func (c *xclient) StartCall(ctx *context.T, name, method string, args []interface{}, opts ...rpc.CallOpt) (rpc.ClientCall, error) {
+	defer apilog.LogCallf(ctx, "name=%.10s...,method=%.10s...,args=,opts...=%v", name, method, opts)(ctx, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	if !ctx.Initialized() {
+		return nil, verror.ExplicitNew(verror.ErrBadArg, i18n.LangID("en-us"), "<rpc.Client>", "StartCall", "context not initialized")
+	}
+	deadline := getDeadline(ctx, opts)
+	return c.startCall(ctx, name, method, args, deadline, opts)
+}
+
+func (c *xclient) startCall(ctx *context.T, name, method string, args []interface{}, deadline time.Time, opts []rpc.CallOpt) (rpc.ClientCall, error) {
+	ctx, span := vtrace.WithNewSpan(ctx, fmt.Sprintf("<rpc.Client>%q.%s", name, method))
+	for retries := uint(0); ; retries++ {
+		switch call, action, requireResolve, err := c.tryCall(ctx, name, method, args, opts); {
+		case err == nil:
+			return call, nil
+		case !shouldRetry(action, requireResolve, deadline, opts):
+			span.Annotatef("Cannot retry after error: %s", err)
+			return nil, err
+		case !backoff(retries, deadline):
+			return nil, err
+		default:
+			span.Annotatef("Retrying due to error: %s", err)
+		}
+	}
+}
+
+func (c *xclient) Call(ctx *context.T, name, method string, inArgs, outArgs []interface{}, opts ...rpc.CallOpt) error {
+	defer apilog.LogCallf(ctx, "name=%.10s...,method=%.10s...,inArgs=,outArgs=,opts...=%v", name, method, opts)(ctx, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	deadline := getDeadline(ctx, opts)
+	for retries := uint(0); ; retries++ {
+		call, err := c.startCall(ctx, name, method, inArgs, deadline, opts)
+		if err != nil {
+			return err
+		}
+		switch err := call.Finish(outArgs...); {
+		case err == nil:
+			return nil
+		case !shouldRetryBackoff(verror.Action(err), deadline, opts):
+			ctx.VI(4).Infof("Cannot retry after error: %s", err)
+			return err
+		case !backoff(retries, deadline):
+			return err
+		default:
+			ctx.VI(4).Infof("Retrying due to error: %s", err)
+		}
+	}
+}
+
+type xserverStatus struct {
+	index          int
+	server, suffix string
+	flow           flow.Flow
+	serverErr      *verror.SubErr
+}
+
+// tryCreateFlow attempts to establish a Flow to "server" (which must be a
+// rooted name), over which a method invocation request could be sent.
+//
+// The server at the remote end of the flow is authorized using the provided
+// authorizer, both during creation of the VC underlying the flow and the
+// flow itself.
+// TODO(cnicolaou): implement real, configurable load balancing.
+func (c *xclient) tryCreateFlow(ctx *context.T, index int, name, server, method string, auth security.Authorizer, ch chan<- *xserverStatus) {
+	defer c.wg.Done()
+	status := &xserverStatus{index: index, server: server}
+	var span vtrace.Span
+	ctx, span = vtrace.WithNewSpan(ctx, "<client>tryCreateFlow")
+	span.Annotatef("address:%v", server)
+	defer func() {
+		ch <- status
+		span.Finish()
+	}()
+	suberr := func(err error) *verror.SubErr {
+		return &verror.SubErr{
+			Name:    suberrName(server, name, method),
+			Err:     err,
+			Options: verror.Print,
+		}
+	}
+
+	address, suffix := naming.SplitAddressName(server)
+	if len(address) == 0 {
+		status.serverErr = suberr(verror.New(errNonRootedName, ctx, server))
+		return
+	}
+	status.suffix = suffix
+
+	ep, err := inaming.NewEndpoint(address)
+	if err != nil {
+		status.serverErr = suberr(verror.New(errInvalidEndpoint, ctx))
+		return
+	}
+	flow, err := c.flowMgr.Dial(ctx, ep, blessingsForPeer{auth, method, suffix}.run)
+	if err != nil {
+		ctx.VI(2).Infof("rpc: failed to create Flow with %v: %v", server, err)
+		status.serverErr = suberr(err)
+		return
+	}
+	status.flow = flow
+}
+
+type blessingsForPeer struct {
+	auth   security.Authorizer
+	method string
+	suffix string
+}
+
+func (x blessingsForPeer) run(
+	ctx *context.T,
+	localEP, remoteEP naming.Endpoint,
+	remoteBlessings security.Blessings,
+	remoteDischarges map[string]security.Discharge) (security.Blessings, map[string]security.Discharge, error) {
+	localPrincipal := v23.GetPrincipal(ctx)
+	call := security.NewCall(&security.CallParams{
+		Timestamp:        time.Now(),
+		Method:           x.method,
+		Suffix:           x.suffix,
+		LocalPrincipal:   localPrincipal,
+		LocalEndpoint:    localEP,
+		RemoteBlessings:  remoteBlessings,
+		RemoteDischarges: remoteDischarges,
+		RemoteEndpoint:   remoteEP,
+		// TODO(toddw): MethodTags, LocalDischarges
+	})
+	if err := x.auth.Authorize(ctx, call); err != nil {
+		return security.Blessings{}, nil, verror.New(errServerAuthorizeFailed, ctx, call.RemoteBlessings(), err)
+	}
+	serverB, serverBRejected := security.RemoteBlessingNames(ctx, call)
+	clientB := localPrincipal.BlessingStore().ForPeer(serverB...)
+	if clientB.IsZero() {
+		// TODO(ataly, ashankar): We need not error out here and instead can just
+		// send the <nil> blessings to the server.
+		return security.Blessings{}, nil, verror.New(errNoBlessingsForPeer, ctx, serverB, serverBRejected)
+	}
+	// TODO(toddw): Return discharge map.
+	return clientB, nil, nil
+}
+
+// tryCall makes a single attempt at a call. It may connect to multiple servers
+// (all that serve "name"), but will invoke the method on at most one of them
+// (the server running on the most preferred protcol and network amongst all
+// the servers that were successfully connected to and authorized).
+// if requireResolve is true on return, then we shouldn't bother retrying unless
+// you can re-resolve.
+//
+// TODO(toddw): Remove action from out-args, the error should tell us the action.
+func (c *xclient) tryCall(ctx *context.T, name, method string, args []interface{}, opts []rpc.CallOpt) (call rpc.ClientCall, action verror.ActionCode, requireResolve bool, err error) {
+	blessingPattern, name := security.SplitPatternName(name)
+	resolved, err := c.ns.Resolve(ctx, name, getNamespaceOpts(opts)...)
+	switch {
+	case verror.ErrorID(err) == naming.ErrNoSuchName.ID:
+		return nil, verror.RetryRefetch, false, verror.New(verror.ErrNoServers, ctx, name)
+	case verror.ErrorID(err) == verror.ErrNoServers.ID:
+		return nil, verror.NoRetry, false, err // avoid unnecessary wrapping
+	case verror.ErrorID(err) == verror.ErrTimeout.ID:
+		return nil, verror.NoRetry, false, err // return timeout without wrapping
+	case err != nil:
+		return nil, verror.NoRetry, false, verror.New(verror.ErrNoServers, ctx, name, err)
+	case len(resolved.Servers) == 0:
+		// This should never happen.
+		return nil, verror.NoRetry, true, verror.New(verror.ErrInternal, ctx, name)
+	}
+	if resolved.Servers, err = filterAndOrderServers(resolved.Servers, c.preferredProtocols, c.ipNets); err != nil {
+		return nil, verror.RetryRefetch, true, verror.New(verror.ErrNoServers, ctx, name, err)
+	}
+
+	// servers is now ordered by the priority heurestic implemented in
+	// filterAndOrderServers.
+	//
+	// Try to connect to all servers in parallel.  Provide sufficient
+	// buffering for all of the connections to finish instantaneously. This
+	// is important because we want to process the responses in priority
+	// order; that order is indicated by the order of entries in servers.
+	// So, if two respones come in at the same 'instant', we prefer the
+	// first in the resolved.Servers)
+	//
+	// TODO(toddw): Refactor the parallel dials so that the policy can be changed,
+	// and so that the goroutines for each Call are tracked separately.
+	responses := make([]*xserverStatus, len(resolved.Servers))
+	ch := make(chan *xserverStatus, len(resolved.Servers))
+	authorizer := newServerAuthorizer(blessingPattern, opts...)
+	for i, server := range resolved.Names() {
+		c.mu.Lock()
+		if c.closed {
+			c.mu.Unlock()
+			return nil, verror.NoRetry, false, verror.New(errClientCloseAlreadyCalled, ctx)
+		}
+		c.wg.Add(1)
+		c.mu.Unlock()
+
+		go c.tryCreateFlow(ctx, i, name, server, method, authorizer, ch)
+	}
+
+	for {
+		// Block for at least one new response from the server, or the timeout.
+		select {
+		case r := <-ch:
+			responses[r.index] = r
+			// Read as many more responses as we can without blocking.
+		LoopNonBlocking:
+			for {
+				select {
+				default:
+					break LoopNonBlocking
+				case r := <-ch:
+					responses[r.index] = r
+				}
+			}
+		case <-ctx.Done():
+			ctx.VI(2).Infof("rpc: timeout on connection to server %v ", name)
+			_, _, _, err := c.failedTryCall(ctx, name, method, responses, ch)
+			if verror.ErrorID(err) != verror.ErrTimeout.ID {
+				return nil, verror.NoRetry, false, verror.New(verror.ErrTimeout, ctx, err)
+			}
+			return nil, verror.NoRetry, false, err
+		}
+
+		// Process new responses, in priority order.
+		numResponses := 0
+		for _, r := range responses {
+			if r != nil {
+				numResponses++
+				if verror.ErrorID(r.serverErr.Err) == message.ErrWrongProtocol.ID {
+					return nil, verror.NoRetry, false, r.serverErr.Err
+				}
+			}
+			if r == nil || r.flow == nil {
+				continue
+			}
+
+			fc, err := newFlowXClient(ctx, r.flow)
+			if err != nil {
+				return nil, verror.NoRetry, false, err
+			}
+
+			// This is the 'point of no return'; once the RPC is started (fc.start
+			// below) we can't be sure if it makes it to the server or not so, this
+			// code will never call fc.start more than once to ensure that we provide
+			// 'at-most-once' rpc semantics at this level. Retrying the network
+			// connections (i.e. creating flows) is fine since we can cleanup that
+			// state if we abort a call (i.e. close the flow).
+			//
+			// We must ensure that all flows other than r.flow are closed.
+			//
+			// TODO(cnicolaou): all errors below are marked as NoRetry
+			// because we want to provide at-most-once rpc semantics so
+			// we only ever attempt an RPC once. In the future, we'll cache
+			// responses on the server and then we can retry in-flight
+			// RPCs.
+			go xcleanupTryCall(r, responses, ch)
+
+			// TODO(toddw): It's wasteful to create this goroutine just for a vtrace
+			// annotation.  Refactor this when we refactor the parallel dial logic.
+			/*
+				if ctx.Done() != nil {
+					go func() {
+						select {
+						case <-ctx.Done():
+							vtrace.GetSpan(fc.ctx).Annotate("Canceled")
+						case <-fc.flow.Closed():
+						}
+					}()
+				}
+			*/
+
+			deadline, _ := ctx.Deadline()
+			if verr := fc.start(r.suffix, method, args, deadline); verr != nil {
+				return nil, verror.NoRetry, false, verr
+			}
+			return fc, verror.NoRetry, false, nil
+		}
+		if numResponses == len(responses) {
+			return c.failedTryCall(ctx, name, method, responses, ch)
+		}
+	}
+}
+
+// xcleanupTryCall ensures we've waited for every response from the tryCreateFlow
+// goroutines, and have closed the flow from each one except skip.  This is a
+// blocking function; it should be called in its own goroutine.
+func xcleanupTryCall(skip *xserverStatus, responses []*xserverStatus, ch chan *xserverStatus) {
+	numPending := 0
+	for _, r := range responses {
+		switch {
+		case r == nil:
+			// The response hasn't arrived yet.
+			numPending++
+		case r == skip || r.flow == nil:
+			// Either we should skip this flow, or we've closed the flow for this
+			// response already; nothing more to do.
+		default:
+			// We received the response, but haven't closed the flow yet.
+			//
+			// TODO(toddw): Currently we only notice cancellation when we read or
+			// write the flow.  Decide how to handle this.
+			r.flow.WriteMsgAndClose() // TODO(toddw): cancel context instead?
+		}
+	}
+	// Now we just need to wait for the pending responses and close their flows.
+	for i := 0; i < numPending; i++ {
+		if r := <-ch; r.flow != nil {
+			r.flow.WriteMsgAndClose() // TODO(toddw): cancel context instead?
+		}
+	}
+}
+
+// failedTryCall performs asynchronous cleanup for tryCall, and returns an
+// appropriate error from the responses we've already received.  All parallel
+// calls in tryCall failed or we timed out if we get here.
+func (c *xclient) failedTryCall(ctx *context.T, name, method string, responses []*xserverStatus, ch chan *xserverStatus) (rpc.ClientCall, verror.ActionCode, bool, error) {
+	go xcleanupTryCall(nil, responses, ch)
+	c.ns.FlushCacheEntry(ctx, name)
+	suberrs := []verror.SubErr{}
+	topLevelError := verror.ErrNoServers
+	topLevelAction := verror.RetryRefetch
+	onlyErrNetwork := true
+	for _, r := range responses {
+		if r != nil && r.serverErr != nil && r.serverErr.Err != nil {
+			switch verror.ErrorID(r.serverErr.Err) {
+			case /*stream.ErrNotTrusted.ID,*/ verror.ErrNotTrusted.ID, errServerAuthorizeFailed.ID:
+				topLevelError = verror.ErrNotTrusted
+				topLevelAction = verror.NoRetry
+				onlyErrNetwork = false
+			/*case stream.ErrAborted.ID, stream.ErrNetwork.ID:*/
+			// do nothing
+			default:
+				onlyErrNetwork = false
+			}
+			suberrs = append(suberrs, *r.serverErr)
+		}
+	}
+
+	if onlyErrNetwork {
+		// If we only encountered network errors, then report ErrBadProtocol.
+		topLevelError = verror.ErrBadProtocol
+	}
+
+	// TODO(cnicolaou): we get system errors for things like dialing using
+	// the 'ws' protocol which can never succeed even if we retry the connection,
+	// hence we return RetryRefetch below except for the case where the servers
+	// are not trusted, in case there's no point in retrying at all.
+	// TODO(cnicolaou): implementing at-most-once rpc semantics in the future
+	// will require thinking through all of the cases where the RPC can
+	// be retried by the client whilst it's actually being executed on the
+	// server.
+	return nil, topLevelAction, false, verror.AddSubErrs(verror.New(topLevelError, ctx), ctx, suberrs...)
+}
+
+func (c *xclient) Close() {
+	defer apilog.LogCall(nil)(nil) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	c.mu.Lock()
+	c.closed = true
+	c.mu.Unlock()
+	// TODO(toddw): Implement this!
+	c.wg.Wait()
+}
+
+// flowXClient implements the RPC client-side protocol for a single RPC, over a
+// flow that's already connected to the server.
+type flowXClient struct {
+	ctx      *context.T   // context to annotate with call details
+	flow     flow.Flow    // the underlying flow
+	dec      *vom.Decoder // to decode responses and results from the server
+	enc      *vom.Encoder // to encode requests and args to the server
+	response rpc.Response // each decoded response message is kept here
+
+	grantedBlessings security.Blessings // the blessings granted to the server.
+
+	sendClosedMu sync.Mutex
+	sendClosed   bool // is the send side already closed? GUARDED_BY(sendClosedMu)
+	finished     bool // has Finish() already been called?
+}
+
+var _ rpc.ClientCall = (*flowXClient)(nil)
+var _ rpc.Stream = (*flowXClient)(nil)
+
+func newFlowXClient(ctx *context.T, flow flow.Flow) (*flowXClient, error) {
+	fc := &flowXClient{
+		ctx:  ctx,
+		flow: flow,
+		dec:  vom.NewDecoder(flow),
+		enc:  vom.NewEncoder(flow),
+	}
+	// TODO(toddw): Add logic to create separate type flows!
+	return fc, nil
+}
+
+// close determines the appropriate error to return, in particular,
+// if a timeout or cancelation has occured then any error
+// is turned into a timeout or cancelation as appropriate.
+// Cancelation takes precedence over timeout. This is needed because
+// a timeout can lead to any other number of errors due to the underlying
+// network connection being shutdown abruptly.
+func (fc *flowXClient) close(err error) error {
+	subErr := verror.SubErr{Err: err, Options: verror.Print}
+	subErr.Name = "remote=" + fc.flow.Conn().RemoteEndpoint().String()
+	// TODO(toddw): cancel context instead?
+	if _, cerr := fc.flow.WriteMsgAndClose(); cerr != nil && err == nil {
+		return verror.New(verror.ErrInternal, fc.ctx, subErr)
+	}
+	if err == nil {
+		return nil
+	}
+	switch verror.ErrorID(err) {
+	case verror.ErrCanceled.ID:
+		return err
+	case verror.ErrTimeout.ID:
+		// Canceled trumps timeout.
+		if fc.ctx.Err() == context.Canceled {
+			return verror.AddSubErrs(verror.New(verror.ErrCanceled, fc.ctx), fc.ctx, subErr)
+		}
+		return err
+	default:
+		switch fc.ctx.Err() {
+		case context.DeadlineExceeded:
+			timeout := verror.New(verror.ErrTimeout, fc.ctx)
+			err := verror.AddSubErrs(timeout, fc.ctx, subErr)
+			return err
+		case context.Canceled:
+			canceled := verror.New(verror.ErrCanceled, fc.ctx)
+			err := verror.AddSubErrs(canceled, fc.ctx, subErr)
+			return err
+		}
+	}
+	switch verror.ErrorID(err) {
+	case errRequestEncoding.ID, errArgEncoding.ID, errResponseDecoding.ID:
+		return verror.New(verror.ErrBadProtocol, fc.ctx, err)
+	}
+	return err
+}
+
+func (fc *flowXClient) start(suffix, method string, args []interface{}, deadline time.Time) error {
+	req := rpc.Request{
+		Suffix:     suffix,
+		Method:     method,
+		NumPosArgs: uint64(len(args)),
+		Deadline:   vtime.Deadline{Time: deadline},
+		// TODO(toddw): Handle GrantedBlessings.
+		TraceRequest: vtrace.GetRequest(fc.ctx),
+		Language:     string(i18n.GetLangID(fc.ctx)),
+	}
+	if err := fc.enc.Encode(req); err != nil {
+		berr := verror.New(verror.ErrBadProtocol, fc.ctx, verror.New(errRequestEncoding, fc.ctx, fmt.Sprintf("%#v", req), err))
+		return fc.close(berr)
+	}
+	for ix, arg := range args {
+		if err := fc.enc.Encode(arg); err != nil {
+			berr := verror.New(errArgEncoding, fc.ctx, ix, err)
+			return fc.close(berr)
+		}
+	}
+	return nil
+}
+
+func (fc *flowXClient) Send(item interface{}) error {
+	defer apilog.LogCallf(nil, "item=")(nil, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	if fc.sendClosed {
+		return verror.New(verror.ErrAborted, fc.ctx)
+	}
+
+	// The empty request header indicates what follows is a streaming arg.
+	if err := fc.enc.Encode(rpc.Request{}); err != nil {
+		berr := verror.New(errRequestEncoding, fc.ctx, rpc.Request{}, err)
+		return fc.close(berr)
+	}
+	if err := fc.enc.Encode(item); err != nil {
+		berr := verror.New(errArgEncoding, fc.ctx, -1, err)
+		return fc.close(berr)
+	}
+	return nil
+}
+
+func (fc *flowXClient) Recv(itemptr interface{}) error {
+	defer apilog.LogCallf(nil, "itemptr=")(nil, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	switch {
+	case fc.response.Error != nil:
+		return verror.New(verror.ErrBadProtocol, fc.ctx, fc.response.Error)
+	case fc.response.EndStreamResults:
+		return io.EOF
+	}
+
+	// Decode the response header and handle errors and EOF.
+	if err := fc.dec.Decode(&fc.response); err != nil {
+		id, verr := decodeNetError(fc.ctx, err)
+		berr := verror.New(id, fc.ctx, verror.New(errResponseDecoding, fc.ctx, verr))
+		return fc.close(berr)
+	}
+	if fc.response.Error != nil {
+		return fc.response.Error
+	}
+	if fc.response.EndStreamResults {
+		// Return EOF to indicate to the caller that there are no more stream
+		// results.  Any error sent by the server is kept in fc.response.Error, and
+		// returned to the user in Finish.
+		return io.EOF
+	}
+	// Decode the streaming result.
+	if err := fc.dec.Decode(itemptr); err != nil {
+		id, verr := decodeNetError(fc.ctx, err)
+		berr := verror.New(id, fc.ctx, verror.New(errResponseDecoding, fc.ctx, verr))
+		// TODO(cnicolaou): should we be caching this?
+		fc.response.Error = berr
+		return fc.close(berr)
+	}
+	return nil
+}
+
+func (fc *flowXClient) CloseSend() error {
+	defer apilog.LogCall(nil)(nil) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	return fc.closeSend()
+}
+
+func (fc *flowXClient) closeSend() error {
+	fc.sendClosedMu.Lock()
+	defer fc.sendClosedMu.Unlock()
+	if fc.sendClosed {
+		return nil
+	}
+	if err := fc.enc.Encode(rpc.Request{EndStreamArgs: true}); err != nil {
+		// TODO(caprita): Indiscriminately closing the flow below causes
+		// a race as described in:
+		// https://docs.google.com/a/google.com/document/d/1C0kxfYhuOcStdV7tnLZELZpUhfQCZj47B0JrzbE29h8/edit
+		//
+		// There should be a finer grained way to fix this (for example,
+		// encoding errors should probably still result in closing the
+		// flow); on the flip side, there may exist other instances
+		// where we are closing the flow but should not.
+		//
+		// For now, commenting out the line below removes the flakiness
+		// from our existing unit tests, but this needs to be revisited
+		// and fixed correctly.
+		//
+		//   return fc.close(verror.ErrBadProtocolf("rpc: end stream args encoding failed: %v", err))
+	}
+	fc.sendClosed = true
+	return nil
+}
+
+// TODO(toddw): Should we require Finish to be called, even if send or recv
+// return an error?
+func (fc *flowXClient) Finish(resultptrs ...interface{}) error {
+	defer apilog.LogCallf(nil, "resultptrs...=%v", resultptrs)(nil, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	defer vtrace.GetSpan(fc.ctx).Finish()
+	if fc.finished {
+		err := verror.New(errClientFinishAlreadyCalled, fc.ctx)
+		return fc.close(verror.New(verror.ErrBadState, fc.ctx, err))
+	}
+	fc.finished = true
+
+	// Call closeSend implicitly, if the user hasn't already called it.  There are
+	// three cases:
+	// 1) Server is blocked on Recv waiting for the final request message.
+	// 2) Server has already finished processing, the final response message and
+	//    out args are queued up on the client, and the flow is closed.
+	// 3) Between 1 and 2: the server isn't blocked on Recv, but the final
+	//    response and args aren't queued up yet, and the flow isn't closed.
+	//
+	// We must call closeSend to handle case (1) and unblock the server; otherwise
+	// we'll deadlock with both client and server waiting for each other.  We must
+	// ignore the error (if any) to handle case (2).  In that case the flow is
+	// closed, meaning writes will fail and reads will succeed, and closeSend will
+	// always return an error.  But this isn't a "real" error; the client should
+	// read the rest of the results and succeed.
+	_ = fc.closeSend()
+	// Decode the response header, if it hasn't already been decoded by Recv.
+	if fc.response.Error == nil && !fc.response.EndStreamResults {
+		if err := fc.dec.Decode(&fc.response); err != nil {
+			id, verr := decodeNetError(fc.ctx, err)
+			berr := verror.New(id, fc.ctx, verror.New(errResponseDecoding, fc.ctx, verr))
+			return fc.close(berr)
+		}
+		// The response header must indicate the streaming results have ended.
+		if fc.response.Error == nil && !fc.response.EndStreamResults {
+			berr := verror.New(errRemainingStreamResults, fc.ctx)
+			return fc.close(berr)
+		}
+	}
+	// Incorporate any VTrace info that was returned.
+	vtrace.GetStore(fc.ctx).Merge(fc.response.TraceResponse)
+	if fc.response.Error != nil {
+		id := verror.ErrorID(fc.response.Error)
+		/*
+		   TODO(toddw): We need to invalidate discharges somehow; there's a method
+		   on the BlessingStore to do this.
+
+		   		if id == verror.ErrNoAccess.ID && fc.dc != nil {
+		   			// In case the error was caused by a bad discharge, we do not want to get stuck
+		   			// with retrying again and again with this discharge. As there is no direct way
+		   			// to detect it, we conservatively flush all discharges we used from the cache.
+		   			// TODO(ataly,andreser): add verror.BadDischarge and handle it explicitly?
+		   			fc.ctx.VI(3).Infof("Discarding %d discharges as RPC failed with %v", len(fc.discharges), fc.response.Error)
+		   			fc.dc.Invalidate(fc.ctx, fc.discharges...)
+		   		}
+		*/
+		if id == errBadNumInputArgs.ID || id == errBadInputArg.ID {
+			return fc.close(verror.New(verror.ErrBadProtocol, fc.ctx, fc.response.Error))
+		}
+		return fc.close(verror.Convert(verror.ErrInternal, fc.ctx, fc.response.Error))
+	}
+	if got, want := fc.response.NumPosResults, uint64(len(resultptrs)); got != want {
+		berr := verror.New(verror.ErrBadProtocol, fc.ctx, verror.New(errMismatchedResults, fc.ctx, got, want))
+		return fc.close(berr)
+	}
+	for ix, r := range resultptrs {
+		if err := fc.dec.Decode(r); err != nil {
+			id, verr := decodeNetError(fc.ctx, err)
+			berr := verror.New(id, fc.ctx, verror.New(errResultDecoding, fc.ctx, ix, verr))
+			return fc.close(berr)
+		}
+	}
+	return fc.close(nil)
+}
+
+func (fc *flowXClient) RemoteBlessings() ([]string, security.Blessings) {
+	defer apilog.LogCall(nil)(nil) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	return nil /*TODO(toddw)*/, fc.flow.RemoteBlessings()
+}
diff --git a/runtime/internal/rpc/xserver.go b/runtime/internal/rpc/xserver.go
new file mode 100644
index 0000000..3907975
--- /dev/null
+++ b/runtime/internal/rpc/xserver.go
@@ -0,0 +1,815 @@
+// 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 rpc
+
+import (
+	"fmt"
+	"io"
+	"net"
+	"strings"
+	"sync"
+	"time"
+
+	"v.io/x/lib/netstate"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/flow"
+	"v.io/v23/i18n"
+	"v.io/v23/namespace"
+	"v.io/v23/naming"
+	"v.io/v23/options"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/security/access"
+	"v.io/v23/vdl"
+	"v.io/v23/verror"
+	"v.io/v23/vom"
+	"v.io/v23/vtrace"
+
+	"v.io/x/ref/lib/apilog"
+	"v.io/x/ref/lib/pubsub"
+	"v.io/x/ref/lib/stats"
+	"v.io/x/ref/runtime/internal/lib/publisher"
+	inaming "v.io/x/ref/runtime/internal/naming"
+)
+
+// TODO(mattr): add/removeAddresses
+// TODO(mattr): dhcpLoop
+
+type xserver struct {
+	sync.Mutex
+	// context used by the server to make internal RPCs, error messages etc.
+	ctx               *context.T
+	cancel            context.CancelFunc // function to cancel the above context.
+	flowMgr           flow.Manager
+	publisher         publisher.Publisher // publisher to publish mounttable mounts.
+	settingsPublisher *pubsub.Publisher   // pubsub publisher for dhcp
+	settingsName      string              // pubwsub stream name for dhcp
+	dhcpState         *dhcpState          // dhcpState, nil if not using dhcp
+	principal         security.Principal
+	blessings         security.Blessings
+	protoEndpoints    []*inaming.Endpoint
+	chosenEndpoints   []*inaming.Endpoint
+
+	// state of proxies keyed by the name of the proxy
+	proxies map[string]proxyState
+
+	disp               rpc.Dispatcher // dispatcher to serve RPCs
+	dispReserved       rpc.Dispatcher // dispatcher for reserved methods
+	active             sync.WaitGroup // active goroutines we've spawned.
+	stoppedChan        chan struct{}  // closed when the server has been stopped.
+	preferredProtocols []string       // protocols to use when resolving proxy name to endpoint.
+	// We cache the IP networks on the device since it is not that cheap to read
+	// network interfaces through os syscall.
+	// TODO(jhahn): Add monitoring the network interface changes.
+	ipNets           []*net.IPNet
+	ns               namespace.T
+	servesMountTable bool
+	isLeaf           bool
+
+	// TODO(cnicolaou): add roaming stats to rpcStats
+	stats *rpcStats // stats for this server.
+}
+
+func InternalNewXServer(ctx *context.T, settingsPublisher *pubsub.Publisher, settingsName string, opts ...rpc.ServerOpt) (rpc.XServer, error) {
+	ctx, cancel := context.WithRootCancel(ctx)
+	flowMgr := v23.ExperimentalGetFlowManager(ctx)
+	ns, principal := v23.GetNamespace(ctx), v23.GetPrincipal(ctx)
+	statsPrefix := naming.Join("rpc", "server", "routing-id", flowMgr.RoutingID().String())
+	s := &xserver{
+		ctx:               ctx,
+		cancel:            cancel,
+		flowMgr:           flowMgr,
+		principal:         principal,
+		blessings:         principal.BlessingStore().Default(),
+		publisher:         publisher.New(ctx, ns, publishPeriod),
+		proxies:           make(map[string]proxyState),
+		stoppedChan:       make(chan struct{}),
+		ns:                ns,
+		stats:             newRPCStats(statsPrefix),
+		settingsPublisher: settingsPublisher,
+		settingsName:      settingsName,
+	}
+	ipNets, err := ipNetworks()
+	if err != nil {
+		return nil, err
+	}
+	s.ipNets = ipNets
+
+	for _, opt := range opts {
+		switch opt := opt.(type) {
+		case options.ServesMountTable:
+			s.servesMountTable = bool(opt)
+		case options.IsLeaf:
+			s.isLeaf = bool(opt)
+		case ReservedNameDispatcher:
+			s.dispReserved = opt.Dispatcher
+		case PreferredServerResolveProtocols:
+			s.preferredProtocols = []string(opt)
+		}
+	}
+
+	blessingsStatsName := naming.Join(statsPrefix, "security", "blessings")
+	// TODO(caprita): revist printing the blessings with %s, and
+	// instead expose them as a list.
+	stats.NewString(blessingsStatsName).Set(fmt.Sprintf("%s", s.blessings))
+	stats.NewStringFunc(blessingsStatsName, func() string {
+		return fmt.Sprintf("%s (default)", s.principal.BlessingStore().Default())
+	})
+	return s, nil
+}
+
+func (s *xserver) Status() rpc.ServerStatus {
+	return rpc.ServerStatus{}
+}
+
+func (s *xserver) WatchNetwork(ch chan<- rpc.NetworkChange) {
+	defer apilog.LogCallf(nil, "ch=")(nil, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	s.Lock()
+	defer s.Unlock()
+	if s.dhcpState != nil {
+		s.dhcpState.watchers[ch] = struct{}{}
+	}
+}
+
+func (s *xserver) UnwatchNetwork(ch chan<- rpc.NetworkChange) {
+	defer apilog.LogCallf(nil, "ch=")(nil, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	s.Lock()
+	defer s.Unlock()
+	if s.dhcpState != nil {
+		delete(s.dhcpState.watchers, ch)
+	}
+}
+
+// resolveToEndpoint resolves an object name or address to an endpoint.
+func (s *xserver) resolveToEndpoint(address string) (string, error) {
+	var resolved *naming.MountEntry
+	var err error
+	if s.ns != nil {
+		if resolved, err = s.ns.Resolve(s.ctx, address); err != nil {
+			return "", err
+		}
+	} else {
+		// Fake a namespace resolution
+		resolved = &naming.MountEntry{Servers: []naming.MountedServer{
+			{Server: address},
+		}}
+	}
+	// An empty set of protocols means all protocols...
+	if resolved.Servers, err = filterAndOrderServers(resolved.Servers, s.preferredProtocols, s.ipNets); err != nil {
+		return "", err
+	}
+	for _, n := range resolved.Names() {
+		address, suffix := naming.SplitAddressName(n)
+		if suffix != "" {
+			continue
+		}
+		if ep, err := inaming.NewEndpoint(address); err == nil {
+			return ep.String(), nil
+		}
+	}
+	return "", verror.New(errFailedToResolveToEndpoint, s.ctx, address)
+}
+
+// createEndpoints creates appropriate inaming.Endpoint instances for
+// all of the externally accessible network addresses that can be used
+// to reach this server.
+func (s *xserver) createEndpoints(lep naming.Endpoint, chooser netstate.AddressChooser) ([]*inaming.Endpoint, string, bool, error) {
+	iep, ok := lep.(*inaming.Endpoint)
+	if !ok {
+		return nil, "", false, verror.New(errInternalTypeConversion, nil, fmt.Sprintf("%T", lep))
+	}
+	if !strings.HasPrefix(iep.Protocol, "tcp") &&
+		!strings.HasPrefix(iep.Protocol, "ws") {
+		// If not tcp, ws, or wsh, just return the endpoint we were given.
+		return []*inaming.Endpoint{iep}, "", false, nil
+	}
+	host, port, err := net.SplitHostPort(iep.Address)
+	if err != nil {
+		return nil, "", false, err
+	}
+	addrs, unspecified, err := netstate.PossibleAddresses(iep.Protocol, host, chooser)
+	if err != nil {
+		return nil, port, false, err
+	}
+
+	ieps := make([]*inaming.Endpoint, 0, len(addrs))
+	for _, addr := range addrs {
+		n, err := inaming.NewEndpoint(lep.String())
+		if err != nil {
+			return nil, port, false, err
+		}
+		n.IsMountTable = s.servesMountTable
+		n.Address = net.JoinHostPort(addr.String(), port)
+		ieps = append(ieps, n)
+	}
+	return ieps, port, unspecified, nil
+}
+
+func (s *xserver) listen(ctx *context.T, listenSpec rpc.ListenSpec) error {
+	s.Lock()
+	defer s.Unlock()
+
+	var lastErr error
+	for _, addr := range listenSpec.Addrs {
+		if len(addr.Address) > 0 {
+			lastErr = s.flowMgr.Listen(ctx, addr.Protocol, addr.Address)
+			s.ctx.VI(2).Infof("Listen(%q, %q, ...) failed: %v", addr.Protocol, addr.Address, lastErr)
+		}
+	}
+
+	leps := s.flowMgr.ListeningEndpoints()
+	if len(leps) == 0 {
+		return verror.New(verror.ErrBadArg, s.ctx, verror.New(errNoListeners, s.ctx, lastErr))
+	}
+
+	roaming := false
+	for _, ep := range leps {
+		eps, _, eproaming, eperr := s.createEndpoints(ep, listenSpec.AddressChooser)
+		s.chosenEndpoints = append(s.chosenEndpoints, eps...)
+		if eproaming && eperr == nil {
+			s.protoEndpoints = append(s.protoEndpoints, ep.(*inaming.Endpoint))
+			roaming = true
+		}
+	}
+
+	if roaming && s.dhcpState == nil && s.settingsPublisher != nil {
+		// TODO(mattr): Support roaming.
+	}
+
+	s.active.Add(1)
+	go s.acceptLoop(ctx)
+	return nil
+}
+
+func (s *xserver) acceptLoop(ctx *context.T) error {
+	var calls sync.WaitGroup
+	defer func() {
+		calls.Wait()
+		s.active.Done()
+		s.ctx.VI(1).Infof("rpc: Stopped accepting")
+	}()
+	for {
+		// TODO(mattr): We need to interrupt Accept at some point.
+		// Should we interrupt it by canceling the context?
+		fl, err := s.flowMgr.Accept(ctx)
+		if err != nil {
+			s.ctx.VI(10).Infof("rpc: Accept failed: %v", err)
+			return err
+		}
+		calls.Add(1)
+		go func(fl flow.Flow) {
+			defer calls.Done()
+			fs, err := newXFlowServer(fl, s)
+			if err != nil {
+				s.ctx.VI(1).Infof("newFlowServer on %v failed", err)
+				return
+			}
+			if err := fs.serve(); err != nil {
+				// TODO(caprita): Logging errors here is too spammy. For example, "not
+				// authorized" errors shouldn't be logged as server errors.
+				// TODO(cnicolaou): revisit this when verror2 transition is
+				// done.
+				if err != io.EOF {
+					s.ctx.VI(2).Infof("Flow.serve failed: %v", err)
+				}
+			}
+		}(fl)
+	}
+}
+
+func (s *server) serve(name string, obj interface{}, authorizer security.Authorizer) error {
+	if obj == nil {
+		return verror.New(verror.ErrBadArg, s.ctx, "nil object")
+	}
+	invoker, err := objectToInvoker(obj)
+	if err != nil {
+		return verror.New(verror.ErrBadArg, s.ctx, fmt.Sprintf("bad object: %v", err))
+	}
+	// TODO(mattr): Does this really need to be locked?
+	s.Lock()
+	s.isLeaf = true
+	s.Unlock()
+	return s.ServeDispatcher(name, &leafDispatcher{invoker, authorizer})
+}
+
+func (s *xserver) serveDispatcher(name string, disp rpc.Dispatcher) error {
+	if disp == nil {
+		return verror.New(verror.ErrBadArg, s.ctx, "nil dispatcher")
+	}
+	s.Lock()
+	defer s.Unlock()
+	vtrace.GetSpan(s.ctx).Annotate("Serving under name: " + name)
+	s.disp = disp
+	if len(name) > 0 {
+		for _, ep := range s.chosenEndpoints {
+			s.publisher.AddServer(ep.String())
+		}
+		s.publisher.AddName(name, s.servesMountTable, s.isLeaf)
+	}
+	return nil
+}
+
+func (s *xserver) AddName(name string) error {
+	defer apilog.LogCallf(nil, "name=%.10s...", name)(nil, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	if len(name) == 0 {
+		return verror.New(verror.ErrBadArg, s.ctx, "name is empty")
+	}
+	s.Lock()
+	defer s.Unlock()
+	vtrace.GetSpan(s.ctx).Annotate("Serving under name: " + name)
+	s.publisher.AddName(name, s.servesMountTable, s.isLeaf)
+	return nil
+}
+
+func (s *xserver) RemoveName(name string) {
+	defer apilog.LogCallf(nil, "name=%.10s...", name)(nil, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	s.Lock()
+	defer s.Unlock()
+	vtrace.GetSpan(s.ctx).Annotate("Removed name: " + name)
+	s.publisher.RemoveName(name)
+}
+
+func (s *xserver) Stop() error {
+	defer apilog.LogCall(nil)(nil) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+
+	serverDebug := fmt.Sprintf("Dispatcher: %T, Status:[%v]", s.disp, s.Status())
+	s.ctx.VI(1).Infof("Stop: %s", serverDebug)
+	defer s.ctx.VI(1).Infof("Stop done: %s", serverDebug)
+
+	s.Lock()
+	if s.disp == nil {
+		s.Unlock()
+		return nil
+	}
+	s.disp = nil
+	close(s.stoppedChan)
+	s.Unlock()
+
+	// Delete the stats object.
+	s.stats.stop()
+
+	// Note, It's safe to Stop/WaitForStop on the publisher outside of the
+	// server lock, since publisher is safe for concurrent access.
+	// Stop the publisher, which triggers unmounting of published names.
+	s.publisher.Stop()
+
+	// Wait for the publisher to be done unmounting before we can proceed to
+	// close the listeners (to minimize the number of mounted names pointing
+	// to endpoint that are no longer serving).
+	//
+	// TODO(caprita): See if make sense to fail fast on rejecting
+	// connections once listeners are closed, and parallelize the publisher
+	// and listener shutdown.
+	s.publisher.WaitForStop()
+
+	s.Lock()
+
+	// TODO(mattr): What should we do when we stop a server now?  We need to
+	// interrupt Accept at some point, but it's weird to stop the flowmanager.
+	// Close all listeners.  No new flows will be accepted, while in-flight
+	// flows will continue until they terminate naturally.
+	// nListeners := len(s.listeners)
+	// errCh := make(chan error, nListeners)
+	// for ln, _ := range s.listeners {
+	// 	go func(ln stream.Listener) {
+	// 		errCh <- ln.Close()
+	// 	}(ln)
+	// }
+
+	if dhcp := s.dhcpState; dhcp != nil {
+		// TODO(cnicolaou,caprita): investigate not having to close and drain
+		// the channel here. It's a little awkward right now since we have to
+		// be careful to not close the channel in two places, i.e. here and
+		// and from the publisher's Shutdown method.
+		if err := dhcp.publisher.CloseFork(dhcp.name, dhcp.ch); err == nil {
+		drain:
+			for {
+				select {
+				case v := <-dhcp.ch:
+					if v == nil {
+						break drain
+					}
+				default:
+					close(dhcp.ch)
+					break drain
+				}
+			}
+		}
+	}
+
+	s.Unlock()
+
+	// At this point, we are guaranteed that no new requests are going to be
+	// accepted.
+
+	// Wait for the publisher and active listener + flows to finish.
+	done := make(chan struct{}, 1)
+	go func() { s.active.Wait(); done <- struct{}{} }()
+
+	select {
+	case <-done:
+	case <-time.After(5 * time.Second):
+		s.ctx.Errorf("%s: Timedout waiting for goroutines to stop", serverDebug)
+		// TODO(mattr): This doesn't make sense, shouldn't we not wait after timing out?
+		<-done
+		s.ctx.Infof("%s: Done waiting.", serverDebug)
+	}
+
+	s.cancel()
+	return nil
+}
+
+// flowServer implements the RPC server-side protocol for a single RPC, over a
+// flow that's already connected to the client.
+type xflowServer struct {
+	ctx    *context.T     // context associated with the RPC
+	server *xserver       // rpc.Server that this flow server belongs to
+	disp   rpc.Dispatcher // rpc.Dispatcher that will serve RPCs on this flow
+	dec    *vom.Decoder   // to decode requests and args from the client
+	enc    *vom.Encoder   // to encode responses and results to the client
+	flow   flow.Flow      // underlying flow
+
+	// Fields filled in during the server invocation.
+	clientBlessings  security.Blessings
+	ackBlessings     bool
+	grantedBlessings security.Blessings
+	method, suffix   string
+	tags             []*vdl.Value
+	discharges       map[string]security.Discharge
+	starttime        time.Time
+	endStreamArgs    bool // are the stream args at EOF?
+}
+
+var (
+	_ rpc.StreamServerCall = (*xflowServer)(nil)
+	_ security.Call        = (*xflowServer)(nil)
+)
+
+func newXFlowServer(flow flow.Flow, server *xserver) (*xflowServer, error) {
+	server.Lock()
+	disp := server.disp
+	server.Unlock()
+
+	fs := &xflowServer{
+		ctx:        server.ctx,
+		server:     server,
+		disp:       disp,
+		flow:       flow,
+		enc:        vom.NewEncoder(flow),
+		dec:        vom.NewDecoder(flow),
+		discharges: make(map[string]security.Discharge),
+	}
+	// TODO(toddw): Add logic to create separate type flows!
+	return fs, nil
+}
+
+// authorizeVtrace works by simulating a call to __debug/vtrace.Trace.  That
+// rpc is essentially equivalent in power to the data we are attempting to
+// attach here.
+func (fs *xflowServer) authorizeVtrace(ctx *context.T) error {
+	// Set up a context as though we were calling __debug/vtrace.
+	params := &security.CallParams{}
+	params.Copy(fs)
+	params.Method = "Trace"
+	params.MethodTags = []*vdl.Value{vdl.ValueOf(access.Debug)}
+	params.Suffix = "__debug/vtrace"
+
+	var auth security.Authorizer
+	if fs.server.dispReserved != nil {
+		_, auth, _ = fs.server.dispReserved.Lookup(ctx, params.Suffix)
+	}
+	return authorize(fs.ctx, security.NewCall(params), auth)
+}
+
+func (fs *xflowServer) serve() error {
+	defer fs.flow.Close()
+
+	results, err := fs.processRequest()
+
+	vtrace.GetSpan(fs.ctx).Finish()
+
+	var traceResponse vtrace.Response
+	// Check if the caller is permitted to view vtrace data.
+	if fs.authorizeVtrace(fs.ctx) == nil {
+		traceResponse = vtrace.GetResponse(fs.ctx)
+	}
+
+	// Respond to the client with the response header and positional results.
+	response := rpc.Response{
+		Error:            err,
+		EndStreamResults: true,
+		NumPosResults:    uint64(len(results)),
+		TraceResponse:    traceResponse,
+		AckBlessings:     fs.ackBlessings,
+	}
+	if err := fs.enc.Encode(response); err != nil {
+		if err == io.EOF {
+			return err
+		}
+		return verror.New(errResponseEncoding, fs.ctx, fs.LocalEndpoint().String(), fs.RemoteEndpoint().String(), err)
+	}
+	if response.Error != nil {
+		return response.Error
+	}
+	for ix, res := range results {
+		if err := fs.enc.Encode(res); err != nil {
+			if err == io.EOF {
+				return err
+			}
+			return verror.New(errResultEncoding, fs.ctx, ix, fmt.Sprintf("%T=%v", res, res), err)
+		}
+	}
+	// TODO(ashankar): Should unread data from the flow be drained?
+	//
+	// Reason to do so:
+	// The common stream.Flow implementation (v.io/x/ref/runtime/internal/rpc/stream/vc/reader.go)
+	// uses iobuf.Slices backed by an iobuf.Pool. If the stream is not drained, these
+	// slices will not be returned to the pool leading to possibly increased memory usage.
+	//
+	// Reason to not do so:
+	// Draining here will conflict with any Reads on the flow in a separate goroutine
+	// (for example, see TestStreamReadTerminatedByServer in full_test.go).
+	//
+	// For now, go with the reason to not do so as having unread data in the stream
+	// should be a rare case.
+	return nil
+}
+
+func (fs *xflowServer) readRPCRequest() (*rpc.Request, error) {
+	// TODO(toddw): How do we set the initial timeout?  It might be shorter than
+	// the timeout we set later, which we learn after we've decoded the request.
+	/*
+		// Set a default timeout before reading from the flow. Without this timeout,
+		// a client that sends no request or a partial request will retain the flow
+		// indefinitely (and lock up server resources).
+		initTimer := newTimer(defaultCallTimeout)
+		defer initTimer.Stop()
+		fs.flow.SetDeadline(initTimer.C)
+	*/
+
+	// Decode the initial request.
+	var req rpc.Request
+	if err := fs.dec.Decode(&req); err != nil {
+		return nil, verror.New(verror.ErrBadProtocol, fs.ctx, newErrBadRequest(fs.ctx, err))
+	}
+	return &req, nil
+}
+
+func (fs *xflowServer) processRequest() ([]interface{}, error) {
+	fs.starttime = time.Now()
+	req, err := fs.readRPCRequest()
+	if err != nil {
+		// We don't know what the rpc call was supposed to be, but we'll create
+		// a placeholder span so we can capture annotations.
+		fs.ctx, _ = vtrace.WithNewSpan(fs.ctx, fmt.Sprintf("\"%s\".UNKNOWN", fs.suffix))
+		return nil, err
+	}
+	// We must call fs.drainDecoderArgs for any error that occurs
+	// after this point, and before we actually decode the arguments.
+	fs.method = req.Method
+	fs.suffix = strings.TrimLeft(req.Suffix, "/")
+
+	if req.Language != "" {
+		fs.ctx = i18n.WithLangID(fs.ctx, i18n.LangID(req.Language))
+	}
+
+	// TODO(mattr): Currently this allows users to trigger trace collection
+	// on the server even if they will not be allowed to collect the
+	// results later.  This might be considered a DOS vector.
+	spanName := fmt.Sprintf("\"%s\".%s", fs.suffix, fs.method)
+	fs.ctx, _ = vtrace.WithContinuedTrace(fs.ctx, spanName, req.TraceRequest)
+
+	var cancel context.CancelFunc
+	if !req.Deadline.IsZero() {
+		fs.ctx, cancel = context.WithDeadline(fs.ctx, req.Deadline.Time)
+	} else {
+		fs.ctx, cancel = context.WithCancel(fs.ctx)
+	}
+	fs.flow.SetContext(fs.ctx)
+	// TODO(toddw): Explicitly cancel the context when the flow is done.
+	_ = cancel
+
+	// Initialize security: blessings, discharges, etc.
+	if err := fs.initSecurity(req); err != nil {
+		fs.drainDecoderArgs(int(req.NumPosArgs))
+		return nil, err
+	}
+	// Lookup the invoker.
+	invoker, auth, err := fs.lookup(fs.suffix, fs.method)
+	if err != nil {
+		fs.drainDecoderArgs(int(req.NumPosArgs))
+		return nil, err
+	}
+
+	// Note that we strip the reserved prefix when calling the invoker so
+	// that __Glob will call Glob.  Note that we've already assigned a
+	// special invoker so that we never call the wrong method by mistake.
+	strippedMethod := naming.StripReserved(fs.method)
+
+	// Prepare invoker and decode args.
+	numArgs := int(req.NumPosArgs)
+	argptrs, tags, err := invoker.Prepare(fs.ctx, strippedMethod, numArgs)
+	fs.tags = tags
+	if err != nil {
+		fs.drainDecoderArgs(numArgs)
+		return nil, err
+	}
+	if called, want := req.NumPosArgs, uint64(len(argptrs)); called != want {
+		fs.drainDecoderArgs(numArgs)
+		return nil, newErrBadNumInputArgs(fs.ctx, fs.suffix, fs.method, called, want)
+	}
+	for ix, argptr := range argptrs {
+		if err := fs.dec.Decode(argptr); err != nil {
+			return nil, newErrBadInputArg(fs.ctx, fs.suffix, fs.method, uint64(ix), err)
+		}
+	}
+
+	// Check application's authorization policy.
+	if err := authorize(fs.ctx, fs, auth); err != nil {
+		return nil, err
+	}
+
+	// Invoke the method.
+	results, err := invoker.Invoke(fs.ctx, fs, strippedMethod, argptrs)
+	fs.server.stats.record(fs.method, time.Since(fs.starttime))
+	return results, err
+}
+
+// drainDecoderArgs drains the next n arguments encoded onto the flows decoder.
+// This is needed to ensure that the client is able to encode all of its args
+// before the server closes its flow. This guarantees that the client will
+// consistently get the server's error response.
+// TODO(suharshs): Figure out a better way to solve this race condition without
+// unnecessarily reading all arguments.
+func (fs *xflowServer) drainDecoderArgs(n int) error {
+	for i := 0; i < n; i++ {
+		if err := fs.dec.Ignore(); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// lookup returns the invoker and authorizer responsible for serving the given
+// name and method.  The suffix is stripped of any leading slashes. If it begins
+// with rpc.DebugKeyword, we use the internal debug dispatcher to look up the
+// invoker. Otherwise, and we use the server's dispatcher. The suffix and method
+// value may be modified to match the actual suffix and method to use.
+func (fs *xflowServer) lookup(suffix string, method string) (rpc.Invoker, security.Authorizer, error) {
+	if naming.IsReserved(method) {
+		return reservedInvoker(fs.disp, fs.server.dispReserved), security.AllowEveryone(), nil
+	}
+	disp := fs.disp
+	if naming.IsReserved(suffix) {
+		disp = fs.server.dispReserved
+	} else if fs.server.isLeaf && suffix != "" {
+		innerErr := verror.New(errUnexpectedSuffix, fs.ctx, suffix)
+		return nil, nil, verror.New(verror.ErrUnknownSuffix, fs.ctx, suffix, innerErr)
+	}
+	if disp != nil {
+		obj, auth, err := disp.Lookup(fs.ctx, suffix)
+		switch {
+		case err != nil:
+			return nil, nil, err
+		case obj != nil:
+			invoker, err := objectToInvoker(obj)
+			if err != nil {
+				return nil, nil, verror.New(verror.ErrInternal, fs.ctx, "invalid received object", err)
+			}
+			return invoker, auth, nil
+		}
+	}
+	return nil, nil, verror.New(verror.ErrUnknownSuffix, fs.ctx, suffix)
+}
+
+func (fs *xflowServer) initSecurity(req *rpc.Request) error {
+	// TODO(toddw): Do something with this.
+	/*
+		// LocalPrincipal is nil which means we are operating under
+		// SecurityNone.
+		if fs.LocalPrincipal() == nil {
+			return nil
+		}
+
+		// If additional credentials are provided, make them available in the context
+		// Detect unusable blessings now, rather then discovering they are unusable on
+		// first use.
+		//
+		// TODO(ashankar,ataly): Potential confused deputy attack: The client provides
+		// the server's identity as the blessing. Figure out what we want to do about
+		// this - should servers be able to assume that a blessing is something that
+		// does not have the authorizations that the server's own identity has?
+		if got, want := req.GrantedBlessings.PublicKey(), fs.LocalPrincipal().PublicKey(); got != nil && !reflect.DeepEqual(got, want) {
+			return verror.New(verror.ErrNoAccess, fs.ctx, fmt.Sprintf("blessing granted not bound to this server(%v vs %v)", got, want))
+		}
+		fs.grantedBlessings = req.GrantedBlessings
+
+		var err error
+		if fs.clientBlessings, err = serverDecodeBlessings(fs.flow.VCDataCache(), req.Blessings, fs.server.stats); err != nil {
+			// When the server can't access the blessings cache, the client is not following
+			// protocol, so the server closes the VCs corresponding to the client endpoint.
+			// TODO(suharshs,toddw): Figure out a way to only shutdown the current VC, instead
+			// of all VCs connected to the RemoteEndpoint.
+			fs.server.streamMgr.ShutdownEndpoint(fs.RemoteEndpoint())
+			return verror.New(verror.ErrBadProtocol, fs.ctx, newErrBadBlessingsCache(fs.ctx, err))
+		}
+		// Verify that the blessings sent by the client in the request have the same public
+		// key as those sent by the client during VC establishment.
+		if got, want := fs.clientBlessings.PublicKey(), fs.flow.RemoteBlessings().PublicKey(); got != nil && !reflect.DeepEqual(got, want) {
+			return verror.New(verror.ErrNoAccess, fs.ctx, fmt.Sprintf("blessings sent with the request are bound to a different public key (%v) from the blessing used during VC establishment (%v)", got, want))
+		}
+		fs.ackBlessings = true
+
+		for _, d := range req.Discharges {
+			fs.discharges[d.ID()] = d
+		}
+	*/
+	return nil
+}
+
+// Send implements the rpc.Stream method.
+func (fs *xflowServer) Send(item interface{}) error {
+	defer apilog.LogCallf(nil, "item=")(nil, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	// The empty response header indicates what follows is a streaming result.
+	if err := fs.enc.Encode(rpc.Response{}); err != nil {
+		return err
+	}
+	return fs.enc.Encode(item)
+}
+
+// Recv implements the rpc.Stream method.
+func (fs *xflowServer) Recv(itemptr interface{}) error {
+	defer apilog.LogCallf(nil, "itemptr=")(nil, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	var req rpc.Request
+	if err := fs.dec.Decode(&req); err != nil {
+		return err
+	}
+	if req.EndStreamArgs {
+		fs.endStreamArgs = true
+		return io.EOF
+	}
+	return fs.dec.Decode(itemptr)
+}
+
+// Implementations of rpc.ServerCall and security.Call methods.
+
+func (fs *xflowServer) Security() security.Call {
+	//nologcall
+	return fs
+}
+func (fs *xflowServer) LocalDischarges() map[string]security.Discharge {
+	//nologcall
+	return fs.flow.LocalDischarges()
+}
+func (fs *xflowServer) RemoteDischarges() map[string]security.Discharge {
+	//nologcall
+	return fs.flow.RemoteDischarges()
+}
+func (fs *xflowServer) Server() rpc.Server {
+	//nologcall
+	return nil // TODO(toddw): Change return to rpc.XServer
+}
+func (fs *xflowServer) Timestamp() time.Time {
+	//nologcall
+	return fs.starttime
+}
+func (fs *xflowServer) Method() string {
+	//nologcall
+	return fs.method
+}
+func (fs *xflowServer) MethodTags() []*vdl.Value {
+	//nologcall
+	return fs.tags
+}
+func (fs *xflowServer) Suffix() string {
+	//nologcall
+	return fs.suffix
+}
+func (fs *xflowServer) LocalPrincipal() security.Principal {
+	//nologcall
+	return fs.server.principal
+}
+func (fs *xflowServer) LocalBlessings() security.Blessings {
+	//nologcall
+	return fs.flow.LocalBlessings()
+}
+func (fs *xflowServer) RemoteBlessings() security.Blessings {
+	//nologcall
+	return fs.flow.RemoteBlessings()
+}
+func (fs *xflowServer) GrantedBlessings() security.Blessings {
+	//nologcall
+	return fs.grantedBlessings
+}
+func (fs *xflowServer) LocalEndpoint() naming.Endpoint {
+	//nologcall
+	return fs.flow.Conn().LocalEndpoint()
+}
+func (fs *xflowServer) RemoteEndpoint() naming.Endpoint {
+	//nologcall
+	return fs.flow.Conn().RemoteEndpoint()
+}
diff --git a/runtime/internal/rt/ipc_test.go b/runtime/internal/rt/ipc_test.go
new file mode 100644
index 0000000..23d320b
--- /dev/null
+++ b/runtime/internal/rt/ipc_test.go
@@ -0,0 +1,346 @@
+// 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 rt_test
+
+import (
+	"fmt"
+	"reflect"
+	"sort"
+	"sync"
+	"testing"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/options"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/verror"
+	"v.io/x/ref/lib/xrpc"
+	_ "v.io/x/ref/runtime/factories/generic"
+	"v.io/x/ref/runtime/internal/rpc/stream/vc"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/testutil"
+)
+
+//go:generate v23 test generate
+
+type testService struct{}
+
+func (testService) EchoBlessings(ctx *context.T, call rpc.ServerCall) ([]string, error) {
+	b, _ := security.RemoteBlessingNames(ctx, call.Security())
+	return b, nil
+}
+
+func (testService) Foo(*context.T, rpc.ServerCall) error {
+	return nil
+}
+
+func newCtxPrincipal(rootCtx *context.T) *context.T {
+	ctx, err := v23.WithPrincipal(rootCtx, testutil.NewPrincipal("defaultBlessings"))
+	if err != nil {
+		panic(err)
+	}
+	return ctx
+}
+
+func union(blessings ...security.Blessings) security.Blessings {
+	var ret security.Blessings
+	var err error
+	for _, b := range blessings {
+		if ret, err = security.UnionOfBlessings(ret, b); err != nil {
+			panic(err)
+		}
+	}
+	return ret
+}
+
+func mkCaveat(cav security.Caveat, err error) security.Caveat {
+	if err != nil {
+		panic(err)
+	}
+	return cav
+}
+
+func mkBlessings(blessings security.Blessings, err error) security.Blessings {
+	if err != nil {
+		panic(err)
+	}
+	return blessings
+}
+
+func mkThirdPartyCaveat(discharger security.PublicKey, location string, caveats ...security.Caveat) security.Caveat {
+	if len(caveats) == 0 {
+		caveats = []security.Caveat{security.UnconstrainedUse()}
+	}
+	tpc, err := security.NewPublicKeyCaveat(discharger, location, security.ThirdPartyRequirements{}, caveats[0], caveats[1:]...)
+	if err != nil {
+		panic(err)
+	}
+	return tpc
+}
+
+func TestClientServerBlessings(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	var (
+		rootAlpha, rootBeta  = testutil.NewIDProvider("alpha"), testutil.NewIDProvider("beta")
+		clientCtx, serverCtx = newCtxPrincipal(ctx), newCtxPrincipal(ctx)
+		pclient              = v23.GetPrincipal(clientCtx)
+		pserver              = v23.GetPrincipal(serverCtx)
+
+		// A bunch of blessings
+		alphaClient = mkBlessings(rootAlpha.NewBlessings(pclient, "client"))
+		betaClient  = mkBlessings(rootBeta.NewBlessings(pclient, "client"))
+
+		alphaServer = mkBlessings(rootAlpha.NewBlessings(pserver, "server"))
+		betaServer  = mkBlessings(rootBeta.NewBlessings(pserver, "server"))
+	)
+	// Setup the client's blessing store
+	pclient.BlessingStore().Set(alphaClient, "alpha/server")
+	pclient.BlessingStore().Set(betaClient, "beta")
+
+	tests := []struct {
+		server security.Blessings // Blessings presented by the server.
+
+		// Expected output
+		wantServer []string // Client's view of the server's blessings
+		wantClient []string // Server's view of the client's blessings
+	}{
+		{
+			server:     alphaServer,
+			wantServer: []string{"alpha/server"},
+			wantClient: []string{"alpha/client"},
+		},
+		{
+			server:     union(alphaServer, betaServer),
+			wantServer: []string{"alpha/server", "beta/server"},
+			wantClient: []string{"alpha/client", "beta/client"},
+		},
+	}
+
+	// Have the client and server both trust both the root principals.
+	for _, ctx := range []*context.T{clientCtx, serverCtx} {
+		for _, b := range []security.Blessings{alphaClient, betaClient} {
+			p := v23.GetPrincipal(ctx)
+			if err := p.AddToRoots(b); err != nil {
+				t.Fatal(err)
+			}
+		}
+	}
+	// Let it rip!
+	for _, test := range tests {
+		if err := pserver.BlessingStore().SetDefault(test.server); err != nil {
+			t.Errorf("pserver.SetDefault(%v) failed: %v", test.server, err)
+			continue
+		}
+		server, err := xrpc.NewServer(serverCtx, "", testService{}, security.AllowEveryone())
+		if err != nil {
+			t.Fatal(err)
+		}
+		serverObjectName := server.Status().Endpoints[0].Name()
+		ctx, client, err := v23.WithNewClient(clientCtx)
+		if err != nil {
+			panic(err)
+		}
+
+		var gotClient []string
+		if call, err := client.StartCall(ctx, serverObjectName, "EchoBlessings", nil); err != nil {
+			t.Errorf("client.StartCall failed: %v", err)
+		} else if err = call.Finish(&gotClient); err != nil {
+			t.Errorf("call.Finish failed: %v", err)
+		} else if !reflect.DeepEqual(gotClient, test.wantClient) {
+			t.Errorf("%v: Got %v, want %v for client blessings", test.server, gotClient, test.wantServer)
+		} else if gotServer, _ := call.RemoteBlessings(); !reflect.DeepEqual(gotServer, test.wantServer) {
+			t.Errorf("%v: Got %v, want %v for server blessings", test.server, gotServer, test.wantClient)
+		}
+
+		server.Stop()
+		client.Close()
+	}
+}
+
+func TestServerEndpointBlessingNames(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	ctx, _ = v23.WithPrincipal(ctx, testutil.NewPrincipal("default"))
+
+	var (
+		p    = v23.GetPrincipal(ctx)
+		b1   = mkBlessings(p.BlessSelf("dev.v.io/users/foo@bar.com/devices/phone/applications/app"))
+		b2   = mkBlessings(p.BlessSelf("otherblessing"))
+		bopt = options.ServerBlessings{Blessings: union(b1, b2)}
+
+		tests = []struct {
+			opts      []rpc.ServerOpt
+			blessings []string
+		}{
+			{nil, []string{"default"}},
+			{[]rpc.ServerOpt{bopt}, []string{"dev.v.io/users/foo@bar.com/devices/phone/applications/app", "otherblessing"}},
+		}
+	)
+	if err := p.AddToRoots(bopt.Blessings); err != nil {
+		t.Fatal(err)
+	}
+	for idx, test := range tests {
+		server, err := xrpc.NewServer(ctx, "", testService{}, nil, test.opts...)
+		if err != nil {
+			t.Errorf("test #%d: %v", idx, err)
+			continue
+		}
+		status := server.Status()
+		want := test.blessings
+		sort.Strings(want)
+		for _, ep := range status.Endpoints {
+			got := ep.BlessingNames()
+			sort.Strings(got)
+			if !reflect.DeepEqual(got, want) {
+				t.Errorf("test #%d: endpoint=%q: Got blessings %v, want %v", idx, ep, got, want)
+			}
+		}
+		// The tests below are dubious: status.Proxies[i].Endpoints is
+		// empty for all i because at the time this test was written,
+		// no proxies were started. Anyway, just to express the
+		// intent...
+		for _, proxy := range status.Proxies {
+			ep := proxy.Endpoint
+			if got := ep.BlessingNames(); !reflect.DeepEqual(got, want) {
+				t.Errorf("test #%d: proxy=%q endpoint=%q: Got blessings %v, want %v", idx, proxy.Proxy, ep, got, want)
+			}
+		}
+	}
+}
+
+type dischargeService struct {
+	called int
+	mu     sync.Mutex
+}
+
+func (ds *dischargeService) Discharge(ctx *context.T, call rpc.StreamServerCall, cav security.Caveat, _ security.DischargeImpetus) (security.Discharge, error) {
+	tp := cav.ThirdPartyDetails()
+	if tp == nil {
+		return security.Discharge{}, fmt.Errorf("discharger: not a third party caveat (%v)", cav)
+	}
+	if err := tp.Dischargeable(ctx, call.Security()); err != nil {
+		return security.Discharge{}, fmt.Errorf("third-party caveat %v cannot be discharged for this context: %v", tp, err)
+	}
+	// If its the first time being called, add an expiry caveat and a MethodCaveat for "EchoBlessings".
+	// Otherwise, just add a MethodCaveat for "Foo".
+	ds.mu.Lock()
+	called := ds.called
+	ds.mu.Unlock()
+	caveat := security.UnconstrainedUse()
+	if called == 0 {
+		caveat = mkCaveat(security.NewExpiryCaveat(time.Now().Add(-1 * time.Second)))
+	}
+
+	return call.Security().LocalPrincipal().MintDischarge(cav, caveat)
+}
+
+func TestServerDischarges(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	var (
+		dischargerCtx, clientCtx, serverCtx = newCtxPrincipal(ctx), newCtxPrincipal(ctx), newCtxPrincipal(ctx)
+		pdischarger                         = v23.GetPrincipal(dischargerCtx)
+		pclient                             = v23.GetPrincipal(clientCtx)
+		pserver                             = v23.GetPrincipal(serverCtx)
+		root                                = testutil.NewIDProvider("root")
+	)
+
+	// Setup the server's and discharger's blessing store and blessing roots, and
+	// start the server and discharger.
+	if err := root.Bless(pdischarger, "discharger"); err != nil {
+		t.Fatal(err)
+	}
+	ds := &dischargeService{}
+	server, err := xrpc.NewServer(dischargerCtx, "", ds, security.AllowEveryone())
+	if err != nil {
+		t.Fatal(err)
+	}
+	dischargeServerName := server.Status().Endpoints[0].Name()
+
+	if err := root.Bless(pserver, "server", mkThirdPartyCaveat(pdischarger.PublicKey(), dischargeServerName)); err != nil {
+		t.Fatal(err)
+	}
+	server, err = xrpc.NewServer(serverCtx, "", testService{}, security.AllowEveryone(), vc.DischargeExpiryBuffer(10*time.Millisecond))
+	if err != nil {
+		t.Fatal(err)
+	}
+	serverName := server.Status().Endpoints[0].Name()
+
+	// Setup up the client's blessing store so that it can talk to the server.
+	rootClient := mkBlessings(root.NewBlessings(pclient, "client"))
+	if _, err := pclient.BlessingStore().Set(security.Blessings{}, security.AllPrincipals); err != nil {
+		t.Fatal(err)
+	}
+	if _, err := pclient.BlessingStore().Set(rootClient, "root/server"); err != nil {
+		t.Fatal(err)
+	}
+	if err := pclient.AddToRoots(rootClient); err != nil {
+		t.Fatal(err)
+	}
+
+	// Test that the client and server can communicate with the expected set of blessings
+	// when server provides appropriate discharges.
+	wantClient := []string{"root/client"}
+	wantServer := []string{"root/server"}
+	var gotClient []string
+	// This opt ensures that if the Blessings do not match the pattern, StartCall will fail.
+	allowedServers := options.AllowedServersPolicy{"root/server"}
+
+	// Create a new client.
+	clientCtx, client, err := v23.WithNewClient(clientCtx)
+	if err != nil {
+		t.Fatal(err)
+	}
+	makeCall := func() error {
+		if call, err := client.StartCall(clientCtx, serverName, "EchoBlessings", nil, allowedServers); err != nil {
+			return err
+		} else if err = call.Finish(&gotClient); err != nil {
+			return fmt.Errorf("call.Finish failed: %v", err)
+		} else if !reflect.DeepEqual(gotClient, wantClient) {
+			return fmt.Errorf("Got %v, want %v for client blessings", gotClient, wantClient)
+		} else if gotServer, _ := call.RemoteBlessings(); !reflect.DeepEqual(gotServer, wantServer) {
+			return fmt.Errorf("Got %v, want %v for server blessings", gotServer, wantServer)
+		}
+		return nil
+	}
+
+	if err := makeCall(); verror.ErrorID(err) != verror.ErrNotTrusted.ID {
+		t.Fatalf("got error %v, expected %v", err, verror.ErrNotTrusted.ID)
+	}
+	ds.mu.Lock()
+	ds.called++
+	ds.mu.Unlock()
+	// makeCall should eventually succeed because a valid discharge should be refreshed.
+	start := time.Now()
+	for err := makeCall(); err != nil; time.Sleep(10 * time.Millisecond) {
+		if time.Since(start) > 10*time.Second {
+			t.Fatalf("Discharge not refreshed in 10 seconds")
+		}
+		err = makeCall()
+	}
+
+	// Test that the client fails to talk to server that does not present appropriate discharges.
+	// Setup a new client so that there are no cached VCs.
+	clientCtx, client, err = v23.WithNewClient(clientCtx)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	rootServerInvalidTPCaveat := mkBlessings(root.NewBlessings(pserver, "server", mkThirdPartyCaveat(pdischarger.PublicKey(), dischargeServerName, mkCaveat(security.NewExpiryCaveat(time.Now().Add(-1*time.Second))))))
+	if err := pserver.BlessingStore().SetDefault(rootServerInvalidTPCaveat); err != nil {
+		t.Fatal(err)
+	}
+	call, err := client.StartCall(clientCtx, serverName, "EchoBlessings", nil)
+	if verror.ErrorID(err) == verror.ErrNoAccess.ID {
+		remoteBlessings, _ := call.RemoteBlessings()
+		t.Errorf("client.StartCall passed unexpectedly with remote end authenticated as: %v", remoteBlessings)
+	}
+	call.Finish() // make sure the rpc finishes before shutting down the test+runtime.
+}
diff --git a/runtime/internal/rt/mgmt.go b/runtime/internal/rt/mgmt.go
new file mode 100644
index 0000000..28c25cf
--- /dev/null
+++ b/runtime/internal/rt/mgmt.go
@@ -0,0 +1,102 @@
+// 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 rt
+
+import (
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/options"
+	"v.io/v23/rpc"
+	"v.io/v23/verror"
+
+	"v.io/x/ref/lib/exec"
+	"v.io/x/ref/lib/mgmt"
+)
+
+const pkgPath = "v.io/x/ref/option/internal/rt"
+
+var (
+	errConfigKeyNotSet = verror.Register(pkgPath+".errConfigKeyNotSet", verror.NoRetry, "{1:}{2:} {3} is not set{:_}")
+)
+
+func (rt *Runtime) initMgmt(ctx *context.T) error {
+	handle, err := exec.GetChildHandle()
+	if err == nil {
+		// No error; fall through.
+	} else if verror.ErrorID(err) == exec.ErrNoVersion.ID {
+		// Do not initialize the mgmt runtime if the process has not
+		// been started through the vanadium exec library by a device
+		// manager.
+		return nil
+	} else {
+		return err
+	}
+	parentName, err := handle.Config.Get(mgmt.ParentNameConfigKey)
+	if err != nil {
+		// If the ParentNameConfigKey is not set, then this process has
+		// not been started by the device manager, but the parent process
+		// is still a Vanadium process using the exec library so we
+		// call SetReady to let it know that this child process has
+		// successfully started.
+		return handle.SetReady()
+	}
+	listenSpec, err := getListenSpec(ctx, handle)
+	if err != nil {
+		return err
+	}
+	var serverOpts []rpc.ServerOpt
+	parentPeerPattern, err := handle.Config.Get(mgmt.ParentBlessingConfigKey)
+	if err == nil && parentPeerPattern != "" {
+		// Grab the blessing from our blessing store that the parent
+		// told us to use so they can talk to us.
+		serverBlessing := rt.GetPrincipal(ctx).BlessingStore().ForPeer(parentPeerPattern)
+		serverOpts = append(serverOpts, options.ServerBlessings{Blessings: serverBlessing})
+	}
+	server, err := rt.NewServer(ctx, serverOpts...)
+	if err != nil {
+		return err
+	}
+	eps, err := server.Listen(*listenSpec)
+	if err != nil {
+		return err
+	}
+	if err := server.Serve("", v23.GetAppCycle(ctx).Remote(), nil); err != nil {
+		server.Stop()
+		return err
+	}
+	err = rt.callbackToParent(ctx, parentName, naming.JoinAddressName(eps[0].String(), ""))
+	if err != nil {
+		server.Stop()
+		return err
+	}
+	return handle.SetReady()
+}
+
+func getListenSpec(ctx *context.T, handle *exec.ChildHandle) (*rpc.ListenSpec, error) {
+	protocol, err := handle.Config.Get(mgmt.ProtocolConfigKey)
+	if err != nil {
+		return nil, err
+	}
+	if protocol == "" {
+		return nil, verror.New(errConfigKeyNotSet, ctx, mgmt.ProtocolConfigKey)
+	}
+
+	address, err := handle.Config.Get(mgmt.AddressConfigKey)
+	if err != nil {
+		return nil, err
+	}
+	if address == "" {
+		return nil, verror.New(errConfigKeyNotSet, ctx, mgmt.AddressConfigKey)
+	}
+	return &rpc.ListenSpec{Addrs: rpc.ListenAddrs{{protocol, address}}}, nil
+}
+
+func (rt *Runtime) callbackToParent(ctx *context.T, parentName, myName string) error {
+	ctx, _ = context.WithTimeout(ctx, time.Minute)
+	return rt.GetClient(ctx).Call(ctx, parentName, "Set", []interface{}{mgmt.AppCycleManagerConfigKey, myName}, nil)
+}
diff --git a/runtime/internal/rt/mgmt_test.go b/runtime/internal/rt/mgmt_test.go
new file mode 100644
index 0000000..ad1f650
--- /dev/null
+++ b/runtime/internal/rt/mgmt_test.go
@@ -0,0 +1,365 @@
+// 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 rt_test
+
+import (
+	"fmt"
+	"os"
+	"reflect"
+	"strings"
+	"testing"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/services/appcycle"
+	"v.io/x/ref/lib/mgmt"
+	"v.io/x/ref/lib/security/securityflag"
+	"v.io/x/ref/lib/xrpc"
+	"v.io/x/ref/services/device"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/expect"
+	"v.io/x/ref/test/modules"
+
+	_ "v.io/x/ref/runtime/factories/generic"
+)
+
+//go:generate v23 test generate
+
+// TestBasic verifies that the basic plumbing works: LocalStop calls result in
+// stop messages being sent on the channel passed to WaitForStop.
+func TestBasic(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	m := v23.GetAppCycle(ctx)
+	ch := make(chan string, 1)
+	m.WaitForStop(ctx, ch)
+	for i := 0; i < 10; i++ {
+		m.Stop(ctx)
+		if want, got := v23.LocalStop, <-ch; want != got {
+			t.Errorf("WaitForStop want %q got %q", want, got)
+		}
+		select {
+		case s := <-ch:
+			t.Errorf("channel expected to be empty, got %q instead", s)
+		default:
+		}
+	}
+}
+
+// TestMultipleWaiters verifies that the plumbing works with more than one
+// registered wait channel.
+func TestMultipleWaiters(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	m := v23.GetAppCycle(ctx)
+	ch1 := make(chan string, 1)
+	m.WaitForStop(ctx, ch1)
+	ch2 := make(chan string, 1)
+	m.WaitForStop(ctx, ch2)
+	for i := 0; i < 10; i++ {
+		m.Stop(ctx)
+		if want, got := v23.LocalStop, <-ch1; want != got {
+			t.Errorf("WaitForStop want %q got %q", want, got)
+		}
+		if want, got := v23.LocalStop, <-ch2; want != got {
+			t.Errorf("WaitForStop want %q got %q", want, got)
+		}
+	}
+}
+
+// TestMultipleStops verifies that LocalStop does not block even if the wait
+// channel is not being drained: once the channel's buffer fills up, future
+// Stops become no-ops.
+func TestMultipleStops(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	m := v23.GetAppCycle(ctx)
+	ch := make(chan string, 1)
+	m.WaitForStop(ctx, ch)
+	for i := 0; i < 10; i++ {
+		m.Stop(ctx)
+	}
+	if want, got := v23.LocalStop, <-ch; want != got {
+		t.Errorf("WaitForStop want %q got %q", want, got)
+	}
+	select {
+	case s := <-ch:
+		t.Errorf("channel expected to be empty, got %q instead", s)
+	default:
+	}
+}
+
+var noWaiters = modules.Register(func(env *modules.Env, args ...string) error {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	m := v23.GetAppCycle(ctx)
+	fmt.Fprintf(env.Stdout, "ready\n")
+	modules.WaitForEOF(env.Stdin)
+	m.Stop(ctx)
+	os.Exit(42) // This should not be reached.
+	return nil
+}, "noWaiters")
+
+// TestNoWaiters verifies that the child process exits in the absence of any
+// wait channel being registered with its runtime.
+func TestNoWaiters(t *testing.T) {
+	sh, err := modules.NewShell(nil, nil, testing.Verbose(), t)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	defer sh.Cleanup(os.Stderr, os.Stderr)
+	h, err := sh.Start(nil, noWaiters)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	h.Expect("ready")
+	want := fmt.Sprintf("exit status %d", v23.UnhandledStopExitCode)
+	if err = h.Shutdown(os.Stderr, os.Stderr); err == nil || err.Error() != want {
+		t.Errorf("got %v, want %s", err, want)
+	}
+}
+
+var forceStop = modules.Register(func(env *modules.Env, args ...string) error {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	m := v23.GetAppCycle(ctx)
+	fmt.Fprintf(env.Stdout, "ready\n")
+	modules.WaitForEOF(env.Stdin)
+	m.WaitForStop(ctx, make(chan string, 1))
+	m.ForceStop(ctx)
+	os.Exit(42) // This should not be reached.
+	return nil
+}, "forceStop")
+
+// TestForceStop verifies that ForceStop causes the child process to exit
+// immediately.
+func TestForceStop(t *testing.T) {
+	sh, err := modules.NewShell(nil, nil, testing.Verbose(), t)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	defer sh.Cleanup(os.Stderr, os.Stderr)
+	h, err := sh.Start(nil, forceStop)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	h.Expect("ready")
+	err = h.Shutdown(os.Stderr, os.Stderr)
+	want := fmt.Sprintf("exit status %d", v23.UnhandledStopExitCode)
+	if err == nil || err.Error() != want {
+		t.Errorf("got %v, want %s", err, want)
+	}
+}
+
+func checkProgress(t *testing.T, ch <-chan v23.Task, progress, goal int32) {
+	if want, got := (v23.Task{Progress: progress, Goal: goal}), <-ch; !reflect.DeepEqual(want, got) {
+		t.Errorf("Unexpected progress: want %+v, got %+v", want, got)
+	}
+}
+
+func checkNoProgress(t *testing.T, ch <-chan v23.Task) {
+	select {
+	case p := <-ch:
+		t.Errorf("channel expected to be empty, got %+v instead", p)
+	default:
+	}
+}
+
+// TestProgress verifies that the ticker update/track logic works for a single
+// tracker.
+func TestProgress(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+
+	m := v23.GetAppCycle(ctx)
+	m.AdvanceGoal(50)
+	ch := make(chan v23.Task, 1)
+	m.TrackTask(ch)
+	checkNoProgress(t, ch)
+	m.AdvanceProgress(10)
+	checkProgress(t, ch, 10, 50)
+	checkNoProgress(t, ch)
+	m.AdvanceProgress(5)
+	checkProgress(t, ch, 15, 50)
+	m.AdvanceGoal(50)
+	checkProgress(t, ch, 15, 100)
+	m.AdvanceProgress(1)
+	checkProgress(t, ch, 16, 100)
+	m.AdvanceGoal(10)
+	checkProgress(t, ch, 16, 110)
+	m.AdvanceProgress(-13)
+	checkNoProgress(t, ch)
+	m.AdvanceGoal(0)
+	checkNoProgress(t, ch)
+	shutdown()
+	if _, ok := <-ch; ok {
+		t.Errorf("Expected channel to be closed")
+	}
+}
+
+// TestProgressMultipleTrackers verifies that the ticker update/track logic
+// works for more than one tracker.  It also ensures that the runtime doesn't
+// block when the tracker channels are full.
+func TestProgressMultipleTrackers(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+
+	m := v23.GetAppCycle(ctx)
+	// ch1 is 1-buffered, ch2 is 2-buffered.
+	ch1, ch2 := make(chan v23.Task, 1), make(chan v23.Task, 2)
+	m.TrackTask(ch1)
+	m.TrackTask(ch2)
+	checkNoProgress(t, ch1)
+	checkNoProgress(t, ch2)
+	m.AdvanceProgress(1)
+	checkProgress(t, ch1, 1, 0)
+	checkNoProgress(t, ch1)
+	checkProgress(t, ch2, 1, 0)
+	checkNoProgress(t, ch2)
+	for i := 0; i < 10; i++ {
+		m.AdvanceProgress(1)
+	}
+	checkProgress(t, ch1, 2, 0)
+	checkNoProgress(t, ch1)
+	checkProgress(t, ch2, 2, 0)
+	checkProgress(t, ch2, 3, 0)
+	checkNoProgress(t, ch2)
+	m.AdvanceGoal(4)
+	checkProgress(t, ch1, 11, 4)
+	checkProgress(t, ch2, 11, 4)
+	shutdown()
+	if _, ok := <-ch1; ok {
+		t.Errorf("Expected channel to be closed")
+	}
+	if _, ok := <-ch2; ok {
+		t.Errorf("Expected channel to be closed")
+	}
+}
+
+var app = modules.Register(func(env *modules.Env, args ...string) error {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	m := v23.GetAppCycle(ctx)
+	ch := make(chan string, 1)
+	m.WaitForStop(ctx, ch)
+	fmt.Fprintf(env.Stdout, "Got %s\n", <-ch)
+	m.AdvanceGoal(10)
+	fmt.Fprintf(env.Stdout, "Doing some work\n")
+	m.AdvanceProgress(2)
+	fmt.Fprintf(env.Stdout, "Doing some more work\n")
+	m.AdvanceProgress(5)
+	return nil
+}, "app")
+
+type configServer struct {
+	ch chan<- string
+}
+
+func (c *configServer) Set(_ *context.T, _ rpc.ServerCall, key, value string) error {
+	if key != mgmt.AppCycleManagerConfigKey {
+		return fmt.Errorf("Unexpected key: %v", key)
+	}
+	c.ch <- value
+	return nil
+
+}
+
+func setupRemoteAppCycleMgr(t *testing.T) (*context.T, modules.Handle, appcycle.AppCycleClientMethods, func()) {
+	ctx, shutdown := test.V23Init()
+
+	ch := make(chan string)
+	service := device.ConfigServer(&configServer{ch})
+	authorizer := securityflag.NewAuthorizerOrDie()
+	configServer, err := xrpc.NewServer(ctx, "", service, authorizer)
+	if err != nil {
+		t.Fatalf("Got error: %v", err)
+	}
+	configServiceName := configServer.Status().Endpoints[0].Name()
+
+	sh, err := modules.NewShell(ctx, v23.GetPrincipal(ctx), testing.Verbose(), t)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	sh.SetConfigKey(mgmt.ParentNameConfigKey, configServiceName)
+	sh.SetConfigKey(mgmt.ProtocolConfigKey, "tcp")
+	sh.SetConfigKey(mgmt.AddressConfigKey, "127.0.0.1:0")
+	h, err := sh.Start(nil, app)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	appCycleName := ""
+	select {
+	case appCycleName = <-ch:
+	case <-time.After(time.Minute):
+		t.Errorf("timeout")
+	}
+	appCycle := appcycle.AppCycleClient(appCycleName)
+	return ctx, h, appCycle, func() {
+		configServer.Stop()
+		sh.Cleanup(os.Stderr, os.Stderr)
+		shutdown()
+	}
+}
+
+// TestRemoteForceStop verifies that the child process exits when sending it
+// a remote ForceStop rpc.
+func TestRemoteForceStop(t *testing.T) {
+	ctx, h, appCycle, cleanup := setupRemoteAppCycleMgr(t)
+	defer cleanup()
+	if err := appCycle.ForceStop(ctx); err == nil || !strings.Contains(err.Error(), "EOF") {
+		t.Fatalf("Expected EOF error, got %v instead", err)
+	}
+	s := expect.NewSession(t, h.Stdout(), time.Minute)
+	s.ExpectEOF()
+	err := h.Shutdown(os.Stderr, os.Stderr)
+	want := fmt.Sprintf("exit status %d", v23.ForceStopExitCode)
+	if err == nil || err.Error() != want {
+		t.Errorf("got %v, want %s", err, want)
+	}
+}
+
+// TestRemoteStop verifies that the child shuts down cleanly when sending it
+// a remote Stop rpc.
+func TestRemoteStop(t *testing.T) {
+	ctx, h, appCycle, cleanup := setupRemoteAppCycleMgr(t)
+	defer cleanup()
+	stream, err := appCycle.Stop(ctx)
+	if err != nil {
+		t.Fatalf("Got error: %v", err)
+	}
+	rStream := stream.RecvStream()
+	expectTask := func(progress, goal int32) {
+		if !rStream.Advance() {
+			t.Fatalf("unexpected streaming error: %q", rStream.Err())
+		}
+		task := rStream.Value()
+		if task.Progress != progress || task.Goal != goal {
+			t.Errorf("Got (%d, %d), want (%d, %d)", task.Progress, task.Goal, progress, goal)
+		}
+	}
+	expectTask(0, 10)
+	expectTask(2, 10)
+	expectTask(7, 10)
+	if rStream.Advance() || rStream.Err() != nil {
+		t.Errorf("Expected EOF, got (%v, %v) instead", rStream.Value(), rStream.Err())
+	}
+	if err := stream.Finish(); err != nil {
+		t.Errorf("Got error %v", err)
+	}
+	s := expect.NewSession(t, h.Stdout(), time.Minute)
+	s.Expect(fmt.Sprintf("Got %s", v23.RemoteStop))
+	s.Expect("Doing some work")
+	s.Expect("Doing some more work")
+	s.ExpectEOF()
+	if err := h.Shutdown(os.Stderr, os.Stderr); err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+}
diff --git a/runtime/internal/rt/rt_test.go b/runtime/internal/rt/rt_test.go
new file mode 100644
index 0000000..81f93d5
--- /dev/null
+++ b/runtime/internal/rt/rt_test.go
@@ -0,0 +1,283 @@
+// 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 rt_test
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"regexp"
+	"testing"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/security"
+
+	"v.io/x/ref"
+	"v.io/x/ref/internal/logger"
+	vsecurity "v.io/x/ref/lib/security"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/expect"
+	"v.io/x/ref/test/modules"
+)
+
+//go:generate v23 test generate
+
+func TestInit(t *testing.T) {
+	ref.EnvClearCredentials()
+	ctx, shutdown := v23.Init()
+	defer shutdown()
+
+	mgr := logger.Manager(ctx)
+	fmt.Println(mgr)
+	args := fmt.Sprintf("%s", mgr)
+	expected := regexp.MustCompile("name=vanadium logdirs=\\[/tmp\\] logtostderr=true|false alsologtostderr=false|true max_stack_buf_size=4292608 v=[0-9] stderrthreshold=2 vmodule= vfilepath= log_backtrace_at=:0")
+
+	if !expected.MatchString(args) {
+		t.Errorf("unexpected default args: %s, want %s", args, expected)
+	}
+	p := v23.GetPrincipal(ctx)
+	if p == nil {
+		t.Fatalf("A new principal should have been created")
+	}
+	if p.BlessingStore() == nil {
+		t.Fatalf("The principal must have a BlessingStore")
+	}
+	if p.BlessingStore().Default().IsZero() {
+		t.Errorf("Principal().BlessingStore().Default() should not be the zero value")
+	}
+	if p.BlessingStore().ForPeer().IsZero() {
+		t.Errorf("Principal().BlessingStore().ForPeer() should not be the zero value")
+	}
+}
+
+var child = modules.Register(func(env *modules.Env, args ...string) error {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	mgr := logger.Manager(ctx)
+	ctx.Infof("%s\n", mgr)
+	fmt.Fprintf(env.Stdout, "%s\n", mgr)
+	modules.WaitForEOF(env.Stdin)
+	fmt.Fprintf(env.Stdout, "done\n")
+	return nil
+}, "child")
+
+func TestInitArgs(t *testing.T) {
+	sh, err := modules.NewShell(nil, nil, testing.Verbose(), t)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	defer sh.Cleanup(os.Stderr, os.Stderr)
+	h, err := sh.Start(nil, child, "--logtostderr=true", "--vmodule=*=3", "--", "foobar")
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	h.Expect(fmt.Sprintf("name=vlog "+
+		"logdirs=[%s] "+
+		"logtostderr=true "+
+		"alsologtostderr=true "+
+		"max_stack_buf_size=4292608 "+
+		"v=0 "+
+		"stderrthreshold=2 "+
+		"vmodule=*=3 "+
+		"vfilepath= "+
+		"log_backtrace_at=:0",
+		os.TempDir()))
+	h.CloseStdin()
+	h.Expect("done")
+	h.ExpectEOF()
+	h.Shutdown(os.Stderr, os.Stderr)
+}
+
+func validatePrincipal(p security.Principal) error {
+	if p == nil {
+		return fmt.Errorf("nil principal")
+	}
+	call := security.NewCall(&security.CallParams{LocalPrincipal: p, RemoteBlessings: p.BlessingStore().Default()})
+	ctx, cancel := context.RootContext()
+	defer cancel()
+	blessings, rejected := security.RemoteBlessingNames(ctx, call)
+	if n := len(blessings); n != 1 {
+		return fmt.Errorf("rt.Principal().BlessingStore().Default() return blessings:%v (rejected:%v), want exactly one recognized blessing", blessings, rejected)
+	}
+	return nil
+}
+
+func defaultBlessing(p security.Principal) string {
+	call := security.NewCall(&security.CallParams{LocalPrincipal: p, RemoteBlessings: p.BlessingStore().Default()})
+	ctx, cancel := context.RootContext()
+	defer cancel()
+	b, _ := security.RemoteBlessingNames(ctx, call)
+	return b[0]
+}
+
+func tmpDir(t *testing.T) string {
+	dir, err := ioutil.TempDir("", "rt_test_dir")
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	return dir
+}
+
+var principal = modules.Register(func(env *modules.Env, args ...string) error {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	p := v23.GetPrincipal(ctx)
+	if err := validatePrincipal(p); err != nil {
+		return err
+	}
+	fmt.Fprintf(env.Stdout, "DEFAULT_BLESSING=%s\n", defaultBlessing(p))
+	return nil
+}, "principal")
+
+// Runner runs a principal as a subprocess and reports back with its
+// own security info and it's childs.
+var runner = modules.Register(func(env *modules.Env, args ...string) error {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	p := v23.GetPrincipal(ctx)
+	if err := validatePrincipal(p); err != nil {
+		return err
+	}
+	fmt.Fprintf(env.Stdout, "RUNNER_DEFAULT_BLESSING=%v\n", defaultBlessing(p))
+	sh, err := modules.NewShell(ctx, p, false, nil)
+	if err != nil {
+		return err
+	}
+	if _, err := sh.Start(nil, principal, args...); err != nil {
+		return err
+	}
+	// Cleanup copies the output of sh to these Writers.
+	sh.Cleanup(env.Stdout, env.Stderr)
+	return nil
+}, "runner")
+
+func createCredentialsInDir(t *testing.T, dir string, blessing string) {
+	principal, err := vsecurity.CreatePersistentPrincipal(dir, nil)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	if err := vsecurity.InitDefaultBlessings(principal, blessing); err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+}
+
+func TestPrincipalInheritance(t *testing.T) {
+	sh, err := modules.NewShell(nil, nil, testing.Verbose(), t)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	defer func() {
+		sh.Cleanup(os.Stdout, os.Stderr)
+	}()
+
+	// Test that the child inherits from the parent's credentials correctly.
+	// The running test process may or may not have a credentials directory set
+	// up so we have to use a 'runner' process to ensure the correct setup.
+	cdir := tmpDir(t)
+	defer os.RemoveAll(cdir)
+
+	createCredentialsInDir(t, cdir, "test")
+
+	// directory supplied by the environment.
+	credEnv := []string{ref.EnvCredentials + "=" + cdir}
+
+	h, err := sh.Start(credEnv, runner)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+
+	runnerBlessing := h.ExpectVar("RUNNER_DEFAULT_BLESSING")
+	principalBlessing := h.ExpectVar("DEFAULT_BLESSING")
+	if err := h.Error(); err != nil {
+		t.Fatalf("failed to read input from children: %s", err)
+	}
+	h.Shutdown(os.Stdout, os.Stderr)
+
+	wantRunnerBlessing := "test"
+	wantPrincipalBlessing := "test/child"
+	if runnerBlessing != wantRunnerBlessing || principalBlessing != wantPrincipalBlessing {
+		t.Fatalf("unexpected default blessing: got runner %s, principal %s, want runner %s, principal %s", runnerBlessing, principalBlessing, wantRunnerBlessing, wantPrincipalBlessing)
+	}
+
+}
+
+func TestPrincipalInit(t *testing.T) {
+	// Collect the process' public key and error status
+	collect := func(sh *modules.Shell, env []string, args ...string) string {
+		h, err := sh.Start(env, principal, args...)
+		if err != nil {
+			t.Fatalf("unexpected error: %s", err)
+		}
+		s := expect.NewSession(t, h.Stdout(), time.Minute)
+		s.SetVerbosity(testing.Verbose())
+		return s.ExpectVar("DEFAULT_BLESSING")
+	}
+
+	// A credentials directory may, or may, not have been already specified.
+	// Either way, we want to use our own, so we set it aside and use our own.
+	origCredentialsDir := os.Getenv(ref.EnvCredentials)
+	defer os.Setenv(ref.EnvCredentials, origCredentialsDir)
+	if err := os.Setenv(ref.EnvCredentials, ""); err != nil {
+		t.Fatal(err)
+	}
+
+	// We create two shells -- one initializing the principal for a child process
+	// via a credentials directory and the other via an agent.
+	sh, err := modules.NewShell(nil, nil, testing.Verbose(), t)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	defer sh.Cleanup(os.Stderr, os.Stderr)
+
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	agentSh, err := modules.NewShell(ctx, v23.GetPrincipal(ctx), testing.Verbose(), t)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	defer agentSh.Cleanup(os.Stderr, os.Stderr)
+
+	// Test that with ref.EnvCredentials unset the runtime's Principal
+	// is correctly initialized for both shells.
+	if len(collect(sh, nil)) == 0 {
+		t.Fatalf("Without agent: child returned an empty default blessings set")
+	}
+	if got, want := collect(agentSh, nil), test.TestBlessing+security.ChainSeparator+"child"; got != want {
+		t.Fatalf("With agent: got %q, want %q", got, want)
+	}
+
+	// Test that credentials specified via the ref.EnvCredentials
+	// environment variable take precedence over an agent.
+	cdir1 := tmpDir(t)
+	defer os.RemoveAll(cdir1)
+	createCredentialsInDir(t, cdir1, "test_env")
+	credEnv := []string{ref.EnvCredentials + "=" + cdir1}
+
+	if got, want := collect(sh, credEnv), "test_env"; got != want {
+		t.Errorf("Without agent: got default blessings: %q, want %q", got, want)
+	}
+	if got, want := collect(agentSh, credEnv), "test_env"; got != want {
+		t.Errorf("With agent: got default blessings: %q, want %q", got, want)
+	}
+
+	// Test that credentials specified via the command line take precedence over the
+	// ref.EnvCredentials environment variable and also the agent.
+	cdir2 := tmpDir(t)
+	defer os.RemoveAll(cdir2)
+	createCredentialsInDir(t, cdir2, "test_cmd")
+
+	if got, want := collect(sh, credEnv, "--v23.credentials="+cdir2), "test_cmd"; got != want {
+		t.Errorf("Without agent: got %q, want %q", got, want)
+	}
+	if got, want := collect(agentSh, credEnv, "--v23.credentials="+cdir2), "test_cmd"; got != want {
+		t.Errorf("With agent: got %q, want %q", got, want)
+	}
+}
diff --git a/runtime/internal/rt/runtime.go b/runtime/internal/rt/runtime.go
new file mode 100644
index 0000000..37a7d0a
--- /dev/null
+++ b/runtime/internal/rt/runtime.go
@@ -0,0 +1,563 @@
+// 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 rt
+
+import (
+	"fmt"
+	"os"
+	"os/signal"
+	"path/filepath"
+	"strings"
+	"syscall"
+	"time"
+
+	"v.io/x/lib/metadata"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/flow"
+	"v.io/v23/i18n"
+	"v.io/v23/namespace"
+	"v.io/v23/naming"
+	"v.io/v23/options"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/verror"
+	"v.io/v23/vtrace"
+
+	"v.io/x/ref/internal/logger"
+	"v.io/x/ref/lib/apilog"
+	"v.io/x/ref/lib/flags"
+	"v.io/x/ref/lib/pubsub"
+	"v.io/x/ref/lib/stats"
+	_ "v.io/x/ref/lib/stats/sysstats"
+	"v.io/x/ref/runtime/internal/flow/manager"
+	"v.io/x/ref/runtime/internal/lib/dependency"
+	inaming "v.io/x/ref/runtime/internal/naming"
+	inamespace "v.io/x/ref/runtime/internal/naming/namespace"
+	irpc "v.io/x/ref/runtime/internal/rpc"
+	"v.io/x/ref/runtime/internal/rpc/stream"
+	imanager "v.io/x/ref/runtime/internal/rpc/stream/manager"
+	ivtrace "v.io/x/ref/runtime/internal/vtrace"
+)
+
+type contextKey int
+
+const (
+	streamManagerKey = contextKey(iota)
+	clientKey
+	namespaceKey
+	principalKey
+	backgroundKey
+	reservedNameKey
+	listenKey
+	flowManagerKey
+
+	// initKey is used to store values that are only set at init time.
+	initKey
+)
+
+type initData struct {
+	appCycle          v23.AppCycle
+	protocols         []string
+	settingsPublisher *pubsub.Publisher
+	settingsName      string
+}
+
+type vtraceDependency struct{}
+
+// Runtime implements the v23.Runtime interface.
+// Please see the interface definition for documentation of the
+// individiual methods.
+type Runtime struct {
+	ctx  *context.T
+	deps *dependency.Graph
+}
+
+func Init(
+	ctx *context.T,
+	appCycle v23.AppCycle,
+	protocols []string,
+	listenSpec *rpc.ListenSpec,
+	settingsPublisher *pubsub.Publisher,
+	settingsName string,
+	flags flags.RuntimeFlags,
+	reservedDispatcher rpc.Dispatcher) (*Runtime, *context.T, v23.Shutdown, error) {
+	r := &Runtime{deps: dependency.NewGraph()}
+
+	ctx = context.WithValue(ctx, initKey, &initData{
+		protocols:         protocols,
+		appCycle:          appCycle,
+		settingsPublisher: settingsPublisher,
+		settingsName:      settingsName,
+	})
+
+	if listenSpec != nil {
+		ctx = context.WithValue(ctx, listenKey, listenSpec.Copy())
+	}
+
+	if reservedDispatcher != nil {
+		ctx = context.WithValue(ctx, reservedNameKey, reservedDispatcher)
+	}
+
+	// Configure the context to use the global logger.
+	ctx = context.WithLogger(ctx, logger.Global())
+
+	// We want to print out metadata only into the log files, to avoid
+	// spamming stderr, see #1246.
+	//
+	// TODO(caprita): We should add it to the log file header information;
+	// since that requires changes to the llog and vlog packages, for now we
+	// condition printing of metadata on having specified an explicit
+	// log_dir for the program.  It's a hack, but it gets us the metadata
+	// to device manager-run apps and avoids it for command-lines, which is
+	// a good enough approximation.
+	if logger.Manager(ctx).LogDir() != os.TempDir() {
+		ctx.Infof(metadata.ToXML())
+	}
+
+	// Setup the initial trace.
+	ctx, err := ivtrace.Init(ctx, flags.Vtrace)
+	if err != nil {
+		return nil, nil, nil, err
+	}
+	ctx, _ = vtrace.WithNewTrace(ctx)
+	r.addChild(ctx, vtraceDependency{}, func() {
+		vtrace.FormatTraces(os.Stderr, vtrace.GetStore(ctx).TraceRecords(), nil)
+	})
+
+	// Setup i18n.
+	ctx = i18n.WithLangID(ctx, i18n.LangIDFromEnv())
+	if len(flags.I18nCatalogue) != 0 {
+		cat := i18n.Cat()
+		for _, filename := range strings.Split(flags.I18nCatalogue, ",") {
+			err := cat.MergeFromFile(filename)
+			if err != nil {
+				fmt.Fprintf(os.Stderr, "%s: i18n: error reading i18n catalogue file %q: %s\n", os.Args[0], filename, err)
+			}
+		}
+	}
+
+	// Setup the program name.
+	ctx = verror.WithComponentName(ctx, filepath.Base(os.Args[0]))
+
+	// Enable signal handling.
+	r.initSignalHandling(ctx)
+
+	// Set the initial namespace.
+	ctx, _, err = r.setNewNamespace(ctx, flags.NamespaceRoots...)
+	if err != nil {
+		return nil, nil, nil, err
+	}
+
+	// Create and set the principal
+	principal, deps, shutdown, err := r.initPrincipal(ctx, flags.Credentials)
+	if err != nil {
+		return nil, nil, nil, err
+	}
+	ctx, err = r.setPrincipal(ctx, principal, shutdown, deps...)
+	if err != nil {
+		return nil, nil, nil, err
+	}
+
+	// Setup authenticated "networking"
+	ctx, err = r.WithNewStreamManager(ctx)
+	if err != nil {
+		return nil, nil, nil, err
+	}
+
+	// Add the flow.Manager to the context.
+	ctx, _, err = r.ExperimentalWithNewFlowManager(ctx)
+	if err != nil {
+		return nil, nil, nil, err
+	}
+
+	r.ctx = ctx
+	return r, r.WithBackgroundContext(ctx), r.shutdown, nil
+}
+
+func (r *Runtime) addChild(ctx *context.T, me interface{}, stop func(), dependsOn ...interface{}) error {
+	if err := r.deps.Depend(me, dependsOn...); err != nil {
+		stop()
+		return err
+	} else if done := ctx.Done(); done != nil {
+		go func() {
+			<-done
+			finish := r.deps.CloseAndWait(me)
+			stop()
+			finish()
+		}()
+	}
+	return nil
+}
+
+func (r *Runtime) Init(ctx *context.T) error {
+	defer apilog.LogCall(ctx)(ctx) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	return r.initMgmt(ctx)
+}
+
+func (r *Runtime) shutdown() {
+	r.deps.CloseAndWaitForAll()
+	r.ctx.FlushLog()
+}
+
+func (r *Runtime) initSignalHandling(ctx *context.T) {
+	// TODO(caprita): Given that our device manager implementation is to
+	// kill all child apps when the device manager dies, we should
+	// enable SIGHUP on apps by default.
+
+	// Automatically handle SIGHUP to prevent applications started as
+	// daemons from being killed.  The developer can choose to still listen
+	// on SIGHUP and take a different action if desired.
+	signals := make(chan os.Signal, 1)
+	signal.Notify(signals, syscall.SIGHUP)
+	go func() {
+		for {
+			sig, ok := <-signals
+			if !ok {
+				break
+			}
+			r.ctx.Infof("Received signal %v", sig)
+		}
+	}()
+	r.addChild(ctx, signals, func() {
+		signal.Stop(signals)
+		close(signals)
+	})
+}
+
+func (*Runtime) NewEndpoint(ep string) (naming.Endpoint, error) {
+	// nologcall
+	return inaming.NewEndpoint(ep)
+}
+
+func (r *Runtime) NewServer(ctx *context.T, opts ...rpc.ServerOpt) (rpc.Server, error) {
+	defer apilog.LogCallf(ctx, "opts...=%v", opts)(ctx, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	// Create a new RoutingID (and StreamManager) for each server.
+	sm, err := newStreamManager(ctx)
+	if err != nil {
+		return nil, fmt.Errorf("failed to create rpc/stream/Manager: %v", err)
+	}
+
+	ns, _ := ctx.Value(namespaceKey).(namespace.T)
+	principal, _ := ctx.Value(principalKey).(security.Principal)
+	client, _ := ctx.Value(clientKey).(rpc.Client)
+
+	otherOpts := append([]rpc.ServerOpt{}, opts...)
+
+	if reservedDispatcher := r.GetReservedNameDispatcher(ctx); reservedDispatcher != nil {
+		otherOpts = append(otherOpts, irpc.ReservedNameDispatcher{
+			Dispatcher: reservedDispatcher,
+		})
+	}
+
+	id, _ := ctx.Value(initKey).(*initData)
+	if id.protocols != nil {
+		otherOpts = append(otherOpts, irpc.PreferredServerResolveProtocols(id.protocols))
+	}
+	if !hasServerBlessingsOpt(opts) && principal != nil {
+		otherOpts = append(otherOpts, options.ServerBlessings{
+			Blessings: principal.BlessingStore().Default(),
+		})
+	}
+	server, err := irpc.InternalNewServer(ctx, sm, ns, id.settingsPublisher, id.settingsName, r.GetClient(ctx), otherOpts...)
+	if err != nil {
+		return nil, err
+	}
+	stop := func() {
+		if err := server.Stop(); err != nil {
+			r.ctx.Errorf("A server could not be stopped: %v", err)
+		}
+		sm.Shutdown()
+	}
+	deps := []interface{}{client, vtraceDependency{}}
+	if principal != nil {
+		deps = append(deps, principal)
+	}
+	if err = r.addChild(ctx, server, stop, deps...); err != nil {
+		return nil, err
+	}
+	return server, nil
+}
+
+func hasServerBlessingsOpt(opts []rpc.ServerOpt) bool {
+	for _, o := range opts {
+		if _, ok := o.(options.ServerBlessings); ok {
+			return true
+		}
+	}
+	return false
+}
+
+func newStreamManager(ctx *context.T) (stream.Manager, error) {
+	rid, err := naming.NewRoutingID()
+	if err != nil {
+		return nil, err
+	}
+	sm := imanager.InternalNew(ctx, rid)
+	return sm, nil
+}
+
+func newFlowManager(ctx *context.T) (flow.Manager, error) {
+	rid, err := naming.NewRoutingID()
+	if err != nil {
+		return nil, err
+	}
+	return manager.New(ctx, rid), nil
+}
+
+func (r *Runtime) setNewFlowManager(ctx *context.T) (*context.T, flow.Manager, error) {
+	fm, err := newFlowManager(ctx)
+	if err != nil {
+		return nil, nil, err
+	}
+	// TODO(mattr): How can we close a flow manager.
+	if err = r.addChild(ctx, fm, func() {}); err != nil {
+		return ctx, nil, err
+	}
+	newctx := context.WithValue(ctx, flowManagerKey, fm)
+	return newctx, fm, nil
+}
+
+func (r *Runtime) setNewStreamManager(ctx *context.T) (*context.T, error) {
+	sm, err := newStreamManager(ctx)
+	if err != nil {
+		return nil, err
+	}
+	newctx := context.WithValue(ctx, streamManagerKey, sm)
+	if err = r.addChild(ctx, sm, sm.Shutdown); err != nil {
+		return ctx, err
+	}
+	return newctx, err
+}
+
+func (r *Runtime) WithNewStreamManager(ctx *context.T) (*context.T, error) {
+	defer apilog.LogCall(ctx)(ctx) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	newctx, err := r.setNewStreamManager(ctx)
+	if err != nil {
+		return ctx, err
+	}
+
+	// Create a new client since it depends on the stream manager.
+	newctx, _, err = r.WithNewClient(newctx)
+	if err != nil {
+		return ctx, err
+	}
+	return newctx, nil
+}
+
+func (r *Runtime) setPrincipal(ctx *context.T, principal security.Principal, shutdown func(), deps ...interface{}) (*context.T, error) {
+	if principal != nil {
+		// We uniquely identify a principal with "security/principal/<publicKey>"
+		principalName := "security/principal/" + principal.PublicKey().String()
+		stats.NewStringFunc(principalName+"/blessingstore", principal.BlessingStore().DebugString)
+		stats.NewStringFunc(principalName+"/blessingroots", principal.Roots().DebugString)
+	}
+	ctx = context.WithValue(ctx, principalKey, principal)
+	return ctx, r.addChild(ctx, principal, shutdown, deps...)
+}
+
+func (r *Runtime) WithPrincipal(ctx *context.T, principal security.Principal) (*context.T, error) {
+	defer apilog.LogCallf(ctx, "principal=%v", principal)(ctx, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	var err error
+	newctx := ctx
+
+	// TODO(mattr, suharshs): If there user gives us some principal that has dependencies
+	// we don't know about, we will not honour those dependencies during shutdown.
+	// For example if they create an agent principal with some client, we don't know
+	// about that, so servers based of this new principal will not prevent the client
+	// from terminating early.
+	if newctx, err = r.setPrincipal(ctx, principal, func() {}); err != nil {
+		return ctx, err
+	}
+	if newctx, err = r.setNewStreamManager(newctx); err != nil {
+		return ctx, err
+	}
+	if newctx, _, err = r.setNewFlowManager(newctx); err != nil {
+		return ctx, err
+	}
+	if newctx, _, err = r.setNewNamespace(newctx, r.GetNamespace(ctx).Roots()...); err != nil {
+		return ctx, err
+	}
+	if newctx, _, err = r.WithNewClient(newctx); err != nil {
+		return ctx, err
+	}
+
+	return newctx, nil
+}
+
+func (*Runtime) GetPrincipal(ctx *context.T) security.Principal {
+	// nologcall
+	p, _ := ctx.Value(principalKey).(security.Principal)
+	return p
+}
+
+func (r *Runtime) WithNewClient(ctx *context.T, opts ...rpc.ClientOpt) (*context.T, rpc.Client, error) {
+	defer apilog.LogCallf(ctx, "opts...=%v", opts)(ctx, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	otherOpts := append([]rpc.ClientOpt{}, opts...)
+
+	p, _ := ctx.Value(principalKey).(security.Principal)
+	sm, _ := ctx.Value(streamManagerKey).(stream.Manager)
+	ns, _ := ctx.Value(namespaceKey).(namespace.T)
+	fm, _ := ctx.Value(flowManagerKey).(flow.Manager)
+	otherOpts = append(otherOpts, imanager.DialTimeout(5*time.Minute))
+
+	if id, _ := ctx.Value(initKey).(*initData); id.protocols != nil {
+		otherOpts = append(otherOpts, irpc.PreferredProtocols(id.protocols))
+	}
+	var client rpc.Client
+	var err error
+	deps := []interface{}{vtraceDependency{}}
+	switch {
+	case fm != nil && sm != nil:
+		client, err = irpc.NewTransitionClient(ctx, sm, ns, otherOpts...)
+		deps = append(deps, fm, sm)
+	case fm != nil:
+		client, err = irpc.InternalNewXClient(ctx, otherOpts...)
+		deps = append(deps, fm)
+	case sm != nil:
+		client, err = irpc.InternalNewClient(sm, ns, otherOpts...)
+		deps = append(deps, sm)
+	}
+	if err != nil {
+		return ctx, nil, err
+	}
+	newctx := context.WithValue(ctx, clientKey, client)
+	if p != nil {
+		deps = append(deps, p)
+	}
+	if err = r.addChild(ctx, client, client.Close, deps...); err != nil {
+		return ctx, nil, err
+	}
+	return newctx, client, err
+}
+
+func (*Runtime) GetClient(ctx *context.T) rpc.Client {
+	// nologcall
+	cl, _ := ctx.Value(clientKey).(rpc.Client)
+	return cl
+}
+
+func (r *Runtime) setNewNamespace(ctx *context.T, roots ...string) (*context.T, namespace.T, error) {
+	ns, err := inamespace.New(roots...)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	if oldNS := r.GetNamespace(ctx); oldNS != nil {
+		ns.CacheCtl(oldNS.CacheCtl()...)
+	}
+
+	if err == nil {
+		ctx = context.WithValue(ctx, namespaceKey, ns)
+	}
+	return ctx, ns, err
+}
+
+func (r *Runtime) WithNewNamespace(ctx *context.T, roots ...string) (*context.T, namespace.T, error) {
+	defer apilog.LogCallf(ctx, "roots...=%v", roots)(ctx, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	newctx, ns, err := r.setNewNamespace(ctx, roots...)
+	if err != nil {
+		return ctx, nil, err
+	}
+
+	// Replace the client since it depends on the namespace.
+	newctx, _, err = r.WithNewClient(newctx)
+	if err != nil {
+		return ctx, nil, err
+	}
+
+	return newctx, ns, err
+}
+
+func (*Runtime) GetNamespace(ctx *context.T) namespace.T {
+	// nologcall
+	ns, _ := ctx.Value(namespaceKey).(namespace.T)
+	return ns
+}
+
+func (*Runtime) GetAppCycle(ctx *context.T) v23.AppCycle {
+	// nologcall
+	id, _ := ctx.Value(initKey).(*initData)
+	return id.appCycle
+}
+
+func (*Runtime) GetListenSpec(ctx *context.T) rpc.ListenSpec {
+	// nologcall
+	ls, _ := ctx.Value(listenKey).(rpc.ListenSpec)
+	return ls
+}
+
+func (*Runtime) WithListenSpec(ctx *context.T, ls rpc.ListenSpec) *context.T {
+	defer apilog.LogCall(ctx)(ctx) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	return context.WithValue(ctx, listenKey, ls.Copy())
+}
+
+func (*Runtime) WithBackgroundContext(ctx *context.T) *context.T {
+	defer apilog.LogCall(ctx)(ctx) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	// Note we add an extra context with a nil value here.
+	// This prevents users from travelling back through the
+	// chain of background contexts.
+	ctx = context.WithValue(ctx, backgroundKey, nil)
+	return context.WithValue(ctx, backgroundKey, ctx)
+}
+
+func (*Runtime) GetBackgroundContext(ctx *context.T) *context.T {
+	// nologcall
+	bctx, _ := ctx.Value(backgroundKey).(*context.T)
+	if bctx == nil {
+		// There should always be a background context.  If we don't find
+		// it, that means that the user passed us the background context
+		// in hopes of following the chain.  Instead we just give them
+		// back what they sent in, which is correct.
+		return ctx
+	}
+	return bctx
+}
+
+func (*Runtime) WithReservedNameDispatcher(ctx *context.T, d rpc.Dispatcher) *context.T {
+	defer apilog.LogCall(ctx)(ctx) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	return context.WithValue(ctx, reservedNameKey, d)
+}
+
+func (*Runtime) GetReservedNameDispatcher(ctx *context.T) rpc.Dispatcher {
+	// nologcall
+	if d, ok := ctx.Value(reservedNameKey).(rpc.Dispatcher); ok {
+		return d
+	}
+	return nil
+}
+
+func (*Runtime) ExperimentalGetFlowManager(ctx *context.T) flow.Manager {
+	// nologcall
+	if d, ok := ctx.Value(flowManagerKey).(flow.Manager); ok {
+		return d
+	}
+	return nil
+}
+
+func (r *Runtime) ExperimentalWithNewFlowManager(ctx *context.T) (*context.T, flow.Manager, error) {
+	defer apilog.LogCall(ctx)(ctx) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	newctx, m, err := r.setNewFlowManager(ctx)
+	if err != nil {
+		return ctx, nil, err
+	}
+	// Create a new client since it depends on the flow manager.
+	newctx, _, err = r.WithNewClient(newctx)
+	if err != nil {
+		return ctx, nil, err
+	}
+	return newctx, m, nil
+}
+
+func (r *Runtime) XWithNewServer(ctx *context.T, name string, object interface{}, auth security.Authorizer, opts ...rpc.ServerOpt) (*context.T, rpc.XServer, error) {
+	defer apilog.LogCall(ctx)(ctx) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	panic("unimplemented")
+}
+
+func (r *Runtime) XWithNewDispatchingServer(ctx *context.T, name string, disp rpc.Dispatcher, opts ...rpc.ServerOpt) (*context.T, rpc.XServer, error) {
+	defer apilog.LogCall(ctx)(ctx) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	panic("unimplemented")
+}
diff --git a/runtime/internal/rt/runtime_test.go b/runtime/internal/rt/runtime_test.go
new file mode 100644
index 0000000..9b7f6cd
--- /dev/null
+++ b/runtime/internal/rt/runtime_test.go
@@ -0,0 +1,175 @@
+// 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 rt_test
+
+import (
+	"testing"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/options"
+
+	"v.io/x/ref/lib/flags"
+	"v.io/x/ref/runtime/internal/rt"
+	"v.io/x/ref/services/debug/debuglib"
+	"v.io/x/ref/test/testutil"
+)
+
+// initForTest creates a context for use in a test.
+func initForTest(t *testing.T) (*rt.Runtime, *context.T, v23.Shutdown) {
+	ctx, cancel := context.RootContext()
+	r, ctx, shutdown, err := rt.Init(ctx, nil, nil, nil, nil, "", flags.RuntimeFlags{}, nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if ctx, err = r.WithPrincipal(ctx, testutil.NewPrincipal("test-blessing")); err != nil {
+		t.Fatal(err)
+	}
+	return r, ctx, func() {
+		cancel()
+		shutdown()
+	}
+}
+
+func TestNewServer(t *testing.T) {
+	r, ctx, shutdown := initForTest(t)
+	defer shutdown()
+
+	// Use options.SecurityNone to avoid calling back into the
+	// v23 runtime, which is not setup in these tests.
+	// TODO(cnicolaou): this can be undone when the security agent
+	// no longer uses rpc as its communication mechanism.
+	if s, err := r.NewServer(ctx, options.SecurityNone); err != nil || s == nil {
+		t.Fatalf("Could not create server: %v", err)
+	}
+}
+
+func TestPrincipal(t *testing.T) {
+	r, ctx, shutdown := initForTest(t)
+	defer shutdown()
+
+	p2 := testutil.NewPrincipal()
+	c2, err := r.WithPrincipal(ctx, p2)
+	if err != nil {
+		t.Fatalf("Could not attach principal: %v", err)
+	}
+	if !c2.Initialized() {
+		t.Fatal("Got uninitialized context.")
+	}
+	if p2 != r.GetPrincipal(c2) {
+		t.Fatal("The new principal should be attached to the context, but it isn't")
+	}
+}
+
+func TestClient(t *testing.T) {
+	r, ctx, shutdown := initForTest(t)
+	defer shutdown()
+
+	orig := r.GetClient(ctx)
+
+	c2, client, err := r.WithNewClient(ctx)
+	if err != nil || client == nil {
+		t.Fatalf("Could not create client: %v", err)
+	}
+	if !c2.Initialized() {
+		t.Fatal("Got uninitialized context.")
+	}
+	if client == orig {
+		t.Fatal("Should have replaced the client but didn't")
+	}
+	if client != r.GetClient(c2) {
+		t.Fatal("The new client should be attached to the context, but it isn't")
+	}
+}
+
+func TestNamespace(t *testing.T) {
+	r, ctx, shutdown := initForTest(t)
+	defer shutdown()
+
+	orig := r.GetNamespace(ctx)
+	orig.CacheCtl(naming.DisableCache(true))
+
+	newroots := []string{"/newroot1", "/newroot2"}
+	c2, ns, err := r.WithNewNamespace(ctx, newroots...)
+	if err != nil || ns == nil {
+		t.Fatalf("Could not create namespace: %v", err)
+	}
+	if !c2.Initialized() {
+		t.Fatal("Got uninitialized context.")
+	}
+	if ns == orig {
+		t.Fatal("Should have replaced the namespace but didn't")
+	}
+	if ns != r.GetNamespace(c2) {
+		t.Fatal("The new namespace should be attached to the context, but it isn't")
+	}
+	newrootmap := map[string]bool{"/newroot1": true, "/newroot2": true}
+	for _, root := range ns.Roots() {
+		if !newrootmap[root] {
+			t.Errorf("root %s found in ns, but we expected: %v", root, newroots)
+		}
+	}
+	opts := ns.CacheCtl()
+	if len(opts) != 1 {
+		t.Fatalf("Expected one option for cache control, got %v", opts)
+	}
+	if disable, ok := opts[0].(naming.DisableCache); !ok || !bool(disable) {
+		t.Errorf("expected a disable(true) message got %#v", opts[0])
+	}
+}
+
+func TestBackgroundContext(t *testing.T) {
+	r, ctx, shutdown := initForTest(t)
+	defer shutdown()
+
+	bgctx := r.GetBackgroundContext(ctx)
+
+	if bgctx == ctx {
+		t.Error("The background context should not be the same as the context")
+	}
+
+	bgctx2 := r.GetBackgroundContext(bgctx)
+	if bgctx != bgctx2 {
+		t.Error("Calling GetBackgroundContext a second time should return the same context.")
+	}
+}
+
+func TestReservedNameDispatcher(t *testing.T) {
+	r, ctx, shutdown := initForTest(t)
+	defer shutdown()
+
+	oldDebugDisp := r.GetReservedNameDispatcher(ctx)
+	newDebugDisp := debuglib.NewDispatcher(nil)
+
+	nctx := r.WithReservedNameDispatcher(ctx, newDebugDisp)
+	debugDisp := r.GetReservedNameDispatcher(nctx)
+
+	if debugDisp != newDebugDisp || debugDisp == oldDebugDisp {
+		t.Error("WithNewDebugDispatcher didn't update the context properly")
+	}
+
+}
+
+func TestFlowManager(t *testing.T) {
+	r, ctx, shutdown := initForTest(t)
+	defer shutdown()
+
+	oldman := r.ExperimentalGetFlowManager(ctx)
+	if oldman == nil {
+		t.Error("ExperimentalGetFlowManager should have returned a non-nil value")
+	}
+	newctx, newman, err := r.ExperimentalWithNewFlowManager(ctx)
+	if err != nil || newman == nil || newman == oldman {
+		t.Fatalf("Could not create flow manager: %v", err)
+	}
+	if !newctx.Initialized() {
+		t.Fatal("Got uninitialized context.")
+	}
+	man := r.ExperimentalGetFlowManager(newctx)
+	if man != newman || man == oldman {
+		t.Error("ExperimentalWithNewFlowManager didn't update the context properly")
+	}
+}
diff --git a/runtime/internal/rt/security.go b/runtime/internal/rt/security.go
new file mode 100644
index 0000000..1b8d6cf
--- /dev/null
+++ b/runtime/internal/rt/security.go
@@ -0,0 +1,170 @@
+// 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 rt
+
+import (
+	"fmt"
+	"os"
+	"os/user"
+	"strconv"
+	"syscall"
+
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/security"
+	"v.io/v23/verror"
+	"v.io/x/ref"
+	"v.io/x/ref/lib/exec"
+	"v.io/x/ref/lib/mgmt"
+	vsecurity "v.io/x/ref/lib/security"
+	inaming "v.io/x/ref/runtime/internal/naming"
+	"v.io/x/ref/services/agent"
+	"v.io/x/ref/services/agent/agentlib"
+)
+
+func (r *Runtime) initPrincipal(ctx *context.T, credentials string) (principal security.Principal, deps []interface{}, shutdown func(), err error) {
+	if principal, _ = ctx.Value(principalKey).(security.Principal); principal != nil {
+		return principal, nil, func() {}, nil
+	}
+	if len(credentials) > 0 {
+		// Explicitly specified credentials, ignore the agent.
+		if _, fd, _ := agentEP(); fd >= 0 {
+			syscall.Close(fd)
+		}
+		// TODO(ataly, ashankar): If multiple runtimes are getting
+		// initialized at the same time from the same
+		// ref.EnvCredentials we will need some kind of locking for the
+		// credential files.
+		if principal, err = vsecurity.LoadPersistentPrincipal(credentials, nil); err != nil {
+			if os.IsNotExist(err) {
+				if principal, err = vsecurity.CreatePersistentPrincipal(credentials, nil); err != nil {
+					return principal, nil, nil, err
+				}
+				return principal, nil, func() {}, vsecurity.InitDefaultBlessings(principal, defaultBlessingName())
+			}
+			return nil, nil, nil, err
+		}
+		return principal, nil, func() {}, nil
+	}
+	// Use credentials stored in the agent.
+	if principal, err := ipcAgent(); err != nil {
+		return nil, nil, nil, err
+	} else if principal != nil {
+		return principal, nil, func() { principal.Close() }, nil
+	}
+	if ep, _, err := agentEP(); err != nil {
+		return nil, nil, nil, err
+	} else if ep != nil {
+		// Use a new stream manager and an "incomplete" client (the
+		// principal is nil) to talk to the agent.
+		//
+		// The lack of a principal works out for the rpc.Client
+		// only because the agent uses anonymous unix sockets and
+		// the SecurityNone option.
+		//
+		// Using a distinct stream manager to manage agent-related
+		// connections helps isolate these connections to the agent
+		// from management of any other connections created in the
+		// process (such as future RPCs to other services).
+		if ctx, err = r.WithNewStreamManager(ctx); err != nil {
+			return nil, nil, nil, err
+		}
+		client := r.GetClient(ctx)
+
+		// We reparent the context we use to construct the agent.
+		// We do this because the agent needs to be able to make RPCs
+		// during runtime shutdown.
+		ctx, shutdown = context.WithRootCancel(ctx)
+
+		// TODO(cnicolaou): the agentlib can call back into runtime to get the principal,
+		// which will be a problem if the runtime is not initialized, hence this code
+		// path is fragile. We should ideally provide an option to work around this case.
+		if principal, err = agentlib.NewAgentPrincipal(ctx, ep, client); err != nil {
+			shutdown()
+			client.Close()
+			return nil, nil, nil, err
+		}
+		return principal, []interface{}{client}, shutdown, nil
+	}
+	// No agent, no explicit credentials specified: - create a new principal and blessing in memory.
+	if principal, err = vsecurity.NewPrincipal(); err != nil {
+		return principal, nil, nil, err
+	}
+	return principal, nil, func() {}, vsecurity.InitDefaultBlessings(principal, defaultBlessingName())
+}
+
+func parseAgentFD(ep naming.Endpoint) (int, error) {
+	fd := ep.Addr().String()
+	ifd, err := strconv.Atoi(fd)
+	if err != nil {
+		ifd = -1
+	}
+	return ifd, nil
+}
+
+func ipcAgent() (agent.Principal, error) {
+	handle, err := exec.GetChildHandle()
+	if err != nil && verror.ErrorID(err) != exec.ErrNoVersion.ID {
+		return nil, err
+	}
+	var path string
+	if handle != nil {
+		// We were started by a parent (presumably, device manager).
+		path, _ = handle.Config.Get(mgmt.SecurityAgentPathConfigKey)
+	} else {
+		path = os.Getenv(ref.EnvAgentPath)
+	}
+	if path == "" {
+		return nil, nil
+	}
+	return agentlib.NewAgentPrincipalX(path)
+}
+
+// agentEP returns an Endpoint to be used to communicate with
+// the security agent if the current process has been configured to use the
+// agent.
+func agentEP() (naming.Endpoint, int, error) {
+	handle, err := exec.GetChildHandle()
+	if err != nil && verror.ErrorID(err) != exec.ErrNoVersion.ID {
+		return nil, -1, err
+	}
+	var endpoint string
+	if handle != nil {
+		// We were started by a parent (presumably, device manager).
+		endpoint, _ = handle.Config.Get(mgmt.SecurityAgentEndpointConfigKey)
+	} else {
+		endpoint = os.Getenv(ref.EnvAgentEndpoint)
+	}
+	if endpoint == "" {
+		return nil, -1, nil
+	}
+	ep, err := inaming.NewEndpoint(endpoint)
+	if err != nil {
+		return nil, -1, err
+	}
+
+	// Don't let children accidentally inherit the agent connection.
+	fd, err := parseAgentFD(ep)
+	if err != nil {
+		return nil, -1, err
+	}
+	if fd >= 0 {
+		syscall.CloseOnExec(fd)
+	}
+	return ep, fd, nil
+}
+
+func defaultBlessingName() string {
+	var name string
+	if user, _ := user.Current(); user != nil && len(user.Username) > 0 {
+		name = user.Username
+	} else {
+		name = "anonymous"
+	}
+	if host, _ := os.Hostname(); len(host) > 0 {
+		name = name + "@" + host
+	}
+	return fmt.Sprintf("%s-%d", name, os.Getpid())
+}
diff --git a/runtime/internal/rt/shutdown_servers_test.go b/runtime/internal/rt/shutdown_servers_test.go
new file mode 100644
index 0000000..b6612e4
--- /dev/null
+++ b/runtime/internal/rt/shutdown_servers_test.go
@@ -0,0 +1,264 @@
+// 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 rt_test
+
+import (
+	"bufio"
+	"fmt"
+	"io"
+	"os"
+	"os/signal"
+	"sync"
+	"syscall"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+
+	"v.io/x/ref/lib/signals"
+	"v.io/x/ref/lib/xrpc"
+	_ "v.io/x/ref/runtime/factories/generic"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
+)
+
+type dummy struct{}
+
+func (*dummy) Echo(*context.T, rpc.ServerCall) error { return nil }
+
+// remoteCmdLoop listens on stdin and interprets commands sent over stdin (from
+// the parent process).
+func remoteCmdLoop(ctx *context.T, stdin io.Reader) func() {
+	done := make(chan struct{})
+	go func() {
+		scanner := bufio.NewScanner(stdin)
+		for scanner.Scan() {
+			switch scanner.Text() {
+			case "stop":
+				v23.GetAppCycle(ctx).Stop(ctx)
+			case "forcestop":
+				fmt.Println("straight exit")
+				v23.GetAppCycle(ctx).ForceStop(ctx)
+			case "close":
+				close(done)
+				return
+			}
+		}
+	}()
+	return func() { <-done }
+}
+
+// complexServerProgram demonstrates the recommended way to write a more
+// complex server application (with several servers, a mix of interruptible
+// and blocking cleanup, and parallel and sequential cleanup execution).
+// For a more typical server, see simpleServerProgram.
+var complexServerProgram = modules.Register(func(env *modules.Env, args ...string) error {
+	// Initialize the runtime.  This is boilerplate.
+	ctx, shutdown := test.V23Init()
+	// shutdown is optional, but it's a good idea to clean up, especially
+	// since it takes care of flushing the logs before exiting.
+	defer shutdown()
+
+	// This is part of the test setup -- we need a way to accept
+	// commands from the parent process to simulate Stop and
+	// RemoteStop commands that would normally be issued from
+	// application code.
+	defer remoteCmdLoop(ctx, env.Stdin)()
+
+	// Create a couple servers, and start serving.
+	server1, err := xrpc.NewServer(ctx, "", &dummy{}, nil)
+	if err != nil {
+		ctx.Fatalf("r.NewServer error: %s", err)
+	}
+	server2, err := xrpc.NewServer(ctx, "", &dummy{}, nil)
+	if err != nil {
+		ctx.Fatalf("r.NewServer error: %s", err)
+	}
+
+	// This is how to wait for a shutdown.  In this example, a shutdown
+	// comes from a signal or a stop command.
+	var done sync.WaitGroup
+	done.Add(1)
+
+	// This is how to configure signal handling to allow clean shutdown.
+	sigChan := make(chan os.Signal, 2)
+	signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT)
+
+	// This is how to configure handling of stop commands to allow clean
+	// shutdown.
+	stopChan := make(chan string, 2)
+	v23.GetAppCycle(ctx).WaitForStop(ctx, stopChan)
+
+	// Blocking is used to prevent the process from exiting upon receiving a
+	// second signal or stop command while critical cleanup code is
+	// executing.
+	var blocking sync.WaitGroup
+	blockingCh := make(chan struct{})
+
+	// This is how to wait for a signal or stop command and initiate the
+	// clean shutdown steps.
+	go func() {
+		// First signal received.
+		select {
+		case sig := <-sigChan:
+			// If the developer wants to take different actions
+			// depending on the type of signal, they can do it here.
+			fmt.Fprintln(env.Stdout, "Received signal", sig)
+		case stop := <-stopChan:
+			fmt.Fprintln(env.Stdout, "Stop", stop)
+		}
+		// This commences the cleanup stage.
+		done.Done()
+		// Wait for a second signal or stop command, and force an exit,
+		// but only once all blocking cleanup code (if any) has
+		// completed.
+		select {
+		case <-sigChan:
+		case <-stopChan:
+		}
+		<-blockingCh
+		os.Exit(signals.DoubleStopExitCode)
+	}()
+
+	// This communicates to the parent test driver process in our unit test
+	// that this server is ready and waiting on signals or stop commands.
+	// It's purely an artifact of our test setup.
+	fmt.Fprintln(env.Stdout, "Ready")
+
+	// Wait for shutdown.
+	done.Wait()
+
+	// Stop the servers.  In this example we stop them in goroutines to
+	// parallelize the wait, but if there was a dependency between the
+	// servers, the developer can simply stop them sequentially.
+	var waitServerStop sync.WaitGroup
+	waitServerStop.Add(2)
+	go func() {
+		server1.Stop()
+		waitServerStop.Done()
+	}()
+	go func() {
+		server2.Stop()
+		waitServerStop.Done()
+	}()
+	waitServerStop.Wait()
+
+	// This is where all cleanup code should go.  By placing it at the end,
+	// we make its purpose and order of execution clear.
+
+	// This is an example of how to mix parallel and sequential cleanup
+	// steps.  Most real-world servers will likely be simpler, with either
+	// just sequential or just parallel cleanup stages.
+
+	// parallelCleanup is used to wait for all goroutines executing cleanup
+	// code in parallel to finish.
+	var parallelCleanup sync.WaitGroup
+
+	// Simulate four parallel cleanup steps, two blocking and two
+	// interruptible.
+	parallelCleanup.Add(1)
+	blocking.Add(1)
+	go func() {
+		fmt.Fprintln(env.Stdout, "Parallel blocking cleanup1")
+		blocking.Done()
+		parallelCleanup.Done()
+	}()
+
+	parallelCleanup.Add(1)
+	blocking.Add(1)
+	go func() {
+		fmt.Fprintln(env.Stdout, "Parallel blocking cleanup2")
+		blocking.Done()
+		parallelCleanup.Done()
+	}()
+
+	parallelCleanup.Add(1)
+	go func() {
+		fmt.Fprintln(env.Stdout, "Parallel interruptible cleanup1")
+		parallelCleanup.Done()
+	}()
+
+	parallelCleanup.Add(1)
+	go func() {
+		fmt.Fprintln(env.Stdout, "Parallel interruptible cleanup2")
+		parallelCleanup.Done()
+	}()
+
+	// Simulate two sequential cleanup steps, one blocking and one
+	// interruptible.
+	fmt.Fprintln(env.Stdout, "Sequential blocking cleanup")
+	blocking.Wait()
+	close(blockingCh)
+
+	fmt.Fprintln(env.Stdout, "Sequential interruptible cleanup")
+
+	parallelCleanup.Wait()
+	return nil
+}, "complexServerProgram")
+
+// simpleServerProgram demonstrates the recommended way to write a typical
+// simple server application (with one server and a clean shutdown triggered by
+// a signal or a stop command).  For an example of something more involved, see
+// complexServerProgram.
+var simpleServerProgram = modules.Register(func(env *modules.Env, args ...string) error {
+	// Initialize the runtime.  This is boilerplate.
+	ctx, shutdown := test.V23Init()
+	// Calling shutdown is optional, but it's a good idea to clean up, especially
+	// since it takes care of flushing the logs before exiting.
+	//
+	// We use defer to ensure this is the last thing in the program (to
+	// avoid shutting down the runtime while it may still be in use), and to
+	// allow it to execute even if a panic occurs down the road.
+	defer shutdown()
+
+	// This is part of the test setup -- we need a way to accept
+	// commands from the parent process to simulate Stop and
+	// RemoteStop commands that would normally be issued from
+	// application code.
+	defer remoteCmdLoop(ctx, env.Stdin)()
+
+	// Create a server, and start serving.
+	server, err := xrpc.NewServer(ctx, "", &dummy{}, nil)
+	if err != nil {
+		ctx.Fatalf("r.NewServer error: %s", err)
+	}
+
+	// This is how to wait for a shutdown.  In this example, a shutdown
+	// comes from a signal or a stop command.
+	//
+	// Note, if the developer wants to exit immediately upon receiving a
+	// signal or stop command, they can skip this, in which case the default
+	// behavior is for the process to exit.
+	waiter := signals.ShutdownOnSignals(ctx)
+
+	// This communicates to the parent test driver process in our unit test
+	// that this server is ready and waiting on signals or stop commands.
+	// It's purely an artifact of our test setup.
+	fmt.Fprintln(env.Stdout, "Ready")
+
+	// Use defer for anything that should still execute even if a panic
+	// occurs.
+	defer fmt.Fprintln(env.Stdout, "Deferred cleanup")
+
+	// Wait for shutdown.
+	sig := <-waiter
+	// The developer could take different actions depending on the type of
+	// signal.
+	fmt.Fprintln(env.Stdout, "Received signal", sig)
+
+	// Cleanup code starts here.  Alternatively, these steps could be
+	// invoked through defer, but we list them here to make the order of
+	// operations obvious.
+
+	// Stop the server.
+	server.Stop()
+
+	// Note, this will not execute in cases of forced shutdown
+	// (e.g. SIGSTOP), when the process calls os.Exit (e.g. via log.Fatal),
+	// or when a panic occurs.
+	fmt.Fprintln(env.Stdout, "Interruptible cleanup")
+
+	return nil
+}, "simpleServerProgram")
diff --git a/runtime/internal/rt/shutdown_test.go b/runtime/internal/rt/shutdown_test.go
new file mode 100644
index 0000000..efb7aff
--- /dev/null
+++ b/runtime/internal/rt/shutdown_test.go
@@ -0,0 +1,227 @@
+// 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 rt_test
+
+import (
+	"fmt"
+	"io"
+	"os"
+	"syscall"
+	"testing"
+
+	"v.io/v23"
+	"v.io/x/ref/lib/signals"
+	"v.io/x/ref/test/modules"
+)
+
+//go:generate v23 test generate
+
+var cstderr io.Writer
+
+func init() {
+	if testing.Verbose() {
+		cstderr = os.Stderr
+	}
+}
+
+func newShell(t *testing.T) *modules.Shell {
+	sh, err := modules.NewShell(nil, nil, testing.Verbose(), t)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	return sh
+}
+
+// TestSimpleServerSignal verifies that sending a signal to the simple server
+// causes it to exit cleanly.
+func TestSimpleServerSignal(t *testing.T) {
+	sh := newShell(t)
+	defer sh.Cleanup(os.Stdout, cstderr)
+	h, _ := sh.Start(nil, simpleServerProgram)
+	h.Expect("Ready")
+	syscall.Kill(h.Pid(), syscall.SIGINT)
+	h.Expect("Received signal interrupt")
+	h.Expect("Interruptible cleanup")
+	h.Expect("Deferred cleanup")
+	fmt.Fprintln(h.Stdin(), "close")
+	h.ExpectEOF()
+}
+
+// TestSimpleServerLocalStop verifies that sending a local stop command to the
+// simple server causes it to exit cleanly.
+func TestSimpleServerLocalStop(t *testing.T) {
+	sh := newShell(t)
+	defer sh.Cleanup(os.Stdout, cstderr)
+	h, _ := sh.Start(nil, simpleServerProgram)
+	h.Expect("Ready")
+	fmt.Fprintln(h.Stdin(), "stop")
+	h.Expect(fmt.Sprintf("Received signal %s", v23.LocalStop))
+	h.Expect("Interruptible cleanup")
+	h.Expect("Deferred cleanup")
+	fmt.Fprintln(h.Stdin(), "close")
+	h.ExpectEOF()
+}
+
+// TestSimpleServerDoubleSignal verifies that sending a succession of two
+// signals to the simple server causes it to initiate the cleanup sequence on
+// the first signal and then exit immediately on the second signal.
+func TestSimpleServerDoubleSignal(t *testing.T) {
+	sh := newShell(t)
+	defer sh.Cleanup(os.Stdout, cstderr)
+	h, _ := sh.Start(nil, simpleServerProgram)
+	h.Expect("Ready")
+	syscall.Kill(h.Pid(), syscall.SIGINT)
+	h.Expect("Received signal interrupt")
+	syscall.Kill(h.Pid(), syscall.SIGINT)
+	err := h.Shutdown(os.Stdout, cstderr)
+	if err == nil {
+		t.Fatalf("expected an error")
+	}
+	if got, want := err.Error(), fmt.Sprintf("exit status %d", signals.DoubleStopExitCode); got != want {
+		t.Errorf("got %q, want %q", got, want)
+	}
+}
+
+// TestSimpleServerLocalForceStop verifies that sending a local ForceStop
+// command to the simple server causes it to exit immediately.
+func TestSimpleServerLocalForceStop(t *testing.T) {
+	sh := newShell(t)
+	defer sh.Cleanup(os.Stdout, cstderr)
+	h, _ := sh.Start(nil, simpleServerProgram)
+	h.Expect("Ready")
+	fmt.Fprintln(h.Stdin(), "forcestop")
+	h.Expect("straight exit")
+	err := h.Shutdown(os.Stdout, cstderr)
+	if err == nil {
+		t.Fatalf("expected an error")
+	}
+	if got, want := err.Error(), fmt.Sprintf("exit status %d", v23.ForceStopExitCode); got != want {
+		t.Errorf("got %q, want %q", got, want)
+	}
+}
+
+// TestSimpleServerKill demonstrates that a SIGKILL still forces the server
+// to exit regardless of our signal handling.
+func TestSimpleServerKill(t *testing.T) {
+	sh := newShell(t)
+	defer sh.Cleanup(os.Stdout, cstderr)
+	h, _ := sh.Start(nil, simpleServerProgram)
+	h.Expect("Ready")
+	syscall.Kill(h.Pid(), syscall.SIGKILL)
+	err := h.Shutdown(os.Stdout, cstderr)
+	if err == nil {
+		t.Fatalf("expected an error")
+	}
+	if got, want := err.Error(), "signal: killed"; got != want {
+		t.Errorf("got %q, want %q", got, want)
+	}
+}
+
+// TestComplexServerSignal verifies that sending a signal to the complex server
+// initiates the cleanup sequence in that server (we observe the printouts
+// corresponding to all the simulated sequential/parallel and
+// blocking/interruptible shutdown steps), and then exits cleanly.
+func TestComplexServerSignal(t *testing.T) {
+	sh := newShell(t)
+	defer sh.Cleanup(os.Stdout, cstderr)
+	h, _ := sh.Start(nil, complexServerProgram)
+	h.Expect("Ready")
+	syscall.Kill(h.Pid(), syscall.SIGINT)
+	h.Expect("Received signal interrupt")
+	h.ExpectSetRE("Sequential blocking cleanup",
+		"Sequential interruptible cleanup",
+		"Parallel blocking cleanup1",
+		"Parallel blocking cleanup2",
+		"Parallel interruptible cleanup1",
+		"Parallel interruptible cleanup2")
+	fmt.Fprintln(h.Stdin(), "close")
+	h.ExpectEOF()
+}
+
+// TestComplexServerLocalStop verifies that sending a local stop command to the
+// complex server initiates the cleanup sequence in that server (we observe the
+// printouts corresponding to all the simulated sequential/parallel and
+// blocking/interruptible shutdown steps), and then exits cleanly.
+func TestComplexServerLocalStop(t *testing.T) {
+	sh := newShell(t)
+	defer sh.Cleanup(os.Stdout, cstderr)
+	h, _ := sh.Start(nil, complexServerProgram)
+	h.Expect("Ready")
+
+	fmt.Fprintln(h.Stdin(), "stop")
+	h.Expect(fmt.Sprintf("Stop %s", v23.LocalStop))
+	h.ExpectSetRE(
+		"Sequential blocking cleanup",
+		"Sequential interruptible cleanup",
+		"Parallel blocking cleanup1",
+		"Parallel blocking cleanup2",
+		"Parallel interruptible cleanup1",
+		"Parallel interruptible cleanup2",
+	)
+	fmt.Fprintln(h.Stdin(), "close")
+	h.ExpectEOF()
+}
+
+// TestComplexServerDoubleSignal verifies that sending a succession of two
+// signals to the complex server has the expected effect: the first signal
+// initiates the cleanup steps and the second signal kills the process, but only
+// after the blocking shutdown steps were allowed to complete (as observed by
+// the corresponding printouts from the server).  Note that we have no
+// expectations on whether or not the interruptible shutdown steps execute.
+func TestComplexServerDoubleSignal(t *testing.T) {
+	sh := newShell(t)
+	defer sh.Cleanup(os.Stdout, cstderr)
+	h, _ := sh.Start(nil, complexServerProgram)
+	h.Expect("Ready")
+	syscall.Kill(h.Pid(), syscall.SIGINT)
+	h.Expect("Received signal interrupt")
+	syscall.Kill(h.Pid(), syscall.SIGINT)
+	h.ExpectSetEventuallyRE(
+		"Sequential blocking cleanup",
+		"Parallel blocking cleanup1",
+		"Parallel blocking cleanup2")
+	err := h.Shutdown(os.Stdout, cstderr)
+	if err == nil {
+		t.Fatalf("expected an error")
+	}
+	if got, want := err.Error(), fmt.Sprintf("exit status %d", signals.DoubleStopExitCode); got != want {
+		t.Errorf("got %q, want %q", got, want)
+	}
+}
+
+// TestComplexServerLocalForceStop verifies that sending a local ForceStop
+// command to the complex server forces it to exit immediately.
+func TestComplexServerLocalForceStop(t *testing.T) {
+	sh := newShell(t)
+	defer sh.Cleanup(os.Stdout, cstderr)
+	h, _ := sh.Start(nil, complexServerProgram)
+	h.Expect("Ready")
+	fmt.Fprintln(h.Stdin(), "forcestop")
+	h.Expect("straight exit")
+	err := h.Shutdown(os.Stdout, cstderr)
+	if err == nil {
+		t.Fatalf("expected an error")
+	}
+	if got, want := err.Error(), fmt.Sprintf("exit status %d", v23.ForceStopExitCode); got != want {
+		t.Errorf("got %q, want %q", got, want)
+	}
+}
+
+// TestComplexServerKill demonstrates that a SIGKILL still forces the server to
+// exit regardless of our signal handling.
+func TestComplexServerKill(t *testing.T) {
+	sh := newShell(t)
+	defer sh.Cleanup(os.Stdout, cstderr)
+	h, _ := sh.Start(nil, complexServerProgram)
+	h.Expect("Ready")
+	syscall.Kill(h.Pid(), syscall.SIGKILL)
+	err := h.Shutdown(os.Stdout, cstderr)
+	if err == nil {
+		t.Fatalf("expected an error")
+	}
+	if got, want := err.Error(), "signal: killed"; got != want {
+		t.Errorf("got %q, want %q", got, want)
+	}
+}
diff --git a/runtime/internal/rt/signal_test.go b/runtime/internal/rt/signal_test.go
new file mode 100644
index 0000000..7d7c466
--- /dev/null
+++ b/runtime/internal/rt/signal_test.go
@@ -0,0 +1,84 @@
+// 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 rt_test
+
+import (
+	"bufio"
+	"fmt"
+	"io"
+	"os"
+	"syscall"
+	"testing"
+	"time"
+
+	_ "v.io/x/ref/runtime/factories/generic"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
+)
+
+func simpleEchoProgram(stdin io.Reader, stdout io.Writer) {
+	fmt.Fprintf(stdout, "ready\n")
+	scanner := bufio.NewScanner(stdin)
+	if scanner.Scan() {
+		fmt.Fprintf(stdout, "%s\n", scanner.Text())
+	}
+	modules.WaitForEOF(stdin)
+}
+
+var withRuntime = modules.Register(func(env *modules.Env, args ...string) error {
+	_, shutdown := test.V23Init()
+	defer shutdown()
+
+	simpleEchoProgram(env.Stdin, env.Stdout)
+	return nil
+}, "withRuntime")
+
+var withoutRuntime = modules.Register(func(env *modules.Env, args ...string) error {
+	simpleEchoProgram(env.Stdin, env.Stdout)
+	return nil
+}, "withoutRuntime")
+
+func TestWithRuntime(t *testing.T) {
+	sh, err := modules.NewShell(nil, nil, testing.Verbose(), t)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	defer sh.Cleanup(os.Stderr, os.Stderr)
+	h, err := sh.Start(nil, withRuntime)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	defer h.Shutdown(os.Stderr, os.Stderr)
+	h.Expect("ready")
+	syscall.Kill(h.Pid(), syscall.SIGHUP)
+	h.Stdin().Write([]byte("foo\n"))
+	h.Expect("foo")
+	h.CloseStdin()
+	h.ExpectEOF()
+}
+
+func TestWithoutRuntime(t *testing.T) {
+	sh, err := modules.NewShell(nil, nil, testing.Verbose(), t)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	defer sh.Cleanup(os.Stderr, os.Stderr)
+	opts := sh.DefaultStartOpts()
+	opts.ShutdownTimeout = 5 * time.Second
+	h, err := sh.StartWithOpts(opts, nil, withoutRuntime)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	defer h.Shutdown(os.Stderr, os.Stderr)
+	h.Expect("ready")
+	syscall.Kill(h.Pid(), syscall.SIGHUP)
+	h.ExpectEOF()
+	err = h.Shutdown(os.Stderr, os.Stderr)
+	want := "exit status 2"
+	if err == nil || err.Error() != want {
+		t.Errorf("got %s, want %s", err, want)
+
+	}
+}
diff --git a/runtime/internal/rt/v23_test.go b/runtime/internal/rt/v23_test.go
new file mode 100644
index 0000000..2145611
--- /dev/null
+++ b/runtime/internal/rt/v23_test.go
@@ -0,0 +1,22 @@
+// 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 rt_test
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	modules.DispatchAndExitIfChild()
+	os.Exit(m.Run())
+}
diff --git a/runtime/internal/testing/concurrency/choice.go b/runtime/internal/testing/concurrency/choice.go
new file mode 100644
index 0000000..aacefbe
--- /dev/null
+++ b/runtime/internal/testing/concurrency/choice.go
@@ -0,0 +1,23 @@
+// 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 concurrency
+
+// choice enumerates the program transitions to choose from and
+// identifies which transition is to be taken next.
+type choice struct {
+	// next records the thread identifier for the thread that was
+	// selected to be scheduled next.
+	next TID
+	// transitions records the transitions for all the threads that
+	// could have been scheduled next.
+	transitions map[TID]*transition
+}
+
+// newChoice is the choice factory.
+func newChoice() *choice {
+	return &choice{
+		transitions: make(map[TID]*transition),
+	}
+}
diff --git a/runtime/internal/testing/concurrency/clock.go b/runtime/internal/testing/concurrency/clock.go
new file mode 100644
index 0000000..88fe635
--- /dev/null
+++ b/runtime/internal/testing/concurrency/clock.go
@@ -0,0 +1,54 @@
+// 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 concurrency
+
+import (
+	"reflect"
+)
+
+// clock is a type for the vector clock, which is used to keep track
+// of logical time in a concurrent program.
+type clock map[TID]int
+
+// newClock is the clock factory.
+func newClock() clock {
+	return make(clock)
+}
+
+// clone produces a copy of the clock.
+func (c clock) clone() clock {
+	clone := newClock()
+	for k, v := range c {
+		clone[k] = v
+	}
+	return clone
+}
+
+// equals checks if this clock identifies a logical time identical to
+// the logical time identified by the given clock.
+func (c clock) equals(other clock) bool {
+	return reflect.DeepEqual(c, other)
+}
+
+// happensBefore checks if this clock identifies a logical time that
+// happened before (in the sense of Lamport's happens-before relation)
+// the logical time identified by the given clock.
+func (c clock) happensBefore(other clock) bool {
+	for k, v := range c {
+		if value, found := other[k]; !found || v > value {
+			return false
+		}
+	}
+	return true
+}
+
+// merge merges the value of the given clock with this clock.
+func (c clock) merge(other clock) {
+	for key, value := range other {
+		if v, found := c[key]; !found || v < value {
+			c[key] = value
+		}
+	}
+}
diff --git a/runtime/internal/testing/concurrency/clock_test.go b/runtime/internal/testing/concurrency/clock_test.go
new file mode 100644
index 0000000..12b22c5
--- /dev/null
+++ b/runtime/internal/testing/concurrency/clock_test.go
@@ -0,0 +1,82 @@
+// 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 concurrency
+
+import (
+	"testing"
+
+	"v.io/x/ref/test/testutil"
+)
+
+//go:generate v23 test generate
+
+// TestClone checks the clone() method of a clock.
+func TestClone(t *testing.T) {
+	testutil.InitRandGenerator(t.Logf)
+	c1 := newClock()
+	c1[0] = testutil.RandomIntn(100)
+	c2 := c1.clone()
+	c1[0]++
+	if c2[0] != c1[0]-1 {
+		t.Errorf("Unexpected clock value: expected %v, got %v", c1[0]-1, c2[0])
+	}
+}
+
+// TestEquality checks the equals() method of a clock.
+func TestEquality(t *testing.T) {
+	c1, c2 := newClock(), newClock()
+	for i := TID(0); i < TID(10); i++ {
+		c1[i] = testutil.RandomIntn(100)
+		c2[i] = c1[i]
+	}
+	if !c1.equals(c2) {
+		t.Errorf("Unexpected inequality between %v and %v", c1, c2)
+	}
+}
+
+// TestHappensBefore checks the happensBefore() method of a clock.
+func TestHappensBefore(t *testing.T) {
+	c1, c2, c3 := newClock(), newClock(), newClock()
+	for i := TID(0); i < TID(10); i++ {
+		c1[i] = testutil.RandomIntn(100)
+		if i%2 == 0 {
+			c2[i] = c1[i] + 1
+			c3[i] = c1[i] + 1
+		} else {
+			c2[i] = c1[i]
+			c3[i] = c2[i] - 1
+		}
+	}
+	if !c1.happensBefore(c1) {
+		t.Errorf("Unexpected outcome of %v.happensBefore(%v): expected %v, got %v", c1, c1, true, false)
+	}
+	if !c1.happensBefore(c2) {
+		t.Errorf("Unexpected outcome of %v.happensBefore(%v): expected %v, got %v", c1, c2, true, false)
+	}
+	if c2.happensBefore(c1) {
+		t.Errorf("Unexpected outcome of %v.happensBefore(%v): expected %v, got %v", c2, c1, false, true)
+	}
+	if c1.happensBefore(c3) {
+		t.Errorf("Unexpected outcome of %v.happensBefore(%v): expected %v, got %v", c1, c3, false, true)
+	}
+	if c3.happensBefore(c1) {
+		t.Errorf("Unexpected outcome of %v.happensBefore(%v): expected %v, got %v", c3, c1, false, true)
+	}
+}
+
+// TestMerge checks the merge() method of a clock.
+func TestMerge(t *testing.T) {
+	c1, c2 := newClock(), newClock()
+	for i := TID(0); i < TID(10); i++ {
+		c1[i] = testutil.RandomIntn(100)
+		c2[i] = testutil.RandomIntn(100)
+	}
+	c1.merge(c2)
+	for i := TID(0); i < TID(10); i++ {
+		if c1[i] < c2[i] {
+			t.Errorf("Unexpected order between %v and %v: expected '>=', got '<'", c1[i], c2[i])
+		}
+	}
+}
diff --git a/runtime/internal/testing/concurrency/context.go b/runtime/internal/testing/concurrency/context.go
new file mode 100644
index 0000000..c52ea0b
--- /dev/null
+++ b/runtime/internal/testing/concurrency/context.go
@@ -0,0 +1,26 @@
+// 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 concurrency
+
+import (
+	"sync"
+)
+
+// context stores the abstract state of resources used in an execution
+// of a concurrent program.
+type context struct {
+	// mutexes stores the abstract state of mutexes.
+	mutexes map[*sync.Mutex]*fakeMutex
+	// rwMutexes stores the abstract state of read-write mutexes.
+	rwMutexes map[*sync.RWMutex]*fakeRWMutex
+}
+
+// newContext if the context factory.
+func newContext() *context {
+	return &context{
+		mutexes:   make(map[*sync.Mutex]*fakeMutex),
+		rwMutexes: make(map[*sync.RWMutex]*fakeRWMutex),
+	}
+}
diff --git a/runtime/internal/testing/concurrency/doc.go b/runtime/internal/testing/concurrency/doc.go
new file mode 100644
index 0000000..59c2aac
--- /dev/null
+++ b/runtime/internal/testing/concurrency/doc.go
@@ -0,0 +1,38 @@
+// 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 concurrency implements a framework for systematic testing
+// of concurrent vanadium Go programs. The framework implements the
+// ideas described in "Systematic and Scalable Testing of Concurrent
+// Programs":
+//
+// http://repository.cmu.edu/cgi/viewcontent.cgi?article=1291&context=dissertations
+//
+// Abstractly, the systematic testing framework divides execution of
+// concurrent threads into coarse-grained transitions, by interposing
+// on events of interest (e.g. thread creation, mutex acquisition, or
+// channel communication).
+//
+// The interposed events suspended and a centralized user-level
+// scheduler is used to serialize the concurrent execution by
+// advancing allowing only one concurrent transition to execute at any
+// given time. In addition to controling the scheduling, this
+// centralized scheduler keeps track of the alternative scheduling
+// choices. This information is then used to explore a different
+// sequence of transitions next time the test body is executed.
+//
+// The framework is initialized through the Init(setup, body, cleanup)
+// function which specifies the test setup, body, and cleanup
+// respectively. To start a systematic exploration, one invokes one of
+// the following functions: Explore(), ExploreN(n), or
+// ExploreFor(d). These functions repeatedly execute the test
+// described through Init(), systematically enumerating the different
+// ways in which different executions sequence concurrent transitions
+// of the test. Finally, each systematic exploration should end by
+// invoking the Finish() function.
+//
+// See mutex_test.go for an example on how to use this framework to
+// test concurrent access to mutexes.
+
+package concurrency
diff --git a/runtime/internal/testing/concurrency/execution.go b/runtime/internal/testing/concurrency/execution.go
new file mode 100644
index 0000000..a8b494d
--- /dev/null
+++ b/runtime/internal/testing/concurrency/execution.go
@@ -0,0 +1,149 @@
+// 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 concurrency
+
+import (
+	"fmt"
+	"sort"
+)
+
+// execution represents an execution of the test.
+type execution struct {
+	// strategy describes the initial sequence of scheduling decisions
+	// to make.
+	strategy []TID
+	// nsteps records the number of scheduling decision made.
+	nsteps int
+	// nthreads records the number of threads in the system.
+	nthreads int
+	// nrequests records the number of currently pending requests.
+	nrequests int
+	// requests is a channel on which scheduling requests are received.
+	requests chan request
+	// done is a channel that the request handlers can use to wake up
+	// the main scheduling loop.
+	done chan struct{}
+	// nextTID is a function that can be used to generate unique thread
+	// identifiers.
+	nextTID func() TID
+	// activeTID records the identifier of the currently active thread.
+	activeTID TID
+	// ctx stores the abstract state of resources used by the
+	// execution.
+	ctx *context
+	// threads records the abstract state of threads active in the
+	// execution.
+	threads map[TID]*thread
+}
+
+// newExecution is the execution factory.
+func newExecution(strategy []TID) *execution {
+	execution := &execution{
+		strategy: strategy,
+		nthreads: 1,
+		requests: make(chan request),
+		nextTID:  TIDGenerator(),
+		ctx:      newContext(),
+		threads:  make(map[TID]*thread),
+	}
+	clock := newClock()
+	clock[0] = 0
+	execution.threads[0] = newThread(0, clock)
+	return execution
+}
+
+// Run executes the body of the test, exploring a sequence of
+// scheduling decisions, and returns a vector of the scheduling
+// decisions it made as well as the alternative scheduling decisions
+// it could have made instead.
+func (e *execution) Run(testBody func()) []*choice {
+	go testBody()
+	choices := make([]*choice, 0)
+	// Keep scheduling requests until there are threads left in the
+	// system.
+	for e.nthreads != 0 {
+		// Keep receiving scheduling requests until all threads are
+		// blocked on a decision.
+		for e.nrequests != e.nthreads {
+			request, ok := <-e.requests
+			if !ok {
+				panic("Command channel closed unexpectedly.")
+			}
+			e.nrequests++
+			request.process(e)
+		}
+		choice := e.generateChoice()
+		choices = append(choices, choice)
+		e.activeTID = choice.next
+		e.schedule(choice.next)
+	}
+	return choices
+}
+
+// findThread uses the given thread identifier to find a thread among
+// the known threads.
+func (e *execution) findThread(tid TID) *thread {
+	thread, ok := e.threads[tid]
+	if !ok {
+		panic(fmt.Sprintf("Could not find thread %v.", tid))
+	}
+	return thread
+}
+
+// generateChoice describes the scheduling choices available at the
+// current abstract program state.
+func (e *execution) generateChoice() *choice {
+	c := newChoice()
+	enabled := make([]TID, 0)
+	for tid, thread := range e.threads {
+		t := &transition{
+			tid:      tid,
+			clock:    thread.clock.clone(),
+			enabled:  thread.enabled(e.ctx),
+			kind:     thread.kind(),
+			readSet:  thread.readSet(),
+			writeSet: thread.writeSet(),
+		}
+		c.transitions[tid] = t
+		if t.enabled {
+			enabled = append(enabled, tid)
+		}
+	}
+	if len(c.transitions) == 0 {
+		panic("Encountered a deadlock.")
+	}
+	if e.nsteps < len(e.strategy) {
+		// Follow the scheduling strategy.
+		c.next = e.strategy[e.nsteps]
+	} else {
+		// Schedule an enabled thread using a deterministic round-robin
+		// scheduler.
+		sort.Sort(IncreasingTID(enabled))
+		index := 0
+		for ; index < len(enabled) && enabled[index] <= e.activeTID; index++ {
+		}
+		if index == len(enabled) {
+			index = 0
+		}
+		c.next = enabled[index]
+	}
+	return c
+}
+
+// schedule advances the execution of the given thread.
+func (e *execution) schedule(tid TID) {
+	e.nrequests--
+	e.nsteps++
+	thread, ok := e.threads[tid]
+	if !ok {
+		panic(fmt.Sprintf("Could not find thread %v.\n", tid))
+	}
+	if !thread.enabled(e.ctx) {
+		panic(fmt.Sprintf("Thread %v is about to be scheduled and is not enabled.", tid))
+	}
+	e.done = make(chan struct{})
+	close(thread.ready)
+	<-e.done
+}
diff --git a/runtime/internal/testing/concurrency/fake.go b/runtime/internal/testing/concurrency/fake.go
new file mode 100644
index 0000000..f781ae2
--- /dev/null
+++ b/runtime/internal/testing/concurrency/fake.go
@@ -0,0 +1,131 @@
+// 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 concurrency
+
+// mutexState is a type to represent different states of a mutex.
+type mutexState uint64
+
+// enumeration of different mutex states.
+const (
+	mutexFree mutexState = iota
+	mutexLocked
+)
+
+// fakeMutex is an abstract representation of a mutex.
+type fakeMutex struct {
+	// clock records the logical time of the last access to the mutex.
+	clock clock
+	// state records the state of the mutex.
+	state mutexState
+}
+
+// newFakeMutex is the fakeMutex factory.
+func newFakeMutex(clock clock) *fakeMutex {
+	return &fakeMutex{clock: clock.clone()}
+}
+
+// free checks if the mutex is free.
+func (m *fakeMutex) free() bool {
+	return m.state == mutexFree
+}
+
+// locked checks if the mutex is locked.
+func (m *fakeMutex) locked() bool {
+	return m.state == mutexLocked
+}
+
+// lock models the action of locking the mutex.
+func (m *fakeMutex) lock() {
+	if m.state != mutexFree {
+		panic("Locking a mutex that is already locked.")
+	}
+	m.state = mutexLocked
+}
+
+// unlock models the action of unlocking the mutex.
+func (m *fakeMutex) unlock() {
+	if m.state != mutexLocked {
+		panic("Unlocking a mutex that is not locked.")
+	}
+	m.state = mutexFree
+}
+
+// rwMutexState is a type to represent different states of a mutex.
+type rwMutexState uint64
+
+// enumeration of different rwMutex states.
+const (
+	rwMutexFree rwMutexState = iota
+	rwMutexShared
+	rwMutexExclusive
+)
+
+// fakeRWMutex is an abstract representation of a read-write mutex.
+type fakeRWMutex struct {
+	// clock records the logical time of the last access to the
+	// read-write mutex.
+	clock clock
+	// state records the state of the read-write mutex.
+	state rwMutexState
+	// nreaders records the number of readers.
+	nreaders int
+}
+
+// newFakeRWMutex is the fakeRWMutex factory.
+func newFakeRWMutex(clock clock) *fakeRWMutex {
+	return &fakeRWMutex{clock: clock.clone()}
+}
+
+// exclusive checks if the read-write mutex is exclusive.
+func (rw *fakeRWMutex) exclusive() bool {
+	return rw.state == rwMutexExclusive
+}
+
+// free checks if the read-write mutex is free.
+func (rw *fakeRWMutex) free() bool {
+	return rw.state == rwMutexFree
+}
+
+// shared checks if the read-write mutex is shared.
+func (rw *fakeRWMutex) shared() bool {
+	return rw.state == rwMutexShared
+}
+
+// lock models the action of read-locking or write-locking the
+// read-write mutex.
+func (rw *fakeRWMutex) lock(read bool) {
+	if read {
+		if rw.state == rwMutexExclusive {
+			panic("Read-locking a read-write mutex that is write-locked.")
+		}
+		if rw.state == rwMutexFree {
+			rw.state = rwMutexShared
+		}
+		rw.nreaders++
+	} else {
+		if rw.state != rwMutexFree {
+			panic("Write-locking a read-write mutex that is not free.")
+		}
+		rw.state = rwMutexExclusive
+	}
+}
+
+// unlock models the action of unlocking the read-write mutex.
+func (rw *fakeRWMutex) unlock(read bool) {
+	if read {
+		if rw.state != rwMutexShared {
+			panic("Read-unlocking a read-write mutex that is not read-locked.")
+		}
+		rw.nreaders--
+		if rw.nreaders == 0 {
+			rw.state = rwMutexFree
+		}
+	} else {
+		if rw.state != rwMutexExclusive {
+			panic("Write-unlocking a read-write mutex that is not write-locked.")
+		}
+		rw.state = rwMutexFree
+	}
+}
diff --git a/runtime/internal/testing/concurrency/mutex_test.go b/runtime/internal/testing/concurrency/mutex_test.go
new file mode 100644
index 0000000..13aa722
--- /dev/null
+++ b/runtime/internal/testing/concurrency/mutex_test.go
@@ -0,0 +1,165 @@
+// 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.
+
+// concurrency_test is a simple test of the framework for systematic
+// testing of concurrency.
+package concurrency_test
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"testing"
+	"time"
+
+	"v.io/x/ref/internal/logger"
+	"v.io/x/ref/runtime/internal/testing/concurrency"
+	"v.io/x/ref/runtime/internal/testing/concurrency/sync"
+)
+
+var m sync.Mutex
+
+// createMutexSets returns sets of thread identifiers that match the
+// logic of mutexThreadClosure.
+func createMutexSet(n int) map[int]bool {
+	locks := make(map[int]bool)
+	for i := 1; i <= n; i++ {
+		locks[i] = true
+	}
+	return locks
+}
+
+// generateMutexOutputs generates all legal outputs of sequencing
+// calls to rw.Lock(), rw.Unlock(). The input identifies the threads
+// that wish to invoke these functions.
+func generateMutexOutputs(locks map[int]bool) []string {
+	if length(locks) == 0 {
+		return []string{""}
+	}
+	result := make([]string, 0)
+	for lock, ok := range locks {
+		if ok {
+			locks[lock] = false
+			for _, s := range generateMutexOutputs(locks) {
+				result = append(result, fmt.Sprintf("%d:Lock()%d:Unlock()%s", lock, lock, s))
+			}
+			locks[lock] = true
+		}
+	}
+	return result
+}
+
+// mutexThreadClosure folds the input arguments inside of the function body
+// as the testing framework only supports functions with no arguments.
+func mutexThreadClosure(t *testing.T, n, max int, out *os.File) func() {
+	return func() {
+		defer concurrency.Exit()
+		if n < max {
+			child := mutexThreadClosure(t, n+1, max, out)
+			concurrency.Start(child)
+		}
+		m.Lock()
+		fmt.Fprintf(out, "%d:Lock()", n)
+		m.Unlock()
+		fmt.Fprintf(out, "%d:Unlock()", n)
+	}
+}
+
+// TestMutex runs mutexThreadCLosure() without systematically testing
+// concurrency.
+func TestMutex(t *testing.T) {
+	for n := 2; n < 6; n++ {
+		thread := mutexThreadClosure(t, 1, n, nil)
+		thread()
+	}
+}
+
+// TestMutexExplore runs mutexThreadClosure() using the framework for systematic
+// testing of concurrency, checking that the exploration explores the
+// correct number of interleavings.
+func TestMutexExplore(t *testing.T) {
+	for n := 2; n < 6; n++ {
+		out, err := ioutil.TempFile("", "")
+		if err != nil {
+			t.Fatalf("TempFile() failed: %v", err)
+		}
+		defer os.Remove(out.Name())
+		defer out.Close()
+		body := mutexThreadClosure(t, 1, n, out)
+		tester := concurrency.Init(setup, body, cleanupClosure(out))
+		defer concurrency.Finish()
+		niterations, err := tester.Explore()
+		if err != nil {
+			t.Fatalf("Unexpected error encountered: %v", err)
+		}
+		outputs := processOutput(t, out)
+		expectedOutputs := generateMutexOutputs(createMutexSet(n))
+		checkExpectedOutputs(t, outputs, expectedOutputs)
+		checkUnexpectedOutputs(t, outputs, expectedOutputs)
+		logger.Global().VI(1).Infof("Explored %v iterations.", niterations)
+	}
+}
+
+// TestMutexExploreN runs mutexThreadClosure() using the framework for
+// systematic testing of concurrency, checking that the exploration
+// explores at most the given number of interleavings.
+func TestMutexExploreN(t *testing.T) {
+	for n := 2; n < 6; n++ {
+		out, err := ioutil.TempFile("", "")
+		if err != nil {
+			t.Fatalf("TempFile() failed: %v", err)
+		}
+		defer os.Remove(out.Name())
+		defer out.Close()
+		body := mutexThreadClosure(t, 1, n, out)
+		tester := concurrency.Init(setup, body, cleanupClosure(out))
+		defer concurrency.Finish()
+		stopAfter := 100
+		niterations, err := tester.ExploreN(stopAfter)
+		if err != nil {
+			t.Fatalf("Unexpected error encountered: %v", err)
+		}
+		outputs := processOutput(t, out)
+		expectedOutputs := generateMutexOutputs(createMutexSet(n))
+		checkUnexpectedOutputs(t, outputs, expectedOutputs)
+		if niterations < stopAfter {
+			checkExpectedOutputs(t, outputs, expectedOutputs)
+		}
+		if niterations > stopAfter {
+			t.Fatalf("Unexpected number of iterations: expected at most %v, got %v", stopAfter, niterations)
+		}
+		logger.Global().VI(1).Infof("Explored %v iterations.", niterations)
+	}
+}
+
+// TestMutexExploreFor runs mutexThreadClosure() using the framework
+// for systematic testing of concurrency, checking that the
+// exploration respects the given "soft" deadline.
+func TestMutexExploreFor(t *testing.T) {
+	for n := 2; n < 6; n++ {
+		out, err := ioutil.TempFile("", "")
+		if err != nil {
+			t.Fatalf("TempFile() failed: %v", err)
+		}
+		defer os.Remove(out.Name())
+		defer out.Close()
+		body := mutexThreadClosure(t, 1, n, out)
+		tester := concurrency.Init(setup, body, cleanupClosure(out))
+		defer concurrency.Finish()
+		start := time.Now()
+		deadline := 10 * time.Millisecond
+		niterations, err := tester.ExploreFor(deadline)
+		end := time.Now()
+		if err != nil {
+			t.Fatalf("Unexpected error encountered: %v", err)
+		}
+		outputs := processOutput(t, out)
+		expectedOutputs := generateMutexOutputs(createMutexSet(n))
+		checkUnexpectedOutputs(t, outputs, expectedOutputs)
+		if start.Add(deadline).After(end) {
+			checkExpectedOutputs(t, outputs, expectedOutputs)
+		}
+		logger.Global().VI(1).Infof("Explored %v iterations.", niterations)
+	}
+}
diff --git a/runtime/internal/testing/concurrency/request.go b/runtime/internal/testing/concurrency/request.go
new file mode 100644
index 0000000..26977a4
--- /dev/null
+++ b/runtime/internal/testing/concurrency/request.go
@@ -0,0 +1,394 @@
+// 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 concurrency
+
+import (
+	"sync"
+)
+
+// request is an interface to describe a scheduling request.
+type request interface {
+	// enabled determines whether the given program transition can be
+	// executed without blocking in the given context.
+	enabled(ctx *context) bool
+	// execute models the effect of advancing the execution of the
+	// calling thread.
+	execute(ready chan struct{}, e *execution)
+	// kind returns the kind of the program transition of the calling
+	// thread.
+	kind() transitionKind
+	// process handles initial processing of an incoming
+	// scheduling request, making sure the calling thread is suspended
+	// until the user-level scheduler decides to advance its execution.
+	process(e *execution)
+	// readSet records the identifiers of the abstract resources read by
+	// the program transition of the calling thread.
+	readSet() resourceSet
+	// writeSet records the identifiers of the abstract resources
+	// written by the program transition of the calling thread.
+	writeSet() resourceSet
+}
+
+type defaultRequest struct {
+	request
+	done chan struct{}
+}
+
+func (r defaultRequest) enabled(ctx *context) bool {
+	return true
+}
+
+func (r defaultRequest) execute(ready chan struct{}, e *execution) {
+	<-ready
+	close(r.done)
+	close(e.done)
+}
+
+func (r defaultRequest) process(e *execution) {
+	thread := e.findThread(e.activeTID)
+	thread.clock[e.activeTID]++
+	ready := make(chan struct{})
+	thread.ready = ready
+	thread.request = r
+	go r.execute(ready, e)
+}
+
+func (r defaultRequest) kind() transitionKind {
+	return tNil
+}
+
+func (r defaultRequest) readSet() resourceSet {
+	return newResourceSet()
+}
+
+func (r defaultRequest) writeSet() resourceSet {
+	return newResourceSet()
+}
+
+// goRequest is to be called before creating a new goroutine through "go
+// fn(tid)" to obtain a thread identifier to supply to the goroutine
+// that is about to be created. This request is a part of the
+// implementation of the Start() function provided by this package.
+type goRequest struct {
+	defaultRequest
+	reply chan TID
+}
+
+func (r goRequest) process(e *execution) {
+	e.nthreads++
+	tid := e.nextTID()
+	thread := e.findThread(e.activeTID)
+	newThread := newThread(tid, thread.clock)
+	newThread.clock[tid] = 0
+	e.threads[tid] = newThread
+	r.reply <- tid
+	e.nrequests--
+	close(r.done)
+}
+
+// goParentRequest is to be called right after a new goroutine is created
+// through "go fn(tid)" to prevent the race between the parent and the
+// child thread. This request is a part of the implementation of the
+// Start() function provided by this package.
+type goParentRequest struct {
+	defaultRequest
+}
+
+func (r goParentRequest) kind() transitionKind {
+	return tGoParent
+}
+
+func (r goParentRequest) process(e *execution) {
+	thread := e.findThread(e.activeTID)
+	thread.clock[e.activeTID]++
+	ready := make(chan struct{})
+	thread.ready = ready
+	thread.request = r
+	go r.execute(ready, e)
+}
+
+// goChildRequest is to be called as the first thing inside of a new
+// goroutine to prevent the race between the parent and the child
+// thread. This request is a part of the implementation of the Start()
+// function provided by this package.
+type goChildRequest struct {
+	defaultRequest
+	tid TID
+}
+
+func (r goChildRequest) kind() transitionKind {
+	return tGoChild
+}
+
+func (r goChildRequest) process(e *execution) {
+	thread := e.findThread(r.tid)
+	thread.clock[r.tid]++
+	ready := make(chan struct{})
+	thread.ready = ready
+	thread.request = r
+	go r.execute(ready, e)
+}
+
+// goExitRequest is to be called as the last thing inside of the body
+// of a test and any goroutine that the test spawns to inform the
+// testing framework about the termination of a thread. This request
+// implements the Exit() function provided by this package.
+type goExitRequest struct {
+	defaultRequest
+}
+
+func (r goExitRequest) execute(ready chan struct{}, e *execution) {
+	<-ready
+	e.nthreads--
+	delete(e.threads, e.activeTID)
+	close(r.done)
+	close(e.done)
+}
+
+func (r goExitRequest) kind() transitionKind {
+	return tGoExit
+}
+
+func (r goExitRequest) process(e *execution) {
+	thread := e.findThread(e.activeTID)
+	thread.clock[e.activeTID]++
+	ready := make(chan struct{})
+	thread.ready = ready
+	thread.request = r
+	go r.execute(ready, e)
+}
+
+// mutexLockRequest is to be called to schedule a mutex lock. This request
+// implements the MutexLock() function provided by this package.
+type mutexLockRequest struct {
+	defaultRequest
+	mutex *sync.Mutex
+}
+
+func (r mutexLockRequest) enabled(ctx *context) bool {
+	m, ok := ctx.mutexes[r.mutex]
+	return !ok || m.free()
+}
+
+func (r mutexLockRequest) execute(ready chan struct{}, e *execution) {
+	<-ready
+	thread := e.findThread(e.activeTID)
+	m, ok := e.ctx.mutexes[r.mutex]
+	if !ok {
+		m = newFakeMutex(thread.clock)
+		e.ctx.mutexes[r.mutex] = m
+	}
+	thread.clock.merge(m.clock)
+	m.clock.merge(thread.clock)
+	m.lock()
+	close(r.done)
+	close(e.done)
+}
+
+func (r mutexLockRequest) kind() transitionKind {
+	return tMutexLock
+}
+
+func (r mutexLockRequest) process(e *execution) {
+	thread := e.findThread(e.activeTID)
+	thread.clock[e.activeTID]++
+	ready := make(chan struct{})
+	thread.ready = ready
+	thread.request = r
+	go r.execute(ready, e)
+}
+
+func (r mutexLockRequest) readSet() resourceSet {
+	set := newResourceSet()
+	set[r.mutex] = struct{}{}
+	return set
+}
+
+func (r mutexLockRequest) writeSet() resourceSet {
+	set := newResourceSet()
+	set[r.mutex] = struct{}{}
+	return set
+}
+
+// mutexUnlockRequest is to be called to schedule a mutex unlock. This
+// request implements the MutexUnlock() function provided by this
+// package.
+type mutexUnlockRequest struct {
+	defaultRequest
+	mutex *sync.Mutex
+}
+
+func (r mutexUnlockRequest) enabled(ctx *context) bool {
+	m, ok := ctx.mutexes[r.mutex]
+	if !ok {
+		panic("Mutex does not exist.")
+	}
+	return m.locked()
+}
+
+func (r mutexUnlockRequest) execute(ready chan struct{}, e *execution) {
+	<-ready
+	m, ok := e.ctx.mutexes[r.mutex]
+	if !ok {
+		panic("Mutex not found.")
+	}
+	thread := e.findThread(e.activeTID)
+	thread.clock.merge(m.clock)
+	m.clock.merge(thread.clock)
+	m.unlock()
+	close(r.done)
+	close(e.done)
+}
+
+func (r mutexUnlockRequest) kind() transitionKind {
+	return tMutexUnlock
+}
+
+func (r mutexUnlockRequest) process(e *execution) {
+	thread := e.findThread(e.activeTID)
+	thread.clock[e.activeTID]++
+	ready := make(chan struct{})
+	thread.ready = ready
+	thread.request = r
+	go r.execute(ready, e)
+}
+
+func (r mutexUnlockRequest) readSet() resourceSet {
+	set := newResourceSet()
+	set[r.mutex] = struct{}{}
+	return set
+}
+
+func (r mutexUnlockRequest) writeSet() resourceSet {
+	set := newResourceSet()
+	set[r.mutex] = struct{}{}
+	return set
+}
+
+// rwMutexLockRequest is to be called to schedule a read-write mutex
+// lock. This request implements the RWMutexLock() function provided
+// by this package.
+type rwMutexLockRequest struct {
+	defaultRequest
+	read    bool
+	rwMutex *sync.RWMutex
+}
+
+func (r rwMutexLockRequest) enabled(ctx *context) bool {
+	rw, ok := ctx.rwMutexes[r.rwMutex]
+	if r.read {
+		return !ok || rw.free() || rw.shared()
+	} else {
+		return !ok || rw.free()
+	}
+}
+
+func (r rwMutexLockRequest) execute(ready chan struct{}, e *execution) {
+	<-ready
+	thread := e.findThread(e.activeTID)
+	rw, ok := e.ctx.rwMutexes[r.rwMutex]
+	if !ok {
+		rw = newFakeRWMutex(thread.clock)
+		e.ctx.rwMutexes[r.rwMutex] = rw
+	}
+	thread.clock.merge(rw.clock)
+	rw.clock.merge(thread.clock)
+	rw.lock(r.read)
+	close(r.done)
+	close(e.done)
+}
+
+func (r rwMutexLockRequest) kind() transitionKind {
+	if r.read {
+		return tRWMutexRLock
+	} else {
+		return tRWMutexLock
+	}
+}
+
+func (r rwMutexLockRequest) process(e *execution) {
+	thread := e.findThread(e.activeTID)
+	thread.clock[e.activeTID]++
+	ready := make(chan struct{})
+	thread.ready = ready
+	thread.request = r
+	go r.execute(ready, e)
+}
+
+func (r rwMutexLockRequest) readSet() resourceSet {
+	set := newResourceSet()
+	set[r.rwMutex] = struct{}{}
+	return set
+}
+
+func (r rwMutexLockRequest) writeSet() resourceSet {
+	set := newResourceSet()
+	set[r.rwMutex] = struct{}{}
+	return set
+}
+
+// rwMutexUnlockRequest is to be called to schedule a read-write mutex
+// unlock. This request implements the RWMutexUnlock() function
+// provided by this package.
+type rwMutexUnlockRequest struct {
+	defaultRequest
+	read    bool
+	rwMutex *sync.RWMutex
+}
+
+func (r rwMutexUnlockRequest) enabled(ctx *context) bool {
+	rw, ok := ctx.rwMutexes[r.rwMutex]
+	if !ok {
+		panic("Read-write mutex does not exist.")
+	}
+	if r.read {
+		return rw.shared()
+	} else {
+		return rw.exclusive()
+	}
+}
+
+func (r rwMutexUnlockRequest) execute(ready chan struct{}, e *execution) {
+	<-ready
+	rw, ok := e.ctx.rwMutexes[r.rwMutex]
+	if !ok {
+		panic("Read-write mutex not found.")
+	}
+	thread := e.findThread(e.activeTID)
+	thread.clock.merge(rw.clock)
+	rw.clock.merge(thread.clock)
+	rw.unlock(r.read)
+	close(r.done)
+	close(e.done)
+}
+
+func (r rwMutexUnlockRequest) kind() transitionKind {
+	if r.read {
+		return tRWMutexRUnlock
+	} else {
+		return tRWMutexUnlock
+	}
+}
+
+func (r rwMutexUnlockRequest) process(e *execution) {
+	thread := e.findThread(e.activeTID)
+	thread.clock[e.activeTID]++
+	ready := make(chan struct{})
+	thread.ready = ready
+	thread.request = r
+	go r.execute(ready, e)
+}
+
+func (r rwMutexUnlockRequest) readSet() resourceSet {
+	set := newResourceSet()
+	set[r.rwMutex] = struct{}{}
+	return set
+}
+
+func (r rwMutexUnlockRequest) writeSet() resourceSet {
+	set := newResourceSet()
+	set[r.rwMutex] = struct{}{}
+	return set
+}
diff --git a/runtime/internal/testing/concurrency/resource.go b/runtime/internal/testing/concurrency/resource.go
new file mode 100644
index 0000000..1a0e3d6
--- /dev/null
+++ b/runtime/internal/testing/concurrency/resource.go
@@ -0,0 +1,16 @@
+// 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 concurrency
+
+// resourceKey represents an identifier of an abstract resource.
+type resourceKey interface{}
+
+// resourceSet represents a set of abstract resources.
+type resourceSet map[resourceKey]struct{}
+
+// newResourceSet if the resourceSet factory.
+func newResourceSet() resourceSet {
+	return make(resourceSet)
+}
diff --git a/runtime/internal/testing/concurrency/rwmutex_test.go b/runtime/internal/testing/concurrency/rwmutex_test.go
new file mode 100644
index 0000000..8d0c2a8
--- /dev/null
+++ b/runtime/internal/testing/concurrency/rwmutex_test.go
@@ -0,0 +1,250 @@
+// 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.
+
+// concurrency_test is a simple test of the framework for systematic
+// testing of concurrency.
+package concurrency_test
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"testing"
+	"time"
+
+	"v.io/x/lib/vlog"
+	"v.io/x/ref/runtime/internal/testing/concurrency"
+	"v.io/x/ref/runtime/internal/testing/concurrency/sync"
+)
+
+var rw sync.RWMutex
+
+// createRWMutexSets returns sets of thread identifiers that match the
+// logic of rwMutexThreadClosure.
+func createRWMutexSets(n int) (map[int]bool, map[int]bool, map[int]bool, map[int]bool, map[int]bool) {
+	locks := make(map[int]bool)
+	rlocks := make(map[int]bool)
+	runlocks := make(map[int]bool)
+	llocks := make(map[int]bool)
+	lunlocks := make(map[int]bool)
+	for i := 1; i <= n; i++ {
+		switch i % 3 {
+		case 0:
+			locks[i] = true
+		case 1:
+			rlocks[i] = true
+			runlocks[i] = true
+		case 2:
+			llocks[i] = true
+			lunlocks[i] = true
+		}
+	}
+	return locks, rlocks, runlocks, llocks, lunlocks
+}
+
+// generateRWMutexOutputs generates all legal outputs of sequencing calls to
+// rw.Lock(), rw.Unlock(), rw.RLock(), rw.RUnlock(),
+// rw.RLocker().Lock(), and rw.RLocker().Unlock(). The inputs identify
+// the threads that wish to invoke these functions.
+func generateRWMutexOutputs(locks, rlocks, runlocks, llocks, lunlocks map[int]bool) []string {
+	if length(locks) == 0 && length(rlocks) == 0 && length(runlocks) == 0 && length(llocks) == 0 && length(lunlocks) == 0 {
+		// Base case.
+		return []string{""}
+	}
+	result := make([]string, 0)
+	if length(rlocks) == length(runlocks) && length(llocks) == length(lunlocks) {
+		// rw.Lock() + rw.Unlock() can happen next if the previous calls
+		// to rw.RLock(), rw.RUnlock, rw.RLocker().Lock(), and
+		// rw.RLocker.Unlock() are balanced.
+		for lock, ok := range locks {
+			if ok {
+				locks[lock] = false
+				for _, s := range generateRWMutexOutputs(locks, rlocks, runlocks, llocks, lunlocks) {
+					result = append(result, fmt.Sprintf("%d:Lock()%d:Unlock()%s", lock, lock, s))
+				}
+				locks[lock] = true
+			}
+		}
+	}
+	for rlock, ok := range rlocks {
+		if ok {
+			// rw.RLock() can happen next any time.
+			rlocks[rlock] = false
+			for _, s := range generateRWMutexOutputs(locks, rlocks, runlocks, llocks, lunlocks) {
+				result = append(result, fmt.Sprintf("%d:RLock()%s", rlock, s))
+			}
+			rlocks[rlock] = true
+		}
+	}
+	for runlock, ok := range runlocks {
+		if ok {
+			if ok := rlocks[runlock]; !ok {
+				// rw.RUnlock() can happen next as long as the same thread
+				// already invoked rw.RLock().
+				runlocks[runlock] = false
+				for _, s := range generateRWMutexOutputs(locks, rlocks, runlocks, llocks, lunlocks) {
+					result = append(result, fmt.Sprintf("%d:RUnlock()%s", runlock, s))
+				}
+				runlocks[runlock] = true
+			}
+		}
+	}
+	for llock, ok := range llocks {
+		if ok {
+			// rw.RLocker().Lock() can happen next any time.
+			llocks[llock] = false
+			for _, s := range generateRWMutexOutputs(locks, rlocks, runlocks, llocks, lunlocks) {
+				result = append(result, fmt.Sprintf("%d:RLocker().Lock()%s", llock, s))
+			}
+			llocks[llock] = true
+		}
+	}
+	for lunlock, ok := range lunlocks {
+		if ok {
+			if ok := llocks[lunlock]; !ok {
+				// rw.RLocker().Unlock() can happen next as long as the same thread
+				// already invoked rw.RLocker().Lock().
+				lunlocks[lunlock] = false
+				for _, s := range generateRWMutexOutputs(locks, rlocks, runlocks, llocks, lunlocks) {
+					result = append(result, fmt.Sprintf("%d:RLocker().Unlock()%s", lunlock, s))
+				}
+				lunlocks[lunlock] = true
+			}
+		}
+	}
+	return result
+}
+
+// rwMutexThreadClosure folds the input arguments inside of the
+// function body as the testing framework only supports functions with
+// no arguments.
+func rwMutexThreadClosure(t *testing.T, n, max int, out *os.File) func() {
+	return func() {
+		defer concurrency.Exit()
+		if n < max {
+			child := rwMutexThreadClosure(t, n+1, max, out)
+			concurrency.Start(child)
+		}
+		switch n % 3 {
+		case 0:
+			rw.Lock()
+			fmt.Fprintf(out, "%d:Lock()", n)
+		case 1:
+			rw.RLock()
+			fmt.Fprintf(out, "%d:RLock()", n)
+		case 2:
+			rw.RLocker().Lock()
+			fmt.Fprintf(out, "%d:RLocker().Lock()", n)
+		}
+		switch n % 3 {
+		case 0:
+			rw.Unlock()
+			fmt.Fprintf(out, "%d:Unlock()", n)
+		case 1:
+			rw.RUnlock()
+			fmt.Fprintf(out, "%d:RUnlock()", n)
+		case 2:
+			rw.RLocker().Unlock()
+			fmt.Fprintf(out, "%d:RLocker().Unlock()", n)
+		}
+	}
+}
+
+// TestRWMutex runs rwMutexThreadClosure() without systematically
+// testing concurrency.
+func TestRWMutex(t *testing.T) {
+	for n := 2; n < 5; n++ {
+		thread := rwMutexThreadClosure(t, 1, n, nil)
+		thread()
+	}
+}
+
+// TestRWMutexExplore runs rwMutexThreadClosure() using the framework
+// for systematic testing of concurrency, checking that the
+// exploration explores the correct number of interleavings.
+func TestRWMutexExplore(t *testing.T) {
+	for n := 2; n < 5; n++ {
+		out, err := ioutil.TempFile("", "")
+		if err != nil {
+			t.Fatalf("TempFile() failed: %v", err)
+		}
+		defer os.Remove(out.Name())
+		defer out.Close()
+		body := rwMutexThreadClosure(t, 1, n, out)
+		tester := concurrency.Init(setup, body, cleanupClosure(out))
+		defer concurrency.Finish()
+		niterations, err := tester.Explore()
+		if err != nil {
+			t.Fatalf("Unexpected error encountered: %v", err)
+		}
+		outputs := processOutput(t, out)
+		expectedOutputs := generateRWMutexOutputs(createRWMutexSets(n))
+		checkExpectedOutputs(t, outputs, expectedOutputs)
+		checkUnexpectedOutputs(t, outputs, expectedOutputs)
+		vlog.VI(1).Infof("Explored %v iterations.", niterations)
+	}
+}
+
+// TestRWMutexExploreN runs rwMutexThreadClosure() using the framework
+// for systematic testing of concurrency, checking that the
+// exploration explores at most the given number of interleavings.
+func TestRWMutexExploreN(t *testing.T) {
+	stopAfter := 100
+	for n := 2; n < 5; n++ {
+		out, err := ioutil.TempFile("", "")
+		if err != nil {
+			t.Fatalf("TempFile() failed: %v", err)
+		}
+		defer os.Remove(out.Name())
+		defer out.Close()
+		body := rwMutexThreadClosure(t, 1, n, out)
+		tester := concurrency.Init(setup, body, cleanupClosure(out))
+		defer concurrency.Finish()
+		niterations, err := tester.ExploreN(stopAfter)
+		if err != nil {
+			t.Fatalf("Unexpected error encountered: %v", err)
+		}
+		outputs := processOutput(t, out)
+		expectedOutputs := generateRWMutexOutputs(createRWMutexSets(n))
+		checkUnexpectedOutputs(t, outputs, expectedOutputs)
+		if niterations < stopAfter {
+			checkExpectedOutputs(t, outputs, expectedOutputs)
+		}
+		if niterations > stopAfter {
+			t.Fatalf("Unexpected number of iterations: expected at most %v, got %v", stopAfter, niterations)
+		}
+		vlog.VI(1).Infof("Explored %v iterations.", niterations)
+	}
+}
+
+// TestRWMutexExploreFor runs rwMutexThreadClosure() using the
+// framework for systematic testing of concurrency, checking that the
+// exploration respects the given "soft" deadline.
+func TestRWMutexExploreFor(t *testing.T) {
+	deadline := 10 * time.Millisecond
+	for n := 2; n < 5; n++ {
+		out, err := ioutil.TempFile("", "")
+		if err != nil {
+			t.Fatalf("TempFile() failed: %v", err)
+		}
+		defer os.Remove(out.Name())
+		defer out.Close()
+		body := rwMutexThreadClosure(t, 1, n, out)
+		tester := concurrency.Init(setup, body, cleanupClosure(out))
+		defer concurrency.Finish()
+		start := time.Now()
+		niterations, err := tester.ExploreFor(deadline)
+		if err != nil {
+			t.Fatalf("Unexpected error encountered: %v", err)
+		}
+		end := time.Now()
+		outputs := processOutput(t, out)
+		expectedOutputs := generateRWMutexOutputs(createRWMutexSets(n))
+		checkUnexpectedOutputs(t, outputs, expectedOutputs)
+		if start.Add(deadline).After(end) {
+			checkExpectedOutputs(t, outputs, expectedOutputs)
+		}
+		vlog.VI(1).Infof("Explored %v iterations.", niterations)
+	}
+}
diff --git a/runtime/internal/testing/concurrency/stack.go b/runtime/internal/testing/concurrency/stack.go
new file mode 100644
index 0000000..5d1d08c
--- /dev/null
+++ b/runtime/internal/testing/concurrency/stack.go
@@ -0,0 +1,64 @@
+// 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 concurrency
+
+import (
+	"errors"
+	"sort"
+)
+
+// stack is an implementation of the stack data structure.
+type stack struct {
+	contents []*state
+}
+
+// IncreasingDepth is used to sort states in the increasing order of
+// their depth.
+type IncreasingDepth []*state
+
+// SORT INTERFACE IMPLEMENTATION
+
+func (states IncreasingDepth) Len() int {
+	return len(states)
+}
+func (states IncreasingDepth) Less(i, j int) bool {
+	return states[i].depth < states[j].depth
+}
+func (states IncreasingDepth) Swap(i, j int) {
+	states[i], states[j] = states[j], states[i]
+}
+
+// Empty checks if the stack is empty.
+func (s *stack) Empty() bool {
+	return len(s.contents) == 0
+}
+
+// Length returns the length of the stack.
+func (s *stack) Length() int {
+	return len(s.contents)
+}
+
+// Pop removes and returns the top element of the stack. If the stack
+// is empty, an error is returned.
+func (s *stack) Pop() (*state, error) {
+	l := len(s.contents)
+	if l > 0 {
+		x := s.contents[l-1]
+		s.contents = s.contents[:l-1]
+		return x, nil
+	}
+	return nil, errors.New("Stack is empty.")
+}
+
+// Push adds a new element to the top of the stack.
+func (s *stack) Push(value *state) {
+	s.contents = append(s.contents, value)
+}
+
+// Sort sorts the elements of the stack in the decreasing order of
+// their depth.
+func (s *stack) Sort() {
+	sort.Sort(IncreasingDepth(s.contents))
+}
diff --git a/runtime/internal/testing/concurrency/state.go b/runtime/internal/testing/concurrency/state.go
new file mode 100644
index 0000000..e6eddc6
--- /dev/null
+++ b/runtime/internal/testing/concurrency/state.go
@@ -0,0 +1,246 @@
+// 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 concurrency
+
+import (
+	"errors"
+	"fmt"
+	"reflect"
+)
+
+// state records a state of the exploration of the space of all
+// possible program states a concurrent test can encounter. The states
+// of the exploration are organized using a tree data structure
+// referred to as the "execution tree". Nodes of the execution tree
+// represent different abstract program states and edges represent the
+// different scheduling decisions that can be made at those states. A
+// path from the root to a leaf thus uniquely represents an execution
+// as a serialization of the scheduling decisions along the execution.
+type state struct {
+	// depth identifies the depth of this node in the execution tree.
+	depth int
+	// parent records the pointer to the parent abstract state.
+	parent *state
+	// children maps threads to the abstract states that correspond to
+	// executing the next transitions of that thread.
+	children map[TID]*state
+	// explored records whether the subtree rooted at this state has
+	// been fully explored.
+	explored bool
+	// seeded records whether the state has been used to seed an
+	// exploration strategy.
+	seeded bool
+	// visited records whether the state has been visited
+	visited bool
+	// tid records the thread identifier of the thread whose transition
+	// lead to this state.
+	tid TID
+	// kind records the type of the transition that lead to this state.
+	kind transitionKind
+	// clock records the logical time of this state.
+	clock clock
+	// enabled records whether the transition that leads to this state
+	// is enabled.
+	enabled bool
+	// readSet records the identifiers of the abstract resources read by
+	// the transition that lead to this state.
+	readSet resourceSet
+	// writeSet records the identifiers of the abstract resources
+	// written by the transition that lead to this state.
+	writeSet resourceSet
+}
+
+// newState is the state factory.
+func newState(parent *state, tid TID) *state {
+	depth := 0
+	if parent != nil {
+		depth = parent.depth + 1
+	}
+	return &state{
+		depth:    depth,
+		children: make(map[TID]*state),
+		parent:   parent,
+		tid:      tid,
+		readSet:  newResourceSet(),
+		writeSet: newResourceSet(),
+		clock:    newClock(),
+	}
+}
+
+// addBranch adds a branch described through the given sequence of
+// choices to the execution tree rooted at this state.
+func (s *state) addBranch(branch []*choice, seeds *stack) error {
+	if len(branch) == 0 {
+		s.explored = true
+		return nil
+	}
+	choice := branch[0]
+	if len(s.children) != 0 {
+		// Check for the absence of divergence.
+		if err := s.checkDivergence(choice.transitions); err != nil {
+			return err
+		}
+	} else {
+		// Add new children.
+		for _, transition := range choice.transitions {
+			child := newState(s, transition.tid)
+			child.clock = transition.clock
+			child.enabled = transition.enabled
+			child.kind = transition.kind
+			child.readSet = transition.readSet
+			child.writeSet = transition.writeSet
+			s.children[child.tid] = child
+		}
+	}
+	next, found := s.children[choice.next]
+	if !found {
+		return errors.New(fmt.Sprintf("invalid choice (no transition fo thread %d).", choice.next))
+	}
+	next.visited = true
+	branch = branch[1:]
+	if err := next.addBranch(branch, seeds); err != nil {
+		return err
+	}
+	s.collectSeeds(choice.next, seeds)
+	s.compactTree()
+	return nil
+}
+
+// approach identifies and enforces exploration of transition(s)
+// originating from this state that near execution of the transition
+// leading to the given state.
+func (s *state) approach(other *state, seeds *stack) {
+	candidates := make([]TID, 0)
+	for tid, child := range s.children {
+		if child.enabled && child.happensBefore(other) {
+			candidates = append(candidates, tid)
+		}
+	}
+	if len(candidates) == 0 {
+		for _, child := range s.children {
+			if child.enabled && !child.seeded && !child.visited {
+				seeds.Push(child)
+				child.seeded = true
+			}
+		}
+	} else {
+		for _, tid := range candidates {
+			child := s.children[tid]
+			if child.seeded || child.visited {
+				return
+			}
+		}
+		tid := candidates[0]
+		child := s.children[tid]
+		seeds.Push(child)
+		child.seeded = true
+	}
+}
+
+// checkDivergence checks whether the given transition matches the
+// transition identified by the given thread identifier.
+func (s *state) checkDivergence(transitions map[TID]*transition) error {
+	if len(s.children) != len(transitions) {
+		return errors.New(fmt.Sprintf("divergence encountered (expected %v, got %v)", len(s.children), len(transitions)))
+	}
+	for tid, t := range transitions {
+		child, found := s.children[tid]
+		if !found {
+			return errors.New(fmt.Sprintf("divergence encountered (no transition for thread %d)", tid))
+		}
+		if child.enabled != t.enabled {
+			return errors.New(fmt.Sprintf("divergence encountered (expected %v, got %v)", child.enabled, t.enabled))
+		}
+		if !child.clock.equals(t.clock) {
+			return errors.New(fmt.Sprintf("divergence encountered (expected %v, got %v)", child.clock, t.clock))
+		}
+		if !reflect.DeepEqual(child.readSet, t.readSet) {
+			return errors.New(fmt.Sprintf("divergence encountered (expected %v, got %v)", child.readSet, t.readSet))
+		}
+		if !reflect.DeepEqual(child.writeSet, t.writeSet) {
+			return errors.New(fmt.Sprintf("divergence encountered (expected %v, got %v)", child.writeSet, t.writeSet))
+		}
+	}
+	return nil
+}
+
+// collectSeeds uses dynamic partial order reduction
+// (http://users.soe.ucsc.edu/~cormac/papers/popl05.pdf) to identify
+// which alternative scheduling choices should be explored.
+func (s *state) collectSeeds(next TID, seeds *stack) {
+	for tid, child := range s.children {
+		if tid != next && !s.mayInterfereWith(child) && !s.happensBefore(child) {
+			continue
+		}
+		for handle := s; handle.parent != nil; handle = handle.parent {
+			if handle.mayInterfereWith(child) && !handle.happensBefore(child) {
+				handle.parent.approach(child, seeds)
+			}
+		}
+	}
+}
+
+// compactTree updates the exploration status of this state and
+// deallocates its children map if all of its children are explored.
+func (s *state) compactTree() {
+	for _, child := range s.children {
+		if (child.seeded || child.visited) && !child.explored {
+			return
+		}
+	}
+	s.children = nil
+	s.explored = true
+}
+
+// generateStrategy generates a sequence of scheduling decisions that
+// will steer an execution towards the state of the execution tree.
+func (s *state) generateStrategy() []TID {
+	if s.parent == nil {
+		return make([]TID, 0)
+	}
+	return append(s.parent.generateStrategy(), s.tid)
+}
+
+// happensBefore checks if the logical time of the transition that
+// leads to this state happened before (in the sense of Lamport's
+// happens-before relation) the logical time of the transition that
+// leads to the given state.
+func (s *state) happensBefore(other *state) bool {
+	return s.clock.happensBefore(other.clock)
+}
+
+// mayInterfereWith checks if the execution of the transition that
+// leads to this state may interfere with the execution of the
+// transition that leads to the given state.
+func (s *state) mayInterfereWith(other *state) bool {
+	if (s.kind == tMutexLock && other.kind == tMutexUnlock) ||
+		(s.kind == tMutexUnlock && other.kind == tMutexLock) {
+		return false
+	}
+	if (s.kind == tRWMutexLock && other.kind == tRWMutexUnlock) ||
+		(s.kind == tRWMutexUnlock && other.kind == tRWMutexLock) {
+		return false
+	}
+	if (s.kind == tRWMutexRLock && other.kind == tRWMutexUnlock) ||
+		(s.kind == tRWMutexRUnlock && other.kind == tRWMutexLock) {
+		return false
+	}
+	for k, _ := range s.readSet {
+		if _, found := other.writeSet[k]; found {
+			return true
+		}
+	}
+	for k, _ := range s.writeSet {
+		if _, found := other.readSet[k]; found {
+			return true
+		}
+	}
+	for k, _ := range s.writeSet {
+		if _, found := other.writeSet[k]; found {
+			return true
+		}
+	}
+	return false
+}
diff --git a/runtime/internal/testing/concurrency/state_test.go b/runtime/internal/testing/concurrency/state_test.go
new file mode 100644
index 0000000..1305139
--- /dev/null
+++ b/runtime/internal/testing/concurrency/state_test.go
@@ -0,0 +1,222 @@
+// 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 concurrency
+
+import (
+	"testing"
+)
+
+// createsBranch creates a branch of a simple execution tree used for
+// testing the state implementation. This branch emulates an execution
+// in which two threads compete for a mutex.
+func createBranch(i, j TID) []*choice {
+	choices := make([]*choice, 0)
+	set := resourceSet{"mutex": struct{}{}}
+
+	{
+		c := newChoice()
+		c.next = i
+		ti := &transition{
+			tid:      i,
+			clock:    newClock(),
+			enabled:  true,
+			kind:     tMutexLock,
+			readSet:  set,
+			writeSet: set,
+		}
+		ti.clock[i] = 1
+		c.transitions[i] = ti
+		tj := &transition{
+			tid:      j,
+			clock:    newClock(),
+			enabled:  true,
+			kind:     tMutexLock,
+			readSet:  set,
+			writeSet: set,
+		}
+		tj.clock[j] = 1
+		c.transitions[j] = tj
+		choices = append(choices, c)
+	}
+
+	{
+		c := newChoice()
+		c.next = i
+		ti := &transition{
+			tid:      i,
+			clock:    newClock(),
+			enabled:  true,
+			kind:     tMutexUnlock,
+			readSet:  set,
+			writeSet: set,
+		}
+		ti.clock[i] = 2
+		c.transitions[i] = ti
+		tj := &transition{
+			tid:      j,
+			clock:    newClock(),
+			enabled:  false,
+			kind:     tMutexLock,
+			readSet:  set,
+			writeSet: set,
+		}
+		tj.clock[j] = 1
+		c.transitions[j] = tj
+		choices = append(choices, c)
+	}
+
+	{
+		c := newChoice()
+		c.next = j
+		tj := &transition{
+			tid:      j,
+			clock:    newClock(),
+			enabled:  true,
+			kind:     tMutexLock,
+			readSet:  set,
+			writeSet: set,
+		}
+		tj.clock[j] = 1
+		c.transitions[j] = tj
+		choices = append(choices, c)
+	}
+
+	{
+		c := newChoice()
+		c.next = j
+		tj := &transition{
+			tid:      j,
+			clock:    newClock(),
+			enabled:  true,
+			kind:     tMutexUnlock,
+			readSet:  set,
+			writeSet: set,
+		}
+		tj.clock[i] = 2
+		tj.clock[j] = 2
+		c.transitions[j] = tj
+		choices = append(choices, c)
+	}
+
+	return choices
+}
+
+// TestCommon checks common operation of the state implementation.
+func TestCommon(t *testing.T) {
+	leftBranch := createBranch(0, 1)
+	rightBranch := createBranch(1, 0)
+	root := newState(nil, 0)
+	seeds := &stack{}
+	if err := root.addBranch(leftBranch, seeds); err != nil {
+		t.Fatalf("addBranch() failed: %v", err)
+	}
+	// Check a new exploration seed has been identified.
+	if seeds.Length() != 1 {
+		t.Fatalf("Unexpected number of seeds: expected %v, got %v", 1, seeds.Length())
+	}
+	if err := root.addBranch(rightBranch, seeds); err != nil {
+		t.Fatalf("addBranch() failed: %v", err)
+	}
+	// Check no new exploration seeds have been identified.
+	if seeds.Length() != 1 {
+		t.Fatalf("Unexpected number of seeds: expected %v, got %v", 1, seeds.Length())
+	}
+	// Check exploration status have been correctly updated.
+	if !root.explored {
+		t.Fatalf("Unexpected exploration status: expected %v, got %v", true, root.explored)
+	}
+	// Check compation of explored children subtrees.
+	if len(root.children) != 0 {
+		t.Fatalf("Unexpected number of children: expected %v, got %v", true, root.explored)
+	}
+}
+
+// TestDivergence checks the various types of execution divergence.
+func TestDivergence(t *testing.T) {
+	// Emulate a missing transition.
+	{
+		leftBranch := createBranch(0, 1)
+		rightBranch := createBranch(1, 0)
+		root := newState(nil, 0)
+		seeds := &stack{}
+		if err := root.addBranch(leftBranch, seeds); err != nil {
+			t.Fatalf("addBranch() failed: %v", err)
+		}
+		delete(rightBranch[0].transitions, 0)
+		if err := root.addBranch(rightBranch, seeds); err == nil {
+			t.Fatalf("addBranch() did not fail")
+		}
+	}
+	// Emulate an extra transition.
+	{
+		leftBranch := createBranch(0, 1)
+		rightBranch := createBranch(1, 0)
+		root := newState(nil, 0)
+		seeds := &stack{}
+		if err := root.addBranch(leftBranch, seeds); err != nil {
+			t.Fatalf("addBranch() failed: %v", err)
+		}
+		rightBranch[0].transitions[2] = &transition{}
+		if err := root.addBranch(rightBranch, seeds); err == nil {
+			t.Fatalf("addBranch() did not fail")
+		}
+	}
+	// Emulate divergent transition enabledness.
+	{
+		leftBranch := createBranch(0, 1)
+		rightBranch := createBranch(1, 0)
+		root := newState(nil, 0)
+		seeds := &stack{}
+		if err := root.addBranch(leftBranch, seeds); err != nil {
+			t.Fatalf("addBranch() failed: %v", err)
+		}
+		rightBranch[0].transitions[0].enabled = false
+		if err := root.addBranch(rightBranch, seeds); err == nil {
+			t.Fatalf("addBranch() did not fail")
+		}
+	}
+	// Emulate divergent transition clock.
+	{
+		leftBranch := createBranch(0, 1)
+		rightBranch := createBranch(1, 0)
+		root := newState(nil, 0)
+		seeds := &stack{}
+		if err := root.addBranch(leftBranch, seeds); err != nil {
+			t.Fatalf("addBranch() failed: %v", err)
+		}
+		rightBranch[0].transitions[1].clock[0]++
+		if err := root.addBranch(rightBranch, seeds); err == nil {
+			t.Fatalf("addBranch() did not fail")
+		}
+	}
+	// Emulate divergent transition read set.
+	{
+		leftBranch := createBranch(0, 1)
+		rightBranch := createBranch(1, 0)
+		root := newState(nil, 0)
+		seeds := &stack{}
+		if err := root.addBranch(leftBranch, seeds); err != nil {
+			t.Fatalf("addBranch() failed: %v", err)
+		}
+		delete(rightBranch[0].transitions[1].readSet, "mutex")
+		if err := root.addBranch(rightBranch, seeds); err == nil {
+			t.Fatalf("addBranch() did not fail")
+		}
+	}
+	// Emulate divergent transition write set.
+	{
+		leftBranch := createBranch(0, 1)
+		rightBranch := createBranch(1, 0)
+		root := newState(nil, 0)
+		seeds := &stack{}
+		if err := root.addBranch(leftBranch, seeds); err != nil {
+			t.Fatalf("addBranch() failed: %v", err)
+		}
+		delete(rightBranch[0].transitions[1].writeSet, "mutex")
+		if err := root.addBranch(rightBranch, seeds); err == nil {
+			t.Fatalf("addBranch() did not fail")
+		}
+	}
+}
diff --git a/runtime/internal/testing/concurrency/sync/sync.go b/runtime/internal/testing/concurrency/sync/sync.go
new file mode 100644
index 0000000..cf933e3
--- /dev/null
+++ b/runtime/internal/testing/concurrency/sync/sync.go
@@ -0,0 +1,90 @@
+// 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 sync
+
+import (
+	"sync"
+
+	"v.io/x/ref/runtime/internal/testing/concurrency"
+)
+
+// Mutex is a wrapper around the Go implementation of Mutex.
+type Mutex struct {
+	m sync.Mutex
+}
+
+// MUTEX INTERFACE IMPLEMENTATION
+
+func (m *Mutex) Lock() {
+	if t := concurrency.T(); t != nil {
+		t.MutexLock(&m.m)
+	} else {
+		m.m.Lock()
+	}
+}
+
+func (m *Mutex) Unlock() {
+	if t := concurrency.T(); t != nil {
+		t.MutexUnlock(&m.m)
+	} else {
+		m.m.Unlock()
+	}
+}
+
+// RWMutex is a wrapper around the Go implementation of RWMutex.
+type RWMutex struct {
+	m sync.RWMutex
+}
+
+// RWMUTEX INTERFACE IMPLEMENTATION
+
+func (m *RWMutex) Lock() {
+	if t := concurrency.T(); t != nil {
+		t.RWMutexLock(&m.m)
+	} else {
+		m.m.Lock()
+	}
+}
+
+func (m *RWMutex) RLock() {
+	if t := concurrency.T(); t != nil {
+		t.RWMutexRLock(&m.m)
+	} else {
+		m.m.RLock()
+	}
+}
+
+func (m *RWMutex) RLocker() sync.Locker {
+	if t := concurrency.T(); t != nil {
+		return (*rlocker)(m)
+	} else {
+		return m.m.RLocker()
+	}
+}
+
+func (m *RWMutex) RUnlock() {
+	if t := concurrency.T(); t != nil {
+		t.RWMutexRUnlock(&m.m)
+	} else {
+		m.m.RUnlock()
+	}
+}
+func (m *RWMutex) Unlock() {
+	if t := concurrency.T(); t != nil {
+		t.RWMutexUnlock(&m.m)
+	} else {
+		m.m.Unlock()
+	}
+}
+
+type rlocker RWMutex
+
+func (r *rlocker) Lock() {
+	(*RWMutex)(r).RLock()
+}
+
+func (r *rlocker) Unlock() {
+	(*RWMutex)(r).RUnlock()
+}
diff --git a/runtime/internal/testing/concurrency/tester.go b/runtime/internal/testing/concurrency/tester.go
new file mode 100644
index 0000000..0772d4e
--- /dev/null
+++ b/runtime/internal/testing/concurrency/tester.go
@@ -0,0 +1,238 @@
+// 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 concurrency
+
+import (
+	"sync"
+	"time"
+)
+
+// globalT stores a pointer to the global instance of the tester.
+var (
+	globalT *Tester = nil
+)
+
+// T returns the global instance of the tester.
+func T() *Tester {
+	return globalT
+}
+
+// Setup sets up a new instance of the tester.
+func Init(setup, body, cleanup func()) *Tester {
+	tree := newState(nil, 0)
+	tree.visited = true
+	seeds := &stack{}
+	seeds.Push(tree)
+	tree.seeded = true
+	globalT = &Tester{
+		tree:    tree,
+		seeds:   seeds,
+		setup:   setup,
+		body:    body,
+		cleanup: cleanup,
+	}
+	return globalT
+}
+
+// Cleanup destroys the existing instance of the tester.
+func Finish() {
+	globalT = nil
+}
+
+// Tester represents an instance of the systematic test.
+type Tester struct {
+	// enabled records whether the tester is to be used or not.
+	enabled bool
+	// execution represents the currently explored execution.
+	execution *execution
+	// tree represents the current state of the exploration of the space
+	// of all possible interleavings of concurrent transitions.
+	tree *state
+	// seeds is records the collection of scheduling alternatives to be
+	// explored in the future.
+	seeds *stack
+	// setup is a function that is executed before an instance of the
+	// test is started. It is assumed to always produce the same initial
+	// state.
+	setup func()
+	// body is a function that implements the body of the test.
+	body func()
+	// cleanup is a function that is executed after an instance of a
+	// test instance terminates.
+	cleanup func()
+}
+
+// Explore explores the space of possible test schedules until the
+// state space is fully exhausted.
+func (t *Tester) Explore() (int, error) {
+	return t.explore(0, 0)
+}
+
+// ExploreFor explores the space of possible test schedules until the
+// state space is fully exhausted or the given duration elapses,
+// whichever occurs first.
+func (t *Tester) ExploreFor(d time.Duration) (int, error) {
+	return t.explore(0, d)
+}
+
+// ExploreN explores the space of possible test schedules until the
+// state space is fully exhausted or the given number of schedules is
+// explored, whichever occurs first.
+func (t *Tester) ExploreN(n int) (int, error) {
+	return t.explore(n, 0)
+}
+
+// MutexLock implements the logic related to modeling and scheduling
+// an execution of "m.Lock()".
+func (t *Tester) MutexLock(m *sync.Mutex) {
+	if t.enabled {
+		done := make(chan struct{})
+		request := mutexLockRequest{defaultRequest{done: done}, m}
+		t.execution.requests <- request
+		<-done
+	} else {
+		m.Lock()
+	}
+}
+
+// MutexUnlock implements the logic related to modeling and scheduling
+// an execution of "m.Unlock()".
+func (t *Tester) MutexUnlock(m *sync.Mutex) {
+	if t.enabled {
+		done := make(chan struct{})
+		request := mutexUnlockRequest{defaultRequest{done: done}, m}
+		t.execution.requests <- request
+		<-done
+	} else {
+		m.Unlock()
+	}
+}
+
+// RWMutexLock implements the logic related to modeling and scheduling
+// an execution of "rw.Lock()".
+func (t *Tester) RWMutexLock(rw *sync.RWMutex) {
+	if t.enabled {
+		done := make(chan struct{})
+		request := rwMutexLockRequest{defaultRequest{done: done}, false, rw}
+		t.execution.requests <- request
+		<-done
+	} else {
+		rw.Lock()
+	}
+}
+
+// RWMutexRLock implements the logic related to modeling and
+// scheduling an execution of "rw.RLock()".
+func (t *Tester) RWMutexRLock(rw *sync.RWMutex) {
+	if t.enabled {
+		done := make(chan struct{})
+		request := rwMutexLockRequest{defaultRequest{done: done}, true, rw}
+		t.execution.requests <- request
+		<-done
+	} else {
+		rw.RLock()
+	}
+}
+
+// RWMutexRUnlock implements the logic related to modeling and
+// scheduling an execution of "rw.RUnlock()".
+func (t *Tester) RWMutexRUnlock(rw *sync.RWMutex) {
+	if t.enabled {
+		done := make(chan struct{})
+		request := rwMutexUnlockRequest{defaultRequest{done: done}, true, rw}
+		t.execution.requests <- request
+		<-done
+	} else {
+		rw.RUnlock()
+	}
+}
+
+// RWMutexUnlock implements the logic related to modeling and
+// scheduling an execution of "rw.Unlock()".
+func (t *Tester) RWMutexUnlock(rw *sync.RWMutex) {
+	if t.enabled {
+		done := make(chan struct{})
+		request := rwMutexUnlockRequest{defaultRequest{done: done}, false, rw}
+		t.execution.requests <- request
+		<-done
+	} else {
+		rw.Unlock()
+	}
+}
+
+// Start implements the logic related to modeling and scheduling an
+// execution of "go fn()".
+func Start(fn func()) {
+	t := globalT
+	if t != nil && t.enabled {
+		done1 := make(chan struct{})
+		reply := make(chan TID)
+		request1 := goRequest{defaultRequest{done: done1}, reply}
+		t.execution.requests <- request1
+		tid := <-reply
+		<-done1
+		go t.startHelper(tid, fn)
+		done2 := make(chan struct{})
+		request2 := goParentRequest{defaultRequest{done: done2}}
+		t.execution.requests <- request2
+		<-done2
+	} else {
+		fn()
+	}
+}
+
+// Exit implements the logic related to modeling and scheduling thread
+// termination.
+func Exit() {
+	t := globalT
+	if t != nil && t.enabled {
+		done := make(chan struct{})
+		request := goExitRequest{defaultRequest{done: done}}
+		t.execution.requests <- request
+		<-done
+	}
+}
+
+// startHelper is a wrapper used by the implementation of Start() to
+// make sure the child thread is registered with the correct
+// identifier.
+func (t *Tester) startHelper(tid TID, fn func()) {
+	done := make(chan struct{})
+	request := goChildRequest{defaultRequest{done: done}, tid}
+	t.execution.requests <- request
+	<-done
+	fn()
+}
+
+func (t *Tester) explore(n int, d time.Duration) (int, error) {
+	niterations := 0
+	start := time.Now()
+	for !t.seeds.Empty() &&
+		(n == 0 || niterations < n) &&
+		(d == 0 || time.Since(start) < d) {
+		t.setup()
+		seed, err := t.seeds.Pop()
+		if err != nil {
+			panic("Corrupted stack.\n")
+		}
+		strategy := seed.generateStrategy()
+		t.execution = newExecution(strategy)
+		t.enabled = true
+		if err := t.tree.addBranch(t.execution.Run(t.body), t.seeds); err != nil {
+			t.enabled = false
+			return niterations, err
+		}
+		t.enabled = false
+		// Sort the seeds because dynamic partial order reduction might
+		// have added elements that violate the depth-first ordering of
+		// seeds. The depth-first ordering is used for space-efficient
+		// (O(d) where d is the depth of the execution tree) exploration
+		// of the execution tree.
+		t.seeds.Sort()
+		t.cleanup()
+		niterations++
+	}
+	return niterations, nil
+}
diff --git a/runtime/internal/testing/concurrency/thread.go b/runtime/internal/testing/concurrency/thread.go
new file mode 100644
index 0000000..cf93ff3
--- /dev/null
+++ b/runtime/internal/testing/concurrency/thread.go
@@ -0,0 +1,93 @@
+// 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 concurrency
+
+import (
+	"fmt"
+)
+
+// TID is the thread identifier type.
+type TID int
+
+// Increasing is used to sort thread identifiers in an increasing order.
+type IncreasingTID []TID
+
+// SORT INTERFACE IMPLEMENTATION
+
+func (tids IncreasingTID) Len() int {
+	return len(tids)
+}
+func (tids IncreasingTID) Less(i, j int) bool {
+	return tids[i] < tids[j]
+}
+func (tids IncreasingTID) Swap(i, j int) {
+	tids[i], tids[j] = tids[j], tids[i]
+}
+
+// TIDGenerator is used for generating unique thread identifiers.
+func TIDGenerator() func() TID {
+	var n int = 0
+	return func() TID {
+		n++
+		return TID(n)
+	}
+}
+
+// thread records the abstract state of a thread during an execution
+// of the test.
+type thread struct {
+	// tid is the thread identifier.
+	tid TID
+	// clock is a vector clock that keeps track of the logical time of
+	// this thread.
+	clock clock
+	// ready is a channel that can be used to schedule execution of the
+	// thread.
+	ready chan struct{}
+	// req holds the current scheduling request of the thread.
+	request request
+}
+
+// newThread is the thread factory.
+func newThread(tid TID, clock clock) *thread {
+	return &thread{
+		tid:   tid,
+		clock: clock.clone(),
+	}
+}
+
+// enabled checks if the thread can be scheduled given the current
+// execution context.
+func (t *thread) enabled(ctx *context) bool {
+	if t.request == nil {
+		panic(fmt.Sprintf("Thread %v has no request.", t.tid))
+	}
+	return t.request.enabled(ctx)
+}
+
+// kind returns the kind of the thread transition.
+func (t *thread) kind() transitionKind {
+	if t.request == nil {
+		panic(fmt.Sprintf("Thread %v has no request.", t.tid))
+	}
+	return t.request.kind()
+}
+
+// readSet returns the set of abstract resources read by the thread.
+func (t *thread) readSet() resourceSet {
+	if t.request == nil {
+		panic(fmt.Sprintf("Thread %v has no request.", t.tid))
+	}
+	return t.request.readSet()
+}
+
+// writeSet returns the set of abstract resources written by the
+// thread.
+func (t *thread) writeSet() resourceSet {
+	if t.request == nil {
+		panic(fmt.Sprintf("Thread %v has no request.", t.tid))
+	}
+	return t.request.writeSet()
+}
diff --git a/runtime/internal/testing/concurrency/transition.go b/runtime/internal/testing/concurrency/transition.go
new file mode 100644
index 0000000..eb2c6dc
--- /dev/null
+++ b/runtime/internal/testing/concurrency/transition.go
@@ -0,0 +1,41 @@
+// 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 concurrency
+
+// transitionKind identifies the kind of transition.
+type transitionKind int
+
+const (
+	tNil transitionKind = iota
+	tGoParent
+	tGoChild
+	tGoExit
+	tMutexLock
+	tMutexUnlock
+	tRWMutexLock
+	tRWMutexRLock
+	tRWMutexRUnlock
+	tRWMutexUnlock
+)
+
+// transition records information about the abstract program
+// transition of a thread.
+type transition struct {
+	// tid identifies the thread this transition belongs to.
+	tid TID
+	// clock records the logical time at the beginning of this
+	// transition as perceived by the thread this transition belongs to.
+	clock map[TID]int
+	// kind records the kind of this transition.
+	kind transitionKind
+	// enable identifies whether this transition is enabled.
+	enabled bool
+	// readSet identifies the set of abstract resources read by this
+	// transition.
+	readSet resourceSet
+	// writeSet identifies the set of abstract resources written by this
+	// transition.
+	writeSet resourceSet
+}
diff --git a/runtime/internal/testing/concurrency/util_test.go b/runtime/internal/testing/concurrency/util_test.go
new file mode 100644
index 0000000..1ab3063
--- /dev/null
+++ b/runtime/internal/testing/concurrency/util_test.go
@@ -0,0 +1,89 @@
+// 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 concurrency_test
+
+import (
+	"bufio"
+	"bytes"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"testing"
+)
+
+// checkExpectedOutputs checks that all expected outputs are
+// generated.
+func checkExpectedOutputs(t *testing.T, outputs, expectedOutputs []string) {
+	for _, expected := range expectedOutputs {
+		found := false
+		for _, output := range outputs {
+			if output == expected {
+				found = true
+				break
+			}
+		}
+		if !found {
+			t.Fatalf("Expected output %v never generated", expected)
+		}
+	}
+}
+
+// checkUnexpectedOutputs checks that no unexpected outputs are
+// generated.
+func checkUnexpectedOutputs(t *testing.T, outputs, expectedOutputs []string) {
+	for _, output := range outputs {
+		found := false
+		for _, expected := range expectedOutputs {
+			if output == expected {
+				found = true
+				break
+			}
+		}
+		if !found {
+			t.Fatalf("Unexpected output %v generated", output)
+		}
+	}
+}
+
+// cleanupClosure returns a function that is used as the cleanup
+// function of systematic tests.
+func cleanupClosure(out *os.File) func() {
+	return func() {
+		fmt.Fprintf(out, "\n")
+	}
+}
+
+// length computes the number of keys in the given set that hold the
+// value 'true'.
+func length(s map[int]bool) int {
+	n := 0
+	for _, ok := range s {
+		if ok {
+			n++
+		}
+	}
+	return n
+}
+
+// processOutput processes the output file, returning a slice of all
+// output lines generated by a test.
+func processOutput(t *testing.T, f *os.File) []string {
+	buffer, err := ioutil.ReadFile(f.Name())
+	if err != nil {
+		t.Fatalf("ReadFile() failed: %v", err)
+	}
+	scanner := bufio.NewScanner(bytes.NewReader(buffer))
+	result := make([]string, 0)
+	for scanner.Scan() {
+		result = append(result, scanner.Text())
+	}
+	if err := scanner.Err(); err != nil {
+		t.Fatalf("Scanning output file failed: %v", err)
+	}
+	return result
+}
+
+// setup is used as the setup function of systematic tests.
+func setup() {}
diff --git a/runtime/internal/testing/concurrency/v23_internal_test.go b/runtime/internal/testing/concurrency/v23_internal_test.go
new file mode 100644
index 0000000..be5e421
--- /dev/null
+++ b/runtime/internal/testing/concurrency/v23_internal_test.go
@@ -0,0 +1,20 @@
+// 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 concurrency
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	os.Exit(m.Run())
+}
diff --git a/runtime/internal/testing/mocks/mocknet/mocknet.go b/runtime/internal/testing/mocks/mocknet/mocknet.go
new file mode 100644
index 0000000..83f301e
--- /dev/null
+++ b/runtime/internal/testing/mocks/mocknet/mocknet.go
@@ -0,0 +1,365 @@
+// 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 mocknet implements a mock net.Conn that can simulate a variety of
+// network errors and/or be used for tracing.
+package mocknet
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"net"
+	"sync"
+	"testing/iotest"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/naming"
+
+	"v.io/x/ref/runtime/internal/lib/iobuf"
+	inaming "v.io/x/ref/runtime/internal/naming"
+	"v.io/x/ref/runtime/internal/rpc/stream/crypto"
+	"v.io/x/ref/runtime/internal/rpc/stream/message"
+)
+
+// TODO(cnicolaou): consider extending Dialer/Listener API to include a cipher
+// to allow access to encrypted data.
+
+type Mode int
+
+const (
+	Trace Mode = iota // Log the sizes of each read/write call
+	Close             // Close the connection after a specified #bytes are read/written
+	Drop              // Drop byes as per a policy specified in opts
+	// Close the connection based on the Vanadium protocol message
+	V23CloseAtMessage
+)
+
+type Opts struct {
+	// The underlying network protocol to use, e.g. "tcp", defaults to tcp.
+	UnderlyingProtocol string
+
+	// The mode to operate under.
+	Mode Mode
+
+	// Buffers to store the transmit and receive message sizes when
+	// in Trace mode.
+	Tx, Rx chan int
+
+	// The number of rx and tx bytes respectively to be seen before the
+	// connection is closed when in Close mode.
+	RxCloseAt, TxCloseAt int
+
+	// TXDropAfter is called to obtain the number of tx bytes to be sent
+	// before dropping the rest of the data passed to that write call. The
+	// number of bytes returned by TxDroptAfter will always be written,
+	// but the number of bytes dropped is unspecified since it depends
+	// on the size of the buffer passed to that write call. TxDropAfter
+	// will be called again after each drop and the current count of
+	// byte sent reset to zero.
+	TxDropAfter func() (pos int)
+
+	// V23MessageMatcher should return true if the connection
+	// should be closed. read is true for a read call, false for a write,
+	// and msg is a copy of the message just received or to be sent.
+	V23MessageMatcher func(read bool, msg message.T) bool
+}
+
+// DialerWithOpts is intended for use with rpc.RegisterProtocol via
+// a closure:
+//
+//  dialer := func(network, address string, timeout time.Duration) (net.Conn, error) {
+//	    return mocknet.DialerWithOpts(mocknet.Opts{UnderlyingProtocol:"tcp"}, network, address, timeout)
+//  }
+// rpc.RegisterProtocol("brkDial", dialer, resolver, net.Listen)
+//
+func DialerWithOpts(opts Opts, network, address string, timeout time.Duration) (net.Conn, error) {
+	protocol := opts.UnderlyingProtocol
+	if len(protocol) == 0 {
+		protocol = "tcp"
+	}
+	c, err := net.DialTimeout(protocol, address, timeout)
+	if err != nil {
+		return nil, err
+	}
+	return newMockConn(opts, c), nil
+}
+
+// ListenerWithOpts is intended for use with rpc.RegisterProtocol via
+// a closure as per DialerWithOpts.
+func ListenerWithOpts(opts Opts, network, laddr string) (net.Listener, error) {
+	protocol := opts.UnderlyingProtocol
+	if len(protocol) == 0 {
+		protocol = "tcp"
+	}
+	ln, err := net.Listen(protocol, laddr)
+	if err != nil {
+		return nil, err
+	}
+	return &listener{opts, ln}, nil
+}
+
+func newMockConn(opts Opts, c net.Conn) net.Conn {
+	switch opts.Mode {
+	case Trace:
+		return &traceConn{
+			conn: c,
+			rx:   opts.Rx,
+			tx:   opts.Tx}
+	case Close:
+		return &closeConn{
+			conn:      c,
+			rxCloseAt: opts.RxCloseAt,
+			txCloseAt: opts.TxCloseAt,
+		}
+	case Drop:
+		return &dropConn{
+			conn:        c,
+			opts:        opts,
+			txDropAfter: opts.TxDropAfter(),
+		}
+	case V23CloseAtMessage:
+		return &v23Conn{
+			conn:   c,
+			opts:   opts,
+			cipher: &crypto.NullControlCipher{},
+			pool:   iobuf.NewPool(1024),
+		}
+	}
+	return nil
+}
+
+type dropConn struct {
+	sync.Mutex
+	opts        Opts
+	conn        net.Conn
+	tx          int
+	txDropAfter int
+}
+
+func (c *dropConn) Read(b []byte) (n int, err error) {
+	return c.conn.Read(b)
+}
+
+func (c *dropConn) Write(b []byte) (n int, err error) {
+	c.Lock()
+	defer c.Unlock()
+	dropped := false
+	if c.tx+len(b) >= c.txDropAfter {
+		b = b[0 : c.txDropAfter-c.tx]
+		c.txDropAfter = c.opts.TxDropAfter()
+		dropped = true
+	}
+	n, err = c.conn.Write(b)
+	if dropped {
+		c.tx = 0
+	} else {
+		c.tx += n
+	}
+	return
+}
+
+func (c *dropConn) Close() error        { return c.conn.Close() }
+func (c *dropConn) LocalAddr() net.Addr { return c.conn.LocalAddr() }
+func (c *dropConn) RemoteAddr() net.Addr {
+	return c.conn.RemoteAddr()
+}
+func (c *dropConn) SetDeadline(t time.Time) error {
+	return c.conn.SetDeadline(t)
+}
+func (c *dropConn) SetReadDeadline(t time.Time) error {
+	return c.conn.SetReadDeadline(t)
+}
+func (c *dropConn) SetWriteDeadline(t time.Time) error {
+	return c.conn.SetWriteDeadline(t)
+}
+
+type closeConn struct {
+	sync.Mutex
+	conn                 net.Conn
+	rx, tx               int
+	rxCloseAt, txCloseAt int
+	closed               bool
+}
+
+func (c *closeConn) Read(b []byte) (n int, err error) {
+	c.Lock()
+	defer c.Unlock()
+	n = len(b)
+	if c.rx+n >= c.rxCloseAt {
+		n = c.rxCloseAt - c.rx
+	}
+	b = b[:n]
+	n, err = c.conn.Read(b[:n])
+	c.rx += n
+	if c.rx == c.rxCloseAt {
+		c.conn.Close()
+	}
+	return
+}
+
+func (c *closeConn) Write(b []byte) (n int, err error) {
+	c.Lock()
+	defer c.Unlock()
+	n = len(b)
+	if c.tx+n >= c.txCloseAt {
+		n = c.txCloseAt - c.tx
+	}
+	n, err = c.conn.Write(b[:n])
+	c.tx += n
+	if c.tx == c.txCloseAt {
+		c.conn.Close()
+	}
+	return
+}
+
+func (c *closeConn) Close() error        { return c.conn.Close() }
+func (c *closeConn) LocalAddr() net.Addr { return c.conn.LocalAddr() }
+func (c *closeConn) RemoteAddr() net.Addr {
+	return c.conn.RemoteAddr()
+}
+func (c *closeConn) SetDeadline(t time.Time) error {
+	return c.conn.SetDeadline(t)
+}
+func (c *closeConn) SetReadDeadline(t time.Time) error {
+	return c.conn.SetReadDeadline(t)
+}
+func (c *closeConn) SetWriteDeadline(t time.Time) error {
+	return c.conn.SetWriteDeadline(t)
+}
+
+type traceConn struct {
+	conn   net.Conn
+	tx, rx chan int
+}
+
+func (c *traceConn) Read(b []byte) (n int, err error) {
+	n, err = c.conn.Read(b)
+	c.rx <- n
+	return n, err
+}
+
+func (c *traceConn) Write(b []byte) (n int, err error) {
+	n, err = c.conn.Write(b)
+	c.tx <- n
+	return
+}
+
+func (c *traceConn) Close() error {
+	c.rx <- -1
+	c.tx <- -1
+	return c.conn.Close()
+}
+
+func (c *traceConn) LocalAddr() net.Addr  { return c.conn.LocalAddr() }
+func (c *traceConn) RemoteAddr() net.Addr { return c.conn.RemoteAddr() }
+func (c *traceConn) SetDeadline(t time.Time) error {
+	return c.conn.SetDeadline(t)
+}
+func (c *traceConn) SetReadDeadline(t time.Time) error {
+	return c.conn.SetReadDeadline(t)
+}
+func (c *traceConn) SetWriteDeadline(t time.Time) error {
+	return c.conn.SetWriteDeadline(t)
+}
+
+type v23Conn struct {
+	conn   net.Conn
+	opts   Opts
+	cipher crypto.ControlCipher
+	pool   *iobuf.Pool
+}
+
+func (c *v23Conn) Read(b []byte) (n int, err error) {
+	rb := bytes.NewBuffer(b[:0])
+	r := iobuf.NewReader(c.pool, io.TeeReader(iotest.OneByteReader(io.LimitReader(c.conn, int64(len(b)))), rb))
+	msg, err := message.ReadFrom(r, c.cipher)
+	if err == nil && c.opts.V23MessageMatcher(true, msg) {
+		c.conn.Close()
+		return 0, io.EOF
+	}
+	return rb.Len(), err
+}
+
+func (c *v23Conn) Write(b []byte) (n int, err error) {
+	rb := bytes.NewBuffer(b)
+	r := iobuf.NewReader(c.pool, iotest.OneByteReader(rb))
+	for rb.Len() > 0 {
+		msg, err := message.ReadFrom(r, c.cipher)
+		if err != nil {
+			return n, err
+		}
+		if c.opts.V23MessageMatcher(false, msg) {
+			c.conn.Close()
+			return n, io.EOF
+		}
+		var wb bytes.Buffer
+		err = message.WriteTo(&wb, msg, c.cipher)
+		if err != nil {
+			return n, err
+		}
+		tx, err := c.conn.Write(wb.Bytes())
+		n += tx
+		if err != nil {
+			return n, err
+		}
+	}
+	return n, nil
+}
+
+func (c *v23Conn) Close() error {
+	return c.conn.Close()
+}
+
+func (c *v23Conn) LocalAddr() net.Addr  { return c.conn.LocalAddr() }
+func (c *v23Conn) RemoteAddr() net.Addr { return c.conn.RemoteAddr() }
+func (c *v23Conn) SetDeadline(t time.Time) error {
+	return c.conn.SetDeadline(t)
+}
+func (c *v23Conn) SetReadDeadline(t time.Time) error {
+	return c.conn.SetReadDeadline(t)
+}
+func (c *v23Conn) SetWriteDeadline(t time.Time) error {
+	return c.conn.SetWriteDeadline(t)
+}
+
+// listener is a wrapper around net.Listener.
+type listener struct {
+	opts  Opts
+	netLn net.Listener
+}
+
+func (ln *listener) Accept() (net.Conn, error) {
+	c, err := ln.netLn.Accept()
+	if err != nil {
+		return nil, err
+	}
+	return newMockConn(ln.opts, c), nil
+}
+
+func (ln *listener) Close() error {
+	return ln.netLn.Close()
+}
+
+func (ln *listener) Addr() net.Addr {
+	return ln.netLn.Addr()
+}
+
+func RewriteEndpointProtocol(ep string, protocol string) (naming.Endpoint, error) {
+	addr := ep
+	if naming.Rooted(ep) {
+		addr, _ = naming.SplitAddressName(ep)
+	}
+	n, err := v23.NewEndpoint(addr)
+	if err != nil {
+		return nil, err
+	}
+	iep, ok := n.(*inaming.Endpoint)
+	if !ok {
+		return nil, fmt.Errorf("failed to convert %T to inaming.Endpoint", n)
+	}
+	iep.Protocol = protocol
+	return iep, nil
+}
diff --git a/runtime/internal/testing/mocks/mocknet/mocknet_test.go b/runtime/internal/testing/mocks/mocknet/mocknet_test.go
new file mode 100644
index 0000000..f545b56
--- /dev/null
+++ b/runtime/internal/testing/mocks/mocknet/mocknet_test.go
@@ -0,0 +1,389 @@
+// 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 mocknet_test
+
+import (
+	"bytes"
+	"errors"
+	"io"
+	"net"
+	"reflect"
+	"strings"
+	"sync"
+	"testing"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/options"
+	"v.io/v23/rpc"
+	"v.io/v23/verror"
+
+	"v.io/x/ref/lib/xrpc"
+	_ "v.io/x/ref/runtime/factories/generic"
+	"v.io/x/ref/runtime/internal/rpc/stream/crypto"
+	"v.io/x/ref/runtime/internal/rpc/stream/message"
+	"v.io/x/ref/runtime/internal/testing/mocks/mocknet"
+	"v.io/x/ref/test"
+)
+
+//go:generate v23 test generate
+
+func newListener(t *testing.T, opts mocknet.Opts) net.Listener {
+	ln, err := mocknet.ListenerWithOpts(opts, "test", "127.0.0.1:0")
+	if err != nil {
+		t.Fatal(err)
+	}
+	return ln
+}
+
+func TestTrace(t *testing.T) {
+	opts := mocknet.Opts{
+		Mode: mocknet.Trace,
+		Tx:   make(chan int, 100),
+		Rx:   make(chan int, 100),
+	}
+	ln := newListener(t, opts)
+	defer ln.Close()
+
+	var rxconn net.Conn
+	var wg sync.WaitGroup
+	wg.Add(1)
+	go func() {
+		rxconn, _ = ln.Accept()
+		wg.Done()
+	}()
+
+	txconn, err := mocknet.DialerWithOpts(opts, "test", ln.Addr().String(), time.Minute)
+	if err != nil {
+		t.Fatal(err)
+	}
+	wg.Wait()
+
+	rw := func(s string) {
+		b := make([]byte, len(s))
+		txconn.Write([]byte(s))
+		rxconn.Read(b[:])
+		if got, want := string(b), s; got != want {
+			t.Fatalf("got %v, want %v", got, want)
+		}
+	}
+
+	sizes := []int{}
+	for _, s := range []string{"hello", " ", "world"} {
+		rw(s)
+		sizes = append(sizes, len(s))
+	}
+	rxconn.Close()
+	close(opts.Tx)
+	close(opts.Rx)
+	sizes = append(sizes, -1)
+
+	drain := func(ch chan int) []int {
+		r := []int{}
+		for v := range ch {
+			r = append(r, v)
+		}
+		return r
+	}
+
+	if got, want := drain(opts.Rx), sizes; !reflect.DeepEqual(got, want) {
+		t.Fatalf("got %v, want %v", got, want)
+	}
+	if got, want := drain(opts.Tx), sizes; !reflect.DeepEqual(got, want) {
+		t.Fatalf("got %v, want %v", got, want)
+	}
+}
+
+func TestClose(t *testing.T) {
+	cases := []struct {
+		txClose, rxClose int
+		tx               []string
+		rx               []string
+		err              error
+	}{
+		{6, 10, []string{"hello", "world"}, []string{"hello", "w"}, io.EOF},
+		{5, 10, []string{"hello", "world"}, []string{"hello", ""}, io.EOF},
+		{8, 6, []string{"hello", "world"}, []string{"hello", "w"}, io.EOF},
+		{8, 5, []string{"hello", "world"}, []string{"hello", ""}, errors.New("use of closed network connection")},
+	}
+
+	for ci, c := range cases {
+		opts := mocknet.Opts{
+			Mode:      mocknet.Close,
+			TxCloseAt: c.txClose,
+			RxCloseAt: c.rxClose,
+		}
+
+		ln := newListener(t, opts)
+		defer ln.Close()
+
+		var rxconn net.Conn
+		var wg sync.WaitGroup
+		wg.Add(1)
+		go func() {
+			rxconn, _ = ln.Accept()
+			wg.Done()
+		}()
+
+		txconn, err := mocknet.DialerWithOpts(opts, "test", ln.Addr().String(), time.Minute)
+		if err != nil {
+			t.Fatal(err)
+		}
+		wg.Wait()
+
+		rw := func(s string) (int, int, string, error) {
+			b := make([]byte, len(s))
+			tx, _ := txconn.Write([]byte(s))
+			rx, err := rxconn.Read(b[:])
+			return tx, rx, string(b[0:rx]), err
+		}
+
+		txBytes := 0
+		rxBytes := 0
+		for i, m := range c.tx {
+			tx, rx, rxed, err := rw(m)
+			if got, want := rxed, c.rx[i]; got != want {
+				t.Fatalf("%d: got %q, want %q", ci, got, want)
+			}
+			txBytes += tx
+			rxBytes += rx
+			if err != nil {
+				if got, want := err.Error(), c.err.Error(); !strings.Contains(got, want) {
+					t.Fatalf("%d: got %q, does not contain %q", ci, got, want)
+				}
+			}
+		}
+		if got, want := txBytes, c.txClose; got != want {
+			t.Fatalf("%d: got %v, want %v", ci, got, want)
+		}
+		rxWant := c.rxClose
+		if rxWant > c.txClose {
+			rxWant = c.txClose
+		}
+		if got, want := rxBytes, rxWant; got != want {
+			t.Fatalf("%d: got %v, want %v", ci, got, want)
+
+		}
+	}
+}
+
+func TestDrop(t *testing.T) {
+	cases := []struct {
+		txDropAfter int
+		tx          []string
+		rx          []string
+	}{
+		{6, []string{"hello", "world"}, []string{"hello", "w"}},
+		{2, []string{"hello", "world"}, []string{"he", "wo"}},
+		{0, []string{"hello", "world"}, []string{"", ""}},
+	}
+
+	for ci, c := range cases {
+		opts := mocknet.Opts{
+			Mode:        mocknet.Drop,
+			TxDropAfter: func() int { return c.txDropAfter },
+		}
+
+		ln := newListener(t, opts)
+		defer ln.Close()
+
+		var rxconn net.Conn
+		var wg sync.WaitGroup
+		wg.Add(1)
+		go func() {
+			rxconn, _ = ln.Accept()
+			wg.Done()
+		}()
+
+		txconn, err := mocknet.DialerWithOpts(opts, "test", ln.Addr().String(), time.Minute)
+		if err != nil {
+			t.Fatal(err)
+		}
+		wg.Wait()
+
+		rw := func(s string, l int) (int, int, string, error) {
+			b := make([]byte, l)
+			tx, _ := txconn.Write([]byte(s))
+			rx, err := rxconn.Read(b[:])
+			return tx, rx, string(b[0:rx]), err
+		}
+
+		for i, m := range c.tx {
+			tx, rx, rxed, _ := rw(m, len(c.rx[i]))
+			if got, want := rxed, c.rx[i]; got != want {
+				t.Fatalf("%d: got %q, want %q", ci, got, want)
+			}
+			if tx != rx {
+				t.Fatalf("%d: tx %d, rx %d", ci, tx, rx)
+			}
+		}
+	}
+}
+
+func TestV23Drop(t *testing.T) {
+	cases := []struct {
+		numMsgs, txClose, rxClose int
+	}{
+		{5, 0, 0},
+		{5, 2, 0},
+		{5, 0, 2},
+		{5, 3, 2},
+		{5, 2, 3},
+	}
+
+	for ci, c := range cases {
+		var txed, rxed int
+		matcher := func(read bool, msg message.T) bool {
+			if read {
+				rxed++
+				return rxed == c.rxClose
+			} else {
+				txed++
+				return txed == c.txClose
+			}
+		}
+		opts := mocknet.Opts{
+			Mode:              mocknet.V23CloseAtMessage,
+			V23MessageMatcher: matcher,
+		}
+
+		ln := newListener(t, opts)
+		defer ln.Close()
+
+		var rxconn net.Conn
+		var wg sync.WaitGroup
+		wg.Add(1)
+		go func() {
+			rxconn, _ = ln.Accept()
+			wg.Done()
+		}()
+
+		txconn, err := mocknet.DialerWithOpts(opts, "test", ln.Addr().String(), time.Minute)
+		if err != nil {
+			t.Fatal(err)
+		}
+		wg.Wait()
+
+		var msgBuf bytes.Buffer
+		for i := 0; i < c.numMsgs; i++ {
+			err = message.WriteTo(&msgBuf, &message.Data{}, crypto.NullControlCipher{})
+			if err != nil {
+				t.Fatal(err)
+			}
+		}
+		perMsgBytes := msgBuf.Len() / c.numMsgs
+
+		n, err := txconn.Write(msgBuf.Bytes())
+		txMsgs := n / perMsgBytes
+		switch {
+		case c.txClose > 0:
+			if got, want := txMsgs, c.txClose-1; got != want {
+				t.Fatalf("%d: got %v, want %v", ci, got, want)
+			}
+			if got, want := err, io.EOF; got != want {
+				t.Fatalf("%d: got %v, want %v", ci, got, want)
+			}
+		default:
+			if got, want := txMsgs, c.numMsgs; got != want {
+				t.Fatalf("%d: got %v, want %v", ci, got, want)
+			}
+			if err != nil {
+				t.Fatalf("%d: %v\n", ci, err)
+			}
+		}
+
+		var rxMsgs int
+		for ; rxMsgs < txMsgs; rxMsgs++ {
+			var n int
+			n, err = rxconn.Read(make([]byte, perMsgBytes*2))
+			if err != nil {
+				break
+			}
+			if got, want := n, perMsgBytes; got != want {
+				t.Fatalf("%d: got %v, want %v", ci, got, want)
+			}
+		}
+		switch {
+		case c.rxClose > 0 && (c.txClose == 0 || c.txClose > c.rxClose):
+			if got, want := rxMsgs, c.rxClose-1; got != want {
+				t.Fatalf("%d: got %v, want %v", ci, got, want)
+			}
+			if got, want := err, io.EOF; got != want {
+				t.Fatalf("%d: got %v, want %v", ci, got, want)
+			}
+		default:
+			if got, want := rxMsgs, txMsgs; got != want {
+				t.Fatalf("%d: got %v, want %v", ci, got, want)
+			}
+			if err != nil {
+				t.Fatalf("%d: %v\n", ci, err)
+			}
+		}
+	}
+}
+
+type simple struct{}
+
+func (s *simple) Ping(ctx *context.T, call rpc.ServerCall) (string, error) {
+	return "pong", nil
+}
+
+func initServer(t *testing.T, ctx *context.T) (string, func()) {
+	server, err := xrpc.NewServer(ctx, "", &simple{}, nil, options.SecurityNone)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	done := make(chan struct{})
+	deferFn := func() { close(done); server.Stop() }
+	return server.Status().Endpoints[0].Name(), deferFn
+}
+
+func TestV23Control(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	matcher := func(_ bool, msg message.T) bool {
+		switch msg.(type) {
+		case *message.Data:
+			return false
+		}
+		// drop first control message
+		return true
+	}
+
+	dropControlDialer := func(ctx *context.T, network, address string, timeout time.Duration) (net.Conn, error) {
+		opts := mocknet.Opts{
+			Mode:              mocknet.V23CloseAtMessage,
+			V23MessageMatcher: matcher,
+		}
+		return mocknet.DialerWithOpts(opts, network, address, timeout)
+	}
+
+	simpleResolver := func(ctx *context.T, network, address string) (string, string, error) {
+		return network, address, nil
+	}
+
+	simpleListen := func(ctx *context.T, network, address string) (net.Listener, error) {
+		return net.Listen(network, address)
+	}
+
+	rpc.RegisterProtocol("dropControl", dropControlDialer, simpleResolver, simpleListen)
+
+	server, fn := initServer(t, ctx)
+	defer fn()
+
+	addr, _ := naming.SplitAddressName(server)
+	dropServer, err := mocknet.RewriteEndpointProtocol(addr, "dropControl")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	_, err = v23.GetClient(ctx).StartCall(ctx, dropServer.Name(), "Ping", nil, options.SecurityNone, options.NoRetry{})
+	if verror.ErrorID(err) != verror.ErrBadProtocol.ID {
+		t.Fatal(err)
+	}
+}
diff --git a/runtime/internal/testing/mocks/mocknet/v23_internal_test.go b/runtime/internal/testing/mocks/mocknet/v23_internal_test.go
new file mode 100644
index 0000000..ec445eb
--- /dev/null
+++ b/runtime/internal/testing/mocks/mocknet/v23_internal_test.go
@@ -0,0 +1,20 @@
+// 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 mocknet
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	os.Exit(m.Run())
+}
diff --git a/runtime/internal/testing/mocks/naming/namespace.go b/runtime/internal/testing/mocks/naming/namespace.go
new file mode 100644
index 0000000..fd0e407
--- /dev/null
+++ b/runtime/internal/testing/mocks/naming/namespace.go
@@ -0,0 +1,178 @@
+// 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 naming
+
+import (
+	"fmt"
+	"strings"
+	"sync"
+	"time"
+
+	"v.io/v23/context"
+	"v.io/v23/namespace"
+	"v.io/v23/naming"
+	"v.io/v23/security"
+	"v.io/v23/security/access"
+	"v.io/v23/verror"
+
+	"v.io/x/ref/lib/apilog"
+	inamespace "v.io/x/ref/runtime/internal/naming/namespace"
+)
+
+// NewSimpleNamespace returns a simple implementation of a Namespace
+// server for use in tests.  In particular, it ignores TTLs and not
+// allow fully overlapping mount names.
+func NewSimpleNamespace() namespace.T {
+	ns, err := inamespace.New()
+	if err != nil {
+		panic(err)
+	}
+	return &namespaceMock{mounts: make(map[string]*naming.MountEntry), ns: ns}
+}
+
+// namespaceMock is a simple partial implementation of namespace.T.
+type namespaceMock struct {
+	sync.Mutex
+	mounts map[string]*naming.MountEntry
+	ns     namespace.T
+}
+
+func (ns *namespaceMock) Mount(ctx *context.T, name, server string, _ time.Duration, opts ...naming.NamespaceOpt) error {
+	defer apilog.LogCallf(ctx, "name=%.10s...,server=%.10s...,opts...=%v", name, server, opts)(ctx, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	ns.Lock()
+	defer ns.Unlock()
+	for n, _ := range ns.mounts {
+		if n != name && (strings.HasPrefix(name, n) || strings.HasPrefix(n, name)) {
+			return fmt.Errorf("simple mount table does not allow names that are a prefix of each other")
+		}
+	}
+	e := ns.mounts[name]
+	if e == nil {
+		e = &naming.MountEntry{}
+		ns.mounts[name] = e
+	}
+
+	isdup := func(n string) bool {
+		for _, s := range e.Servers {
+			if n == s.Server {
+				return true
+			}
+		}
+		return false
+	}
+	if !isdup(server) {
+		e.Servers = append(e.Servers, naming.MountedServer{Server: server})
+	}
+	return nil
+}
+
+func (ns *namespaceMock) Unmount(ctx *context.T, name, server string, opts ...naming.NamespaceOpt) error {
+	defer apilog.LogCallf(ctx, "name=%.10s...,server=%.10s...,opts...=%v", name, server, opts)(ctx, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	ns.Lock()
+	defer ns.Unlock()
+	e := ns.mounts[name]
+	if e == nil {
+		return nil
+	}
+	if len(server) == 0 {
+		delete(ns.mounts, name)
+		return nil
+	}
+	var keep []naming.MountedServer
+	for _, s := range e.Servers {
+		if s.Server != server {
+			keep = append(keep, s)
+		}
+	}
+	if len(keep) == 0 {
+		delete(ns.mounts, name)
+		return nil
+	}
+	e.Servers = keep
+	return nil
+}
+
+func (ns *namespaceMock) Delete(ctx *context.T, name string, removeSubtree bool, opts ...naming.NamespaceOpt) error {
+	defer apilog.LogCallf(ctx, "name=%.10s...,removeSubtree=%v,opts...=%v", name, removeSubtree, opts)(ctx, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	ns.Lock()
+	defer ns.Unlock()
+	e := ns.mounts[name]
+	if e == nil {
+		return nil
+	}
+	delete(ns.mounts, name)
+	if !removeSubtree {
+		return nil
+	}
+	for k := range ns.mounts {
+		if strings.HasPrefix(k, name+"/") {
+			delete(ns.mounts, k)
+		}
+	}
+	return nil
+}
+
+func (ns *namespaceMock) Resolve(ctx *context.T, name string, opts ...naming.NamespaceOpt) (*naming.MountEntry, error) {
+	defer apilog.LogCallf(ctx, "name=%.10s...,opts...=%v", name, opts)(ctx, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	_, name = security.SplitPatternName(name)
+	if address, suffix := naming.SplitAddressName(name); len(address) > 0 {
+		return &naming.MountEntry{
+			Name:    suffix,
+			Servers: []naming.MountedServer{{Server: address}},
+		}, nil
+	}
+	ns.Lock()
+	defer ns.Unlock()
+	for prefix, e := range ns.mounts {
+		if strings.HasPrefix(name, prefix) {
+			ret := *e
+			ret.Name = strings.TrimLeft(strings.TrimPrefix(name, prefix), "/")
+			return &ret, nil
+		}
+	}
+	return nil, verror.New(naming.ErrNoSuchName, ctx, fmt.Sprintf("Resolve name %q not found in %v", name, ns.mounts))
+}
+
+func (ns *namespaceMock) ResolveToMountTable(ctx *context.T, name string, opts ...naming.NamespaceOpt) (*naming.MountEntry, error) {
+	defer apilog.LogCallf(ctx, "name=%.10s...,opts...=%v", name, opts)(ctx, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	// TODO(mattr): Implement this method for tests that might need it.
+	panic("ResolveToMountTable not implemented")
+}
+
+func (ns *namespaceMock) FlushCacheEntry(ctx *context.T, name string) bool {
+	defer apilog.LogCallf(nil, "name=%.10s...", name)(nil, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	return false
+}
+
+func (ns *namespaceMock) CacheCtl(ctls ...naming.CacheCtl) []naming.CacheCtl {
+	defer apilog.LogCallf(nil, "ctls...=%v", ctls)(nil, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	return nil
+}
+
+func (ns *namespaceMock) Glob(ctx *context.T, pattern string, opts ...naming.NamespaceOpt) (<-chan naming.GlobReply, error) {
+	defer apilog.LogCallf(ctx, "pattern=%.10s...,opts...=%v", pattern, opts)(ctx, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	// TODO(mattr): Implement this method for tests that might need it.
+	panic("Glob not implemented")
+}
+
+func (ns *namespaceMock) SetRoots(...string) error {
+	defer apilog.LogCall(nil)(nil) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	panic("Calling SetRoots on a mock namespace.  This is not supported.")
+}
+
+func (ns *namespaceMock) Roots() []string {
+	defer apilog.LogCall(nil)(nil) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	panic("Calling Roots on a mock namespace.  This is not supported.")
+}
+
+func (ns *namespaceMock) GetPermissions(ctx *context.T, name string, opts ...naming.NamespaceOpt) (perms access.Permissions, version string, err error) {
+	defer apilog.LogCallf(ctx, "name=%.10s...,opts...=%v", name, opts)(ctx, "perms=,version=%.10s...,err=%v", &version, &err) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	panic("Calling GetPermissions on a mock namespace.  This is not supported.")
+}
+
+func (ns *namespaceMock) SetPermissions(ctx *context.T, name string, perms access.Permissions, version string, opts ...naming.NamespaceOpt) error {
+	defer apilog.LogCallf(ctx, "name=%.10s...,perms=,version=%.10s...,opts...=%v", name, version, opts)(ctx, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	panic("Calling SetPermissions on a mock namespace.  This is not supported.")
+}
diff --git a/runtime/internal/util.go b/runtime/internal/util.go
new file mode 100644
index 0000000..f26022c
--- /dev/null
+++ b/runtime/internal/util.go
@@ -0,0 +1,125 @@
+// 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
+
+import (
+	"fmt"
+	"net"
+	"strings"
+
+	"v.io/x/lib/netstate"
+
+	"v.io/v23/logging"
+	"v.io/v23/verror"
+
+	"v.io/x/ref/internal/logger"
+	"v.io/x/ref/lib/exec"
+	"v.io/x/ref/lib/flags"
+)
+
+// ParseFlags parses all registered flags taking into account overrides from other
+// configuration and environment variables. It must be called by the profile and
+// flags.RuntimeFlags() must be passed to the runtime initialization function. The
+// profile can use or modify the flags as it pleases.
+func ParseFlags(f *flags.Flags) error {
+	handle, err := exec.GetChildHandle()
+	if err == nil {
+		// The process has been started through the vanadium exec
+		// library.
+	} else if verror.ErrorID(err) == exec.ErrNoVersion.ID {
+		// The process has not been started through the vanadium exec
+		// library. No further action is needed.
+	} else {
+		return err
+	}
+
+	// Parse runtime flags.
+	var config map[string]string
+	if handle != nil {
+		config = handle.Config.Dump()
+	}
+	return parseFlagsInternal(f, config)
+}
+
+// ParseFlagsAndConfigurGlobalLogger calls ParseFlags and then
+// ConfigureGlobalLoggerFromFlags.
+func ParseFlagsAndConfigureGlobalLogger(f *flags.Flags) error {
+	if err := ParseFlags(f); err != nil {
+		return err
+	}
+	if err := ConfigureGlobalLoggerFromFlags(); err != nil {
+		return err
+	}
+	return nil
+}
+
+// ConfigureGlobalLoggerFromFlags configures the global logger from command
+// line flags.  Should be called immediately after ParseFlags.
+func ConfigureGlobalLoggerFromFlags() error {
+	err := logger.Manager(logger.Global()).ConfigureFromFlags()
+	if err != nil && !logger.IsAlreadyConfiguredError(err) {
+		return err
+	}
+	return nil
+}
+
+// IPAddressChooser returns the preferred IP address, which is,
+// a public IPv4 address, then any non-loopback IPv4, then a public
+// IPv6 address and finally any non-loopback/link-local IPv6
+type IPAddressChooser struct{}
+
+func (IPAddressChooser) ChooseAddress(network string, addrs []net.Addr) ([]net.Addr, error) {
+	if !netstate.IsIPProtocol(network) {
+		return nil, fmt.Errorf("can't support network protocol %q", network)
+	}
+	accessible := netstate.ConvertToAddresses(addrs)
+
+	// Try and find an address on a interface with a default route.
+	// We give preference to IPv4 over IPv6 for compatibility for now.
+	var predicates []netstate.AddressPredicate
+	if !strings.HasSuffix(network, "6") {
+		predicates = append(predicates, netstate.IsPublicUnicastIPv4, netstate.IsUnicastIPv4)
+	}
+	if !strings.HasSuffix(network, "4") {
+		predicates = append(predicates, netstate.IsPublicUnicastIPv6, netstate.IsUnicastIPv6)
+	}
+	for _, predicate := range predicates {
+		if addrs := accessible.Filter(predicate); len(addrs) > 0 {
+			onDefaultRoutes := addrs.Filter(netstate.IsOnDefaultRoute)
+			if len(onDefaultRoutes) > 0 {
+				return onDefaultRoutes.AsNetAddrs(), nil
+			}
+		}
+	}
+
+	// We failed to find any addresses with default routes, try again
+	// but without the default route requirement.
+	for _, predicate := range predicates {
+		if addrs := accessible.Filter(predicate); len(addrs) > 0 {
+			return addrs.AsNetAddrs(), nil
+		}
+	}
+	return []net.Addr{}, nil
+}
+
+// HasPublicIP returns true if the host has at least one public IP address.
+func HasPublicIP(log logging.Logger) bool {
+	state, err := netstate.GetAccessibleIPs()
+	if err != nil {
+		log.Infof("failed to determine network state: %s", err)
+		return false
+	}
+	any := state.Filter(netstate.IsUnicastIP)
+	if len(any) == 0 {
+		log.Infof("failed to find any usable IP addresses at startup")
+		return false
+	}
+	for _, a := range any {
+		if netstate.IsPublicUnicastIPv4(a) {
+			return true
+		}
+	}
+	return false
+}
diff --git a/runtime/internal/v23_util.go b/runtime/internal/v23_util.go
new file mode 100644
index 0000000..5375851
--- /dev/null
+++ b/runtime/internal/v23_util.go
@@ -0,0 +1,17 @@
+// 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 !mojo
+
+package internal
+
+import (
+	"os"
+
+	"v.io/x/ref/lib/flags"
+)
+
+func parseFlagsInternal(f *flags.Flags, config map[string]string) error {
+	return f.Parse(os.Args[1:], config)
+}
diff --git a/runtime/internal/vtrace/store.go b/runtime/internal/vtrace/store.go
new file mode 100644
index 0000000..55bf928
--- /dev/null
+++ b/runtime/internal/vtrace/store.go
@@ -0,0 +1,283 @@
+// 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 vtrace
+
+import (
+	"math/rand"
+	"regexp"
+	"sync"
+	"time"
+
+	"v.io/v23/uniqueid"
+	"v.io/v23/vtrace"
+
+	"v.io/x/ref/lib/apilog"
+	"v.io/x/ref/lib/flags"
+)
+
+// Store implements a store for traces.  The idea is to keep all the
+// information we have about some subset of traces that pass through
+// the server.  For now we just implement an LRU cache, so the least
+// recently started/finished/annotated traces expire after some
+// maximum trace count is reached.
+// TODO(mattr): LRU is the wrong policy in the long term, we should
+// try to keep some diverse set of traces and allow users to
+// specifically tell us to capture a specific trace.  LRU will work OK
+// for many testing scenarios and low volume applications.
+type Store struct {
+	opts          flags.VtraceFlags
+	collectRegexp *regexp.Regexp
+
+	// traces and head together implement a linked-hash-map.
+	// head points to the head and tail of the doubly-linked-list
+	// of recently used items (the tail is the LRU traceStore).
+	// TODO(mattr): Use rwmutex.
+	mu     sync.Mutex
+	traces map[uniqueid.Id]*traceStore // GUARDED_BY(mu)
+	head   *traceStore                 // GUARDED_BY(mu)
+}
+
+// NewStore creates a new store according to the passed in opts.
+func NewStore(opts flags.VtraceFlags) (*Store, error) {
+	head := &traceStore{}
+	head.next, head.prev = head, head
+
+	var collectRegexp *regexp.Regexp
+	if opts.CollectRegexp != "" {
+		var err error
+		if collectRegexp, err = regexp.Compile(opts.CollectRegexp); err != nil {
+			return nil, err
+		}
+	}
+
+	return &Store{
+		opts:          opts,
+		collectRegexp: collectRegexp,
+		traces:        make(map[uniqueid.Id]*traceStore),
+		head:          head,
+	}, nil
+}
+
+func (s *Store) ForceCollect(id uniqueid.Id) {
+	defer apilog.LogCallf(nil, "id=")(nil, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	s.mu.Lock()
+	s.forceCollectLocked(id)
+	s.mu.Unlock()
+}
+
+func (s *Store) forceCollectLocked(id uniqueid.Id) *traceStore {
+	ts := s.traces[id]
+	if ts == nil {
+		ts = newTraceStore(id)
+		s.traces[id] = ts
+		ts.moveAfter(s.head)
+		// Trim elements beyond our size limit.
+		for len(s.traces) > s.opts.CacheSize {
+			el := s.head.prev
+			el.removeFromList()
+			delete(s.traces, el.id)
+		}
+	}
+	return ts
+}
+
+// Merge merges a vtrace.Response into the current store.
+func (s *Store) Merge(t vtrace.Response) {
+	defer apilog.LogCallf(nil, "t=")(nil, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	s.mu.Lock()
+	defer s.mu.Unlock()
+
+	var ts *traceStore
+	if t.Flags&vtrace.CollectInMemory != 0 {
+		ts = s.forceCollectLocked(t.Trace.Id)
+	} else {
+		ts = s.traces[t.Trace.Id]
+	}
+	if ts != nil {
+		ts.merge(t.Trace.Spans)
+	}
+}
+
+// annotate stores an annotation for the trace if it is being collected.
+func (s *Store) annotate(span *span, msg string) {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+	ts := s.traces[span.trace]
+	if ts == nil {
+		if s.collectRegexp != nil && s.collectRegexp.MatchString(msg) {
+			ts = s.forceCollectLocked(span.trace)
+		}
+	}
+
+	if ts != nil {
+		ts.annotate(span, msg)
+		ts.moveAfter(s.head)
+	}
+}
+
+// start stores data about a starting span if the trace is being collected.
+func (s *Store) start(span *span) {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+
+	ts := s.traces[span.trace]
+	if ts == nil {
+		sr := s.opts.SampleRate
+		if span.trace == span.parent && sr > 0.0 && (sr >= 1.0 || rand.Float64() < sr) {
+			// If this is a root span, we may automatically sample it for collection.
+			ts = s.forceCollectLocked(span.trace)
+		} else if s.collectRegexp != nil && s.collectRegexp.MatchString(span.name) {
+			// If this span matches collectRegexp, then force collect its trace.
+			ts = s.forceCollectLocked(span.trace)
+		}
+	}
+	if ts != nil {
+		ts.start(span)
+		ts.moveAfter(s.head)
+	}
+}
+
+// finish stores data about a finished span if the trace is being collected.
+func (s *Store) finish(span *span) {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+	if ts := s.traces[span.trace]; ts != nil {
+		ts.finish(span)
+		ts.moveAfter(s.head)
+	}
+}
+
+// method returns the collection method for the given trace.
+func (s *Store) flags(id uniqueid.Id) vtrace.TraceFlags {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+	if ts := s.traces[id]; ts != nil {
+		return vtrace.CollectInMemory
+	}
+	return vtrace.Empty
+}
+
+// TraceRecords returns TraceRecords for all traces saved in the store.
+func (s *Store) TraceRecords() []vtrace.TraceRecord {
+	defer apilog.LogCall(nil)(nil) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	s.mu.Lock()
+	defer s.mu.Unlock()
+
+	out := make([]vtrace.TraceRecord, len(s.traces))
+	i := 0
+	for _, ts := range s.traces {
+		ts.traceRecord(&out[i])
+		i++
+	}
+	return out
+}
+
+// TraceRecord returns a TraceRecord for a given Id.  Returns
+// nil if the given id is not present.
+func (s *Store) TraceRecord(id uniqueid.Id) *vtrace.TraceRecord {
+	defer apilog.LogCallf(nil, "id=")(nil, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+	s.mu.Lock()
+	defer s.mu.Unlock()
+	out := &vtrace.TraceRecord{}
+	ts := s.traces[id]
+	if ts != nil {
+		ts.traceRecord(out)
+	}
+	return out
+}
+
+type traceStore struct {
+	id         uniqueid.Id
+	spans      map[uniqueid.Id]*vtrace.SpanRecord
+	prev, next *traceStore
+}
+
+func newTraceStore(id uniqueid.Id) *traceStore {
+	return &traceStore{
+		id:    id,
+		spans: make(map[uniqueid.Id]*vtrace.SpanRecord),
+	}
+}
+
+func (ts *traceStore) record(s *span) *vtrace.SpanRecord {
+	record, ok := ts.spans[s.id]
+	if !ok {
+		record = &vtrace.SpanRecord{
+			Id:     s.id,
+			Parent: s.parent,
+			Name:   s.name,
+			Start:  s.start,
+		}
+		ts.spans[s.id] = record
+	}
+	return record
+}
+
+func (ts *traceStore) annotate(s *span, msg string) {
+	record := ts.record(s)
+	record.Annotations = append(record.Annotations, vtrace.Annotation{
+		When:    time.Now(),
+		Message: msg,
+	})
+}
+
+func (ts *traceStore) start(s *span) {
+	ts.record(s)
+}
+
+func (ts *traceStore) finish(s *span) {
+	ts.record(s).End = time.Now()
+}
+
+func (ts *traceStore) merge(spans []vtrace.SpanRecord) {
+	// TODO(mattr): We need to carefully merge here to correct for
+	// clock skew and ordering.  We should estimate the clock skew
+	// by assuming that children of parent need to start after parent
+	// and end before now.
+	for _, span := range spans {
+		if ts.spans[span.Id] == nil {
+			ts.spans[span.Id] = copySpanRecord(&span)
+		}
+	}
+}
+
+func (ts *traceStore) removeFromList() {
+	if ts.prev != nil {
+		ts.prev.next = ts.next
+	}
+	if ts.next != nil {
+		ts.next.prev = ts.prev
+	}
+	ts.next = nil
+	ts.prev = nil
+}
+
+func (ts *traceStore) moveAfter(prev *traceStore) {
+	ts.removeFromList()
+	ts.prev = prev
+	ts.next = prev.next
+	prev.next.prev = ts
+	prev.next = ts
+}
+
+func copySpanRecord(in *vtrace.SpanRecord) *vtrace.SpanRecord {
+	return &vtrace.SpanRecord{
+		Id:          in.Id,
+		Parent:      in.Parent,
+		Name:        in.Name,
+		Start:       in.Start,
+		End:         in.End,
+		Annotations: append([]vtrace.Annotation{}, in.Annotations...),
+	}
+}
+
+func (ts *traceStore) traceRecord(out *vtrace.TraceRecord) {
+	spans := make([]vtrace.SpanRecord, 0, len(ts.spans))
+	for _, span := range ts.spans {
+		spans = append(spans, *copySpanRecord(span))
+	}
+	out.Id = ts.id
+	out.Spans = spans
+}
diff --git a/runtime/internal/vtrace/store_test.go b/runtime/internal/vtrace/store_test.go
new file mode 100644
index 0000000..3d38700
--- /dev/null
+++ b/runtime/internal/vtrace/store_test.go
@@ -0,0 +1,131 @@
+// 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 vtrace
+
+import (
+	"encoding/binary"
+	"reflect"
+	"sort"
+	"testing"
+
+	"v.io/v23/uniqueid"
+	"v.io/v23/vtrace"
+
+	"v.io/x/ref/lib/flags"
+)
+
+var nextid = uint64(1)
+
+func id() uniqueid.Id {
+	var out uniqueid.Id
+	binary.BigEndian.PutUint64(out[8:], nextid)
+	nextid++
+	return out
+}
+
+func makeTraces(n int, st *Store) []uniqueid.Id {
+	traces := make([]uniqueid.Id, n)
+	for i := range traces {
+		curid := id()
+		traces[i] = curid
+		st.ForceCollect(curid)
+	}
+	return traces
+}
+
+func recordids(records ...vtrace.TraceRecord) map[uniqueid.Id]bool {
+	out := make(map[uniqueid.Id]bool)
+	for _, trace := range records {
+		out[trace.Id] = true
+	}
+	return out
+}
+
+func traceids(traces ...uniqueid.Id) map[uniqueid.Id]bool {
+	out := make(map[uniqueid.Id]bool)
+	for _, trace := range traces {
+		out[trace] = true
+	}
+	return out
+}
+
+func pretty(in map[uniqueid.Id]bool) []int {
+	out := make([]int, 0, len(in))
+	for k, _ := range in {
+		out = append(out, int(k[15]))
+	}
+	sort.Ints(out)
+	return out
+}
+
+func compare(t *testing.T, want map[uniqueid.Id]bool, records []vtrace.TraceRecord) {
+	got := recordids(records...)
+	if !reflect.DeepEqual(want, got) {
+		t.Errorf("Got wrong traces.  Got %v, want %v.", pretty(got), pretty(want))
+	}
+}
+
+func TestTrimming(t *testing.T) {
+	st, err := NewStore(flags.VtraceFlags{CacheSize: 5})
+	if err != nil {
+		t.Fatalf("Could not create store: %v", err)
+	}
+	traces := makeTraces(10, st)
+
+	compare(t, traceids(traces[5:]...), st.TraceRecords())
+
+	traces = append(traces, id(), id(), id())
+
+	// Starting a span on an existing trace brings it to the front of the queue
+	// and prevent it from being removed when a new trace begins.
+	st.start(&span{trace: traces[5], id: id()})
+	st.ForceCollect(traces[10])
+	compare(t, traceids(traces[10], traces[5], traces[7], traces[8], traces[9]), st.TraceRecords())
+
+	// Finishing a span on one of the traces should bring it back into the stored set.
+	st.finish(&span{trace: traces[7], id: id()})
+	st.ForceCollect(traces[11])
+	compare(t, traceids(traces[10], traces[11], traces[5], traces[7], traces[9]), st.TraceRecords())
+
+	// Annotating a span on one of the traces should bring it back into the stored set.
+	st.annotate(&span{trace: traces[9], id: id()}, "hello")
+	st.ForceCollect(traces[12])
+	compare(t, traceids(traces[10], traces[11], traces[12], traces[7], traces[9]), st.TraceRecords())
+}
+
+func TestRegexp(t *testing.T) {
+	traces := []uniqueid.Id{id(), id(), id()}
+
+	type testcase struct {
+		pattern string
+		results []uniqueid.Id
+	}
+	tests := []testcase{
+		{".*", traces},
+		{"foo.*", traces},
+		{".*bar", traces[1:2]},
+		{".*bang", traces[2:3]},
+	}
+
+	for _, test := range tests {
+		st, err := NewStore(flags.VtraceFlags{
+			CacheSize:     10,
+			CollectRegexp: test.pattern,
+		})
+		if err != nil {
+			t.Fatalf("Could not create store: %v", err)
+		}
+
+		newSpan(traces[0], "foo", traces[0], st)
+		newSpan(traces[1], "foobar", traces[1], st)
+		sp, err := newSpan(traces[2], "baz", traces[2], st)
+		if err != nil {
+			t.Fatal(err)
+		}
+		sp.Annotate("foobang")
+
+		compare(t, traceids(test.results...), st.TraceRecords())
+	}
+}
diff --git a/runtime/internal/vtrace/vtrace.go b/runtime/internal/vtrace/vtrace.go
new file mode 100644
index 0000000..11bfd0c
--- /dev/null
+++ b/runtime/internal/vtrace/vtrace.go
@@ -0,0 +1,209 @@
+// 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 vtrace implements the Trace and Span interfaces in v.io/v23/vtrace.
+// We also provide internal utilities for migrating trace information across
+// RPC calls.
+package vtrace
+
+import (
+	"fmt"
+	"time"
+
+	"v.io/v23/context"
+	"v.io/v23/uniqueid"
+	"v.io/v23/vtrace"
+
+	"v.io/x/ref/lib/flags"
+)
+
+// A span represents an annotated period of time.
+type span struct {
+	id     uniqueid.Id
+	parent uniqueid.Id
+	name   string
+	trace  uniqueid.Id
+	start  time.Time
+	store  *Store
+}
+
+func newSpan(parent uniqueid.Id, name string, trace uniqueid.Id, store *Store) (*span, error) {
+	id, err := uniqueid.Random()
+	if err != nil {
+		return nil, fmt.Errorf("vtrace: Couldn't generate Span ID, debug data may be lost: %v", err)
+	}
+	s := &span{
+		id:     id,
+		parent: parent,
+		name:   name,
+		trace:  trace,
+		start:  time.Now(),
+		store:  store,
+	}
+	store.start(s)
+	return s, nil
+}
+
+func (s *span) ID() uniqueid.Id {
+	// nologcall
+	return s.id
+}
+func (s *span) Parent() uniqueid.Id {
+	// nologcall
+	return s.parent
+}
+func (s *span) Name() string {
+	// nologcall
+	return s.name
+}
+func (s *span) Trace() uniqueid.Id {
+	// nologcall
+	return s.trace
+}
+func (s *span) Annotate(msg string) {
+	// nologcall
+	s.store.annotate(s, msg)
+}
+func (s *span) Annotatef(format string, a ...interface{}) {
+	// nologcall
+	s.store.annotate(s, fmt.Sprintf(format, a...))
+}
+func (s *span) Finish() {
+	// nologcall
+	s.store.finish(s)
+}
+func (s *span) flags() vtrace.TraceFlags {
+	return s.store.flags(s.trace)
+}
+
+type contextKey int
+
+const (
+	storeKey = contextKey(iota)
+	spanKey
+)
+
+// Manager allows you to create new traces and spans and access the
+// vtrace store.
+type manager struct{}
+
+// WithNewTrace creates a new vtrace context that is not the child of any
+// other span.  This is useful when starting operations that are
+// disconnected from the activity ctx is performing.  For example
+// this might be used to start background tasks.
+func (m manager) WithNewTrace(ctx *context.T) (*context.T, vtrace.Span) {
+	// nologcall
+	id, err := uniqueid.Random()
+	if err != nil {
+		ctx.Errorf("vtrace: Couldn't generate Trace Id, debug data may be lost: %v", err)
+	}
+	s, err := newSpan(id, "", id, getStore(ctx))
+	if err != nil {
+		ctx.Error(err)
+	}
+
+	return context.WithValue(ctx, spanKey, s), s
+}
+
+// WithContinuedTrace creates a span that represents a continuation of
+// a trace from a remote server.  name is the name of the new span and
+// req contains the parameters needed to connect this span with it's
+// trace.
+func (m manager) WithContinuedTrace(ctx *context.T, name string, req vtrace.Request) (*context.T, vtrace.Span) {
+	// nologcall
+	st := getStore(ctx)
+	if req.Flags&vtrace.CollectInMemory != 0 {
+		st.ForceCollect(req.TraceId)
+	}
+	newSpan, err := newSpan(req.SpanId, name, req.TraceId, st)
+	if err != nil {
+		ctx.Error(err)
+	}
+	return context.WithValue(ctx, spanKey, newSpan), newSpan
+}
+
+// WithNewSpan derives a context with a new Span that can be used to
+// trace and annotate operations across process boundaries.
+func (m manager) WithNewSpan(ctx *context.T, name string) (*context.T, vtrace.Span) {
+	// nologcall
+	if curSpan := getSpan(ctx); curSpan != nil {
+		if curSpan.store == nil {
+			panic("nil store")
+		}
+		s, err := newSpan(curSpan.ID(), name, curSpan.trace, curSpan.store)
+		if err != nil {
+			ctx.Error(err)
+		}
+		return context.WithValue(ctx, spanKey, s), s
+	}
+
+	ctx.Error("vtrace: Creating a new child span from context with no existing span.")
+	return m.WithNewTrace(ctx)
+}
+
+// Span finds the currently active span.
+func (m manager) GetSpan(ctx *context.T) vtrace.Span {
+	// nologcall
+	if span := getSpan(ctx); span != nil {
+		return span
+	}
+	return nil
+}
+
+// Request generates a vtrace.Request from the active Span.
+func (m manager) GetRequest(ctx *context.T) vtrace.Request {
+	// nologcall
+	if span := getSpan(ctx); span != nil {
+		return vtrace.Request{
+			SpanId:  span.id,
+			TraceId: span.trace,
+			Flags:   span.flags(),
+		}
+	}
+	return vtrace.Request{}
+}
+
+// Response captures the vtrace.Response for the active Span.
+func (m manager) GetResponse(ctx *context.T) vtrace.Response {
+	// nologcall
+	if span := getSpan(ctx); span != nil {
+		return vtrace.Response{
+			Flags: span.flags(),
+			Trace: *span.store.TraceRecord(span.trace),
+		}
+	}
+	return vtrace.Response{}
+}
+
+// Store returns the current vtrace.Store.
+func (m manager) GetStore(ctx *context.T) vtrace.Store {
+	// nologcall
+	if store := getStore(ctx); store != nil {
+		return store
+	}
+	return nil
+}
+
+// getSpan returns the internal span type.
+func getSpan(ctx *context.T) *span {
+	span, _ := ctx.Value(spanKey).(*span)
+	return span
+}
+
+// GetStore returns the *Store attached to the context.
+func getStore(ctx *context.T) *Store {
+	store, _ := ctx.Value(storeKey).(*Store)
+	return store
+}
+
+// Init initializes vtrace and attaches some state to the context.
+// This should be called by the runtimes initialization function.
+func Init(ctx *context.T, opts flags.VtraceFlags) (*context.T, error) {
+	nctx := vtrace.WithManager(ctx, manager{})
+	store, err := NewStore(opts)
+	if err != nil {
+		return ctx, err
+	}
+	return context.WithValue(nctx, storeKey, store), nil
+}
diff --git a/runtime/internal/vtrace/vtrace_test.go b/runtime/internal/vtrace/vtrace_test.go
new file mode 100644
index 0000000..4bb4525
--- /dev/null
+++ b/runtime/internal/vtrace/vtrace_test.go
@@ -0,0 +1,371 @@
+// 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 vtrace_test
+
+import (
+	"bytes"
+	"fmt"
+	"strings"
+	"testing"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/options"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/security/access"
+	"v.io/v23/uniqueid"
+	"v.io/v23/vtrace"
+
+	"v.io/x/ref/lib/flags"
+	_ "v.io/x/ref/lib/security/securityflag"
+	"v.io/x/ref/lib/xrpc"
+	_ "v.io/x/ref/runtime/factories/generic"
+	ivtrace "v.io/x/ref/runtime/internal/vtrace"
+	"v.io/x/ref/services/mounttable/mounttablelib"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/testutil"
+)
+
+func init() {
+	test.Init()
+}
+
+// initForTest initializes the vtrace runtime and starts a mounttable.
+func initForTest(t *testing.T) (*context.T, v23.Shutdown, *testutil.IDProvider) {
+	idp := testutil.NewIDProvider("base")
+	ctx, shutdown := test.V23Init()
+
+	if err := idp.Bless(v23.GetPrincipal(ctx), "alice"); err != nil {
+		t.Fatalf("Could not bless initial principal %v", err)
+	}
+	// Start a local mounttable.
+	disp, err := mounttablelib.NewMountTableDispatcher(ctx, "", "", "mounttable")
+	if err != nil {
+		t.Fatalf("Could not create mt dispatcher %v", err)
+	}
+	s, err := xrpc.NewDispatchingServer(ctx, "", disp, options.ServesMountTable(true))
+	if err != nil {
+		t.Fatalf("Could not create mt server %v", err)
+	}
+	v23.GetNamespace(ctx).SetRoots(s.Status().Endpoints[0].Name())
+	return ctx, shutdown, idp
+}
+
+func TestNewFromContext(t *testing.T) {
+	c0, shutdown, _ := initForTest(t)
+	defer shutdown()
+	c1, s1 := vtrace.WithNewSpan(c0, "s1")
+	c2, s2 := vtrace.WithNewSpan(c1, "s2")
+	c3, s3 := vtrace.WithNewSpan(c2, "s3")
+	expected := map[*context.T]vtrace.Span{
+		c1: s1,
+		c2: s2,
+		c3: s3,
+	}
+	for ctx, expectedSpan := range expected {
+		if s := vtrace.GetSpan(ctx); s != expectedSpan {
+			t.Errorf("Wrong span for ctx %v.  Got %v, want %v", c0, s, expectedSpan)
+		}
+	}
+}
+
+// testServer can be easily configured to have child servers of the
+// same type which it will call when it receives a call.
+type testServer struct {
+	name         string
+	child        string
+	stop         func() error
+	forceCollect bool
+}
+
+func (c *testServer) Run(ctx *context.T, call rpc.ServerCall) error {
+	if c.forceCollect {
+		vtrace.ForceCollect(ctx)
+	}
+	vtrace.GetSpan(ctx).Annotate(c.name + "-begin")
+	if c.child != "" {
+		clientCall, err := v23.GetClient(ctx).StartCall(ctx, c.child, "Run", nil)
+		if err != nil {
+			return err
+		}
+		if err := clientCall.Finish(); err != nil {
+			return err
+		}
+	}
+	vtrace.GetSpan(ctx).Annotate(c.name + "-end")
+	return nil
+}
+
+func verifyMount(ctx *context.T, name string) error {
+	ns := v23.GetNamespace(ctx)
+	for {
+		if _, err := ns.Resolve(ctx, name); err == nil {
+			return nil
+		}
+		time.Sleep(10 * time.Millisecond)
+	}
+}
+
+func runCallChain(t *testing.T, ctx *context.T, idp *testutil.IDProvider, force1, force2 bool) *vtrace.TraceRecord {
+	ctx, span := vtrace.WithNewSpan(ctx, "")
+	span.Annotate("c0-begin")
+	_, stop, err := makeChainedTestServers(ctx, idp, force1, force2)
+	if err != nil {
+		t.Fatalf("Could not start servers %v", err)
+	}
+	defer stop()
+	call, err := v23.GetClient(ctx).StartCall(ctx, "c1", "Run", nil)
+	if err != nil {
+		t.Fatal("can't call: ", err)
+	}
+	if err := call.Finish(); err != nil {
+		t.Error(err)
+	}
+	span.Annotate("c0-end")
+	span.Finish()
+
+	return vtrace.GetStore(ctx).TraceRecord(span.Trace())
+}
+
+func makeChainedTestServers(ctx *context.T, idp *testutil.IDProvider, force ...bool) ([]*testServer, func(), error) {
+	out := []*testServer{}
+	last := len(force) - 1
+	ext := "alice"
+	for i, f := range force {
+		name := fmt.Sprintf("c%d", i+1)
+		ext += "/" + name
+		principal := testutil.NewPrincipal()
+		if err := idp.Bless(principal, ext); err != nil {
+			return nil, nil, err
+		}
+		c, err := makeTestServer(ctx, principal, name)
+		if err != nil {
+			return nil, nil, err
+		}
+		if i < last {
+			c.child = fmt.Sprintf("c%d", i+2)
+		}
+		c.forceCollect = f
+		out = append(out, c)
+		// Make sure the server is mounted to avoid any retries in when StartCall
+		// is invoked in runCallChain which complicate the span comparisons.
+		verifyMount(ctx, name)
+	}
+	return out, func() {
+		for _, s := range out {
+			s.stop()
+		}
+	}, nil
+}
+
+func makeTestServer(ctx *context.T, principal security.Principal, name string) (*testServer, error) {
+	// Set a new vtrace store to simulate a separate process.
+	ctx, err := ivtrace.Init(ctx, flags.VtraceFlags{CacheSize: 100})
+	if err != nil {
+		return nil, err
+	}
+	ctx, _ = vtrace.WithNewTrace(ctx)
+	ctx, err = v23.WithPrincipal(ctx, principal)
+	if err != nil {
+		return nil, err
+	}
+	c := &testServer{
+		name: name,
+	}
+	s, err := xrpc.NewServer(ctx, name, c, security.AllowEveryone())
+	if err != nil {
+		return nil, err
+	}
+	c.stop = s.Stop
+	return c, nil
+}
+
+func summary(span *vtrace.SpanRecord) string {
+	summary := span.Name
+	if len(span.Annotations) > 0 {
+		msgs := []string{}
+		for _, annotation := range span.Annotations {
+			msgs = append(msgs, annotation.Message)
+		}
+		summary += ": " + strings.Join(msgs, ", ")
+	}
+	return summary
+}
+
+func traceString(trace *vtrace.TraceRecord) string {
+	var b bytes.Buffer
+	vtrace.FormatTrace(&b, trace, nil)
+	return b.String()
+}
+
+type spanSet map[uniqueid.Id]*vtrace.SpanRecord
+
+func newSpanSet(trace vtrace.TraceRecord) spanSet {
+	out := spanSet{}
+	for i := range trace.Spans {
+		span := &trace.Spans[i]
+		out[span.Id] = span
+	}
+	return out
+}
+
+func (s spanSet) hasAncestor(span *vtrace.SpanRecord, ancestor *vtrace.SpanRecord) bool {
+	for span = s[span.Parent]; span != nil; span = s[span.Parent] {
+		if span == ancestor {
+			return true
+		}
+	}
+	return false
+}
+
+func expectSequence(t *testing.T, trace vtrace.TraceRecord, expectedSpans []string) {
+	s := newSpanSet(trace)
+	found := make(map[string]*vtrace.SpanRecord)
+	for _, es := range expectedSpans {
+		found[es] = nil
+	}
+
+	for i := range trace.Spans {
+		span := &trace.Spans[i]
+		smry := summary(span)
+		if _, ok := found[smry]; ok {
+			found[smry] = span
+		}
+	}
+
+	for i, es := range expectedSpans {
+		span := found[es]
+		if span == nil {
+			t.Errorf("expected span %s not found in\n%s", es, traceString(&trace))
+			continue
+		}
+		// All spans should have a start.
+		if span.Start.IsZero() {
+			t.Errorf("span missing start: %x\n%s", span.Id[12:], traceString(&trace))
+		}
+		// All spans except the root should have a valid end.
+		if span.Parent != trace.Id {
+			if span.End.IsZero() {
+				t.Errorf("span missing end: %x\n%s", span.Id[12:], traceString(&trace))
+			} else if !span.Start.Before(span.End) {
+				t.Errorf("span end should be after start: %x\n%s", span.Id[12:], traceString(&trace))
+			}
+		}
+		// Spans should decend from the previous span in the list.
+		if i == 0 {
+			continue
+		}
+		if ancestor := found[expectedSpans[i-1]]; ancestor != nil && !s.hasAncestor(span, ancestor) {
+			t.Errorf("span %s does not have ancestor %s", es, expectedSpans[i-1])
+		}
+	}
+}
+
+// TestCancellationPropagation tests that cancellation propogates along an
+// RPC call chain without user intervention.
+func TestTraceAcrossRPCs(t *testing.T) {
+	ctx, shutdown, idp := initForTest(t)
+	defer shutdown()
+
+	vtrace.ForceCollect(ctx)
+	record := runCallChain(t, ctx, idp, false, false)
+
+	expectSequence(t, *record, []string{
+		": c0-begin, c0-end",
+		"<rpc.Client>\"c1\".Run",
+		"\"\".Run: c1-begin, c1-end",
+		"<rpc.Client>\"c2\".Run",
+		"\"\".Run: c2-begin, c2-end",
+	})
+}
+
+// TestCancellationPropagationLateForce tests that cancellation propogates along an
+// RPC call chain when tracing is initiated by someone deep in the call chain.
+func TestTraceAcrossRPCsLateForce(t *testing.T) {
+	ctx, shutdown, idp := initForTest(t)
+	defer shutdown()
+
+	record := runCallChain(t, ctx, idp, false, true)
+
+	expectSequence(t, *record, []string{
+		": c0-end",
+		"<rpc.Client>\"c1\".Run",
+		"\"\".Run: c1-end",
+		"<rpc.Client>\"c2\".Run",
+		"\"\".Run: c2-begin, c2-end",
+	})
+}
+
+func traceWithAuth(t *testing.T, ctx *context.T, principal security.Principal) bool {
+	s, err := makeTestServer(ctx, principal, "server")
+	if err != nil {
+		t.Fatalf("Couldn't start server %v", err)
+	}
+	defer s.stop()
+
+	ctx, span := vtrace.WithNewTrace(ctx)
+	vtrace.ForceCollect(ctx)
+
+	ctx, client, err := v23.WithNewClient(ctx)
+	defer client.Close()
+	if err != nil {
+		t.Fatalf("Couldn't create client %v", err)
+	}
+	call, err := client.StartCall(ctx, "server", "Run", nil)
+	if err != nil {
+		t.Fatalf("Couldn't make call %v", err)
+	}
+	if err = call.Finish(); err != nil {
+		t.Fatalf("Couldn't complete call %v", err)
+	}
+	record := vtrace.GetStore(ctx).TraceRecord(span.Trace())
+	for _, sp := range record.Spans {
+		if sp.Name == `"".Run` {
+			return true
+		}
+	}
+	return false
+}
+
+type debugDispatcher string
+
+func (permsDisp debugDispatcher) Lookup(*context.T, string) (interface{}, security.Authorizer, error) {
+	perms, err := access.ReadPermissions(strings.NewReader(string(permsDisp)))
+	if err != nil {
+		return nil, nil, err
+	}
+	auth := access.TypicalTagTypePermissionsAuthorizer(perms)
+	return nil, auth, nil
+}
+
+// TestPermissions tests that only permitted users are allowed to gather tracing
+// information.
+func TestTracePermissions(t *testing.T) {
+	ctx, shutdown, idp := initForTest(t)
+	defer shutdown()
+
+	type testcase struct {
+		perms string
+		spans bool
+	}
+	cases := []testcase{
+		{`{}`, false},
+		{`{"Read":{"In": ["base/alice"]}, "Write":{"In": ["base/alice"]}}`, false},
+		{`{"Debug":{"In": ["base/alice"]}}`, true},
+	}
+
+	// Create a different principal for the server.
+	pserver := testutil.NewPrincipal()
+	idp.Bless(pserver, "server")
+
+	for _, tc := range cases {
+		ctx2 := v23.WithReservedNameDispatcher(ctx, debugDispatcher(tc.perms))
+		if found := traceWithAuth(t, ctx2, pserver); found != tc.spans {
+			t.Errorf("got %v wanted %v for perms %s", found, tc.spans, tc.perms)
+		}
+	}
+}
diff --git a/services/agent/agentd/doc.go b/services/agent/agentd/doc.go
new file mode 100644
index 0000000..22f3860
--- /dev/null
+++ b/services/agent/agentd/doc.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.
+
+// This file was auto-generated via go generate.
+// DO NOT UPDATE MANUALLY
+
+/*
+Command agentd runs the security agent daemon, which holds a private key in
+memory and makes it available to a subprocess.
+
+Loads the private key specified in privatekey.pem in the specified credentials
+directory into memory, then starts the specified command with access to the
+private key via the agent protocol instead of directly reading from disk.
+
+Usage:
+   agentd [flags] command [command_args...]
+
+The command is started as a subprocess with the given [command_args...].
+
+The agentd flags are:
+ -additional-principals=
+   If non-empty, allow for the creation of new principals and save them in this
+   directory.
+ -new-principal-blessing-name=
+   If creating a new principal (--v23.credentials does not exist), then have it
+   blessed with this name.
+ -no-passphrase=false
+   If true, user will not be prompted for principal encryption passphrase.
+ -restart-exit-code=
+   If non-empty, will restart the command when it exits, provided that the
+   command's exit code matches the value of this flag.  The value must be an
+   integer, or an integer preceded by '!' (in which case all exit codes except
+   the flag will trigger a restart).
+ -v23.credentials=
+   The directory containing the (possibly encrypted) credentials to serve.  Must
+   be specified.
+
+The global flags are:
+ -alsologtostderr=true
+   log to standard error as well as files
+ -log_backtrace_at=:0
+   when logging hits line file:N, emit a stack trace
+ -log_dir=
+   if non-empty, write log files to this directory
+ -logtostderr=false
+   log to standard error instead of files
+ -max_stack_buf_size=4292608
+   max size in bytes of the buffer to use for logging stack traces
+ -metadata=<just specify -metadata to activate>
+   Displays metadata for the program and exits.
+ -stderrthreshold=2
+   logs at or above this threshold go to stderr
+ -v=0
+   log level for V logs
+ -vmodule=
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
+*/
+package main
diff --git a/services/agent/agentd/main.go b/services/agent/agentd/main.go
new file mode 100644
index 0000000..dd7a657
--- /dev/null
+++ b/services/agent/agentd/main.go
@@ -0,0 +1,324 @@
+// 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.
+
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go . -help
+
+package main
+
+import (
+	"flag"
+	"fmt"
+	"io"
+	"os"
+	"os/exec"
+	"os/signal"
+	"path/filepath"
+	"strconv"
+	"syscall"
+
+	"golang.org/x/crypto/ssh/terminal"
+
+	"v.io/v23/security"
+	"v.io/v23/verror"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref"
+	"v.io/x/ref/internal/logger"
+	vsecurity "v.io/x/ref/lib/security"
+	vsignals "v.io/x/ref/lib/signals"
+	"v.io/x/ref/services/agent/internal/ipc"
+	"v.io/x/ref/services/agent/internal/lockfile"
+	"v.io/x/ref/services/agent/internal/server"
+)
+
+const childAgentFd = 3
+const pkgPath = "v.io/x/ref/services/agent/agentd"
+const agentSocketName = "agent.sock" // Keep in sync with internal/lockfile/lockfile.go
+
+var (
+	errCantReadPassphrase       = verror.Register(pkgPath+".errCantReadPassphrase", verror.NoRetry, "{1:}{2:} failed to read passphrase{:_}")
+	errNeedPassphrase           = verror.Register(pkgPath+".errNeedPassphrase", verror.NoRetry, "{1:}{2:} Passphrase required for decrypting principal{:_}")
+	errCantParseRestartExitCode = verror.Register(pkgPath+".errCantParseRestartExitCode", verror.NoRetry, "{1:}{2:} Failed to parse restart exit code{:_}")
+
+	keypath, restartExitCode string
+	newname, credentials     string
+	noPassphrase             bool
+)
+
+func main() {
+	cmdAgentD.Flags.StringVar(&keypath, "additional-principals", "", "If non-empty, allow for the creation of new principals and save them in this directory.")
+	cmdAgentD.Flags.BoolVar(&noPassphrase, "no-passphrase", false, "If true, user will not be prompted for principal encryption passphrase.")
+
+	// TODO(caprita): We use the exit code of the child to determine if the
+	// agent should restart it.  Consider changing this to use the unix
+	// socket for this purpose.
+	cmdAgentD.Flags.StringVar(&restartExitCode, "restart-exit-code", "", "If non-empty, will restart the command when it exits, provided that the command's exit code matches the value of this flag.  The value must be an integer, or an integer preceded by '!' (in which case all exit codes except the flag will trigger a restart).")
+
+	cmdAgentD.Flags.StringVar(&newname, "new-principal-blessing-name", "", "If creating a new principal (--v23.credentials does not exist), then have it blessed with this name.")
+
+	cmdAgentD.Flags.StringVar(&credentials, "v23.credentials", "", "The directory containing the (possibly encrypted) credentials to serve.  Must be specified.")
+
+	cmdline.HideGlobalFlagsExcept()
+	cmdline.Main(cmdAgentD)
+}
+
+var cmdAgentD = &cmdline.Command{
+	Runner: cmdline.RunnerFunc(runAgentD),
+	Name:   "agentd",
+	Short:  "Holds a private key in memory and makes it available to a subprocess",
+	Long: `
+Command agentd runs the security agent daemon, which holds a private key in
+memory and makes it available to a subprocess.
+
+Loads the private key specified in privatekey.pem in the specified
+credentials directory into memory, then starts the specified command
+with access to the private key via the agent protocol instead of
+directly reading from disk.
+`,
+	ArgsName: "command [command_args...]",
+	ArgsLong: `
+The command is started as a subprocess with the given [command_args...].
+`,
+}
+
+func runAgentD(env *cmdline.Env, args []string) error {
+	if len(args) < 1 {
+		return env.UsageErrorf("Need at least one argument.")
+	}
+	var restartOpts restartOptions
+	if err := restartOpts.parse(); err != nil {
+		return env.UsageErrorf("%v", err)
+	}
+
+	if len(credentials) == 0 {
+		credentials = os.Getenv(ref.EnvCredentials)
+	}
+	if len(credentials) == 0 {
+		return env.UsageErrorf("The -credentials flag must be specified.")
+	}
+
+	p, passphrase, err := newPrincipalFromDir(credentials)
+	if err != nil {
+		return fmt.Errorf("failed to create new principal from dir(%s): %v", credentials, err)
+	}
+	defer lockfile.RemoveLockfile(credentials)
+
+	if keypath == "" && passphrase != nil {
+		// If we're done with the passphrase, zero it out so it doesn't stay in memory
+		for i := range passphrase {
+			passphrase[i] = 0
+		}
+		passphrase = nil
+	}
+
+	// Start running our server.
+	i := ipc.NewIPC()
+	defer i.Close()
+	if err = server.ServeAgent(i, p); err != nil {
+		return fmt.Errorf("ServeAgent: %v", err)
+	}
+	if keypath != "" {
+		if err = server.ServeKeyManager(i, keypath, passphrase); err != nil {
+			return fmt.Errorf("ServeKeyManager: %v", err)
+		}
+	}
+	path, err := filepath.Abs(filepath.Join(credentials, agentSocketName))
+	if err != nil {
+		return fmt.Errorf("abs: %v", err)
+	}
+	path = filepath.Clean(path)
+	if err = os.Setenv(ref.EnvAgentPath, path); err != nil {
+		return fmt.Errorf("setenv: %v", err)
+	}
+	if err = i.Listen(path); err != nil {
+		return err
+	}
+
+	// Clear out the environment variable before starting the child.
+	if err = ref.EnvClearCredentials(); err != nil {
+		return fmt.Errorf("ref.EnvClearCredentials: %v", err)
+	}
+
+	exitCode := 0
+	for {
+		// Run the client and wait for it to finish.
+		cmd := exec.Command(flag.Args()[0], flag.Args()[1:]...)
+		cmd.Stdin = env.Stdin
+		cmd.Stdout = env.Stdout
+		cmd.Stderr = env.Stderr
+
+		err = cmd.Start()
+		if err != nil {
+			return fmt.Errorf("Error starting child: %v", err)
+		}
+		shutdown := make(chan struct{})
+		go func() {
+			select {
+			case sig := <-vsignals.ShutdownOnSignals(nil):
+				// TODO(caprita): Should we also relay double
+				// signal to the child?  That currently just
+				// force exits the current process.
+				if sig == vsignals.STOP {
+					sig = syscall.SIGTERM
+				}
+				cmd.Process.Signal(sig)
+			case <-shutdown:
+			}
+		}()
+		cmd.Wait()
+		close(shutdown)
+		exitCode = cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus()
+		if !restartOpts.restart(exitCode) {
+			break
+		}
+	}
+	if exitCode != 0 {
+		return cmdline.ErrExitCode(exitCode)
+	}
+	return nil
+}
+
+func newPrincipalFromDir(dir string) (p security.Principal, pass []byte, err error) {
+	defer func() {
+		if err == nil {
+			err = lockfile.CreateLockfile(dir)
+		}
+	}()
+	p, err = vsecurity.LoadPersistentPrincipal(dir, nil)
+	if os.IsNotExist(err) {
+		return handleDoesNotExist(dir)
+	}
+	if verror.ErrorID(err) == vsecurity.ErrBadPassphrase.ID {
+		return handlePassphrase(dir)
+	}
+	return p, nil, err
+}
+
+func handleDoesNotExist(dir string) (security.Principal, []byte, error) {
+	fmt.Println("Private key file does not exist. Creating new private key...")
+	var pass []byte
+	if !noPassphrase {
+		var err error
+		if pass, err = getPassword("Enter passphrase (entering nothing will store unencrypted): "); err != nil {
+			return nil, nil, verror.New(errCantReadPassphrase, nil, err)
+		}
+	}
+	p, err := vsecurity.CreatePersistentPrincipal(dir, pass)
+	if err != nil {
+		return nil, pass, err
+	}
+	name := newname
+	if len(name) == 0 {
+		name = "agent_principal"
+	}
+	vsecurity.InitDefaultBlessings(p, name)
+	return p, pass, nil
+}
+
+func handlePassphrase(dir string) (security.Principal, []byte, error) {
+	if noPassphrase {
+		return nil, nil, verror.New(errNeedPassphrase, nil)
+	}
+	pass, err := getPassword("Private key file is encrypted. Please enter passphrase.\nEnter passphrase: ")
+	if err != nil {
+		return nil, nil, verror.New(errCantReadPassphrase, nil, err)
+	}
+	p, err := vsecurity.LoadPersistentPrincipal(dir, pass)
+	return p, pass, err
+}
+
+func getPassword(prompt string) ([]byte, error) {
+	if !terminal.IsTerminal(int(os.Stdin.Fd())) {
+		// If the standard input is not a terminal, the password is obtained by reading a line from it.
+		return readPassword()
+	}
+	fmt.Printf(prompt)
+	stop := make(chan bool)
+	defer close(stop)
+	state, err := terminal.GetState(int(os.Stdin.Fd()))
+	if err != nil {
+		return nil, err
+	}
+	go catchTerminationSignals(stop, state)
+	defer fmt.Printf("\n")
+	return terminal.ReadPassword(int(os.Stdin.Fd()))
+}
+
+// readPassword reads form Stdin until it sees '\n' or EOF.
+func readPassword() ([]byte, error) {
+	var pass []byte
+	var total int
+	for {
+		b := make([]byte, 1)
+		count, err := os.Stdin.Read(b)
+		if err != nil && err != io.EOF {
+			return nil, err
+		}
+		if err == io.EOF || b[0] == '\n' {
+			return pass[:total], nil
+		}
+		total += count
+		pass = secureAppend(pass, b)
+	}
+}
+
+func secureAppend(s, t []byte) []byte {
+	res := append(s, t...)
+	if len(res) > cap(s) {
+		// When append needs to allocate a new array, clear out the old one.
+		for i := range s {
+			s[i] = '0'
+		}
+	}
+	// Clear out the second array.
+	for i := range t {
+		t[i] = '0'
+	}
+	return res
+}
+
+// catchTerminationSignals catches signals to allow us to turn terminal echo back on.
+func catchTerminationSignals(stop <-chan bool, state *terminal.State) {
+	var successErrno syscall.Errno
+	sig := make(chan os.Signal, 4)
+	// Catch the blockable termination signals.
+	signal.Notify(sig, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGHUP)
+	select {
+	case <-sig:
+		// Start on new line in terminal.
+		fmt.Printf("\n")
+		if err := terminal.Restore(int(os.Stdin.Fd()), state); err != successErrno {
+			logger.Global().Errorf("Failed to restore terminal state (%v), you words may not show up when you type, enter 'stty echo' to fix this.", err)
+		}
+		os.Exit(-1)
+	case <-stop:
+		signal.Stop(sig)
+	}
+}
+
+type restartOptions struct {
+	enabled, unless bool
+	code            int
+}
+
+func (opts *restartOptions) parse() error {
+	code := restartExitCode
+	if code == "" {
+		return nil
+	}
+	opts.enabled = true
+	if code[0] == '!' {
+		opts.unless = true
+		code = code[1:]
+	}
+	var err error
+	if opts.code, err = strconv.Atoi(code); err != nil {
+		return verror.New(errCantParseRestartExitCode, nil, err)
+	}
+	return nil
+}
+
+func (opts *restartOptions) restart(exitCode int) bool {
+	return opts.enabled && opts.unless != (exitCode == opts.code)
+}
diff --git a/services/agent/agentlib/agent_test.go b/services/agent/agentlib/agent_test.go
new file mode 100644
index 0000000..03d07fa
--- /dev/null
+++ b/services/agent/agentlib/agent_test.go
@@ -0,0 +1,315 @@
+// 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 agentlib_test
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"reflect"
+	"testing"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/security"
+	"v.io/x/ref/services/agent/agentlib"
+	"v.io/x/ref/services/agent/internal/ipc"
+	"v.io/x/ref/services/agent/internal/server"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
+	"v.io/x/ref/test/testutil"
+
+	_ "v.io/x/ref/runtime/factories/generic"
+)
+
+// As of April 28, 2015, the benchmarks for serving a principal with and
+// without the agent are as follows:
+//
+// BenchmarkSignNoAgent                    :  889608 ns/op
+// BenchmarkSignCachedAgent                : 6961410 ns/op
+// BenchmarkSignUncachedAgent	           : 7403763 ns/op
+// BenchmarkDefaultNoAgent	           :     139 ns/op
+// BenchmarkDefaultCachedAgent	           :      41 ns/op
+// BenchmarkDefaultUncachedAgent	   : 9732978 ns/op
+// BenchmarkRecognizedNegativeNoAgent	   :   34859 ns/op
+// BenchmarkRecognizedNegativeCachedAgent  :   31043 ns/op
+// BenchmarkRecognizedNegativeUncachedAgent: 5110308 ns/op
+// BenchmarkRecognizedNoAgent	           :   13457 ns/op
+// BenchmarkRecognizedCachedAgent	   :   12609 ns/op
+// BenchmarkRecognizedUncachedAgent	   : 4232959 ns/op
+
+//go:generate v23 test generate
+
+var getPrincipalAndHang = modules.Register(func(env *modules.Env, args ...string) error {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	p := v23.GetPrincipal(ctx)
+	fmt.Fprintf(env.Stdout, "DEFAULT_BLESSING=%s\n", p.BlessingStore().Default())
+	ioutil.ReadAll(env.Stdin)
+	return nil
+}, "getPrincipalAndHang")
+
+func newAgent(path string, cached bool) (security.Principal, error) {
+	if cached {
+		return agentlib.NewAgentPrincipalX(path)
+	} else {
+		return agentlib.NewUncachedPrincipalX(path)
+	}
+}
+
+func setupAgentPair(t *testing.T, p security.Principal) (security.Principal, security.Principal, func()) {
+	i := ipc.NewIPC()
+	if err := server.ServeAgent(i, p); err != nil {
+		t.Fatal(err)
+	}
+	dir, err := ioutil.TempDir("", "agent")
+	if err != nil {
+		t.Fatal(err)
+	}
+	sock := filepath.Join(dir, "sock")
+	defer os.RemoveAll(dir)
+	if err := i.Listen(sock); err != nil {
+		t.Fatal(err)
+	}
+	agent1, err := newAgent(sock, true)
+	if err != nil {
+		t.Fatal(err)
+	}
+	agent2, err := newAgent(sock, true)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	return agent1, agent2, i.Close
+}
+
+func setupAgent(caching bool) (security.Principal, func()) {
+	p := testutil.NewPrincipal("agentTest")
+	i := ipc.NewIPC()
+	if err := server.ServeAgent(i, p); err != nil {
+		panic(err)
+	}
+	dir, err := ioutil.TempDir("", "agent")
+	if err != nil {
+		panic(err)
+	}
+	sock := filepath.Join(dir, "sock")
+	defer os.RemoveAll(dir)
+	if err := i.Listen(sock); err != nil {
+		panic(err)
+	}
+
+	agent, err := newAgent(sock, caching)
+	if err != nil {
+		panic(err)
+	}
+	return agent, i.Close
+}
+
+func TestAgent(t *testing.T) {
+
+	var (
+		p                       = testutil.NewPrincipal("agentTest")
+		agent1, agent2, cleanup = setupAgentPair(t, p)
+	)
+	defer cleanup()
+
+	defP, def1, def2 := p.BlessingStore().Default(), agent1.BlessingStore().Default(), agent2.BlessingStore().Default()
+
+	if !reflect.DeepEqual(defP, def1) {
+		t.Errorf("Default blessing mismatch. Wanted %v, got %v", defP, def1)
+	}
+	if !reflect.DeepEqual(defP, def2) {
+		t.Errorf("Default blessing mismatch. Wanted %v, got %v", defP, def2)
+	}
+
+	// Check that we're caching:
+	// Modify the principal directly and the client's shouldn't notic.
+	blessing, err := p.BlessSelf("alice")
+	if err != nil {
+		t.Fatal(err)
+	}
+	if err = p.BlessingStore().SetDefault(blessing); err != nil {
+		t.Fatal(err)
+	}
+	def1, def2 = agent1.BlessingStore().Default(), agent2.BlessingStore().Default()
+	if !reflect.DeepEqual(defP, def1) {
+		t.Errorf("Default blessing mismatch. Wanted %v, got %v", defP, def1)
+	}
+	if !reflect.DeepEqual(defP, def2) {
+		t.Errorf("Default blessing mismatch. Wanted %v, got %v", defP, def2)
+	}
+
+	// Now make a change through the client.
+	blessing, err = agent1.BlessSelf("john")
+	if err != nil {
+		t.Fatal(err)
+	}
+	if err = agent2.BlessingStore().SetDefault(blessing); err != nil {
+		t.Fatal(err)
+	}
+	// The principal and the other client should both reflect the change.
+	newDefault := p.BlessingStore().Default()
+	if !reflect.DeepEqual(newDefault, blessing) {
+		t.Errorf("Default blessing mismatch. Wanted %v, got %v", blessing, newDefault)
+	}
+	// There's no synchronization, so keep fetching from the other client.
+	// Eventually it should get notified of the new value.
+	for i := 0; i < 10000 && !reflect.DeepEqual(blessing, agent1.BlessingStore().Default()); i += 1 {
+		time.Sleep(100 * time.Millisecond)
+	}
+
+	if !reflect.DeepEqual(agent1.BlessingStore().Default(), blessing) {
+		t.Errorf("Default blessing mismatch. Wanted %v, got %v", blessing, agent1.BlessingStore().Default())
+	}
+}
+
+func TestAgentShutdown(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+
+	// This starts an agent
+	sh, err := modules.NewShell(ctx, nil, testing.Verbose(), t)
+	if err != nil {
+		t.Fatal(err)
+	}
+	// The child process will connect to the agent
+	h, err := sh.Start(nil, getPrincipalAndHang)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	fmt.Fprintf(os.Stderr, "reading var...\n")
+	h.ExpectVar("DEFAULT_BLESSING")
+	fmt.Fprintf(os.Stderr, "read\n")
+	if err := h.Error(); err != nil {
+		t.Fatalf("failed to read input: %s", err)
+	}
+	fmt.Fprintf(os.Stderr, "shutting down...\n")
+	// This should not hang
+	shutdown()
+	fmt.Fprintf(os.Stderr, "shut down\n")
+
+	fmt.Fprintf(os.Stderr, "cleanup...\n")
+	sh.Cleanup(os.Stdout, os.Stderr)
+	fmt.Fprintf(os.Stderr, "cleanup done\n")
+}
+
+var message = []byte("bugs bunny")
+
+func runSignBenchmark(b *testing.B, p security.Principal) {
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		if _, err := p.Sign(message); err != nil {
+			b.Fatal(err)
+		}
+	}
+}
+
+func runDefaultBenchmark(b *testing.B, p security.Principal) {
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		if d := p.BlessingStore().Default(); d.IsZero() {
+			b.Fatal("empty blessings")
+		}
+	}
+}
+
+func runRecognizedNegativeBenchmark(b *testing.B, p security.Principal) {
+	key := p.PublicKey()
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		if d := p.Roots().Recognized(key, "foobar"); d == nil {
+			b.Fatal("nil")
+		}
+	}
+}
+
+func runRecognizedBenchmark(b *testing.B, p security.Principal) {
+	key := p.PublicKey()
+	blessing, err := p.BlessSelf("foobar")
+	if err != nil {
+		b.Fatal(err)
+	}
+	err = p.AddToRoots(blessing)
+	if err != nil {
+		b.Fatal(err)
+	}
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		if d := p.Roots().Recognized(key, "foobar"); d != nil {
+			b.Fatal(d)
+		}
+	}
+}
+
+func BenchmarkSignNoAgent(b *testing.B) {
+	p := testutil.NewPrincipal("agentTest")
+	runSignBenchmark(b, p)
+}
+
+func BenchmarkSignCachedAgent(b *testing.B) {
+	p, cleanup := setupAgent(true)
+	defer cleanup()
+	runSignBenchmark(b, p)
+}
+
+func BenchmarkSignUncachedAgent(b *testing.B) {
+	p, cleanup := setupAgent(false)
+	defer cleanup()
+	runSignBenchmark(b, p)
+}
+
+func BenchmarkDefaultNoAgent(b *testing.B) {
+	p := testutil.NewPrincipal("agentTest")
+	runDefaultBenchmark(b, p)
+}
+
+func BenchmarkDefaultCachedAgent(b *testing.B) {
+	p, cleanup := setupAgent(true)
+	defer cleanup()
+	runDefaultBenchmark(b, p)
+}
+
+func BenchmarkDefaultUncachedAgent(b *testing.B) {
+	p, cleanup := setupAgent(false)
+	defer cleanup()
+	runDefaultBenchmark(b, p)
+}
+
+func BenchmarkRecognizedNegativeNoAgent(b *testing.B) {
+	p := testutil.NewPrincipal("agentTest")
+	runRecognizedNegativeBenchmark(b, p)
+}
+
+func BenchmarkRecognizedNegativeCachedAgent(b *testing.B) {
+	p, cleanup := setupAgent(true)
+	defer cleanup()
+	runRecognizedNegativeBenchmark(b, p)
+}
+
+func BenchmarkRecognizedNegativeUncachedAgent(b *testing.B) {
+	p, cleanup := setupAgent(false)
+	defer cleanup()
+	runRecognizedNegativeBenchmark(b, p)
+}
+
+func BenchmarkRecognizedNoAgent(b *testing.B) {
+	p := testutil.NewPrincipal("agentTest")
+	runRecognizedBenchmark(b, p)
+}
+
+func BenchmarkRecognizedCachedAgent(b *testing.B) {
+	p, cleanup := setupAgent(true)
+	defer cleanup()
+	runRecognizedBenchmark(b, p)
+}
+
+func BenchmarkRecognizedUncachedAgent(b *testing.B) {
+	p, cleanup := setupAgent(false)
+	defer cleanup()
+	runRecognizedBenchmark(b, p)
+}
diff --git a/services/agent/agentlib/agent_v23_test.go b/services/agent/agentlib/agent_v23_test.go
new file mode 100644
index 0000000..8b84787
--- /dev/null
+++ b/services/agent/agentlib/agent_v23_test.go
@@ -0,0 +1,265 @@
+// 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 agentlib_test
+
+import (
+	"bytes"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strings"
+	"text/template"
+
+	"v.io/v23/security"
+	"v.io/x/ref"
+	vsecurity "v.io/x/ref/lib/security"
+	"v.io/x/ref/test/v23tests"
+)
+
+//go:generate v23 test generate
+
+func V23TestTestPassPhraseUse(i *v23tests.T) {
+	bin := i.BuildGoPkg("v.io/x/ref/services/agent/agentd").WithEnv(ref.EnvCredentials + "=" + i.NewTempDir(""))
+
+	// Create the passphrase
+	agent := bin.Start("echo", "Hello")
+	fmt.Fprintln(agent.Stdin(), "PASSWORD")
+	agent.ReadLine() // Skip over ...creating new key... message
+	agent.Expect("Hello")
+	agent.ExpectEOF()
+	if err := agent.Error(); err != nil {
+		i.Fatal(err)
+	}
+
+	// Use it successfully
+	agent = bin.Start("echo", "Hello")
+	fmt.Fprintln(agent.Stdin(), "PASSWORD")
+	agent.Expect("Hello")
+	agent.ExpectEOF()
+	if err := agent.Error(); err != nil {
+		i.Fatal(err)
+	}
+
+	// Provide a bad password
+	agent = bin.Start("echo", "Hello")
+	fmt.Fprintln(agent.Stdin(), "BADPASSWORD")
+	agent.ExpectEOF()
+	var stdout, stderr bytes.Buffer
+	err := agent.Wait(&stdout, &stderr)
+	if err == nil {
+		i.Fatalf("expected an error.STDOUT:%v\nSTDERR:%v\n", stdout.String(), stderr.String())
+	}
+	if got, want := err.Error(), "exit status 1"; got != want {
+		i.Errorf("Got %q, want %q", got, want)
+	}
+	if got, want := stderr.String(), "passphrase incorrect for decrypting private key"; !strings.Contains(got, want) {
+		i.Errorf("Got %q, wanted it to contain %q", got, want)
+	}
+	if err := agent.Error(); err != nil {
+		i.Error(err)
+	}
+}
+
+func V23TestAllPrincipalMethods(i *v23tests.T) {
+	// Test all methods of the principal interface.
+	// (Errors are printed to STDERR)
+	testbin := i.BuildGoPkg("v.io/x/ref/services/agent/internal/test_principal").Path()
+	i.BuildGoPkg("v.io/x/ref/services/agent/agentd").
+		WithEnv(ref.EnvCredentials+"="+i.NewTempDir("")).
+		Start(testbin).
+		WaitOrDie(nil, os.Stderr)
+}
+
+func V23TestAgentProcesses(i *v23tests.T) {
+	// Setup two principals: One for the agent that runs the pingpong
+	// server, one for the client.  Since the server uses the default
+	// authorization policy, the client must have a blessing delegated from
+	// the server.
+	var (
+		clientAgent, serverAgent = createClientAndServerAgents(i)
+		pingpong                 = i.BuildGoPkg("v.io/x/ref/services/agent/internal/pingpong").Path()
+		serverName               = serverAgent.Start(pingpong).ExpectVar("NAME")
+	)
+	// Run the client via an agent once.
+	client := clientAgent.Start(pingpong, serverName)
+	client.Expect("Pinging...")
+	client.Expect("pong (client:[pingpongd/client] server:[pingpongd])")
+	client.WaitOrDie(os.Stdout, os.Stderr)
+	if err := client.Error(); err != nil { // Check expectations
+		i.Fatal(err)
+	}
+
+	// Run it through a shell to test that the agent can pass credentials
+	// to subprocess of a shell (making things like "agentd bash" provide
+	// useful terminals).
+	// This only works with shells that propagate open file descriptors to
+	// children. POSIX-compliant shells do this as to many other commonly
+	// used ones like bash.
+	script := filepath.Join(i.NewTempDir(""), "test.sh")
+	if err := writeScript(
+		script,
+		`#!/bin/bash
+echo "Running client"
+{{.Bin}} {{.Server}} || exit 101
+echo "Running client again"
+{{.Bin}} {{.Server}} || exit 102
+`,
+		struct{ Bin, Server string }{pingpong, serverName}); err != nil {
+		i.Fatal(err)
+	}
+	client = clientAgent.Start("bash", script)
+	client.Expect("Running client")
+	client.Expect("Pinging...")
+	client.Expect("pong (client:[pingpongd/client] server:[pingpongd])")
+	client.Expect("Running client again")
+	client.Expect("Pinging...")
+	client.Expect("pong (client:[pingpongd/client] server:[pingpongd])")
+	client.WaitOrDie(os.Stdout, os.Stderr)
+	if err := client.Error(); err != nil { // Check expectations
+		i.Fatal(err)
+	}
+}
+
+func V23TestAgentRestartExitCode(i *v23tests.T) {
+	var (
+		clientAgent, serverAgent = createClientAndServerAgents(i)
+		pingpong                 = i.BuildGoPkg("v.io/x/ref/services/agent/internal/pingpong").Path()
+		serverName               = serverAgent.Start(pingpong).ExpectVar("NAME")
+
+		scriptDir = i.NewTempDir("")
+		counter   = filepath.Join(scriptDir, "counter")
+		script    = filepath.Join(scriptDir, "test.sh")
+	)
+	if err := writeScript(
+		script,
+		`#!/bin/bash
+# Execute the client ping once
+{{.Bin}} {{.Server}} || exit 101
+# Increment the contents of the counter file
+readonly COUNT=$(expr $(<"{{.Counter}}") + 1)
+echo -n $COUNT >{{.Counter}}
+# Exit code is 0 if the counter is less than 5
+[[ $COUNT -lt 5 ]]; exit $?
+`,
+		struct{ Bin, Server, Counter string }{
+			Bin:     pingpong,
+			Server:  serverName,
+			Counter: counter,
+		}); err != nil {
+		i.Fatal(err)
+	}
+
+	tests := []struct {
+		RestartExitCode string
+		WantError       string
+		WantCounter     string
+	}{
+		{
+			// With --restart-exit-code=0, the script should be kicked off
+			// 5 times till the counter reaches 5 and the last iteration of
+			// the script will exit.
+			RestartExitCode: "0",
+			WantError:       "exit status 1",
+			WantCounter:     "5",
+		},
+		{
+			// With --restart-exit-code=!0, the script will be executed only once.
+			RestartExitCode: "!0",
+			WantError:       "",
+			WantCounter:     "1",
+		},
+		{
+			// --restart-exit-code=!1, should be the same
+			// as --restart-exit-code=0 for this
+			// particular script only exits with 0 or 1
+			RestartExitCode: "!1",
+			WantError:       "exit status 1",
+			WantCounter:     "5",
+		},
+	}
+	for _, test := range tests {
+		// Clear out the counter file.
+		if err := ioutil.WriteFile(counter, []byte("0"), 0644); err != nil {
+			i.Fatalf("%q: %v", counter, err)
+		}
+		// Run the script under the agent
+		var gotError string
+		if err := clientAgent.Start(
+			"--restart-exit-code="+test.RestartExitCode,
+			"bash", "-c", script).
+			Wait(os.Stdout, os.Stderr); err != nil {
+			gotError = err.Error()
+		}
+		if got, want := gotError, test.WantError; got != want {
+			i.Errorf("%+v: Got %q, want %q", test, got, want)
+		}
+		if buf, err := ioutil.ReadFile(counter); err != nil {
+			i.Errorf("ioutil.ReadFile(%q) failed: %v", counter, err)
+		} else if got, want := string(buf), test.WantCounter; got != want {
+			i.Errorf("%+v: Got %q, want %q", test, got, want)
+		}
+	}
+}
+
+func writeScript(dstfile, tmpl string, args interface{}) error {
+	t, err := template.New(dstfile).Parse(tmpl)
+	if err != nil {
+		return err
+	}
+	var buf bytes.Buffer
+	if err := t.Execute(&buf, args); err != nil {
+		return err
+	}
+	if err := ioutil.WriteFile(dstfile, buf.Bytes(), 0700); err != nil {
+		return err
+	}
+	return nil
+}
+
+// createClientAndServerAgents creates two principals, sets up their
+// blessings and returns the agent binaries that will use the created credentials.
+//
+// The server will have a single blessing "pingpongd".
+// The client will have a single blessing "pingpongd/client", blessed by the server.
+func createClientAndServerAgents(i *v23tests.T) (client, server *v23tests.Binary) {
+	var (
+		agentd    = i.BuildGoPkg("v.io/x/ref/services/agent/agentd")
+		clientDir = i.NewTempDir("")
+		serverDir = i.NewTempDir("")
+	)
+	pserver, err := vsecurity.CreatePersistentPrincipal(serverDir, nil)
+	if err != nil {
+		i.Fatal(err)
+	}
+	pclient, err := vsecurity.CreatePersistentPrincipal(clientDir, nil)
+	if err != nil {
+		i.Fatal(err)
+	}
+	// Server will only serve, not make any client requests, so only needs a default blessing.
+	bserver, err := pserver.BlessSelf("pingpongd")
+	if err != nil {
+		i.Fatal(err)
+	}
+	if err := pserver.BlessingStore().SetDefault(bserver); err != nil {
+		i.Fatal(err)
+	}
+	// Clients need not have a default blessing as they will only make a request to the server.
+	bclient, err := pserver.Bless(pclient.PublicKey(), bserver, "client", security.UnconstrainedUse())
+	if err != nil {
+		i.Fatal(err)
+	}
+	if _, err := pclient.BlessingStore().Set(bclient, "pingpongd"); err != nil {
+		i.Fatal(err)
+	}
+	// The client and server must both recognize bserver and its delegates.
+	if err := pserver.AddToRoots(bserver); err != nil {
+		i.Fatal(err)
+	}
+	if err := pclient.AddToRoots(bserver); err != nil {
+		i.Fatal(err)
+	}
+	return agentd.WithEnv(ref.EnvCredentials + "=" + clientDir), agentd.WithEnv(ref.EnvCredentials + "=" + serverDir)
+}
diff --git a/services/agent/agentlib/client.go b/services/agent/agentlib/client.go
new file mode 100644
index 0000000..1d72439
--- /dev/null
+++ b/services/agent/agentlib/client.go
@@ -0,0 +1,419 @@
+// 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 agentlib implements a client for communicating with an agentd process
+// holding the private key for an identity.
+package agentlib
+
+import (
+	"fmt"
+	"io"
+	"net"
+	"os"
+	"strconv"
+	"sync"
+	"syscall"
+
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/options"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/verror"
+	"v.io/v23/vtrace"
+	"v.io/x/ref/internal/logger"
+	"v.io/x/ref/services/agent"
+	"v.io/x/ref/services/agent/internal/cache"
+	"v.io/x/ref/services/agent/internal/ipc"
+	"v.io/x/ref/services/agent/internal/unixfd"
+)
+
+const pkgPath = "v.io/x/ref/services/agent/agentlib"
+
+// Errors
+var (
+	errInvalidProtocol = verror.Register(pkgPath+".errInvalidProtocol",
+		verror.NoRetry, "{1:}{2:} invalid agent protocol {3}")
+)
+
+type client struct {
+	caller caller
+	key    security.PublicKey
+}
+
+type caller interface {
+	call(name string, results []interface{}, args ...interface{}) error
+	io.Closer
+}
+
+type ipcCaller struct {
+	conn  *ipc.IPCConn
+	flush func()
+	mu    sync.Mutex
+}
+
+func (i *ipcCaller) call(name string, results []interface{}, args ...interface{}) error {
+	return i.conn.Call(name, args, results...)
+}
+
+func (i *ipcCaller) Close() error {
+	i.conn.Close()
+	return nil
+}
+
+func (i *ipcCaller) FlushAllCaches() error {
+	var flush func()
+	i.mu.Lock()
+	flush = i.flush
+	i.mu.Unlock()
+	if flush != nil {
+		flush()
+	}
+	return nil
+}
+
+type vrpcCaller struct {
+	ctx    *context.T
+	client rpc.Client
+	name   string
+	cancel func()
+}
+
+func (c *vrpcCaller) Close() error {
+	c.cancel()
+	return nil
+}
+
+func (c *vrpcCaller) call(name string, results []interface{}, args ...interface{}) error {
+	call, err := c.startCall(name, args...)
+	if err != nil {
+		return err
+	}
+	if err := call.Finish(results...); err != nil {
+		return err
+	}
+	return nil
+}
+
+func (c *vrpcCaller) startCall(name string, args ...interface{}) (rpc.ClientCall, error) {
+	ctx, _ := vtrace.WithNewTrace(c.ctx)
+	// SecurityNone is safe here since we're using anonymous unix sockets.
+	return c.client.StartCall(ctx, c.name, name, args, options.SecurityNone, options.NoResolve{})
+}
+
+func results(inputs ...interface{}) []interface{} {
+	return inputs
+}
+
+func newUncachedPrincipalX(path string) (*client, error) {
+	caller := new(ipcCaller)
+	i := ipc.NewIPC()
+	i.Serve(caller)
+	conn, err := i.Connect(path)
+	if err != nil {
+		return nil, err
+	}
+	caller.conn = conn
+	agent := &client{caller: caller}
+	if err := agent.fetchPublicKey(); err != nil {
+		return nil, err
+	}
+	return agent, nil
+}
+
+// NewAgentPrincipal returns a security.Pricipal using the PrivateKey held in a remote agent process.
+// 'path' is the path to the agent socket, typically obtained from
+// os.GetEnv(envvar.AgentAddress).
+func NewAgentPrincipalX(path string) (agent.Principal, error) {
+	p, err := newUncachedPrincipalX(path)
+	if err != nil {
+		return nil, err
+	}
+	cached, flush, err := cache.NewCachedPrincipalX(p)
+	if err != nil {
+		return nil, err
+	}
+	caller := p.caller.(*ipcCaller)
+	caller.mu.Lock()
+	caller.flush = flush
+	caller.mu.Unlock()
+	return cached, nil
+}
+
+// NewAgentPrincipal returns a security.Pricipal using the PrivateKey held in a remote agent process.
+// 'endpoint' is the endpoint for connecting to the agent, typically obtained from
+// os.GetEnv(envvar.AgentEndpoint).
+// 'ctx' should not have a deadline, and should never be cancelled while the
+// principal is in use.
+func NewAgentPrincipal(ctx *context.T, endpoint naming.Endpoint, insecureClient rpc.Client) (security.Principal, error) {
+	p, err := newUncachedPrincipal(ctx, endpoint, insecureClient)
+	if err != nil {
+		return p, err
+	}
+	caller := p.caller.(*vrpcCaller)
+	call, callErr := caller.startCall("NotifyWhenChanged")
+	if callErr != nil {
+		return nil, callErr
+	}
+	return cache.NewCachedPrincipal(caller.ctx, p, call)
+}
+func newUncachedPrincipal(ctx *context.T, ep naming.Endpoint, insecureClient rpc.Client) (*client, error) {
+	// This isn't a real vanadium endpoint. It contains the vanadium version
+	// info, but the address is serving the agent protocol.
+	if ep.Addr().Network() != "" {
+		return nil, verror.New(errInvalidProtocol, ctx, ep.Addr().Network())
+	}
+	fd, err := strconv.Atoi(ep.Addr().String())
+	if err != nil {
+		return nil, err
+	}
+	syscall.ForkLock.Lock()
+	fd, err = syscall.Dup(fd)
+	if err == nil {
+		syscall.CloseOnExec(fd)
+	}
+	syscall.ForkLock.Unlock()
+	if err != nil {
+		return nil, err
+	}
+	f := os.NewFile(uintptr(fd), "agent_client")
+	defer f.Close()
+	conn, err := net.FileConn(f)
+	if err != nil {
+		return nil, err
+	}
+	// This is just an arbitrary 1 byte string. The value is ignored.
+	data := make([]byte, 1)
+	addr, err := unixfd.SendConnection(conn.(*net.UnixConn), data)
+	if err != nil {
+		return nil, err
+	}
+	ctx, cancel := context.WithCancel(ctx)
+	caller := &vrpcCaller{
+		client: insecureClient,
+		name:   naming.JoinAddressName(agentEndpoint("unixfd", addr.String()), ""),
+		ctx:    ctx,
+		cancel: cancel,
+	}
+	agent := &client{caller: caller}
+	if err := agent.fetchPublicKey(); err != nil {
+		return nil, err
+	}
+	return agent, nil
+}
+
+func (c *client) Close() error {
+	return c.caller.Close()
+}
+
+func (c *client) fetchPublicKey() (err error) {
+	var b []byte
+	if err = c.caller.call("PublicKey", results(&b)); err != nil {
+		return
+	}
+	c.key, err = security.UnmarshalPublicKey(b)
+	return
+}
+
+func (c *client) Bless(key security.PublicKey, with security.Blessings, extension string, caveat security.Caveat, additionalCaveats ...security.Caveat) (security.Blessings, error) {
+	var blessings security.Blessings
+	marshalledKey, err := key.MarshalBinary()
+	if err != nil {
+		return security.Blessings{}, err
+	}
+	err = c.caller.call("Bless", results(&blessings), marshalledKey, with, extension, caveat, additionalCaveats)
+	return blessings, err
+}
+
+func (c *client) BlessSelf(name string, caveats ...security.Caveat) (security.Blessings, error) {
+	var blessings security.Blessings
+	err := c.caller.call("BlessSelf", results(&blessings), name, caveats)
+	return blessings, err
+}
+
+func (c *client) Sign(message []byte) (sig security.Signature, err error) {
+	err = c.caller.call("Sign", results(&sig), message)
+	return
+}
+
+func (c *client) MintDischarge(forCaveat, caveatOnDischarge security.Caveat, additionalCaveatsOnDischarge ...security.Caveat) (security.Discharge, error) {
+	var discharge security.Discharge
+	if err := c.caller.call("MintDischarge", results(&discharge), forCaveat, caveatOnDischarge, additionalCaveatsOnDischarge); err != nil {
+		return security.Discharge{}, err
+	}
+	return discharge, nil
+}
+
+func (c *client) PublicKey() security.PublicKey {
+	return c.key
+}
+
+func (c *client) BlessingsByName(pattern security.BlessingPattern) []security.Blessings {
+	var blessings []security.Blessings
+	if err := c.caller.call("BlessingsByName", results(&blessings), pattern); err != nil {
+		logger.Global().Infof("error calling BlessingsByName: %v", err)
+		return nil
+	}
+	return blessings
+}
+
+func (c *client) BlessingsInfo(blessings security.Blessings) map[string][]security.Caveat {
+	var bInfo map[string][]security.Caveat
+	err := c.caller.call("BlessingsInfo", results(&bInfo), blessings)
+	if err != nil {
+		logger.Global().Infof("error calling BlessingsInfo: %v", err)
+		return nil
+	}
+	return bInfo
+}
+
+func (c *client) BlessingStore() security.BlessingStore {
+	return &blessingStore{caller: c.caller, key: c.key}
+}
+
+func (c *client) Roots() security.BlessingRoots {
+	return &blessingRoots{c.caller}
+}
+
+func (c *client) AddToRoots(blessings security.Blessings) error {
+	return c.caller.call("AddToRoots", results(), blessings)
+}
+
+type blessingStore struct {
+	caller caller
+	key    security.PublicKey
+}
+
+func (b *blessingStore) Set(blessings security.Blessings, forPeers security.BlessingPattern) (security.Blessings, error) {
+	var previous security.Blessings
+	err := b.caller.call("BlessingStoreSet", results(&previous), blessings, forPeers)
+	return previous, err
+}
+
+func (b *blessingStore) ForPeer(peerBlessings ...string) security.Blessings {
+	var blessings security.Blessings
+	if err := b.caller.call("BlessingStoreForPeer", results(&blessings), peerBlessings); err != nil {
+		logger.Global().Infof("error calling BlessingStorePeerBlessings: %v", err)
+	}
+	return blessings
+}
+
+func (b *blessingStore) SetDefault(blessings security.Blessings) error {
+	return b.caller.call("BlessingStoreSetDefault", results(), blessings)
+}
+
+func (b *blessingStore) Default() security.Blessings {
+	var blessings security.Blessings
+	err := b.caller.call("BlessingStoreDefault", results(&blessings))
+	if err != nil {
+		logger.Global().Infof("error calling BlessingStoreDefault: %v", err)
+		return security.Blessings{}
+	}
+	return blessings
+}
+
+func (b *blessingStore) PublicKey() security.PublicKey {
+	return b.key
+}
+
+func (b *blessingStore) PeerBlessings() map[security.BlessingPattern]security.Blessings {
+	var bmap map[security.BlessingPattern]security.Blessings
+	err := b.caller.call("BlessingStorePeerBlessings", results(&bmap))
+	if err != nil {
+		logger.Global().Infof("error calling BlessingStorePeerBlessings: %v", err)
+		return nil
+	}
+	return bmap
+}
+
+func (b *blessingStore) DebugString() (s string) {
+	err := b.caller.call("BlessingStoreDebugString", results(&s))
+	if err != nil {
+		s = fmt.Sprintf("error calling BlessingStoreDebugString: %v", err)
+		logger.Global().Infof(s)
+	}
+	return
+}
+
+func (b *blessingStore) CacheDischarge(d security.Discharge, c security.Caveat, i security.DischargeImpetus) {
+	err := b.caller.call("BlessingStoreCacheDischarge", results(), d, c, i)
+	if err != nil {
+		logger.Global().Infof("error calling BlessingStoreCacheDischarge: %v", err)
+	}
+}
+
+func (b *blessingStore) ClearDischarges(discharges ...security.Discharge) {
+	err := b.caller.call("BlessingStoreClearDischarges", results(), discharges)
+	if err != nil {
+		logger.Global().Infof("error calling BlessingStoreClearDischarges: %v", err)
+	}
+}
+
+func (b *blessingStore) Discharge(caveat security.Caveat, impetus security.DischargeImpetus) (out security.Discharge) {
+	err := b.caller.call("BlessingStoreDischarge", results(&out), caveat, impetus)
+	if err != nil {
+		logger.Global().Infof("error calling BlessingStoreDischarge: %v", err)
+	}
+	return
+}
+
+type blessingRoots struct {
+	caller caller
+}
+
+func (b *blessingRoots) Add(root security.PublicKey, pattern security.BlessingPattern) error {
+	marshalledKey, err := root.MarshalBinary()
+	if err != nil {
+		return err
+	}
+	return b.caller.call("BlessingRootsAdd", results(), marshalledKey, pattern)
+}
+
+func (b *blessingRoots) Recognized(root security.PublicKey, blessing string) error {
+	marshalledKey, err := root.MarshalBinary()
+	if err != nil {
+		return err
+	}
+	return b.caller.call("BlessingRootsRecognized", results(), marshalledKey, blessing)
+}
+
+func (b *blessingRoots) Dump() map[security.BlessingPattern][]security.PublicKey {
+	var marshaledRoots map[security.BlessingPattern][][]byte
+	if err := b.caller.call("BlessingRootsDump", results(&marshaledRoots)); err != nil {
+		logger.Global().Infof("error calling BlessingRootsDump: %v", err)
+		return nil
+	}
+	ret := make(map[security.BlessingPattern][]security.PublicKey)
+	for p, marshaledKeys := range marshaledRoots {
+		for _, marshaledKey := range marshaledKeys {
+			key, err := security.UnmarshalPublicKey(marshaledKey)
+			if err != nil {
+				logger.Global().Infof("security.UnmarshalPublicKey(%v) returned error: %v", marshaledKey, err)
+				continue
+			}
+			ret[p] = append(ret[p], key)
+		}
+	}
+	return ret
+}
+
+func (b *blessingRoots) DebugString() (s string) {
+	err := b.caller.call("BlessingRootsDebugString", results(&s))
+	if err != nil {
+		s = fmt.Sprintf("error calling BlessingRootsDebugString: %v", err)
+		logger.Global().Infof(s)
+	}
+	return
+}
+
+func agentEndpoint(proto, addr string) string {
+	// TODO: use naming.FormatEndpoint when it supports version 5.
+	return fmt.Sprintf("@5@%s@%s@@s@@@", proto, addr)
+}
+
+func AgentEndpoint(fd int) string {
+	// We use an empty protocol here because this isn't really speaking
+	// veyron rpc.
+	return agentEndpoint("", fmt.Sprintf("%d", fd))
+}
diff --git a/services/agent/agentlib/peer_test.go b/services/agent/agentlib/peer_test.go
new file mode 100644
index 0000000..3d5ceeb
--- /dev/null
+++ b/services/agent/agentlib/peer_test.go
@@ -0,0 +1,20 @@
+// 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 agentlib
+
+import (
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+)
+
+func NewUncachedPrincipal(ctx *context.T, endpoint naming.Endpoint, insecureClient rpc.Client) (security.Principal, error) {
+	return newUncachedPrincipal(ctx, endpoint, insecureClient)
+}
+
+func NewUncachedPrincipalX(path string) (security.Principal, error) {
+	return newUncachedPrincipalX(path)
+}
diff --git a/services/agent/agentlib/v23_test.go b/services/agent/agentlib/v23_test.go
new file mode 100644
index 0000000..334393a
--- /dev/null
+++ b/services/agent/agentlib/v23_test.go
@@ -0,0 +1,42 @@
+// 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 agentlib_test
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
+	"v.io/x/ref/test/v23tests"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	modules.DispatchAndExitIfChild()
+	cleanup := v23tests.UseSharedBinDir()
+	r := m.Run()
+	cleanup()
+	os.Exit(r)
+}
+
+func TestV23TestPassPhraseUse(t *testing.T) {
+	v23tests.RunTest(t, V23TestTestPassPhraseUse)
+}
+
+func TestV23AllPrincipalMethods(t *testing.T) {
+	v23tests.RunTest(t, V23TestAllPrincipalMethods)
+}
+
+func TestV23AgentProcesses(t *testing.T) {
+	v23tests.RunTest(t, V23TestAgentProcesses)
+}
+
+func TestV23AgentRestartExitCode(t *testing.T) {
+	v23tests.RunTest(t, V23TestAgentRestartExitCode)
+}
diff --git a/services/agent/internal/cache/cache.go b/services/agent/internal/cache/cache.go
new file mode 100644
index 0000000..f4500a8
--- /dev/null
+++ b/services/agent/internal/cache/cache.go
@@ -0,0 +1,439 @@
+// 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 cache
+
+import (
+	"fmt"
+	"sync"
+
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/verror"
+	"v.io/x/ref/internal/logger"
+	"v.io/x/ref/services/agent"
+	"v.io/x/ref/services/agent/internal/lru"
+)
+
+const pkgPath = "v.io/x/ref/services/agent/internal/cache"
+
+var (
+	errNotImplemented = verror.Register(pkgPath+".errNotImplemented", verror.NoRetry, "{1:}{2:} Not implemented{:_}")
+)
+
+const (
+	maxNegativeCacheEntries = 100
+)
+
+// cachedRoots is a security.BlessingRoots implementation that
+// wraps over another implementation and adds caching.
+type cachedRoots struct {
+	mu    *sync.RWMutex
+	impl  security.BlessingRoots
+	cache map[string][]security.BlessingPattern // GUARDED_BY(mu)
+
+	// TODO(ataly): Get rid of the following fields once all agents have been
+	// updated to support the 'Dump' method.
+	dumpExists bool       // GUARDED_BY(my)
+	negative   *lru.Cache // key + blessing -> error
+}
+
+func newCachedRoots(impl security.BlessingRoots, mu *sync.RWMutex) (*cachedRoots, error) {
+	roots := &cachedRoots{mu: mu, impl: impl}
+	roots.flush()
+	if err := roots.fetchAndCacheRoots(); err != nil {
+		return nil, err
+	}
+	return roots, nil
+}
+
+func (r *cachedRoots) Add(root security.PublicKey, pattern security.BlessingPattern) error {
+	cacheKey, err := keyToString(root)
+	if err != nil {
+		return err
+	}
+
+	defer r.mu.Unlock()
+	r.mu.Lock()
+
+	if err := r.impl.Add(root, pattern); err != nil {
+		return err
+	}
+
+	if r.cache != nil {
+
+		r.cache[cacheKey] = append(r.cache[cacheKey], pattern)
+	}
+	return nil
+}
+
+func (r *cachedRoots) Recognized(root security.PublicKey, blessing string) (result error) {
+	key, err := keyToString(root)
+	if err != nil {
+		return err
+	}
+
+	r.mu.RLock()
+	var cacheExists bool
+	if r.cache != nil {
+		err = r.recognizeFromCache(key, root, blessing)
+		cacheExists = true
+	}
+	r.mu.RUnlock()
+
+	if !cacheExists {
+		r.mu.Lock()
+		if err := r.fetchAndCacheRoots(); err != nil {
+			r.mu.Unlock()
+			return err
+		}
+		err = r.recognizeFromCache(key, root, blessing)
+		r.mu.Unlock()
+	}
+
+	// TODO(ataly): Get rid of the following block once all agents have been updated
+	// to support the 'Dump' method.
+	r.mu.RLock()
+	if !r.dumpExists && err != nil {
+		negKey := key + blessing
+		negErr, ok := r.negative.Get(negKey)
+		if !ok {
+			r.mu.RUnlock()
+			return r.recognizeFromImpl(key, root, blessing)
+		}
+		r.negative.Put(negKey, err)
+		err = negErr.(error)
+	}
+	r.mu.RUnlock()
+
+	return err
+}
+
+func (r *cachedRoots) Dump() map[security.BlessingPattern][]security.PublicKey {
+	var (
+		cacheExists bool
+		dump        map[security.BlessingPattern][]security.PublicKey
+	)
+
+	r.mu.RLock()
+	if r.cache != nil {
+		cacheExists = true
+		dump = r.dumpFromCache()
+	}
+	r.mu.RUnlock()
+
+	if !cacheExists {
+		r.mu.Lock()
+		if err := r.fetchAndCacheRoots(); err != nil {
+			logger.Global().Errorf("failed to cache roots: %v", err)
+			r.mu.Unlock()
+			return nil
+		}
+		dump = r.dumpFromCache()
+		r.mu.Unlock()
+	}
+	return dump
+}
+
+func (r *cachedRoots) DebugString() string {
+	return r.impl.DebugString()
+}
+
+// Must be called while holding mu.
+func (r *cachedRoots) fetchAndCacheRoots() error {
+	dump := r.impl.Dump()
+	r.cache = make(map[string][]security.BlessingPattern)
+	if dump == nil {
+		r.dumpExists = false
+		return nil
+	}
+
+	for p, keys := range dump {
+		for _, key := range keys {
+			cacheKey, err := keyToString(key)
+			if err != nil {
+				return err
+			}
+			r.cache[cacheKey] = append(r.cache[cacheKey], p)
+		}
+	}
+	r.dumpExists = true
+	return nil
+}
+
+// Must be called while holding mu.
+func (r *cachedRoots) flush() {
+	r.cache = nil
+	r.negative = lru.New(maxNegativeCacheEntries)
+}
+
+// Must be called while holding mu.
+func (r *cachedRoots) dumpFromCache() map[security.BlessingPattern][]security.PublicKey {
+	if !r.dumpExists {
+		return nil
+	}
+	dump := make(map[security.BlessingPattern][]security.PublicKey)
+	for keyStr, patterns := range r.cache {
+		key, err := security.UnmarshalPublicKey([]byte(keyStr))
+		if err != nil {
+			logger.Global().Errorf("security.UnmarshalPublicKey(%v) returned error: %v", []byte(keyStr), err)
+			return nil
+		}
+		for _, p := range patterns {
+			dump[p] = append(dump[p], key)
+		}
+	}
+	return dump
+}
+
+// Must be called while holding mu.
+func (r *cachedRoots) recognizeFromCache(key string, root security.PublicKey, blessing string) error {
+	for _, p := range r.cache[key] {
+		if p.MatchedBy(blessing) {
+			return nil
+		}
+	}
+	return security.NewErrUnrecognizedRoot(nil, root.String(), nil)
+}
+
+// TODO(ataly): Get rid of this method once all agents have been updated
+// to support the 'Dump' method.
+func (r *cachedRoots) recognizeFromImpl(key string, root security.PublicKey, blessing string) error {
+	negKey := key + blessing
+	err := r.impl.Recognized(root, blessing)
+
+	r.mu.Lock()
+	if err == nil {
+		r.cache[key] = append(r.cache[key], security.BlessingPattern(blessing))
+	} else {
+		r.negative.Put(negKey, err)
+	}
+	r.mu.Unlock()
+	return err
+}
+
+// cachedStore is a security.BlessingStore implementation that
+// wraps over another implementation and adds caching.
+type cachedStore struct {
+	mu     *sync.RWMutex
+	key    security.PublicKey
+	def    security.Blessings
+	hasDef bool
+	peers  map[security.BlessingPattern]security.Blessings
+	impl   security.BlessingStore
+}
+
+func (s *cachedStore) Default() (result security.Blessings) {
+	s.mu.RLock()
+	if !s.hasDef {
+		s.mu.RUnlock()
+		return s.fetchAndCacheDefault()
+	}
+	result = s.def
+	s.mu.RUnlock()
+	return
+}
+
+func (s *cachedStore) SetDefault(blessings security.Blessings) error {
+	defer s.mu.Unlock()
+	s.mu.Lock()
+	err := s.impl.SetDefault(blessings)
+	if err != nil {
+		// We're not sure what happened, so we need to re-read the default.
+		s.hasDef = false
+		return err
+	}
+	s.def = blessings
+	s.hasDef = true
+	return nil
+}
+
+func (s *cachedStore) fetchAndCacheDefault() security.Blessings {
+	result := s.impl.Default()
+	s.mu.Lock()
+	s.def = result
+	s.hasDef = true
+	s.mu.Unlock()
+	return result
+}
+
+func (s *cachedStore) ForPeer(peerBlessings ...string) security.Blessings {
+	var ret security.Blessings
+	for pat, b := range s.PeerBlessings() {
+		if pat.MatchedBy(peerBlessings...) {
+			if union, err := security.UnionOfBlessings(ret, b); err == nil {
+				ret = union
+			} else {
+				logger.Global().Errorf("UnionOfBlessings(%v, %v) failed: %v, dropping the latter from BlessingStore.ForPeers(%v)", ret, b, err, peerBlessings)
+			}
+		}
+	}
+	return ret
+}
+
+func (s *cachedStore) PeerBlessings() map[security.BlessingPattern]security.Blessings {
+	s.mu.RLock()
+	ret := s.peers
+	s.mu.RUnlock()
+	if ret != nil {
+		return ret
+	}
+	return s.fetchAndCacheBlessings()
+}
+
+func (s *cachedStore) Set(blessings security.Blessings, forPeers security.BlessingPattern) (security.Blessings, error) {
+	defer s.mu.Unlock()
+	s.mu.Lock()
+	oldBlessings, err := s.impl.Set(blessings, forPeers)
+	if err == nil && s.peers != nil {
+		s.peers[forPeers] = blessings
+	}
+	return oldBlessings, err
+}
+
+func (s *cachedStore) fetchAndCacheBlessings() map[security.BlessingPattern]security.Blessings {
+	ret := s.impl.PeerBlessings()
+	s.mu.Lock()
+	s.peers = ret
+	s.mu.Unlock()
+	return ret
+}
+
+func (s *cachedStore) PublicKey() security.PublicKey {
+	return s.key
+}
+
+func (s *cachedStore) DebugString() string {
+	return s.impl.DebugString()
+}
+
+func (s *cachedStore) String() string {
+	return fmt.Sprintf("cached[%s]", s.impl)
+}
+
+func (s *cachedStore) CacheDischarge(d security.Discharge, c security.Caveat, i security.DischargeImpetus) {
+	s.mu.Lock()
+	s.impl.CacheDischarge(d, c, i)
+	s.mu.Unlock()
+}
+
+func (s *cachedStore) ClearDischarges(discharges ...security.Discharge) {
+	s.mu.Lock()
+	s.impl.ClearDischarges(discharges...)
+	s.mu.Unlock()
+}
+
+func (s *cachedStore) Discharge(caveat security.Caveat, impetus security.DischargeImpetus) security.Discharge {
+	defer s.mu.Unlock()
+	s.mu.Lock()
+	return s.impl.Discharge(caveat, impetus)
+}
+
+// Must be called while holding mu.
+func (s *cachedStore) flush() {
+	s.hasDef = false
+	s.peers = nil
+}
+
+// cachedPrincipal is a security.Principal implementation that
+// wraps over another implementation and adds caching.
+type cachedPrincipal struct {
+	cache security.Principal
+	/* impl */ agent.Principal
+}
+
+func (p *cachedPrincipal) BlessingsByName(pattern security.BlessingPattern) []security.Blessings {
+	return p.cache.BlessingsByName(pattern)
+}
+
+func (p *cachedPrincipal) BlessingsInfo(blessings security.Blessings) map[string][]security.Caveat {
+	return p.cache.BlessingsInfo(blessings)
+}
+
+func (p *cachedPrincipal) BlessingStore() security.BlessingStore {
+	return p.cache.BlessingStore()
+}
+
+func (p *cachedPrincipal) Roots() security.BlessingRoots {
+	return p.cache.Roots()
+}
+
+func (p *cachedPrincipal) AddToRoots(blessings security.Blessings) error {
+	return p.cache.AddToRoots(blessings)
+}
+
+func (p *cachedPrincipal) Close() error {
+	return p.Principal.Close()
+}
+
+type dummySigner struct {
+	key security.PublicKey
+}
+
+func (s dummySigner) Sign(purpose, message []byte) (security.Signature, error) {
+	var sig security.Signature
+	return sig, verror.New(errNotImplemented, nil)
+}
+
+func (s dummySigner) PublicKey() security.PublicKey {
+	return s.key
+}
+
+func NewCachedPrincipal(ctx *context.T, impl agent.Principal, call rpc.ClientCall) (p agent.Principal, err error) {
+	p, flush, err := NewCachedPrincipalX(impl)
+
+	if err == nil {
+		go func() {
+			var x bool
+			for {
+				if recvErr := call.Recv(&x); recvErr != nil {
+					if ctx.Err() != context.Canceled {
+						logger.Global().Errorf("Error from agent: %v", recvErr)
+					}
+					flush()
+					call.Finish()
+					return
+				}
+				flush()
+			}
+		}()
+	}
+
+	return
+}
+
+func NewCachedPrincipalX(impl agent.Principal) (p agent.Principal, flush func(), err error) {
+	var mu sync.RWMutex
+	cachedRoots, err := newCachedRoots(impl.Roots(), &mu)
+	if err != nil {
+		return
+	}
+	cachedStore := &cachedStore{
+		mu:   &mu,
+		key:  impl.PublicKey(),
+		impl: impl.BlessingStore(),
+	}
+	flush = func() {
+		defer mu.Unlock()
+		mu.Lock()
+		cachedRoots.flush()
+		cachedStore.flush()
+	}
+	sp, err := security.CreatePrincipal(dummySigner{impl.PublicKey()}, cachedStore, cachedRoots)
+	if err != nil {
+		return
+	}
+
+	p = &cachedPrincipal{sp, impl}
+	return
+}
+
+func keyToString(key security.PublicKey) (string, error) {
+	bytes, err := key.MarshalBinary()
+	if err != nil {
+		return "", err
+	}
+	return string(bytes), nil
+}
diff --git a/services/agent/internal/cache/cache_test.go b/services/agent/internal/cache/cache_test.go
new file mode 100644
index 0000000..e0c2193
--- /dev/null
+++ b/services/agent/internal/cache/cache_test.go
@@ -0,0 +1,344 @@
+// 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 cache
+
+import (
+	"fmt"
+	"reflect"
+	"sync"
+	"testing"
+
+	"v.io/v23/context"
+	"v.io/v23/security"
+	"v.io/x/ref/internal/logger"
+	"v.io/x/ref/test/testutil"
+)
+
+func createRoots() (security.PublicKey, security.BlessingRoots, *cachedRoots) {
+	var mu sync.RWMutex
+	ctx, _ := context.RootContext()
+	ctx = context.WithLogger(ctx, logger.Global())
+	p := testutil.NewPrincipal()
+	impl := p.Roots()
+	roots, err := newCachedRoots(impl, &mu)
+	if err != nil {
+		panic(err)
+	}
+	return p.PublicKey(), impl, roots
+}
+
+func TestCreateRoots(t *testing.T) {
+	_, impl, cache := createRoots()
+	if impl == security.BlessingRoots(cache) {
+		t.Fatalf("Same roots")
+	}
+	if impl == nil || cache == nil {
+		t.Fatalf("No roots %v %v", impl, cache)
+	}
+}
+
+func expectRecognized(roots security.BlessingRoots, key security.PublicKey, blessing string) string {
+	err := roots.Recognized(key, blessing)
+	if err != nil {
+		return fmt.Sprintf("Key (%s, %v) not matched by roots:\n%s, Recognized returns error: %v", key, blessing, roots.DebugString(), err)
+	}
+	return ""
+}
+
+func expectNotRecognized(roots security.BlessingRoots, key security.PublicKey, blessing string) string {
+	err := roots.Recognized(key, blessing)
+	if err == nil {
+		return fmt.Sprintf("Key (%s, %s) should not match roots:\n%s", key, blessing, roots.DebugString())
+	}
+	return ""
+}
+
+func TestAddRoots(t *testing.T) {
+	key, impl, cache := createRoots()
+	if s := expectNotRecognized(impl, key, "alice"); s != "" {
+		t.Error(s)
+	}
+	if s := expectNotRecognized(cache, key, "alice"); s != "" {
+		t.Error(s)
+	}
+
+	if err := cache.Add(key, "alice/$"); err != nil {
+		t.Fatalf("Add failed: %v", err)
+	}
+	if err := cache.Add(key, "bob"); err != nil {
+		t.Fatalf("Add failed: %v", err)
+	}
+	if s := expectRecognized(impl, key, "alice"); s != "" {
+		t.Error(s)
+	}
+	if s := expectRecognized(impl, key, "bob"); s != "" {
+		t.Error(s)
+	}
+	if s := expectNotRecognized(impl, key, "alice/friend"); s != "" {
+		t.Error(s)
+	}
+	if s := expectRecognized(impl, key, "bob/friend"); s != "" {
+		t.Error(s)
+	}
+	if s := expectRecognized(cache, key, "alice"); s != "" {
+		t.Error(s)
+	}
+	if s := expectRecognized(cache, key, "bob"); s != "" {
+		t.Error(s)
+	}
+	if s := expectNotRecognized(cache, key, "alice/friend"); s != "" {
+		t.Error(s)
+	}
+	if s := expectRecognized(cache, key, "bob/friend"); s != "" {
+		t.Error(s)
+	}
+
+	if s := expectNotRecognized(impl, key, "carol"); s != "" {
+		t.Error(s)
+	}
+	if s := expectNotRecognized(cache, key, "carol"); s != "" {
+		t.Error(s)
+	}
+}
+
+func TestNegativeCache(t *testing.T) {
+	key, impl, cache := createRoots()
+
+	if s := expectNotRecognized(cache, key, "alice"); s != "" {
+		t.Error(s)
+	}
+
+	if err := impl.Add(key, "alice"); err != nil {
+		t.Fatalf("Add failed: %v", err)
+	}
+
+	// Should return the cached error.
+	if s := expectNotRecognized(cache, key, "alice"); s != "" {
+		t.Error(s)
+	}
+
+	// Until we flush...
+	cache.flush()
+	if s := expectRecognized(cache, key, "alice"); s != "" {
+		t.Error(s)
+	}
+}
+
+func TestRootsDebugString(t *testing.T) {
+	key, impl, cache := createRoots()
+
+	if err := impl.Add(key, "alice/friend"); err != nil {
+		t.Fatalf("Add failed: %v", err)
+	}
+
+	if a, b := impl.DebugString(), cache.DebugString(); a != b {
+		t.Errorf("DebugString doesn't match. Expected:\n%s\nGot:\n%s", a, b)
+	}
+}
+
+func TestRootsDump(t *testing.T) {
+	key, impl, cache := createRoots()
+
+	if err := cache.Add(key, "alice/friend"); err != nil {
+		t.Fatalf("Add failed: %v", err)
+	}
+
+	orig := impl.Dump()
+	if got := cache.Dump(); !reflect.DeepEqual(orig, got) {
+		t.Errorf("Dump() got %v, want %v", got, orig)
+	}
+
+	impl.Add(key, "carol")
+	if got := cache.Dump(); !reflect.DeepEqual(orig, got) {
+		t.Errorf("Dump() got %v, want %v", got, orig)
+	}
+
+	cache.flush()
+	if cur, got := impl.Dump(), cache.Dump(); !reflect.DeepEqual(cur, got) {
+		t.Errorf("Dump() got %v, want %v", got, cur)
+	}
+}
+
+func createStore(p security.Principal) (security.BlessingStore, *cachedStore) {
+	var mu sync.RWMutex
+	impl := p.BlessingStore()
+	return impl, &cachedStore{mu: &mu, key: p.PublicKey(), impl: impl}
+}
+
+func TestDefaultBlessing(t *testing.T) {
+	p := testutil.NewPrincipal("bob")
+	store, cache := createStore(p)
+
+	bob := store.Default()
+	if cached := cache.Default(); !reflect.DeepEqual(bob, cached) {
+		t.Errorf("Default(): got: %v, want: %v", cached, bob)
+	}
+
+	alice, err := p.BlessSelf("alice")
+	if err != nil {
+		t.Fatalf("BlessSelf failed: %v", err)
+	}
+	err = store.SetDefault(alice)
+	if err != nil {
+		t.Fatalf("SetDefault failed: %v", err)
+	}
+
+	if cached := cache.Default(); !reflect.DeepEqual(bob, cached) {
+		t.Errorf("Default(): got: %v, want: %v", cached, bob)
+	}
+
+	cache.flush()
+	if cached := cache.Default(); !reflect.DeepEqual(alice, cached) {
+		t.Errorf("Default(): got: %v, want: %v", cached, alice)
+	}
+
+	carol, err := p.BlessSelf("carol")
+	if err != nil {
+		t.Fatalf("BlessSelf failed: %v", err)
+	}
+	err = cache.SetDefault(carol)
+	if err != nil {
+		t.Fatalf("SetDefault failed: %v", err)
+	}
+
+	if cur := store.Default(); !reflect.DeepEqual(carol, cur) {
+		t.Errorf("Default(): got: %v, want: %v", cur, carol)
+	}
+	if cached := cache.Default(); !reflect.DeepEqual(carol, cached) {
+		t.Errorf("Default(): got: %v, want: %v", cached, carol)
+	}
+
+	john := testutil.NewPrincipal("john")
+	if nil == cache.SetDefault(john.BlessingStore().Default()) {
+		t.Errorf("Expected error setting default with bad key.")
+	}
+	if cached := cache.Default(); !reflect.DeepEqual(carol, cached) {
+		t.Errorf("Default(): got: %v, want: %v", cached, carol)
+	}
+
+}
+
+func TestSet(t *testing.T) {
+	p := testutil.NewPrincipal("bob")
+	store, cache := createStore(p)
+	var noBlessings security.Blessings
+
+	bob := store.Default()
+	alice, err := p.BlessSelf("alice")
+	if err != nil {
+		t.Fatalf("BlessSelf failed: %v", err)
+	}
+	john := testutil.NewPrincipal("john").BlessingStore().Default()
+
+	store.Set(noBlessings, "...")
+	if _, err := cache.Set(bob, "bob"); err != nil {
+		t.Errorf("Set() failed: %v", err)
+	}
+
+	if got := cache.ForPeer("bob/server"); !reflect.DeepEqual(bob, got) {
+		t.Errorf("ForPeer(bob/server) got: %v, want: %v", got, bob)
+	}
+
+	blessings, err := cache.Set(noBlessings, "bob")
+	if err != nil {
+		t.Errorf("Set() failed: %v", err)
+	}
+	if !reflect.DeepEqual(bob, blessings) {
+		t.Errorf("Previous blessings %v, wanted %v", blessings, bob)
+	}
+	if got, want := cache.ForPeer("bob/server"), (security.Blessings{}); !reflect.DeepEqual(want, got) {
+		t.Errorf("ForPeer(bob/server) got: %v, want: %v", got, want)
+	}
+
+	blessings, err = cache.Set(john, "john")
+	if err == nil {
+		t.Errorf("No error from set")
+	}
+	if got := cache.ForPeer("john/server"); got.PublicKey() != nil {
+		t.Errorf("ForPeer(john/server) got: %v, want: %v", got, nil)
+	}
+
+	blessings, err = cache.Set(bob, "...")
+	if err != nil {
+		t.Errorf("Set() failed: %v", err)
+	}
+	blessings, err = cache.Set(alice, "bob")
+	if err != nil {
+		t.Errorf("Set() failed: %v", err)
+	}
+
+	expected, err := security.UnionOfBlessings(bob, alice)
+	if err != nil {
+		t.Errorf("UnionOfBlessings failed: %v", err)
+	}
+	if got := cache.ForPeer("bob/server"); !reflect.DeepEqual(expected, got) {
+		t.Errorf("ForPeer(bob/server) got: %v, want: %v", got, expected)
+	}
+}
+
+func TestForPeerCaching(t *testing.T) {
+	p := testutil.NewPrincipal("bob")
+	store, cache := createStore(p)
+
+	bob := store.Default()
+	alice, err := p.BlessSelf("alice")
+	if err != nil {
+		t.Fatalf("BlessSelf failed: %v", err)
+	}
+
+	store.Set(security.Blessings{}, "...")
+	store.Set(bob, "bob")
+
+	if got := cache.ForPeer("bob/server"); !reflect.DeepEqual(bob, got) {
+		t.Errorf("ForPeer(bob/server) got: %v, want: %v", got, bob)
+	}
+
+	store.Set(alice, "bob")
+	if got := cache.ForPeer("bob/server"); !reflect.DeepEqual(bob, got) {
+		t.Errorf("ForPeer(bob/server) got: %v, want: %v", got, bob)
+	}
+
+	cache.flush()
+	if got := cache.ForPeer("bob/server"); !reflect.DeepEqual(alice, got) {
+		t.Errorf("ForPeer(bob/server) got: %v, want: %v", got, alice)
+	}
+}
+
+func TestPeerBlessings(t *testing.T) {
+	p := testutil.NewPrincipal("bob")
+	store, cache := createStore(p)
+
+	alice, err := p.BlessSelf("alice")
+	if err != nil {
+		t.Fatalf("BlessSelf failed: %v", err)
+	}
+
+	if _, err = cache.Set(alice, "alice"); err != nil {
+		t.Errorf("Set() failed: %v", err)
+	}
+
+	orig := store.PeerBlessings()
+	if got := cache.PeerBlessings(); !reflect.DeepEqual(orig, got) {
+		t.Errorf("PeerBlessings() got %v, want %v", got, orig)
+	}
+
+	store.Set(alice, "carol")
+	if got := cache.PeerBlessings(); !reflect.DeepEqual(orig, got) {
+		t.Errorf("PeerBlessings() got %v, want %v", got, orig)
+	}
+
+	cache.flush()
+	if cur, got := store.PeerBlessings(), cache.PeerBlessings(); !reflect.DeepEqual(cur, got) {
+		t.Errorf("PeerBlessings() got %v, want %v", got, cur)
+	}
+}
+
+func TestStoreDebugString(t *testing.T) {
+	impl, cache := createStore(testutil.NewPrincipal("bob/friend/alice"))
+
+	if a, b := impl.DebugString(), cache.DebugString(); a != b {
+		t.Errorf("DebugString doesn't match. Expected:\n%s\nGot:\n%s", a, b)
+	}
+}
diff --git a/services/agent/internal/ipc/ipc.go b/services/agent/internal/ipc/ipc.go
new file mode 100644
index 0000000..ca1bd9d
--- /dev/null
+++ b/services/agent/internal/ipc/ipc.go
@@ -0,0 +1,408 @@
+// 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 ipc implements a simple IPC system based on VOM.
+package ipc
+
+import (
+	"fmt"
+	"io"
+	"net"
+	"reflect"
+	"sync"
+	"v.io/v23/vom"
+	"v.io/x/lib/vlog"
+	"v.io/x/ref/services/agent"
+)
+
+const currentVersion = 1
+
+type decoderFunc func(n uint32, dec *vom.Decoder) ([]reflect.Value, error)
+
+type rpcInfo struct {
+	id      uint64
+	results []interface{}
+	done    chan error
+}
+
+type rpcHandler struct {
+	decode decoderFunc
+	f      reflect.Value
+}
+
+// IPC provides interprocess rpcs.
+// One process must act as the "server" by listening for connections.
+// However once connected rpcs can flow in either direction.
+type IPC struct {
+	handlers map[string]rpcHandler
+	mu       sync.Mutex
+	listener *net.UnixListener
+	conns    []*IPCConn
+}
+
+// IPCConn represents a connection to a process.
+// It can be used to make rpcs to the other process.  It also
+// dispatches rpcs from that process to handlers registered with the
+// parent IPC object.
+type IPCConn struct {
+	enc    *vom.Encoder
+	dec    *vom.Decoder
+	conn   net.Conn
+	mu     sync.Mutex
+	nextId uint64
+	ipc    *IPC
+	rpcs   map[uint64]rpcInfo
+}
+
+// Close the connection to the process.
+// All outstanding rpcs will return errors.
+func (c *IPCConn) Close() {
+	c.ipc.closeConn(c)
+	c.mu.Lock()
+	for _, rpc := range c.rpcs {
+		rpc.done <- io.EOF
+	}
+	c.rpcs = nil
+	c.mu.Unlock()
+}
+
+// Perform an rpc with the given name and arguments.
+// 'args' must correspond with the method registered in the other process,
+// and results must contain pointers to the return types of the method.
+// All rpc methods must have a final error result, which is not included in 'results'.
+// It is the return value of Call().
+func (c *IPCConn) Call(method string, args []interface{}, results ...interface{}) error {
+	ch := c.ipc.startCall(c, method, args, results)
+	return <-ch
+}
+
+// Create a new IPC object.
+// After calling new you can register handlers with Serve().
+// Then call Listen() or Connect().
+func NewIPC() *IPC {
+	ipc := new(IPC)
+	ipc.handlers = make(map[string]rpcHandler)
+	return ipc
+}
+
+// Listen for connections by creating a UNIX domain socket at 'path'.
+func (ipc *IPC) Listen(path string) error {
+	addr, err := net.ResolveUnixAddr("unix", path)
+	if err != nil {
+		return err
+	}
+	ipc.mu.Lock()
+	ipc.listener, err = net.ListenUnix("unix", addr)
+	ipc.mu.Unlock()
+	if err == nil {
+		go ipc.listenLoop()
+	}
+	return err
+}
+
+// Connect to a listening socket at 'path'.
+func (ipc *IPC) Connect(path string) (*IPCConn, error) {
+	conn, err := net.Dial("unix", path)
+	if err != nil {
+		return nil, err
+	}
+	ipc.mu.Lock()
+	ic := ipc.newConnLocked(conn)
+	ipc.mu.Unlock()
+	go ipc.readLoop(ic)
+	return ic, nil
+}
+
+// Connections returns all the current connections in this IPC.
+func (ipc *IPC) Connections() []*IPCConn {
+	defer ipc.mu.Unlock()
+	ipc.mu.Lock()
+	conns := make([]*IPCConn, len(ipc.conns))
+	copy(conns, ipc.conns)
+	return conns
+}
+
+// Must be called while holding ipc.mu
+func (ipc *IPC) newConnLocked(conn net.Conn) *IPCConn {
+	result := &IPCConn{enc: vom.NewEncoder(conn), dec: vom.NewDecoder(conn), conn: conn, ipc: ipc, rpcs: make(map[uint64]rpcInfo)}
+	// Don't allow any rpcs to be sent until negotiateVersion unlocks this.
+	result.mu.Lock()
+	ipc.conns = append(ipc.conns, result)
+	return result
+}
+
+func (ipc *IPC) closeConn(c *IPCConn) {
+	ipc.mu.Lock()
+	for i := range ipc.conns {
+		if ipc.conns[i] == c {
+			last := len(ipc.conns) - 1
+			ipc.conns[i], ipc.conns[last], ipc.conns = ipc.conns[last], nil, ipc.conns[:last]
+			break
+		}
+	}
+	ipc.mu.Unlock()
+	c.conn.Close()
+}
+
+func (ipc *IPC) listenLoop() {
+	ipc.mu.Lock()
+	l := ipc.listener
+	ipc.mu.Unlock()
+	for l != nil {
+		conn, err := l.AcceptUnix()
+		if err != nil {
+			vlog.VI(3).Infof("Accept error: %v", err)
+			return
+		}
+		ipc.mu.Lock()
+		l = ipc.listener
+		if l == nil {
+			// We're already closed!
+			conn.Close()
+			return
+		}
+		ic := ipc.newConnLocked(conn)
+		ipc.mu.Unlock()
+		go ipc.readLoop(ic)
+	}
+}
+
+func (ipc *IPC) readLoop(c *IPCConn) {
+	v := ipc.negotiateVersion(c)
+	if v != currentVersion {
+		ipc.closeConn(c)
+		return
+	}
+	for {
+		if f, err := ipc.readMessage(c); err != nil {
+			vlog.VI(3).Infof("ipc read error: %v", err)
+			c.Close()
+			return
+		} else if f != nil {
+			go f()
+		}
+	}
+}
+
+func (ipc *IPC) negotiateVersion(c *IPCConn) int32 {
+	go func() {
+		myInfo := agent.ConnInfo{MinVersion: currentVersion, MaxVersion: currentVersion}
+		// c.mu.Lock() already called in newConnLocked
+		vlog.VI(4).Infof("negotiateVersion: sending %v", myInfo)
+		err := c.enc.Encode(myInfo)
+		c.mu.Unlock()
+		if err != nil {
+			vlog.VI(3).Infof("ipc.negotiateVersion encode: %v", err)
+			ipc.closeConn(c)
+		}
+	}()
+	var theirInfo agent.ConnInfo
+	err := c.dec.Decode(&theirInfo)
+	if err != nil {
+		vlog.VI(3).Infof("ipc.negotiateVersion decode: %v", err)
+	}
+	if theirInfo.MinVersion <= currentVersion && theirInfo.MaxVersion >= currentVersion {
+		return currentVersion
+	}
+	vlog.VI(3).Infof("ipc.negotiateVersion %d not in remote range: %d-%d", currentVersion, theirInfo.MinVersion, theirInfo.MaxVersion)
+	return -1
+}
+
+func makeDecoder(t reflect.Type) decoderFunc {
+	numArgs := t.NumIn() - 1
+	inTypes := make([]reflect.Type, numArgs)
+	for i := 1; i < numArgs+1; i++ {
+		inTypes[i-1] = t.In(i)
+	}
+	return func(n uint32, dec *vom.Decoder) (result []reflect.Value, err error) {
+		if n != uint32(numArgs) {
+			for i := uint32(0); i < n; i++ {
+				if err = dec.Ignore(); err != nil {
+					return nil, err
+				}
+			}
+			return nil, fmt.Errorf("wrong number of args: expected %d, got %d", numArgs, n)
+		}
+
+		result = make([]reflect.Value, numArgs)
+		for i := 0; i < numArgs; i++ {
+			v := reflect.New(inTypes[i])
+			result[i] = reflect.Indirect(v)
+			if err = dec.Decode(v.Interface()); err != nil {
+				vlog.VI(3).Infof("decode error for %v: %v", inTypes[i], err)
+				return nil, err
+			}
+		}
+		return
+	}
+}
+
+func noDecoder(n uint32, dec *vom.Decoder) (result []reflect.Value, err error) {
+	for i := uint32(0); i < n; i++ {
+		if err = dec.Ignore(); err != nil {
+			return nil, err
+		}
+	}
+	return nil, fmt.Errorf("unknown method")
+}
+
+// Serve registers rpc handlers.
+// All exported methods in 'x' are registered for rpc.
+// All arguments and results for these methods must be VOM serializable.
+// Additionally each method must have at least one return value, and
+// the final return value must be an 'error'.
+// Serve must be called before Listen() or Connect().
+func (ipc *IPC) Serve(x interface{}) error {
+	v := reflect.ValueOf(x)
+	t := reflect.TypeOf(x)
+	for i, numMethods := 0, v.NumMethod(); i < numMethods; i++ {
+		m := t.Method(i)
+		if _, exists := ipc.handlers[m.Name]; exists {
+			return fmt.Errorf("Method %s already registered", m.Name)
+		}
+		ipc.handlers[m.Name] = rpcHandler{makeDecoder(m.Type), v.Method(i)}
+		vlog.VI(4).Infof("Registered method %q", m.Name)
+	}
+	return nil
+}
+
+func (ipc *IPC) dispatch(req agent.RpcRequest) rpcHandler {
+	handler, ok := ipc.handlers[req.Method]
+	if !ok {
+		handler = rpcHandler{noDecoder, reflect.Value{}}
+	}
+	return handler
+}
+
+// Close the IPC and all it's connections.
+func (ipc *IPC) Close() {
+	ipc.mu.Lock()
+	if ipc.listener != nil {
+		ipc.listener.Close()
+		ipc.listener = nil
+	}
+	for _, c := range ipc.conns {
+		c.conn.Close()
+	}
+	ipc.conns = nil
+	ipc.mu.Unlock()
+}
+
+func (ipc *IPC) startCall(c *IPCConn, method string, args []interface{}, results []interface{}) (ch chan error) {
+	ch = make(chan error, 1)
+	header := agent.RpcMessageReq{Value: agent.RpcRequest{Method: method, NumArgs: uint32(len(args))}}
+	shouldClose := false
+	defer func() {
+		// Only close c after we unlock it.
+		if shouldClose {
+			c.Close()
+		}
+	}()
+	defer c.mu.Unlock()
+	c.mu.Lock()
+	header.Value.Id = c.nextId
+	c.nextId++
+	vlog.VI(4).Infof("startCall sending %v", header)
+	if err := c.enc.Encode(header); err != nil {
+		// TODO: Close?
+		vlog.VI(4).Infof("startCall encode error %v", err)
+		ch <- err
+		return
+	}
+	for _, arg := range args {
+		if err := c.enc.Encode(arg); err != nil {
+			vlog.VI(4).Infof("startCall arg error %v", err)
+			shouldClose = true
+			ch <- err
+			return
+		}
+	}
+	c.rpcs[header.Value.Id] = rpcInfo{header.Value.Id, results, ch}
+	return
+}
+
+func (ipc *IPC) readMessage(c *IPCConn) (func(), error) {
+	var m agent.RpcMessage
+	if err := c.dec.Decode(&m); err != nil {
+		return nil, err
+	}
+	vlog.VI(4).Infof("readMessage decoded %v", m)
+	switch msg := m.Interface().(type) {
+	case agent.RpcRequest:
+		handler := ipc.dispatch(msg)
+		args, err := handler.decode(msg.NumArgs, c.dec)
+
+		if err != nil {
+			err = fmt.Errorf("%s: %v", msg.Method, err)
+		}
+		return func() { ipc.handleReq(c, msg.Id, handler.f, args, err) }, nil
+	case agent.RpcResponse:
+		return nil, ipc.handleResp(c, msg)
+	default:
+		return nil, fmt.Errorf("unsupported type %t", msg)
+	}
+}
+
+func (ipc *IPC) handleReq(c *IPCConn, id uint64, method reflect.Value, args []reflect.Value, err error) {
+	var results []reflect.Value
+	if err == nil {
+		results = method.Call(args)
+		last := len(results) - 1
+		if !results[last].IsNil() {
+			err = results[last].Interface().(error)
+		}
+		results = results[:last]
+	}
+	header := agent.RpcMessageResp{Value: agent.RpcResponse{Id: id, NumArgs: uint32(len(results)), Err: err}}
+	shouldClose := false
+	defer func() {
+		if shouldClose {
+			c.Close()
+		}
+	}()
+	defer c.mu.Unlock()
+	c.mu.Lock()
+	vlog.VI(4).Infof("handleReq sending %v", header)
+	if err = c.enc.Encode(header); err != nil {
+		vlog.VI(3).Infof("handleReq error %v", err)
+		shouldClose = true
+		return
+	}
+	for _, result := range results {
+		if err = c.enc.Encode(result.Interface()); err != nil {
+			vlog.VI(3).Infof("handleReq result error %v", err)
+			shouldClose = true
+			return
+		}
+	}
+}
+
+func (ipc *IPC) handleResp(c *IPCConn, resp agent.RpcResponse) error {
+	c.mu.Lock()
+	info, ok := c.rpcs[resp.Id]
+	if ok {
+		delete(c.rpcs, resp.Id)
+	}
+	c.mu.Unlock()
+	if ok && resp.NumArgs == uint32(len(info.results)) {
+		for i := range info.results {
+			if err := c.dec.Decode(info.results[i]); err != nil {
+				vlog.VI(3).Infof("result decode error for %T: %v", info.results[i], err)
+				info.done <- err
+				return err
+			}
+		}
+		info.done <- resp.Err
+	} else {
+		for i := uint32(0); i < resp.NumArgs; i++ {
+			c.dec.Ignore()
+		}
+		err := resp.Err
+		if err == nil {
+			err = fmt.Errorf("invalid results")
+		}
+		info.done <- err
+	}
+	return nil
+}
diff --git a/services/agent/internal/ipc/ipc_test.go b/services/agent/internal/ipc/ipc_test.go
new file mode 100644
index 0000000..052a08d
--- /dev/null
+++ b/services/agent/internal/ipc/ipc_test.go
@@ -0,0 +1,299 @@
+// 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 ipc
+
+import (
+	"flag"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"net"
+	"os"
+	"path/filepath"
+	"strings"
+	"testing"
+	"v.io/v23/vom"
+	"v.io/x/lib/vlog"
+	"v.io/x/ref/services/agent"
+)
+
+type echo int
+
+func (e echo) Echo(m string) (string, error) {
+	return m, nil
+}
+
+type delayedEcho struct {
+	gotA, gotB   chan struct{}
+	doneA, doneB chan string
+}
+
+func newDelayedEcho() delayedEcho {
+	return delayedEcho{make(chan struct{}, 1), make(chan struct{}, 1), make(chan string, 1), make(chan string, 1)}
+}
+
+func (d delayedEcho) EchoA() (string, error) {
+	close(d.gotA)
+	return <-d.doneA, nil
+}
+
+func (d delayedEcho) EchoB(prefix string) (string, error) {
+	close(d.gotB)
+	return prefix + <-d.doneB, nil
+}
+
+type returnError int
+
+func (returnError) BreakMe(a, b string) (int, error) {
+	return 0, fmt.Errorf("%s %s", a, b)
+}
+
+type tunnel struct {
+	ipc *IPC
+}
+
+func (t *tunnel) Tunnel() (str string, err error) {
+	conn := t.ipc.Connections()[0]
+	err = conn.Call("Echo", []interface{}{"Tunnel"}, &str)
+	return
+}
+
+func newServer(t *testing.T, servers ...interface{}) (ipc *IPC, path string, f func()) {
+	dir, err := ioutil.TempDir("", "sock")
+	if err != nil {
+		t.Fatal(err)
+	}
+	path = filepath.Join(dir, "sock")
+	ipc = NewIPC()
+	for _, s := range servers {
+		if err := ipc.Serve(s); err != nil {
+			t.Fatal(err)
+		}
+	}
+	if err := ipc.Listen(path); err != nil {
+		os.RemoveAll(dir)
+		t.Fatal(err)
+	}
+	return ipc, path, func() { ipc.Close(); os.RemoveAll(dir) }
+}
+
+func TestDoubleServe(t *testing.T) {
+	ipc := NewIPC()
+	var a echo
+	var b echo
+	if err := ipc.Serve(a); err != nil {
+		t.Fatal(err)
+	}
+	if err := ipc.Serve(b); err == nil {
+		t.Fatal("Expected error")
+	}
+}
+
+func TestListen(t *testing.T) {
+	ipc1, path, cleanup := newServer(t)
+	defer cleanup()
+	if stat, err := os.Stat(path); err == nil {
+		if stat.Mode()&os.ModeSocket == 0 {
+			t.Fatalf("Not a socket: %#o", stat.Mode())
+		}
+	} else {
+		t.Fatal(err)
+	}
+	ipc1.Close()
+	if _, err := os.Stat(path); !os.IsNotExist(err) {
+		t.Fatalf("Expected NotExist, got %v", err)
+	}
+}
+
+func TestBadConnect(t *testing.T) {
+	_, path, cleanup := newServer(t)
+	defer cleanup()
+	conn, err := net.Dial("unix", path)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	enc := vom.NewEncoder(conn)
+	dec := vom.NewDecoder(conn)
+
+	var theirInfo agent.ConnInfo
+	if err = dec.Decode(&theirInfo); err != nil {
+		t.Fatal(err)
+	}
+
+	if err = enc.Encode(agent.RpcMessageReq{Value: agent.RpcRequest{Id: 0, Method: "foo", NumArgs: 0}}); err != nil {
+		t.Fatal(err)
+	}
+	var response agent.RpcMessage
+	if err = dec.Decode(&response); err != io.EOF {
+		t.Fatalf("Expected eof, got %v", err)
+	}
+}
+
+func TestBadMinVersion(t *testing.T) {
+	_, path, cleanup := newServer(t)
+	defer cleanup()
+	conn, err := net.Dial("unix", path)
+	if err != nil {
+		t.Fatal(err)
+	}
+	enc := vom.NewEncoder(conn)
+	dec := vom.NewDecoder(conn)
+
+	var info agent.ConnInfo
+	if err = dec.Decode(&info); err != nil {
+		t.Fatal(err)
+	}
+
+	info.MaxVersion++
+	info.MinVersion = info.MaxVersion
+	if err = enc.Encode(info); err != nil {
+		t.Fatal(err)
+	}
+
+	var response agent.RpcMessage
+	if err = dec.Decode(&response); err != io.EOF {
+		t.Fatalf("Expected EOF, got %v", err)
+	}
+}
+
+func TestBadMaxVersion(t *testing.T) {
+	_, path, cleanup := newServer(t)
+	defer cleanup()
+	conn, err := net.Dial("unix", path)
+	if err != nil {
+		t.Fatal(err)
+	}
+	enc := vom.NewEncoder(conn)
+	dec := vom.NewDecoder(conn)
+
+	var info agent.ConnInfo
+	if err = dec.Decode(&info); err != nil {
+		t.Fatal(err)
+	}
+
+	info.MinVersion--
+	info.MaxVersion = info.MinVersion
+	if err = enc.Encode(info); err != nil {
+		t.Fatal(err)
+	}
+
+	var response agent.RpcMessage
+	if err = dec.Decode(&response); err != io.EOF {
+		t.Fatalf("Expected EOF, got %v", err)
+	}
+}
+
+func TestEcho(t *testing.T) {
+	var e echo
+	_, path, cleanup := newServer(t, e)
+	defer cleanup()
+
+	ipc2 := NewIPC()
+	defer ipc2.Close()
+	client, err := ipc2.Connect(path)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	var result string
+	err = client.Call("Echo", []interface{}{"hello"}, &result)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if result != "hello" {
+		t.Fatalf("Expected hello, got %q", result)
+	}
+}
+
+func TestOutOfOrder(t *testing.T) {
+	delay := newDelayedEcho()
+	_, path, cleanup := newServer(t, delay)
+	defer cleanup()
+
+	ipc2 := NewIPC()
+	defer ipc2.Close()
+	client, err := ipc2.Connect(path)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	var resultA string
+	errA := make(chan error)
+	go func() { errA <- client.Call("EchoA", nil, &resultA) }()
+	<-delay.gotA
+
+	delay.doneB <- " world"
+	var resultB string
+	err = client.Call("EchoB", []interface{}{"hello"}, &resultB)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if resultB != "hello world" {
+		t.Fatalf("Expected 'hello word', got %q", resultB)
+	}
+	delay.doneA <- "foobar"
+	if err := <-errA; err != nil {
+		t.Fatal(err)
+	}
+	if resultA != "foobar" {
+		t.Fatalf("Expected foobar, got %q", resultA)
+	}
+
+}
+
+func TestErrorReturn(t *testing.T) {
+	var s returnError
+	_, path, cleanup := newServer(t, s)
+	defer cleanup()
+
+	ipc2 := NewIPC()
+	defer ipc2.Close()
+	client, err := ipc2.Connect(path)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	var result int
+	err = client.Call("BreakMe", []interface{}{"oh", "no"}, &result)
+	if err == nil {
+		t.Fatalf("Expected error, got %d", result)
+	}
+	if !strings.HasSuffix(err.Error(), "oh no") {
+		t.Fatalf("Expected 'oh no', got %q", err.Error())
+	}
+}
+
+func TestBidi(t *testing.T) {
+	s := &tunnel{}
+	ipc1, path, cleanup := newServer(t, s)
+	defer cleanup()
+	s.ipc = ipc1
+
+	ipc2 := NewIPC()
+	defer ipc2.Close()
+
+	var e echo
+	ipc2.Serve(e)
+	client, err := ipc2.Connect(path)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	var result string
+	err = client.Call("Tunnel", nil, &result)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if result != "Tunnel" {
+		t.Fatalf("Expected Tunnel, got %q", result)
+	}
+}
+
+func TestMain(m *testing.M) {
+	flag.Parse()
+	vlog.ConfigureLibraryLoggerFromFlags()
+	os.Exit(m.Run())
+}
diff --git a/services/agent/internal/lockfile/lockfile.go b/services/agent/internal/lockfile/lockfile.go
new file mode 100644
index 0000000..0011510
--- /dev/null
+++ b/services/agent/internal/lockfile/lockfile.go
@@ -0,0 +1,106 @@
+// 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 lockfile
+
+import (
+	"bytes"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"regexp"
+	"strconv"
+	"syscall"
+
+	"v.io/x/lib/vlog"
+)
+
+const lockfileName = "agent.lock"
+const tempLockfileName = "agent.templock"
+const agentSocketName = "agent.sock" // Keep in sync with agentd/main.go
+
+func CreateLockfile(dir string) error {
+	f, err := ioutil.TempFile(dir, tempLockfileName)
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+	defer os.Remove(f.Name())
+	cmd := makePsCommand(os.Getpid())
+	cmd.Stdout = f
+	cmd.Stderr = nil
+	if err = cmd.Run(); err != nil {
+		return err
+	}
+	lockfile := filepath.Join(dir, lockfileName)
+	err = os.Link(f.Name(), lockfile)
+	if err == nil {
+		return nil
+	}
+
+	// Check for a stale lock
+	lockProcessInfo, err := ioutil.ReadFile(lockfile)
+	if err != nil {
+		return err
+	}
+	if err, running := StillRunning(lockProcessInfo); running {
+		return fmt.Errorf("agentd is already running:\n%s", lockProcessInfo)
+	} else if err != nil {
+		return err
+	}
+
+	// Delete the socket if the old agent left one behind.
+	if err = os.RemoveAll(filepath.Join(dir, agentSocketName)); err != nil {
+		return err
+	}
+
+	// Note(ribrdb): There's a race here between checking the file contents
+	// and deleting the file, but I don't think it will be an issue in normal
+	// usage.
+	return os.Rename(f.Name(), lockfile)
+}
+
+var pidRegex = regexp.MustCompile("\n\\s*(\\d+)")
+
+func StillRunning(expected []byte) (error, bool) {
+	match := pidRegex.FindSubmatch(expected)
+	if match == nil {
+		// Corrupt lockfile. Just delete it.
+		return nil, false
+	}
+	pid, err := strconv.Atoi(string(match[1]))
+	if err != nil {
+		return nil, false
+	}
+	// Go's os turns the standard errors into indecipherable ones,
+	// so use syscall directly.
+	if err := syscall.Kill(pid, syscall.Signal(0)); err != nil {
+		if errno, ok := err.(syscall.Errno); ok && errno == syscall.ESRCH {
+			return nil, false
+		}
+	}
+	cmd := makePsCommand(pid)
+	out, err := cmd.Output()
+	if err != nil {
+		return err, false
+	}
+	return nil, bytes.Equal(expected, out)
+}
+
+func RemoveLockfile(dir string) {
+	path := filepath.Join(dir, lockfileName)
+	if err := os.Remove(path); err != nil {
+		vlog.Infof("Unable to remove lockfile %q: %v", path, err)
+	}
+	path = filepath.Join(dir, agentSocketName)
+	if err := os.RemoveAll(path); err != nil {
+		vlog.Infof("Unable to remove socket %q: %v", path, err)
+	}
+}
+
+func makePsCommand(pid int) *exec.Cmd {
+	return exec.Command("ps", "-o", "pid,ppid,lstart,user,comm", "-p", strconv.Itoa(pid))
+}
diff --git a/services/agent/internal/lockfile/lockfile_test.go b/services/agent/internal/lockfile/lockfile_test.go
new file mode 100644
index 0000000..1540056
--- /dev/null
+++ b/services/agent/internal/lockfile/lockfile_test.go
@@ -0,0 +1,118 @@
+// 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 lockfile
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"testing"
+
+	"v.io/x/ref/test/modules"
+
+	_ "v.io/x/ref/runtime/factories/generic"
+)
+
+//go:generate v23 test generate
+
+var createLockfile = modules.Register(func(env *modules.Env, args ...string) error {
+	dir := args[0]
+	err := CreateLockfile(dir)
+	if err == nil {
+		fmt.Println("Grabbed lock")
+	} else {
+		fmt.Println("Lock failed")
+	}
+	return err
+}, "createLockfile")
+
+func TestLockFile(t *testing.T) {
+	dir, err := ioutil.TempDir("", "lf")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(dir)
+	if err = CreateLockfile(dir); err != nil {
+		t.Fatal(err)
+	}
+	path := filepath.Join(dir, lockfileName)
+	bytes, err := ioutil.ReadFile(path)
+	if err != nil {
+		t.Fatal(err)
+	}
+	err, running := StillRunning(bytes)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !running {
+		t.Fatal("expected StillRunning() = true")
+	}
+
+	if err = CreateLockfile(dir); err == nil {
+		t.Fatal("Creating 2nd lockfile should fail")
+	}
+
+	RemoveLockfile(dir)
+	_, err = os.Lstat(path)
+	if !os.IsNotExist(err) {
+		t.Fatalf("%s: expected NotExist, got %v", path, err)
+	}
+}
+
+func TestOtherProcess(t *testing.T) {
+	dir, err := ioutil.TempDir("", "lf")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(dir)
+
+	sh, err := modules.NewShell(nil, nil, testing.Verbose(), t)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// Start a new child which creates a lockfile and exits.
+	h, err := sh.Start(nil, createLockfile, dir)
+	if err != nil {
+		t.Fatal(err)
+	}
+	h.Expect("Grabbed lock")
+	h.Shutdown(os.Stdout, os.Stderr)
+	if h.Failed() {
+		t.Fatal(h.Error())
+	}
+
+	// Verify it created a lockfile.
+	path := filepath.Join(dir, lockfileName)
+	bytes, err := ioutil.ReadFile(path)
+	if err != nil {
+		t.Fatal(err)
+	}
+	// And that we know the lockfile is invalid.
+	err, running := StillRunning(bytes)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if running {
+		t.Fatal("child process is dead")
+	}
+
+	// Now create a lockfile for the process.
+	if err = CreateLockfile(dir); err != nil {
+		t.Fatal(err)
+	}
+
+	// Now the child should fail to create one.
+	h, err = sh.Start(nil, createLockfile, dir)
+	if err != nil {
+		t.Fatal(err)
+	}
+	h.Expect("Lock failed")
+	h.Shutdown(os.Stderr, os.Stderr)
+	if h.Failed() {
+		t.Fatal(h.Error())
+	}
+}
diff --git a/services/agent/internal/lockfile/v23_internal_test.go b/services/agent/internal/lockfile/v23_internal_test.go
new file mode 100644
index 0000000..2ffce56
--- /dev/null
+++ b/services/agent/internal/lockfile/v23_internal_test.go
@@ -0,0 +1,22 @@
+// 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 lockfile
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	modules.DispatchAndExitIfChild()
+	os.Exit(m.Run())
+}
diff --git a/services/agent/internal/lru/lru.go b/services/agent/internal/lru/lru.go
new file mode 100644
index 0000000..c5379df
--- /dev/null
+++ b/services/agent/internal/lru/lru.go
@@ -0,0 +1,88 @@
+// 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 lru implements a Least-Recently-Used (LRU) cache of objects keyed by a string.
+//
+// A given key can have multiple values associated with it in the cache.
+package lru
+
+import "container/list"
+
+// Cache implements a Least-Recently-Used cache of objects.
+// Cache objects are not safe for concurrent use.
+type Cache struct {
+	m        map[string]*list.List // map from cache key to elements in l that are associated with that key.
+	l        *list.List            // contents of the cache (as lElem objects), LRU at the back.
+	capacity int                   // maximum number of (key, value) pairs to cache.
+}
+
+// lElem is the type used for elements in the Cache.l list.
+type lElem struct {
+	key   string
+	value interface{}
+}
+
+// New creates an LRU-cache that can hold up to capacity objects.
+func New(capacity int) *Cache {
+	return &Cache{make(map[string]*list.List), list.New(), capacity}
+}
+
+// Put adds the provided (key, value) pair to the cache and returns the (key,
+// value) pair that was evicted (if any) in order to make space for the
+// provided pair.
+//
+// It is legal to use the same key in multiple Put invocations as the cache can
+// hold multiple values associated with the same key.
+func (c *Cache) Put(key string, value interface{}) (evictedKey string, evictedValue interface{}, evicted bool) {
+	if evict := c.l.Len() >= c.capacity; evict {
+		lElem := c.evict()
+		evicted = true
+		evictedKey = lElem.key
+		evictedValue = lElem.value
+	}
+	ml, ok := c.m[key]
+	if !ok {
+		ml = list.New()
+		c.m[key] = ml
+	}
+	// Now add to c.l and ml
+	ml.PushFront(c.l.PushFront(lElem{key, value}))
+	return
+}
+
+// Get returns a value in the cache associated with the provided key and
+// removes it from the cache's storage.  It returns (nil, false) if no value is
+// associated with the key.
+//
+// If there are multiple values associated with the provided key, Get will
+// return the most recently used one.
+func (c *Cache) Get(key string) (interface{}, bool) {
+	ml, ok := c.m[key]
+	if !ok {
+		return nil, false
+	}
+	lElem := c.l.Remove(ml.Remove(ml.Front()).(*list.Element)).(lElem)
+	// At this point, lElem.key == key
+	// if lElem.key != key { panic("Bug! Cache.m[key] is not in sync with Cacke.l") }
+	if ml.Len() == 0 {
+		delete(c.m, key)
+	}
+	return lElem.value, true
+}
+
+func (c *Cache) evict() lElem {
+	lElem := c.l.Remove(c.l.Back()).(lElem)
+	// Remove the entry in c.m[key].
+	ml := c.m[lElem.key]
+	ml.Remove(ml.Back())
+	if ml.Len() == 0 {
+		delete(c.m, lElem.key)
+	}
+	return lElem
+}
+
+// Size returns the number of items currently in the cache.
+func (c *Cache) Size() int {
+	return c.l.Len()
+}
diff --git a/services/agent/internal/lru/lru_test.go b/services/agent/internal/lru/lru_test.go
new file mode 100644
index 0000000..b95ae0b
--- /dev/null
+++ b/services/agent/internal/lru/lru_test.go
@@ -0,0 +1,42 @@
+// 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 lru
+
+import "testing"
+
+func TestBasic(t *testing.T) {
+	c := New(3)
+	// Put (A, 1), (B, 2), (A, 3), (B, 4)
+	// (A, 1) should be evicted when (B, 4) is inserted.
+	if ek, ev, e := c.Put("A", 1); e || ek != "" || ev != nil {
+		t.Errorf("Unexpected eviction (%q, %v)", ek, ev)
+	}
+	if ek, ev, e := c.Put("B", 2); e || ek != "" || ev != nil {
+		t.Errorf("Unexpected eviction (%q, %v)", ek, ev)
+	}
+	if ek, ev, e := c.Put("A", 3); e || ek != "" || ev != nil {
+		t.Errorf("Unexpected eviction (%q, %v)", ek, ev)
+	}
+	if ek, ev, e := c.Put("B", 4); !e || ek != "A" || ev.(int) != 1 {
+		t.Errorf("Got eviction (%q, %v, %v), want (%q, %v, %v)", ek, ev, e, "A", 1, true)
+	}
+
+	// Get() removes from the cache
+	if v, ok := c.Get("A"); !ok || v.(int) != 3 {
+		t.Errorf("Got (%v, %v) want (3, true)", v, ok)
+	}
+	if v, ok := c.Get("A"); ok || v != nil {
+		t.Errorf("Got (%v, %v) want (nil, false)", v, ok)
+	}
+	if v, ok := c.Get("B"); !ok || v.(int) != 4 {
+		t.Errorf("Got (%v, %v) want (4, true)", v, ok)
+	}
+	if v, ok := c.Get("B"); !ok || v.(int) != 2 {
+		t.Errorf("Got (%v, %v) want (2, true)", v, ok)
+	}
+	if v, ok := c.Get("B"); ok || v != nil {
+		t.Errorf("Got (%v, %v) want (nil, false)", v, ok)
+	}
+}
diff --git a/services/agent/internal/pingpong/doc.go b/services/agent/internal/pingpong/doc.go
new file mode 100644
index 0000000..d223a6a
--- /dev/null
+++ b/services/agent/internal/pingpong/doc.go
@@ -0,0 +1,62 @@
+// 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
+
+/*
+Command pingpong runs a pingpong client or server.  If no args are given the
+server is run, otherwise the client is run.
+
+Usage:
+   pingpong [server]
+
+If [server] is specified, pingpong is run in client mode, and connects to the
+named server.
+
+The global flags are:
+ -alsologtostderr=true
+   log to standard error as well as files
+ -log_backtrace_at=:0
+   when logging hits line file:N, emit a stack trace
+ -log_dir=
+   if non-empty, write log files to this directory
+ -logtostderr=false
+   log to standard error instead of files
+ -max_stack_buf_size=4292608
+   max size in bytes of the buffer to use for logging stack traces
+ -metadata=<just specify -metadata to activate>
+   Displays metadata for the program and exits.
+ -stderrthreshold=2
+   logs at or above this threshold go to stderr
+ -v=0
+   log level for V logs
+ -v23.credentials=
+   directory to use for storing security credentials
+ -v23.i18n-catalogue=
+   18n catalogue files to load, comma separated
+ -v23.namespace.root=[/(dev.v.io/role/vprod/service/mounttabled)@ns.dev.v.io:8101]
+   local namespace root; can be repeated to provided multiple roots
+ -v23.proxy=
+   object name of proxy service to use to export services across network
+   boundaries
+ -v23.tcp.address=
+   address to listen on
+ -v23.tcp.protocol=wsh
+   protocol to listen with
+ -v23.vtrace.cache-size=1024
+   The number of vtrace traces to store in memory.
+ -v23.vtrace.collect-regexp=
+   Spans and annotations that match this regular expression will trigger trace
+   collection.
+ -v23.vtrace.dump-on-shutdown=true
+   If true, dump all stored traces on runtime shutdown.
+ -v23.vtrace.sample-rate=0
+   Rate (from 0.0 to 1.0) to sample vtrace traces.
+ -vmodule=
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
+*/
+package main
diff --git a/services/agent/internal/pingpong/main.go b/services/agent/internal/pingpong/main.go
new file mode 100644
index 0000000..2c27383
--- /dev/null
+++ b/services/agent/internal/pingpong/main.go
@@ -0,0 +1,89 @@
+// 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.
+
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go . -help
+
+package main
+
+import (
+	"fmt"
+
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/flags"
+	"v.io/x/ref/lib/signals"
+	"v.io/x/ref/lib/v23cmd"
+	"v.io/x/ref/lib/xrpc"
+
+	_ "v.io/x/ref/runtime/factories/generic"
+)
+
+func main() {
+	cmdline.HideGlobalFlagsExcept()
+	cmdline.Main(cmdPingPong)
+	flags.SetDefaultHostPort("127.0.0.1:0")
+}
+
+var cmdPingPong = &cmdline.Command{
+	Runner: v23cmd.RunnerFunc(runPingPong),
+	Name:   "pingpong",
+	Short:  "Runs pingpong client or server",
+	Long: `
+Command pingpong runs a pingpong client or server.  If no args are given the
+server is run, otherwise the client is run.
+`,
+	ArgsName: "[server]",
+	ArgsLong: `
+If [server] is specified, pingpong is run in client mode, and connects to the
+named server.
+`,
+}
+
+type pongd struct{}
+
+func (f *pongd) Ping(ctx *context.T, call rpc.ServerCall, message string) (result string, err error) {
+	client, _ := security.RemoteBlessingNames(ctx, call.Security())
+	server := security.LocalBlessingNames(ctx, call.Security())
+	return fmt.Sprintf("pong (client:%v server:%v)", client, server), nil
+}
+
+func clientMain(ctx *context.T, server string) error {
+	fmt.Println("Pinging...")
+	pong, err := PingPongClient(server).Ping(ctx, "ping")
+	if err != nil {
+		return fmt.Errorf("error pinging: %v", err)
+	}
+	fmt.Println(pong)
+	return nil
+}
+
+func serverMain(ctx *context.T) error {
+	// Provide an empty name, no need to mount on any mounttable.
+	//
+	// Use the default authorization policy (nil authorizer), which will
+	// only authorize clients if the blessings of the client is a prefix of
+	// that of the server or vice-versa.
+	s, err := xrpc.NewServer(ctx, "", PingPongServer(&pongd{}), nil)
+	if err != nil {
+		return fmt.Errorf("failure creating server: %v", err)
+	}
+	ctx.Info("Waiting for ping")
+	fmt.Printf("NAME=%v\n", s.Status().Endpoints[0].Name())
+	// Wait forever.
+	<-signals.ShutdownOnSignals(ctx)
+	return nil
+}
+
+func runPingPong(ctx *context.T, env *cmdline.Env, args []string) error {
+	switch len(args) {
+	case 0:
+		return serverMain(ctx)
+	case 1:
+		return clientMain(ctx, args[0])
+	}
+	return env.UsageErrorf("Too many arguments")
+}
diff --git a/services/agent/internal/pingpong/wire.vdl b/services/agent/internal/pingpong/wire.vdl
new file mode 100644
index 0000000..335fd9b
--- /dev/null
+++ b/services/agent/internal/pingpong/wire.vdl
@@ -0,0 +1,10 @@
+// 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
+
+// Simple service used in the agent tests.
+type PingPong interface {
+	Ping(message string) (string | error)
+}
diff --git a/services/agent/internal/pingpong/wire.vdl.go b/services/agent/internal/pingpong/wire.vdl.go
new file mode 100644
index 0000000..f90156b
--- /dev/null
+++ b/services/agent/internal/pingpong/wire.vdl.go
@@ -0,0 +1,119 @@
+// 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 by the vanadium vdl tool.
+// Source: wire.vdl
+
+package main
+
+import (
+	// VDL system imports
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+)
+
+// PingPongClientMethods is the client interface
+// containing PingPong methods.
+//
+// Simple service used in the agent tests.
+type PingPongClientMethods interface {
+	Ping(ctx *context.T, message string, opts ...rpc.CallOpt) (string, error)
+}
+
+// PingPongClientStub adds universal methods to PingPongClientMethods.
+type PingPongClientStub interface {
+	PingPongClientMethods
+	rpc.UniversalServiceMethods
+}
+
+// PingPongClient returns a client stub for PingPong.
+func PingPongClient(name string) PingPongClientStub {
+	return implPingPongClientStub{name}
+}
+
+type implPingPongClientStub struct {
+	name string
+}
+
+func (c implPingPongClientStub) Ping(ctx *context.T, i0 string, opts ...rpc.CallOpt) (o0 string, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "Ping", []interface{}{i0}, []interface{}{&o0}, opts...)
+	return
+}
+
+// PingPongServerMethods is the interface a server writer
+// implements for PingPong.
+//
+// Simple service used in the agent tests.
+type PingPongServerMethods interface {
+	Ping(ctx *context.T, call rpc.ServerCall, message string) (string, error)
+}
+
+// PingPongServerStubMethods is the server interface containing
+// PingPong methods, as expected by rpc.Server.
+// There is no difference between this interface and PingPongServerMethods
+// since there are no streaming methods.
+type PingPongServerStubMethods PingPongServerMethods
+
+// PingPongServerStub adds universal methods to PingPongServerStubMethods.
+type PingPongServerStub interface {
+	PingPongServerStubMethods
+	// Describe the PingPong interfaces.
+	Describe__() []rpc.InterfaceDesc
+}
+
+// PingPongServer returns a server stub for PingPong.
+// It converts an implementation of PingPongServerMethods into
+// an object that may be used by rpc.Server.
+func PingPongServer(impl PingPongServerMethods) PingPongServerStub {
+	stub := implPingPongServerStub{
+		impl: impl,
+	}
+	// Initialize GlobState; always check the stub itself first, to handle the
+	// case where the user has the Glob method defined in their VDL source.
+	if gs := rpc.NewGlobState(stub); gs != nil {
+		stub.gs = gs
+	} else if gs := rpc.NewGlobState(impl); gs != nil {
+		stub.gs = gs
+	}
+	return stub
+}
+
+type implPingPongServerStub struct {
+	impl PingPongServerMethods
+	gs   *rpc.GlobState
+}
+
+func (s implPingPongServerStub) Ping(ctx *context.T, call rpc.ServerCall, i0 string) (string, error) {
+	return s.impl.Ping(ctx, call, i0)
+}
+
+func (s implPingPongServerStub) Globber() *rpc.GlobState {
+	return s.gs
+}
+
+func (s implPingPongServerStub) Describe__() []rpc.InterfaceDesc {
+	return []rpc.InterfaceDesc{PingPongDesc}
+}
+
+// PingPongDesc describes the PingPong interface.
+var PingPongDesc rpc.InterfaceDesc = descPingPong
+
+// descPingPong hides the desc to keep godoc clean.
+var descPingPong = rpc.InterfaceDesc{
+	Name:    "PingPong",
+	PkgPath: "v.io/x/ref/services/agent/internal/pingpong",
+	Doc:     "// Simple service used in the agent tests.",
+	Methods: []rpc.MethodDesc{
+		{
+			Name: "Ping",
+			InArgs: []rpc.ArgDesc{
+				{"message", ``}, // string
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"", ``}, // string
+			},
+		},
+	},
+}
diff --git a/services/agent/internal/server/server.go b/services/agent/internal/server/server.go
new file mode 100644
index 0000000..ba9c579
--- /dev/null
+++ b/services/agent/internal/server/server.go
@@ -0,0 +1,412 @@
+// 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 server
+
+import (
+	"crypto/ecdsa"
+	"crypto/elliptic"
+	"crypto/rand"
+	"crypto/sha512"
+	"crypto/x509"
+	"encoding/base64"
+	"fmt"
+	"os"
+	"path/filepath"
+	"sync"
+
+	"v.io/v23/security"
+	"v.io/v23/verror"
+	vsecurity "v.io/x/ref/lib/security"
+	"v.io/x/ref/services/agent"
+	"v.io/x/ref/services/agent/internal/ipc"
+)
+
+const pkgPath = "v.io/x/ref/services/agent/internal/server"
+
+// Errors
+var (
+	errStoragePathRequired = verror.Register(pkgPath+".errStoragePathRequired",
+		verror.NoRetry, "{1:}{2:} RunKeyManager: storage path is required")
+	errNotMultiKeyMode = verror.Register(pkgPath+".errNotMultiKeyMode",
+		verror.NoRetry, "{1:}{2:} Not running in multi-key mode")
+)
+
+type agentd struct {
+	ipc       *ipc.IPC
+	principal security.Principal
+	mu        sync.RWMutex
+}
+
+type keyData struct {
+	p     security.Principal
+	agent *ipc.IPC
+}
+
+type keymgr struct {
+	path       string
+	passphrase []byte
+	cache      map[[agent.PrincipalHandleByteSize]byte]keyData
+	mu         sync.Mutex
+}
+
+// ServeAgent registers the agent server with 'ipc'.
+// It will respond to requests using 'principal'.
+// Must be called before ipc.Listen or ipc.Connect.
+func ServeAgent(i *ipc.IPC, principal security.Principal) (err error) {
+	server := &agentd{ipc: i, principal: principal}
+	return i.Serve(server)
+}
+
+func newKeyManager(path string, passphrase []byte) (*keymgr, error) {
+	if path == "" {
+		return nil, verror.New(errStoragePathRequired, nil)
+	}
+
+	mgr := &keymgr{path: path, passphrase: passphrase, cache: make(map[[agent.PrincipalHandleByteSize]byte]keyData)}
+
+	if err := os.MkdirAll(filepath.Join(mgr.path, "keys"), 0700); err != nil {
+		return nil, err
+	}
+	if err := os.MkdirAll(filepath.Join(mgr.path, "creds"), 0700); err != nil {
+		return nil, err
+	}
+	return mgr, nil
+}
+
+type localKeymgr struct {
+	*keymgr
+}
+
+func NewLocalKeyManager(path string, passphrase []byte) (agent.KeyManager, error) {
+	m, err := newKeyManager(path, passphrase)
+	return localKeymgr{m}, err
+}
+
+func (l localKeymgr) Close() error {
+	defer l.mu.Unlock()
+	l.mu.Lock()
+	for _, data := range l.cache {
+		if data.agent != nil {
+			data.agent.Close()
+		}
+	}
+	return nil
+}
+
+// ServeKeyManager registers key manager server with 'ipc'.
+// It will persist principals in 'path' using 'passphrase'.
+// Must be called before ipc.Listen or ipc.Connect.
+func ServeKeyManager(i *ipc.IPC, path string, passphrase []byte) error {
+	mgr, err := newKeyManager(path, passphrase)
+	if err != nil {
+		return err
+	}
+	return i.Serve(mgr)
+}
+
+func (a *keymgr) readKey(handle [agent.PrincipalHandleByteSize]byte) (keyData, error) {
+	{
+		a.mu.Lock()
+		cached, ok := a.cache[handle]
+		a.mu.Unlock()
+		if ok {
+			return cached, nil
+		}
+	}
+
+	var nodata keyData
+	filename := base64.URLEncoding.EncodeToString(handle[:])
+	in, err := os.Open(filepath.Join(a.path, "keys", filename))
+	if err != nil {
+		return nodata, fmt.Errorf("unable to open key file: %v", err)
+	}
+	defer in.Close()
+	key, err := vsecurity.LoadPEMKey(in, a.passphrase)
+	if err != nil {
+		return nodata, fmt.Errorf("unable to load key in %q: %v", in.Name(), err)
+	}
+	state, err := vsecurity.NewPrincipalStateSerializer(filepath.Join(a.path, "creds", filename))
+	if err != nil {
+		return nodata, fmt.Errorf("unable to create persisted state serializer: %v", err)
+	}
+	principal, err := vsecurity.NewPrincipalFromSigner(security.NewInMemoryECDSASigner(key.(*ecdsa.PrivateKey)), state)
+	if err != nil {
+		return nodata, fmt.Errorf("unable to load principal: %v", err)
+	}
+	data := keyData{p: principal}
+	a.mu.Lock()
+	if cachedData, ok := a.cache[handle]; ok {
+		data = cachedData
+	} else {
+		a.cache[handle] = data
+	}
+	a.mu.Unlock()
+	return data, nil
+}
+
+func (a *agentd) Bless(key []byte, with security.Blessings, extension string, caveat security.Caveat, additionalCaveats []security.Caveat) (security.Blessings, error) {
+	pkey, err := security.UnmarshalPublicKey(key)
+	if err != nil {
+		return security.Blessings{}, err
+	}
+	return a.principal.Bless(pkey, with, extension, caveat, additionalCaveats...)
+}
+
+func (a *agentd) BlessSelf(name string, caveats []security.Caveat) (security.Blessings, error) {
+	return a.principal.BlessSelf(name, caveats...)
+}
+
+func (a *agentd) Sign(message []byte) (security.Signature, error) {
+	return a.principal.Sign(message)
+}
+
+func (a *agentd) MintDischarge(forCaveat, caveatOnDischarge security.Caveat, additionalCaveatsOnDischarge []security.Caveat) (security.Discharge, error) {
+	return a.principal.MintDischarge(forCaveat, caveatOnDischarge, additionalCaveatsOnDischarge...)
+}
+
+func (a *keymgr) NewPrincipal(in_memory bool) (handle [agent.PrincipalHandleByteSize]byte, err error) {
+	if a.path == "" {
+		return handle, verror.New(errNotMultiKeyMode, nil)
+	}
+	key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+	if err != nil {
+		return handle, err
+	}
+	if handle, err = keyid(key); err != nil {
+		return handle, err
+	}
+	signer := security.NewInMemoryECDSASigner(key)
+	var p security.Principal
+	if in_memory {
+		if p, err = vsecurity.NewPrincipalFromSigner(signer, nil); err != nil {
+			return handle, err
+		}
+	} else {
+		filename := base64.URLEncoding.EncodeToString(handle[:])
+		out, err := os.OpenFile(filepath.Join(a.path, "keys", filename), os.O_WRONLY|os.O_CREATE, 0600)
+		if err != nil {
+			return handle, err
+		}
+		defer out.Close()
+		err = vsecurity.SavePEMKey(out, key, a.passphrase)
+		if err != nil {
+			return handle, err
+		}
+		state, err := vsecurity.NewPrincipalStateSerializer(filepath.Join(a.path, "creds", filename))
+		if err != nil {
+			return handle, err
+		}
+		p, err = vsecurity.NewPrincipalFromSigner(signer, state)
+		if err != nil {
+			return handle, err
+		}
+	}
+	data := keyData{p: p}
+	a.mu.Lock()
+	if _, ok := a.cache[handle]; !ok {
+		a.cache[handle] = data
+	}
+	a.mu.Unlock()
+	return handle, nil
+}
+
+func keyid(key *ecdsa.PrivateKey) (handle [agent.PrincipalHandleByteSize]byte, err error) {
+	slice, err := x509.MarshalPKIXPublicKey(&key.PublicKey)
+	if err != nil {
+		return
+	}
+	return sha512.Sum512(slice), nil
+}
+
+func (a *agentd) unlock() {
+	a.mu.Unlock()
+	for _, conn := range a.ipc.Connections() {
+		go conn.Call("FlushAllCaches", nil)
+	}
+}
+
+func (a *agentd) PublicKey() ([]byte, error) {
+	return a.principal.PublicKey().MarshalBinary()
+}
+
+func (a *agentd) BlessingsByName(name security.BlessingPattern) ([]security.Blessings, error) {
+	defer a.mu.RUnlock()
+	a.mu.RLock()
+	return a.principal.BlessingsByName(name), nil
+}
+
+func (a *agentd) BlessingsInfo(blessings security.Blessings) (map[string][]security.Caveat, error) {
+	a.mu.RLock()
+	return a.principal.BlessingsInfo(blessings), nil
+}
+
+func (a *agentd) AddToRoots(blessings security.Blessings) error {
+	defer a.unlock()
+	a.mu.Lock()
+	return a.principal.AddToRoots(blessings)
+}
+
+func (a *agentd) BlessingStoreSet(blessings security.Blessings, forPeers security.BlessingPattern) (security.Blessings, error) {
+	defer a.unlock()
+	a.mu.Lock()
+	return a.principal.BlessingStore().Set(blessings, forPeers)
+}
+
+func (a *agentd) BlessingStoreForPeer(peerBlessings []string) (security.Blessings, error) {
+	defer a.mu.RUnlock()
+	a.mu.RLock()
+	return a.principal.BlessingStore().ForPeer(peerBlessings...), nil
+}
+
+func (a *agentd) BlessingStoreSetDefault(blessings security.Blessings) error {
+	defer a.unlock()
+	a.mu.Lock()
+	return a.principal.BlessingStore().SetDefault(blessings)
+}
+
+func (a *agentd) BlessingStorePeerBlessings() (map[security.BlessingPattern]security.Blessings, error) {
+	defer a.mu.RUnlock()
+	a.mu.RLock()
+	return a.principal.BlessingStore().PeerBlessings(), nil
+}
+
+func (a *agentd) BlessingStoreDebugString() (string, error) {
+	defer a.mu.RUnlock()
+	a.mu.RLock()
+	return a.principal.BlessingStore().DebugString(), nil
+}
+
+func (a *agentd) BlessingStoreDefault() (security.Blessings, error) {
+	defer a.mu.RUnlock()
+	a.mu.RLock()
+	return a.principal.BlessingStore().Default(), nil
+}
+
+func (a *agentd) BlessingStoreCacheDischarge(discharge security.Discharge, caveat security.Caveat, impetus security.DischargeImpetus) error {
+	defer a.mu.Unlock()
+	a.mu.Lock()
+	a.principal.BlessingStore().CacheDischarge(discharge, caveat, impetus)
+	return nil
+}
+
+func (a *agentd) BlessingStoreClearDischarges(discharges []security.Discharge) error {
+	defer a.mu.Unlock()
+	a.mu.Lock()
+	a.principal.BlessingStore().ClearDischarges(discharges...)
+	return nil
+}
+
+func (a *agentd) BlessingStoreDischarge(caveat security.Caveat, impetus security.DischargeImpetus) (security.Discharge, error) {
+	defer a.mu.Unlock()
+	a.mu.Lock()
+	return a.principal.BlessingStore().Discharge(caveat, impetus), nil
+}
+
+func (a *agentd) BlessingRootsAdd(root []byte, pattern security.BlessingPattern) error {
+	pkey, err := security.UnmarshalPublicKey(root)
+	if err != nil {
+		return err
+	}
+	defer a.unlock()
+	a.mu.Lock()
+	return a.principal.Roots().Add(pkey, pattern)
+}
+
+func (a *agentd) BlessingRootsRecognized(root []byte, blessing string) error {
+	pkey, err := security.UnmarshalPublicKey(root)
+	if err != nil {
+		return err
+	}
+	defer a.mu.RUnlock()
+	a.mu.RLock()
+	return a.principal.Roots().Recognized(pkey, blessing)
+}
+
+func (a *agentd) BlessingRootsDump() (map[security.BlessingPattern][][]byte, error) {
+	ret := make(map[security.BlessingPattern][][]byte)
+	defer a.mu.RUnlock()
+	a.mu.RLock()
+	for p, keys := range a.principal.Roots().Dump() {
+		for _, key := range keys {
+			marshaledKey, err := key.MarshalBinary()
+			if err != nil {
+				return nil, err
+			}
+			ret[p] = append(ret[p], marshaledKey)
+		}
+	}
+	return ret, nil
+}
+
+func (a *agentd) BlessingRootsDebugString() (string, error) {
+	defer a.mu.RUnlock()
+	a.mu.RLock()
+	return a.principal.Roots().DebugString(), nil
+}
+
+func (m *keymgr) ServePrincipal(handle [agent.PrincipalHandleByteSize]byte, path string) error {
+	if _, err := m.readKey(handle); err != nil {
+		return err
+	}
+	defer m.mu.Unlock()
+	m.mu.Lock()
+	data, ok := m.cache[handle]
+	if !ok {
+		return fmt.Errorf("key deleted")
+	}
+	if data.agent != nil {
+		return verror.NewErrExist(nil)
+	}
+	ipc := ipc.NewIPC()
+	if err := ServeAgent(ipc, data.p); err != nil {
+		return err
+	}
+	if err := ipc.Listen(path); err != nil {
+		return err
+	}
+	data.agent = ipc
+	m.cache[handle] = data
+	return nil
+}
+
+func (m *keymgr) StopServing(handle [agent.PrincipalHandleByteSize]byte) error {
+	if _, err := m.readKey(handle); err != nil {
+		return err
+	}
+	defer m.mu.Unlock()
+	m.mu.Lock()
+	data, ok := m.cache[handle]
+	if !ok {
+		return fmt.Errorf("key deleted")
+	}
+	if data.agent == nil {
+		return verror.NewErrNoExist(nil)
+	}
+	data.agent.Close()
+	data.agent = nil
+	m.cache[handle] = data
+	return nil
+}
+
+func (m *keymgr) DeletePrincipal(handle [agent.PrincipalHandleByteSize]byte) error {
+	defer m.mu.Unlock()
+	m.mu.Lock()
+	data, cached := m.cache[handle]
+	if cached {
+		if data.agent != nil {
+			data.agent.Close()
+		}
+		delete(m.cache, handle)
+	}
+	filename := base64.URLEncoding.EncodeToString(handle[:])
+	keyErr := os.Remove(filepath.Join(m.path, "keys", filename))
+	credErr := os.RemoveAll(filepath.Join(m.path, "creds", filename))
+	if os.IsNotExist(keyErr) && !cached {
+		return verror.NewErrNoExist(nil)
+	} else if keyErr != nil {
+		return keyErr
+	}
+	return credErr
+}
diff --git a/services/agent/internal/server/sharing.go b/services/agent/internal/server/sharing.go
new file mode 100644
index 0000000..4433d27
--- /dev/null
+++ b/services/agent/internal/server/sharing.go
@@ -0,0 +1,78 @@
+// 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 server
+
+import (
+	"sync"
+)
+
+// watchers provides syncronization and notifications for a shared resource.
+type watchers struct {
+	mu      sync.RWMutex
+	nextId  int
+	clients map[int][]chan struct{}
+}
+
+func (p *watchers) lock() {
+	p.mu.Lock()
+}
+
+func (p *watchers) unlock(id int) {
+	for i, c := range p.clients {
+		if i != id {
+			for _, ch := range c {
+				// Non-blocking send. If the channel is full we don't need
+				// to send a duplicate flush.
+				select {
+				case ch <- struct{}{}:
+				default:
+				}
+			}
+		}
+	}
+	p.mu.Unlock()
+}
+
+func (p *watchers) rlock() {
+	p.mu.RLock()
+}
+
+func (p *watchers) runlock() {
+	p.mu.RUnlock()
+}
+
+func (p *watchers) newID() int {
+	p.mu.Lock()
+	id := p.nextId
+	p.nextId += 1
+	p.mu.Unlock()
+	return id
+}
+
+func (p *watchers) register(id int) chan struct{} {
+	ch := make(chan struct{}, 1)
+	p.mu.Lock()
+	p.clients[id] = append(p.clients[id], ch)
+	p.mu.Unlock()
+	return ch
+}
+
+func (p *watchers) unregister(id int, ch chan struct{}) {
+	p.mu.Lock()
+	defer p.mu.Unlock()
+	clients := p.clients[id]
+	max := len(clients) - 1
+	for i, v := range clients {
+		if v == ch {
+			clients[i], clients[max], p.clients[id] = clients[max], nil, clients[:max]
+			close(ch)
+			return
+		}
+	}
+}
+
+func newWatchers() *watchers {
+	return &watchers{clients: make(map[int][]chan struct{})}
+}
diff --git a/services/agent/internal/server/sharing_test.go b/services/agent/internal/server/sharing_test.go
new file mode 100644
index 0000000..a8ee809
--- /dev/null
+++ b/services/agent/internal/server/sharing_test.go
@@ -0,0 +1,71 @@
+// 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 server
+
+import (
+	"testing"
+
+	_ "v.io/x/ref/runtime/factories/generic"
+)
+
+func doRead(c chan struct{}) string {
+	select {
+	case _, ok := <-c:
+		if !ok {
+			return "closed"
+		}
+		return "ok"
+	default:
+		return "nada"
+	}
+}
+
+func TestNotify(t *testing.T) {
+	p := newWatchers()
+	a1, a2 := p.newID(), p.newID()
+	c1, c2 := p.register(a1), p.register(a2)
+
+	if got := doRead(c1); got != "nada" {
+		t.Errorf("agent1: Unexpected %s", got)
+	}
+	if got := doRead(c2); got != "nada" {
+		t.Errorf("agent2: Unexpected %s", got)
+	}
+	p.lock()
+	p.unlock(a1)
+
+	if got := doRead(c1); got != "nada" {
+		t.Errorf("agent1: Unexpected %s", got)
+	}
+	if got := doRead(c2); got != "ok" {
+		t.Errorf("agent2: Unexpected %s, wanted ok", got)
+	}
+
+	p.lock()
+	p.unlock(a2)
+	if got := doRead(c2); got != "nada" {
+		t.Errorf("agent2: Unexpected %s", got)
+	}
+	if got := doRead(c1); got != "ok" {
+		t.Errorf("agent1: Unexpected %s, wanted ok", got)
+	}
+
+	p.unregister(a2, c2)
+	if got := doRead(c2); got != "closed" {
+		t.Errorf("agent2: Unexpected %s", got)
+	}
+
+	p.lock()
+	p.unlock(a1)
+	if got := doRead(c1); got != "nada" {
+		t.Errorf("agent1: Unexpected %s", got)
+	}
+
+	p.lock()
+	p.unlock(a2)
+	if got := doRead(c1); got != "ok" {
+		t.Errorf("agent1: Unexpected %s, wanted ok", got)
+	}
+}
diff --git a/services/agent/internal/test_principal/doc.go b/services/agent/internal/test_principal/doc.go
new file mode 100644
index 0000000..05b9476
--- /dev/null
+++ b/services/agent/internal/test_principal/doc.go
@@ -0,0 +1,58 @@
+// 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
+
+/*
+Command test_principal runs tests against a principal.
+
+Usage:
+   test_principal
+
+The global flags are:
+ -alsologtostderr=true
+   log to standard error as well as files
+ -log_backtrace_at=:0
+   when logging hits line file:N, emit a stack trace
+ -log_dir=
+   if non-empty, write log files to this directory
+ -logtostderr=false
+   log to standard error instead of files
+ -max_stack_buf_size=4292608
+   max size in bytes of the buffer to use for logging stack traces
+ -metadata=<just specify -metadata to activate>
+   Displays metadata for the program and exits.
+ -stderrthreshold=2
+   logs at or above this threshold go to stderr
+ -v=0
+   log level for V logs
+ -v23.credentials=
+   directory to use for storing security credentials
+ -v23.i18n-catalogue=
+   18n catalogue files to load, comma separated
+ -v23.namespace.root=[/(dev.v.io/role/vprod/service/mounttabled)@ns.dev.v.io:8101]
+   local namespace root; can be repeated to provided multiple roots
+ -v23.proxy=
+   object name of proxy service to use to export services across network
+   boundaries
+ -v23.tcp.address=
+   address to listen on
+ -v23.tcp.protocol=wsh
+   protocol to listen with
+ -v23.vtrace.cache-size=1024
+   The number of vtrace traces to store in memory.
+ -v23.vtrace.collect-regexp=
+   Spans and annotations that match this regular expression will trigger trace
+   collection.
+ -v23.vtrace.dump-on-shutdown=true
+   If true, dump all stored traces on runtime shutdown.
+ -v23.vtrace.sample-rate=0
+   Rate (from 0.0 to 1.0) to sample vtrace traces.
+ -vmodule=
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
+*/
+package main
diff --git a/services/agent/internal/test_principal/main.go b/services/agent/internal/test_principal/main.go
new file mode 100644
index 0000000..40c4f5c
--- /dev/null
+++ b/services/agent/internal/test_principal/main.go
@@ -0,0 +1,155 @@
+// 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.
+
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go . -help
+
+package main
+
+import (
+	"crypto/ecdsa"
+	"crypto/elliptic"
+	"crypto/rand"
+	"fmt"
+	"reflect"
+	"runtime"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/security"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref"
+	"v.io/x/ref/lib/v23cmd"
+
+	_ "v.io/x/ref/runtime/factories/generic"
+)
+
+func newKey() security.PublicKey {
+	k, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+	if err != nil {
+		panic(err)
+	}
+	return security.NewECDSAPublicKey(&k.PublicKey)
+}
+
+func main() {
+	cmdline.HideGlobalFlagsExcept()
+	cmdline.Main(cmdTestPrincipal)
+}
+
+var cmdTestPrincipal = &cmdline.Command{
+	Runner: v23cmd.RunnerFunc(runTestPrincipal),
+	Name:   "test_principal",
+	Short:  "Runs tests against a principal",
+	Long:   "Command test_principal runs tests against a principal.",
+}
+
+func runTestPrincipal(ctx *context.T, env *cmdline.Env, args []string) error {
+	var errors []string
+	errorf := func(format string, args ...interface{}) {
+		_, file, line, _ := runtime.Caller(1)
+		errors = append(errors, fmt.Sprintf("%v:%d: %v", file, line, fmt.Sprintf(format, args...)))
+	}
+	p := v23.GetPrincipal(ctx)
+	// Make sure we're running under a pristine agent to begin with.
+	// The agent aims to be transparent, so use a collection of heuristics
+	// to detect this.
+	if got := env.Vars[ref.EnvCredentials]; len(got) != 0 {
+		errorf("%v environment variable is unexpectedly set", ref.EnvCredentials)
+	}
+	if got := env.Vars[ref.EnvAgentPath]; len(got) == 0 {
+		errorf("%v environment variable is not set", ref.EnvAgentPath)
+	}
+	// A pristine agent has a single blessing "agent_principal" (from agentd/main.go).
+	if blessings := p.BlessingsInfo(p.BlessingStore().Default()); len(blessings) != 1 {
+		errorf("Got %d blessings, expected 1: %v", len(blessings), blessings)
+	} else if _, ok := blessings["agent_principal"]; !ok {
+		errorf("No agent_principal blessins, got %v", blessings)
+	}
+
+	// BlessSelf
+	b, err := p.BlessSelf("batman")
+	if err != nil {
+		errorf("BlessSelf: %v", err)
+	}
+	// Bless
+	if _, err := p.Bless(newKey(), b, "delegate", security.UnconstrainedUse()); err != nil {
+		errorf("Bless: %v", err)
+	}
+	// Sign & PublicKey
+	signature, err := p.Sign([]byte("bugs bunny"))
+	if err != nil {
+		errorf("Sign: %v", err)
+	}
+	if !signature.Verify(p.PublicKey(), []byte("bugs bunny")) {
+		errorf("signature.Verify: %v", err)
+	}
+	// MintDischarge
+	cav, err := security.NewMethodCaveat("method")
+	if err != nil {
+		errorf("security.MethodCaveat: %v", err)
+	}
+	tpcav, err := security.NewPublicKeyCaveat(p.PublicKey(), "location", security.ThirdPartyRequirements{}, cav)
+	if err != nil {
+		errorf("security.NewPublicKeyCaveat: %v", err)
+	}
+	dis, err := p.MintDischarge(tpcav, cav)
+	if err != nil {
+		errorf("MintDischarge: %v", err)
+	}
+	// BlessingRoots
+	if err := p.Roots().Recognized(p.PublicKey(), "batman"); err == nil {
+		errorf("Roots().Recognized returned nil")
+	}
+	if err := p.AddToRoots(b); err != nil {
+		errorf("AddToRoots: %v", err)
+	}
+	if err := p.Roots().Recognized(p.PublicKey(), "batman"); err != nil {
+		errorf("Roots().Recognized: %v", err)
+	}
+	// BlessingStore: Defaults
+	if err := p.BlessingStore().SetDefault(security.Blessings{}); err != nil {
+		errorf("BlessingStore().SetDefault: %v", err)
+	}
+	if def := p.BlessingStore().Default(); !def.IsZero() {
+		errorf("BlessingStore().Default returned %v, want empty", def)
+	}
+	if err := p.BlessingStore().SetDefault(b); err != nil {
+		errorf("BlessingStore().SetDefault: %v", err)
+	}
+	if def := p.BlessingStore().Default(); !reflect.DeepEqual(def, b) {
+		errorf("BlessingStore().Default returned [%v], want [%v]", def, b)
+	}
+	// BlessingStore: Set & ForPeer
+	// First, clear out the self-generated default of the blessing store.
+	if _, err := p.BlessingStore().Set(security.Blessings{}, security.AllPrincipals); err != nil {
+		errorf("BlessingStore().Set(nil, %q): %v", security.AllPrincipals, err)
+	}
+	if forpeer := p.BlessingStore().ForPeer("superman/friend"); !forpeer.IsZero() {
+		errorf("BlessingStore().ForPeer unexpectedly returned %v", forpeer)
+	}
+	if old, err := p.BlessingStore().Set(b, "superman"); err != nil {
+		errorf("BlessingStore().Set returned (%v, %v)", old, err)
+	}
+	if forpeer := p.BlessingStore().ForPeer("superman/friend"); !reflect.DeepEqual(forpeer, b) {
+		errorf("BlessingStore().ForPeer returned %v and not %v", forpeer, b)
+	}
+	p.BlessingStore().CacheDischarge(dis, tpcav, security.DischargeImpetus{})
+	if got := p.BlessingStore().Discharge(tpcav, security.DischargeImpetus{}); !dis.Equivalent(got) {
+		errorf("BlessingStore().Discharges returned %#v want %#v", got, dis)
+	}
+	p.BlessingStore().ClearDischarges(dis)
+	if got := p.BlessingStore().Discharge(tpcav, security.DischargeImpetus{}); got.ID() != "" {
+		errorf("BlessingStore().Discharges returned %#v want empty", got)
+	}
+
+	if len(errors) > 0 {
+		// Print out all errors and exit with failure.
+		for _, e := range errors {
+			fmt.Fprintln(env.Stderr, e)
+		}
+		return cmdline.ErrExitCode(1)
+	}
+	return nil
+}
diff --git a/services/agent/internal/unixfd/unixfd.go b/services/agent/internal/unixfd/unixfd.go
new file mode 100644
index 0000000..025ccaf
--- /dev/null
+++ b/services/agent/internal/unixfd/unixfd.go
@@ -0,0 +1,352 @@
+// 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 unixfd provides provides support for Dialing and Listening
+// on already connected file descriptors (like those returned by socketpair).
+package unixfd
+
+import (
+	"fmt"
+	"io"
+	"net"
+	"os"
+	"strconv"
+	"sync"
+	"syscall"
+	"time"
+	"unsafe"
+
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/verror"
+)
+
+const pkgPath = "v.io/x/ref/services/agent/internal/unixfd"
+
+var (
+	errListenerClosed            = verror.Register(pkgPath+".errListenerClosed", verror.NoRetry, "{1:}{2:} listener closed{:_}")
+	errListenerAlreadyClosed     = verror.Register(pkgPath+".errListenerAlreadyClosed", verror.NoRetry, "{1:}{2:} listener already closed{:_}")
+	errCantSendSocketWithoutData = verror.Register(pkgPath+".errCantSendSocketWithoutData", verror.NoRetry, "{1:}{2:} cannot send a socket without data.{:_}")
+	errWrongSentLength           = verror.Register(pkgPath+".errWrongSentLength", verror.NoRetry, "{1:}{2:} expected to send {3}, {4} bytes,  sent {5}, {6}{:_}")
+	errTooBigOOB                 = verror.Register(pkgPath+".errTooBigOOB", verror.NoRetry, "{1:}{2:} received too large oob data ({3}, max {4}){:_}")
+	errBadNetwork                = verror.Register(pkgPath+".errBadNetwork", verror.NoRetry, "{1:}{2:} invalid network{:_}")
+)
+
+const Network string = "unixfd"
+
+func init() {
+	rpc.RegisterProtocol(Network, unixFDConn, unixFDResolve, unixFDListen)
+}
+
+// singleConnListener implements net.Listener for an already-connected socket.
+// This is different from net.FileListener, which calls syscall.Listen
+// on an unconnected socket.
+type singleConnListener struct {
+	c    chan net.Conn
+	addr net.Addr
+	sync.Mutex
+}
+
+func (l *singleConnListener) getChan() chan net.Conn {
+	l.Lock()
+	defer l.Unlock()
+	return l.c
+}
+
+func (l *singleConnListener) Accept() (net.Conn, error) {
+	c := l.getChan()
+	if c == nil {
+		return nil, verror.New(errListenerClosed, nil)
+	}
+	if conn, ok := <-c; ok {
+		return conn, nil
+	}
+	return nil, io.EOF
+}
+
+func (l *singleConnListener) Close() error {
+	l.Lock()
+	defer l.Unlock()
+	lc := l.c
+	if lc == nil {
+		return verror.New(errListenerAlreadyClosed, nil)
+	}
+	close(l.c)
+	l.c = nil
+	// If the socket was never Accept'ed we need to close it.
+	if c, ok := <-lc; ok {
+		return c.Close()
+	}
+	return nil
+}
+
+func (l *singleConnListener) Addr() net.Addr {
+	return l.addr
+}
+
+func unixFDConn(ctx *context.T, protocol, address string, timeout time.Duration) (net.Conn, error) {
+	// TODO(cnicolaou): have this respect the timeout. Possibly have a helper
+	// function that can be generally used for this, but in practice, I think
+	// it'll be cleaner to use the underlying protocol's deadline support of it
+	// has it.
+	fd, err := strconv.ParseInt(address, 10, 32)
+	if err != nil {
+		return nil, err
+	}
+	file := os.NewFile(uintptr(fd), "tmp")
+	conn, err := net.FileConn(file)
+	// 'file' is not used after this point, but we keep it open
+	// so that 'address' remains valid.
+	if err != nil {
+		file.Close()
+		return nil, err
+	}
+	// We wrap 'conn' so we can customize the address, and also
+	// to close 'file'.
+	return &fdConn{addr: addr(address), sock: file, Conn: conn}, nil
+}
+
+type fdConn struct {
+	addr net.Addr
+	sock *os.File
+	net.Conn
+
+	mu     sync.Mutex
+	closed bool
+}
+
+func (c *fdConn) Close() (err error) {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+
+	if c.closed {
+		return nil
+	}
+
+	c.closed = true
+	defer c.sock.Close()
+	return c.Conn.Close()
+}
+
+func (c *fdConn) LocalAddr() net.Addr {
+	return c.addr
+}
+
+func (c *fdConn) RemoteAddr() net.Addr {
+	return c.addr
+}
+
+func unixFDResolve(ctx *context.T, _, address string) (string, string, error) {
+	return Network, address, nil
+}
+
+func unixFDListen(ctx *context.T, protocol, address string) (net.Listener, error) {
+	conn, err := unixFDConn(ctx, protocol, address, 0)
+	if err != nil {
+		return nil, err
+	}
+	c := make(chan net.Conn, 1)
+	c <- conn
+	return &singleConnListener{c, conn.LocalAddr(), sync.Mutex{}}, nil
+}
+
+type addr string
+
+func (a addr) Network() string { return Network }
+func (a addr) String() string  { return string(a) }
+
+// Addr returns a net.Addr for the unixfd network for the given file descriptor.
+func Addr(fd uintptr) net.Addr {
+	return addr(fmt.Sprintf("%d", fd))
+}
+
+type fileDescriptor struct {
+	fd   chan int
+	name string
+}
+
+func newFd(fd int, name string) *fileDescriptor {
+	ch := make(chan int, 1)
+	ch <- fd
+	close(ch)
+	d := &fileDescriptor{ch, name}
+	return d
+}
+
+func (f *fileDescriptor) releaseAddr() net.Addr {
+	if fd, ok := <-f.fd; ok {
+		return Addr(uintptr(fd))
+	}
+	return nil
+}
+
+func (f *fileDescriptor) releaseFile() *os.File {
+	if fd, ok := <-f.fd; ok {
+		return os.NewFile(uintptr(fd), f.name)
+	}
+	return nil
+}
+
+// maybeClose closes the file descriptor, if it hasn't been released.
+func (f *fileDescriptor) maybeClose() {
+	if file := f.releaseFile(); file != nil {
+		file.Close()
+	}
+}
+
+// Socketpair returns a pair of connected sockets for communicating with a child process.
+func Socketpair() (*net.UnixConn, *os.File, error) {
+	lfd, rfd, err := socketpair()
+	if err != nil {
+		return nil, nil, err
+	}
+	defer rfd.maybeClose()
+	file := lfd.releaseFile()
+	// FileConn dups the fd, so we still want to close the original one.
+	defer file.Close()
+	conn, err := net.FileConn(file)
+	if err != nil {
+		return nil, nil, err
+	}
+	return conn.(*net.UnixConn), rfd.releaseFile(), nil
+}
+
+func socketpair() (local, remote *fileDescriptor, err error) {
+	syscall.ForkLock.RLock()
+	fds, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
+	if err == nil {
+		syscall.CloseOnExec(fds[0])
+		syscall.CloseOnExec(fds[1])
+	}
+	syscall.ForkLock.RUnlock()
+	if err != nil {
+		return nil, nil, err
+	}
+	return newFd(fds[0], "local"), newFd(fds[1], "remote"), nil
+}
+
+// SendConnection creates a new connected socket and sends
+// one end over 'conn', along with 'data'. It returns the address for
+// the local end of the socketpair.
+// Note that the returned address is an open file descriptor,
+// which you must close if you do not Dial or Listen to the address.
+func SendConnection(conn *net.UnixConn, data []byte) (addr net.Addr, err error) {
+	if len(data) < 1 {
+		return nil, verror.New(errCantSendSocketWithoutData, nil)
+	}
+	remote, local, err := socketpair()
+	if err != nil {
+		return nil, err
+	}
+	defer local.maybeClose()
+	rfile := remote.releaseFile()
+
+	rights := syscall.UnixRights(int(rfile.Fd()))
+	n, oobn, err := conn.WriteMsgUnix(data, rights, nil)
+	if err != nil {
+		rfile.Close()
+		return nil, err
+	} else if n != len(data) || oobn != len(rights) {
+		rfile.Close()
+		return nil, verror.New(errWrongSentLength, nil, len(data), len(rights), n, oobn)
+	}
+	// Wait for the other side to acknowledge.
+	// This is to work around a race on OS X where it appears we can close
+	// the file descriptor before it gets transfered over the socket.
+	f := local.releaseFile()
+	syscall.ForkLock.Lock()
+	fd, err := syscall.Dup(int(f.Fd()))
+	if err != nil {
+		syscall.ForkLock.Unlock()
+		f.Close()
+		rfile.Close()
+		return nil, err
+	}
+	syscall.CloseOnExec(fd)
+	syscall.ForkLock.Unlock()
+	newConn, err := net.FileConn(f)
+	f.Close()
+	if err != nil {
+		rfile.Close()
+		return nil, err
+	}
+	newConn.Read(make([]byte, 1))
+	newConn.Close()
+	rfile.Close()
+
+	return Addr(uintptr(fd)), nil
+}
+
+const cmsgDataLength = int(unsafe.Sizeof(int(1)))
+
+// ReadConnection reads a connection and additional data sent on 'conn' via a call to SendConnection.
+// 'buf' must be large enough to hold the data.
+// The returned function must be called when you are ready for the other side
+// to start sending data, but before writing anything to the connection.
+// If there is an error you must still call the function before closing the connection.
+func ReadConnection(conn *net.UnixConn, buf []byte) (net.Addr, int, func(), error) {
+	oob := make([]byte, syscall.CmsgLen(cmsgDataLength))
+	n, oobn, _, _, err := conn.ReadMsgUnix(buf, oob)
+	if err != nil {
+		return nil, n, nil, err
+	}
+	if oobn > len(oob) {
+		return nil, n, nil, verror.New(errTooBigOOB, nil, oobn, len(oob))
+	}
+	scms, err := syscall.ParseSocketControlMessage(oob[:oobn])
+	if err != nil {
+		return nil, n, nil, err
+	}
+	fd := -1
+	// Loop through any file descriptors we are sent, and close
+	// all extras.
+	for _, scm := range scms {
+		fds, err := syscall.ParseUnixRights(&scm)
+		if err != nil {
+			return nil, n, nil, err
+		}
+		for _, f := range fds {
+			if fd == -1 {
+				fd = f
+			} else if f != -1 {
+				syscall.Close(f)
+			}
+		}
+	}
+	if fd == -1 {
+		return nil, n, nil, nil
+	}
+	result := Addr(uintptr(fd))
+	syscall.ForkLock.Lock()
+	fd, err = syscall.Dup(fd)
+	if err != nil {
+		syscall.ForkLock.Unlock()
+		CloseUnixAddr(result)
+		return nil, n, nil, err
+	}
+	syscall.CloseOnExec(fd)
+	syscall.ForkLock.Unlock()
+	file := os.NewFile(uintptr(fd), "newconn")
+	newconn, err := net.FileConn(file)
+	file.Close()
+	if err != nil {
+		CloseUnixAddr(result)
+		return nil, n, nil, err
+	}
+	return result, n, func() {
+		newconn.Write(make([]byte, 1))
+		newconn.Close()
+	}, nil
+}
+
+func CloseUnixAddr(addr net.Addr) error {
+	if addr.Network() != Network {
+		return verror.New(errBadNetwork, nil)
+	}
+	fd, err := strconv.ParseInt(addr.String(), 10, 32)
+	if err != nil {
+		return err
+	}
+	return syscall.Close(int(fd))
+}
diff --git a/services/agent/internal/unixfd/unixfd_test.go b/services/agent/internal/unixfd/unixfd_test.go
new file mode 100644
index 0000000..06f7010
--- /dev/null
+++ b/services/agent/internal/unixfd/unixfd_test.go
@@ -0,0 +1,190 @@
+// 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 unixfd
+
+import (
+	"bytes"
+	"io"
+	"net"
+	"reflect"
+	"testing"
+
+	"v.io/v23/context"
+)
+
+type nothing struct{}
+
+func dial(fd *fileDescriptor) (net.Conn, net.Addr, error) {
+	addr := fd.releaseAddr()
+	ctx, _ := context.RootContext()
+	conn, err := unixFDConn(ctx, Network, addr.String(), 0)
+	return conn, addr, err
+}
+
+func listen(fd *fileDescriptor) (net.Listener, net.Addr, error) {
+	addr := fd.releaseAddr()
+	ctx, _ := context.RootContext()
+	l, err := unixFDListen(ctx, Network, addr.String())
+	return l, addr, err
+}
+
+func testWrite(t *testing.T, c net.Conn, data string) {
+	n, err := c.Write([]byte(data))
+	if err != nil {
+		t.Errorf("Write: %v", err)
+		return
+	}
+	if n != len(data) {
+		t.Errorf("Wrote %d bytes, expected %d", n, len(data))
+	}
+}
+
+func testRead(t *testing.T, c net.Conn, expected string) {
+	buf := make([]byte, len(expected)+2)
+	n, err := c.Read(buf)
+	if err != nil {
+		t.Errorf("Read: %v", err)
+		return
+	}
+	if n != len(expected) || !bytes.Equal(buf[0:n], []byte(expected)) {
+		t.Errorf("got %q, expected %q", buf[0:n], expected)
+	}
+}
+
+func TestDial(t *testing.T) {
+	local, remote, err := socketpair()
+	if err != nil {
+		t.Fatalf("socketpair: %v", err)
+	}
+	a, a_addr, err := dial(local)
+	if err != nil {
+		t.Fatalf("dial: %v", err)
+	}
+	b, b_addr, err := dial(remote)
+	if err != nil {
+		t.Fatalf("dial: %v", err)
+	}
+
+	testWrite(t, a, "TEST1")
+	testRead(t, b, "TEST1")
+	testWrite(t, b, "TEST2")
+	testRead(t, a, "TEST2")
+
+	if !reflect.DeepEqual(a.LocalAddr(), a_addr) {
+		t.Errorf("Invalid address %v, expected %v", a.LocalAddr(), a_addr)
+	}
+	if !reflect.DeepEqual(a.RemoteAddr(), a_addr) {
+		t.Errorf("Invalid address %v, expected %v", a.RemoteAddr(), a_addr)
+	}
+	if !reflect.DeepEqual(b.LocalAddr(), b_addr) {
+		t.Errorf("Invalid address %v, expected %v", a.LocalAddr(), b_addr)
+	}
+	if !reflect.DeepEqual(b.RemoteAddr(), b_addr) {
+		t.Errorf("Invalid address %v, expected %v", a.RemoteAddr(), b_addr)
+	}
+}
+
+func TestListen(t *testing.T) {
+	local, remote, err := socketpair()
+	if err != nil {
+		t.Fatalf("socketpair: %v", err)
+	}
+	a, _, err := dial(local)
+	if err != nil {
+		t.Fatalf("dial: %v", err)
+	}
+	l, _, err := listen(remote)
+	if err != nil {
+		t.Fatalf("listen: %v", err)
+	}
+	b, err := l.Accept()
+	if err != nil {
+		t.Fatalf("accept: %v", err)
+	}
+	start := make(chan nothing, 0)
+	done := make(chan nothing)
+	go func() {
+		defer close(done)
+		<-start
+		if _, err := l.Accept(); err != io.EOF {
+			t.Fatalf("accept: expected EOF, got %v", err)
+		}
+	}()
+
+	// block until the goroutine starts running
+	start <- nothing{}
+	testWrite(t, a, "LISTEN")
+	testRead(t, b, "LISTEN")
+
+	err = l.Close()
+	if err != nil {
+		t.Fatalf("close: %v", err)
+	}
+	<-done
+
+	// After closed, accept should fail immediately
+	_, err = l.Accept()
+	if err == nil {
+		t.Fatalf("Accept succeeded after close")
+	}
+	err = l.Close()
+	if err == nil {
+		t.Fatalf("Close succeeded twice")
+	}
+}
+
+func TestSendConnection(t *testing.T) {
+	server, client, err := Socketpair()
+	if err != nil {
+		t.Fatalf("Socketpair: %v", err)
+	}
+	uclient, err := net.FileConn(client)
+	if err != nil {
+		t.Fatalf("FileConn: %v", err)
+	}
+	var readErr error
+	var n int
+	var saddr net.Addr
+	done := make(chan struct{})
+	buf := make([]byte, 10)
+	go func() {
+		var ack func()
+		saddr, n, ack, readErr = ReadConnection(server, buf)
+		if ack != nil {
+			ack()
+		}
+		close(done)
+	}()
+	caddr, err := SendConnection(uclient.(*net.UnixConn), []byte("hello"))
+	if err != nil {
+		t.Fatalf("SendConnection: %v", err)
+	}
+	<-done
+	if readErr != nil {
+		t.Fatalf("ReadConnection: %v", readErr)
+	}
+	if saddr == nil {
+		t.Fatalf("ReadConnection returned nil, %d", n)
+	}
+	data := buf[0:n]
+	if !bytes.Equal([]byte("hello"), data) {
+		t.Fatalf("unexpected data %q", data)
+	}
+
+	ctx, _ := context.RootContext()
+	a, err := unixFDConn(ctx, Network, caddr.String(), 0)
+	if err != nil {
+		t.Fatalf("dial %v: %v", caddr, err)
+	}
+	b, err := unixFDConn(ctx, Network, saddr.String(), 0)
+	if err != nil {
+		t.Fatalf("dial %v: %v", saddr, err)
+	}
+
+	testWrite(t, a, "TEST1")
+	testRead(t, b, "TEST1")
+	testWrite(t, b, "TEST2")
+	testRead(t, a, "TEST2")
+}
diff --git a/services/agent/keymgr/client.go b/services/agent/keymgr/client.go
new file mode 100644
index 0000000..7515f58
--- /dev/null
+++ b/services/agent/keymgr/client.go
@@ -0,0 +1,161 @@
+// 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 keymgr implements a client for deviced to manage keys in the agentd
+// process.
+package keymgr
+
+import (
+	"net"
+	"os"
+	"strconv"
+	"sync"
+
+	"v.io/v23/context"
+	"v.io/v23/verror"
+	"v.io/x/ref/services/agent"
+	"v.io/x/ref/services/agent/internal/ipc"
+	"v.io/x/ref/services/agent/internal/server"
+	"v.io/x/ref/services/agent/internal/unixfd"
+)
+
+const pkgPath = "v.io/x/ref/services/agent/keymgr"
+
+// Errors
+var (
+	errInvalidResponse = verror.Register(pkgPath+".errInvalidResponse",
+		verror.NoRetry, "{1:}{2:} invalid response from agent. (expected {3} bytes, got {4})")
+	errInvalidKeyHandle = verror.Register(pkgPath+".errInvalidKeyHandle",
+		verror.NoRetry, "{1:}{2:} Invalid key handle")
+)
+
+const defaultManagerSocket = 4
+
+type keyManager struct {
+	conn *ipc.IPCConn
+}
+
+type Agent struct {
+	conn *net.UnixConn // Guarded by mu
+	mu   sync.Mutex
+}
+
+// NewAgent returns a client connected to the agent on the default file descriptors.
+func NewAgent() (*Agent, error) {
+	return newAgent(defaultManagerSocket)
+}
+
+// NewKeyManager returns a client connected to the specified KeyManager.
+func NewKeyManager(path string) (agent.KeyManager, error) {
+	i := ipc.NewIPC()
+	conn, err := i.Connect(path)
+	var m *keyManager
+	if err == nil {
+		m = &keyManager{conn}
+	}
+	return m, err
+}
+
+func NewLocalAgent(path string, passphrase []byte) (agent.KeyManager, error) {
+	return server.NewLocalKeyManager(path, passphrase)
+}
+
+func newAgent(fd int) (a *Agent, err error) {
+	file := os.NewFile(uintptr(fd), "fd")
+	defer file.Close()
+	conn, err := net.FileConn(file)
+	if err != nil {
+		return nil, err
+	}
+
+	return &Agent{conn: conn.(*net.UnixConn)}, nil
+}
+
+// TODO(caprita): Get rid of *context.T arg.  Doesn't seem to be used.
+
+// NewPrincipal creates a new principal and returns the handle and a socket serving
+// the principal.
+// Typically the socket will be passed to a child process using cmd.ExtraFiles.
+func (a *Agent) NewPrincipal(ctx *context.T, inMemory bool) (handle []byte, conn *os.File, err error) {
+	req := make([]byte, 1)
+	if inMemory {
+		req[0] = 1
+	}
+	a.mu.Lock()
+	defer a.mu.Unlock()
+	conn, err = a.connect(req)
+	if err != nil {
+		return nil, nil, err
+	}
+	buf := make([]byte, agent.PrincipalHandleByteSize)
+	n, err := a.conn.Read(buf)
+	if err != nil {
+		conn.Close()
+		return nil, nil, err
+	}
+	if n != agent.PrincipalHandleByteSize {
+		conn.Close()
+		return nil, nil, verror.New(errInvalidResponse, ctx, agent.PrincipalHandleByteSize, n)
+	}
+	return buf, conn, nil
+}
+
+// NewPrincipal creates a new principal and returns a handle.
+// The handle may be passed to ServePrincipal to start an agent serving the principal.
+func (m *keyManager) NewPrincipal(inMemory bool) (handle [agent.PrincipalHandleByteSize]byte, err error) {
+	args := []interface{}{inMemory}
+	err = m.conn.Call("NewPrincipal", args, &handle)
+	return
+}
+
+func (a *Agent) connect(req []byte) (*os.File, error) {
+	addr, err := unixfd.SendConnection(a.conn, req)
+	if err != nil {
+		return nil, err
+	}
+	fd, err := strconv.ParseInt(addr.String(), 10, 32)
+	if err != nil {
+		return nil, err
+	}
+	return os.NewFile(uintptr(fd), "client"), nil
+}
+
+// NewConnection creates a connection to an agent which exports a principal
+// previously created with NewPrincipal.
+// Typically this will be passed to a child process using cmd.ExtraFiles.
+func (a *Agent) NewConnection(handle []byte) (*os.File, error) {
+	if len(handle) != agent.PrincipalHandleByteSize {
+		return nil, verror.New(errInvalidKeyHandle, nil)
+	}
+	a.mu.Lock()
+	defer a.mu.Unlock()
+	return a.connect(handle)
+}
+
+// ServePrincipal creates a socket at socketPath and serves a principal
+// previously created with NewPrincipal.
+func (m *keyManager) ServePrincipal(handle [agent.PrincipalHandleByteSize]byte, socketPath string) error {
+	args := []interface{}{handle, socketPath}
+	return m.conn.Call("ServePrincipal", args)
+}
+
+// StopServing shuts down a server previously started with ServePrincipal.
+// The principal is not deleted and the server can be restarted by calling
+// ServePrincipal again.
+func (m *keyManager) StopServing(handle [agent.PrincipalHandleByteSize]byte) error {
+	args := []interface{}{handle}
+	return m.conn.Call("StopServing", args)
+}
+
+// DeletePrincipal shuts down a server started by ServePrincipal and additionally
+// deletes the principal.
+func (m *keyManager) DeletePrincipal(handle [agent.PrincipalHandleByteSize]byte) error {
+	args := []interface{}{handle}
+	return m.conn.Call("DeletePrincipal", args)
+}
+
+func (m *keyManager) Close() error {
+	m.conn.Close()
+	return nil
+}
diff --git a/services/agent/keymgr/keymgr_test.go b/services/agent/keymgr/keymgr_test.go
new file mode 100644
index 0000000..7a4bd77
--- /dev/null
+++ b/services/agent/keymgr/keymgr_test.go
@@ -0,0 +1,176 @@
+// 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 keymgr
+
+import (
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"reflect"
+	"testing"
+
+	"v.io/v23/security"
+	"v.io/v23/verror"
+	"v.io/x/ref/services/agent"
+	"v.io/x/ref/services/agent/agentlib"
+	"v.io/x/ref/services/agent/internal/ipc"
+	"v.io/x/ref/services/agent/internal/server"
+
+	_ "v.io/x/ref/runtime/factories/generic"
+)
+
+func createAgent(path string) (agent.KeyManager, func(), error) {
+	var defers []func()
+	cleanup := func() {
+		for _, f := range defers {
+			f()
+		}
+	}
+	i := ipc.NewIPC()
+	if err := server.ServeKeyManager(i, path, nil); err != nil {
+		return nil, cleanup, err
+	}
+	defers = append(defers, func() { os.RemoveAll(path) })
+	sock := filepath.Join(path, "keymgr.sock")
+	if err := i.Listen(sock); err != nil {
+		return nil, cleanup, err
+	}
+	defers = append(defers, i.Close)
+
+	m, err := NewKeyManager(sock)
+	return m, cleanup, err
+}
+
+func TestNoDeviceManager(t *testing.T) {
+	agent, cleanup, err := createAgent("")
+	defer cleanup()
+	if err == nil {
+		t.Fatal(err)
+	}
+	if agent != nil {
+		t.Fatal("No agent should be created when key path is empty")
+	}
+}
+
+func createClient(deviceAgent agent.KeyManager, id [64]byte) (security.Principal, error) {
+	dir, err := ioutil.TempDir("", "conn")
+	if err != nil {
+		return nil, err
+	}
+	path := filepath.Join(dir, "sock")
+	if err := deviceAgent.ServePrincipal(id, path); err != nil {
+		return nil, err
+	}
+	defer os.RemoveAll(dir)
+	return agentlib.NewAgentPrincipalX(path)
+}
+
+func TestSigning(t *testing.T) {
+	path, err := ioutil.TempDir("", "agent")
+	if err != nil {
+		t.Fatal(err)
+	}
+	agent, cleanup, err := createAgent(path)
+	defer cleanup()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	id1, err := agent.NewPrincipal(false)
+	if err != nil {
+		t.Fatal(err)
+	}
+	id2, err := agent.NewPrincipal(false)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	dir, err := os.Open(filepath.Join(path, "keys"))
+	if err != nil {
+		t.Fatal(err)
+	}
+	files, err := dir.Readdir(-1)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if len(files) != 2 {
+		t.Errorf("Expected 2 files created, found %d", len(files))
+	}
+
+	a, err := createClient(agent, id1)
+	if err != nil {
+		t.Fatal(err)
+	}
+	// Serving again should be an error
+	if _, err := createClient(agent, id1); verror.ErrorID(err) != verror.ErrExist.ID {
+		t.Fatalf("Expected ErrExist, got %v", err)
+	}
+
+	b, err := createClient(agent, id2)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if reflect.DeepEqual(a.PublicKey(), b.PublicKey()) {
+		t.Fatal("Keys should not be equal")
+	}
+	sig1, err := a.Sign([]byte("foobar"))
+	if err != nil {
+		t.Fatal(err)
+	}
+	sig2, err := b.Sign([]byte("foobar"))
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !sig1.Verify(a.PublicKey(), []byte("foobar")) {
+		t.Errorf("Signature a fails verification")
+	}
+	if !sig2.Verify(b.PublicKey(), []byte("foobar")) {
+		t.Errorf("Signature b fails verification")
+	}
+	if sig2.Verify(a.PublicKey(), []byte("foobar")) {
+		t.Errorf("Signatures should not cross verify")
+	}
+}
+
+func TestInMemorySigning(t *testing.T) {
+	path, err := ioutil.TempDir("", "agent")
+	if err != nil {
+		t.Fatal(err)
+	}
+	agent, cleanup, err := createAgent(path)
+	defer cleanup()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	id, err := agent.NewPrincipal(true)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	dir, err := os.Open(filepath.Join(path, "keys"))
+	if err != nil {
+		t.Fatal(err)
+	}
+	files, err := dir.Readdir(-1)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if len(files) != 0 {
+		t.Errorf("Expected 0 files created, found %d", len(files))
+	}
+
+	c, err := createClient(agent, id)
+	if err != nil {
+		t.Fatal(err)
+	}
+	sig, err := c.Sign([]byte("foobar"))
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !sig.Verify(c.PublicKey(), []byte("foobar")) {
+		t.Errorf("Signature a fails verification")
+	}
+}
diff --git a/services/agent/model.go b/services/agent/model.go
new file mode 100644
index 0000000..38d6ece
--- /dev/null
+++ b/services/agent/model.go
@@ -0,0 +1,27 @@
+// 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 agent
+
+import (
+	"crypto/sha512"
+	"io"
+
+	"v.io/v23/security"
+)
+
+type Principal interface {
+	security.Principal
+	io.Closer
+}
+
+const PrincipalHandleByteSize = sha512.Size
+
+type KeyManager interface {
+	NewPrincipal(inMemory bool) (handle [PrincipalHandleByteSize]byte, err error)
+	ServePrincipal(handle [PrincipalHandleByteSize]byte, socketPath string) error
+	StopServing(handle [PrincipalHandleByteSize]byte) error
+	DeletePrincipal(handle [PrincipalHandleByteSize]byte) error
+	io.Closer
+}
diff --git a/services/agent/vbecome/doc.go b/services/agent/vbecome/doc.go
new file mode 100644
index 0000000..7dcdc36
--- /dev/null
+++ b/services/agent/vbecome/doc.go
@@ -0,0 +1,68 @@
+// 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
+
+/*
+Command vbecome executes commands with a derived Vanadium principal.
+
+Usage:
+   vbecome [flags] <command> [command args...]
+
+The vbecome flags are:
+ -duration=1h0m0s
+   Duration for the blessing.
+ -name=
+   Name to use for the blessing.
+ -role=
+   Role object from which to request the blessing. If set, the blessings from
+   this role server are used and --name is ignored. If not set, the default
+   blessings of the calling principal are extended with --name.
+
+The global flags are:
+ -alsologtostderr=true
+   log to standard error as well as files
+ -log_backtrace_at=:0
+   when logging hits line file:N, emit a stack trace
+ -log_dir=
+   if non-empty, write log files to this directory
+ -logtostderr=false
+   log to standard error instead of files
+ -max_stack_buf_size=4292608
+   max size in bytes of the buffer to use for logging stack traces
+ -metadata=<just specify -metadata to activate>
+   Displays metadata for the program and exits.
+ -stderrthreshold=2
+   logs at or above this threshold go to stderr
+ -v=0
+   log level for V logs
+ -v23.credentials=
+   directory to use for storing security credentials
+ -v23.i18n-catalogue=
+   18n catalogue files to load, comma separated
+ -v23.namespace.root=[/(dev.v.io/role/vprod/service/mounttabled)@ns.dev.v.io:8101]
+   local namespace root; can be repeated to provided multiple roots
+ -v23.proxy=
+   object name of proxy service to use to export services across network
+   boundaries
+ -v23.tcp.address=
+   address to listen on
+ -v23.tcp.protocol=wsh
+   protocol to listen with
+ -v23.vtrace.cache-size=1024
+   The number of vtrace traces to store in memory.
+ -v23.vtrace.collect-regexp=
+   Spans and annotations that match this regular expression will trigger trace
+   collection.
+ -v23.vtrace.dump-on-shutdown=true
+   If true, dump all stored traces on runtime shutdown.
+ -v23.vtrace.sample-rate=0
+   Rate (from 0.0 to 1.0) to sample vtrace traces.
+ -vmodule=
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
+*/
+package main
diff --git a/services/agent/vbecome/v23_test.go b/services/agent/vbecome/v23_test.go
new file mode 100644
index 0000000..69f2726
--- /dev/null
+++ b/services/agent/vbecome/v23_test.go
@@ -0,0 +1,34 @@
+// 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 main_test
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
+	"v.io/x/ref/test/v23tests"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	modules.DispatchAndExitIfChild()
+	cleanup := v23tests.UseSharedBinDir()
+	r := m.Run()
+	cleanup()
+	os.Exit(r)
+}
+
+func TestV23BecomeRole(t *testing.T) {
+	v23tests.RunTest(t, V23TestBecomeRole)
+}
+
+func TestV23BecomeName(t *testing.T) {
+	v23tests.RunTest(t, V23TestBecomeName)
+}
diff --git a/services/agent/vbecome/vbecome.go b/services/agent/vbecome/vbecome.go
new file mode 100644
index 0000000..15abcc0
--- /dev/null
+++ b/services/agent/vbecome/vbecome.go
@@ -0,0 +1,187 @@
+// 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.
+
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go . -help
+
+package main
+
+import (
+	"crypto/ecdsa"
+	"crypto/elliptic"
+	"crypto/rand"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"syscall"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/security"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref"
+	vsecurity "v.io/x/ref/lib/security"
+	"v.io/x/ref/lib/v23cmd"
+	"v.io/x/ref/services/agent/internal/ipc"
+	"v.io/x/ref/services/agent/internal/server"
+	"v.io/x/ref/services/role"
+
+	_ "v.io/x/ref/runtime/factories/generic"
+)
+
+var (
+	durationFlag time.Duration
+	nameFlag     string
+	roleFlag     string
+)
+
+var cmdVbecome = &cmdline.Command{
+	Runner:   v23cmd.RunnerFunc(vbecome),
+	Name:     "vbecome",
+	Short:    "executes commands with a derived Vanadium principal",
+	Long:     "Command vbecome executes commands with a derived Vanadium principal.",
+	ArgsName: "<command> [command args...]",
+}
+
+const childAgentFd = 3
+const keyServerFd = 4
+
+func main() {
+	cmdline.HideGlobalFlagsExcept()
+	syscall.CloseOnExec(childAgentFd)
+	syscall.CloseOnExec(keyServerFd)
+
+	cmdVbecome.Flags.DurationVar(&durationFlag, "duration", 1*time.Hour, "Duration for the blessing.")
+	cmdVbecome.Flags.StringVar(&nameFlag, "name", "", "Name to use for the blessing.")
+	cmdVbecome.Flags.StringVar(&roleFlag, "role", "", "Role object from which to request the blessing. If set, the blessings from this role server are used and --name is ignored. If not set, the default blessings of the calling principal are extended with --name.")
+
+	cmdline.Main(cmdVbecome)
+}
+
+func vbecome(ctx *context.T, env *cmdline.Env, args []string) error {
+	if len(args) == 0 {
+		if shell := env.Vars["SHELL"]; shell != "" {
+			args = []string{shell}
+		} else {
+			return fmt.Errorf("You must specify a command to run.")
+		}
+	}
+
+	key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+	if err != nil {
+		return err
+	}
+	signer := security.NewInMemoryECDSASigner(key)
+	principal, err := vsecurity.NewPrincipalFromSigner(signer, nil)
+	if err != nil {
+		return err
+	}
+
+	if len(roleFlag) == 0 {
+		if len(nameFlag) == 0 {
+			nameFlag = filepath.Base(args[0])
+		}
+		if err := bless(ctx, principal, nameFlag); err != nil {
+			return err
+		}
+		ctx, err = v23.WithPrincipal(ctx, principal)
+		if err != nil {
+			return err
+		}
+	} else {
+		// The role server expects the client's blessing name to end
+		// with RoleSuffix. This is to avoid accidentally granting role
+		// access to anything else that might have been blessed by the
+		// same principal.
+		if err := bless(ctx, principal, role.RoleSuffix); err != nil {
+			return err
+		}
+		ctx, err = v23.WithPrincipal(ctx, principal)
+		if err != nil {
+			return err
+		}
+		if err = setupRoleBlessings(ctx, roleFlag); err != nil {
+			return err
+		}
+	}
+
+	// Start an agent server.
+	i := ipc.NewIPC()
+	if err := server.ServeAgent(i, principal); err != nil {
+		return err
+	}
+	dir, err := ioutil.TempDir("", "vbecome")
+	if err != nil {
+		return err
+	}
+	defer os.RemoveAll(dir)
+	path := filepath.Join(dir, "sock")
+	if err := i.Listen(path); err != nil {
+		return err
+	}
+	defer i.Close()
+	if err = os.Setenv(ref.EnvAgentPath, path); err != nil {
+		ctx.Fatalf("setenv: %v", err)
+	}
+
+	return doExec(args)
+}
+
+func bless(ctx *context.T, p security.Principal, name string) error {
+	caveat, err := security.NewExpiryCaveat(time.Now().Add(durationFlag))
+	if err != nil {
+		ctx.Errorf("Couldn't create caveat")
+		return err
+	}
+
+	rp := v23.GetPrincipal(ctx)
+	blessing, err := rp.Bless(p.PublicKey(), rp.BlessingStore().Default(), name, caveat)
+	if err != nil {
+		ctx.Errorf("Couldn't bless")
+		return err
+	}
+
+	if err = p.BlessingStore().SetDefault(blessing); err != nil {
+		ctx.Errorf("Couldn't set default blessing")
+		return err
+	}
+	if _, err = p.BlessingStore().Set(blessing, security.AllPrincipals); err != nil {
+		ctx.Errorf("Couldn't set default client blessing")
+		return err
+	}
+	if err = p.AddToRoots(blessing); err != nil {
+		ctx.Errorf("Couldn't set trusted roots")
+		return err
+	}
+	return nil
+}
+
+func doExec(args []string) error {
+	cmd := exec.Command(args[0], args[1:]...)
+	cmd.Stdin = os.Stdin
+	cmd.Stdout = os.Stdout
+	cmd.Stderr = os.Stderr
+	return cmd.Run()
+}
+
+func setupRoleBlessings(ctx *context.T, roleStr string) error {
+	b, err := role.RoleClient(roleStr).SeekBlessings(ctx)
+	if err != nil {
+		return err
+	}
+	p := v23.GetPrincipal(ctx)
+	// TODO(rthellend,ashankar): Revisit this configuration.
+	// SetDefault: Should we expect users to want to act as a server on behalf of the role (by default?)
+	// AllPrincipals: Do we not want to be discriminating about which services we use the role blessing at.
+	if err := p.BlessingStore().SetDefault(b); err != nil {
+		return err
+	}
+	if _, err := p.BlessingStore().Set(b, security.AllPrincipals); err != nil {
+		return err
+	}
+	return nil
+}
diff --git a/services/agent/vbecome/vbecome_v23_test.go b/services/agent/vbecome/vbecome_v23_test.go
new file mode 100644
index 0000000..162baa6
--- /dev/null
+++ b/services/agent/vbecome/vbecome_v23_test.go
@@ -0,0 +1,67 @@
+// 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_test
+
+import (
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"regexp"
+
+	_ "v.io/x/ref/runtime/factories/generic"
+	"v.io/x/ref/test/v23tests"
+)
+
+//go:generate v23 test generate .
+
+func writeRoledConfig() (path string, shutdown func(), err error) {
+	dir, err := ioutil.TempDir("", "")
+	if err != nil {
+		return dir, nil, err
+	}
+	err = ioutil.WriteFile(filepath.Join(dir, "therole.conf"), []byte(`
+{
+  "Members": ["root/child"],
+  "Extend": true
+}
+`), 0644)
+	return dir, func() { os.RemoveAll(dir) }, err
+}
+
+func V23TestBecomeRole(t *v23tests.T) {
+	vbecome := t.BuildV23Pkg("v.io/x/ref/services/agent/vbecome")
+	principal := t.BuildV23Pkg("v.io/x/ref/cmd/principal")
+
+	roled := t.BuildV23Pkg("v.io/x/ref/services/role/roled")
+	roledCreds, _ := t.Shell().NewChildCredentials("master")
+	roled = roled.WithStartOpts(roled.StartOpts().WithCustomCredentials(roledCreds))
+
+	v23tests.RunRootMT(t, "--v23.tcp.address=127.0.0.1:0")
+
+	dir, shutdown, err := writeRoledConfig()
+	if err != nil {
+		t.Fatalf("Couldn't write roled config: %v", err)
+	}
+	defer shutdown()
+	roled.Start("--v23.tcp.address=127.0.0.1:0", "--config-dir", dir, "--name", "roled")
+
+	output := vbecome.Run("--role=roled/therole", principal.Path(), "dump")
+	want := regexp.MustCompile(`Default Blessings\s+root/master/therole/root/child`)
+	if !want.MatchString(output) {
+		t.Errorf("Principal didn't have the role blessing:\n %s", output)
+	}
+}
+
+func V23TestBecomeName(t *v23tests.T) {
+	vbecome := t.BuildV23Pkg("v.io/x/ref/services/agent/vbecome")
+	principal := t.BuildV23Pkg("v.io/x/ref/cmd/principal")
+
+	v23tests.RunRootMT(t, "--v23.tcp.address=127.0.0.1:0")
+	output := vbecome.Run("--name=bob", principal.Path(), "dump")
+	want := regexp.MustCompile(`Default Blessings\s+root/child/bob`)
+	if !want.MatchString(output) {
+		t.Errorf("Principal didn't have the expected blessing:\n %s", output)
+	}
+}
diff --git a/services/agent/wire.vdl b/services/agent/wire.vdl
new file mode 100644
index 0000000..b26c22d
--- /dev/null
+++ b/services/agent/wire.vdl
@@ -0,0 +1,90 @@
+// 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 agent defines an interface to keep a private key in memory, and for
+// clients to have access to the private key.
+//
+// Protocol
+//
+// The agent starts processes with the VEYRON_AGENT_FD set to one end of a
+// unix domain socket. To connect to the agent, a client should create
+// a unix domain socket pair. Then send one end of the socket to the agent
+// with 1 byte of data. The agent will then serve the Agent service on
+// the received socket, using SecurityNone.
+//
+// The agent also supports an optional mode where it can manage multiple principals.
+// Typically this is only used by Device Manager. In this mode, VEYRON_AGENT_FD
+// will be 3, and there will be another socket at fd 4.
+// Creating a new principal is similar to connecting to to agent: create a socket
+// pair and send one end on fd 4 with 1 byte of data.
+// Set the data to 1 to request the principal only be stored in memory.
+// The agent will create a new principal and respond with a principal handle on fd 4.
+// To connect using a previously created principal, create a socket pair and send
+// one end with the principal handle as data on fd 4. The agent will not send a
+// response on fd 4.
+// In either, you can use the normal process to connect to an agent over the
+// other end of the pair. Typically you would pass the other end to a child
+// process and set VEYRON_AGENT_FD so it knows to connect.
+//
+// The protocol also has limited support for caching: A client can
+// request notification when any other client modifies the principal so it
+// can flush the cache. See NotifyWhenChanged for details.
+package agent
+
+import (
+	"v.io/v23/security"
+)
+
+type Agent interface {
+	Bless(key []byte, wit security.WireBlessings, extension string, caveat security.Caveat, additionalCaveats []security.Caveat) (security.WireBlessings | error)
+	BlessSelf(name string, caveats []security.Caveat) (security.WireBlessings | error)
+	Sign(message []byte) (security.Signature | error)
+	MintDischarge(forCaveat, caveatOnDischarge security.Caveat, additionalCaveatsOnDischarge []security.Caveat) (security.WireDischarge | error)
+	PublicKey() ([]byte | error)
+	BlessingsByName(name security.BlessingPattern) ([]security.WireBlessings | error)
+	BlessingsInfo(blessings security.WireBlessings) (map[string][]security.Caveat | error)
+	AddToRoots(blessing security.WireBlessings) error
+
+	BlessingStoreSet(blessings security.WireBlessings, forPeers security.BlessingPattern) (security.WireBlessings | error)
+	BlessingStoreForPeer(peerBlessings []string) (security.WireBlessings | error)
+	BlessingStoreSetDefault(blessings security.WireBlessings) error
+	BlessingStoreDefault() (security.WireBlessings | error)
+	BlessingStorePeerBlessings() (map[security.BlessingPattern]security.WireBlessings | error)
+	BlessingStoreDebugString() (string | error)
+	BlessingStoreCacheDischarge(discharge security.WireDischarge, caveat security.Caveat, impetus security.DischargeImpetus) error
+	BlessingStoreClearDischarges(discharges []security.WireDischarge) error
+	BlessingStoreDischarge(caveat security.Caveat, impetus security.DischargeImpetus) (wd security.WireDischarge | error)
+
+	BlessingRootsAdd(root []byte, pattern security.BlessingPattern) error
+	BlessingRootsRecognized(root []byte, blessing string) error
+	BlessingRootsDump() (map[security.BlessingPattern][][]byte | error)
+	BlessingRootsDebugString() (string | error)
+
+	// Clients using caching should call NotifyWhenChanged upon connecting to
+	// the server. The server will stream back values whenever the client should
+	// flush the cache. The streamed value is arbitrary, simply flush whenever
+	// recieving a new item.
+	NotifyWhenChanged() stream<_, bool> error
+}
+
+type ConnInfo struct {
+	MinVersion, MaxVersion int32
+}
+
+type RpcRequest struct {
+	Id uint64
+	Method string
+	NumArgs uint32
+}
+
+type RpcResponse struct {
+	Id uint64
+	Err error
+	NumArgs uint32
+}
+
+type RpcMessage union {
+	Req RpcRequest
+	Resp RpcResponse
+}
diff --git a/services/agent/wire.vdl.go b/services/agent/wire.vdl.go
new file mode 100644
index 0000000..d3d1bd6
--- /dev/null
+++ b/services/agent/wire.vdl.go
@@ -0,0 +1,765 @@
+// 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 by the vanadium vdl tool.
+// Source: wire.vdl
+
+// Package agent defines an interface to keep a private key in memory, and for
+// clients to have access to the private key.
+//
+// Protocol
+//
+// The agent starts processes with the VEYRON_AGENT_FD set to one end of a
+// unix domain socket. To connect to the agent, a client should create
+// a unix domain socket pair. Then send one end of the socket to the agent
+// with 1 byte of data. The agent will then serve the Agent service on
+// the received socket, using SecurityNone.
+//
+// The agent also supports an optional mode where it can manage multiple principals.
+// Typically this is only used by Device Manager. In this mode, VEYRON_AGENT_FD
+// will be 3, and there will be another socket at fd 4.
+// Creating a new principal is similar to connecting to to agent: create a socket
+// pair and send one end on fd 4 with 1 byte of data.
+// Set the data to 1 to request the principal only be stored in memory.
+// The agent will create a new principal and respond with a principal handle on fd 4.
+// To connect using a previously created principal, create a socket pair and send
+// one end with the principal handle as data on fd 4. The agent will not send a
+// response on fd 4.
+// In either, you can use the normal process to connect to an agent over the
+// other end of the pair. Typically you would pass the other end to a child
+// process and set VEYRON_AGENT_FD so it knows to connect.
+//
+// The protocol also has limited support for caching: A client can
+// request notification when any other client modifies the principal so it
+// can flush the cache. See NotifyWhenChanged for details.
+package agent
+
+import (
+	// VDL system imports
+	"io"
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/vdl"
+
+	// VDL user imports
+	"v.io/v23/security"
+)
+
+type ConnInfo struct {
+	MinVersion int32
+	MaxVersion int32
+}
+
+func (ConnInfo) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/services/agent.ConnInfo"`
+}) {
+}
+
+type RpcRequest struct {
+	Id      uint64
+	Method  string
+	NumArgs uint32
+}
+
+func (RpcRequest) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/services/agent.RpcRequest"`
+}) {
+}
+
+type RpcResponse struct {
+	Id      uint64
+	Err     error
+	NumArgs uint32
+}
+
+func (RpcResponse) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/services/agent.RpcResponse"`
+}) {
+}
+
+type (
+	// RpcMessage represents any single field of the RpcMessage union type.
+	RpcMessage interface {
+		// Index returns the field index.
+		Index() int
+		// Interface returns the field value as an interface.
+		Interface() interface{}
+		// Name returns the field name.
+		Name() string
+		// __VDLReflect describes the RpcMessage union type.
+		__VDLReflect(__RpcMessageReflect)
+	}
+	// RpcMessageReq represents field Req of the RpcMessage union type.
+	RpcMessageReq struct{ Value RpcRequest }
+	// RpcMessageResp represents field Resp of the RpcMessage union type.
+	RpcMessageResp struct{ Value RpcResponse }
+	// __RpcMessageReflect describes the RpcMessage union type.
+	__RpcMessageReflect struct {
+		Name  string `vdl:"v.io/x/ref/services/agent.RpcMessage"`
+		Type  RpcMessage
+		Union struct {
+			Req  RpcMessageReq
+			Resp RpcMessageResp
+		}
+	}
+)
+
+func (x RpcMessageReq) Index() int                       { return 0 }
+func (x RpcMessageReq) Interface() interface{}           { return x.Value }
+func (x RpcMessageReq) Name() string                     { return "Req" }
+func (x RpcMessageReq) __VDLReflect(__RpcMessageReflect) {}
+
+func (x RpcMessageResp) Index() int                       { return 1 }
+func (x RpcMessageResp) Interface() interface{}           { return x.Value }
+func (x RpcMessageResp) Name() string                     { return "Resp" }
+func (x RpcMessageResp) __VDLReflect(__RpcMessageReflect) {}
+
+func init() {
+	vdl.Register((*ConnInfo)(nil))
+	vdl.Register((*RpcRequest)(nil))
+	vdl.Register((*RpcResponse)(nil))
+	vdl.Register((*RpcMessage)(nil))
+}
+
+// AgentClientMethods is the client interface
+// containing Agent methods.
+type AgentClientMethods interface {
+	Bless(ctx *context.T, key []byte, wit security.Blessings, extension string, caveat security.Caveat, additionalCaveats []security.Caveat, opts ...rpc.CallOpt) (security.Blessings, error)
+	BlessSelf(ctx *context.T, name string, caveats []security.Caveat, opts ...rpc.CallOpt) (security.Blessings, error)
+	Sign(ctx *context.T, message []byte, opts ...rpc.CallOpt) (security.Signature, error)
+	MintDischarge(ctx *context.T, forCaveat security.Caveat, caveatOnDischarge security.Caveat, additionalCaveatsOnDischarge []security.Caveat, opts ...rpc.CallOpt) (security.Discharge, error)
+	PublicKey(*context.T, ...rpc.CallOpt) ([]byte, error)
+	BlessingsByName(ctx *context.T, name security.BlessingPattern, opts ...rpc.CallOpt) ([]security.Blessings, error)
+	BlessingsInfo(ctx *context.T, blessings security.Blessings, opts ...rpc.CallOpt) (map[string][]security.Caveat, error)
+	AddToRoots(ctx *context.T, blessing security.Blessings, opts ...rpc.CallOpt) error
+	BlessingStoreSet(ctx *context.T, blessings security.Blessings, forPeers security.BlessingPattern, opts ...rpc.CallOpt) (security.Blessings, error)
+	BlessingStoreForPeer(ctx *context.T, peerBlessings []string, opts ...rpc.CallOpt) (security.Blessings, error)
+	BlessingStoreSetDefault(ctx *context.T, blessings security.Blessings, opts ...rpc.CallOpt) error
+	BlessingStoreDefault(*context.T, ...rpc.CallOpt) (security.Blessings, error)
+	BlessingStorePeerBlessings(*context.T, ...rpc.CallOpt) (map[security.BlessingPattern]security.Blessings, error)
+	BlessingStoreDebugString(*context.T, ...rpc.CallOpt) (string, error)
+	BlessingStoreCacheDischarge(ctx *context.T, discharge security.Discharge, caveat security.Caveat, impetus security.DischargeImpetus, opts ...rpc.CallOpt) error
+	BlessingStoreClearDischarges(ctx *context.T, discharges []security.Discharge, opts ...rpc.CallOpt) error
+	BlessingStoreDischarge(ctx *context.T, caveat security.Caveat, impetus security.DischargeImpetus, opts ...rpc.CallOpt) (wd security.Discharge, err error)
+	BlessingRootsAdd(ctx *context.T, root []byte, pattern security.BlessingPattern, opts ...rpc.CallOpt) error
+	BlessingRootsRecognized(ctx *context.T, root []byte, blessing string, opts ...rpc.CallOpt) error
+	BlessingRootsDump(*context.T, ...rpc.CallOpt) (map[security.BlessingPattern][][]byte, error)
+	BlessingRootsDebugString(*context.T, ...rpc.CallOpt) (string, error)
+	// Clients using caching should call NotifyWhenChanged upon connecting to
+	// the server. The server will stream back values whenever the client should
+	// flush the cache. The streamed value is arbitrary, simply flush whenever
+	// recieving a new item.
+	NotifyWhenChanged(*context.T, ...rpc.CallOpt) (AgentNotifyWhenChangedClientCall, error)
+}
+
+// AgentClientStub adds universal methods to AgentClientMethods.
+type AgentClientStub interface {
+	AgentClientMethods
+	rpc.UniversalServiceMethods
+}
+
+// AgentClient returns a client stub for Agent.
+func AgentClient(name string) AgentClientStub {
+	return implAgentClientStub{name}
+}
+
+type implAgentClientStub struct {
+	name string
+}
+
+func (c implAgentClientStub) Bless(ctx *context.T, i0 []byte, i1 security.Blessings, i2 string, i3 security.Caveat, i4 []security.Caveat, opts ...rpc.CallOpt) (o0 security.Blessings, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "Bless", []interface{}{i0, i1, i2, i3, i4}, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implAgentClientStub) BlessSelf(ctx *context.T, i0 string, i1 []security.Caveat, opts ...rpc.CallOpt) (o0 security.Blessings, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "BlessSelf", []interface{}{i0, i1}, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implAgentClientStub) Sign(ctx *context.T, i0 []byte, opts ...rpc.CallOpt) (o0 security.Signature, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "Sign", []interface{}{i0}, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implAgentClientStub) MintDischarge(ctx *context.T, i0 security.Caveat, i1 security.Caveat, i2 []security.Caveat, opts ...rpc.CallOpt) (o0 security.Discharge, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "MintDischarge", []interface{}{i0, i1, i2}, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implAgentClientStub) PublicKey(ctx *context.T, opts ...rpc.CallOpt) (o0 []byte, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "PublicKey", nil, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implAgentClientStub) BlessingsByName(ctx *context.T, i0 security.BlessingPattern, opts ...rpc.CallOpt) (o0 []security.Blessings, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "BlessingsByName", []interface{}{i0}, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implAgentClientStub) BlessingsInfo(ctx *context.T, i0 security.Blessings, opts ...rpc.CallOpt) (o0 map[string][]security.Caveat, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "BlessingsInfo", []interface{}{i0}, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implAgentClientStub) AddToRoots(ctx *context.T, i0 security.Blessings, opts ...rpc.CallOpt) (err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "AddToRoots", []interface{}{i0}, nil, opts...)
+	return
+}
+
+func (c implAgentClientStub) BlessingStoreSet(ctx *context.T, i0 security.Blessings, i1 security.BlessingPattern, opts ...rpc.CallOpt) (o0 security.Blessings, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "BlessingStoreSet", []interface{}{i0, i1}, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implAgentClientStub) BlessingStoreForPeer(ctx *context.T, i0 []string, opts ...rpc.CallOpt) (o0 security.Blessings, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "BlessingStoreForPeer", []interface{}{i0}, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implAgentClientStub) BlessingStoreSetDefault(ctx *context.T, i0 security.Blessings, opts ...rpc.CallOpt) (err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "BlessingStoreSetDefault", []interface{}{i0}, nil, opts...)
+	return
+}
+
+func (c implAgentClientStub) BlessingStoreDefault(ctx *context.T, opts ...rpc.CallOpt) (o0 security.Blessings, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "BlessingStoreDefault", nil, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implAgentClientStub) BlessingStorePeerBlessings(ctx *context.T, opts ...rpc.CallOpt) (o0 map[security.BlessingPattern]security.Blessings, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "BlessingStorePeerBlessings", nil, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implAgentClientStub) BlessingStoreDebugString(ctx *context.T, opts ...rpc.CallOpt) (o0 string, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "BlessingStoreDebugString", nil, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implAgentClientStub) BlessingStoreCacheDischarge(ctx *context.T, i0 security.Discharge, i1 security.Caveat, i2 security.DischargeImpetus, opts ...rpc.CallOpt) (err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "BlessingStoreCacheDischarge", []interface{}{i0, i1, i2}, nil, opts...)
+	return
+}
+
+func (c implAgentClientStub) BlessingStoreClearDischarges(ctx *context.T, i0 []security.Discharge, opts ...rpc.CallOpt) (err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "BlessingStoreClearDischarges", []interface{}{i0}, nil, opts...)
+	return
+}
+
+func (c implAgentClientStub) BlessingStoreDischarge(ctx *context.T, i0 security.Caveat, i1 security.DischargeImpetus, opts ...rpc.CallOpt) (o0 security.Discharge, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "BlessingStoreDischarge", []interface{}{i0, i1}, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implAgentClientStub) BlessingRootsAdd(ctx *context.T, i0 []byte, i1 security.BlessingPattern, opts ...rpc.CallOpt) (err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "BlessingRootsAdd", []interface{}{i0, i1}, nil, opts...)
+	return
+}
+
+func (c implAgentClientStub) BlessingRootsRecognized(ctx *context.T, i0 []byte, i1 string, opts ...rpc.CallOpt) (err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "BlessingRootsRecognized", []interface{}{i0, i1}, nil, opts...)
+	return
+}
+
+func (c implAgentClientStub) BlessingRootsDump(ctx *context.T, opts ...rpc.CallOpt) (o0 map[security.BlessingPattern][][]byte, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "BlessingRootsDump", nil, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implAgentClientStub) BlessingRootsDebugString(ctx *context.T, opts ...rpc.CallOpt) (o0 string, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "BlessingRootsDebugString", nil, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implAgentClientStub) NotifyWhenChanged(ctx *context.T, opts ...rpc.CallOpt) (ocall AgentNotifyWhenChangedClientCall, err error) {
+	var call rpc.ClientCall
+	if call, err = v23.GetClient(ctx).StartCall(ctx, c.name, "NotifyWhenChanged", nil, opts...); err != nil {
+		return
+	}
+	ocall = &implAgentNotifyWhenChangedClientCall{ClientCall: call}
+	return
+}
+
+// AgentNotifyWhenChangedClientStream is the client stream for Agent.NotifyWhenChanged.
+type AgentNotifyWhenChangedClientStream interface {
+	// RecvStream returns the receiver side of the Agent.NotifyWhenChanged client stream.
+	RecvStream() interface {
+		// Advance stages an item so that it may be retrieved via Value.  Returns
+		// true iff there is an item to retrieve.  Advance must be called before
+		// Value is called.  May block if an item is not available.
+		Advance() bool
+		// Value returns the item that was staged by Advance.  May panic if Advance
+		// returned false or was not called.  Never blocks.
+		Value() bool
+		// Err returns any error encountered by Advance.  Never blocks.
+		Err() error
+	}
+}
+
+// AgentNotifyWhenChangedClientCall represents the call returned from Agent.NotifyWhenChanged.
+type AgentNotifyWhenChangedClientCall interface {
+	AgentNotifyWhenChangedClientStream
+	// Finish blocks until the server is done, and returns the positional return
+	// values for call.
+	//
+	// Finish returns immediately if the call has been canceled; depending on the
+	// timing the output could either be an error signaling cancelation, or the
+	// valid positional return values from the server.
+	//
+	// Calling Finish is mandatory for releasing stream resources, unless the call
+	// has been canceled or any of the other methods return an error.  Finish should
+	// be called at most once.
+	Finish() error
+}
+
+type implAgentNotifyWhenChangedClientCall struct {
+	rpc.ClientCall
+	valRecv bool
+	errRecv error
+}
+
+func (c *implAgentNotifyWhenChangedClientCall) RecvStream() interface {
+	Advance() bool
+	Value() bool
+	Err() error
+} {
+	return implAgentNotifyWhenChangedClientCallRecv{c}
+}
+
+type implAgentNotifyWhenChangedClientCallRecv struct {
+	c *implAgentNotifyWhenChangedClientCall
+}
+
+func (c implAgentNotifyWhenChangedClientCallRecv) Advance() bool {
+	c.c.errRecv = c.c.Recv(&c.c.valRecv)
+	return c.c.errRecv == nil
+}
+func (c implAgentNotifyWhenChangedClientCallRecv) Value() bool {
+	return c.c.valRecv
+}
+func (c implAgentNotifyWhenChangedClientCallRecv) Err() error {
+	if c.c.errRecv == io.EOF {
+		return nil
+	}
+	return c.c.errRecv
+}
+func (c *implAgentNotifyWhenChangedClientCall) Finish() (err error) {
+	err = c.ClientCall.Finish()
+	return
+}
+
+// AgentServerMethods is the interface a server writer
+// implements for Agent.
+type AgentServerMethods interface {
+	Bless(ctx *context.T, call rpc.ServerCall, key []byte, wit security.Blessings, extension string, caveat security.Caveat, additionalCaveats []security.Caveat) (security.Blessings, error)
+	BlessSelf(ctx *context.T, call rpc.ServerCall, name string, caveats []security.Caveat) (security.Blessings, error)
+	Sign(ctx *context.T, call rpc.ServerCall, message []byte) (security.Signature, error)
+	MintDischarge(ctx *context.T, call rpc.ServerCall, forCaveat security.Caveat, caveatOnDischarge security.Caveat, additionalCaveatsOnDischarge []security.Caveat) (security.Discharge, error)
+	PublicKey(*context.T, rpc.ServerCall) ([]byte, error)
+	BlessingsByName(ctx *context.T, call rpc.ServerCall, name security.BlessingPattern) ([]security.Blessings, error)
+	BlessingsInfo(ctx *context.T, call rpc.ServerCall, blessings security.Blessings) (map[string][]security.Caveat, error)
+	AddToRoots(ctx *context.T, call rpc.ServerCall, blessing security.Blessings) error
+	BlessingStoreSet(ctx *context.T, call rpc.ServerCall, blessings security.Blessings, forPeers security.BlessingPattern) (security.Blessings, error)
+	BlessingStoreForPeer(ctx *context.T, call rpc.ServerCall, peerBlessings []string) (security.Blessings, error)
+	BlessingStoreSetDefault(ctx *context.T, call rpc.ServerCall, blessings security.Blessings) error
+	BlessingStoreDefault(*context.T, rpc.ServerCall) (security.Blessings, error)
+	BlessingStorePeerBlessings(*context.T, rpc.ServerCall) (map[security.BlessingPattern]security.Blessings, error)
+	BlessingStoreDebugString(*context.T, rpc.ServerCall) (string, error)
+	BlessingStoreCacheDischarge(ctx *context.T, call rpc.ServerCall, discharge security.Discharge, caveat security.Caveat, impetus security.DischargeImpetus) error
+	BlessingStoreClearDischarges(ctx *context.T, call rpc.ServerCall, discharges []security.Discharge) error
+	BlessingStoreDischarge(ctx *context.T, call rpc.ServerCall, caveat security.Caveat, impetus security.DischargeImpetus) (wd security.Discharge, err error)
+	BlessingRootsAdd(ctx *context.T, call rpc.ServerCall, root []byte, pattern security.BlessingPattern) error
+	BlessingRootsRecognized(ctx *context.T, call rpc.ServerCall, root []byte, blessing string) error
+	BlessingRootsDump(*context.T, rpc.ServerCall) (map[security.BlessingPattern][][]byte, error)
+	BlessingRootsDebugString(*context.T, rpc.ServerCall) (string, error)
+	// Clients using caching should call NotifyWhenChanged upon connecting to
+	// the server. The server will stream back values whenever the client should
+	// flush the cache. The streamed value is arbitrary, simply flush whenever
+	// recieving a new item.
+	NotifyWhenChanged(*context.T, AgentNotifyWhenChangedServerCall) error
+}
+
+// AgentServerStubMethods is the server interface containing
+// Agent methods, as expected by rpc.Server.
+// The only difference between this interface and AgentServerMethods
+// is the streaming methods.
+type AgentServerStubMethods interface {
+	Bless(ctx *context.T, call rpc.ServerCall, key []byte, wit security.Blessings, extension string, caveat security.Caveat, additionalCaveats []security.Caveat) (security.Blessings, error)
+	BlessSelf(ctx *context.T, call rpc.ServerCall, name string, caveats []security.Caveat) (security.Blessings, error)
+	Sign(ctx *context.T, call rpc.ServerCall, message []byte) (security.Signature, error)
+	MintDischarge(ctx *context.T, call rpc.ServerCall, forCaveat security.Caveat, caveatOnDischarge security.Caveat, additionalCaveatsOnDischarge []security.Caveat) (security.Discharge, error)
+	PublicKey(*context.T, rpc.ServerCall) ([]byte, error)
+	BlessingsByName(ctx *context.T, call rpc.ServerCall, name security.BlessingPattern) ([]security.Blessings, error)
+	BlessingsInfo(ctx *context.T, call rpc.ServerCall, blessings security.Blessings) (map[string][]security.Caveat, error)
+	AddToRoots(ctx *context.T, call rpc.ServerCall, blessing security.Blessings) error
+	BlessingStoreSet(ctx *context.T, call rpc.ServerCall, blessings security.Blessings, forPeers security.BlessingPattern) (security.Blessings, error)
+	BlessingStoreForPeer(ctx *context.T, call rpc.ServerCall, peerBlessings []string) (security.Blessings, error)
+	BlessingStoreSetDefault(ctx *context.T, call rpc.ServerCall, blessings security.Blessings) error
+	BlessingStoreDefault(*context.T, rpc.ServerCall) (security.Blessings, error)
+	BlessingStorePeerBlessings(*context.T, rpc.ServerCall) (map[security.BlessingPattern]security.Blessings, error)
+	BlessingStoreDebugString(*context.T, rpc.ServerCall) (string, error)
+	BlessingStoreCacheDischarge(ctx *context.T, call rpc.ServerCall, discharge security.Discharge, caveat security.Caveat, impetus security.DischargeImpetus) error
+	BlessingStoreClearDischarges(ctx *context.T, call rpc.ServerCall, discharges []security.Discharge) error
+	BlessingStoreDischarge(ctx *context.T, call rpc.ServerCall, caveat security.Caveat, impetus security.DischargeImpetus) (wd security.Discharge, err error)
+	BlessingRootsAdd(ctx *context.T, call rpc.ServerCall, root []byte, pattern security.BlessingPattern) error
+	BlessingRootsRecognized(ctx *context.T, call rpc.ServerCall, root []byte, blessing string) error
+	BlessingRootsDump(*context.T, rpc.ServerCall) (map[security.BlessingPattern][][]byte, error)
+	BlessingRootsDebugString(*context.T, rpc.ServerCall) (string, error)
+	// Clients using caching should call NotifyWhenChanged upon connecting to
+	// the server. The server will stream back values whenever the client should
+	// flush the cache. The streamed value is arbitrary, simply flush whenever
+	// recieving a new item.
+	NotifyWhenChanged(*context.T, *AgentNotifyWhenChangedServerCallStub) error
+}
+
+// AgentServerStub adds universal methods to AgentServerStubMethods.
+type AgentServerStub interface {
+	AgentServerStubMethods
+	// Describe the Agent interfaces.
+	Describe__() []rpc.InterfaceDesc
+}
+
+// AgentServer returns a server stub for Agent.
+// It converts an implementation of AgentServerMethods into
+// an object that may be used by rpc.Server.
+func AgentServer(impl AgentServerMethods) AgentServerStub {
+	stub := implAgentServerStub{
+		impl: impl,
+	}
+	// Initialize GlobState; always check the stub itself first, to handle the
+	// case where the user has the Glob method defined in their VDL source.
+	if gs := rpc.NewGlobState(stub); gs != nil {
+		stub.gs = gs
+	} else if gs := rpc.NewGlobState(impl); gs != nil {
+		stub.gs = gs
+	}
+	return stub
+}
+
+type implAgentServerStub struct {
+	impl AgentServerMethods
+	gs   *rpc.GlobState
+}
+
+func (s implAgentServerStub) Bless(ctx *context.T, call rpc.ServerCall, i0 []byte, i1 security.Blessings, i2 string, i3 security.Caveat, i4 []security.Caveat) (security.Blessings, error) {
+	return s.impl.Bless(ctx, call, i0, i1, i2, i3, i4)
+}
+
+func (s implAgentServerStub) BlessSelf(ctx *context.T, call rpc.ServerCall, i0 string, i1 []security.Caveat) (security.Blessings, error) {
+	return s.impl.BlessSelf(ctx, call, i0, i1)
+}
+
+func (s implAgentServerStub) Sign(ctx *context.T, call rpc.ServerCall, i0 []byte) (security.Signature, error) {
+	return s.impl.Sign(ctx, call, i0)
+}
+
+func (s implAgentServerStub) MintDischarge(ctx *context.T, call rpc.ServerCall, i0 security.Caveat, i1 security.Caveat, i2 []security.Caveat) (security.Discharge, error) {
+	return s.impl.MintDischarge(ctx, call, i0, i1, i2)
+}
+
+func (s implAgentServerStub) PublicKey(ctx *context.T, call rpc.ServerCall) ([]byte, error) {
+	return s.impl.PublicKey(ctx, call)
+}
+
+func (s implAgentServerStub) BlessingsByName(ctx *context.T, call rpc.ServerCall, i0 security.BlessingPattern) ([]security.Blessings, error) {
+	return s.impl.BlessingsByName(ctx, call, i0)
+}
+
+func (s implAgentServerStub) BlessingsInfo(ctx *context.T, call rpc.ServerCall, i0 security.Blessings) (map[string][]security.Caveat, error) {
+	return s.impl.BlessingsInfo(ctx, call, i0)
+}
+
+func (s implAgentServerStub) AddToRoots(ctx *context.T, call rpc.ServerCall, i0 security.Blessings) error {
+	return s.impl.AddToRoots(ctx, call, i0)
+}
+
+func (s implAgentServerStub) BlessingStoreSet(ctx *context.T, call rpc.ServerCall, i0 security.Blessings, i1 security.BlessingPattern) (security.Blessings, error) {
+	return s.impl.BlessingStoreSet(ctx, call, i0, i1)
+}
+
+func (s implAgentServerStub) BlessingStoreForPeer(ctx *context.T, call rpc.ServerCall, i0 []string) (security.Blessings, error) {
+	return s.impl.BlessingStoreForPeer(ctx, call, i0)
+}
+
+func (s implAgentServerStub) BlessingStoreSetDefault(ctx *context.T, call rpc.ServerCall, i0 security.Blessings) error {
+	return s.impl.BlessingStoreSetDefault(ctx, call, i0)
+}
+
+func (s implAgentServerStub) BlessingStoreDefault(ctx *context.T, call rpc.ServerCall) (security.Blessings, error) {
+	return s.impl.BlessingStoreDefault(ctx, call)
+}
+
+func (s implAgentServerStub) BlessingStorePeerBlessings(ctx *context.T, call rpc.ServerCall) (map[security.BlessingPattern]security.Blessings, error) {
+	return s.impl.BlessingStorePeerBlessings(ctx, call)
+}
+
+func (s implAgentServerStub) BlessingStoreDebugString(ctx *context.T, call rpc.ServerCall) (string, error) {
+	return s.impl.BlessingStoreDebugString(ctx, call)
+}
+
+func (s implAgentServerStub) BlessingStoreCacheDischarge(ctx *context.T, call rpc.ServerCall, i0 security.Discharge, i1 security.Caveat, i2 security.DischargeImpetus) error {
+	return s.impl.BlessingStoreCacheDischarge(ctx, call, i0, i1, i2)
+}
+
+func (s implAgentServerStub) BlessingStoreClearDischarges(ctx *context.T, call rpc.ServerCall, i0 []security.Discharge) error {
+	return s.impl.BlessingStoreClearDischarges(ctx, call, i0)
+}
+
+func (s implAgentServerStub) BlessingStoreDischarge(ctx *context.T, call rpc.ServerCall, i0 security.Caveat, i1 security.DischargeImpetus) (security.Discharge, error) {
+	return s.impl.BlessingStoreDischarge(ctx, call, i0, i1)
+}
+
+func (s implAgentServerStub) BlessingRootsAdd(ctx *context.T, call rpc.ServerCall, i0 []byte, i1 security.BlessingPattern) error {
+	return s.impl.BlessingRootsAdd(ctx, call, i0, i1)
+}
+
+func (s implAgentServerStub) BlessingRootsRecognized(ctx *context.T, call rpc.ServerCall, i0 []byte, i1 string) error {
+	return s.impl.BlessingRootsRecognized(ctx, call, i0, i1)
+}
+
+func (s implAgentServerStub) BlessingRootsDump(ctx *context.T, call rpc.ServerCall) (map[security.BlessingPattern][][]byte, error) {
+	return s.impl.BlessingRootsDump(ctx, call)
+}
+
+func (s implAgentServerStub) BlessingRootsDebugString(ctx *context.T, call rpc.ServerCall) (string, error) {
+	return s.impl.BlessingRootsDebugString(ctx, call)
+}
+
+func (s implAgentServerStub) NotifyWhenChanged(ctx *context.T, call *AgentNotifyWhenChangedServerCallStub) error {
+	return s.impl.NotifyWhenChanged(ctx, call)
+}
+
+func (s implAgentServerStub) Globber() *rpc.GlobState {
+	return s.gs
+}
+
+func (s implAgentServerStub) Describe__() []rpc.InterfaceDesc {
+	return []rpc.InterfaceDesc{AgentDesc}
+}
+
+// AgentDesc describes the Agent interface.
+var AgentDesc rpc.InterfaceDesc = descAgent
+
+// descAgent hides the desc to keep godoc clean.
+var descAgent = rpc.InterfaceDesc{
+	Name:    "Agent",
+	PkgPath: "v.io/x/ref/services/agent",
+	Methods: []rpc.MethodDesc{
+		{
+			Name: "Bless",
+			InArgs: []rpc.ArgDesc{
+				{"key", ``},               // []byte
+				{"wit", ``},               // security.Blessings
+				{"extension", ``},         // string
+				{"caveat", ``},            // security.Caveat
+				{"additionalCaveats", ``}, // []security.Caveat
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"", ``}, // security.Blessings
+			},
+		},
+		{
+			Name: "BlessSelf",
+			InArgs: []rpc.ArgDesc{
+				{"name", ``},    // string
+				{"caveats", ``}, // []security.Caveat
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"", ``}, // security.Blessings
+			},
+		},
+		{
+			Name: "Sign",
+			InArgs: []rpc.ArgDesc{
+				{"message", ``}, // []byte
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"", ``}, // security.Signature
+			},
+		},
+		{
+			Name: "MintDischarge",
+			InArgs: []rpc.ArgDesc{
+				{"forCaveat", ``},                    // security.Caveat
+				{"caveatOnDischarge", ``},            // security.Caveat
+				{"additionalCaveatsOnDischarge", ``}, // []security.Caveat
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"", ``}, // security.Discharge
+			},
+		},
+		{
+			Name: "PublicKey",
+			OutArgs: []rpc.ArgDesc{
+				{"", ``}, // []byte
+			},
+		},
+		{
+			Name: "BlessingsByName",
+			InArgs: []rpc.ArgDesc{
+				{"name", ``}, // security.BlessingPattern
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"", ``}, // []security.Blessings
+			},
+		},
+		{
+			Name: "BlessingsInfo",
+			InArgs: []rpc.ArgDesc{
+				{"blessings", ``}, // security.Blessings
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"", ``}, // map[string][]security.Caveat
+			},
+		},
+		{
+			Name: "AddToRoots",
+			InArgs: []rpc.ArgDesc{
+				{"blessing", ``}, // security.Blessings
+			},
+		},
+		{
+			Name: "BlessingStoreSet",
+			InArgs: []rpc.ArgDesc{
+				{"blessings", ``}, // security.Blessings
+				{"forPeers", ``},  // security.BlessingPattern
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"", ``}, // security.Blessings
+			},
+		},
+		{
+			Name: "BlessingStoreForPeer",
+			InArgs: []rpc.ArgDesc{
+				{"peerBlessings", ``}, // []string
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"", ``}, // security.Blessings
+			},
+		},
+		{
+			Name: "BlessingStoreSetDefault",
+			InArgs: []rpc.ArgDesc{
+				{"blessings", ``}, // security.Blessings
+			},
+		},
+		{
+			Name: "BlessingStoreDefault",
+			OutArgs: []rpc.ArgDesc{
+				{"", ``}, // security.Blessings
+			},
+		},
+		{
+			Name: "BlessingStorePeerBlessings",
+			OutArgs: []rpc.ArgDesc{
+				{"", ``}, // map[security.BlessingPattern]security.Blessings
+			},
+		},
+		{
+			Name: "BlessingStoreDebugString",
+			OutArgs: []rpc.ArgDesc{
+				{"", ``}, // string
+			},
+		},
+		{
+			Name: "BlessingStoreCacheDischarge",
+			InArgs: []rpc.ArgDesc{
+				{"discharge", ``}, // security.Discharge
+				{"caveat", ``},    // security.Caveat
+				{"impetus", ``},   // security.DischargeImpetus
+			},
+		},
+		{
+			Name: "BlessingStoreClearDischarges",
+			InArgs: []rpc.ArgDesc{
+				{"discharges", ``}, // []security.Discharge
+			},
+		},
+		{
+			Name: "BlessingStoreDischarge",
+			InArgs: []rpc.ArgDesc{
+				{"caveat", ``},  // security.Caveat
+				{"impetus", ``}, // security.DischargeImpetus
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"wd", ``}, // security.Discharge
+			},
+		},
+		{
+			Name: "BlessingRootsAdd",
+			InArgs: []rpc.ArgDesc{
+				{"root", ``},    // []byte
+				{"pattern", ``}, // security.BlessingPattern
+			},
+		},
+		{
+			Name: "BlessingRootsRecognized",
+			InArgs: []rpc.ArgDesc{
+				{"root", ``},     // []byte
+				{"blessing", ``}, // string
+			},
+		},
+		{
+			Name: "BlessingRootsDump",
+			OutArgs: []rpc.ArgDesc{
+				{"", ``}, // map[security.BlessingPattern][][]byte
+			},
+		},
+		{
+			Name: "BlessingRootsDebugString",
+			OutArgs: []rpc.ArgDesc{
+				{"", ``}, // string
+			},
+		},
+		{
+			Name: "NotifyWhenChanged",
+			Doc:  "// Clients using caching should call NotifyWhenChanged upon connecting to\n// the server. The server will stream back values whenever the client should\n// flush the cache. The streamed value is arbitrary, simply flush whenever\n// recieving a new item.",
+		},
+	},
+}
+
+// AgentNotifyWhenChangedServerStream is the server stream for Agent.NotifyWhenChanged.
+type AgentNotifyWhenChangedServerStream interface {
+	// SendStream returns the send side of the Agent.NotifyWhenChanged server stream.
+	SendStream() interface {
+		// Send places the item onto the output stream.  Returns errors encountered
+		// while sending.  Blocks if there is no buffer space; will unblock when
+		// buffer space is available.
+		Send(item bool) error
+	}
+}
+
+// AgentNotifyWhenChangedServerCall represents the context passed to Agent.NotifyWhenChanged.
+type AgentNotifyWhenChangedServerCall interface {
+	rpc.ServerCall
+	AgentNotifyWhenChangedServerStream
+}
+
+// AgentNotifyWhenChangedServerCallStub is a wrapper that converts rpc.StreamServerCall into
+// a typesafe stub that implements AgentNotifyWhenChangedServerCall.
+type AgentNotifyWhenChangedServerCallStub struct {
+	rpc.StreamServerCall
+}
+
+// Init initializes AgentNotifyWhenChangedServerCallStub from rpc.StreamServerCall.
+func (s *AgentNotifyWhenChangedServerCallStub) Init(call rpc.StreamServerCall) {
+	s.StreamServerCall = call
+}
+
+// SendStream returns the send side of the Agent.NotifyWhenChanged server stream.
+func (s *AgentNotifyWhenChangedServerCallStub) SendStream() interface {
+	Send(item bool) error
+} {
+	return implAgentNotifyWhenChangedServerCallSend{s}
+}
+
+type implAgentNotifyWhenChangedServerCallSend struct {
+	s *AgentNotifyWhenChangedServerCallStub
+}
+
+func (s implAgentNotifyWhenChangedServerCallSend) Send(item bool) error {
+	return s.s.Send(item)
+}
diff --git a/services/application/application/doc.go b/services/application/application/doc.go
new file mode 100644
index 0000000..59af378
--- /dev/null
+++ b/services/application/application/doc.go
@@ -0,0 +1,131 @@
+// 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
+
+/*
+Command application manages the Vanadium application repository.
+
+Usage:
+   application <command>
+
+The application commands are:
+   match       Shows the first matching envelope that matches the given
+               profiles.
+   put         Add the given envelope to the application for the given profiles.
+   remove      removes the application envelope for the given profile.
+   edit        edits the application envelope for the given profile.
+   help        Display help for commands or topics
+
+The global flags are:
+ -alsologtostderr=true
+   log to standard error as well as files
+ -log_backtrace_at=:0
+   when logging hits line file:N, emit a stack trace
+ -log_dir=
+   if non-empty, write log files to this directory
+ -logtostderr=false
+   log to standard error instead of files
+ -max_stack_buf_size=4292608
+   max size in bytes of the buffer to use for logging stack traces
+ -metadata=<just specify -metadata to activate>
+   Displays metadata for the program and exits.
+ -stderrthreshold=2
+   logs at or above this threshold go to stderr
+ -v=0
+   log level for V logs
+ -v23.credentials=
+   directory to use for storing security credentials
+ -v23.i18n-catalogue=
+   18n catalogue files to load, comma separated
+ -v23.namespace.root=[/(dev.v.io/role/vprod/service/mounttabled)@ns.dev.v.io:8101]
+   local namespace root; can be repeated to provided multiple roots
+ -v23.proxy=
+   object name of proxy service to use to export services across network
+   boundaries
+ -v23.tcp.address=
+   address to listen on
+ -v23.tcp.protocol=wsh
+   protocol to listen with
+ -v23.vtrace.cache-size=1024
+   The number of vtrace traces to store in memory.
+ -v23.vtrace.collect-regexp=
+   Spans and annotations that match this regular expression will trigger trace
+   collection.
+ -v23.vtrace.dump-on-shutdown=true
+   If true, dump all stored traces on runtime shutdown.
+ -v23.vtrace.sample-rate=0
+   Rate (from 0.0 to 1.0) to sample vtrace traces.
+ -vmodule=
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
+
+Application match
+
+Shows the first matching envelope that matches the given profiles.
+
+Usage:
+   application match <application> <profiles>
+
+<application> is the full name of the application. <profiles> is a
+comma-separated list of profiles.
+
+Application put
+
+Add the given envelope to the application for the given profiles.
+
+Usage:
+   application put <application> <profiles> [<envelope>]
+
+<application> is the full name of the application. <profiles> is a
+comma-separated list of profiles. <envelope> is the file that contains a
+JSON-encoded envelope. If this file is not provided, the user will be prompted
+to enter the data manually.
+
+Application remove
+
+removes the application envelope for the given profile.
+
+Usage:
+   application remove <application> <profile>
+
+<application> is the full name of the application. <profile> is a profile.
+
+Application edit
+
+edits the application envelope for the given profile.
+
+Usage:
+   application edit <application> <profile>
+
+<application> is the full name of the application. <profile> is a profile.
+
+Application help - Display help for commands or topics
+
+Help with no args displays the usage of the parent command.
+
+Help with args displays the usage of the specified sub-command or help topic.
+
+"help ..." recursively displays help for all commands and topics.
+
+Usage:
+   application help [flags] [command/topic ...]
+
+[command/topic ...] optionally identifies a specific sub-command or help topic.
+
+The application help flags are:
+ -style=compact
+   The formatting style for help output:
+      compact - Good for compact cmdline output.
+      full    - Good for cmdline output, shows all global flags.
+      godoc   - Good for godoc processing.
+   Override the default by setting the CMDLINE_STYLE environment variable.
+ -width=<terminal width>
+   Format output to this target width in runes, or unlimited if width < 0.
+   Defaults to the terminal width if available.  Override the default by setting
+   the CMDLINE_WIDTH environment variable.
+*/
+package main
diff --git a/services/application/application/impl.go b/services/application/application/impl.go
new file mode 100644
index 0000000..dfce079
--- /dev/null
+++ b/services/application/application/impl.go
@@ -0,0 +1,246 @@
+// 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.
+
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go .
+
+package main
+
+import (
+	"bytes"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"strings"
+	"time"
+
+	"v.io/v23/context"
+	"v.io/v23/services/application"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/v23cmd"
+	_ "v.io/x/ref/runtime/factories/generic"
+	"v.io/x/ref/services/repository"
+)
+
+func main() {
+	cmdline.HideGlobalFlagsExcept()
+	cmdline.Main(cmdRoot)
+}
+
+func getEnvelopeJSON(ctx *context.T, app repository.ApplicationClientMethods, profiles string) ([]byte, error) {
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
+	defer cancel()
+	env, err := app.Match(ctx, strings.Split(profiles, ","))
+	if err != nil {
+		return nil, err
+	}
+	j, err := json.MarshalIndent(env, "", "  ")
+	if err != nil {
+		return nil, fmt.Errorf("MarshalIndent(%v) failed: %v", env, err)
+	}
+	return j, nil
+}
+
+func putEnvelopeJSON(ctx *context.T, app repository.ApplicationClientMethods, profiles string, j []byte) error {
+	var env application.Envelope
+	if err := json.Unmarshal(j, &env); err != nil {
+		return fmt.Errorf("Unmarshal(%v) failed: %v", string(j), err)
+	}
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
+	defer cancel()
+	if err := app.Put(ctx, strings.Split(profiles, ","), env); err != nil {
+		return err
+	}
+	return nil
+}
+
+func promptUser(env *cmdline.Env, msg string) string {
+	fmt.Fprint(env.Stdout, msg)
+	var answer string
+	if _, err := fmt.Scanf("%s", &answer); err != nil {
+		return ""
+	}
+	return answer
+}
+
+var cmdMatch = &cmdline.Command{
+	Runner:   v23cmd.RunnerFunc(runMatch),
+	Name:     "match",
+	Short:    "Shows the first matching envelope that matches the given profiles.",
+	Long:     "Shows the first matching envelope that matches the given profiles.",
+	ArgsName: "<application> <profiles>",
+	ArgsLong: `
+<application> is the full name of the application.
+<profiles> is a comma-separated list of profiles.`,
+}
+
+func runMatch(ctx *context.T, env *cmdline.Env, args []string) error {
+	if expected, got := 2, len(args); expected != got {
+		return env.UsageErrorf("match: incorrect number of arguments, expected %d, got %d", expected, got)
+	}
+	name, profiles := args[0], args[1]
+	app := repository.ApplicationClient(name)
+	j, err := getEnvelopeJSON(ctx, app, profiles)
+	if err != nil {
+		return err
+	}
+	fmt.Fprintln(env.Stdout, string(j))
+	return nil
+}
+
+var cmdPut = &cmdline.Command{
+	Runner:   v23cmd.RunnerFunc(runPut),
+	Name:     "put",
+	Short:    "Add the given envelope to the application for the given profiles.",
+	Long:     "Add the given envelope to the application for the given profiles.",
+	ArgsName: "<application> <profiles> [<envelope>]",
+	ArgsLong: `
+<application> is the full name of the application.
+<profiles> is a comma-separated list of profiles.
+<envelope> is the file that contains a JSON-encoded envelope. If this file is
+not provided, the user will be prompted to enter the data manually.`,
+}
+
+func runPut(ctx *context.T, env *cmdline.Env, args []string) error {
+	if got := len(args); got != 2 && got != 3 {
+		return env.UsageErrorf("put: incorrect number of arguments, expected 2 or 3, got %d", got)
+	}
+	name, profiles := args[0], args[1]
+	app := repository.ApplicationClient(name)
+	if len(args) == 3 {
+		envelope := args[2]
+		j, err := ioutil.ReadFile(envelope)
+		if err != nil {
+			return fmt.Errorf("ReadFile(%v): %v", envelope, err)
+		}
+		if err = putEnvelopeJSON(ctx, app, profiles, j); err != nil {
+			return err
+		}
+		fmt.Fprintln(env.Stdout, "Application envelope added successfully.")
+		return nil
+	}
+	envelope := application.Envelope{Args: []string{}, Env: []string{}, Packages: application.Packages{}}
+	j, err := json.MarshalIndent(envelope, "", "  ")
+	if err != nil {
+		return fmt.Errorf("MarshalIndent() failed: %v", err)
+	}
+	if err := editAndPutEnvelopeJSON(ctx, env, app, profiles, j); err != nil {
+		return err
+	}
+	return nil
+}
+
+var cmdRemove = &cmdline.Command{
+	Runner:   v23cmd.RunnerFunc(runRemove),
+	Name:     "remove",
+	Short:    "removes the application envelope for the given profile.",
+	Long:     "removes the application envelope for the given profile.",
+	ArgsName: "<application> <profile>",
+	ArgsLong: `
+<application> is the full name of the application.
+<profile> is a profile.`,
+}
+
+func runRemove(ctx *context.T, env *cmdline.Env, args []string) error {
+	if expected, got := 2, len(args); expected != got {
+		return env.UsageErrorf("remove: incorrect number of arguments, expected %d, got %d", expected, got)
+	}
+	name, profile := args[0], args[1]
+	app := repository.ApplicationClient(name)
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
+	defer cancel()
+	if err := app.Remove(ctx, profile); err != nil {
+		return err
+	}
+	fmt.Fprintln(env.Stdout, "Application envelope removed successfully.")
+	return nil
+}
+
+var cmdEdit = &cmdline.Command{
+	Runner:   v23cmd.RunnerFunc(runEdit),
+	Name:     "edit",
+	Short:    "edits the application envelope for the given profile.",
+	Long:     "edits the application envelope for the given profile.",
+	ArgsName: "<application> <profile>",
+	ArgsLong: `
+<application> is the full name of the application.
+<profile> is a profile.`,
+}
+
+func runEdit(ctx *context.T, env *cmdline.Env, args []string) error {
+	if expected, got := 2, len(args); expected != got {
+		return env.UsageErrorf("edit: incorrect number of arguments, expected %d, got %d", expected, got)
+	}
+	name, profile := args[0], args[1]
+	app := repository.ApplicationClient(name)
+
+	envData, err := getEnvelopeJSON(ctx, app, profile)
+	if err != nil {
+		return err
+	}
+	if err := editAndPutEnvelopeJSON(ctx, env, app, profile, envData); err != nil {
+		return err
+	}
+	return nil
+}
+
+func editAndPutEnvelopeJSON(ctx *context.T, env *cmdline.Env, app repository.ApplicationClientMethods, profile string, envData []byte) error {
+	f, err := ioutil.TempFile("", "application-edit-")
+	if err != nil {
+		return fmt.Errorf("TempFile() failed: %v", err)
+	}
+	fileName := f.Name()
+	f.Close()
+	defer os.Remove(fileName)
+	if err = ioutil.WriteFile(fileName, envData, os.FileMode(0644)); err != nil {
+		return err
+	}
+	editor := env.Vars["EDITOR"]
+	if len(editor) == 0 {
+		editor = "nano"
+	}
+	for {
+		c := exec.Command("sh", "-c", fmt.Sprintf("%s %s", editor, fileName))
+		c.Stdin = os.Stdin
+		c.Stdout = os.Stdout
+		c.Stderr = os.Stderr
+		if err := c.Run(); err != nil {
+			return fmt.Errorf("failed to run %s %s", editor, fileName)
+		}
+		newData, err := ioutil.ReadFile(fileName)
+		if err != nil {
+			fmt.Fprintf(env.Stdout, "Error: %v\n", err)
+			if ans := promptUser(env, "Try again? [y/N] "); strings.ToUpper(ans) == "Y" {
+				continue
+			}
+			return errors.New("aborted")
+		}
+		if bytes.Compare(envData, newData) == 0 {
+			fmt.Fprintln(env.Stdout, "Nothing changed")
+			return nil
+		}
+		if err = putEnvelopeJSON(ctx, app, profile, newData); err != nil {
+			fmt.Fprintf(env.Stdout, "Error: %v\n", err)
+			if ans := promptUser(env, "Try again? [y/N] "); strings.ToUpper(ans) == "Y" {
+				continue
+			}
+			return errors.New("aborted")
+		}
+		break
+	}
+	fmt.Fprintln(env.Stdout, "Application envelope updated successfully.")
+	return nil
+}
+
+var cmdRoot = &cmdline.Command{
+	Name:  "application",
+	Short: "manages the Vanadium application repository",
+	Long: `
+Command application manages the Vanadium application repository.
+`,
+	Children: []*cmdline.Command{cmdMatch, cmdPut, cmdRemove, cmdEdit},
+}
diff --git a/services/application/application/impl_test.go b/services/application/application/impl_test.go
new file mode 100644
index 0000000..9cb5353
--- /dev/null
+++ b/services/application/application/impl_test.go
@@ -0,0 +1,197 @@
+// 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 (
+	"bytes"
+	"io/ioutil"
+	"os"
+	"strings"
+	"testing"
+
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/security/access"
+	"v.io/v23/services/application"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/v23cmd"
+	"v.io/x/ref/lib/xrpc"
+	_ "v.io/x/ref/runtime/factories/generic"
+	"v.io/x/ref/services/repository"
+	"v.io/x/ref/test"
+)
+
+var (
+	envelope = application.Envelope{
+		Title:  "fifa world cup",
+		Args:   []string{"arg1", "arg2", "arg3"},
+		Binary: application.SignedFile{File: "/path/to/binary"},
+		Env:    []string{"env1", "env2", "env3"},
+		Packages: map[string]application.SignedFile{
+			"pkg1": application.SignedFile{
+				File: "/path/to/package1",
+			},
+		},
+		Restarts:          0,
+		RestartTimeWindow: 0,
+	}
+	jsonEnv = `{
+  "Title": "fifa world cup",
+  "Args": [
+    "arg1",
+    "arg2",
+    "arg3"
+  ],
+  "Binary": {
+    "File": "/path/to/binary",
+    "Signature": {
+      "Purpose": null,
+      "Hash": "",
+      "R": null,
+      "S": null
+    }
+  },
+  "Publisher": "",
+  "Env": [
+    "env1",
+    "env2",
+    "env3"
+  ],
+  "Packages": {
+    "pkg1": {
+      "File": "/path/to/package1",
+      "Signature": {
+        "Purpose": null,
+        "Hash": "",
+        "R": null,
+        "S": null
+      }
+    }
+  },
+  "Restarts": 0,
+  "RestartTimeWindow": 0
+}`
+)
+
+//go:generate v23 test generate
+
+type server struct {
+	suffix string
+}
+
+func (s *server) Match(ctx *context.T, _ rpc.ServerCall, profiles []string) (application.Envelope, error) {
+	ctx.VI(2).Infof("%v.Match(%v) was called", s.suffix, profiles)
+	return envelope, nil
+}
+
+func (s *server) Put(ctx *context.T, _ rpc.ServerCall, profiles []string, env application.Envelope) error {
+	ctx.VI(2).Infof("%v.Put(%v, %v) was called", s.suffix, profiles, env)
+	return nil
+}
+
+func (s *server) Remove(ctx *context.T, _ rpc.ServerCall, profile string) error {
+	ctx.VI(2).Infof("%v.Remove(%v) was called", s.suffix, profile)
+	return nil
+}
+
+func (s *server) SetPermissions(ctx *context.T, _ rpc.ServerCall, perms access.Permissions, version string) error {
+	ctx.VI(2).Infof("%v.SetPermissions(%v, %v) was called", perms, version)
+	return nil
+}
+
+func (s *server) GetPermissions(ctx *context.T, _ rpc.ServerCall) (access.Permissions, string, error) {
+	ctx.VI(2).Infof("%v.GetPermissions() was called")
+	return nil, "", nil
+}
+
+func (s *server) TidyNow(ctx *context.T, _ rpc.ServerCall) error {
+	ctx.VI(2).Infof("%v.TidyNow() was called", s)
+	return nil
+}
+
+type dispatcher struct{}
+
+func (d *dispatcher) Lookup(_ *context.T, suffix string) (interface{}, security.Authorizer, error) {
+	return repository.ApplicationServer(&server{suffix: suffix}), nil, nil
+}
+
+func TestApplicationClient(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	server, err := xrpc.NewDispatchingServer(ctx, "", &dispatcher{})
+	if err != nil {
+		t.Errorf("NewServer failed: %v", err)
+		return
+	}
+	endpoint := server.Status().Endpoints[0]
+
+	// Setup the command-line.
+	var stdout, stderr bytes.Buffer
+	env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
+	appName := naming.JoinAddressName(endpoint.String(), "myapp/1")
+	profile := "myprofile"
+
+	// Test the 'Match' command.
+	if err := v23cmd.ParseAndRunForTest(cmdRoot, ctx, env, []string{"match", appName, profile}); err != nil {
+		t.Fatalf("%v", err)
+	}
+	if expected, got := jsonEnv, strings.TrimSpace(stdout.String()); got != expected {
+		t.Errorf("Unexpected output from match. Got %q, expected %q", got, expected)
+	}
+	stdout.Reset()
+
+	// Test the 'put' command.
+	f, err := ioutil.TempFile("", "test")
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	fileName := f.Name()
+	defer os.Remove(fileName)
+	if _, err = f.Write([]byte(jsonEnv)); err != nil {
+		t.Fatalf("%v", err)
+	}
+	if err = f.Close(); err != nil {
+		t.Fatalf("%v", err)
+	}
+	if err := v23cmd.ParseAndRunForTest(cmdRoot, ctx, env, []string{"put", appName, profile, fileName}); err != nil {
+		t.Fatalf("%v", err)
+	}
+	if expected, got := "Application envelope added successfully.", strings.TrimSpace(stdout.String()); got != expected {
+		t.Errorf("Unexpected output from put. Got %q, expected %q", got, expected)
+	}
+	stdout.Reset()
+
+	// Test the 'remove' command.
+	if err := v23cmd.ParseAndRunForTest(cmdRoot, ctx, env, []string{"remove", appName, profile}); err != nil {
+		t.Fatalf("%v", err)
+	}
+	if expected, got := "Application envelope removed successfully.", strings.TrimSpace(stdout.String()); got != expected {
+		t.Errorf("Unexpected output from remove. Got %q, expected %q", got, expected)
+	}
+	stdout.Reset()
+
+	// Test the 'edit' command. (nothing changed)
+	env.Vars = map[string]string{"EDITOR": "true"}
+	if err := v23cmd.ParseAndRunForTest(cmdRoot, ctx, env, []string{"edit", appName, profile}); err != nil {
+		t.Fatalf("%v", err)
+	}
+	if expected, got := "Nothing changed", strings.TrimSpace(stdout.String()); got != expected {
+		t.Errorf("Unexpected output from edit. Got %q, expected %q", got, expected)
+	}
+	stdout.Reset()
+
+	// Test the 'edit' command.
+	env.Vars = map[string]string{"EDITOR": "perl -pi -e 's/arg1/arg111/'"}
+	if err := v23cmd.ParseAndRunForTest(cmdRoot, ctx, env, []string{"edit", appName, profile}); err != nil {
+		t.Fatalf("%v", err)
+	}
+	if expected, got := "Application envelope updated successfully.", strings.TrimSpace(stdout.String()); got != expected {
+		t.Errorf("Unexpected output from edit. Got %q, expected %q", got, expected)
+	}
+	stdout.Reset()
+}
diff --git a/services/application/application/v23_internal_test.go b/services/application/application/v23_internal_test.go
new file mode 100644
index 0000000..ae59080
--- /dev/null
+++ b/services/application/application/v23_internal_test.go
@@ -0,0 +1,20 @@
+// 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 main
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	os.Exit(m.Run())
+}
diff --git a/services/application/applicationd/applicationd_v23_test.go b/services/application/applicationd/applicationd_v23_test.go
new file mode 100644
index 0000000..b76a389
--- /dev/null
+++ b/services/application/applicationd/applicationd_v23_test.go
@@ -0,0 +1,108 @@
+// 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_test
+
+import (
+	"encoding/json"
+	"os"
+	"strings"
+
+	"v.io/v23/naming"
+	"v.io/v23/services/application"
+	"v.io/x/ref/test/v23tests"
+)
+
+//go:generate v23 test generate
+
+func helper(i *v23tests.T, clientBin *v23tests.Binary, expectError bool, cmd string, args ...string) string {
+	args = append([]string{cmd}, args...)
+	inv := clientBin.Start(args...)
+	out := inv.Output()
+	err := inv.Wait(os.Stdout, os.Stderr)
+	if err != nil && !expectError {
+		i.Fatalf("%s %q failed: %v\n%v", clientBin.Path(), strings.Join(args, " "), err, out)
+	}
+	if err == nil && expectError {
+		i.Fatalf("%s %q did not fail when it should", clientBin.Path(), strings.Join(args, " "))
+	}
+	return strings.TrimSpace(out)
+
+}
+
+func matchEnvelope(i *v23tests.T, clientBin *v23tests.Binary, expectError bool, name, suffix string) string {
+	return helper(i, clientBin, expectError, "match", naming.Join(name, suffix), "test-profile")
+}
+
+func putEnvelope(i *v23tests.T, clientBin *v23tests.Binary, name, suffix, envelope string) string {
+	return helper(i, clientBin, false, "put", naming.Join(name, suffix), "test-profile", envelope)
+}
+
+func removeEnvelope(i *v23tests.T, clientBin *v23tests.Binary, name, suffix string) string {
+	return helper(i, clientBin, false, "remove", naming.Join(name, suffix), "test-profile")
+}
+
+func binaryWithCredentials(i *v23tests.T, extension, pkgpath string) *v23tests.Binary {
+	creds, err := i.Shell().NewChildCredentials(extension)
+	if err != nil {
+		i.Fatalf("NewCustomCredentials (for %q) failed: %v", pkgpath, err)
+	}
+	b := i.BuildV23Pkg(pkgpath)
+	return b.WithStartOpts(b.StartOpts().WithCustomCredentials(creds))
+}
+
+func V23TestApplicationRepository(i *v23tests.T) {
+	v23tests.RunRootMT(i, "--v23.tcp.address=127.0.0.1:0")
+
+	// Start the application repository.
+	appRepoName := "test-app-repo"
+	binaryWithCredentials(i, "applicationd", "v.io/x/ref/services/application/applicationd").Start(
+		"-name="+appRepoName,
+		"-store="+i.NewTempDir(""),
+		"-v=2",
+		"-v23.tcp.address=127.0.0.1:0")
+
+	// Build the client binary (must be a delegate of the server to pass
+	// the default authorization policy).
+	clientBin := binaryWithCredentials(i, "applicationd/client", "v.io/x/ref/services/application/application")
+
+	// Generate publisher blessings
+	publisher, err := i.Shell().NewChildCredentials("publisher")
+	if err != nil {
+		i.Fatal(err)
+	}
+	sig, err := publisher.Principal().Sign([]byte("binarycontents"))
+	if err != nil {
+		i.Fatal(err)
+	}
+	// Create an application envelope.
+	appRepoSuffix := "test-application/v1"
+	appEnvelopeFile := i.NewTempFile()
+	wantEnvelope, err := json.MarshalIndent(application.Envelope{
+		Title: "title",
+		Binary: application.SignedFile{
+			File:      "foo",
+			Signature: sig,
+		},
+		Publisher: publisher.Principal().BlessingStore().Default(),
+	}, "", "  ")
+	if err != nil {
+		i.Fatal(err)
+	}
+	if _, err := appEnvelopeFile.Write([]byte(wantEnvelope)); err != nil {
+		i.Fatalf("Write() failed: %v", err)
+	}
+	putEnvelope(i, clientBin, appRepoName, appRepoSuffix, appEnvelopeFile.Name())
+
+	// Match the application envelope.
+	if got, want := matchEnvelope(i, clientBin, false, appRepoName, appRepoSuffix), string(wantEnvelope); got != want {
+		i.Fatalf("unexpected output: got %v, want %v", got, want)
+	}
+
+	// Remove the application envelope.
+	removeEnvelope(i, clientBin, appRepoName, appRepoSuffix)
+
+	// Check that the application envelope no longer exists.
+	matchEnvelope(i, clientBin, true, appRepoName, appRepoSuffix)
+}
diff --git a/services/application/applicationd/dispatcher.go b/services/application/applicationd/dispatcher.go
new file mode 100644
index 0000000..506d3ae
--- /dev/null
+++ b/services/application/applicationd/dispatcher.go
@@ -0,0 +1,68 @@
+// 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 (
+	"path/filepath"
+
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/security/access"
+	"v.io/v23/verror"
+	"v.io/x/ref/services/internal/fs"
+	"v.io/x/ref/services/internal/pathperms"
+	"v.io/x/ref/services/repository"
+)
+
+// dispatcher holds the state of the application repository dispatcher.
+type dispatcher struct {
+	store     *fs.Memstore
+	storeRoot string
+}
+
+// NewDispatcher is the dispatcher factory. storeDir is a path to a directory in which to
+// serialize the applicationd state.
+func NewDispatcher(storeDir string) (rpc.Dispatcher, error) {
+	store, err := fs.NewMemstore(filepath.Join(storeDir, "applicationdstate.db"))
+	if err != nil {
+		return nil, err
+	}
+	return &dispatcher{store: store, storeRoot: storeDir}, nil
+}
+
+func (d *dispatcher) Lookup(_ *context.T, suffix string) (interface{}, security.Authorizer, error) {
+	name, _, err := parse(nil, suffix)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	auth, err := pathperms.NewHierarchicalAuthorizer(
+		naming.Join("/acls", "data"),
+		naming.Join("/acls", name, "data"),
+		(*applicationPermsStore)(d.store),
+		[]string{"Put", "__Glob"})
+	if err != nil {
+		return nil, nil, err
+	}
+	return repository.ApplicationServer(NewApplicationService(d.store, d.storeRoot, suffix)), auth, nil
+}
+
+type applicationPermsStore fs.Memstore
+
+// PermsForPath implements PermsGetter so that applicationd can use the
+// hierarchicalAuthorizer.
+func (store *applicationPermsStore) PermsForPath(ctx *context.T, path string) (access.Permissions, bool, error) {
+	perms, _, err := getPermissions(ctx, (*fs.Memstore)(store), path)
+
+	if verror.ErrorID(err) == verror.ErrNoExist.ID {
+		return nil, true, nil
+	}
+	if err != nil {
+		return nil, false, err
+	}
+	return perms, false, nil
+}
diff --git a/services/application/applicationd/doc.go b/services/application/applicationd/doc.go
new file mode 100644
index 0000000..740e023
--- /dev/null
+++ b/services/application/applicationd/doc.go
@@ -0,0 +1,70 @@
+// 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
+
+/*
+Command applicationd runs the application daemon, which implements the
+v.io/x/ref/services/repository.Application interface.
+
+Usage:
+   applicationd [flags]
+
+The applicationd flags are:
+ -name=
+   Name to mount the application repository as.
+ -store=
+   Local directory to store application envelopes in.
+
+The global flags are:
+ -alsologtostderr=true
+   log to standard error as well as files
+ -log_backtrace_at=:0
+   when logging hits line file:N, emit a stack trace
+ -log_dir=
+   if non-empty, write log files to this directory
+ -logtostderr=false
+   log to standard error instead of files
+ -max_stack_buf_size=4292608
+   max size in bytes of the buffer to use for logging stack traces
+ -metadata=<just specify -metadata to activate>
+   Displays metadata for the program and exits.
+ -stderrthreshold=2
+   logs at or above this threshold go to stderr
+ -v=0
+   log level for V logs
+ -v23.credentials=
+   directory to use for storing security credentials
+ -v23.i18n-catalogue=
+   18n catalogue files to load, comma separated
+ -v23.namespace.root=[/(dev.v.io/role/vprod/service/mounttabled)@ns.dev.v.io:8101]
+   local namespace root; can be repeated to provided multiple roots
+ -v23.permissions.file=map[]
+   specify a perms file as <name>:<permsfile>
+ -v23.permissions.literal=
+   explicitly specify the runtime perms as a JSON-encoded access.Permissions.
+   Overrides all --v23.permissions.file flags.
+ -v23.proxy=
+   object name of proxy service to use to export services across network
+   boundaries
+ -v23.tcp.address=
+   address to listen on
+ -v23.tcp.protocol=wsh
+   protocol to listen with
+ -v23.vtrace.cache-size=1024
+   The number of vtrace traces to store in memory.
+ -v23.vtrace.collect-regexp=
+   Spans and annotations that match this regular expression will trigger trace
+   collection.
+ -v23.vtrace.dump-on-shutdown=true
+   If true, dump all stored traces on runtime shutdown.
+ -v23.vtrace.sample-rate=0
+   Rate (from 0.0 to 1.0) to sample vtrace traces.
+ -vmodule=
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
+*/
+package main
diff --git a/services/application/applicationd/impl_test.go b/services/application/applicationd/impl_test.go
new file mode 100644
index 0000000..b045bbf
--- /dev/null
+++ b/services/application/applicationd/impl_test.go
@@ -0,0 +1,541 @@
+// 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_test
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"reflect"
+	"testing"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/security"
+	"v.io/v23/services/application"
+	"v.io/v23/verror"
+
+	"v.io/x/ref/lib/xrpc"
+	appd "v.io/x/ref/services/application/applicationd"
+	"v.io/x/ref/services/repository"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/testutil"
+)
+
+func newPublisherSignature(t *testing.T, ctx *context.T, msg []byte) (security.Blessings, security.Signature) {
+	// Generate publisher blessings
+	p := v23.GetPrincipal(ctx)
+	b, err := p.BlessSelf("publisher")
+	if err != nil {
+		t.Fatal(err)
+	}
+	sig, err := p.Sign(msg)
+	if err != nil {
+		t.Fatal(err)
+	}
+	return b, sig
+}
+
+// TestInterface tests that the implementation correctly implements
+// the Application interface.
+func TestInterface(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	dir, prefix := "", ""
+	store, err := ioutil.TempDir(dir, prefix)
+	if err != nil {
+		t.Fatalf("TempDir(%q, %q) failed: %v", dir, prefix, err)
+	}
+	defer os.RemoveAll(store)
+	dispatcher, err := appd.NewDispatcher(store)
+	if err != nil {
+		t.Fatalf("NewDispatcher() failed: %v", err)
+	}
+
+	server, err := xrpc.NewDispatchingServer(ctx, "", dispatcher)
+	if err != nil {
+		t.Fatalf("NewServer(%v) failed: %v", dispatcher, err)
+	}
+	endpoint := server.Status().Endpoints[0].String()
+
+	// Create client stubs for talking to the server.
+	stub := repository.ApplicationClient(naming.JoinAddressName(endpoint, "search"))
+	stubV0 := repository.ApplicationClient(naming.JoinAddressName(endpoint, "search/v0"))
+	stubV1 := repository.ApplicationClient(naming.JoinAddressName(endpoint, "search/v1"))
+	stubV2 := repository.ApplicationClient(naming.JoinAddressName(endpoint, "search/v2"))
+	stubV3 := repository.ApplicationClient(naming.JoinAddressName(endpoint, "search/v3"))
+
+	blessings, sig := newPublisherSignature(t, ctx, []byte("binarycontents"))
+
+	// Create example envelopes.
+	envelopeV1 := application.Envelope{
+		Args: []string{"--help"},
+		Env:  []string{"DEBUG=1"},
+		Binary: application.SignedFile{
+			File:      "/v23/name/of/binary",
+			Signature: sig,
+		},
+		Publisher: blessings,
+	}
+	envelopeV2 := application.Envelope{
+		Args: []string{"--verbose"},
+		Env:  []string{"DEBUG=0"},
+		Binary: application.SignedFile{
+			File:      "/v23/name/of/binary",
+			Signature: sig,
+		},
+		Publisher: blessings,
+	}
+	envelopeV3 := application.Envelope{
+		Args: []string{"--verbose", "--spiffynewflag"},
+		Env:  []string{"DEBUG=0"},
+		Binary: application.SignedFile{
+			File:      "/v23/name/of/binary",
+			Signature: sig,
+		},
+		Publisher: blessings,
+	}
+
+	// Test Put(), adding a number of application envelopes.
+	if err := stubV1.Put(ctx, []string{"base", "media"}, envelopeV1); err != nil {
+		t.Fatalf("Put() failed: %v", err)
+	}
+	if err := stubV2.Put(ctx, []string{"base"}, envelopeV2); err != nil {
+		t.Fatalf("Put() failed: %v", err)
+	}
+	if err := stub.Put(ctx, []string{"base", "media"}, envelopeV1); err == nil || verror.ErrorID(err) != appd.ErrInvalidSuffix.ID {
+		t.Fatalf("Unexpected error: expected %v, got %v", appd.ErrInvalidSuffix, err)
+	}
+
+	// Test Match(), trying to retrieve both existing and non-existing
+	// application envelopes.
+	var output application.Envelope
+	if output, err = stubV2.Match(ctx, []string{"base", "media"}); err != nil {
+		t.Fatalf("Match() failed: %v", err)
+	}
+	if !reflect.DeepEqual(envelopeV2, output) {
+		t.Fatalf("Incorrect output: expected %v, got %v", envelopeV2, output)
+	}
+	if output, err = stubV1.Match(ctx, []string{"media"}); err != nil {
+		t.Fatalf("Match() failed: %v", err)
+	}
+	if !reflect.DeepEqual(envelopeV1, output) {
+		t.Fatalf("Unexpected output: expected %v, got %v", envelopeV1, output)
+	}
+	if _, err := stubV2.Match(ctx, []string{"media"}); err == nil || verror.ErrorID(err) != verror.ErrNoExist.ID {
+		t.Fatalf("Unexpected error: expected %v, got %v", verror.ErrNoExist, err)
+	}
+	if _, err := stubV2.Match(ctx, []string{}); err == nil || verror.ErrorID(err) != verror.ErrNoExist.ID {
+		t.Fatalf("Unexpected error: expected %v, got %v", verror.ErrNoExist, err)
+	}
+
+	// Test that Match() against a name without a version suffix returns the latest.
+	if output, err = stub.Match(ctx, []string{"base", "media"}); err != nil {
+		t.Fatalf("Match() failed: %v", err)
+	}
+	if !reflect.DeepEqual(envelopeV2, output) {
+		t.Fatalf("Incorrect output: expected %v, got %v", envelopeV2, output)
+	}
+
+	// Test that we can add another envelope in sort order and we still get the
+	// correct (i.e. newest) version.
+	if err := stubV3.Put(ctx, []string{"base"}, envelopeV3); err != nil {
+		t.Fatalf("Put() failed: %v", err)
+	}
+	if output, err = stub.Match(ctx, []string{"base", "media"}); err != nil {
+		t.Fatalf("Match() failed: %v", err)
+	}
+	if !reflect.DeepEqual(envelopeV3, output) {
+		t.Fatalf("Incorrect output: expected %v, got %v", envelopeV3, output)
+	}
+
+	// Test that this is not based on time but on sort order.
+	envelopeV0 := application.Envelope{
+		Args: []string{"--help", "--zeroth"},
+		Env:  []string{"DEBUG=1"},
+		Binary: application.SignedFile{
+			File:      "/v23/name/of/binary",
+			Signature: sig,
+		},
+		Publisher: blessings,
+	}
+	if err := stubV0.Put(ctx, []string{"base"}, envelopeV0); err != nil {
+		t.Fatalf("Put() failed: %v", err)
+	}
+	if output, err = stub.Match(ctx, []string{"base", "media"}); err != nil {
+		t.Fatalf("Match() failed: %v", err)
+	}
+	if !reflect.DeepEqual(envelopeV3, output) {
+		t.Fatalf("Incorrect output: expected %v, got %v", envelopeV3, output)
+	}
+
+	// Test Glob
+	matches, _, err := testutil.GlobName(ctx, naming.JoinAddressName(endpoint, ""), "...")
+	if err != nil {
+		t.Errorf("Unexpected Glob error: %v", err)
+	}
+	expected := []string{
+		"",
+		"search",
+		"search/v0",
+		"search/v1",
+		"search/v2",
+		"search/v3",
+	}
+	if !reflect.DeepEqual(matches, expected) {
+		t.Errorf("unexpected Glob results. Got %q, want %q", matches, expected)
+	}
+
+	// Test Remove(), trying to remove both existing and non-existing
+	// application envelopes.
+	if err := stubV1.Remove(ctx, "base"); err != nil {
+		t.Fatalf("Remove() failed: %v", err)
+	}
+	if output, err = stubV1.Match(ctx, []string{"media"}); err != nil {
+		t.Fatalf("Match() failed: %v", err)
+	}
+	if err := stubV1.Remove(ctx, "base"); err == nil || verror.ErrorID(err) != verror.ErrNoExist.ID {
+		t.Fatalf("Unexpected error: expected %v, got %v", verror.ErrNoExist, err)
+	}
+	if err := stub.Remove(ctx, "base"); err != nil {
+		t.Fatalf("Remove() failed: %v", err)
+	}
+	if err := stubV2.Remove(ctx, "media"); err == nil || verror.ErrorID(err) != verror.ErrNoExist.ID {
+		t.Fatalf("Unexpected error: expected %v, got %v", verror.ErrNoExist, err)
+	}
+	if err := stubV1.Remove(ctx, "media"); err != nil {
+		t.Fatalf("Remove() failed: %v", err)
+	}
+
+	// Finally, use Match() to test that Remove really removed the
+	// application envelopes.
+	if _, err := stubV1.Match(ctx, []string{"base"}); err == nil || verror.ErrorID(err) != verror.ErrNoExist.ID {
+		t.Fatalf("Unexpected error: expected %v, got %v", verror.ErrNoExist, err)
+	}
+	if _, err := stubV1.Match(ctx, []string{"media"}); err == nil || verror.ErrorID(err) != verror.ErrNoExist.ID {
+		t.Fatalf("Unexpected error: expected %v, got %v", verror.ErrNoExist, err)
+	}
+	if _, err := stubV2.Match(ctx, []string{"base"}); err == nil || verror.ErrorID(err) != verror.ErrNoExist.ID {
+		t.Fatalf("Unexpected error: expected %v, got %v", verror.ErrNoExist, err)
+	}
+
+	// Shutdown the application repository server.
+	if err := server.Stop(); err != nil {
+		t.Fatalf("Stop() failed: %v", err)
+	}
+}
+
+func TestPreserveAcrossRestarts(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	dir, prefix := "", ""
+	storedir, err := ioutil.TempDir(dir, prefix)
+	if err != nil {
+		t.Fatalf("TempDir(%q, %q) failed: %v", dir, prefix, err)
+	}
+	defer os.RemoveAll(storedir)
+
+	dispatcher, err := appd.NewDispatcher(storedir)
+	if err != nil {
+		t.Fatalf("NewDispatcher() failed: %v", err)
+	}
+
+	server, err := xrpc.NewDispatchingServer(ctx, "", dispatcher)
+	if err != nil {
+		t.Fatalf("Serve(%v) failed: %v", dispatcher, err)
+	}
+	endpoint := server.Status().Endpoints[0].String()
+
+	// Create client stubs for talking to the server.
+	stubV1 := repository.ApplicationClient(naming.JoinAddressName(endpoint, "search/v1"))
+
+	blessings, sig := newPublisherSignature(t, ctx, []byte("binarycontents"))
+
+	// Create example envelopes.
+	envelopeV1 := application.Envelope{
+		Args: []string{"--help"},
+		Env:  []string{"DEBUG=1"},
+		Binary: application.SignedFile{
+			File:      "/v23/name/of/binary",
+			Signature: sig,
+		},
+		Publisher: blessings,
+	}
+
+	if err := stubV1.Put(ctx, []string{"media"}, envelopeV1); err != nil {
+		t.Fatalf("Put() failed: %v", err)
+	}
+
+	// There is content here now.
+	output, err := stubV1.Match(ctx, []string{"media"})
+	if err != nil {
+		t.Fatalf("Match(%v) failed: %v", "media", err)
+	}
+	if !reflect.DeepEqual(envelopeV1, output) {
+		t.Fatalf("Incorrect output: expected %v, got %v", envelopeV1, output)
+	}
+
+	server.Stop()
+
+	// Setup and start a second application server.
+	dispatcher, err = appd.NewDispatcher(storedir)
+	if err != nil {
+		t.Fatalf("NewDispatcher() failed: %v", err)
+	}
+
+	server, err = xrpc.NewDispatchingServer(ctx, "", dispatcher)
+	if err != nil {
+		t.Fatalf("NewServer(%v) failed: %v", dispatcher, err)
+	}
+	endpoint = server.Status().Endpoints[0].String()
+
+	stubV1 = repository.ApplicationClient(naming.JoinAddressName(endpoint, "search/v1"))
+
+	output, err = stubV1.Match(ctx, []string{"media"})
+	if err != nil {
+		t.Fatalf("Match(%v) failed: %v", "media", err)
+	}
+	if !reflect.DeepEqual(envelopeV1, output) {
+		t.Fatalf("Incorrect output: expected %v, got %v", envelopeV1, output)
+	}
+}
+
+// TestTidyNow tests that TidyNow operates correctly.
+func TestTidyNow(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	dir, prefix := "", ""
+	store, err := ioutil.TempDir(dir, prefix)
+	if err != nil {
+		t.Fatalf("TempDir(%q, %q) failed: %v", dir, prefix, err)
+	}
+	defer os.RemoveAll(store)
+	dispatcher, err := appd.NewDispatcher(store)
+	if err != nil {
+		t.Fatalf("NewDispatcher() failed: %v", err)
+	}
+
+	server, err := xrpc.NewDispatchingServer(ctx, "", dispatcher)
+	if err != nil {
+		t.Fatalf("NewServer(%v) failed: %v", dispatcher, err)
+	}
+
+	defer func() {
+		if err := server.Stop(); err != nil {
+			t.Fatalf("Stop() failed: %v", err)
+		}
+	}()
+	endpoint := server.Status().Endpoints[0].String()
+
+	// Create client stubs for talking to the server.
+	stub := repository.ApplicationClient(naming.JoinAddressName(endpoint, "search"))
+	stubs := make([]repository.ApplicationClientStub, 0)
+	for _, vn := range []string{"v0", "v1", "v2", "v3"} {
+		s := repository.ApplicationClient(naming.JoinAddressName(endpoint, fmt.Sprintf("search/%s", vn)))
+		stubs = append(stubs, s)
+	}
+	blessings, sig := newPublisherSignature(t, ctx, []byte("binarycontents"))
+
+	// Create example envelopes.
+	envelopeV1 := application.Envelope{
+		Args: []string{"--help"},
+		Env:  []string{"DEBUG=1"},
+		Binary: application.SignedFile{
+			File:      "/v23/name/of/binary",
+			Signature: sig,
+		},
+		Publisher: blessings,
+	}
+	envelopeV2 := application.Envelope{
+		Args: []string{"--verbose"},
+		Env:  []string{"DEBUG=0"},
+		Binary: application.SignedFile{
+			File:      "/v23/name/of/binary",
+			Signature: sig,
+		},
+		Publisher: blessings,
+	}
+	envelopeV3 := application.Envelope{
+		Args: []string{"--verbose", "--spiffynewflag"},
+		Env:  []string{"DEBUG=0"},
+		Binary: application.SignedFile{
+			File:      "/v23/name/of/binary",
+			Signature: sig,
+		},
+		Publisher: blessings,
+	}
+
+	stuffEnvelopes(t, ctx, stubs, []profEnvTuple{
+		{
+			&envelopeV1,
+			[]string{"base", "media"},
+		},
+	})
+
+	// Verify that we have one
+	testGlob(t, ctx, endpoint, []string{
+		"",
+		"search",
+		"search/v0",
+	})
+
+	// Tidy when already tidy does not alter.
+	if err := stubs[0].TidyNow(ctx); err != nil {
+		t.Errorf("TidyNow failed: %v", err)
+	}
+	testGlob(t, ctx, endpoint, []string{
+		"",
+		"search",
+		"search/v0",
+	})
+
+	stuffEnvelopes(t, ctx, stubs, []profEnvTuple{
+		{
+			&envelopeV1,
+			[]string{"base", "media"},
+		},
+		{
+			&envelopeV2,
+			[]string{"media"},
+		},
+		{
+			&envelopeV3,
+			[]string{"base"},
+		},
+	})
+
+	// Now there are three envelopes which is one more than the
+	// numberOfVersionsToKeep set for the test. However
+	// we need both envelopes v0 and v2 to keep two versions for
+	// profile media and envelopes v0 and v3 to keep two versions
+	// for profile base so we continue to have three versions.
+	if err := stubs[0].TidyNow(ctx); err != nil {
+		t.Errorf("TidyNow failed: %v", err)
+	}
+	testGlob(t, ctx, endpoint, []string{
+		"",
+		"search",
+		"search/v0",
+		"search/v1",
+		"search/v2",
+	})
+
+	// And the newest version for each profile differs because
+	// not every version supports all profiles.
+	output1, err := stub.Match(ctx, []string{"media"})
+	if err != nil {
+		t.Fatalf("Match(%v) failed: %v", "media", err)
+	}
+	if !reflect.DeepEqual(envelopeV2, output1) {
+		t.Fatalf("Incorrect output: expected %v, got %v", envelopeV2, output1)
+	}
+
+	output2, err := stub.Match(ctx, []string{"base"})
+	if err != nil {
+		t.Fatalf("Match(%v) failed: %v", "base", err)
+	}
+	if !reflect.DeepEqual(envelopeV3, output2) {
+		t.Fatalf("Incorrect output: expected %v, got %v", envelopeV3, output2)
+	}
+
+	// Test that we can add an envelope for v3 with profile media and after calling
+	// TidyNow(), there will be all versions still in glob but v0 will only match profile
+	// base and not have an envelope for profile media.
+	if err := stubs[3].Put(ctx, []string{"media"}, envelopeV3); err != nil {
+		t.Fatalf("Put() failed: %v", err)
+	}
+
+	if err := stubs[0].TidyNow(ctx); err != nil {
+		t.Errorf("TidyNow failed: %v", err)
+	}
+	testGlob(t, ctx, endpoint, []string{
+		"",
+		"search",
+		"search/v0",
+		"search/v1",
+		"search/v2",
+		"search/v3",
+	})
+
+	output3, err := stubs[0].Match(ctx, []string{"base"})
+	if err != nil {
+		t.Fatalf("Match(%v) failed: %v", "base", err)
+	}
+	if !reflect.DeepEqual(envelopeV3, output2) {
+		t.Fatalf("Incorrect output: expected %v, got %v", envelopeV3, output3)
+	}
+
+	output4, err := stubs[0].Match(ctx, []string{"base"})
+	if err != nil {
+		t.Fatalf("Match(%v) failed: %v", "base", err)
+	}
+	if !reflect.DeepEqual(envelopeV3, output2) {
+		t.Fatalf("Incorrect output: expected %v, got %v", envelopeV3, output4)
+	}
+
+	_, err = stubs[0].Match(ctx, []string{"media"})
+	if verror.ErrorID(err) != verror.ErrNoExist.ID {
+		t.Fatalf("got error %v,  expected %v", err, verror.ErrNoExist)
+	}
+
+	stuffEnvelopes(t, ctx, stubs, []profEnvTuple{
+		{
+			&envelopeV1,
+			[]string{"base", "media"},
+		},
+		{
+			&envelopeV2,
+			[]string{"base", "media"},
+		},
+		{
+			&envelopeV3,
+			[]string{"base", "media"},
+		},
+		{
+			&envelopeV3,
+			[]string{"base", "media"},
+		},
+	})
+
+	// Now there are four versions for all profiles so tidying
+	// will remove the older versions.
+	if err := stubs[0].TidyNow(ctx); err != nil {
+		t.Errorf("TidyNow failed: %v", err)
+	}
+
+	testGlob(t, ctx, endpoint, []string{
+		"",
+		"search",
+		"search/v2",
+		"search/v3",
+	})
+}
+
+type profEnvTuple struct {
+	e *application.Envelope
+	p []string
+}
+
+func testGlob(t *testing.T, ctx *context.T, endpoint string, expected []string) {
+	matches, _, err := testutil.GlobName(ctx, naming.JoinAddressName(endpoint, ""), "...")
+	if err != nil {
+		t.Errorf("Unexpected Glob error: %v", err)
+	}
+	if !reflect.DeepEqual(matches, expected) {
+		t.Errorf("unexpected Glob results. Got %q, want %q", matches, expected)
+	}
+}
+
+func stuffEnvelopes(t *testing.T, ctx *context.T, stubs []repository.ApplicationClientStub, pets []profEnvTuple) {
+	for i, pet := range pets {
+		if err := stubs[i].Put(ctx, pet.p, *pet.e); err != nil {
+			t.Fatalf("%d: Put(%v) failed: %v", i, pet, err)
+		}
+	}
+}
diff --git a/services/application/applicationd/main.go b/services/application/applicationd/main.go
new file mode 100644
index 0000000..e1b4e6d
--- /dev/null
+++ b/services/application/applicationd/main.go
@@ -0,0 +1,65 @@
+// 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.
+
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go . -help
+
+package main
+
+import (
+	"fmt"
+
+	"v.io/v23/context"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/signals"
+	"v.io/x/ref/lib/v23cmd"
+	"v.io/x/ref/lib/xrpc"
+	_ "v.io/x/ref/runtime/factories/roaming"
+)
+
+var name, store string
+
+func main() {
+	cmdAppD.Flags.StringVar(&name, "name", "", "Name to mount the application repository as.")
+	cmdAppD.Flags.StringVar(&store, "store", "", "Local directory to store application envelopes in.")
+
+	cmdline.HideGlobalFlagsExcept()
+	cmdline.Main(cmdAppD)
+}
+
+var cmdAppD = &cmdline.Command{
+	Runner: v23cmd.RunnerFunc(runAppD),
+	Name:   "applicationd",
+	Short:  "Runs the application daemon.",
+	Long: `
+Command applicationd runs the application daemon, which implements the
+v.io/x/ref/services/repository.Application interface.
+`,
+}
+
+func runAppD(ctx *context.T, env *cmdline.Env, args []string) error {
+	if store == "" {
+		return env.UsageErrorf("Specify a directory for storing application envelopes using --store=<name>")
+	}
+
+	dispatcher, err := NewDispatcher(store)
+	if err != nil {
+		return fmt.Errorf("NewDispatcher() failed: %v", err)
+	}
+
+	server, err := xrpc.NewDispatchingServer(ctx, name, dispatcher)
+	if err != nil {
+		return fmt.Errorf("NewServer() failed: %v", err)
+	}
+	defer server.Stop()
+	epName := server.Status().Endpoints[0].Name()
+	if name != "" {
+		ctx.Infof("Application repository serving at %q (%q)", name, epName)
+	} else {
+		ctx.Infof("Application repository serving at %q", epName)
+	}
+	// Wait until shutdown.
+	<-signals.ShutdownOnSignals(ctx)
+	return nil
+}
diff --git a/services/application/applicationd/only_for_test.go b/services/application/applicationd/only_for_test.go
new file mode 100644
index 0000000..0b3724e
--- /dev/null
+++ b/services/application/applicationd/only_for_test.go
@@ -0,0 +1,9 @@
+// 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
+
+func init() {
+	numberOfVersionsToKeep = 2
+}
diff --git a/services/application/applicationd/perms_test.go b/services/application/applicationd/perms_test.go
new file mode 100644
index 0000000..f934eec
--- /dev/null
+++ b/services/application/applicationd/perms_test.go
@@ -0,0 +1,404 @@
+// 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_test
+
+import (
+	"fmt"
+	"os"
+	"reflect"
+	"syscall"
+	"testing"
+
+	"v.io/v23"
+	"v.io/v23/security"
+	"v.io/v23/security/access"
+	"v.io/v23/services/application"
+	"v.io/v23/verror"
+	"v.io/x/ref/lib/signals"
+	"v.io/x/ref/lib/xrpc"
+	appd "v.io/x/ref/services/application/applicationd"
+	"v.io/x/ref/services/internal/servicetest"
+	"v.io/x/ref/services/repository"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
+	"v.io/x/ref/test/testutil"
+)
+
+//go:generate v23 test generate
+
+var appRepository = modules.Register(func(env *modules.Env, args ...string) error {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	if len(args) < 2 {
+		ctx.Fatalf("repository expected at least name and store arguments and optionally Permissions flags per PermissionsFromFlag")
+	}
+	publishName := args[0]
+	storedir := args[1]
+
+	defer fmt.Fprintf(env.Stdout, "%v terminating\n", publishName)
+	defer ctx.VI(1).Infof("%v terminating", publishName)
+
+	dispatcher, err := appd.NewDispatcher(storedir)
+	if err != nil {
+		ctx.Fatalf("Failed to create repository dispatcher: %v", err)
+	}
+	server, err := xrpc.NewDispatchingServer(ctx, publishName, dispatcher)
+	if err != nil {
+		ctx.Fatalf("NewDispatchingServer(%v) failed: %v", publishName, err)
+	}
+	ctx.VI(1).Infof("applicationd name: %v", server.Status().Endpoints[0].Name())
+
+	fmt.Fprintf(env.Stdout, "ready:%d\n", os.Getpid())
+	<-signals.ShutdownOnSignals(ctx)
+
+	return nil
+}, "appRepository")
+
+func TestApplicationUpdatePermissions(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	// V23Init sets the context up with a self-signed principal, whose
+	// blessing (test-blessing) will act as the root blessing for the test.
+	const rootBlessing = test.TestBlessing
+	idp := testutil.IDProviderFromPrincipal(v23.GetPrincipal(ctx))
+	// Call ourselves test-blessing/self, distinct from test-blessing/other
+	// which we'll give to the 'other' context.
+	if err := idp.Bless(v23.GetPrincipal(ctx), "self"); err != nil {
+		t.Fatal(err)
+	}
+
+	sh, deferFn := servicetest.CreateShell(t, ctx, nil)
+	defer deferFn()
+
+	// setup mock up directory to put state in
+	storedir, cleanup := servicetest.SetupRootDir(t, "application")
+	defer cleanup()
+
+	nmh := servicetest.RunCommand(t, sh, nil, appRepository, "repo", storedir)
+	pid := servicetest.ReadPID(t, nmh)
+	defer syscall.Kill(pid, syscall.SIGINT)
+
+	otherCtx, err := v23.WithPrincipal(ctx, testutil.NewPrincipal())
+	if err != nil {
+		t.Fatal(err)
+	}
+	if err := idp.Bless(v23.GetPrincipal(otherCtx), "other"); err != nil {
+		t.Fatal(err)
+	}
+
+	v1stub := repository.ApplicationClient("repo/search/v1")
+	repostub := repository.ApplicationClient("repo")
+
+	// Create example envelopes.
+	envelopeV1 := application.Envelope{
+		Args:   []string{"--help"},
+		Env:    []string{"DEBUG=1"},
+		Binary: application.SignedFile{File: "/v23/name/of/binary"},
+	}
+
+	// Envelope putting as other should fail.
+	if err := v1stub.Put(otherCtx, []string{"base"}, envelopeV1); verror.ErrorID(err) != verror.ErrNoAccess.ID {
+		t.Fatalf("Put() returned errorid=%v wanted errorid=%v [%v]", verror.ErrorID(err), verror.ErrNoAccess.ID, err)
+	}
+
+	// Envelope putting as global should succeed.
+	if err := v1stub.Put(ctx, []string{"base"}, envelopeV1); err != nil {
+		t.Fatalf("Put() failed: %v", err)
+	}
+
+	ctx.VI(2).Infof("Accessing the Permission Lists of the root returns a (simulated) list providing default authorization.")
+	perms, version, err := repostub.GetPermissions(ctx)
+	if err != nil {
+		t.Fatalf("GetPermissions should not have failed: %v", err)
+	}
+	if got, want := version, ""; got != want {
+		t.Fatalf("GetPermissions got %v, want %v", got, want)
+	}
+	expected := access.Permissions{
+		"Admin": access.AccessList{
+			In:    []security.BlessingPattern{test.TestBlessing + "/$", rootBlessing + "/self/$", rootBlessing + "/self/child"},
+			NotIn: []string(nil)},
+		"Read": access.AccessList{
+			In:    []security.BlessingPattern{rootBlessing + "/$", rootBlessing + "/self/$", rootBlessing + "/self/child"},
+			NotIn: []string(nil)},
+		"Write": access.AccessList{
+			In:    []security.BlessingPattern{rootBlessing + "/$", rootBlessing + "/self/$", rootBlessing + "/self/child"},
+			NotIn: []string(nil)},
+		"Debug": access.AccessList{
+			In:    []security.BlessingPattern{rootBlessing + "/$", rootBlessing + "/self/$", rootBlessing + "/self/child"},
+			NotIn: []string(nil)},
+		"Resolve": access.AccessList{
+			In:    []security.BlessingPattern{rootBlessing + "/$", rootBlessing + "/self/$", rootBlessing + "/self/child"},
+			NotIn: []string(nil)}}
+	if got := perms; !reflect.DeepEqual(expected.Normalize(), got.Normalize()) {
+		t.Errorf("got %#v, expected %#v ", got, expected)
+	}
+
+	ctx.VI(2).Infof("self attempting to give other permission to update application")
+	newPerms := make(access.Permissions)
+	for _, tag := range access.AllTypicalTags() {
+		newPerms.Add(rootBlessing+"/self", string(tag))
+		newPerms.Add(rootBlessing+"/other", string(tag))
+	}
+	if err := repostub.SetPermissions(ctx, newPerms, ""); err != nil {
+		t.Fatalf("SetPermissions failed: %v", err)
+	}
+
+	perms, version, err = repostub.GetPermissions(ctx)
+	if err != nil {
+		t.Fatalf("GetPermissions should not have failed: %v", err)
+	}
+	expected = newPerms
+	if got := perms; !reflect.DeepEqual(expected.Normalize(), got.Normalize()) {
+		t.Errorf("got %#v, exected %#v ", got, expected)
+	}
+
+	// Envelope putting as other should now succeed.
+	if err := v1stub.Put(otherCtx, []string{"base"}, envelopeV1); err != nil {
+		t.Fatalf("Put() wrongly failed: %v", err)
+	}
+
+	// Other takes control.
+	perms, version, err = repostub.GetPermissions(otherCtx)
+	if err != nil {
+		t.Fatalf("GetPermissions 2 should not have failed: %v", err)
+	}
+	perms["Admin"] = access.AccessList{
+		In:    []security.BlessingPattern{rootBlessing + "/other"},
+		NotIn: []string{}}
+	if err = repostub.SetPermissions(otherCtx, perms, version); err != nil {
+		t.Fatalf("SetPermissions failed: %v", err)
+	}
+
+	// Self is now locked out but other isn't.
+	if _, _, err = repostub.GetPermissions(ctx); err == nil {
+		t.Fatalf("GetPermissions should not have succeeded")
+	}
+	perms, _, err = repostub.GetPermissions(otherCtx)
+	if err != nil {
+		t.Fatalf("GetPermissions should not have failed: %v", err)
+	}
+	expected = access.Permissions{
+		"Admin": access.AccessList{
+			In:    []security.BlessingPattern{rootBlessing + "/other"},
+			NotIn: []string{}},
+		"Read": access.AccessList{In: []security.BlessingPattern{rootBlessing + "/other",
+			rootBlessing + "/self"},
+			NotIn: []string{}},
+		"Write": access.AccessList{In: []security.BlessingPattern{rootBlessing + "/other",
+			rootBlessing + "/self"},
+			NotIn: []string{}},
+		"Debug": access.AccessList{In: []security.BlessingPattern{rootBlessing + "/other",
+			rootBlessing + "/self"},
+			NotIn: []string{}},
+		"Resolve": access.AccessList{In: []security.BlessingPattern{rootBlessing + "/other",
+			rootBlessing + "/self"},
+			NotIn: []string{}}}
+
+	if got := perms; !reflect.DeepEqual(expected.Normalize(), got.Normalize()) {
+		t.Errorf("got %#v, exected %#v ", got, expected)
+	}
+}
+
+func TestPerAppPermissions(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	// By default, all principals in this test will have blessings generated based
+	// on the username/machine running this process. Give them recognizable names
+	// ("root/self" etc.), so the Permissions can be set deterministically.
+	idp := testutil.NewIDProvider("root")
+	if err := idp.Bless(v23.GetPrincipal(ctx), "self"); err != nil {
+		t.Fatal(err)
+	}
+
+	sh, deferFn := servicetest.CreateShellAndMountTable(t, ctx, v23.GetPrincipal(ctx))
+	defer deferFn()
+
+	// setup mock up directory to put state in
+	storedir, cleanup := servicetest.SetupRootDir(t, "application")
+	defer cleanup()
+
+	otherCtx, err := v23.WithPrincipal(ctx, testutil.NewPrincipal())
+	if err != nil {
+		t.Fatal(err)
+	}
+	if err := idp.Bless(v23.GetPrincipal(otherCtx), "other"); err != nil {
+		t.Fatal(err)
+	}
+
+	nmh := servicetest.RunCommand(t, sh, nil, appRepository, "repo", storedir)
+	pid := servicetest.ReadPID(t, nmh)
+	defer syscall.Kill(pid, syscall.SIGINT)
+
+	// Create example envelope.
+	envelopeV1 := application.Envelope{
+		Args:   []string{"--help"},
+		Env:    []string{"DEBUG=1"},
+		Binary: application.SignedFile{File: "/v23/name/of/binary"},
+	}
+
+	ctx.VI(2).Info("Upload an envelope")
+	v1stub := repository.ApplicationClient("repo/search/v1")
+	if err := v1stub.Put(ctx, []string{"base"}, envelopeV1); err != nil {
+		t.Fatalf("Put() failed: %v", err)
+	}
+	v2stub := repository.ApplicationClient("repo/search/v2")
+	if err := v2stub.Put(ctx, []string{"base"}, envelopeV1); err != nil {
+		t.Fatalf("Put() failed: %v", err)
+	}
+	v3stub := repository.ApplicationClient("repo/naps/v1")
+	if err := v3stub.Put(ctx, []string{"base"}, envelopeV1); err != nil {
+		t.Fatalf("Put() failed: %v", err)
+	}
+
+	ctx.VI(2).Info("Self can access Permissions but other can't.")
+	expectedSelfPermissions := access.Permissions{
+		"Admin": access.AccessList{
+			In:    []security.BlessingPattern{"root/$", "root/self"},
+			NotIn: []string{}},
+		"Read": access.AccessList{In: []security.BlessingPattern{"root/$", "root/self"},
+			NotIn: []string{}},
+		"Write": access.AccessList{In: []security.BlessingPattern{"root/$", "root/self"},
+			NotIn: []string{}},
+		"Debug": access.AccessList{In: []security.BlessingPattern{"root/$", "root/self"},
+			NotIn: []string{}},
+		"Resolve": access.AccessList{In: []security.BlessingPattern{"root/$", "root/self"},
+			NotIn: []string{}}}
+
+	for _, path := range []string{"repo/search", "repo/search/v1", "repo/search/v2", "repo/naps", "repo/naps/v1"} {
+		stub := repository.ApplicationClient(path)
+		perms, _, err := stub.GetPermissions(ctx)
+		if err != nil {
+			t.Fatalf("Newly uploaded envelopes failed to receive permission lists: %v", err)
+		}
+
+		if got := perms; !reflect.DeepEqual(expectedSelfPermissions.Normalize(), got.Normalize()) {
+			t.Errorf("got %#v, expected %#v ", got, expectedSelfPermissions)
+		}
+
+		// But otherCtx doesn't have admin permissions so has no access.
+		if _, _, err := stub.GetPermissions(otherCtx); err == nil {
+			t.Fatalf("GetPermissions didn't fail for other when it should have.")
+		}
+	}
+
+	ctx.VI(2).Infof("Self sets root Permissions.")
+	repostub := repository.ApplicationClient("repo")
+	newPerms := make(access.Permissions)
+	for _, tag := range access.AllTypicalTags() {
+		newPerms.Add("root/self", string(tag))
+	}
+	if err := repostub.SetPermissions(ctx, newPerms, ""); err != nil {
+		t.Fatalf("SetPermissions failed: %v", err)
+	}
+
+	ctx.VI(2).Infof("Other still can't access anything.")
+	if _, _, err = repostub.GetPermissions(otherCtx); err == nil {
+		t.Fatalf("GetPermissions should have failed")
+	}
+
+	ctx.VI(2).Infof("Self gives other full access to repo/search/...")
+	newPerms, version, err := v1stub.GetPermissions(ctx)
+	if err != nil {
+		t.Fatalf("GetPermissions should not have failed: %v", err)
+	}
+	for _, tag := range access.AllTypicalTags() {
+		newPerms.Add("root/other", string(tag))
+	}
+	if err := v1stub.SetPermissions(ctx, newPerms, version); err != nil {
+		t.Fatalf("SetPermissions failed: %v", err)
+	}
+
+	expected := access.Permissions{
+		"Resolve": access.AccessList{In: []security.BlessingPattern{
+			"root/$",
+			"root/other",
+			"root/self"},
+			NotIn: []string(nil)},
+		"Admin": access.AccessList{In: []security.BlessingPattern{
+			"root/$",
+			"root/other",
+			"root/self"},
+			NotIn: []string(nil)},
+		"Read": access.AccessList{In: []security.BlessingPattern{
+			"root/$",
+			"root/other",
+			"root/self"},
+			NotIn: []string(nil)},
+		"Write": access.AccessList{In: []security.BlessingPattern{
+			"root/$",
+			"root/other",
+			"root/self"},
+			NotIn: []string(nil)},
+		"Debug": access.AccessList{In: []security.BlessingPattern{
+			"root/$",
+			"root/other", "root/self"},
+			NotIn: []string(nil)},
+	}
+
+	for _, path := range []string{"repo/search", "repo/search/v1", "repo/search/v2"} {
+		stub := repository.ApplicationClient(path)
+		ctx.VI(2).Infof("Other can now access this app independent of version.")
+		perms, _, err := stub.GetPermissions(otherCtx)
+		if err != nil {
+			t.Fatalf("GetPermissions should not have failed: %v", err)
+		}
+
+		if got := perms; !reflect.DeepEqual(expected.Normalize(), got.Normalize()) {
+			t.Errorf("got %#v, expected %#v ", got, expected)
+		}
+		ctx.VI(2).Infof("Self can also access thanks to hierarchical auth.")
+		if _, _, err = stub.GetPermissions(ctx); err != nil {
+			t.Fatalf("GetPermissions should not have failed: %v", err)
+		}
+	}
+
+	ctx.VI(2).Infof("But other locations are unaffected and other cannot access.")
+	for _, path := range []string{"repo/naps", "repo/naps/v1"} {
+		stub := repository.ApplicationClient(path)
+		if _, _, err := stub.GetPermissions(otherCtx); err == nil {
+			t.Fatalf("GetPermissions didn't fail when it should have.")
+		}
+	}
+
+	// Self gives other write perms on base.
+	newPerms, version, err = repostub.GetPermissions(ctx)
+	if err != nil {
+		t.Fatalf("GetPermissions should not have failed: %v", err)
+	}
+	newPerms["Write"] = access.AccessList{In: []security.BlessingPattern{"root/other", "root/self"}}
+	if err := repostub.SetPermissions(ctx, newPerms, version); err != nil {
+		t.Fatalf("SetPermissions failed: %v", err)
+	}
+
+	// Other can now upload an envelope at both locations.
+	for _, stub := range []repository.ApplicationClientStub{v1stub, v2stub} {
+		if err := stub.Put(otherCtx, []string{"base"}, envelopeV1); err != nil {
+			t.Fatalf("Put() failed: %v", err)
+		}
+	}
+
+	// But because application search already exists, the Permissions do not change.
+	for _, path := range []string{"repo/search", "repo/search/v1", "repo/search/v2"} {
+		stub := repository.ApplicationClient(path)
+		perms, _, err := stub.GetPermissions(otherCtx)
+		if err != nil {
+			t.Fatalf("GetPermissions should not have failed: %v", err)
+		}
+		if got := perms; !reflect.DeepEqual(expected.Normalize(), got.Normalize()) {
+			t.Errorf("got %#v, expected %#v ", got, expected)
+		}
+	}
+
+	// But self didn't give other Permissions modification permissions.
+	for _, path := range []string{"repo/search", "repo/search/v2"} {
+		stub := repository.ApplicationClient(path)
+		if _, _, err := stub.GetPermissions(otherCtx); err != nil {
+			t.Fatalf("GetPermissions failed when it should not have for same application: %v", err)
+		}
+	}
+}
diff --git a/services/application/applicationd/service.go b/services/application/applicationd/service.go
new file mode 100644
index 0000000..2ea8bff
--- /dev/null
+++ b/services/application/applicationd/service.go
@@ -0,0 +1,403 @@
+// 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 (
+	"sort"
+	"strings"
+
+	"v.io/v23/context"
+	"v.io/v23/glob"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/security/access"
+	"v.io/v23/services/application"
+	"v.io/v23/verror"
+	"v.io/x/lib/set"
+	"v.io/x/ref/services/internal/fs"
+	"v.io/x/ref/services/internal/pathperms"
+	"v.io/x/ref/services/repository"
+)
+
+// appRepoService implements the Application repository interface.
+type appRepoService struct {
+	// store is the storage server used for storing application
+	// metadata.
+	// All objects share the same Memstore.
+	store *fs.Memstore
+	// storeRoot is a name in the directory under which all data will be
+	// stored.
+	storeRoot string
+	// suffix is the name of the application object.
+	suffix string
+}
+
+const pkgPath = "v.io/x/ref/services/application/applicationd/"
+
+var (
+	ErrInvalidSuffix   = verror.Register(pkgPath+".InvalidSuffix", verror.NoRetry, "{1:}{2:} invalid suffix{:_}")
+	ErrOperationFailed = verror.Register(pkgPath+".OperationFailed", verror.NoRetry, "{1:}{2:} operation failed{:_}")
+	ErrNotAuthorized   = verror.Register(pkgPath+".errNotAuthorized", verror.NoRetry, "{1:}{2:} none of the client's blessings are valid {:_}")
+)
+
+// NewApplicationService returns a new Application service implementation.
+func NewApplicationService(store *fs.Memstore, storeRoot, suffix string) repository.ApplicationServerMethods {
+	return &appRepoService{store: store, storeRoot: storeRoot, suffix: suffix}
+}
+
+func parse(ctx *context.T, suffix string) (string, string, error) {
+	tokens := strings.Split(suffix, "/")
+	switch len(tokens) {
+	case 2:
+		return tokens[0], tokens[1], nil
+	case 1:
+		return tokens[0], "", nil
+	default:
+		return "", "", verror.New(ErrInvalidSuffix, ctx)
+	}
+}
+
+func (i *appRepoService) Match(ctx *context.T, call rpc.ServerCall, profiles []string) (application.Envelope, error) {
+	ctx.VI(0).Infof("%v.Match(%v)", i.suffix, profiles)
+	empty := application.Envelope{}
+	name, version, err := parse(ctx, i.suffix)
+	if err != nil {
+		return empty, err
+	}
+
+	i.store.Lock()
+	defer i.store.Unlock()
+
+	if version == "" {
+		versions, err := i.allAppVersionsForProfiles(name, profiles)
+		if err != nil {
+			return empty, err
+		}
+		if len(versions) < 1 {
+			return empty, verror.New(ErrInvalidSuffix, ctx)
+		}
+		sort.Strings(versions)
+		version = versions[len(versions)-1]
+	}
+
+	for _, profile := range profiles {
+		path := naming.Join("/applications", name, profile, version)
+		entry, err := i.store.BindObject(path).Get(call)
+		if err != nil {
+			continue
+		}
+		envelope, ok := entry.Value.(application.Envelope)
+		if !ok {
+			continue
+		}
+		return envelope, nil
+	}
+	return empty, verror.New(verror.ErrNoExist, ctx)
+}
+
+func (i *appRepoService) Put(ctx *context.T, call rpc.ServerCall, profiles []string, envelope application.Envelope) error {
+	ctx.VI(0).Infof("%v.Put(%v, %v)", i.suffix, profiles, envelope)
+	name, version, err := parse(ctx, i.suffix)
+	if err != nil {
+		return err
+	}
+	if version == "" {
+		return verror.New(ErrInvalidSuffix, ctx)
+	}
+	i.store.Lock()
+	defer i.store.Unlock()
+	// Transaction is rooted at "", so tname == tid.
+	tname, err := i.store.BindTransactionRoot("").CreateTransaction(call)
+	if err != nil {
+		return err
+	}
+
+	// Only add a Permissions value if there is not already one present.
+	apath := naming.Join("/acls", name, "data")
+	aobj := i.store.BindObject(apath)
+	if _, err := aobj.Get(call); verror.ErrorID(err) == fs.ErrNotInMemStore.ID {
+		rb, _ := security.RemoteBlessingNames(ctx, call.Security())
+		if len(rb) == 0 {
+			// None of the client's blessings are valid.
+			return verror.New(ErrNotAuthorized, ctx)
+		}
+		newperms := pathperms.PermissionsForBlessings(rb)
+		if _, err := aobj.Put(nil, newperms); err != nil {
+			return err
+		}
+	}
+
+	for _, profile := range profiles {
+		path := naming.Join(tname, "/applications", name, profile, version)
+
+		object := i.store.BindObject(path)
+		_, err := object.Put(call, envelope)
+		if err != nil {
+			return verror.New(ErrOperationFailed, ctx)
+		}
+	}
+	if err := i.store.BindTransaction(tname).Commit(call); err != nil {
+		return verror.New(ErrOperationFailed, ctx)
+	}
+	return nil
+}
+
+func (i *appRepoService) Remove(ctx *context.T, call rpc.ServerCall, profile string) error {
+	ctx.VI(0).Infof("%v.Remove(%v)", i.suffix, profile)
+	name, version, err := parse(ctx, i.suffix)
+	if err != nil {
+		return err
+	}
+	i.store.Lock()
+	defer i.store.Unlock()
+	// Transaction is rooted at "", so tname == tid.
+	tname, err := i.store.BindTransactionRoot("").CreateTransaction(call)
+	if err != nil {
+		return err
+	}
+	path := naming.Join(tname, "/applications", name, profile)
+	if version != "" {
+		path += "/" + version
+	}
+	object := i.store.BindObject(path)
+	found, err := object.Exists(call)
+	if err != nil {
+		return verror.New(ErrOperationFailed, ctx)
+	}
+	if !found {
+		return verror.New(verror.ErrNoExist, ctx)
+	}
+	if err := object.Remove(call); err != nil {
+		return verror.New(ErrOperationFailed, ctx)
+	}
+	if err := i.store.BindTransaction(tname).Commit(call); err != nil {
+		return verror.New(ErrOperationFailed, ctx)
+	}
+	return nil
+}
+
+func (i *appRepoService) allApplications() ([]string, error) {
+	apps, err := i.store.BindObject("/applications").Children()
+	if err != nil {
+		return nil, err
+	}
+	return apps, nil
+}
+
+func (i *appRepoService) allAppVersionsForProfiles(appName string, profiles []string) ([]string, error) {
+	uniqueVersions := make(map[string]struct{})
+	for _, profile := range profiles {
+		versions, err := i.store.BindObject(naming.Join("/applications", appName, profile)).Children()
+		if verror.ErrorID(err) == verror.ErrNoExist.ID {
+			continue
+		} else if err != nil {
+			return nil, err
+		}
+		set.String.Union(uniqueVersions, set.String.FromSlice(versions))
+	}
+	return set.String.ToSlice(uniqueVersions), nil
+}
+
+func (i *appRepoService) allAppVersions(appName string) ([]string, error) {
+	profiles, err := i.store.BindObject(naming.Join("/applications", appName)).Children()
+	if err != nil {
+		return nil, err
+	}
+	return i.allAppVersionsForProfiles(appName, profiles)
+}
+
+func (i *appRepoService) GlobChildren__(ctx *context.T, call rpc.GlobChildrenServerCall, m *glob.Element) error {
+	ctx.VI(0).Infof("%v.GlobChildren__()", i.suffix)
+	i.store.Lock()
+	defer i.store.Unlock()
+
+	var elems []string
+	if i.suffix != "" {
+		elems = strings.Split(i.suffix, "/")
+	}
+
+	var results []string
+	var err error
+	switch len(elems) {
+	case 0:
+		results, err = i.allApplications()
+		if err != nil {
+			return err
+		}
+	case 1:
+		results, err = i.allAppVersions(elems[0])
+		if err != nil {
+			return err
+		}
+	case 2:
+		versions, err := i.allAppVersions(elems[0])
+		if err != nil {
+			return err
+		}
+		for _, v := range versions {
+			if v == elems[1] {
+				return nil
+			}
+		}
+		return verror.New(verror.ErrNoExist, nil)
+	default:
+		return verror.New(verror.ErrNoExist, nil)
+	}
+
+	for _, r := range results {
+		if m.Match(r) {
+			call.SendStream().Send(naming.GlobChildrenReplyName{Value: r})
+		}
+	}
+	return nil
+}
+
+func (i *appRepoService) GetPermissions(ctx *context.T, call rpc.ServerCall) (perms access.Permissions, version string, err error) {
+	name, _, err := parse(ctx, i.suffix)
+	if err != nil {
+		return nil, "", err
+	}
+	i.store.Lock()
+	defer i.store.Unlock()
+	path := naming.Join("/acls", name, "data")
+
+	perms, version, err = getPermissions(ctx, i.store, path)
+	if verror.ErrorID(err) == verror.ErrNoExist.ID {
+		return pathperms.NilAuthPermissions(ctx, call.Security()), "", nil
+	}
+
+	return perms, version, err
+}
+
+func (i *appRepoService) SetPermissions(ctx *context.T, _ rpc.ServerCall, perms access.Permissions, version string) error {
+	name, _, err := parse(ctx, i.suffix)
+	if err != nil {
+		return err
+	}
+	i.store.Lock()
+	defer i.store.Unlock()
+	path := naming.Join("/acls", name, "data")
+	return setPermissions(ctx, i.store, path, perms, version)
+}
+
+// getPermissions fetches a Permissions out of the Memstore at the provided path.
+// path is expected to already have been cleaned by naming.Join or its ilk.
+func getPermissions(ctx *context.T, store *fs.Memstore, path string) (access.Permissions, string, error) {
+	entry, err := store.BindObject(path).Get(nil)
+
+	if verror.ErrorID(err) == fs.ErrNotInMemStore.ID {
+		// No Permissions exists
+		return nil, "", verror.New(verror.ErrNoExist, nil)
+	} else if err != nil {
+		ctx.Errorf("getPermissions: internal failure in fs.Memstore")
+		return nil, "", err
+	}
+
+	perms, ok := entry.Value.(access.Permissions)
+	if !ok {
+		return nil, "", err
+	}
+
+	version, err := pathperms.ComputeVersion(perms)
+	if err != nil {
+		return nil, "", err
+	}
+	return perms, version, nil
+}
+
+// setPermissions writes a Permissions into the Memstore at the provided path.
+// where path is expected to have already been cleaned by naming.Join.
+func setPermissions(ctx *context.T, store *fs.Memstore, path string, perms access.Permissions, version string) error {
+	if version != "" {
+		_, oversion, err := getPermissions(ctx, store, path)
+		if verror.ErrorID(err) == verror.ErrNoExist.ID {
+			oversion = version
+		} else if err != nil {
+			return err
+		}
+
+		if oversion != version {
+			return verror.NewErrBadVersion(nil)
+		}
+	}
+
+	tname, err := store.BindTransactionRoot("").CreateTransaction(nil)
+	if err != nil {
+		return err
+	}
+
+	object := store.BindObject(path)
+
+	if _, err := object.Put(nil, perms); err != nil {
+		return err
+	}
+	if err := store.BindTransaction(tname).Commit(nil); err != nil {
+		return verror.New(ErrOperationFailed, nil)
+	}
+	return nil
+}
+
+func (i *appRepoService) tidyRemoveVersions(call rpc.ServerCall, tname, appName, profile string, versions []string) error {
+	for _, v := range versions {
+		path := naming.Join(tname, "/applications", appName, profile, v)
+		object := i.store.BindObject(path)
+		if err := object.Remove(call); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// numberOfVersionsToKeep can be set for tests.
+var numberOfVersionsToKeep = 5
+
+func (i *appRepoService) TidyNow(ctx *context.T, call rpc.ServerCall) error {
+	ctx.VI(2).Infof("%v.TidyNow()", i.suffix)
+	i.store.Lock()
+	defer i.store.Unlock()
+
+	tname, err := i.store.BindTransactionRoot("").CreateTransaction(call)
+	if err != nil {
+		return err
+	}
+
+	apps, err := i.allApplications()
+	if err != nil {
+		return err
+	}
+
+	for _, app := range apps {
+		profiles, err := i.store.BindObject(naming.Join("/applications", app)).Children()
+		if err != nil {
+			return err
+		}
+
+		for _, profile := range profiles {
+			versions, err := i.store.BindObject(naming.Join("/applications", app, profile)).Children()
+			if err != nil {
+				return err
+			}
+
+			lv := len(versions)
+			if lv <= numberOfVersionsToKeep {
+				continue
+			}
+
+			// Per assumption in Match, version names should ascend.
+			sort.Strings(versions)
+			versionsToRemove := versions[0 : lv-numberOfVersionsToKeep]
+			if err := i.tidyRemoveVersions(call, tname, app, profile, versionsToRemove); err != nil {
+				return err
+			}
+		}
+	}
+
+	if err := i.store.BindTransaction(tname).Commit(call); err != nil {
+		return verror.New(ErrOperationFailed, ctx)
+	}
+	return nil
+
+}
diff --git a/services/application/applicationd/v23_test.go b/services/application/applicationd/v23_test.go
new file mode 100644
index 0000000..fff5ec8
--- /dev/null
+++ b/services/application/applicationd/v23_test.go
@@ -0,0 +1,30 @@
+// 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 main_test
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
+	"v.io/x/ref/test/v23tests"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	modules.DispatchAndExitIfChild()
+	cleanup := v23tests.UseSharedBinDir()
+	r := m.Run()
+	cleanup()
+	os.Exit(r)
+}
+
+func TestV23ApplicationRepository(t *testing.T) {
+	v23tests.RunTest(t, V23TestApplicationRepository)
+}
diff --git a/services/binary/binary/doc.go b/services/binary/binary/doc.go
new file mode 100644
index 0000000..f609d8f
--- /dev/null
+++ b/services/binary/binary/doc.go
@@ -0,0 +1,132 @@
+// 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
+
+/*
+Command binary manages the Vanadium binary repository.
+
+Usage:
+   binary <command>
+
+The binary commands are:
+   delete      Delete a binary
+   download    Download a binary
+   upload      Upload a binary or directory archive
+   url         Fetch a download URL
+   help        Display help for commands or topics
+
+The global flags are:
+ -alsologtostderr=true
+   log to standard error as well as files
+ -log_backtrace_at=:0
+   when logging hits line file:N, emit a stack trace
+ -log_dir=
+   if non-empty, write log files to this directory
+ -logtostderr=false
+   log to standard error instead of files
+ -max_stack_buf_size=4292608
+   max size in bytes of the buffer to use for logging stack traces
+ -metadata=<just specify -metadata to activate>
+   Displays metadata for the program and exits.
+ -stderrthreshold=2
+   logs at or above this threshold go to stderr
+ -v=0
+   log level for V logs
+ -v23.credentials=
+   directory to use for storing security credentials
+ -v23.i18n-catalogue=
+   18n catalogue files to load, comma separated
+ -v23.namespace.root=[/(dev.v.io/role/vprod/service/mounttabled)@ns.dev.v.io:8101]
+   local namespace root; can be repeated to provided multiple roots
+ -v23.proxy=
+   object name of proxy service to use to export services across network
+   boundaries
+ -v23.tcp.address=
+   address to listen on
+ -v23.tcp.protocol=wsh
+   protocol to listen with
+ -v23.vtrace.cache-size=1024
+   The number of vtrace traces to store in memory.
+ -v23.vtrace.collect-regexp=
+   Spans and annotations that match this regular expression will trigger trace
+   collection.
+ -v23.vtrace.dump-on-shutdown=true
+   If true, dump all stored traces on runtime shutdown.
+ -v23.vtrace.sample-rate=0
+   Rate (from 0.0 to 1.0) to sample vtrace traces.
+ -vmodule=
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
+
+Binary delete - Delete a binary
+
+Delete connects to the binary repository and deletes the specified binary
+
+Usage:
+   binary delete <von>
+
+<von> is the vanadium object name of the binary to delete
+
+Binary download - Download a binary
+
+Download connects to the binary repository, downloads the specified binary, and
+writes it to a file.
+
+Usage:
+   binary download <von> <filename>
+
+<von> is the vanadium object name of the binary to download <filename> is the
+name of the file where the binary will be written
+
+Binary upload - Upload a binary or directory archive
+
+Upload connects to the binary repository and uploads the binary of the specified
+file or archive of the specified directory. When successful, it writes the name
+of the new binary to stdout.
+
+Usage:
+   binary upload <von> <filename>
+
+<von> is the vanadium object name of the binary to upload <filename> is the name
+of the file or directory to upload
+
+Binary url - Fetch a download URL
+
+Connect to the binary repository and fetch the download URL for the given
+vanadium object name.
+
+Usage:
+   binary url <von>
+
+<von> is the vanadium object name of the binary repository
+
+Binary help - Display help for commands or topics
+
+Help with no args displays the usage of the parent command.
+
+Help with args displays the usage of the specified sub-command or help topic.
+
+"help ..." recursively displays help for all commands and topics.
+
+Usage:
+   binary help [flags] [command/topic ...]
+
+[command/topic ...] optionally identifies a specific sub-command or help topic.
+
+The binary help flags are:
+ -style=compact
+   The formatting style for help output:
+      compact - Good for compact cmdline output.
+      full    - Good for cmdline output, shows all global flags.
+      godoc   - Good for godoc processing.
+   Override the default by setting the CMDLINE_STYLE environment variable.
+ -width=<terminal width>
+   Format output to this target width in runes, or unlimited if width < 0.
+   Defaults to the terminal width if available.  Override the default by setting
+   the CMDLINE_WIDTH environment variable.
+*/
+package main
diff --git a/services/binary/binary/impl.go b/services/binary/binary/impl.go
new file mode 100644
index 0000000..342eb2d
--- /dev/null
+++ b/services/binary/binary/impl.go
@@ -0,0 +1,143 @@
+// 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.
+
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go .
+
+package main
+
+import (
+	"fmt"
+	"os"
+
+	"v.io/v23/context"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/v23cmd"
+	_ "v.io/x/ref/runtime/factories/generic"
+	"v.io/x/ref/services/internal/binarylib"
+)
+
+func main() {
+	cmdline.HideGlobalFlagsExcept()
+	cmdline.Main(cmdRoot)
+}
+
+var cmdDelete = &cmdline.Command{
+	Runner:   v23cmd.RunnerFunc(runDelete),
+	Name:     "delete",
+	Short:    "Delete a binary",
+	Long:     "Delete connects to the binary repository and deletes the specified binary",
+	ArgsName: "<von>",
+	ArgsLong: "<von> is the vanadium object name of the binary to delete",
+}
+
+func runDelete(ctx *context.T, env *cmdline.Env, args []string) error {
+	if expected, got := 1, len(args); expected != got {
+		return env.UsageErrorf("delete: incorrect number of arguments, expected %d, got %d", expected, got)
+	}
+	von := args[0]
+	if err := binarylib.Delete(ctx, von); err != nil {
+		return err
+	}
+	fmt.Fprintf(env.Stdout, "Binary deleted successfully\n")
+	return nil
+}
+
+var cmdDownload = &cmdline.Command{
+	Runner: v23cmd.RunnerFunc(runDownload),
+	Name:   "download",
+	Short:  "Download a binary",
+	Long: `
+Download connects to the binary repository, downloads the specified binary, and
+writes it to a file.
+`,
+	ArgsName: "<von> <filename>",
+	ArgsLong: `
+<von> is the vanadium object name of the binary to download
+<filename> is the name of the file where the binary will be written
+`,
+}
+
+func runDownload(ctx *context.T, env *cmdline.Env, args []string) error {
+	if expected, got := 2, len(args); expected != got {
+		return env.UsageErrorf("download: incorrect number of arguments, expected %d, got %d", expected, got)
+	}
+	von, filename := args[0], args[1]
+	if err := binarylib.DownloadToFile(ctx, von, filename); err != nil {
+		return err
+	}
+	fmt.Fprintf(env.Stdout, "Binary downloaded to file %s\n", filename)
+	return nil
+}
+
+var cmdUpload = &cmdline.Command{
+	Runner: v23cmd.RunnerFunc(runUpload),
+	Name:   "upload",
+	Short:  "Upload a binary or directory archive",
+	Long: `
+Upload connects to the binary repository and uploads the binary of the specified
+file or archive of the specified directory. When successful, it writes the name of the new binary to stdout.
+`,
+	ArgsName: "<von> <filename>",
+	ArgsLong: `
+<von> is the vanadium object name of the binary to upload
+<filename> is the name of the file or directory to upload
+`,
+}
+
+func runUpload(ctx *context.T, env *cmdline.Env, args []string) error {
+	if expected, got := 2, len(args); expected != got {
+		return env.UsageErrorf("upload: incorrect number of arguments, expected %d, got %d", expected, got)
+	}
+	von, filename := args[0], args[1]
+	fi, err := os.Stat(filename)
+	if err != nil {
+		return err
+	}
+	if fi.IsDir() {
+		sig, err := binarylib.UploadFromDir(ctx, von, filename)
+		if err != nil {
+			return err
+		}
+		fmt.Fprintf(env.Stdout, "Binary package uploaded from directory %s signature(%v)\n", filename, sig)
+		return nil
+	}
+	sig, err := binarylib.UploadFromFile(ctx, von, filename)
+	if err != nil {
+		return err
+	}
+	fmt.Fprintf(env.Stdout, "Binary uploaded from file %s signature(%v)\n", filename, sig)
+	return nil
+}
+
+var cmdURL = &cmdline.Command{
+	Runner:   v23cmd.RunnerFunc(runURL),
+	Name:     "url",
+	Short:    "Fetch a download URL",
+	Long:     "Connect to the binary repository and fetch the download URL for the given vanadium object name.",
+	ArgsName: "<von>",
+	ArgsLong: "<von> is the vanadium object name of the binary repository",
+}
+
+func runURL(ctx *context.T, env *cmdline.Env, args []string) error {
+	if expected, got := 1, len(args); expected != got {
+		return env.UsageErrorf("rooturl: incorrect number of arguments, expected %d, got %d", expected, got)
+	}
+	von := args[0]
+	url, _, err := binarylib.DownloadUrl(ctx, von)
+	if err != nil {
+		return err
+	}
+	fmt.Fprintf(env.Stdout, "%v\n", url)
+	return nil
+}
+
+var cmdRoot = &cmdline.Command{
+	Name:  "binary",
+	Short: "manages the Vanadium binary repository",
+	Long: `
+Command binary manages the Vanadium binary repository.
+`,
+	Children: []*cmdline.Command{cmdDelete, cmdDownload, cmdUpload, cmdURL},
+}
diff --git a/services/binary/binary/impl_test.go b/services/binary/binary/impl_test.go
new file mode 100644
index 0000000..1036c8d
--- /dev/null
+++ b/services/binary/binary/impl_test.go
@@ -0,0 +1,162 @@
+// 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 (
+	"bytes"
+	"crypto/md5"
+	"encoding/hex"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path"
+	"strings"
+	"testing"
+
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/security/access"
+	"v.io/v23/services/binary"
+	"v.io/v23/services/repository"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/v23cmd"
+	"v.io/x/ref/lib/xrpc"
+	_ "v.io/x/ref/runtime/factories/generic"
+	"v.io/x/ref/test"
+)
+
+//go:generate v23 test generate
+
+type server struct {
+	suffix string
+}
+
+func (s *server) Create(ctx *context.T, _ rpc.ServerCall, _ int32, _ repository.MediaInfo) error {
+	ctx.Infof("Create() was called. suffix=%v", s.suffix)
+	return nil
+}
+
+func (s *server) Delete(ctx *context.T, _ rpc.ServerCall) error {
+	ctx.Infof("Delete() was called. suffix=%v", s.suffix)
+	if s.suffix != "exists" {
+		return fmt.Errorf("binary doesn't exist: %v", s.suffix)
+	}
+	return nil
+}
+
+func (s *server) Download(ctx *context.T, call repository.BinaryDownloadServerCall, _ int32) error {
+	ctx.Infof("Download() was called. suffix=%v", s.suffix)
+	sender := call.SendStream()
+	sender.Send([]byte("Hello"))
+	sender.Send([]byte("World"))
+	return nil
+}
+
+func (s *server) DownloadUrl(ctx *context.T, _ rpc.ServerCall) (string, int64, error) {
+	ctx.Infof("DownloadUrl() was called. suffix=%v", s.suffix)
+	if s.suffix != "" {
+		return "", 0, fmt.Errorf("non-empty suffix: %v", s.suffix)
+	}
+	return "test-download-url", 0, nil
+}
+
+func (s *server) Stat(ctx *context.T, _ rpc.ServerCall) ([]binary.PartInfo, repository.MediaInfo, error) {
+	ctx.Infof("Stat() was called. suffix=%v", s.suffix)
+	h := md5.New()
+	text := "HelloWorld"
+	h.Write([]byte(text))
+	part := binary.PartInfo{Checksum: hex.EncodeToString(h.Sum(nil)), Size: int64(len(text))}
+	return []binary.PartInfo{part}, repository.MediaInfo{Type: "text/plain"}, nil
+}
+
+func (s *server) Upload(ctx *context.T, call repository.BinaryUploadServerCall, _ int32) error {
+	ctx.Infof("Upload() was called. suffix=%v", s.suffix)
+	rStream := call.RecvStream()
+	for rStream.Advance() {
+	}
+	return nil
+}
+
+func (s *server) GetPermissions(ctx *context.T, _ rpc.ServerCall) (perms access.Permissions, version string, err error) {
+	return nil, "", nil
+}
+
+func (s *server) SetPermissions(ctx *context.T, _ rpc.ServerCall, perms access.Permissions, version string) error {
+	return nil
+}
+
+type dispatcher struct {
+}
+
+func NewDispatcher() rpc.Dispatcher {
+	return &dispatcher{}
+}
+
+func (d *dispatcher) Lookup(_ *context.T, suffix string) (interface{}, security.Authorizer, error) {
+	return repository.BinaryServer(&server{suffix: suffix}), nil, nil
+}
+
+func TestBinaryClient(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	server, err := xrpc.NewDispatchingServer(ctx, "", NewDispatcher())
+	if err != nil {
+		t.Fatalf("NewServer failed: %v", err)
+	}
+	endpoint := server.Status().Endpoints[0]
+
+	// Setup the command-line.
+	var out bytes.Buffer
+	env := &cmdline.Env{Stdout: &out, Stderr: &out}
+
+	// Test the 'delete' command.
+	if err := v23cmd.ParseAndRunForTest(cmdRoot, ctx, env, []string{"delete", naming.JoinAddressName(endpoint.String(), "exists")}); err != nil {
+		t.Fatalf("%v failed: %v\n%v", "delete", err, out.String())
+	}
+	if expected, got := "Binary deleted successfully", strings.TrimSpace(out.String()); got != expected {
+		t.Errorf("Got %q, expected %q", got, expected)
+	}
+	out.Reset()
+
+	// Test the 'download' command.
+	dir, err := ioutil.TempDir("", "binaryimpltest")
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	defer os.RemoveAll(dir)
+	file := path.Join(dir, "testfile")
+	defer os.Remove(file)
+	if err := v23cmd.ParseAndRunForTest(cmdRoot, ctx, env, []string{"download", naming.JoinAddressName(endpoint.String(), "exists"), file}); err != nil {
+		t.Fatalf("%v failed: %v\n%v", "download", err, out.String())
+	}
+	if expected, got := "Binary downloaded to file "+file, strings.TrimSpace(out.String()); got != expected {
+		t.Errorf("Got %q, expected %q", got, expected)
+	}
+	buf, err := ioutil.ReadFile(file)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	if expected := "HelloWorld"; string(buf) != expected {
+		t.Errorf("Got %q, expected %q", string(buf), expected)
+	}
+	out.Reset()
+
+	// Test the 'upload' command.
+	if err := v23cmd.ParseAndRunForTest(cmdRoot, ctx, env, []string{"upload", naming.JoinAddressName(endpoint.String(), "exists"), file}); err != nil {
+		t.Fatalf("%v failed: %v\n%v", "upload", err, out.String())
+	}
+	out.Reset()
+
+	// Test the 'url' command.
+	if err := v23cmd.ParseAndRunForTest(cmdRoot, ctx, env, []string{"url", naming.JoinAddressName(endpoint.String(), "")}); err != nil {
+		t.Fatalf("%v failed: %v\n%v", "url", err, out.String())
+	}
+	if expected, got := "test-download-url", strings.TrimSpace(out.String()); got != expected {
+		t.Errorf("Got %q, expected %q", got, expected)
+	}
+}
diff --git a/services/binary/binary/v23_internal_test.go b/services/binary/binary/v23_internal_test.go
new file mode 100644
index 0000000..ae59080
--- /dev/null
+++ b/services/binary/binary/v23_internal_test.go
@@ -0,0 +1,20 @@
+// 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 main
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	os.Exit(m.Run())
+}
diff --git a/services/binary/binaryd/binaryd_v23_test.go b/services/binary/binaryd/binaryd_v23_test.go
new file mode 100644
index 0000000..cdd6faf
--- /dev/null
+++ b/services/binary/binaryd/binaryd_v23_test.go
@@ -0,0 +1,181 @@
+// 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_test
+
+import (
+	"bytes"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"os"
+	"os/exec"
+	"strings"
+
+	"v.io/v23/naming"
+	"v.io/x/ref/test/testutil"
+	"v.io/x/ref/test/v23tests"
+)
+
+//go:generate v23 test generate
+
+func checkFileType(i *v23tests.T, file, typeString string) {
+	var catOut bytes.Buffer
+	catCmd := exec.Command("cat", file+".__info")
+	catCmd.Stdout = &catOut
+	catCmd.Stderr = &catOut
+	if err := catCmd.Run(); err != nil {
+		i.Fatalf("%q failed: %v\n%v", strings.Join(catCmd.Args, " "), err, catOut.String())
+	}
+	if got, want := strings.TrimSpace(catOut.String()), typeString; got != want {
+		i.Fatalf("unexpect file type: got %v, want %v", got, want)
+	}
+}
+
+func readFileOrDie(i *v23tests.T, path string) []byte {
+	result, err := ioutil.ReadFile(path)
+	if err != nil {
+		i.Fatalf("ReadFile(%q) failed: %v", path, err)
+	}
+	return result
+}
+
+func compareFiles(i *v23tests.T, f1, f2 string) {
+	if !bytes.Equal(readFileOrDie(i, f1), readFileOrDie(i, f2)) {
+		i.Fatalf("the contents of %s and %s differ when they should not", f1, f2)
+	}
+}
+
+func deleteFile(i *v23tests.T, clientBin *v23tests.Binary, name, suffix string) {
+	clientBin.Start("delete", naming.Join(name, suffix)).WaitOrDie(os.Stdout, os.Stderr)
+}
+
+func downloadFile(i *v23tests.T, clientBin *v23tests.Binary, expectError bool, name, path, suffix string) {
+	args := []string{"download", naming.Join(name, suffix), path}
+	err := clientBin.Start(args...).Wait(os.Stdout, os.Stderr)
+	if expectError && err == nil {
+		i.Fatalf("%s %v: did not fail when it should", clientBin.Path(), args)
+	}
+	if !expectError && err != nil {
+		i.Fatalf("%s %v: failed: %v", clientBin.Path(), args, err)
+	}
+}
+
+func downloadURL(i *v23tests.T, path, rootURL, suffix string) {
+	url := fmt.Sprintf("http://%v/%v", rootURL, suffix)
+	resp, err := http.Get(url)
+	if err != nil {
+		i.Fatalf("Get(%q) failed: %v", url, err)
+	}
+	output, err := ioutil.ReadAll(resp.Body)
+	resp.Body.Close()
+	if err != nil {
+		i.Fatalf("ReadAll() failed: %v", err)
+	}
+	if err = ioutil.WriteFile(path, output, 0600); err != nil {
+		i.Fatalf("WriteFile() failed: %v", err)
+	}
+}
+
+func rootURL(i *v23tests.T, clientBin *v23tests.Binary, name string) string {
+	return strings.TrimSpace(clientBin.Start("url", name).Output())
+}
+
+func uploadFile(i *v23tests.T, clientBin *v23tests.Binary, name, path, suffix string) {
+	clientBin.Start("upload", naming.Join(name, suffix), path).WaitOrDie(os.Stdout, os.Stderr)
+}
+
+func binaryWithCredentials(i *v23tests.T, extension, pkgpath string) *v23tests.Binary {
+	creds, err := i.Shell().NewChildCredentials(extension)
+	if err != nil {
+		i.Fatalf("NewCustomCredentials (for %q) failed: %v", pkgpath, err)
+	}
+	b := i.BuildV23Pkg(pkgpath)
+	return b.WithStartOpts(b.StartOpts().WithCustomCredentials(creds))
+}
+
+func V23TestBinaryRepositoryIntegration(i *v23tests.T) {
+	testutil.InitRandGenerator(i.Logf)
+	v23tests.RunRootMT(i, "--v23.tcp.address=127.0.0.1:0")
+
+	// Build the required binaries.
+	// The client must run as a "delegate" of the server in order to pass
+	// the default authorization checks on the server.
+	var (
+		binaryRepoBin = binaryWithCredentials(i, "binaryd", "v.io/x/ref/services/binary/binaryd")
+		clientBin     = binaryWithCredentials(i, "binaryd/client", "v.io/x/ref/services/binary/binary")
+	)
+
+	// Start the build server.
+	binaryRepoName := "test-binary-repository"
+	binaryRepoBin.Start(
+		"-name="+binaryRepoName,
+		"-http=127.0.0.1:0",
+		"-v23.tcp.address=127.0.0.1:0")
+
+	// Upload a random binary file.
+	binFile := i.NewTempFile()
+	if _, err := binFile.Write(testutil.RandomBytes(16 * 1000 * 1000)); err != nil {
+		i.Fatalf("Write() failed: %v", err)
+	}
+	binSuffix := "test-binary"
+	uploadFile(i, clientBin, binaryRepoName, binFile.Name(), binSuffix)
+
+	// Upload a compressed version of the binary file.
+	tarFile := binFile.Name() + ".tar.gz"
+	var tarOut bytes.Buffer
+	tarCmd := exec.Command("tar", "zcvf", tarFile, binFile.Name())
+	tarCmd.Stdout = &tarOut
+	tarCmd.Stderr = &tarOut
+	if err := tarCmd.Run(); err != nil {
+		i.Fatalf("%q failed: %v\n%v", strings.Join(tarCmd.Args, " "), err, tarOut.String())
+	}
+	defer os.Remove(tarFile)
+	tarSuffix := "test-compressed-file"
+	uploadFile(i, clientBin, binaryRepoName, tarFile, tarSuffix)
+
+	// Download the binary file and check that it matches the
+	// original one and that it has the right file type.
+	downloadedBinFile := binFile.Name() + "-downloaded"
+	defer os.Remove(downloadedBinFile)
+	downloadFile(i, clientBin, false, binaryRepoName, downloadedBinFile, binSuffix)
+	compareFiles(i, binFile.Name(), downloadedBinFile)
+	checkFileType(i, downloadedBinFile, `{"Type":"application/octet-stream","Encoding":""}`)
+
+	// Download the compressed version of the binary file and
+	// check that it matches the original one and that it has the
+	// right file type.
+	downloadedTarFile := binFile.Name() + "-downloaded.tar.gz"
+	defer os.Remove(downloadedTarFile)
+	downloadFile(i, clientBin, false, binaryRepoName, downloadedTarFile, tarSuffix)
+	compareFiles(i, tarFile, downloadedTarFile)
+	checkFileType(i, downloadedTarFile, `{"Type":"application/x-tar","Encoding":"gzip"}`)
+
+	// Fetch the root URL of the HTTP server used by the binary
+	// repository to serve URLs.
+	root := rootURL(i, clientBin, binaryRepoName)
+
+	// Download the binary file using the HTTP protocol and check
+	// that it matches the original one.
+	downloadedBinFileURL := binFile.Name() + "-downloaded-url"
+	defer os.Remove(downloadedBinFileURL)
+	downloadURL(i, downloadedBinFileURL, root, binSuffix)
+	compareFiles(i, downloadedBinFile, downloadedBinFileURL)
+
+	// Download the compressed version of the binary file using
+	// the HTTP protocol and check that it matches the original
+	// one.
+	downloadedTarFileURL := binFile.Name() + "-downloaded-url.tar.gz"
+	defer os.Remove(downloadedTarFileURL)
+	downloadURL(i, downloadedTarFileURL, root, tarSuffix)
+	compareFiles(i, downloadedTarFile, downloadedTarFileURL)
+
+	// Delete the files.
+	deleteFile(i, clientBin, binaryRepoName, binSuffix)
+	deleteFile(i, clientBin, binaryRepoName, tarSuffix)
+
+	// Check the files no longer exist.
+	downloadFile(i, clientBin, true, binaryRepoName, downloadedBinFile, binSuffix)
+	downloadFile(i, clientBin, true, binaryRepoName, downloadedTarFile, tarSuffix)
+}
diff --git a/services/binary/binaryd/doc.go b/services/binary/binaryd/doc.go
new file mode 100644
index 0000000..e3c8fa3
--- /dev/null
+++ b/services/binary/binaryd/doc.go
@@ -0,0 +1,72 @@
+// 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
+
+/*
+Command binaryd runs the binary daemon, which implements the
+v.io/v23/services/repository.Binary interface.
+
+Usage:
+   binaryd [flags]
+
+The binaryd flags are:
+ -http=:0
+   TCP address on which the HTTP server runs.
+ -name=
+   Name to mount the binary repository as.
+ -root-dir=
+   Root directory for the binary repository.
+
+The global flags are:
+ -alsologtostderr=true
+   log to standard error as well as files
+ -log_backtrace_at=:0
+   when logging hits line file:N, emit a stack trace
+ -log_dir=
+   if non-empty, write log files to this directory
+ -logtostderr=false
+   log to standard error instead of files
+ -max_stack_buf_size=4292608
+   max size in bytes of the buffer to use for logging stack traces
+ -metadata=<just specify -metadata to activate>
+   Displays metadata for the program and exits.
+ -stderrthreshold=2
+   logs at or above this threshold go to stderr
+ -v=0
+   log level for V logs
+ -v23.credentials=
+   directory to use for storing security credentials
+ -v23.i18n-catalogue=
+   18n catalogue files to load, comma separated
+ -v23.namespace.root=[/(dev.v.io/role/vprod/service/mounttabled)@ns.dev.v.io:8101]
+   local namespace root; can be repeated to provided multiple roots
+ -v23.permissions.file=map[]
+   specify a perms file as <name>:<permsfile>
+ -v23.permissions.literal=
+   explicitly specify the runtime perms as a JSON-encoded access.Permissions.
+   Overrides all --v23.permissions.file flags.
+ -v23.proxy=
+   object name of proxy service to use to export services across network
+   boundaries
+ -v23.tcp.address=
+   address to listen on
+ -v23.tcp.protocol=wsh
+   protocol to listen with
+ -v23.vtrace.cache-size=1024
+   The number of vtrace traces to store in memory.
+ -v23.vtrace.collect-regexp=
+   Spans and annotations that match this regular expression will trigger trace
+   collection.
+ -v23.vtrace.dump-on-shutdown=true
+   If true, dump all stored traces on runtime shutdown.
+ -v23.vtrace.sample-rate=0
+   Rate (from 0.0 to 1.0) to sample vtrace traces.
+ -vmodule=
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
+*/
+package main
diff --git a/services/binary/binaryd/main.go b/services/binary/binaryd/main.go
new file mode 100644
index 0000000..d7fc5f1
--- /dev/null
+++ b/services/binary/binaryd/main.go
@@ -0,0 +1,115 @@
+// 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.
+
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go . -help
+
+package main
+
+import (
+	"fmt"
+	"net"
+	"net/http"
+	"os"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/x/lib/cmdline"
+	"v.io/x/lib/netstate"
+	"v.io/x/ref/lib/signals"
+	"v.io/x/ref/lib/v23cmd"
+	"v.io/x/ref/lib/xrpc"
+	_ "v.io/x/ref/runtime/factories/roaming"
+	"v.io/x/ref/services/internal/binarylib"
+)
+
+const defaultDepth = 3
+
+var name, rootDirFlag, httpAddr string
+
+func main() {
+	cmdBinaryD.Flags.StringVar(&name, "name", "", "Name to mount the binary repository as.")
+	cmdBinaryD.Flags.StringVar(&rootDirFlag, "root-dir", "", "Root directory for the binary repository.")
+	cmdBinaryD.Flags.StringVar(&httpAddr, "http", ":0", "TCP address on which the HTTP server runs.")
+
+	cmdline.HideGlobalFlagsExcept()
+	cmdline.Main(cmdBinaryD)
+}
+
+var cmdBinaryD = &cmdline.Command{
+	Runner: v23cmd.RunnerFunc(runBinaryD),
+	Name:   "binaryd",
+	Short:  "Runs the binary daemon.",
+	Long: `
+Command binaryd runs the binary daemon, which implements the
+v.io/v23/services/repository.Binary interface.
+`,
+}
+
+// toIPPort tries to swap in the 'best' accessible IP for the host part of the
+// address, if the provided address has an unspecified IP.
+func toIPPort(ctx *context.T, addr string) string {
+	// TODO(caprita): consider using netstate.PossibleAddresses()
+	host, port, err := net.SplitHostPort(addr)
+	if err != nil {
+		ctx.Errorf("SplitHostPort(%v) failed: %v", addr, err)
+		os.Exit(1)
+	}
+	ip := net.ParseIP(host)
+	if ip.IsUnspecified() {
+		host = "127.0.0.1"
+		ips, err := netstate.GetAccessibleIPs()
+		if err == nil {
+			ls := v23.GetListenSpec(ctx)
+			if a, err := ls.AddressChooser.ChooseAddress("tcp", ips.AsNetAddrs()); err == nil && len(a) > 0 {
+				host = a[0].String()
+			}
+		}
+	}
+	return net.JoinHostPort(host, port)
+}
+
+func runBinaryD(ctx *context.T, env *cmdline.Env, args []string) error {
+	rootDir, err := binarylib.SetupRootDir(rootDirFlag)
+	if err != nil {
+		return fmt.Errorf("SetupRootDir(%q) failed: %v", rootDirFlag, err)
+	}
+	ctx.Infof("Binary repository rooted at %v", rootDir)
+
+	listener, err := net.Listen("tcp", httpAddr)
+	if err != nil {
+		return fmt.Errorf("Listen(%s) failed: %v", httpAddr, err)
+	}
+	rootURL := toIPPort(ctx, listener.Addr().String())
+	state, err := binarylib.NewState(rootDir, rootURL, defaultDepth)
+	if err != nil {
+		return fmt.Errorf("NewState(%v, %v, %v) failed: %v", rootDir, rootURL, defaultDepth, err)
+	}
+	ctx.Infof("Binary repository HTTP server at: %q", rootURL)
+	go func() {
+		if err := http.Serve(listener, http.FileServer(binarylib.NewHTTPRoot(ctx, state))); err != nil {
+			ctx.Errorf("Serve() failed: %v", err)
+			os.Exit(1)
+		}
+	}()
+
+	dis, err := binarylib.NewDispatcher(ctx, state)
+	if err != nil {
+		return fmt.Errorf("NewDispatcher() failed: %v\n", err)
+	}
+	server, err := xrpc.NewDispatchingServer(ctx, name, dis)
+	if err != nil {
+		return fmt.Errorf("NewServer() failed: %v", err)
+	}
+	defer server.Stop()
+	epName := server.Status().Endpoints[0].Name()
+	if name != "" {
+		ctx.Infof("Binary repository serving at %q (%q)", name, epName)
+	} else {
+		ctx.Infof("Binary repository serving at %q", epName)
+	}
+	// Wait until shutdown.
+	<-signals.ShutdownOnSignals(ctx)
+	return nil
+}
diff --git a/services/binary/binaryd/v23_test.go b/services/binary/binaryd/v23_test.go
new file mode 100644
index 0000000..5c12adf
--- /dev/null
+++ b/services/binary/binaryd/v23_test.go
@@ -0,0 +1,30 @@
+// 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 main_test
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
+	"v.io/x/ref/test/v23tests"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	modules.DispatchAndExitIfChild()
+	cleanup := v23tests.UseSharedBinDir()
+	r := m.Run()
+	cleanup()
+	os.Exit(r)
+}
+
+func TestV23BinaryRepositoryIntegration(t *testing.T) {
+	v23tests.RunTest(t, V23TestBinaryRepositoryIntegration)
+}
diff --git a/services/binary/tidy/appd/mock.go b/services/binary/tidy/appd/mock.go
new file mode 100644
index 0000000..d401402
--- /dev/null
+++ b/services/binary/tidy/appd/mock.go
@@ -0,0 +1,55 @@
+// 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 appd
+
+import (
+	"testing"
+
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/services/application"
+
+	"v.io/x/ref/services/binary/tidy/binaryd"
+	"v.io/x/ref/services/internal/servicetest"
+)
+
+type mockAppdInvoker struct {
+	binaryd.MockBinarydInvoker
+}
+
+type MatchStimulus struct {
+	Name     string
+	Suffix   string
+	Profiles []string
+}
+
+type MatchResult struct {
+	Env application.Envelope
+	Err error
+}
+
+func (mdi *mockAppdInvoker) Match(ctx *context.T, _ rpc.ServerCall, profiles []string) (application.Envelope, error) {
+	ir := mdi.Tape.Record(MatchStimulus{"Match", mdi.Suffix, profiles})
+	r := ir.(MatchResult)
+	return r.Env, r.Err
+}
+
+func (mdi *mockAppdInvoker) TidyNow(ctx *context.T, _ rpc.ServerCall) error {
+	return mdi.SimpleCore("TidyNow", "TidyNow")
+}
+
+type dispatcher struct {
+	tape *servicetest.Tape
+	t    *testing.T
+}
+
+func NewDispatcher(t *testing.T, tape *servicetest.Tape) rpc.Dispatcher {
+	return &dispatcher{tape: tape, t: t}
+}
+
+func (d *dispatcher) Lookup(p *context.T, suffix string) (interface{}, security.Authorizer, error) {
+	return &mockAppdInvoker{binaryd.NewMockBinarydInvoker(suffix, d.tape, d.t)}, nil, nil
+}
diff --git a/services/binary/tidy/binaryd/mock.go b/services/binary/tidy/binaryd/mock.go
new file mode 100644
index 0000000..d3efd8c
--- /dev/null
+++ b/services/binary/tidy/binaryd/mock.go
@@ -0,0 +1,98 @@
+// 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 binaryd
+
+import (
+	"log"
+	"testing"
+
+	"v.io/v23/context"
+	"v.io/v23/glob"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/services/binary"
+	"v.io/v23/services/repository"
+
+	"v.io/x/ref/services/internal/servicetest"
+)
+
+type MockBinarydInvoker struct {
+	Suffix string
+	Tape   *servicetest.Tape
+	t      *testing.T
+}
+
+// simpleCore implements the core of all mock methods that take
+// arguments and return error.
+func (mdi *MockBinarydInvoker) SimpleCore(callRecord interface{}, name string) error {
+	ri := mdi.Tape.Record(callRecord)
+	switch r := ri.(type) {
+	case nil:
+		return nil
+	case error:
+		return r
+	}
+	log.Fatalf("%s (mock) response %v is of bad type", name, ri)
+	return nil
+}
+
+type DeleteStimulus struct {
+	Op     string
+	Suffix string
+}
+
+func (mdi *MockBinarydInvoker) Delete(ctx *context.T, _ rpc.ServerCall) error {
+	return mdi.SimpleCore(DeleteStimulus{"Delete", mdi.Suffix}, "Delete")
+}
+
+type StatStimulus struct {
+	Op     string
+	Suffix string
+}
+
+func (mdi *MockBinarydInvoker) Stat(ctx *context.T, _ rpc.ServerCall) ([]binary.PartInfo, repository.MediaInfo, error) {
+	// Only the presence or absence of the error is necessary.
+	if err := mdi.SimpleCore(StatStimulus{"Stat", mdi.Suffix}, "Stat"); err != nil {
+		return nil, repository.MediaInfo{}, err
+	}
+	return nil, repository.MediaInfo{}, nil
+}
+
+type GlobStimulus struct {
+	Pattern string
+}
+
+type GlobResponse struct {
+	Results []string
+	Err     error
+}
+
+func (mdi *MockBinarydInvoker) Glob__(p *context.T, call rpc.GlobServerCall, g *glob.Glob) error {
+	gs := GlobStimulus{g.String()}
+	gr := mdi.Tape.Record(gs).(GlobResponse)
+	for _, r := range gr.Results {
+		call.SendStream().Send(naming.GlobReplyEntry{Value: naming.MountEntry{Name: r}})
+	}
+	return gr.Err
+}
+
+type dispatcher struct {
+	tape *servicetest.Tape
+	t    *testing.T
+}
+
+func NewDispatcher(t *testing.T, tape *servicetest.Tape) rpc.Dispatcher {
+	return &dispatcher{tape: tape, t: t}
+}
+
+func NewMockBinarydInvoker(suffix string, tape *servicetest.Tape, t *testing.T) MockBinarydInvoker {
+	return MockBinarydInvoker{Suffix: suffix, Tape: tape, t: t}
+}
+
+func (d *dispatcher) Lookup(p *context.T, suffix string) (interface{}, security.Authorizer, error) {
+	v := NewMockBinarydInvoker(suffix, d.tape, d.t)
+	return &v, nil, nil
+}
diff --git a/services/binary/tidy/doc.go b/services/binary/tidy/doc.go
new file mode 100644
index 0000000..7f67a37
--- /dev/null
+++ b/services/binary/tidy/doc.go
@@ -0,0 +1,110 @@
+// 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
+
+/*
+Tidy tidies the Vanadium repository by removing unused envelopes and binaries.
+
+Usage:
+   tidy <command>
+
+The tidy commands are:
+   binary      Binary sub-command tidies a specified binaryd
+   application Application sub-command tidies a specified applicationd
+   help        Display help for commands or topics
+
+The global flags are:
+ -alsologtostderr=true
+   log to standard error as well as files
+ -log_backtrace_at=:0
+   when logging hits line file:N, emit a stack trace
+ -log_dir=
+   if non-empty, write log files to this directory
+ -logtostderr=false
+   log to standard error instead of files
+ -max_stack_buf_size=4292608
+   max size in bytes of the buffer to use for logging stack traces
+ -metadata=<just specify -metadata to activate>
+   Displays metadata for the program and exits.
+ -stderrthreshold=2
+   logs at or above this threshold go to stderr
+ -v=0
+   log level for V logs
+ -v23.credentials=
+   directory to use for storing security credentials
+ -v23.i18n-catalogue=
+   18n catalogue files to load, comma separated
+ -v23.namespace.root=[/(dev.v.io/role/vprod/service/mounttabled)@ns.dev.v.io:8101]
+   local namespace root; can be repeated to provided multiple roots
+ -v23.proxy=
+   object name of proxy service to use to export services across network
+   boundaries
+ -v23.tcp.address=
+   address to listen on
+ -v23.tcp.protocol=wsh
+   protocol to listen with
+ -v23.vtrace.cache-size=1024
+   The number of vtrace traces to store in memory.
+ -v23.vtrace.collect-regexp=
+   Spans and annotations that match this regular expression will trigger trace
+   collection.
+ -v23.vtrace.dump-on-shutdown=true
+   If true, dump all stored traces on runtime shutdown.
+ -v23.vtrace.sample-rate=0
+   Rate (from 0.0 to 1.0) to sample vtrace traces.
+ -vmodule=
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
+
+Tidy binary - Binary sub-command tidies a specified binaryd
+
+Binary sub-command removes all binaries from a specified binaryd that are not
+referenced by an applicationd envelope stored in the specified applicationd.
+
+Usage:
+   tidy binary <applicationd> <binaryd>
+
+<applicationd> is the name or endpoint of the applicationd instance sourcing the
+envelopes. <binaryd> is the name or endpoint of a binaryd instance to clean up.
+
+Tidy application - Application sub-command tidies a specified applicationd
+
+Application sub-command uses the Tidy RPC to clean up outdated envelopes from
+the specified appilcationd. Call this before tidying a binaryd instance for
+maximum tidiness.
+
+Usage:
+   tidy application <applicationd>
+
+<applicationd> is the name or endpoint of the applicationd instance to tidy.
+
+Tidy help - Display help for commands or topics
+
+Help with no args displays the usage of the parent command.
+
+Help with args displays the usage of the specified sub-command or help topic.
+
+"help ..." recursively displays help for all commands and topics.
+
+Usage:
+   tidy help [flags] [command/topic ...]
+
+[command/topic ...] optionally identifies a specific sub-command or help topic.
+
+The tidy help flags are:
+ -style=compact
+   The formatting style for help output:
+      compact - Good for compact cmdline output.
+      full    - Good for cmdline output, shows all global flags.
+      godoc   - Good for godoc processing.
+   Override the default by setting the CMDLINE_STYLE environment variable.
+ -width=<terminal width>
+   Format output to this target width in runes, or unlimited if width < 0.
+   Defaults to the terminal width if available.  Override the default by setting
+   the CMDLINE_WIDTH environment variable.
+*/
+package main
diff --git a/services/binary/tidy/impl.go b/services/binary/tidy/impl.go
new file mode 100644
index 0000000..1f17f80
--- /dev/null
+++ b/services/binary/tidy/impl.go
@@ -0,0 +1,225 @@
+// 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.
+
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go .
+
+package main
+
+import (
+	"fmt"
+	"sort"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/x/lib/cmdline"
+	"v.io/x/lib/set"
+	"v.io/x/lib/vlog"
+	"v.io/x/ref/lib/v23cmd"
+	_ "v.io/x/ref/runtime/factories/generic"
+	"v.io/x/ref/services/internal/binarylib"
+	"v.io/x/ref/services/internal/profiles"
+	"v.io/x/ref/services/repository"
+)
+
+var cmdBinaryTidy = &cmdline.Command{
+	Runner: v23cmd.RunnerFunc(runBinaryTidy),
+	Name:   "binary",
+	Short:  "Binary sub-command tidies a specified binaryd",
+	Long: `
+Binary sub-command removes all binaries from a specified binaryd that
+are not referenced by an applicationd envelope stored in the specified
+applicationd.
+`,
+	ArgsName: "<applicationd> <binaryd>",
+	ArgsLong: `
+<applicationd> is the name or endpoint of the applicationd instance
+sourcing the envelopes.
+<binaryd> is the name or endpoint of a binaryd instance to clean up.
+`,
+}
+
+// simpleGlob globs the provided endpoint as the namespace cmd does.
+func mapGlob(ctx *context.T, pattern string, mapFunc func(string)) (error, []error) {
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
+	defer cancel()
+
+	ns := v23.GetNamespace(ctx)
+	c, err := ns.Glob(ctx, pattern)
+	if err != nil {
+		vlog.Infof("ns.Glob(%q) failed: %v", pattern, err)
+		return err, nil
+	}
+
+	errors := []*naming.GlobError{}
+	for res := range c {
+		switch v := res.(type) {
+		case *naming.GlobReplyEntry:
+			if v.Value.Name != "" {
+				mapFunc(v.Value.Name)
+			}
+		case *naming.GlobReplyError:
+			errors = append(errors, &v.Value)
+		}
+	}
+
+	globErrors := make([]error, 0, len(errors))
+	for _, err := range errors {
+		globErrors = append(globErrors, fmt.Errorf("Glob error: %s: %v\n", err.Name, err.Error))
+	}
+	return nil, globErrors
+}
+
+func logGlobErrors(env *cmdline.Env, errors []error) {
+	for _, err := range errors {
+		vlog.Errorf("Glob error: %v", err)
+	}
+}
+
+// getProfileNames uses glob to extract the list of profile names
+// available from the binary server specified by endpoint.
+func getProfileNames(ctx *context.T, env *cmdline.Env, endpoint string) ([]string, error) {
+	profiles, err := profiles.GetKnownProfiles()
+	if err != nil {
+		return nil, err
+	}
+
+	pnames := make([]string, 0, len(profiles))
+	for _, p := range profiles {
+		pnames = append(pnames, p.Label)
+	}
+	return pnames, nil
+}
+
+func getNames(ctx *context.T, env *cmdline.Env, endpoint string) ([]string, error) {
+	resultSet := make(map[string]struct{})
+	err, errors := mapGlob(ctx, endpoint, func(s string) {
+		resultSet[s] = struct{}{}
+	})
+
+	if err != nil {
+		return nil, err
+	}
+	logGlobErrors(env, errors)
+	s := set.String.ToSlice(resultSet)
+	sort.Strings(s)
+	return s, nil
+}
+
+func runBinaryTidy(ctx *context.T, env *cmdline.Env, args []string) error {
+	if expected, got := 2, len(args); expected != got {
+		return env.UsageErrorf("match: incorrect number of arguments, expected %d, got %d", expected, got)
+	}
+
+	appEndpoint, binEndpoint := args[0], args[1]
+
+	profileNames, err := getProfileNames(ctx, env, binEndpoint)
+	if err != nil {
+		return err
+	}
+
+	envelopeNames, err := getNames(ctx, env, naming.Join(appEndpoint, "..."))
+	if err != nil {
+		return err
+	}
+
+	// Add every path in use to a set. Deletion scope is limited to
+	// only the binEndpoint.
+	bpaths := make(map[string]struct{})
+	for _, en := range envelopeNames {
+		// convert an envelope name into an envelope.
+		ac := repository.ApplicationClient(en)
+
+		for _, p := range profileNames {
+			e, err := ac.Match(ctx, []string{p})
+			if err != nil {
+				// This error case is very noisy.
+				vlog.VI(2).Infof("applicationd.Match(%s, %s) failed: %v\n", en, p, err)
+				continue
+			}
+
+			root, relative := naming.SplitAddressName(e.Binary.File)
+			if root == binEndpoint || root == "" {
+				bpaths[relative] = struct{}{}
+			}
+			for _, sf := range e.Packages {
+				root, relative := naming.SplitAddressName(sf.File)
+				if root == binEndpoint || root == "" {
+					bpaths[relative] = struct{}{}
+				}
+			}
+		}
+	}
+
+	binaryNames, err := getNames(ctx, env, naming.Join(binEndpoint, "..."))
+	if err != nil {
+		return err
+	}
+
+	deletionCandidates := make([]int, 0, len(binaryNames)-len(envelopeNames))
+	for i, bn := range binaryNames {
+		_, relative := naming.SplitAddressName(bn)
+		if _, ok := bpaths[relative]; ok {
+			// relative is mentioned in an envelope.
+			continue
+		}
+
+		if _, err := binarylib.Stat(ctx, bn); err != nil {
+			// This name is not a binary.
+			continue
+		}
+		deletionCandidates = append(deletionCandidates, i)
+	}
+
+	for _, i := range deletionCandidates {
+		b := binaryNames[i]
+		if err := binarylib.Delete(ctx, b); err != nil {
+			vlog.Errorf("Couldn't delete binary %s: %v", b, err)
+		}
+	}
+
+	return nil
+}
+
+var cmdApplicationTidy = &cmdline.Command{
+	Runner: v23cmd.RunnerFunc(runApplicationTidy),
+	Name:   "application",
+	Short:  "Application sub-command tidies a specified applicationd",
+	Long: `
+Application sub-command uses the Tidy RPC to clean up outdated
+envelopes from the specified appilcationd. Call this before tidying a
+binaryd instance for maximum tidiness.
+`,
+	ArgsName: "<applicationd>",
+	ArgsLong: `
+<applicationd> is the name or endpoint of the applicationd instance
+to tidy.
+`,
+}
+
+func runApplicationTidy(ctx *context.T, env *cmdline.Env, args []string) error {
+	vlog.Infof("runApplicationTidy")
+	if expected, got := 1, len(args); expected != got {
+		return env.UsageErrorf("match: incorrect number of arguments, expected %d, got %d", expected, got)
+	}
+	ac := repository.ApplicationClient(args[0])
+	return ac.TidyNow(ctx)
+}
+
+var cmdRoot = &cmdline.Command{
+	Name:  "tidy",
+	Short: "Tidy binary repositories",
+	Long: `
+Tidy tidies the Vanadium repository by removing unused
+envelopes and binaries.
+`,
+	Children: []*cmdline.Command{cmdBinaryTidy, cmdApplicationTidy},
+}
+
+func main() {
+	cmdline.HideGlobalFlagsExcept()
+	cmdline.Main(cmdRoot)
+}
diff --git a/services/binary/tidy/impl_test.go b/services/binary/tidy/impl_test.go
new file mode 100644
index 0000000..8b05eab
--- /dev/null
+++ b/services/binary/tidy/impl_test.go
@@ -0,0 +1,356 @@
+// 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 (
+	"bytes"
+	"fmt"
+	"reflect"
+	"strings"
+	"testing"
+
+	"v.io/v23/services/application"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/v23cmd"
+	"v.io/x/ref/lib/xrpc"
+	_ "v.io/x/ref/runtime/factories/generic"
+	"v.io/x/ref/services/binary/tidy/appd"
+	"v.io/x/ref/services/binary/tidy/binaryd"
+	"v.io/x/ref/services/internal/servicetest"
+	"v.io/x/ref/test"
+)
+
+//go:generate v23 test generate
+
+func TestApplicationTidying(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	apptape := servicetest.NewTape()
+	appserver, err := xrpc.NewDispatchingServer(ctx, "", appd.NewDispatcher(t, apptape))
+	if err != nil {
+		t.Fatalf("applicationd NewDispatchingServer failed: %v", err)
+	}
+
+	// Setup the command-line.
+	var stdout, stderr bytes.Buffer
+	env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
+	applicationName := appserver.Status().Endpoints[0].Name()
+
+	apptape.SetResponses(
+		// TidyNow()
+		nil,
+	)
+
+	if err := v23cmd.ParseAndRunForTest(cmdApplicationTidy, ctx, env, []string{applicationName}); err != nil {
+		t.Errorf("error: %v", err)
+	}
+
+	// Verify no output.
+	if expected, got := "", strings.TrimSpace(stdout.String()); got != expected {
+		t.Errorf("Unexpected output from tidy application. Got %q, expected %q", got, expected)
+	}
+	if expected, got := "", strings.TrimSpace(stderr.String()); got != expected {
+		t.Errorf("Unexpected error from tidy application. Got %q, expected %q", got, expected)
+	}
+
+	// Verify application tape.
+	if got, expected := apptape.Play(), []interface{}{
+		"TidyNow",
+	}; !reflect.DeepEqual(expected, got) {
+		t.Errorf("apptape invalid call sequence. Got %#v, want %#v", got, expected)
+	}
+}
+
+func TestBinaryTidying(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	binarytape := servicetest.NewTape()
+	binserver, err := xrpc.NewDispatchingServer(ctx, "", binaryd.NewDispatcher(t, binarytape))
+	if err != nil {
+		t.Fatalf("binaryd NewDispatchingServer failed: %v", err)
+	}
+
+	apptape := servicetest.NewTape()
+	appserver, err := xrpc.NewDispatchingServer(ctx, "", appd.NewDispatcher(t, apptape))
+	if err != nil {
+		t.Fatalf("applicationd NewDispatchingServer failed: %v", err)
+	}
+
+	// Setup the command-line.
+	var stdout, stderr bytes.Buffer
+	env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
+	binaryName := binserver.Status().Endpoints[0].Name()
+	applicationName := appserver.Status().Endpoints[0].Name()
+
+	binarytape.SetResponses(
+		// Glob for all binary names
+		binaryd.GlobResponse{[]string{
+			"binaries",
+			"binaries/applicationd",
+			"binaries/applicationd/darwin-amd64",
+			"binaries/applicationd/darwin-amd64/app-darwin-amd-1",
+			"binaries/applicationd/darwin-amd64/app-darwin-amd-2",
+			"binaries/applicationd/linux-amd64",
+			"binaries/applicationd/linux-amd64/app-linux-amd-1",
+			"binaries/applicationd/linux-amd64/app-linux-amd-2",
+			"binaries/binaryd",
+			"binaries/binaryd/linux-amd64",
+			"binaries/binaryd/linux-amd64/bind-linux-amd-1",
+			"binaries/binaryd/linux-amd64/bind-linux-amd-2",
+			"binaries/binaryd/linux-amd64/bind-linux-amd-3",
+			"binaries/libraries",
+			"binaries/libraries/linux-amd64",
+			"binaries/libraries/linux-amd64/extra-goo-1",
+		},
+			nil,
+		},
+
+		// Stat calls
+		fmt.Errorf("binaries"),
+		fmt.Errorf("binaries/applicationd"),
+		fmt.Errorf("binaries/applicationd/darwin-amd64"),
+		nil, // binaries/applicationd/darwin-amd64/app-darwin-amd-1
+		fmt.Errorf("binaries/applicationd/linux-amd64"),
+		nil, // binaries/applicationd/linux-amd64/app-linux-amd-1
+		fmt.Errorf("binaries/binaryd"),
+		fmt.Errorf("binaries/binaryd/linux-amd64"),
+		nil, // binaries/binaryd/linux-amd64/bind-linux-amd-1
+		nil, // binaries/binaryd/linux-amd64/bind-linux-amd-2
+		fmt.Errorf("binaries/libraries"),
+		fmt.Errorf("binaries/libraries/linux-amd64"),
+
+		// Deletion of five binaries.
+		nil,
+		nil,
+		nil,
+		nil,
+		nil,
+	)
+
+	apptape.SetResponses(
+		// Glob for all versions of all apps
+		binaryd.GlobResponse{[]string{
+			"applications",
+			"applications/applicationd",
+			"applications/applicationd/0",
+			"applications/binaryd",
+			"applications/binaryd/1",
+		},
+			nil,
+		},
+
+		// applications.Match(linux-amd64)
+		appd.MatchResult{
+			application.Envelope{},
+			fmt.Errorf("no applications.Match(linux-amd64)"),
+		},
+		// applications.Match(linux-386)
+		appd.MatchResult{
+			application.Envelope{},
+			fmt.Errorf("no applications.Match(linux-386)"),
+		},
+		// applications.Match(linux-arm)
+		appd.MatchResult{
+			application.Envelope{},
+			fmt.Errorf("no applications.Match(linux-arm)"),
+		},
+		// applications.Match(darwin-amd64)
+		appd.MatchResult{
+			application.Envelope{},
+			fmt.Errorf("no applications.Match(darwin-amd64)"),
+		},
+		// applications/applicationd.Match(linux-amd64)
+		appd.MatchResult{
+			application.Envelope{
+				Binary: application.SignedFile{
+					File: "binaries/applicationd/linux-amd64/app-linux-amd-2",
+				},
+			},
+			nil,
+		},
+		// applications/applicationd.Match(linux-386)
+		appd.MatchResult{
+			application.Envelope{},
+			fmt.Errorf("no applications/applicationd.Match(linux-386)"),
+		},
+		// applications/applicationd.Match(linux-arm)
+		appd.MatchResult{
+			application.Envelope{},
+			fmt.Errorf("no applications/applicationd.Match(linux-arm)"),
+		},
+		// applications/applicationd.Match(darwin-amd64)
+		appd.MatchResult{
+			application.Envelope{
+				Binary: application.SignedFile{
+					File: "binaries/applicationd/darwin-amd64/app-darwin-amd-2",
+				},
+			},
+			nil,
+		},
+		// applications/applicationd/0.Match(linux-amd64)
+		appd.MatchResult{
+			application.Envelope{
+				Binary: application.SignedFile{
+					File: "binaries/applicationd/linux-amd64/app-linux-amd-2",
+				},
+			},
+			nil,
+		},
+		// applications/applicationd/0.Match(linux-386)
+		appd.MatchResult{
+			application.Envelope{},
+			fmt.Errorf("no applications/applicationd/0.Match(linux-386)"),
+		},
+		// applications/applicationd/0.Match(linux-arm)
+		appd.MatchResult{
+			application.Envelope{},
+			fmt.Errorf("no applications/applicationd/0.Match(linux-arm)"),
+		},
+		// applications/applicationd/0.Match(darwin-amd64)
+		appd.MatchResult{
+			application.Envelope{
+				Binary: application.SignedFile{
+					File: "binaries/applicationd/darwin-amd64/app-darwin-amd-2",
+				},
+			},
+			nil,
+		},
+		// applications/binaryd.Match(linux-amd64)
+		appd.MatchResult{
+			application.Envelope{
+				Binary: application.SignedFile{
+					File: "binaries/binaryd/linux-amd64/bind-linux-amd-3",
+				},
+				Packages: application.Packages{
+					"somewhere": {
+						File: "binaries/libraries/linux-amd64/extra-goo-1",
+					},
+				},
+			},
+			nil,
+		},
+		// applications/binaryd.Match(linux-386)
+		appd.MatchResult{
+			application.Envelope{},
+			fmt.Errorf("no applications/binaryd.Match(linux-386)"),
+		},
+		// applications/binaryd.Match(linux-arm)
+		appd.MatchResult{
+			application.Envelope{},
+			fmt.Errorf("no applications/binaryd.Match(linux-arm)"),
+		},
+		// applications/binaryd.Match(darwin-amd64)
+		appd.MatchResult{
+			application.Envelope{
+				Binary: application.SignedFile{
+					// Deliberately doesn't exist to show that this case is correctly handled.
+					File: "binaries/binaryd/darwin-amd64/bind-darwin-amd-2",
+				},
+			},
+			nil,
+		},
+		// applications/binaryd/1.Match(linux-amd64)
+		appd.MatchResult{
+			application.Envelope{
+				Binary: application.SignedFile{
+					File: "binaries/binaryd/linux-amd64/bind-linux-amd-3",
+				},
+				Packages: application.Packages{
+					"somewhere": {
+						File: "binaries/libraries/linux-amd64/extra-goo-1",
+					},
+				},
+			},
+			nil,
+		},
+		// applications/binaryd/1.Match(linux-386)
+		appd.MatchResult{
+			application.Envelope{},
+			fmt.Errorf("no applications/binaryd/1.Match(linux-386)"),
+		},
+		// applications/binaryd/1.Match(linux-arm)
+		appd.MatchResult{
+			application.Envelope{},
+			fmt.Errorf("no applications/binaryd/1.Match(linux-arm)"),
+		},
+		// applications/binaryd/1.Match(darwin-amd64)
+		appd.MatchResult{
+			application.Envelope{
+				Binary: application.SignedFile{
+					// Deliberately doesn't exist to show that this case is correctly handled.
+					File: "binaries/binaryd/darwin-amd64/bind-darwin-amd-2",
+				},
+			},
+			nil,
+		},
+	)
+
+	if err := v23cmd.ParseAndRunForTest(cmdBinaryTidy, ctx, env, []string{applicationName, binaryName}); err != nil {
+		t.Errorf("error: %v", err)
+	}
+
+	// Verify no output.
+	if expected, got := "", strings.TrimSpace(stdout.String()); got != expected {
+		t.Errorf("Unexpected output from tidy binary. Got %q, expected %q", got, expected)
+	}
+	if expected, got := "", strings.TrimSpace(stderr.String()); got != expected {
+		t.Errorf("Unexpected error from tidy binary. Got %q, expected %q", got, expected)
+	}
+
+	// Verify binaryd tape.
+	if got, expected := binarytape.Play(), []interface{}{
+		binaryd.GlobStimulus{Pattern: "..."},
+
+		binaryd.StatStimulus{Op: "Stat", Suffix: "binaries"},
+		binaryd.StatStimulus{Op: "Stat", Suffix: "binaries/applicationd"},
+		binaryd.StatStimulus{Op: "Stat", Suffix: "binaries/applicationd/darwin-amd64"},
+		binaryd.StatStimulus{Op: "Stat", Suffix: "binaries/applicationd/darwin-amd64/app-darwin-amd-1"},
+		binaryd.StatStimulus{Op: "Stat", Suffix: "binaries/applicationd/linux-amd64"},
+		binaryd.StatStimulus{Op: "Stat", Suffix: "binaries/applicationd/linux-amd64/app-linux-amd-1"},
+		binaryd.StatStimulus{Op: "Stat", Suffix: "binaries/binaryd"},
+		binaryd.StatStimulus{Op: "Stat", Suffix: "binaries/binaryd/linux-amd64"},
+		binaryd.StatStimulus{Op: "Stat", Suffix: "binaries/binaryd/linux-amd64/bind-linux-amd-1"},
+		binaryd.StatStimulus{Op: "Stat", Suffix: "binaries/binaryd/linux-amd64/bind-linux-amd-2"},
+		binaryd.StatStimulus{Op: "Stat", Suffix: "binaries/libraries"},
+		binaryd.StatStimulus{Op: "Stat", Suffix: "binaries/libraries/linux-amd64"},
+
+		binaryd.DeleteStimulus{Op: "Delete", Suffix: "binaries/applicationd/darwin-amd64/app-darwin-amd-1"},
+		binaryd.DeleteStimulus{Op: "Delete", Suffix: "binaries/applicationd/linux-amd64/app-linux-amd-1"},
+		binaryd.DeleteStimulus{Op: "Delete", Suffix: "binaries/binaryd/linux-amd64/bind-linux-amd-1"},
+		binaryd.DeleteStimulus{Op: "Delete", Suffix: "binaries/binaryd/linux-amd64/bind-linux-amd-2"},
+	}; !reflect.DeepEqual(expected, got) {
+		t.Errorf("binarytape invalid call sequence. Got %#v, want %#v", got, expected)
+	}
+
+	// Verify application tape.
+	if got, expected := apptape.Play(), []interface{}{
+		binaryd.GlobStimulus{"..."},
+		appd.MatchStimulus{Name: "Match", Suffix: "applications", Profiles: []string{"linux-amd64"}},
+		appd.MatchStimulus{Name: "Match", Suffix: "applications", Profiles: []string{"linux-386"}},
+		appd.MatchStimulus{Name: "Match", Suffix: "applications", Profiles: []string{"linux-arm"}},
+		appd.MatchStimulus{Name: "Match", Suffix: "applications", Profiles: []string{"darwin-amd64"}},
+		appd.MatchStimulus{Name: "Match", Suffix: "applications/applicationd", Profiles: []string{"linux-amd64"}},
+		appd.MatchStimulus{Name: "Match", Suffix: "applications/applicationd", Profiles: []string{"linux-386"}},
+		appd.MatchStimulus{Name: "Match", Suffix: "applications/applicationd", Profiles: []string{"linux-arm"}},
+		appd.MatchStimulus{Name: "Match", Suffix: "applications/applicationd", Profiles: []string{"darwin-amd64"}},
+		appd.MatchStimulus{Name: "Match", Suffix: "applications/applicationd/0", Profiles: []string{"linux-amd64"}},
+		appd.MatchStimulus{Name: "Match", Suffix: "applications/applicationd/0", Profiles: []string{"linux-386"}},
+		appd.MatchStimulus{Name: "Match", Suffix: "applications/applicationd/0", Profiles: []string{"linux-arm"}},
+		appd.MatchStimulus{Name: "Match", Suffix: "applications/applicationd/0", Profiles: []string{"darwin-amd64"}},
+		appd.MatchStimulus{Name: "Match", Suffix: "applications/binaryd", Profiles: []string{"linux-amd64"}},
+		appd.MatchStimulus{Name: "Match", Suffix: "applications/binaryd", Profiles: []string{"linux-386"}},
+		appd.MatchStimulus{Name: "Match", Suffix: "applications/binaryd", Profiles: []string{"linux-arm"}},
+		appd.MatchStimulus{Name: "Match", Suffix: "applications/binaryd", Profiles: []string{"darwin-amd64"}},
+		appd.MatchStimulus{Name: "Match", Suffix: "applications/binaryd/1", Profiles: []string{"linux-amd64"}},
+		appd.MatchStimulus{Name: "Match", Suffix: "applications/binaryd/1", Profiles: []string{"linux-386"}},
+		appd.MatchStimulus{Name: "Match", Suffix: "applications/binaryd/1", Profiles: []string{"linux-arm"}},
+		appd.MatchStimulus{Name: "Match", Suffix: "applications/binaryd/1", Profiles: []string{"darwin-amd64"}},
+	}; !reflect.DeepEqual(expected, got) {
+		t.Errorf("apptape invalid call sequence. Got %#v, want %#v", got, expected)
+	}
+
+}
diff --git a/services/binary/tidy/v23_internal_test.go b/services/binary/tidy/v23_internal_test.go
new file mode 100644
index 0000000..a80e0ec
--- /dev/null
+++ b/services/binary/tidy/v23_internal_test.go
@@ -0,0 +1,22 @@
+// 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 main
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	modules.DispatchAndExitIfChild()
+	os.Exit(m.Run())
+}
diff --git a/services/build/build/doc.go b/services/build/build/doc.go
new file mode 100644
index 0000000..2699b49
--- /dev/null
+++ b/services/build/build/doc.go
@@ -0,0 +1,109 @@
+// 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
+
+/*
+Command build sends commands to a Vanadium build server.
+
+Usage:
+   build <command>
+
+The build commands are:
+   build       Build vanadium Go packages
+   help        Display help for commands or topics
+
+The global flags are:
+ -alsologtostderr=true
+   log to standard error as well as files
+ -log_backtrace_at=:0
+   when logging hits line file:N, emit a stack trace
+ -log_dir=
+   if non-empty, write log files to this directory
+ -logtostderr=false
+   log to standard error instead of files
+ -max_stack_buf_size=4292608
+   max size in bytes of the buffer to use for logging stack traces
+ -metadata=<just specify -metadata to activate>
+   Displays metadata for the program and exits.
+ -stderrthreshold=2
+   logs at or above this threshold go to stderr
+ -v=0
+   log level for V logs
+ -v23.credentials=
+   directory to use for storing security credentials
+ -v23.i18n-catalogue=
+   18n catalogue files to load, comma separated
+ -v23.namespace.root=[/(dev.v.io/role/vprod/service/mounttabled)@ns.dev.v.io:8101]
+   local namespace root; can be repeated to provided multiple roots
+ -v23.proxy=
+   object name of proxy service to use to export services across network
+   boundaries
+ -v23.tcp.address=
+   address to listen on
+ -v23.tcp.protocol=wsh
+   protocol to listen with
+ -v23.vtrace.cache-size=1024
+   The number of vtrace traces to store in memory.
+ -v23.vtrace.collect-regexp=
+   Spans and annotations that match this regular expression will trigger trace
+   collection.
+ -v23.vtrace.dump-on-shutdown=true
+   If true, dump all stored traces on runtime shutdown.
+ -v23.vtrace.sample-rate=0
+   Rate (from 0.0 to 1.0) to sample vtrace traces.
+ -vmodule=
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
+
+Build build - Build vanadium Go packages
+
+Build vanadium Go packages using a remote build server. The command collects all
+source code files that are not part of the Go standard library that the target
+packages depend on, sends them to a build server, and receives the built
+binaries.
+
+Usage:
+   build build [flags] <name> <packages>
+
+<name> is a vanadium object name of a build server <packages> is a list of
+packages to build, specified as arguments for each command. The format is
+similar to the go tool.  In its simplest form each package is an import path;
+e.g. "v.io/x/ref/services/build/build". A package that ends with "..." does a
+wildcard match against all packages with that prefix.
+
+The build build flags are:
+ -arch=<runtime.GOARCH>
+   Target architecture.  The default is the value of runtime.GOARCH.
+ -os=<runtime.GOOS>
+   Target operating system.  The default is the value of runtime.GOOS.
+
+Build help - Display help for commands or topics
+
+Help with no args displays the usage of the parent command.
+
+Help with args displays the usage of the specified sub-command or help topic.
+
+"help ..." recursively displays help for all commands and topics.
+
+Usage:
+   build help [flags] [command/topic ...]
+
+[command/topic ...] optionally identifies a specific sub-command or help topic.
+
+The build help flags are:
+ -style=compact
+   The formatting style for help output:
+      compact - Good for compact cmdline output.
+      full    - Good for cmdline output, shows all global flags.
+      godoc   - Good for godoc processing.
+   Override the default by setting the CMDLINE_STYLE environment variable.
+ -width=<terminal width>
+   Format output to this target width in runes, or unlimited if width < 0.
+   Defaults to the terminal width if available.  Override the default by setting
+   the CMDLINE_WIDTH environment variable.
+*/
+package main
diff --git a/services/build/build/impl.go b/services/build/build/impl.go
new file mode 100644
index 0000000..abe2639
--- /dev/null
+++ b/services/build/build/impl.go
@@ -0,0 +1,257 @@
+// 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.
+
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go .
+
+package main
+
+import (
+	"fmt"
+	"go/build"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"runtime"
+	"strings"
+	"time"
+
+	"v.io/v23/context"
+	vbuild "v.io/v23/services/build"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/v23cmd"
+	_ "v.io/x/ref/runtime/factories/generic"
+)
+
+func main() {
+	cmdline.Main(cmdRoot)
+}
+
+var (
+	flagArch string
+	flagOS   string
+)
+
+func init() {
+	cmdline.HideGlobalFlagsExcept()
+	cmdBuild.Flags.StringVar(&flagArch, "arch", runtime.GOARCH, "Target architecture.  The default is the value of runtime.GOARCH.")
+	cmdBuild.Flags.Lookup("arch").DefValue = "<runtime.GOARCH>"
+	cmdBuild.Flags.StringVar(&flagOS, "os", runtime.GOOS, "Target operating system.  The default is the value of runtime.GOOS.")
+	cmdBuild.Flags.Lookup("os").DefValue = "<runtime.GOOS>"
+}
+
+var cmdRoot = &cmdline.Command{
+	Name:  "build",
+	Short: "sends commands to a Vanadium build server",
+	Long: `
+Command build sends commands to a Vanadium build server.
+`,
+	Children: []*cmdline.Command{cmdBuild},
+}
+
+var cmdBuild = &cmdline.Command{
+	Runner: v23cmd.RunnerFunc(runBuild),
+	Name:   "build",
+	Short:  "Build vanadium Go packages",
+	Long: `
+Build vanadium Go packages using a remote build server. The command collects all
+source code files that are not part of the Go standard library that the target
+packages depend on, sends them to a build server, and receives the built
+binaries.
+`,
+	ArgsName: "<name> <packages>",
+	ArgsLong: `
+<name> is a vanadium object name of a build server <packages> is a list of
+packages to build, specified as arguments for each command. The format is
+similar to the go tool.  In its simplest form each package is an import path;
+e.g. "v.io/x/ref/services/build/build". A package that ends with "..." does a
+wildcard match against all packages with that prefix.
+`,
+}
+
+// TODO(jsimsa): Add support for importing (and remotely building)
+// packages from multiple package source root GOPATH directories with
+// identical names.
+func importPackages(paths []string, pkgMap map[string]*build.Package) error {
+	for _, path := range paths {
+		recurse := false
+		if strings.HasSuffix(path, "...") {
+			recurse = true
+			path = strings.TrimSuffix(path, "...")
+		}
+		if _, exists := pkgMap[path]; !exists {
+			srcDir, mode := "", build.ImportMode(0)
+			pkg, err := build.Import(path, srcDir, mode)
+			if err != nil {
+				// "C" is a pseudo-package for cgo: http://golang.org/cmd/cgo/
+				// Do not attempt recursive imports.
+				if pkg.ImportPath == "C" {
+					continue
+				}
+				return fmt.Errorf("Import(%q,%q,%v) failed: %v", path, srcDir, mode, err)
+			}
+			if pkg.Goroot {
+				continue
+			}
+			pkgMap[path] = pkg
+			if err := importPackages(pkg.Imports, pkgMap); err != nil {
+				return err
+			}
+		}
+		if recurse {
+			pkg := pkgMap[path]
+			fis, err := ioutil.ReadDir(pkg.Dir)
+			if err != nil {
+				return fmt.Errorf("ReadDir(%v) failed: %v", pkg.Dir, err)
+			}
+			for _, fi := range fis {
+				if fi.IsDir() {
+					subPath := filepath.Join(path, fi.Name(), "...")
+					if err := importPackages([]string{subPath}, pkgMap); err != nil {
+						return err
+					}
+				}
+			}
+		}
+	}
+	return nil
+}
+
+func getSources(ctx *context.T, pkgMap map[string]*build.Package, errchan chan<- error) <-chan vbuild.File {
+	sources := make(chan vbuild.File)
+	go func() {
+		defer close(sources)
+		for _, pkg := range pkgMap {
+			for _, files := range [][]string{pkg.CFiles, pkg.CgoFiles, pkg.GoFiles, pkg.SFiles} {
+				for _, file := range files {
+					path := filepath.Join(pkg.Dir, file)
+					bytes, err := ioutil.ReadFile(path)
+					if err != nil {
+						errchan <- fmt.Errorf("ReadFile(%v) failed: %v", path, err)
+						return
+					}
+					select {
+					case sources <- vbuild.File{Contents: bytes, Name: filepath.Join(pkg.ImportPath, file)}:
+					case <-ctx.Done():
+						errchan <- fmt.Errorf("Get sources failed: %v", ctx.Err())
+						return
+					}
+				}
+			}
+		}
+		errchan <- nil
+	}()
+	return sources
+}
+
+func invokeBuild(ctx *context.T, name string, sources <-chan vbuild.File, errchan chan<- error) <-chan vbuild.File {
+	binaries := make(chan vbuild.File)
+	go func() {
+		defer close(binaries)
+		ctx, cancel := context.WithCancel(ctx)
+		defer cancel()
+
+		client := vbuild.BuilderClient(name)
+		var arch vbuild.Architecture
+		if err := arch.SetFromGoArch(flagArch); err != nil {
+			errchan <- err
+			return
+		}
+		var os vbuild.OperatingSystem
+		if err := os.SetFromGoOS(flagOS); err != nil {
+			errchan <- err
+			return
+		}
+		stream, err := client.Build(ctx, arch, os)
+		if err != nil {
+			errchan <- fmt.Errorf("Build() failed: %v", err)
+			return
+		}
+		sender := stream.SendStream()
+		for source := range sources {
+			if err := sender.Send(source); err != nil {
+				errchan <- fmt.Errorf("Send() failed: %v", err)
+				return
+			}
+		}
+		if err := sender.Close(); err != nil {
+			errchan <- fmt.Errorf("Close() failed: %v", err)
+			return
+		}
+		iterator := stream.RecvStream()
+		for iterator.Advance() {
+			select {
+			case binaries <- iterator.Value():
+			case <-ctx.Done():
+				errchan <- fmt.Errorf("Invoke build failed: %v", ctx.Err())
+				return
+			}
+		}
+		if err := iterator.Err(); err != nil {
+			errchan <- fmt.Errorf("Advance() failed: %v", err)
+			return
+		}
+		if out, err := stream.Finish(); err != nil {
+			errchan <- fmt.Errorf("Finish() failed: (%v, %v)", string(out), err)
+			return
+		}
+		errchan <- nil
+	}()
+	return binaries
+}
+
+func saveBinaries(ctx *context.T, prefix string, binaries <-chan vbuild.File, errchan chan<- error) {
+	go func() {
+		for binary := range binaries {
+			select {
+			case <-ctx.Done():
+				errchan <- fmt.Errorf("Save binaries failed: %v", ctx.Err())
+				return
+			default:
+			}
+			path, perm := filepath.Join(prefix, filepath.Base(binary.Name)), os.FileMode(0755)
+			if err := ioutil.WriteFile(path, binary.Contents, perm); err != nil {
+				errchan <- fmt.Errorf("WriteFile(%v, %v) failed: %v", path, perm, err)
+				return
+			}
+			fmt.Printf("Generated binary %v\n", path)
+		}
+		errchan <- nil
+	}()
+}
+
+// runBuild identifies the source files needed to build the packages
+// specified on command-line and then creates a pipeline that
+// concurrently 1) reads the source files, 2) sends them to the build
+// server and receives binaries from the build server, and 3) writes
+// the binaries out to the disk.
+func runBuild(ctx *context.T, env *cmdline.Env, args []string) error {
+	name, paths := args[0], args[1:]
+	pkgMap := map[string]*build.Package{}
+	if err := importPackages(paths, pkgMap); err != nil {
+		return err
+	}
+	errchan := make(chan error)
+	defer close(errchan)
+
+	ctx, ctxCancel := context.WithTimeout(ctx, time.Minute)
+	defer ctxCancel()
+
+	// Start all stages of the pipeline.
+	sources := getSources(ctx, pkgMap, errchan)
+	binaries := invokeBuild(ctx, name, sources, errchan)
+	saveBinaries(ctx, os.TempDir(), binaries, errchan)
+	// Wait for all stages of the pipeline to terminate.
+	errors, numStages := []error{}, 3
+	for i := 0; i < numStages; i++ {
+		if err := <-errchan; err != nil {
+			errors = append(errors, err)
+			ctxCancel()
+		}
+	}
+	if len(errors) != 0 {
+		return fmt.Errorf("build failed(%v)", errors)
+	}
+	return nil
+}
diff --git a/services/build/build/impl_test.go b/services/build/build/impl_test.go
new file mode 100644
index 0000000..2c7ab70
--- /dev/null
+++ b/services/build/build/impl_test.go
@@ -0,0 +1,72 @@
+// 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 (
+	"bytes"
+	"strings"
+	"testing"
+
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+	"v.io/v23/services/binary"
+	"v.io/v23/services/build"
+	"v.io/v23/verror"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/v23cmd"
+	"v.io/x/ref/lib/xrpc"
+	_ "v.io/x/ref/runtime/factories/generic"
+	"v.io/x/ref/test"
+)
+
+//go:generate v23 test generate
+
+type mock struct{}
+
+func (mock) Build(ctx *context.T, call build.BuilderBuildServerCall, arch build.Architecture, opsys build.OperatingSystem) ([]byte, error) {
+	ctx.VI(2).Infof("Build(%v, %v) was called", arch, opsys)
+	iterator := call.RecvStream()
+	for iterator.Advance() {
+	}
+	if err := iterator.Err(); err != nil {
+		ctx.Errorf("Advance() failed: %v", err)
+		return nil, verror.New(verror.ErrInternal, ctx)
+	}
+	return nil, nil
+}
+
+func (mock) Describe(ctx *context.T, _ rpc.ServerCall, name string) (binary.Description, error) {
+	ctx.VI(2).Infof("Describe(%v) was called", name)
+	return binary.Description{}, nil
+}
+
+type dispatcher struct{}
+
+func startServer(ctx *context.T, t *testing.T) naming.Endpoint {
+	unpublished := ""
+	server, err := xrpc.NewServer(ctx, unpublished, build.BuilderServer(&mock{}), nil)
+	if err != nil {
+		t.Fatalf("NewServer failed: %v", err)
+	}
+	return server.Status().Endpoints[0]
+}
+
+func TestBuildClient(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	endpoint := startServer(ctx, t)
+
+	var stdout, stderr bytes.Buffer
+	env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
+	args := []string{"build", naming.JoinAddressName(endpoint.String(), ""), "v.io/x/ref/services/build/build"}
+	if err := v23cmd.ParseAndRunForTest(cmdRoot, ctx, env, args); err != nil {
+		t.Fatalf("Run failed: %v", err)
+	}
+	if got, want := strings.TrimSpace(stdout.String()), ""; got != want {
+		t.Errorf("got %q, want %q", got, want)
+	}
+}
diff --git a/services/build/build/v23_internal_test.go b/services/build/build/v23_internal_test.go
new file mode 100644
index 0000000..ae59080
--- /dev/null
+++ b/services/build/build/v23_internal_test.go
@@ -0,0 +1,20 @@
+// 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 main
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	os.Exit(m.Run())
+}
diff --git a/services/build/buildd/buildd_v23_test.go b/services/build/buildd/buildd_v23_test.go
new file mode 100644
index 0000000..e42767e
--- /dev/null
+++ b/services/build/buildd/buildd_v23_test.go
@@ -0,0 +1,94 @@
+// 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_test
+
+import (
+	"bytes"
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"runtime"
+	"strings"
+
+	"v.io/x/ref/test/v23tests"
+)
+
+//go:generate v23 test generate
+
+var testProgram = `package main
+
+import "fmt"
+
+func main() { fmt.Println("Hello World!") }
+`
+
+func V23TestBuildServerIntegration(i *v23tests.T) {
+	goBin, err := exec.LookPath("go")
+	if err != nil {
+		i.Fatalf("%v", err)
+	}
+	goRoot := runtime.GOROOT()
+
+	v23tests.RunRootMT(i, "--v23.tcp.address=127.0.0.1:0")
+
+	// Build binaries for the client and server.
+	// Since Permissions are not setup on the server, the client must pass the
+	// default authorization policy, i.e., must be a "delegate" of the server.
+	var (
+		buildServerBin = binaryWithCredentials(i, "buildd", "v.io/x/ref/services/build/buildd")
+		buildBin       = binaryWithCredentials(i, "buildd/client", "v.io/x/ref/services/build/build")
+	)
+
+	// Start the build server.
+	buildServerName := "test-build-server"
+	buildServerBin.Start(
+		"-name="+buildServerName,
+		"-gobin="+goBin,
+		"-goroot="+goRoot,
+		"-v23.tcp.address=127.0.0.1:0")
+
+	// Create and build a test source file.
+	testGoPath := i.NewTempDir("")
+	testBinDir := filepath.Join(testGoPath, "bin")
+	if err := os.MkdirAll(testBinDir, os.FileMode(0700)); err != nil {
+		i.Fatalf("MkdirAll(%v) failed: %v", testBinDir, err)
+	}
+	testBinFile := filepath.Join(testBinDir, "test")
+	testSrcDir := filepath.Join(testGoPath, "src", "test")
+	if err := os.MkdirAll(testSrcDir, os.FileMode(0700)); err != nil {
+		i.Fatalf("MkdirAll(%v) failed: %v", testSrcDir, err)
+	}
+	testSrcFile := filepath.Join(testSrcDir, "test.go")
+	if err := ioutil.WriteFile(testSrcFile, []byte(testProgram), os.FileMode(0600)); err != nil {
+		i.Fatalf("WriteFile(%v) failed: %v", testSrcFile, err)
+	}
+	buildBin.WithEnv(
+		"GOPATH="+testGoPath,
+		"GOROOT="+goRoot,
+		"TMPDIR="+testBinDir).Start(
+		"build",
+		buildServerName,
+		"test").WaitOrDie(os.Stdout, os.Stderr)
+	var testOut bytes.Buffer
+	testCmd := exec.Command(testBinFile)
+	testCmd.Stdout = &testOut
+	testCmd.Stderr = &testOut
+	if err := testCmd.Run(); err != nil {
+		i.Fatalf("%q failed: %v\n%v", strings.Join(testCmd.Args, " "), err, testOut.String())
+	}
+	if got, want := strings.TrimSpace(testOut.String()), "Hello World!"; got != want {
+		i.Fatalf("unexpected output: got %v, want %v", got, want)
+	}
+}
+
+func binaryWithCredentials(i *v23tests.T, extension, pkgpath string) *v23tests.Binary {
+	creds, err := i.Shell().NewChildCredentials(extension)
+	if err != nil {
+		i.Fatalf("NewCustomCredentials (for %q) failed: %v", pkgpath, err)
+	}
+	b := i.BuildV23Pkg(pkgpath)
+	return b.WithStartOpts(b.StartOpts().WithCustomCredentials(creds))
+}
diff --git a/services/build/buildd/doc.go b/services/build/buildd/doc.go
new file mode 100644
index 0000000..2a5df3a
--- /dev/null
+++ b/services/build/buildd/doc.go
@@ -0,0 +1,73 @@
+// 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
+
+/*
+Command buildd runs the builder daemon, which implements the
+v.io/v23/services/build.Builder interface.
+
+Usage:
+   buildd [flags]
+
+The buildd flags are:
+ -gobin=go
+   Path to the Go compiler.
+ -goroot=<GOROOT>
+   GOROOT to use with the Go compiler.  The default is the value of the GOROOT
+   environment variable.
+ -name=
+   Name to mount the build server as.
+
+The global flags are:
+ -alsologtostderr=true
+   log to standard error as well as files
+ -log_backtrace_at=:0
+   when logging hits line file:N, emit a stack trace
+ -log_dir=
+   if non-empty, write log files to this directory
+ -logtostderr=false
+   log to standard error instead of files
+ -max_stack_buf_size=4292608
+   max size in bytes of the buffer to use for logging stack traces
+ -metadata=<just specify -metadata to activate>
+   Displays metadata for the program and exits.
+ -stderrthreshold=2
+   logs at or above this threshold go to stderr
+ -v=0
+   log level for V logs
+ -v23.credentials=
+   directory to use for storing security credentials
+ -v23.i18n-catalogue=
+   18n catalogue files to load, comma separated
+ -v23.namespace.root=[/(dev.v.io/role/vprod/service/mounttabled)@ns.dev.v.io:8101]
+   local namespace root; can be repeated to provided multiple roots
+ -v23.permissions.file=map[]
+   specify a perms file as <name>:<permsfile>
+ -v23.permissions.literal=
+   explicitly specify the runtime perms as a JSON-encoded access.Permissions.
+   Overrides all --v23.permissions.file flags.
+ -v23.proxy=
+   object name of proxy service to use to export services across network
+   boundaries
+ -v23.tcp.address=
+   address to listen on
+ -v23.tcp.protocol=wsh
+   protocol to listen with
+ -v23.vtrace.cache-size=1024
+   The number of vtrace traces to store in memory.
+ -v23.vtrace.collect-regexp=
+   Spans and annotations that match this regular expression will trigger trace
+   collection.
+ -v23.vtrace.dump-on-shutdown=true
+   If true, dump all stored traces on runtime shutdown.
+ -v23.vtrace.sample-rate=0
+   Rate (from 0.0 to 1.0) to sample vtrace traces.
+ -vmodule=
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
+*/
+package main
diff --git a/services/build/buildd/impl_test.go b/services/build/buildd/impl_test.go
new file mode 100644
index 0000000..147b165
--- /dev/null
+++ b/services/build/buildd/impl_test.go
@@ -0,0 +1,222 @@
+// 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 (
+	"os"
+	"os/exec"
+	"path/filepath"
+	"runtime"
+	"strings"
+	"testing"
+
+	"v.io/v23/context"
+	"v.io/v23/services/build"
+
+	"v.io/x/ref/lib/xrpc"
+	"v.io/x/ref/test"
+)
+
+//go:generate v23 test generate
+
+// findGoBinary returns the path to the given Go binary and
+// the GOROOT environment variable to use.
+func findGoBinary(t *testing.T, name string) (bin, goroot string) {
+	root := os.Getenv("V23_ROOT")
+	if root == "" {
+		t.Fatalf("V23_ROOT is not set")
+	}
+	envroot := filepath.Join(root, "environment", "go", runtime.GOOS, runtime.GOARCH, "go")
+	envbin := filepath.Join(envroot, "bin", name)
+	if _, err := os.Stat(envbin); err == nil {
+		return envbin, envroot
+	} else if !os.IsNotExist(err) {
+		t.Fatalf("Stat(%v) failed: %v", envbin, err)
+	}
+	pathbin, err := exec.LookPath(name)
+	if err != nil {
+		if err == exec.ErrNotFound {
+			t.Fatalf("%q does not exist and %q not found in PATH", envbin, name)
+		} else {
+			t.Fatalf("LookPath(%q) failed: %v", name, err)
+		}
+	}
+	return pathbin, os.Getenv("GOROOT")
+}
+
+// getArch returns an Architecture representing the host architecture.
+func getArch(t *testing.T) (arch build.Architecture) {
+	if err := arch.SetFromGoArch(runtime.GOARCH); err != nil {
+		t.Fatalf("%v", err)
+	}
+	return
+}
+
+// getOS returns an OperatingSystem representing the host operating
+// system.
+func getOS(t *testing.T) (os build.OperatingSystem) {
+	if err := os.SetFromGoOS(runtime.GOOS); err != nil {
+		t.Fatalf("%v", err)
+	}
+	return
+}
+
+// startServer starts the build server.
+func startServer(t *testing.T, ctx *context.T) build.BuilderClientMethods {
+	gobin, goroot := findGoBinary(t, "go")
+	service := build.BuilderServer(NewBuilderService(gobin, goroot))
+	unpublished := ""
+	server, err := xrpc.NewServer(ctx, unpublished, service, nil)
+	if err != nil {
+		t.Fatalf("NewServer() failed: %v", err)
+	}
+	return build.BuilderClient(server.Status().Endpoints[0].Name())
+}
+
+func invokeBuild(t *testing.T, ctx *context.T, client build.BuilderClientMethods, files []build.File) ([]byte, []build.File, error) {
+	arch, os := getArch(t), getOS(t)
+	ctx, cancel := context.WithCancel(ctx)
+	defer cancel()
+	stream, err := client.Build(ctx, arch, os)
+	if err != nil {
+		t.Errorf("Build(%v, %v) failed: %v", err, arch, os)
+		return nil, nil, err
+	}
+	sender := stream.SendStream()
+	for _, file := range files {
+		if err := sender.Send(file); err != nil {
+			t.Logf("Send() failed: %v", err)
+			return nil, nil, err
+		}
+	}
+	if err := sender.Close(); err != nil {
+		t.Logf("Close() failed: %v", err)
+		return nil, nil, err
+	}
+	bins := make([]build.File, 0)
+	rStream := stream.RecvStream()
+	for rStream.Advance() {
+		bins = append(bins, rStream.Value())
+	}
+	if err := rStream.Err(); err != nil {
+		t.Logf("Advance() failed: %v", err)
+		return nil, nil, err
+	}
+	output, err := stream.Finish()
+	if err != nil {
+		t.Logf("Finish() failed: %v", err)
+		return nil, nil, err
+	}
+	return output, bins, nil
+}
+
+const mainSrc = `package main
+
+import "fmt"
+
+func main() {
+	fmt.Println("Hello World!")
+}
+`
+
+func containsPkg(pkgs, target string) bool {
+	pkgs = strings.TrimSpace(strings.Replace(pkgs, "\n", " ", -1))
+	for _, str := range strings.Split(pkgs, " ") {
+		if str == target {
+			return true
+		}
+	}
+	return false
+}
+
+// TestSuccess checks that the build server successfully builds a
+// package that depends on the standard Go library.
+func TestSuccess(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	client := startServer(t, ctx)
+
+	files := []build.File{
+		build.File{
+			Name:     "testfoopkg/main.go",
+			Contents: []byte(mainSrc),
+		},
+	}
+	output, bins, err := invokeBuild(t, ctx, client, files)
+	if err != nil {
+		t.FailNow()
+	}
+	if got, want := string(output), "testfoopkg"; !containsPkg(got, want) {
+		t.Fatalf("Unexpected output: got %v, want %v", got, want)
+	}
+	if got, want := len(bins), 1; got != want {
+		t.Fatalf("Unexpected number of binaries: got %v, want %v", got, want)
+	}
+}
+
+const fooSrc = `package foo
+
+import "fmt"
+
+func foo() {
+	fmt.Println("Hello World!")
+}
+`
+
+// TestEmpty checks that the build server successfully builds a
+// package that does not produce a binary.
+func TestEmpty(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	client := startServer(t, ctx)
+
+	files := []build.File{
+		build.File{
+			Name:     "test/foo.go",
+			Contents: []byte(fooSrc),
+		},
+	}
+	output, bins, err := invokeBuild(t, ctx, client, files)
+	if err != nil {
+		t.FailNow()
+	}
+	if got, expected := strings.TrimSpace(string(output)), "test"; got != expected {
+		t.Fatalf("Unexpected output: got %v, expected %v", got, expected)
+	}
+	if got, expected := len(bins), 0; got != expected {
+		t.Fatalf("Unexpected number of binaries: got %v, expected %v", got, expected)
+	}
+}
+
+const failSrc = `package main
+
+import "fmt"
+
+func main() {
+        ...
+}
+`
+
+// TestFailure checks that the build server fails to build a package
+// consisting of an empty file.
+func TestFailure(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	client := startServer(t, ctx)
+
+	files := []build.File{
+		build.File{
+			Name:     "test/main.go",
+			Contents: []byte(failSrc),
+		},
+	}
+	if output, _, err := invokeBuild(t, ctx, client, files); err == nil {
+		t.Logf("%v", string(output))
+		t.FailNow()
+	}
+}
diff --git a/services/build/buildd/main.go b/services/build/buildd/main.go
new file mode 100644
index 0000000..cf5cc2d
--- /dev/null
+++ b/services/build/buildd/main.go
@@ -0,0 +1,56 @@
+// 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.
+
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go . -help
+
+package main
+
+import (
+	"fmt"
+	"os"
+
+	"v.io/v23/context"
+	"v.io/v23/services/build"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/security/securityflag"
+	"v.io/x/ref/lib/signals"
+	"v.io/x/ref/lib/v23cmd"
+	"v.io/x/ref/lib/xrpc"
+	_ "v.io/x/ref/runtime/factories/roaming"
+)
+
+var gobin, goroot, name string
+
+func main() {
+	cmdBuildD.Flags.StringVar(&gobin, "gobin", "go", "Path to the Go compiler.")
+	cmdBuildD.Flags.StringVar(&goroot, "goroot", os.Getenv("GOROOT"), "GOROOT to use with the Go compiler.  The default is the value of the GOROOT environment variable.")
+	cmdBuildD.Flags.Lookup("goroot").DefValue = "<GOROOT>"
+	cmdBuildD.Flags.StringVar(&name, "name", "", "Name to mount the build server as.")
+
+	cmdline.HideGlobalFlagsExcept()
+	cmdline.Main(cmdBuildD)
+}
+
+var cmdBuildD = &cmdline.Command{
+	Runner: v23cmd.RunnerFunc(runBuildD),
+	Name:   "buildd",
+	Short:  "Runs the builder daemon.",
+	Long: `
+Command buildd runs the builder daemon, which implements the
+v.io/v23/services/build.Builder interface.
+`,
+}
+
+func runBuildD(ctx *context.T, env *cmdline.Env, args []string) error {
+	server, err := xrpc.NewServer(ctx, name, build.BuilderServer(NewBuilderService(gobin, goroot)), securityflag.NewAuthorizerOrDie())
+	if err != nil {
+		return fmt.Errorf("NewServer() failed: %v", err)
+	}
+	ctx.Infof("Build server running at endpoint=%q", server.Status().Endpoints[0].Name())
+
+	// Wait until shutdown.
+	<-signals.ShutdownOnSignals(ctx)
+	return nil
+}
diff --git a/services/build/buildd/service.go b/services/build/buildd/service.go
new file mode 100644
index 0000000..44ca144
--- /dev/null
+++ b/services/build/buildd/service.go
@@ -0,0 +1,142 @@
+// 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 (
+	"bytes"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"runtime"
+	"strings"
+
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/services/binary"
+	"v.io/v23/services/build"
+	"v.io/v23/verror"
+	"v.io/x/lib/host"
+)
+
+const pkgPath = "v.io/x/ref/services/build/buildd"
+
+// Errors
+var (
+	errBuildFailed = verror.Register(pkgPath+".errBuildFailed", verror.NoRetry, "{1:}{2:} build failed{:_}")
+)
+
+// builderService implements the Builder server interface.
+type builderService struct {
+	// Path to the binary and the value of the GOROOT environment variable.
+	gobin, goroot string
+}
+
+// NewBuilderService returns a new Build service implementation.
+func NewBuilderService(gobin, goroot string) build.BuilderServerMethods {
+	return &builderService{
+		gobin:  gobin,
+		goroot: goroot,
+	}
+}
+
+// TODO(jsimsa): Add support for building for a specific profile
+// specified as a suffix the Build().
+//
+// TODO(jsimsa): Analyze the binary files for shared library
+// dependencies and ship these back.
+func (i *builderService) Build(ctx *context.T, call build.BuilderBuildServerCall, arch build.Architecture, opsys build.OperatingSystem) ([]byte, error) {
+	ctx.VI(1).Infof("Build(%v, %v) called.", arch, opsys)
+	dir, prefix := "", ""
+	dirPerm, filePerm := os.FileMode(0700), os.FileMode(0600)
+	root, err := ioutil.TempDir(dir, prefix)
+	if err != nil {
+		ctx.Errorf("TempDir(%v, %v) failed: %v", dir, prefix, err)
+		return nil, verror.New(verror.ErrInternal, ctx)
+	}
+	defer os.RemoveAll(root)
+	srcDir := filepath.Join(root, "go", "src")
+	if err := os.MkdirAll(srcDir, dirPerm); err != nil {
+		ctx.Errorf("MkdirAll(%v, %v) failed: %v", srcDir, dirPerm, err)
+		return nil, verror.New(verror.ErrInternal, ctx)
+	}
+	iterator := call.RecvStream()
+	for iterator.Advance() {
+		srcFile := iterator.Value()
+		filePath := filepath.Join(srcDir, filepath.FromSlash(srcFile.Name))
+		dir := filepath.Dir(filePath)
+		if err := os.MkdirAll(dir, dirPerm); err != nil {
+			ctx.Errorf("MkdirAll(%v, %v) failed: %v", dir, dirPerm, err)
+			return nil, verror.New(verror.ErrInternal, ctx)
+		}
+		if err := ioutil.WriteFile(filePath, srcFile.Contents, filePerm); err != nil {
+			ctx.Errorf("WriteFile(%v, %v) failed: %v", filePath, filePerm, err)
+			return nil, verror.New(verror.ErrInternal, ctx)
+		}
+	}
+	if err := iterator.Err(); err != nil {
+		ctx.Errorf("Advance() failed: %v", err)
+		return nil, verror.New(verror.ErrInternal, ctx)
+	}
+	// NOTE: we actually want run "go install -v {srcDir}/..." here, but
+	// the go tool seems to have a bug where it doesn't interpret rooted
+	// (absolute) paths with wildcards correctly.  So we run "go install
+	// -v all" instead, which has the downside that it might cause some
+	// standard packages to be built spuriously.
+	cmd := exec.Command(i.gobin, "install", "-v", "all")
+	cmd.Env = append(cmd.Env, "GOARCH="+arch.ToGoArch())
+	cmd.Env = append(cmd.Env, "GOOS="+opsys.ToGoOS())
+	cmd.Env = append(cmd.Env, "GOPATH="+filepath.Dir(srcDir))
+	if i.goroot != "" {
+		cmd.Env = append(cmd.Env, "GOROOT="+i.goroot)
+	}
+	var output bytes.Buffer
+	cmd.Stdout = &output
+	cmd.Stderr = &output
+	if err := cmd.Run(); err != nil {
+		ctx.Errorf("Run(%q) failed: %v", strings.Join(cmd.Args, " "), err)
+		if output.Len() != 0 {
+			ctx.Errorf("%v", output.String())
+		}
+		return output.Bytes(), verror.New(errBuildFailed, ctx)
+	}
+	binDir := filepath.Join(root, "go", "bin")
+	machineArch, err := host.Arch()
+	if err != nil {
+		ctx.Errorf("Arch() failed: %v", err)
+		return nil, verror.New(verror.ErrInternal, ctx)
+	}
+	if machineArch != arch.ToGoArch() || runtime.GOOS != opsys.ToGoOS() {
+		binDir = filepath.Join(binDir, fmt.Sprintf("%v_%v", opsys.ToGoOS(), arch.ToGoArch()))
+	}
+	files, err := ioutil.ReadDir(binDir)
+	if err != nil && !os.IsNotExist(err) {
+		ctx.Errorf("ReadDir(%v) failed: %v", binDir, err)
+		return nil, verror.New(verror.ErrInternal, ctx)
+	}
+	for _, file := range files {
+		binPath := filepath.Join(binDir, file.Name())
+		bytes, err := ioutil.ReadFile(binPath)
+		if err != nil {
+			ctx.Errorf("ReadFile(%v) failed: %v", binPath, err)
+			return nil, verror.New(verror.ErrInternal, ctx)
+		}
+		result := build.File{
+			Name:     "bin/" + file.Name(),
+			Contents: bytes,
+		}
+		if err := call.SendStream().Send(result); err != nil {
+			ctx.Errorf("Send() failed: %v", err)
+			return nil, verror.New(verror.ErrInternal, ctx)
+		}
+	}
+	return output.Bytes(), nil
+}
+
+func (i *builderService) Describe(_ *context.T, _ rpc.ServerCall, name string) (binary.Description, error) {
+	// TODO(jsimsa): Implement.
+	return binary.Description{}, nil
+}
diff --git a/services/build/buildd/v23_test.go b/services/build/buildd/v23_test.go
new file mode 100644
index 0000000..dbfb5ac
--- /dev/null
+++ b/services/build/buildd/v23_test.go
@@ -0,0 +1,30 @@
+// 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 main_test
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
+	"v.io/x/ref/test/v23tests"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	modules.DispatchAndExitIfChild()
+	cleanup := v23tests.UseSharedBinDir()
+	r := m.Run()
+	cleanup()
+	os.Exit(r)
+}
+
+func TestV23BuildServerIntegration(t *testing.T) {
+	v23tests.RunTest(t, V23TestBuildServerIntegration)
+}
diff --git a/services/debug/debug/debug_v23_test.go b/services/debug/debug/debug_v23_test.go
new file mode 100644
index 0000000..c854906
--- /dev/null
+++ b/services/debug/debug/debug_v23_test.go
@@ -0,0 +1,220 @@
+// 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_test
+
+import (
+	"bufio"
+	"fmt"
+	"os"
+	"path/filepath"
+	"regexp"
+	"strconv"
+	"strings"
+	"time"
+
+	"v.io/x/ref/test/v23tests"
+)
+
+//go:generate v23 test generate
+
+func V23TestDebugGlob(i *v23tests.T) {
+	v23tests.RunRootMT(i, "--v23.tcp.address=127.0.0.1:0")
+
+	binary := i.BuildV23Pkg("v.io/x/ref/services/debug/debug")
+	inv := binary.Start("glob", "__debug/*")
+
+	var want string
+	for _, entry := range []string{"logs", "pprof", "stats", "vtrace"} {
+		want += "__debug/" + entry + "\n"
+	}
+	if got := inv.Output(); got != want {
+		i.Fatalf("unexpected output, want %s, got %s", want, got)
+	}
+}
+
+func V23TestDebugGlobLogs(i *v23tests.T) {
+	v23tests.RunRootMT(i, "--v23.tcp.address=127.0.0.1:0")
+
+	// Create a temp file before we list the logs.
+	fileName := filepath.Base(i.NewTempFile().Name())
+	binary := i.BuildV23Pkg("v.io/x/ref/services/debug/debug")
+	output := binary.Start("glob", "__debug/logs/*").Output()
+
+	// The output should contain the filename.
+	want := "/logs/" + fileName
+	if !strings.Contains(output, want) {
+		i.Fatalf("output should contain %s but did not\n%s", want, output)
+	}
+}
+
+func V23TestReadHostname(i *v23tests.T) {
+	v23tests.RunRootMT(i, "--v23.tcp.address=127.0.0.1:0")
+
+	path := "__debug/stats/system/hostname"
+	binary := i.BuildV23Pkg("v.io/x/ref/services/debug/debug")
+	got := binary.Start("stats", "read", path).Output()
+	hostname, err := os.Hostname()
+	if err != nil {
+		i.Fatalf("Hostname() failed: %v", err)
+	}
+	if want := path + ": " + hostname + "\n"; got != want {
+		i.Fatalf("unexpected output, want %q, got %q", want, got)
+	}
+}
+
+func createTestLogFile(i *v23tests.T, content string) *os.File {
+	file := i.NewTempFile()
+	_, err := file.Write([]byte(content))
+	if err != nil {
+		i.Fatalf("Write failed: %v", err)
+	}
+	return file
+}
+
+func V23TestLogSize(i *v23tests.T) {
+	v23tests.RunRootMT(i, "--v23.tcp.address=127.0.0.1:0")
+
+	binary := i.BuildV23Pkg("v.io/x/ref/services/debug/debug")
+	testLogData := "This is a test log file"
+	file := createTestLogFile(i, testLogData)
+
+	// Check to ensure the file size is accurate
+	str := strings.TrimSpace(binary.Start("logs", "size", "__debug/logs/"+filepath.Base(file.Name())).Output())
+	got, err := strconv.Atoi(str)
+	if err != nil {
+		i.Fatalf("Atoi(\"%q\") failed", str)
+	}
+	want := len(testLogData)
+	if got != want {
+		i.Fatalf("unexpected output, want %d, got %d", got, want)
+	}
+}
+
+func V23TestStatsRead(i *v23tests.T) {
+	v23tests.RunRootMT(i, "--v23.tcp.address=127.0.0.1:0")
+
+	binary := i.BuildV23Pkg("v.io/x/ref/services/debug/debug")
+	testLogData := "This is a test log file\n"
+	file := createTestLogFile(i, testLogData)
+	logName := filepath.Base(file.Name())
+	runCount := 12
+	for c := 0; c < runCount; c++ {
+		binary.Start("logs", "read", "__debug/logs/"+logName).WaitOrDie(os.Stderr, os.Stderr)
+	}
+
+	got := binary.Start("stats", "read", "__debug/stats/rpc/server/routing-id/*/methods/ReadLog/latency-ms").Output()
+
+	want := fmt.Sprintf("Count: %d", runCount)
+	if !strings.Contains(got, want) {
+		i.Fatalf("expected output %q to contain %q, but did not\n", got, want)
+	}
+}
+
+func V23TestStatsWatch(i *v23tests.T) {
+	v23tests.RunRootMT(i, "--v23.tcp.address=127.0.0.1:0")
+
+	binary := i.BuildV23Pkg("v.io/x/ref/services/debug/debug")
+	testLogData := "This is a test log file\n"
+	file := createTestLogFile(i, testLogData)
+	logName := filepath.Base(file.Name())
+	binary.Start("logs", "read", "__debug/logs/"+logName).WaitOrDie(nil, nil)
+
+	inv := binary.Start("stats", "watch", "-raw", "__debug/stats/rpc/server/routing-id/*/methods/ReadLog/latency-ms")
+
+	lineChan := make(chan string)
+	// Go off and read the invocation's stdout.
+	go func() {
+		line, err := bufio.NewReader(inv.Stdout()).ReadString('\n')
+		if err != nil {
+			i.Fatalf("Could not read line from invocation")
+		}
+		lineChan <- line
+	}()
+
+	// Wait up to 10 seconds for some stats output. Either some output
+	// occurs or the timeout expires without any output.
+	select {
+	case <-time.After(10 * time.Second):
+		i.Errorf("Timed out waiting for output")
+	case got := <-lineChan:
+		// Expect one ReadLog call to have occurred.
+		want := "}}{Count: 1"
+		if !strings.Contains(got, want) {
+			i.Errorf("wanted but could not find %q in output\n%s", want, got)
+		}
+	}
+}
+
+func performTracedRead(debugBinary *v23tests.Binary, path string) string {
+	return debugBinary.Start("--v23.vtrace.sample-rate=1", "logs", "read", path).Output()
+}
+
+func V23TestVTrace(i *v23tests.T) {
+	v23tests.RunRootMT(i, "--v23.tcp.address=127.0.0.1:0")
+
+	binary := i.BuildV23Pkg("v.io/x/ref/services/debug/debug")
+	logContent := "Hello, world!\n"
+	logPath := "__debug/logs/" + filepath.Base(createTestLogFile(i, logContent).Name())
+	// Create a log file with tracing, read it and check that the resulting trace exists.
+	got := performTracedRead(binary, logPath)
+	if logContent != got {
+		i.Fatalf("unexpected output: want %s, got %s", logContent, got)
+	}
+
+	// Grab the ID of the first and only trace.
+	want, traceContent := 1, binary.Start("vtrace", "__debug/vtrace").Output()
+	if count := strings.Count(traceContent, "Trace -"); count != want {
+		i.Fatalf("unexpected trace count, want %d, got %d: %s", want, count, traceContent)
+	}
+	fields := strings.Split(traceContent, " ")
+	if len(fields) < 3 {
+		i.Fatalf("expected at least 3 space-delimited fields, got %d: %v", len(fields), traceContent)
+	}
+	traceId := fields[2]
+
+	// Do a sanity check on the trace ID: it should be a 32-character hex ID prefixed with 0x
+	if match, _ := regexp.MatchString("0x[0-9a-f]{32}", traceId); !match {
+		i.Fatalf("wanted a 32-character hex ID prefixed with 0x, got %s", traceId)
+	}
+
+	// Do another traced read, this will generate a new trace entry.
+	performTracedRead(binary, logPath)
+
+	// Read vtrace, we should have 2 traces now.
+	want, output := 2, binary.Start("vtrace", "__debug/vtrace").Output()
+	if count := strings.Count(output, "Trace -"); count != want {
+		i.Fatalf("unexpected trace count, want %d, got %d\n%s", want, count, output)
+	}
+
+	// Now ask for a particular trace. The output should contain exactly
+	// one trace whose ID is equal to the one we asked for.
+	want, got = 1, binary.Start("vtrace", "__debug/vtrace", traceId).Output()
+	if count := strings.Count(got, "Trace -"); count != want {
+		i.Fatalf("unexpected trace count, want %d, got %d\n%s", want, count, got)
+	}
+	fields = strings.Split(got, " ")
+	if len(fields) < 3 {
+		i.Fatalf("expected at least 3 space-delimited fields, got %d: %v", len(fields), got)
+	}
+	got = fields[2]
+	if traceId != got {
+		i.Fatalf("unexpected traceId, want %s, got %s", traceId, got)
+	}
+}
+
+func V23TestPprof(i *v23tests.T) {
+	v23tests.RunRootMT(i, "--v23.tcp.address=127.0.0.1:0")
+
+	binary := i.BuildV23Pkg("v.io/x/ref/services/debug/debug")
+	inv := binary.Start("pprof", "run", "__debug/pprof", "heap", "--text")
+
+	// Assert that a profile indicating the heap size was written out.
+	want, got := "(.*) of (.*) total", inv.Output()
+	var groups []string
+	if groups = regexp.MustCompile(want).FindStringSubmatch(got); len(groups) < 3 {
+		i.Fatalf("could not find regexp %q in output\n%s", want, got)
+	}
+	i.Logf("got a heap profile showing a heap size of %s", groups[2])
+}
diff --git a/services/debug/debug/doc.go b/services/debug/debug/doc.go
new file mode 100644
index 0000000..a84d555
--- /dev/null
+++ b/services/debug/debug/doc.go
@@ -0,0 +1,230 @@
+// 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
+
+/*
+Command debug supports debugging Vanadium servers.
+
+Usage:
+   debug <command>
+
+The debug commands are:
+   glob        Returns all matching entries from the namespace.
+   vtrace      Returns vtrace traces.
+   logs        Accesses log files
+   stats       Accesses stats
+   pprof       Accesses profiling data
+   help        Display help for commands or topics
+
+The global flags are:
+ -alsologtostderr=true
+   log to standard error as well as files
+ -log_backtrace_at=:0
+   when logging hits line file:N, emit a stack trace
+ -log_dir=
+   if non-empty, write log files to this directory
+ -logtostderr=false
+   log to standard error instead of files
+ -max_stack_buf_size=4292608
+   max size in bytes of the buffer to use for logging stack traces
+ -metadata=<just specify -metadata to activate>
+   Displays metadata for the program and exits.
+ -stderrthreshold=2
+   logs at or above this threshold go to stderr
+ -v=0
+   log level for V logs
+ -v23.credentials=
+   directory to use for storing security credentials
+ -v23.i18n-catalogue=
+   18n catalogue files to load, comma separated
+ -v23.namespace.root=[/(dev.v.io/role/vprod/service/mounttabled)@ns.dev.v.io:8101]
+   local namespace root; can be repeated to provided multiple roots
+ -v23.proxy=
+   object name of proxy service to use to export services across network
+   boundaries
+ -v23.tcp.address=
+   address to listen on
+ -v23.tcp.protocol=wsh
+   protocol to listen with
+ -v23.vtrace.cache-size=1024
+   The number of vtrace traces to store in memory.
+ -v23.vtrace.collect-regexp=
+   Spans and annotations that match this regular expression will trigger trace
+   collection.
+ -v23.vtrace.dump-on-shutdown=true
+   If true, dump all stored traces on runtime shutdown.
+ -v23.vtrace.sample-rate=0
+   Rate (from 0.0 to 1.0) to sample vtrace traces.
+ -vmodule=
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
+
+Debug glob
+
+Returns all matching entries from the namespace.
+
+Usage:
+   debug glob <pattern> ...
+
+<pattern> is a glob pattern to match.
+
+Debug vtrace
+
+Returns matching vtrace traces (or all stored traces if no ids are given).
+
+Usage:
+   debug vtrace <name> [id ...]
+
+<name> is the name of a vtrace object. [id] is a vtrace trace id.
+
+Debug logs - Accesses log files
+
+Accesses log files
+
+Usage:
+   debug logs <command>
+
+The debug logs commands are:
+   read        Reads the content of a log file object.
+   size        Returns the size of a log file object.
+
+Debug logs read
+
+Reads the content of a log file object.
+
+Usage:
+   debug logs read [flags] <name>
+
+<name> is the name of the log file object.
+
+The debug logs read flags are:
+ -f=false
+   When true, read will wait for new log entries when it reaches the end of the
+   file.
+ -n=-1
+   The number of log entries to read.
+ -o=0
+   The position, in bytes, from which to start reading the log file.
+ -v=false
+   When true, read will be more verbose.
+
+Debug logs size
+
+Returns the size of a log file object.
+
+Usage:
+   debug logs size <name>
+
+<name> is the name of the log file object.
+
+Debug stats - Accesses stats
+
+Accesses stats
+
+Usage:
+   debug stats <command>
+
+The debug stats commands are:
+   read        Returns the value of stats objects.
+   watch       Returns a stream of all matching entries and their values as they
+               change.
+
+Debug stats read
+
+Returns the value of stats objects.
+
+Usage:
+   debug stats read [flags] <name> ...
+
+<name> is the name of a stats object, or a glob pattern to match against stats
+object names.
+
+The debug stats read flags are:
+ -raw=false
+   When true, the command will display the raw value of the object.
+ -type=false
+   When true, the type of the values will be displayed.
+
+Debug stats watch
+
+Returns a stream of all matching entries and their values as they change.
+
+Usage:
+   debug stats watch [flags] <pattern> ...
+
+<pattern> is a glob pattern to match.
+
+The debug stats watch flags are:
+ -raw=false
+   When true, the command will display the raw value of the object.
+ -type=false
+   When true, the type of the values will be displayed.
+
+Debug pprof - Accesses profiling data
+
+Accesses profiling data
+
+Usage:
+   debug pprof <command>
+
+The debug pprof commands are:
+   run         Runs the pprof tool.
+   proxy       Runs an http proxy to a pprof object.
+
+Debug pprof run
+
+Runs the pprof tool.
+
+Usage:
+   debug pprof run [flags] <name> <profile> [passthru args] ...
+
+<name> is the name of the pprof object. <profile> the name of the profile to
+use.
+
+All the [passthru args] are passed to the pprof tool directly, e.g.
+
+$ debug pprof run a/b/c heap --text $ debug pprof run a/b/c profile -gv
+
+The debug pprof run flags are:
+ -pprofcmd=v23 go tool pprof
+   The pprof command to use.
+
+Debug pprof proxy
+
+Runs an http proxy to a pprof object.
+
+Usage:
+   debug pprof proxy <name>
+
+<name> is the name of the pprof object.
+
+Debug help - Display help for commands or topics
+
+Help with no args displays the usage of the parent command.
+
+Help with args displays the usage of the specified sub-command or help topic.
+
+"help ..." recursively displays help for all commands and topics.
+
+Usage:
+   debug help [flags] [command/topic ...]
+
+[command/topic ...] optionally identifies a specific sub-command or help topic.
+
+The debug help flags are:
+ -style=compact
+   The formatting style for help output:
+      compact - Good for compact cmdline output.
+      full    - Good for cmdline output, shows all global flags.
+      godoc   - Good for godoc processing.
+   Override the default by setting the CMDLINE_STYLE environment variable.
+ -width=<terminal width>
+   Format output to this target width in runes, or unlimited if width < 0.
+   Defaults to the terminal width if available.  Override the default by setting
+   the CMDLINE_WIDTH environment variable.
+*/
+package main
diff --git a/services/debug/debug/impl.go b/services/debug/debug/impl.go
new file mode 100644
index 0000000..0a584d7
--- /dev/null
+++ b/services/debug/debug/impl.go
@@ -0,0 +1,586 @@
+// 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.
+
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go .
+
+package main
+
+import (
+	"fmt"
+	"os"
+	"os/exec"
+	"regexp"
+	"sort"
+	"strings"
+	"sync"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/glob"
+	"v.io/v23/naming"
+	"v.io/v23/services/logreader"
+	"v.io/v23/services/pprof"
+	"v.io/v23/services/stats"
+	s_vtrace "v.io/v23/services/vtrace"
+	"v.io/v23/services/watch"
+	"v.io/v23/uniqueid"
+	"v.io/v23/vdl"
+	"v.io/v23/vtrace"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/signals"
+	"v.io/x/ref/lib/v23cmd"
+	_ "v.io/x/ref/runtime/factories/generic"
+	"v.io/x/ref/services/internal/pproflib"
+)
+
+func main() {
+	cmdline.Main(cmdRoot)
+}
+
+var (
+	follow     bool
+	verbose    bool
+	numEntries int
+	startPos   int64
+	raw        bool
+	showType   bool
+	pprofCmd   string
+)
+
+func init() {
+	cmdline.HideGlobalFlagsExcept()
+
+	// logs read flags
+	cmdLogsRead.Flags.BoolVar(&follow, "f", false, "When true, read will wait for new log entries when it reaches the end of the file.")
+	cmdLogsRead.Flags.BoolVar(&verbose, "v", false, "When true, read will be more verbose.")
+	cmdLogsRead.Flags.IntVar(&numEntries, "n", int(logreader.AllEntries), "The number of log entries to read.")
+	cmdLogsRead.Flags.Int64Var(&startPos, "o", 0, "The position, in bytes, from which to start reading the log file.")
+
+	// stats read flags
+	cmdStatsRead.Flags.BoolVar(&raw, "raw", false, "When true, the command will display the raw value of the object.")
+	cmdStatsRead.Flags.BoolVar(&showType, "type", false, "When true, the type of the values will be displayed.")
+
+	// stats watch flags
+	cmdStatsWatch.Flags.BoolVar(&raw, "raw", false, "When true, the command will display the raw value of the object.")
+	cmdStatsWatch.Flags.BoolVar(&showType, "type", false, "When true, the type of the values will be displayed.")
+
+	// pprof flags
+	cmdPProfRun.Flags.StringVar(&pprofCmd, "pprofcmd", "v23 go tool pprof", "The pprof command to use.")
+}
+
+var cmdVtrace = &cmdline.Command{
+	Runner:   v23cmd.RunnerFunc(runVtrace),
+	Name:     "vtrace",
+	Short:    "Returns vtrace traces.",
+	Long:     "Returns matching vtrace traces (or all stored traces if no ids are given).",
+	ArgsName: "<name> [id ...]",
+	ArgsLong: `
+<name> is the name of a vtrace object.
+[id] is a vtrace trace id.
+`,
+}
+
+func doFetchTrace(ctx *context.T, wg *sync.WaitGroup, client s_vtrace.StoreClientStub,
+	id uniqueid.Id, traces chan *vtrace.TraceRecord, errors chan error) {
+	defer wg.Done()
+
+	trace, err := client.Trace(ctx, id)
+	if err != nil {
+		errors <- err
+	} else {
+		traces <- &trace
+	}
+}
+
+func runVtrace(ctx *context.T, env *cmdline.Env, args []string) error {
+	arglen := len(args)
+	if arglen == 0 {
+		return env.UsageErrorf("vtrace: incorrect number of arguments, got %d want >= 1", arglen)
+	}
+
+	name := args[0]
+	client := s_vtrace.StoreClient(name)
+	if arglen == 1 {
+		call, err := client.AllTraces(ctx)
+		if err != nil {
+			return err
+		}
+		stream := call.RecvStream()
+		for stream.Advance() {
+			trace := stream.Value()
+			vtrace.FormatTrace(os.Stdout, &trace, nil)
+		}
+		if err := stream.Err(); err != nil {
+			return err
+		}
+		return call.Finish()
+	}
+
+	ntraces := len(args) - 1
+	traces := make(chan *vtrace.TraceRecord, ntraces)
+	errors := make(chan error, ntraces)
+	var wg sync.WaitGroup
+	wg.Add(ntraces)
+	for _, arg := range args[1:] {
+		id, err := uniqueid.FromHexString(arg)
+		if err != nil {
+			return err
+		}
+		go doFetchTrace(ctx, &wg, client, id, traces, errors)
+	}
+	go func() {
+		wg.Wait()
+		close(traces)
+		close(errors)
+	}()
+
+	for trace := range traces {
+		vtrace.FormatTrace(os.Stdout, trace, nil)
+	}
+
+	// Just return one of the errors.
+	return <-errors
+}
+
+var cmdGlob = &cmdline.Command{
+	Runner:   v23cmd.RunnerFunc(runGlob),
+	Name:     "glob",
+	Short:    "Returns all matching entries from the namespace.",
+	Long:     "Returns all matching entries from the namespace.",
+	ArgsName: "<pattern> ...",
+	ArgsLong: `
+<pattern> is a glob pattern to match.
+`,
+}
+
+func runGlob(ctx *context.T, env *cmdline.Env, args []string) error {
+	if min, got := 1, len(args); got < min {
+		return env.UsageErrorf("glob: incorrect number of arguments, got %d, want >=%d", got, min)
+	}
+	results := make(chan naming.GlobReply)
+	errors := make(chan error)
+	doGlobs(ctx, args, results, errors)
+	var lastErr error
+	for {
+		select {
+		case err := <-errors:
+			lastErr = err
+			fmt.Fprintln(env.Stderr, "Error:", err)
+		case me, ok := <-results:
+			if !ok {
+				return lastErr
+			}
+			switch v := me.(type) {
+			case *naming.GlobReplyEntry:
+				fmt.Fprint(env.Stdout, v.Value.Name)
+				for _, s := range v.Value.Servers {
+					fmt.Fprintf(env.Stdout, " %s (Deadline %s)", s.Server, s.Deadline.Time)
+				}
+				fmt.Fprintln(env.Stdout)
+			case *naming.GlobReplyError:
+				fmt.Fprintf(env.Stderr, "Error: %s: %v\n", v.Value.Name, v.Value.Error)
+			}
+		}
+	}
+}
+
+// doGlobs calls Glob on multiple patterns in parallel and sends all the results
+// on the results channel and all the errors on the errors channel. It closes
+// the results channel when all the results have been sent.
+func doGlobs(ctx *context.T, patterns []string, results chan<- naming.GlobReply, errors chan<- error) {
+	var wg sync.WaitGroup
+	wg.Add(len(patterns))
+	for _, p := range patterns {
+		go doGlob(ctx, p, results, errors, &wg)
+	}
+	go func() {
+		wg.Wait()
+		close(results)
+	}()
+}
+
+func doGlob(ctx *context.T, pattern string, results chan<- naming.GlobReply, errors chan<- error, wg *sync.WaitGroup) {
+	defer wg.Done()
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
+	defer cancel()
+	c, err := v23.GetNamespace(ctx).Glob(ctx, pattern)
+	if err != nil {
+		errors <- fmt.Errorf("%s: %v", pattern, err)
+		return
+	}
+	for me := range c {
+		results <- me
+	}
+}
+
+var cmdLogsRead = &cmdline.Command{
+	Runner:   v23cmd.RunnerFunc(runLogsRead),
+	Name:     "read",
+	Short:    "Reads the content of a log file object.",
+	Long:     "Reads the content of a log file object.",
+	ArgsName: "<name>",
+	ArgsLong: `
+<name> is the name of the log file object.
+`,
+}
+
+func runLogsRead(ctx *context.T, env *cmdline.Env, args []string) error {
+	if want, got := 1, len(args); want != got {
+		return env.UsageErrorf("read: incorrect number of arguments, got %d, want %d", got, want)
+	}
+	name := args[0]
+	lf := logreader.LogFileClient(name)
+	stream, err := lf.ReadLog(ctx, startPos, int32(numEntries), follow)
+	if err != nil {
+		return err
+	}
+	iterator := stream.RecvStream()
+	for iterator.Advance() {
+		entry := iterator.Value()
+		if verbose {
+			fmt.Fprintf(env.Stdout, "[%d] %s\n", entry.Position, entry.Line)
+		} else {
+			fmt.Fprintf(env.Stdout, "%s\n", entry.Line)
+		}
+	}
+	if err = iterator.Err(); err != nil {
+		return err
+	}
+	offset, err := stream.Finish()
+	if err != nil {
+		return err
+	}
+	if verbose {
+		fmt.Fprintf(env.Stdout, "Offset: %d\n", offset)
+	}
+	return nil
+}
+
+var cmdLogsSize = &cmdline.Command{
+	Runner:   v23cmd.RunnerFunc(runLogsSize),
+	Name:     "size",
+	Short:    "Returns the size of a log file object.",
+	Long:     "Returns the size of a log file object.",
+	ArgsName: "<name>",
+	ArgsLong: `
+<name> is the name of the log file object.
+`,
+}
+
+func runLogsSize(ctx *context.T, env *cmdline.Env, args []string) error {
+	if want, got := 1, len(args); want != got {
+		return env.UsageErrorf("size: incorrect number of arguments, got %d, want %d", got, want)
+	}
+	name := args[0]
+	lf := logreader.LogFileClient(name)
+	size, err := lf.Size(ctx)
+	if err != nil {
+		return err
+	}
+	fmt.Fprintln(env.Stdout, size)
+	return nil
+}
+
+var cmdStatsRead = &cmdline.Command{
+	Runner:   v23cmd.RunnerFunc(runStatsRead),
+	Name:     "read",
+	Short:    "Returns the value of stats objects.",
+	Long:     "Returns the value of stats objects.",
+	ArgsName: "<name> ...",
+	ArgsLong: `
+<name> is the name of a stats object, or a glob pattern to match against stats
+object names.
+`,
+}
+
+func runStatsRead(ctx *context.T, env *cmdline.Env, args []string) error {
+	if min, got := 1, len(args); got < min {
+		return env.UsageErrorf("read: incorrect number of arguments, got %d, want >=%d", got, min)
+	}
+	globResults := make(chan naming.GlobReply)
+	errors := make(chan error)
+	doGlobs(ctx, args, globResults, errors)
+
+	output := make(chan string)
+	go func() {
+		var wg sync.WaitGroup
+		for me := range globResults {
+			switch v := me.(type) {
+			case *naming.GlobReplyEntry:
+				wg.Add(1)
+				go doValue(ctx, v.Value.Name, output, errors, &wg)
+			}
+		}
+		wg.Wait()
+		close(output)
+	}()
+
+	var lastErr error
+	for {
+		select {
+		case err := <-errors:
+			lastErr = err
+			fmt.Fprintln(env.Stderr, err)
+		case out, ok := <-output:
+			if !ok {
+				return lastErr
+			}
+			fmt.Fprintln(env.Stdout, out)
+		}
+	}
+}
+
+func doValue(ctx *context.T, name string, output chan<- string, errors chan<- error, wg *sync.WaitGroup) {
+	defer wg.Done()
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
+	defer cancel()
+	v, err := stats.StatsClient(name).Value(ctx)
+	if err != nil {
+		errors <- fmt.Errorf("%s: %v", name, err)
+		return
+	}
+	fv, err := formatValue(v)
+	if err != nil {
+		errors <- fmt.Errorf("%s: %v", name, err)
+		// fv is still valid, so dump it out too.
+	}
+	output <- fmt.Sprintf("%s: %v", name, fv)
+}
+
+var cmdStatsWatch = &cmdline.Command{
+	Runner:   v23cmd.RunnerFunc(runStatsWatch),
+	Name:     "watch",
+	Short:    "Returns a stream of all matching entries and their values as they change.",
+	Long:     "Returns a stream of all matching entries and their values as they change.",
+	ArgsName: "<pattern> ...",
+	ArgsLong: `
+<pattern> is a glob pattern to match.
+`,
+}
+
+func runStatsWatch(ctx *context.T, env *cmdline.Env, args []string) error {
+	if want, got := 1, len(args); got < want {
+		return env.UsageErrorf("watch: incorrect number of arguments, got %d, want >=%d", got, want)
+	}
+
+	results := make(chan string)
+	errors := make(chan error)
+	var wg sync.WaitGroup
+	wg.Add(len(args))
+	for _, arg := range args {
+		go doWatch(ctx, arg, results, errors, &wg)
+	}
+	go func() {
+		wg.Wait()
+		close(results)
+	}()
+	var lastErr error
+	for {
+		select {
+		case err := <-errors:
+			lastErr = err
+			fmt.Fprintln(env.Stderr, "Error:", err)
+		case r, ok := <-results:
+			if !ok {
+				return lastErr
+			}
+			fmt.Fprintln(env.Stdout, r)
+		}
+	}
+}
+
+func doWatch(ctx *context.T, pattern string, results chan<- string, errors chan<- error, wg *sync.WaitGroup) {
+	defer wg.Done()
+	root, globPattern := naming.SplitAddressName(pattern)
+	g, err := glob.Parse(globPattern)
+	if err != nil {
+		errors <- fmt.Errorf("%s: %v", globPattern, err)
+		return
+	}
+	var prefixElems []string
+	prefixElems, g = g.SplitFixedElements()
+	name := naming.Join(prefixElems...)
+	if len(root) != 0 {
+		name = naming.JoinAddressName(root, name)
+	}
+	c := watch.GlobWatcherClient(name)
+	for retry := false; ; retry = true {
+		if retry {
+			time.Sleep(10 * time.Second)
+		}
+		stream, err := c.WatchGlob(ctx, watch.GlobRequest{Pattern: g.String()})
+		if err != nil {
+			errors <- fmt.Errorf("%s: %v", name, err)
+			continue
+		}
+		iterator := stream.RecvStream()
+		for iterator.Advance() {
+			v := iterator.Value()
+			fv, err := formatValue(v.Value)
+			if err != nil {
+				errors <- fmt.Errorf("%s: %v", name, err)
+				// fv is still valid, so dump it out too.
+			}
+			results <- fmt.Sprintf("%s: %s", naming.Join(name, v.Name), fv)
+		}
+		if err = iterator.Err(); err != nil {
+			errors <- fmt.Errorf("%s: %v", name, err)
+			continue
+		}
+		if err = stream.Finish(); err != nil {
+			errors <- fmt.Errorf("%s: %v", name, err)
+		}
+	}
+}
+
+func formatValue(value *vdl.Value) (string, error) {
+	var ret string
+	if showType {
+		ret += value.Type().String() + ": "
+	}
+	if raw {
+		return ret + value.String(), nil
+	}
+	// Convert the *vdl.Value into an interface{}, so that things like histograms
+	// get pretty-printed.
+	var pretty interface{}
+	err := vdl.Convert(&pretty, value)
+	if err != nil {
+		// If we can't convert, just print the raw value, but still return an error.
+		err = fmt.Errorf("couldn't pretty-print, consider setting -raw: %v", err)
+		pretty = value
+	}
+	return ret + fmt.Sprint(pretty), err
+}
+
+var cmdPProfRun = &cmdline.Command{
+	Runner:   v23cmd.RunnerFunc(runPProf),
+	Name:     "run",
+	Short:    "Runs the pprof tool.",
+	Long:     "Runs the pprof tool.",
+	ArgsName: "<name> <profile> [passthru args] ...",
+	ArgsLong: `
+<name> is the name of the pprof object.
+<profile> the name of the profile to use.
+
+All the [passthru args] are passed to the pprof tool directly, e.g.
+
+$ debug pprof run a/b/c heap --text
+$ debug pprof run a/b/c profile -gv
+`,
+}
+
+func runPProf(ctx *context.T, env *cmdline.Env, args []string) error {
+	if min, got := 1, len(args); got < min {
+		return env.UsageErrorf("pprof: incorrect number of arguments, got %d, want >=%d", got, min)
+	}
+	name := args[0]
+	if len(args) == 1 {
+		return showPProfProfiles(ctx, env, name)
+	}
+	profile := args[1]
+	listener, err := pproflib.StartProxy(ctx, name)
+	if err != nil {
+		return err
+	}
+	defer listener.Close()
+
+	// Construct the pprof command line:
+	// <pprofCmd> http://<proxyaddr>/pprof/<profile> [pprof flags]
+	pargs := []string{pprofCmd} // pprofCmd is purposely not escaped.
+	for i := 2; i < len(args); i++ {
+		pargs = append(pargs, shellEscape(args[i]))
+	}
+	pargs = append(pargs, shellEscape(fmt.Sprintf("http://%s/pprof/%s", listener.Addr(), profile)))
+	pcmd := strings.Join(pargs, " ")
+	fmt.Fprintf(env.Stdout, "Running: %s\n", pcmd)
+	c := exec.Command("sh", "-c", pcmd)
+	c.Stdin = os.Stdin
+	c.Stdout = env.Stdout
+	c.Stderr = env.Stderr
+	return c.Run()
+}
+
+func showPProfProfiles(ctx *context.T, env *cmdline.Env, name string) error {
+	v, err := pprof.PProfClient(name).Profiles(ctx)
+	if err != nil {
+		return err
+	}
+	v = append(v, "profile")
+	sort.Strings(v)
+	fmt.Fprintln(env.Stdout, "Available profiles:")
+	for _, p := range v {
+		fmt.Fprintf(env.Stdout, "  %s\n", p)
+	}
+	return nil
+}
+
+func shellEscape(s string) string {
+	if !strings.Contains(s, "'") {
+		return "'" + s + "'"
+	}
+	re := regexp.MustCompile("([\"$`\\\\])")
+	return `"` + re.ReplaceAllString(s, "\\$1") + `"`
+}
+
+var cmdPProfRunProxy = &cmdline.Command{
+	Runner:   v23cmd.RunnerFunc(runPProfProxy),
+	Name:     "proxy",
+	Short:    "Runs an http proxy to a pprof object.",
+	Long:     "Runs an http proxy to a pprof object.",
+	ArgsName: "<name>",
+	ArgsLong: `
+<name> is the name of the pprof object.
+`,
+}
+
+func runPProfProxy(ctx *context.T, env *cmdline.Env, args []string) error {
+	if want, got := 1, len(args); got != want {
+		return env.UsageErrorf("proxy: incorrect number of arguments, got %d, want %d", got, want)
+	}
+	name := args[0]
+	listener, err := pproflib.StartProxy(ctx, name)
+	if err != nil {
+		return err
+	}
+	defer listener.Close()
+
+	fmt.Fprintln(env.Stdout)
+	fmt.Fprintf(env.Stdout, "The pprof proxy is listening at http://%s/pprof\n", listener.Addr())
+	fmt.Fprintln(env.Stdout)
+	fmt.Fprintln(env.Stdout, "Hit CTRL-C to exit")
+
+	<-signals.ShutdownOnSignals(ctx)
+	return nil
+}
+
+var cmdRoot = &cmdline.Command{
+	Name:  "debug",
+	Short: "supports debugging Vanadium servers.",
+	Long:  "Command debug supports debugging Vanadium servers.",
+	Children: []*cmdline.Command{
+		cmdGlob,
+		cmdVtrace,
+		&cmdline.Command{
+			Name:     "logs",
+			Short:    "Accesses log files",
+			Long:     "Accesses log files",
+			Children: []*cmdline.Command{cmdLogsRead, cmdLogsSize},
+		},
+		&cmdline.Command{
+			Name:     "stats",
+			Short:    "Accesses stats",
+			Long:     "Accesses stats",
+			Children: []*cmdline.Command{cmdStatsRead, cmdStatsWatch},
+		},
+		&cmdline.Command{
+			Name:     "pprof",
+			Short:    "Accesses profiling data",
+			Long:     "Accesses profiling data",
+			Children: []*cmdline.Command{cmdPProfRun, cmdPProfRunProxy},
+		},
+	},
+}
diff --git a/services/debug/debug/v23_test.go b/services/debug/debug/v23_test.go
new file mode 100644
index 0000000..007a5fd
--- /dev/null
+++ b/services/debug/debug/v23_test.go
@@ -0,0 +1,58 @@
+// 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 main_test
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
+	"v.io/x/ref/test/v23tests"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	modules.DispatchAndExitIfChild()
+	cleanup := v23tests.UseSharedBinDir()
+	r := m.Run()
+	cleanup()
+	os.Exit(r)
+}
+
+func TestV23DebugGlob(t *testing.T) {
+	v23tests.RunTest(t, V23TestDebugGlob)
+}
+
+func TestV23DebugGlobLogs(t *testing.T) {
+	v23tests.RunTest(t, V23TestDebugGlobLogs)
+}
+
+func TestV23ReadHostname(t *testing.T) {
+	v23tests.RunTest(t, V23TestReadHostname)
+}
+
+func TestV23LogSize(t *testing.T) {
+	v23tests.RunTest(t, V23TestLogSize)
+}
+
+func TestV23StatsRead(t *testing.T) {
+	v23tests.RunTest(t, V23TestStatsRead)
+}
+
+func TestV23StatsWatch(t *testing.T) {
+	v23tests.RunTest(t, V23TestStatsWatch)
+}
+
+func TestV23VTrace(t *testing.T) {
+	v23tests.RunTest(t, V23TestVTrace)
+}
+
+func TestV23Pprof(t *testing.T) {
+	v23tests.RunTest(t, V23TestPprof)
+}
diff --git a/services/debug/debuglib/dispatcher.go b/services/debug/debuglib/dispatcher.go
new file mode 100644
index 0000000..7c3557a
--- /dev/null
+++ b/services/debug/debuglib/dispatcher.go
@@ -0,0 +1,66 @@
+// 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 debuglib implements debug server support.
+package debuglib
+
+import (
+	"strings"
+	"time"
+
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/x/ref/internal/logger"
+	"v.io/x/ref/services/internal/logreaderlib"
+	"v.io/x/ref/services/internal/pproflib"
+	"v.io/x/ref/services/internal/statslib"
+	"v.io/x/ref/services/internal/vtracelib"
+)
+
+// dispatcher holds the state of the debug dispatcher.
+type dispatcher struct {
+	auth security.Authorizer
+}
+
+var _ rpc.Dispatcher = (*dispatcher)(nil)
+
+func NewDispatcher(authorizer security.Authorizer) rpc.Dispatcher {
+	return &dispatcher{authorizer}
+}
+
+// The first part of the names of the objects served by this dispatcher.
+var rootName = "__debug"
+
+func (d *dispatcher) Lookup(ctx *context.T, suffix string) (interface{}, security.Authorizer, error) {
+	if suffix == "" {
+		return rpc.ChildrenGlobberInvoker(rootName), d.auth, nil
+	}
+	if !strings.HasPrefix(suffix, rootName) {
+		return nil, nil, nil
+	}
+	suffix = strings.TrimPrefix(suffix, rootName)
+	suffix = strings.TrimLeft(suffix, "/")
+
+	if suffix == "" {
+		return rpc.ChildrenGlobberInvoker("logs", "pprof", "stats", "vtrace"), d.auth, nil
+	}
+	parts := strings.SplitN(suffix, "/", 2)
+	if len(parts) == 2 {
+		suffix = parts[1]
+	} else {
+		suffix = ""
+	}
+	switch parts[0] {
+	case "logs":
+		return logreaderlib.NewLogFileService(logger.Manager(ctx).LogDir(), suffix), d.auth, nil
+	case "pprof":
+		return pproflib.NewPProfService(), d.auth, nil
+	case "stats":
+		return statslib.NewStatsService(suffix, 10*time.Second), d.auth, nil
+	case "vtrace":
+		return vtracelib.NewVtraceService(), d.auth, nil
+	}
+	return nil, d.auth, nil
+}
diff --git a/services/debug/debuglib/dispatcher_test.go b/services/debug/debuglib/dispatcher_test.go
new file mode 100644
index 0000000..1dcd3b6
--- /dev/null
+++ b/services/debug/debuglib/dispatcher_test.go
@@ -0,0 +1,256 @@
+// 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 debuglib
+
+import (
+	"io"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"reflect"
+	"sort"
+	"strings"
+	"testing"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/services/logreader"
+	"v.io/v23/services/stats"
+	s_vtrace "v.io/v23/services/vtrace"
+	"v.io/v23/vdl"
+	"v.io/v23/verror"
+	"v.io/v23/vtrace"
+	"v.io/x/lib/vlog"
+	libstats "v.io/x/ref/lib/stats"
+	"v.io/x/ref/lib/xrpc"
+	_ "v.io/x/ref/runtime/factories/generic"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/testutil"
+)
+
+//go:generate v23 test generate
+
+func TestDebugServer(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	tracedContext := func(ctx *context.T) *context.T {
+		ctx, _ = vtrace.WithNewTrace(ctx)
+		vtrace.ForceCollect(ctx)
+		return ctx
+	}
+	rootName = "debug"
+
+	workdir, err := ioutil.TempDir("", "logreadertest")
+	if err != nil {
+		t.Fatalf("ioutil.TempDir: %v", err)
+	}
+	defer os.RemoveAll(workdir)
+	if err = ioutil.WriteFile(filepath.Join(workdir, "test.INFO"), []byte("test"), os.FileMode(0644)); err != nil {
+		t.Fatalf("ioutil.WriteFile failed: %v", err)
+	}
+
+	// Use logger configured with the directory that we want to use for this test.
+	testLogger := vlog.NewLogger("TestDebugServer")
+	testLogger.Configure(vlog.LogDir(workdir))
+	ctx = context.WithLogger(ctx, testLogger)
+
+	disp := NewDispatcher(nil)
+	server, err := xrpc.NewDispatchingServer(ctx, "", disp)
+	if err != nil {
+		t.Fatalf("failed to start debug server: %v", err)
+	}
+	endpoint := server.Status().Endpoints[0].String()
+
+	// Access a logs directory that exists.
+	{
+		results, _, err := testutil.GlobName(ctx, naming.JoinAddressName(endpoint, "debug/logs"), "*")
+		if err != nil {
+			t.Errorf("Glob failed: %v", err)
+		}
+		if len(results) != 1 || results[0] != "test.INFO" {
+			t.Errorf("unexpected result. Got %v, want 'test.INFO'", results)
+		}
+	}
+
+	// Access a logs directory that doesn't exist.
+	{
+		results, _, err := testutil.GlobName(ctx, naming.JoinAddressName(endpoint, "debug/logs/nowheretobefound"), "*")
+		if len(results) != 0 {
+			t.Errorf("unexpected result. Got %v, want ''", results)
+		}
+		if err != nil {
+			t.Errorf("unexpected error value: %v", err)
+		}
+	}
+
+	// Access a log file that exists.
+	{
+		lf := logreader.LogFileClient(naming.JoinAddressName(endpoint, "debug/logs/test.INFO"))
+		size, err := lf.Size(tracedContext(ctx))
+		if err != nil {
+			t.Errorf("Size failed: %v", err)
+		}
+		if expected := int64(len("test")); size != expected {
+			t.Errorf("unexpected result. Got %v, want %v", size, expected)
+		}
+	}
+
+	// Access a log file that doesn't exist.
+	{
+		lf := logreader.LogFileClient(naming.JoinAddressName(endpoint, "debug/logs/nosuchfile.INFO"))
+		_, err = lf.Size(tracedContext(ctx))
+		if expected := verror.ErrNoExist.ID; verror.ErrorID(err) != expected {
+			t.Errorf("unexpected error value, got %v, want: %v", err, expected)
+		}
+	}
+
+	// Access a stats object that exists.
+	{
+		foo := libstats.NewInteger("testing/foo")
+		foo.Set(123)
+
+		st := stats.StatsClient(naming.JoinAddressName(endpoint, "debug/stats/testing/foo"))
+		v, err := st.Value(tracedContext(ctx))
+		if err != nil {
+			t.Errorf("Value failed: %v", err)
+		}
+		if want := vdl.Int64Value(123); !vdl.EqualValue(v, want) {
+			t.Errorf("unexpected result. got %v, want %v", v, want)
+		}
+	}
+
+	// Access a stats object that doesn't exists.
+	{
+		st := stats.StatsClient(naming.JoinAddressName(endpoint, "debug/stats/testing/nobodyhome"))
+		_, err = st.Value(tracedContext(ctx))
+		if expected := verror.ErrNoExist.ID; verror.ErrorID(err) != expected {
+			t.Errorf("unexpected error value, got %v, want: %v", err, expected)
+		}
+	}
+
+	// Access vtrace.
+	{
+		vt := s_vtrace.StoreClient(naming.JoinAddressName(endpoint, "debug/vtrace"))
+		call, err := vt.AllTraces(ctx)
+		if err != nil {
+			t.Errorf("AllTraces failed: %v", err)
+		}
+		ntraces := 0
+		stream := call.RecvStream()
+		for stream.Advance() {
+			stream.Value()
+			ntraces++
+		}
+		if err = stream.Err(); err != nil && err != io.EOF {
+			t.Fatalf("Unexpected error reading trace stream: %s", err)
+		}
+		if ntraces != 4 {
+			t.Errorf("We expected 4 traces, got: %d", ntraces)
+		}
+	}
+
+	// Glob from the root.
+	{
+		ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
+		defer cancel()
+
+		ns := v23.GetNamespace(ctx)
+		ns.SetRoots(naming.JoinAddressName(endpoint, "debug"))
+
+		c, err := ns.Glob(ctx, "logs/...")
+		if err != nil {
+			t.Errorf("ns.Glob failed: %v", err)
+		}
+		results := []string{}
+		for res := range c {
+			switch v := res.(type) {
+			case *naming.GlobReplyEntry:
+				results = append(results, v.Value.Name)
+			}
+		}
+		sort.Strings(results)
+		expected := []string{
+			"logs",
+			"logs/test.INFO",
+		}
+		if !reflect.DeepEqual(expected, results) {
+			t.Errorf("unexpected result. Got %v, want %v", results, expected)
+		}
+
+		c, err = ns.Glob(ctx, "stats/testing/*")
+		if err != nil {
+			t.Errorf("ns.Glob failed: %v", err)
+		}
+		results = []string{}
+		for res := range c {
+			t.Logf("got %v", res)
+			switch v := res.(type) {
+			case *naming.GlobReplyEntry:
+				results = append(results, v.Value.Name)
+			}
+		}
+		sort.Strings(results)
+		expected = []string{
+			"stats/testing/foo",
+		}
+		if !reflect.DeepEqual(expected, results) {
+			t.Errorf("unexpected result. Got %v, want %v", results, expected)
+		}
+
+		c, err = ns.Glob(ctx, "*")
+		if err != nil {
+			t.Errorf("ns.Glob failed: %v", err)
+		}
+		results = []string{}
+		for res := range c {
+			switch v := res.(type) {
+			case *naming.GlobReplyEntry:
+				results = append(results, v.Value.Name)
+			}
+		}
+		sort.Strings(results)
+		expected = []string{
+			"logs",
+			"pprof",
+			"stats",
+			"vtrace",
+		}
+		if !reflect.DeepEqual(expected, results) {
+			t.Errorf("unexpected result. Got %v, want %v", results, expected)
+		}
+
+		c, err = ns.Glob(ctx, "...")
+		if err != nil {
+			t.Errorf("ns.Glob failed: %v", err)
+		}
+		results = []string{}
+		for res := range c {
+			switch v := res.(type) {
+			case *naming.GlobReplyEntry:
+				if strings.HasPrefix(v.Value.Name, "stats/") && !strings.HasPrefix(v.Value.Name, "stats/testing/") {
+					// Skip any non-testing stats.
+					continue
+				}
+				results = append(results, v.Value.Name)
+			}
+		}
+		sort.Strings(results)
+		expected = []string{
+			"",
+			"logs",
+			"logs/test.INFO",
+			"pprof",
+			"stats",
+			"stats/testing/foo",
+			"vtrace",
+		}
+		if !reflect.DeepEqual(expected, results) {
+			t.Errorf("unexpected result. Got %v, want %v", results, expected)
+		}
+	}
+}
diff --git a/services/debug/debuglib/v23_internal_test.go b/services/debug/debuglib/v23_internal_test.go
new file mode 100644
index 0000000..77006f9
--- /dev/null
+++ b/services/debug/debuglib/v23_internal_test.go
@@ -0,0 +1,20 @@
+// 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 debuglib
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	os.Exit(m.Run())
+}
diff --git a/services/device/claimable/claimable_v23_test.go b/services/device/claimable/claimable_v23_test.go
new file mode 100644
index 0000000..ce8da3a
--- /dev/null
+++ b/services/device/claimable/claimable_v23_test.go
@@ -0,0 +1,116 @@
+// 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_test
+
+import (
+	"encoding/base64"
+	"encoding/json"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+
+	"v.io/v23/security"
+	lsecurity "v.io/x/ref/lib/security"
+	"v.io/x/ref/test/modules"
+	"v.io/x/ref/test/v23tests"
+)
+
+//go:generate v23 test generate
+
+func V23TestClaimableServer(t *v23tests.T) {
+	workdir, err := ioutil.TempDir("", "claimable-test-")
+	if err != nil {
+		t.Fatalf("ioutil.TempDir failed: %v", err)
+	}
+	defer os.RemoveAll(workdir)
+
+	permsDir := filepath.Join(workdir, "perms")
+
+	serverCreds, err := detachedCredentials(t, "server")
+	if err != nil {
+		t.Fatalf("Failed to create server credentials: %v", err)
+	}
+	legitClientCreds, err := t.Shell().NewChildCredentials("legit")
+	if err != nil {
+		t.Fatalf("Failed to create legit credentials: %v", err)
+	}
+	badClientCreds1, err := t.Shell().NewCustomCredentials()
+	if err != nil {
+		t.Fatalf("Failed to create bad credentials: %v", err)
+	}
+	badClientCreds2, err := t.Shell().NewChildCredentials("other-guy")
+	if err != nil {
+		t.Fatalf("Failed to create bad credentials: %v", err)
+	}
+
+	serverBin := t.BuildV23Pkg("v.io/x/ref/services/device/claimable")
+	serverBin = serverBin.WithStartOpts(serverBin.StartOpts().WithCustomCredentials(serverCreds))
+
+	server := serverBin.Start(
+		"--v23.tcp.address=127.0.0.1:0",
+		"--perms-dir="+permsDir,
+		"--blessing-root="+blessingRoots(t, legitClientCreds.Principal()),
+		"--v23.permissions.literal={\"Admin\":{\"In\":[\"root/legit\"]}}",
+	)
+	addr := server.ExpectVar("NAME")
+
+	clientBin := t.BuildV23Pkg("v.io/x/ref/services/device/device")
+
+	testcases := []struct {
+		creds      *modules.CustomCredentials
+		success    bool
+		permsExist bool
+	}{
+		{badClientCreds1, false, false},
+		{badClientCreds2, false, false},
+		{legitClientCreds, true, true},
+	}
+
+	for _, tc := range testcases {
+		clientBin = clientBin.WithStartOpts(clientBin.StartOpts().WithCustomCredentials(tc.creds))
+		client := clientBin.Start("claim", addr, "my-device")
+		if err := client.Wait(nil, nil); (err == nil) != tc.success {
+			t.Errorf("Unexpected exit value. Expected success=%v, got err=%v", tc.success, err)
+		}
+		if _, err := os.Stat(permsDir); (err == nil) != tc.permsExist {
+			t.Errorf("Unexpected permsDir state. Got %v, expected %v", err == nil, tc.permsExist)
+		}
+	}
+	// Server should exit cleanly after the successful Claim.
+	if err := server.ExpectEOF(); err != nil {
+		t.Errorf("Expected server to exit cleanly, got %v", err)
+	}
+}
+
+func detachedCredentials(t *v23tests.T, name string) (*modules.CustomCredentials, error) {
+	creds, err := t.Shell().NewCustomCredentials()
+	if err != nil {
+		return nil, err
+	}
+	return creds, lsecurity.InitDefaultBlessings(creds.Principal(), name)
+}
+
+func blessingRoots(t *v23tests.T, p security.Principal) string {
+	pk, ok := p.Roots().Dump()["root"]
+	if !ok || len(pk) == 0 {
+		t.Fatalf("Failed to find root blessing")
+	}
+	der, err := pk[0].MarshalBinary()
+	if err != nil {
+		t.Fatalf("MarshalPublicKey failed: %v", err)
+	}
+	rootInfo := struct {
+		Names     []string `json:"names"`
+		PublicKey string   `json:"publicKey"`
+	}{
+		Names:     []string{"root"},
+		PublicKey: base64.URLEncoding.EncodeToString(der),
+	}
+	out, err := json.Marshal(rootInfo)
+	if err != nil {
+		t.Fatalf("json.Marshal failed: %v", err)
+	}
+	return string(out)
+}
diff --git a/services/device/claimable/doc.go b/services/device/claimable/doc.go
new file mode 100644
index 0000000..dcb6397
--- /dev/null
+++ b/services/device/claimable/doc.go
@@ -0,0 +1,74 @@
+// 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
+
+/*
+Claimable is a server that implements the Claimable interface from
+v.io/v23/services/device. It exits immediately if the device is already claimed.
+Otherwise, it keeps running until a successful Claim() request is received.
+
+It uses -v23.permissions.* to authorize the Claim request.
+
+Usage:
+   claimable [flags]
+
+The claimable flags are:
+ -blessing-root=
+   The blessing root to trust, JSON-encoded, e.g. from
+   https://v.io/auth/blessing-root
+ -perms-dir=
+   The directory where permissions will be stored.
+
+The global flags are:
+ -alsologtostderr=true
+   log to standard error as well as files
+ -log_backtrace_at=:0
+   when logging hits line file:N, emit a stack trace
+ -log_dir=
+   if non-empty, write log files to this directory
+ -logtostderr=false
+   log to standard error instead of files
+ -max_stack_buf_size=4292608
+   max size in bytes of the buffer to use for logging stack traces
+ -metadata=<just specify -metadata to activate>
+   Displays metadata for the program and exits.
+ -stderrthreshold=2
+   logs at or above this threshold go to stderr
+ -v=0
+   log level for V logs
+ -v23.credentials=
+   directory to use for storing security credentials
+ -v23.i18n-catalogue=
+   18n catalogue files to load, comma separated
+ -v23.namespace.root=[/(dev.v.io/role/vprod/service/mounttabled)@ns.dev.v.io:8101]
+   local namespace root; can be repeated to provided multiple roots
+ -v23.permissions.file=map[]
+   specify a perms file as <name>:<permsfile>
+ -v23.permissions.literal=
+   explicitly specify the runtime perms as a JSON-encoded access.Permissions.
+   Overrides all --v23.permissions.file flags.
+ -v23.proxy=
+   object name of proxy service to use to export services across network
+   boundaries
+ -v23.tcp.address=
+   address to listen on
+ -v23.tcp.protocol=wsh
+   protocol to listen with
+ -v23.vtrace.cache-size=1024
+   The number of vtrace traces to store in memory.
+ -v23.vtrace.collect-regexp=
+   Spans and annotations that match this regular expression will trigger trace
+   collection.
+ -v23.vtrace.dump-on-shutdown=true
+   If true, dump all stored traces on runtime shutdown.
+ -v23.vtrace.sample-rate=0
+   Rate (from 0.0 to 1.0) to sample vtrace traces.
+ -vmodule=
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
+*/
+package main
diff --git a/services/device/claimable/main.go b/services/device/claimable/main.go
new file mode 100644
index 0000000..0fd53e4
--- /dev/null
+++ b/services/device/claimable/main.go
@@ -0,0 +1,106 @@
+// 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.
+
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go . -help
+
+package main
+
+import (
+	"encoding/base64"
+	"encoding/json"
+	"errors"
+	"fmt"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/security"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/security/securityflag"
+	"v.io/x/ref/lib/signals"
+	"v.io/x/ref/lib/v23cmd"
+	_ "v.io/x/ref/runtime/factories/roaming"
+	"v.io/x/ref/services/device/internal/claim"
+	"v.io/x/ref/services/identity"
+)
+
+var (
+	permsDir     string
+	blessingRoot string
+)
+
+func runServer(ctx *context.T, _ *cmdline.Env, _ []string) error {
+	if blessingRoot != "" {
+		addRoot(ctx, blessingRoot)
+	}
+
+	auth := securityflag.NewAuthorizerOrDie()
+	claimable, claimed := claim.NewClaimableDispatcher(ctx, permsDir, "", auth)
+	if claimable == nil {
+		return errors.New("device is already claimed")
+	}
+
+	server, err := v23.NewServer(ctx)
+	if err != nil {
+		return err
+	}
+	if _, err := server.Listen(v23.GetListenSpec(ctx)); err != nil {
+		return err
+	}
+	if err := server.ServeDispatcher("", claimable); err != nil {
+		return err
+	}
+
+	status := server.Status()
+	ctx.Infof("Listening on: %v", status.Endpoints)
+	if len(status.Endpoints) > 0 {
+		fmt.Printf("NAME=%s\n", status.Endpoints[0].Name())
+	}
+	select {
+	case <-claimed:
+		return nil
+	case s := <-signals.ShutdownOnSignals(ctx):
+		return fmt.Errorf("received signal %v", s)
+	}
+}
+
+func addRoot(ctx *context.T, jRoot string) {
+	var bRoot identity.BlessingRootResponse
+	if err := json.Unmarshal([]byte(jRoot), &bRoot); err != nil {
+		ctx.Fatalf("unable to unmarshal the json blessing root: %v", err)
+	}
+	decodedKey, err := base64.URLEncoding.DecodeString(bRoot.PublicKey)
+	if err != nil {
+		ctx.Fatalf("unable to decode public key: %v", err)
+	}
+	key, err := security.UnmarshalPublicKey(decodedKey)
+	if err != nil {
+		ctx.Fatalf("unable to unmarshal the public key: %v", err)
+	}
+	roots := v23.GetPrincipal(ctx).Roots()
+	for _, name := range bRoot.Names {
+		if err := roots.Add(key, security.BlessingPattern(name)); err != nil {
+			ctx.Fatalf("unable to add root: %v", err)
+		}
+	}
+}
+
+func main() {
+	rootCmd := &cmdline.Command{
+		Name:  "claimable",
+		Short: "Run claimable server",
+		Long: `
+Claimable is a server that implements the Claimable interface from
+v.io/v23/services/device. It exits immediately if the device is already
+claimed. Otherwise, it keeps running until a successful Claim() request
+is received.
+
+It uses -v23.permissions.* to authorize the Claim request.
+`,
+		Runner: v23cmd.RunnerFunc(runServer),
+	}
+	rootCmd.Flags.StringVar(&permsDir, "perms-dir", "", "The directory where permissions will be stored.")
+	rootCmd.Flags.StringVar(&blessingRoot, "blessing-root", "", "The blessing root to trust, JSON-encoded, e.g. from https://v.io/auth/blessing-root")
+	cmdline.Main(rootCmd)
+}
diff --git a/services/device/claimable/v23_test.go b/services/device/claimable/v23_test.go
new file mode 100644
index 0000000..ae373a9
--- /dev/null
+++ b/services/device/claimable/v23_test.go
@@ -0,0 +1,30 @@
+// 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 main_test
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
+	"v.io/x/ref/test/v23tests"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	modules.DispatchAndExitIfChild()
+	cleanup := v23tests.UseSharedBinDir()
+	r := m.Run()
+	cleanup()
+	os.Exit(r)
+}
+
+func TestV23ClaimableServer(t *testing.T) {
+	v23tests.RunTest(t, V23TestClaimableServer)
+}
diff --git a/services/device/config.vdl b/services/device/config.vdl
new file mode 100644
index 0000000..459fccf
--- /dev/null
+++ b/services/device/config.vdl
@@ -0,0 +1,11 @@
+// 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 device
+
+// Config is an RPC API to the config service.
+type Config interface {
+	// Set sets the value for key.
+	Set(key, value string) error
+}
diff --git a/services/device/config.vdl.go b/services/device/config.vdl.go
new file mode 100644
index 0000000..63e522b
--- /dev/null
+++ b/services/device/config.vdl.go
@@ -0,0 +1,120 @@
+// 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 by the vanadium vdl tool.
+// Source: config.vdl
+
+package device
+
+import (
+	// VDL system imports
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+)
+
+// ConfigClientMethods is the client interface
+// containing Config methods.
+//
+// Config is an RPC API to the config service.
+type ConfigClientMethods interface {
+	// Set sets the value for key.
+	Set(ctx *context.T, key string, value string, opts ...rpc.CallOpt) error
+}
+
+// ConfigClientStub adds universal methods to ConfigClientMethods.
+type ConfigClientStub interface {
+	ConfigClientMethods
+	rpc.UniversalServiceMethods
+}
+
+// ConfigClient returns a client stub for Config.
+func ConfigClient(name string) ConfigClientStub {
+	return implConfigClientStub{name}
+}
+
+type implConfigClientStub struct {
+	name string
+}
+
+func (c implConfigClientStub) Set(ctx *context.T, i0 string, i1 string, opts ...rpc.CallOpt) (err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "Set", []interface{}{i0, i1}, nil, opts...)
+	return
+}
+
+// ConfigServerMethods is the interface a server writer
+// implements for Config.
+//
+// Config is an RPC API to the config service.
+type ConfigServerMethods interface {
+	// Set sets the value for key.
+	Set(ctx *context.T, call rpc.ServerCall, key string, value string) error
+}
+
+// ConfigServerStubMethods is the server interface containing
+// Config methods, as expected by rpc.Server.
+// There is no difference between this interface and ConfigServerMethods
+// since there are no streaming methods.
+type ConfigServerStubMethods ConfigServerMethods
+
+// ConfigServerStub adds universal methods to ConfigServerStubMethods.
+type ConfigServerStub interface {
+	ConfigServerStubMethods
+	// Describe the Config interfaces.
+	Describe__() []rpc.InterfaceDesc
+}
+
+// ConfigServer returns a server stub for Config.
+// It converts an implementation of ConfigServerMethods into
+// an object that may be used by rpc.Server.
+func ConfigServer(impl ConfigServerMethods) ConfigServerStub {
+	stub := implConfigServerStub{
+		impl: impl,
+	}
+	// Initialize GlobState; always check the stub itself first, to handle the
+	// case where the user has the Glob method defined in their VDL source.
+	if gs := rpc.NewGlobState(stub); gs != nil {
+		stub.gs = gs
+	} else if gs := rpc.NewGlobState(impl); gs != nil {
+		stub.gs = gs
+	}
+	return stub
+}
+
+type implConfigServerStub struct {
+	impl ConfigServerMethods
+	gs   *rpc.GlobState
+}
+
+func (s implConfigServerStub) Set(ctx *context.T, call rpc.ServerCall, i0 string, i1 string) error {
+	return s.impl.Set(ctx, call, i0, i1)
+}
+
+func (s implConfigServerStub) Globber() *rpc.GlobState {
+	return s.gs
+}
+
+func (s implConfigServerStub) Describe__() []rpc.InterfaceDesc {
+	return []rpc.InterfaceDesc{ConfigDesc}
+}
+
+// ConfigDesc describes the Config interface.
+var ConfigDesc rpc.InterfaceDesc = descConfig
+
+// descConfig hides the desc to keep godoc clean.
+var descConfig = rpc.InterfaceDesc{
+	Name:    "Config",
+	PkgPath: "v.io/x/ref/services/device",
+	Doc:     "// Config is an RPC API to the config service.",
+	Methods: []rpc.MethodDesc{
+		{
+			Name: "Set",
+			Doc:  "// Set sets the value for key.",
+			InArgs: []rpc.ArgDesc{
+				{"key", ``},   // string
+				{"value", ``}, // string
+			},
+		},
+	},
+}
diff --git a/services/device/device/acl.go b/services/device/device/acl.go
new file mode 100644
index 0000000..3c9973e
--- /dev/null
+++ b/services/device/device/acl.go
@@ -0,0 +1,146 @@
+// 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
+
+// Commands to get/set Permissions.
+
+import (
+	"fmt"
+
+	"v.io/v23/context"
+	"v.io/v23/security"
+	"v.io/v23/security/access"
+	"v.io/v23/services/device"
+	"v.io/v23/verror"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/v23cmd"
+)
+
+var cmdGet = &cmdline.Command{
+	Runner:   v23cmd.RunnerFunc(runGet),
+	Name:     "get",
+	Short:    "Get Permissions for the given target.",
+	Long:     "Get Permissions for the given target.",
+	ArgsName: "<device manager name>",
+	ArgsLong: `
+<device manager name> can be a Vanadium name for a device manager,
+application installation or instance.`,
+}
+
+func runGet(ctx *context.T, env *cmdline.Env, args []string) error {
+	if expected, got := 1, len(args); expected != got {
+		return env.UsageErrorf("get: incorrect number of arguments, expected %d, got %d", expected, got)
+	}
+
+	vanaName := args[0]
+	objPerms, _, err := device.ApplicationClient(vanaName).GetPermissions(ctx)
+	if err != nil {
+		return fmt.Errorf("GetPermissions on %s failed: %v", vanaName, err)
+	}
+	// Convert objPerms (Permissions) into permsEntries for pretty printing.
+	entries := make(permsEntries)
+	for tag, perms := range objPerms {
+		for _, p := range perms.In {
+			entries.Tags(string(p))[tag] = false
+		}
+		for _, b := range perms.NotIn {
+			entries.Tags(b)[tag] = true
+		}
+	}
+	fmt.Fprintln(env.Stdout, entries)
+	return nil
+}
+
+// TODO(caprita): Add unit test logic for 'force set'.
+var forceSet bool
+
+var cmdSet = &cmdline.Command{
+	Runner:   v23cmd.RunnerFunc(runSet),
+	Name:     "set",
+	Short:    "Set Permissions for the given target.",
+	Long:     "Set Permissions for the given target",
+	ArgsName: "<device manager name>  (<blessing> [!]<tag>(,[!]<tag>)*",
+	ArgsLong: `
+<device manager name> can be a Vanadium name for a device manager,
+application installation or instance.
+
+<blessing> is a blessing pattern.
+If the same pattern is repeated multiple times in the command, then
+the only the last occurrence will be honored.
+
+<tag> is a subset of defined access types ("Admin", "Read", "Write" etc.).
+If the access right is prefixed with a '!' then <blessing> is added to the
+NotIn list for that right. Using "^" as a "tag" causes all occurrences of
+<blessing> in the current AccessList to be cleared.
+
+Examples:
+set root/self ^
+will remove "root/self" from the In and NotIn lists for all access rights.
+
+set root/self Read,!Write
+will add "root/self" to the In list for Read access and the NotIn list
+for Write access (and remove "root/self" from both the In and NotIn
+lists of all other access rights)`,
+}
+
+func init() {
+	cmdSet.Flags.BoolVar(&forceSet, "f", false, "Instead of making the AccessLists additive, do a complete replacement based on the specified settings.")
+}
+
+func runSet(ctx *context.T, env *cmdline.Env, args []string) error {
+	if got := len(args); !((got%2) == 1 && got >= 3) {
+		return env.UsageErrorf("set: incorrect number of arguments %d, must be 1 + 2n", got)
+	}
+
+	vanaName := args[0]
+	pairs := args[1:]
+
+	entries := make(permsEntries)
+	for i := 0; i < len(pairs); i += 2 {
+		blessing := pairs[i]
+		tags, err := parseAccessTags(pairs[i+1])
+		if err != nil {
+			return env.UsageErrorf("failed to parse access tags for %q: %v", blessing, err)
+		}
+		entries[blessing] = tags
+	}
+
+	// Set the Permissions on the specified names.
+	for {
+		objPerms, version := make(access.Permissions), ""
+		if !forceSet {
+			var err error
+			if objPerms, version, err = device.ApplicationClient(vanaName).GetPermissions(ctx); err != nil {
+				return fmt.Errorf("GetPermissions(%s) failed: %v", vanaName, err)
+			}
+		}
+		for blessingOrPattern, tags := range entries {
+			objPerms.Clear(blessingOrPattern) // Clear out any existing references
+			for tag, blacklist := range tags {
+				if blacklist {
+					objPerms.Blacklist(blessingOrPattern, tag)
+				} else {
+					objPerms.Add(security.BlessingPattern(blessingOrPattern), tag)
+				}
+			}
+		}
+		switch err := device.ApplicationClient(vanaName).SetPermissions(ctx, objPerms, version); {
+		case err != nil && verror.ErrorID(err) != verror.ErrBadVersion.ID:
+			return fmt.Errorf("SetPermissions(%s) failed: %v", vanaName, err)
+		case err == nil:
+			return nil
+		}
+		fmt.Fprintln(env.Stderr, "WARNING: trying again because of asynchronous change")
+	}
+}
+
+var cmdACL = &cmdline.Command{
+	Name:  "acl",
+	Short: "Tool for setting device manager Permissions",
+	Long: `
+The acl tool manages Permissions on the device manger, installations and instances.
+`,
+	Children: []*cmdline.Command{cmdGet, cmdSet},
+}
diff --git a/services/device/device/acl_fmt.go b/services/device/device/acl_fmt.go
new file mode 100644
index 0000000..4089fa7
--- /dev/null
+++ b/services/device/device/acl_fmt.go
@@ -0,0 +1,86 @@
+// 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 (
+	"fmt"
+	"sort"
+	"strings"
+
+	"v.io/v23/security"
+	"v.io/x/lib/set"
+)
+
+// permsEntries maps blessing patterns to the kind of access they should have.
+type permsEntries map[string]accessTags
+
+// accessTags maps access tags to whether they should be blacklisted
+// (i.e., part of the NotIn list) or not (part of the In list).
+//
+// TODO(ashankar,caprita): This structure is not friendly to a blessing
+// appearing in both the "In" and "NotIn" lists of an AccessList. Arguably, such
+// an AccessList is silly (In: ["foo"], NotIn: ["foo"]), but is legal. This
+// structure can end up hiding that.
+type accessTags map[string]bool
+
+// String representation of access tags.  Between String and parseAccessTags,
+// the "get" and "set" commands are able to speak the same language: the output
+// of "get" and be copied/pasted into "set".
+func (tags accessTags) String() string {
+	// Sort tags and then apply "!".
+	list := set.StringBool.ToSlice(tags)
+	sort.Strings(list)
+	for ix, tag := range list {
+		if tags[tag] {
+			list[ix] = "!" + list[ix]
+		}
+	}
+	return strings.Join(list, ",")
+}
+
+func parseAccessTags(input string) (accessTags, error) {
+	ret := make(accessTags)
+	if input == "^" {
+		return ret, nil
+	}
+	for _, tag := range strings.Split(input, ",") {
+		blacklist := strings.HasPrefix(tag, "!")
+		if blacklist {
+			tag = tag[1:]
+		}
+		if len(tag) == 0 {
+			return nil, fmt.Errorf("empty access tag in %q", input)
+		}
+		ret[tag] = blacklist
+	}
+	return ret, nil
+}
+
+func (entries permsEntries) String() string {
+	var list []string
+	for pattern, _ := range entries {
+		list = append(list, pattern)
+	}
+	sort.Strings(list)
+	for ix, pattern := range list {
+		list[ix] = fmt.Sprintf("%s %v", pattern, entries[pattern])
+	}
+	return strings.Join(list, "\n")
+}
+
+func (entries permsEntries) Tags(pattern string) accessTags {
+	tags, exists := entries[pattern]
+	if !exists {
+		tags = make(accessTags)
+		entries[pattern] = tags
+	}
+	return tags
+}
+
+type byPattern []security.BlessingPattern
+
+func (a byPattern) Len() int           { return len(a) }
+func (a byPattern) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
+func (a byPattern) Less(i, j int) bool { return a[i] < a[j] }
diff --git a/services/device/device/acl_test.go b/services/device/device/acl_test.go
new file mode 100644
index 0000000..0deb899
--- /dev/null
+++ b/services/device/device/acl_test.go
@@ -0,0 +1,302 @@
+// 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_test
+
+import (
+	"bytes"
+	"reflect"
+	"regexp"
+	"strings"
+	"testing"
+
+	"v.io/v23/security"
+	"v.io/v23/security/access"
+	"v.io/v23/verror"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/v23cmd"
+	"v.io/x/ref/lib/xrpc"
+	"v.io/x/ref/test"
+
+	cmd_device "v.io/x/ref/services/device/device"
+	"v.io/x/ref/services/internal/servicetest"
+)
+
+const pkgPath = "v.io/x/ref/services/device/main"
+
+var (
+	errOops = verror.Register(pkgPath+".errOops", verror.NoRetry, "oops!")
+)
+
+func TestAccessListGetCommand(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	tapes := servicetest.NewTapeMap()
+	server, err := xrpc.NewDispatchingServer(ctx, "", newDispatcher(t, tapes))
+	if err != nil {
+		t.Fatalf("NewServer failed: %v", err)
+	}
+
+	// Setup the command-line.
+	cmd := cmd_device.CmdRoot
+	var stdout, stderr bytes.Buffer
+	env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
+	deviceName := server.Status().Endpoints[0].Name()
+
+	// Test the 'get' command.
+	rootTape := tapes.ForSuffix("")
+	rootTape.SetResponses(GetPermissionsResponse{
+		perms: access.Permissions{
+			"Admin": access.AccessList{
+				In:    []security.BlessingPattern{"self"},
+				NotIn: []string{"self/bad"},
+			},
+			"Read": access.AccessList{
+				In: []security.BlessingPattern{"other", "self"},
+			},
+		},
+		version: "aVersionForToday",
+		err:     nil,
+	})
+
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"acl", "get", deviceName}); err != nil {
+		t.Fatalf("error: %v", err)
+	}
+	if expected, got := strings.TrimSpace(`
+other Read
+self Admin,Read
+self/bad !Admin
+`), strings.TrimSpace(stdout.String()); got != expected {
+		t.Errorf("Unexpected output from get. Got %q, expected %q", got, expected)
+	}
+	if got, expected := rootTape.Play(), []interface{}{"GetPermissions"}; !reflect.DeepEqual(expected, got) {
+		t.Errorf("invalid call sequence. Got %#v, want %#v", got, expected)
+	}
+}
+
+func TestAccessListSetCommand(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	tapes := servicetest.NewTapeMap()
+	server, err := xrpc.NewDispatchingServer(ctx, "", newDispatcher(t, tapes))
+	if err != nil {
+		t.Fatalf("NewServer failed: %v", err)
+	}
+
+	// Setup the command-line.
+	cmd := cmd_device.CmdRoot
+	var stdout, stderr bytes.Buffer
+	env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
+	deviceName := server.Status().Endpoints[0].Name()
+
+	// Some tests to validate parse.
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"acl", "set", deviceName}); err == nil {
+		t.Fatalf("failed to correctly detect insufficient parameters")
+	}
+	if expected, got := "ERROR: set: incorrect number of arguments 1, must be 1 + 2n", strings.TrimSpace(stderr.String()); !strings.HasPrefix(got, expected) {
+		t.Fatalf("Unexpected output from list. Got %q, expected prefix %q", got, expected)
+	}
+
+	stderr.Reset()
+	stdout.Reset()
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"acl", "set", deviceName, "foo"}); err == nil {
+		t.Fatalf("failed to correctly detect insufficient parameters")
+	}
+	if expected, got := "ERROR: set: incorrect number of arguments 2, must be 1 + 2n", strings.TrimSpace(stderr.String()); !strings.HasPrefix(got, expected) {
+		t.Fatalf("Unexpected output from list. Got %q, expected prefix %q", got, expected)
+	}
+
+	stderr.Reset()
+	stdout.Reset()
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"acl", "set", deviceName, "foo", "bar", "ohno"}); err == nil {
+		t.Fatalf("failed to correctly detect insufficient parameters")
+	}
+	if expected, got := "ERROR: set: incorrect number of arguments 4, must be 1 + 2n", strings.TrimSpace(stderr.String()); !strings.HasPrefix(got, expected) {
+		t.Fatalf("Unexpected output from list. Got %q, expected prefix %q", got, expected)
+	}
+
+	stderr.Reset()
+	stdout.Reset()
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"acl", "set", deviceName, "foo", "!"}); err == nil {
+		t.Fatalf("failed to detect invalid parameter")
+	}
+	if expected, got := "ERROR: failed to parse access tags for \"foo\": empty access tag", strings.TrimSpace(stderr.String()); !strings.HasPrefix(got, expected) {
+		t.Errorf("Unexpected output from list. Got %q, expected prefix %q", got, expected)
+	}
+
+	// Correct operation in the absence of errors.
+	stderr.Reset()
+	stdout.Reset()
+	rootTape := tapes.ForSuffix("")
+	rootTape.SetResponses(
+		GetPermissionsResponse{
+			perms: access.Permissions{
+				"Admin": access.AccessList{
+					In: []security.BlessingPattern{"self"},
+				},
+				"Read": access.AccessList{
+					In:    []security.BlessingPattern{"other", "self"},
+					NotIn: []string{"other/bob"},
+				},
+			},
+			version: "aVersionForToday",
+			err:     nil,
+		},
+		verror.NewErrBadVersion(nil),
+		GetPermissionsResponse{
+			perms: access.Permissions{
+				"Admin": access.AccessList{
+					In: []security.BlessingPattern{"self"},
+				},
+				"Read": access.AccessList{
+					In:    []security.BlessingPattern{"other", "self"},
+					NotIn: []string{"other/bob/baddevice"},
+				},
+			},
+			version: "aVersionForTomorrow",
+			err:     nil,
+		},
+		nil,
+	)
+
+	// set command that:
+	// - Adds entry for "friends" to "Write" & "Admin"
+	// - Adds a blacklist entry for "friend/alice"  for "Admin"
+	// - Edits existing entry for "self" (adding "Write" access)
+	// - Removes entry for "other/bob/baddevice"
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{
+		"acl",
+		"set",
+		deviceName,
+		"friends", "Admin,Write",
+		"friends/alice", "!Admin,Write",
+		"self", "Admin,Write,Read",
+		"other/bob/baddevice", "^",
+	}); err != nil {
+		t.Fatalf("SetPermissions failed: %v", err)
+	}
+
+	if expected, got := "", strings.TrimSpace(stdout.String()); got != expected {
+		t.Fatalf("Unexpected output from list. Got %q, expected %q", got, expected)
+	}
+	if expected, got := "WARNING: trying again because of asynchronous change", strings.TrimSpace(stderr.String()); got != expected {
+		t.Fatalf("Unexpected output from list. Got %q, expected %q", got, expected)
+	}
+	expected := []interface{}{
+		"GetPermissions",
+		SetPermissionsStimulus{
+			fun: "SetPermissions",
+			perms: access.Permissions{
+				"Admin": access.AccessList{
+					In:    []security.BlessingPattern{"friends", "self"},
+					NotIn: []string{"friends/alice"},
+				},
+				"Read": access.AccessList{
+					In:    []security.BlessingPattern{"other", "self"},
+					NotIn: []string{"other/bob"},
+				},
+				"Write": access.AccessList{
+					In:    []security.BlessingPattern{"friends", "friends/alice", "self"},
+					NotIn: []string(nil),
+				},
+			},
+			version: "aVersionForToday",
+		},
+		"GetPermissions",
+		SetPermissionsStimulus{
+			fun: "SetPermissions",
+			perms: access.Permissions{
+				"Admin": access.AccessList{
+					In:    []security.BlessingPattern{"friends", "self"},
+					NotIn: []string{"friends/alice"},
+				},
+				"Read": access.AccessList{
+					In:    []security.BlessingPattern{"other", "self"},
+					NotIn: []string(nil),
+				},
+				"Write": access.AccessList{
+					In:    []security.BlessingPattern{"friends", "friends/alice", "self"},
+					NotIn: []string(nil),
+				},
+			},
+			version: "aVersionForTomorrow",
+		},
+	}
+
+	if got := rootTape.Play(); !reflect.DeepEqual(expected, got) {
+		t.Errorf("invalid call sequence. Got %#v, want %#v", got, expected)
+	}
+	rootTape.Rewind()
+	stdout.Reset()
+	stderr.Reset()
+
+	// GetPermissions fails.
+	rootTape.SetResponses(GetPermissionsResponse{
+		perms:   access.Permissions{},
+		version: "aVersionForToday",
+		err:     verror.New(errOops, nil),
+	})
+
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"acl", "set", deviceName, "vana/bad", "Read"}); err == nil {
+		t.Fatalf("GetPermissions RPC inside perms set command failed but error wrongly not detected")
+	} else if expected, got := `^GetPermissions\(`+deviceName+`\) failed:.*oops!`, err.Error(); !regexp.MustCompile(expected).MatchString(got) {
+		t.Fatalf("Unexpected output from list. Got %q, regexp %q", got, expected)
+	}
+	if expected, got := "", strings.TrimSpace(stdout.String()); got != expected {
+		t.Fatalf("Unexpected output from list. Got %q, expected %q", got, expected)
+	}
+	expected = []interface{}{
+		"GetPermissions",
+	}
+
+	if got := rootTape.Play(); !reflect.DeepEqual(expected, got) {
+		t.Errorf("invalid call sequence. Got %#v, want %#v", got, expected)
+	}
+	rootTape.Rewind()
+	stdout.Reset()
+	stderr.Reset()
+
+	// SetPermissions fails with something other than a bad version failure.
+	rootTape.SetResponses(
+		GetPermissionsResponse{
+			perms: access.Permissions{
+				"Read": access.AccessList{
+					In: []security.BlessingPattern{"other", "self"},
+				},
+			},
+			version: "aVersionForToday",
+			err:     nil,
+		},
+		verror.New(errOops, nil),
+	)
+
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"acl", "set", deviceName, "friend", "Read"}); err == nil {
+		t.Fatalf("SetPermissions should have failed: %v", err)
+	} else if expected, got := `^SetPermissions\(`+deviceName+`\) failed:.*oops!`, err.Error(); !regexp.MustCompile(expected).MatchString(got) {
+		t.Fatalf("Unexpected output from list. Got %q, regexp %q", got, expected)
+	}
+	if expected, got := "", strings.TrimSpace(stdout.String()); got != expected {
+		t.Fatalf("Unexpected output from list. Got %q, expected %q", got, expected)
+	}
+
+	expected = []interface{}{
+		"GetPermissions",
+		SetPermissionsStimulus{
+			fun: "SetPermissions",
+			perms: access.Permissions{
+				"Read": access.AccessList{
+					In:    []security.BlessingPattern{"friend", "other", "self"},
+					NotIn: []string(nil),
+				},
+			},
+			version: "aVersionForToday",
+		},
+	}
+
+	if got := rootTape.Play(); !reflect.DeepEqual(expected, got) {
+		t.Errorf("invalid call sequence. Got %#v, want %#v", got, expected)
+	}
+}
diff --git a/services/device/device/associate.go b/services/device/device/associate.go
new file mode 100644
index 0000000..a22d394
--- /dev/null
+++ b/services/device/device/associate.go
@@ -0,0 +1,93 @@
+// 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 (
+	"fmt"
+	"time"
+
+	"v.io/v23/context"
+	"v.io/v23/services/device"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/v23cmd"
+)
+
+var cmdList = &cmdline.Command{
+	Runner:   v23cmd.RunnerFunc(runList),
+	Name:     "list",
+	Short:    "Lists the account associations.",
+	Long:     "Lists all account associations.",
+	ArgsName: "<devicemanager>.",
+	ArgsLong: `
+<devicemanager> is the name of the device manager to connect to.`,
+}
+
+func runList(ctx *context.T, env *cmdline.Env, args []string) error {
+	if expected, got := 1, len(args); expected != got {
+		return env.UsageErrorf("list: incorrect number of arguments, expected %d, got %d", expected, got)
+	}
+
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
+	defer cancel()
+	assocs, err := device.DeviceClient(args[0]).ListAssociations(ctx)
+	if err != nil {
+		return fmt.Errorf("ListAssociations failed: %v", err)
+	}
+
+	for _, a := range assocs {
+		fmt.Fprintf(env.Stdout, "%s %s\n", a.IdentityName, a.AccountName)
+	}
+	return nil
+}
+
+var cmdAdd = &cmdline.Command{
+	Runner:   v23cmd.RunnerFunc(runAdd),
+	Name:     "add",
+	Short:    "Add the listed blessings with the specified system account.",
+	Long:     "Add the listed blessings with the specified system account.",
+	ArgsName: "<devicemanager> <systemName> <blessing>...",
+	ArgsLong: `
+<devicemanager> is the name of the device manager to connect to.
+<systemName> is the name of an account holder on the local system.
+<blessing>.. are the blessings to associate systemAccount with.`,
+}
+
+func runAdd(ctx *context.T, env *cmdline.Env, args []string) error {
+	if expected, got := 3, len(args); got < expected {
+		return env.UsageErrorf("add: incorrect number of arguments, expected at least %d, got %d", expected, got)
+	}
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
+	defer cancel()
+	return device.DeviceClient(args[0]).AssociateAccount(ctx, args[2:], args[1])
+}
+
+var cmdRemove = &cmdline.Command{
+	Runner:   v23cmd.RunnerFunc(runRemove),
+	Name:     "remove",
+	Short:    "Removes system accounts associated with the listed blessings.",
+	Long:     "Removes system accounts associated with the listed blessings.",
+	ArgsName: "<devicemanager>  <blessing>...",
+	ArgsLong: `
+<devicemanager> is the name of the device manager to connect to.
+<blessing>... is a list of blessings.`,
+}
+
+func runRemove(ctx *context.T, env *cmdline.Env, args []string) error {
+	if expected, got := 2, len(args); got < expected {
+		return env.UsageErrorf("remove: incorrect number of arguments, expected at least %d, got %d", expected, got)
+	}
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
+	defer cancel()
+	return device.DeviceClient(args[0]).AssociateAccount(ctx, args[1:], "")
+}
+
+var cmdAssociate = &cmdline.Command{
+	Name:  "associate",
+	Short: "Tool for creating associations between Vanadium blessings and a system account",
+	Long: `
+The associate tool facilitates managing blessing to system account associations.
+`,
+	Children: []*cmdline.Command{cmdList, cmdAdd, cmdRemove},
+}
diff --git a/services/device/device/associate_test.go b/services/device/device/associate_test.go
new file mode 100644
index 0000000..099d424
--- /dev/null
+++ b/services/device/device/associate_test.go
@@ -0,0 +1,163 @@
+// 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_test
+
+import (
+	"bytes"
+	"reflect"
+	"strings"
+	"testing"
+
+	"v.io/v23/services/device"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/v23cmd"
+	"v.io/x/ref/lib/xrpc"
+	"v.io/x/ref/test"
+
+	cmd_device "v.io/x/ref/services/device/device"
+	"v.io/x/ref/services/internal/servicetest"
+)
+
+func TestListCommand(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	tapes := servicetest.NewTapeMap()
+	server, err := xrpc.NewDispatchingServer(ctx, "", newDispatcher(t, tapes))
+	if err != nil {
+		t.Fatalf("NewServer failed: %v", err)
+	}
+
+	// Setup the command-line.
+	cmd := cmd_device.CmdRoot
+	var stdout, stderr bytes.Buffer
+	env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
+	deviceName := server.Status().Endpoints[0].Name()
+
+	rootTape := tapes.ForSuffix("")
+	// Test the 'list' command.
+	rootTape.SetResponses(ListAssociationResponse{
+		na: []device.Association{
+			{
+				"root/self",
+				"alice_self_account",
+			},
+			{
+				"root/other",
+				"alice_other_account",
+			},
+		},
+		err: nil,
+	})
+
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"associate", "list", deviceName}); err != nil {
+		t.Fatalf("%v", err)
+	}
+	if expected, got := "root/self alice_self_account\nroot/other alice_other_account", strings.TrimSpace(stdout.String()); got != expected {
+		t.Fatalf("Unexpected output from list. Got %q, expected %q", got, expected)
+	}
+	if got, expected := rootTape.Play(), []interface{}{"ListAssociations"}; !reflect.DeepEqual(expected, got) {
+		t.Errorf("invalid call sequence. Got %v, want %v", got, expected)
+	}
+	rootTape.Rewind()
+	stdout.Reset()
+
+	// Test list with bad parameters.
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"associate", "list", deviceName, "hello"}); err == nil {
+		t.Fatalf("wrongly failed to receive a non-nil error.")
+	}
+	if got, expected := len(rootTape.Play()), 0; got != expected {
+		t.Errorf("invalid call sequence. Got %v, want %v", got, expected)
+	}
+}
+
+func TestAddCommand(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	tapes := servicetest.NewTapeMap()
+	server, err := xrpc.NewDispatchingServer(ctx, "", newDispatcher(t, tapes))
+	if err != nil {
+		t.Fatalf("NewServer failed: %v", err)
+	}
+
+	// Setup the command-line.
+	cmd := cmd_device.CmdRoot
+	var stdout, stderr bytes.Buffer
+	env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
+	deviceName := server.Status().Endpoints[0].Name()
+
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"add", "one"}); err == nil {
+		t.Fatalf("wrongly failed to receive a non-nil error.")
+	}
+	rootTape := tapes.ForSuffix("")
+	if got, expected := len(rootTape.Play()), 0; got != expected {
+		t.Errorf("invalid call sequence. Got %v, want %v", got, expected)
+	}
+	rootTape.Rewind()
+	stdout.Reset()
+
+	rootTape.SetResponses(nil)
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"associate", "add", deviceName, "alice", "root/self"}); err != nil {
+		t.Fatalf("%v", err)
+	}
+	expected := []interface{}{
+		AddAssociationStimulus{"AssociateAccount", []string{"root/self"}, "alice"},
+	}
+	if got := rootTape.Play(); !reflect.DeepEqual(expected, got) {
+		t.Errorf("unexpected result. Got %v want %v", got, expected)
+	}
+	rootTape.Rewind()
+	stdout.Reset()
+
+	rootTape.SetResponses(nil)
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"associate", "add", deviceName, "alice", "root/other", "root/self"}); err != nil {
+		t.Fatalf("%v", err)
+	}
+	expected = []interface{}{
+		AddAssociationStimulus{"AssociateAccount", []string{"root/other", "root/self"}, "alice"},
+	}
+	if got := rootTape.Play(); !reflect.DeepEqual(expected, got) {
+		t.Errorf("unexpected result. Got %v want %v", got, expected)
+	}
+}
+
+func TestRemoveCommand(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	tapes := servicetest.NewTapeMap()
+	server, err := xrpc.NewDispatchingServer(ctx, "", newDispatcher(t, tapes))
+	if err != nil {
+		t.Fatalf("NewServer failed: %v", err)
+	}
+
+	// Setup the command-line.
+	cmd := cmd_device.CmdRoot
+	var stdout, stderr bytes.Buffer
+	env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
+	deviceName := server.Status().Endpoints[0].Name()
+
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"remove", "one"}); err == nil {
+		t.Fatalf("wrongly failed to receive a non-nil error.")
+	}
+	rootTape := tapes.ForSuffix("")
+	if got, expected := len(rootTape.Play()), 0; got != expected {
+		t.Errorf("invalid call sequence. Got %v, want %v", got, expected)
+	}
+	rootTape.Rewind()
+	stdout.Reset()
+
+	rootTape.SetResponses(nil)
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"associate", "remove", deviceName, "root/self"}); err != nil {
+		t.Fatalf("%v", err)
+	}
+	expected := []interface{}{
+		AddAssociationStimulus{"AssociateAccount", []string{"root/self"}, ""},
+	}
+	if got := rootTape.Play(); !reflect.DeepEqual(expected, got) {
+		t.Errorf("unexpected result. Got %v want %v", got, expected)
+	}
+}
diff --git a/services/device/device/claim.go b/services/device/device/claim.go
new file mode 100644
index 0000000..2fb911c
--- /dev/null
+++ b/services/device/device/claim.go
@@ -0,0 +1,67 @@
+// 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 (
+	"encoding/base64"
+	"fmt"
+
+	"v.io/v23/context"
+	"v.io/v23/options"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/services/device"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/v23cmd"
+)
+
+var cmdClaim = &cmdline.Command{
+	Runner:   v23cmd.RunnerFunc(runClaim),
+	Name:     "claim",
+	Short:    "Claim the device.",
+	Long:     "Claim the device.",
+	ArgsName: "<device> <grant extension> <pairing token> <device publickey>",
+	ArgsLong: `
+<device> is the vanadium object name of the device manager's device service.
+
+<grant extension> is used to extend the default blessing of the
+current principal when blessing the app instance.
+
+<pairing token> is a token that the device manager expects to be replayed
+during a claim operation on the device.
+
+<device publickey> is the marshalled public key of the device manager we
+are claiming.`,
+}
+
+func runClaim(ctx *context.T, env *cmdline.Env, args []string) error {
+	if expected, max, got := 2, 4, len(args); expected > got || got > max {
+		return env.UsageErrorf("claim: incorrect number of arguments, expected atleast %d (max: %d), got %d", expected, max, got)
+	}
+	deviceName, grant := args[0], args[1]
+	var pairingToken string
+	if len(args) > 2 {
+		pairingToken = args[2]
+	}
+	var serverKeyOpts rpc.CallOpt
+	if len(args) > 3 {
+		marshalledPublicKey, err := base64.URLEncoding.DecodeString(args[3])
+		if err != nil {
+			return fmt.Errorf("Failed to base64 decode publickey: %v", err)
+		}
+		if deviceKey, err := security.UnmarshalPublicKey(marshalledPublicKey); err != nil {
+			return fmt.Errorf("Failed to unmarshal device public key:%v", err)
+		} else {
+			serverKeyOpts = options.ServerPublicKey{PublicKey: deviceKey}
+		}
+	}
+	// Skip server endpoint authorization since an unclaimed device might have
+	// roots that will not be recognized by the claimer.
+	if err := device.ClaimableClient(deviceName).Claim(ctx, pairingToken, &granter{extension: grant}, serverKeyOpts, options.SkipServerEndpointAuthorization{}); err != nil {
+		return err
+	}
+	fmt.Fprintln(env.Stdout, "Successfully claimed.")
+	return nil
+}
diff --git a/services/device/device/claim_test.go b/services/device/device/claim_test.go
new file mode 100644
index 0000000..0ac45aa
--- /dev/null
+++ b/services/device/device/claim_test.go
@@ -0,0 +1,117 @@
+// 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_test
+
+import (
+	"bytes"
+	"encoding/base64"
+	"reflect"
+	"strings"
+	"testing"
+
+	"v.io/v23"
+	"v.io/v23/verror"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/security"
+	"v.io/x/ref/lib/v23cmd"
+	"v.io/x/ref/lib/xrpc"
+	"v.io/x/ref/test"
+
+	cmd_device "v.io/x/ref/services/device/device"
+	"v.io/x/ref/services/internal/servicetest"
+)
+
+func TestClaimCommand(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	tapes := servicetest.NewTapeMap()
+	server, err := xrpc.NewDispatchingServer(ctx, "", newDispatcher(t, tapes))
+	if err != nil {
+		t.Fatalf("NewServer failed: %v", err)
+	}
+
+	// Setup the command-line.
+	cmd := cmd_device.CmdRoot
+	var stdout, stderr bytes.Buffer
+	env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
+	deviceName := server.Status().Endpoints[0].Name()
+	deviceKey, err := v23.GetPrincipal(ctx).PublicKey().MarshalBinary()
+	if err != nil {
+		t.Fatalf("Failed to marshal principal public key: %v", err)
+	}
+
+	// Confirm that we correctly enforce the number of arguments.
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"claim", "nope"}); err == nil {
+		t.Fatalf("wrongly failed to receive a non-nil error.")
+	}
+	if expected, got := "ERROR: claim: incorrect number of arguments, expected atleast 2 (max: 4), got 1", strings.TrimSpace(stderr.String()); !strings.HasPrefix(got, expected) {
+		t.Fatalf("Unexpected output from claim. Got %q, expected prefix %q", got, expected)
+	}
+	stdout.Reset()
+	stderr.Reset()
+	rootTape := tapes.ForSuffix("")
+	rootTape.Rewind()
+
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"claim", "nope", "nope", "nope", "nope", "nope"}); err == nil {
+		t.Fatalf("wrongly failed to receive a non-nil error.")
+	}
+	if expected, got := "ERROR: claim: incorrect number of arguments, expected atleast 2 (max: 4), got 5", strings.TrimSpace(stderr.String()); !strings.HasPrefix(got, expected) {
+		t.Fatalf("Unexpected output from claim. Got %q, expected prefix %q", got, expected)
+	}
+	stdout.Reset()
+	stderr.Reset()
+	rootTape.Rewind()
+
+	// Incorrect operation
+	var pairingToken string
+	var deviceKeyWrong []byte
+	if publicKey, _, err := security.NewPrincipalKey(); err != nil {
+		t.Fatalf("NewPrincipalKey failed: %v", err)
+	} else {
+		if deviceKeyWrong, err = publicKey.MarshalBinary(); err != nil {
+			t.Fatalf("Failed to marshal principal public key: %v", err)
+		}
+	}
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"claim", deviceName, "grant", pairingToken, base64.URLEncoding.EncodeToString(deviceKeyWrong)}); verror.ErrorID(err) != verror.ErrNotTrusted.ID {
+		t.Fatalf("wrongly failed to receive correct error on claim with incorrect device key:%v id:%v", err, verror.ErrorID(err))
+	}
+	stdout.Reset()
+	stderr.Reset()
+	rootTape.Rewind()
+
+	// Correct operation.
+	rootTape.SetResponses(nil)
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"claim", deviceName, "grant", pairingToken, base64.URLEncoding.EncodeToString(deviceKey)}); err != nil {
+		t.Fatalf("Claim(%s, %s, %s) failed: %v", deviceName, "grant", pairingToken, err)
+	}
+	if got, expected := len(rootTape.Play()), 1; got != expected {
+		t.Errorf("invalid call sequence. Got %v, want %v", got, expected)
+	}
+	if expected, got := "Successfully claimed.", strings.TrimSpace(stdout.String()); !strings.HasPrefix(got, expected) {
+		t.Fatalf("Unexpected output from claim. Got %q, expected prefix %q", got, expected)
+	}
+	expected := []interface{}{
+		"Claim",
+	}
+	if got := rootTape.Play(); !reflect.DeepEqual(expected, got) {
+		t.Errorf("unexpected result. Got %v want %v", got, expected)
+	}
+	rootTape.Rewind()
+	stdout.Reset()
+	stderr.Reset()
+
+	// Error operation.
+	rootTape.SetResponses(verror.New(errOops, nil))
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"claim", deviceName, "grant", pairingToken}); err == nil {
+		t.Fatal("claim() failed to detect error:", err)
+	}
+	expected = []interface{}{
+		"Claim",
+	}
+	if got := rootTape.Play(); !reflect.DeepEqual(expected, got) {
+		t.Errorf("unexpected result. Got %v want %v", got, expected)
+	}
+}
diff --git a/services/device/device/debug.go b/services/device/device/debug.go
new file mode 100644
index 0000000..e91b9ac
--- /dev/null
+++ b/services/device/device/debug.go
@@ -0,0 +1,38 @@
+// 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 (
+	"fmt"
+	"io"
+	"strings"
+
+	"v.io/v23/context"
+	"v.io/v23/services/device"
+	"v.io/x/lib/cmdline"
+)
+
+var cmdDebug = &cmdline.Command{
+	Name:     "debug",
+	Short:    "Debug the device.",
+	Long:     "Get internal debug information about application installations and instances.",
+	ArgsName: "<app name patterns...>",
+	ArgsLong: `
+<app name patterns...> are vanadium object names or glob name patterns corresponding to application installations and instances.`,
+}
+
+func init() {
+	globify(cmdDebug, runDebug, new(GlobSettings))
+}
+
+func runDebug(entry GlobResult, ctx *context.T, stdout, _ io.Writer) error {
+	if description, err := device.DeviceClient(entry.Name).Debug(ctx); err != nil {
+		return fmt.Errorf("Debug failed: %v", err)
+	} else {
+		line := strings.Repeat("*", len(entry.Name)+4)
+		fmt.Fprintf(stdout, "%s\n* %s *\n%s\n%v\n", line, entry.Name, line, description)
+	}
+	return nil
+}
diff --git a/services/device/device/debug_test.go b/services/device/device/debug_test.go
new file mode 100644
index 0000000..cc8f528
--- /dev/null
+++ b/services/device/device/debug_test.go
@@ -0,0 +1,56 @@
+// 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_test
+
+import (
+	"bytes"
+	"fmt"
+	"reflect"
+	"strings"
+	"testing"
+
+	"v.io/v23/naming"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/v23cmd"
+	"v.io/x/ref/lib/xrpc"
+	"v.io/x/ref/test"
+
+	cmd_device "v.io/x/ref/services/device/device"
+	"v.io/x/ref/services/internal/servicetest"
+)
+
+func TestDebugCommand(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	tapes := servicetest.NewTapeMap()
+	server, err := xrpc.NewDispatchingServer(ctx, "", newDispatcher(t, tapes))
+	if err != nil {
+		t.Fatalf("NewServer failed: %v", err)
+	}
+
+	cmd := cmd_device.CmdRoot
+	addr := server.Status().Endpoints[0].String()
+	globName := naming.JoinAddressName(addr, "glob")
+	appName := naming.JoinAddressName(addr, "app")
+	rootTape, appTape := tapes.ForSuffix(""), tapes.ForSuffix("app")
+	rootTape.SetResponses(GlobResponse{results: []string{"app"}})
+
+	var stdout, stderr bytes.Buffer
+	env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
+
+	debugMessage := "the secrets of the universe, revealed"
+	appTape.SetResponses(instanceRunning, debugMessage)
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"debug", globName}); err != nil {
+		t.Fatalf("%v", err)
+	}
+	line := strings.Repeat("*", len(appName)+4)
+	expected := fmt.Sprintf("%s\n* %s *\n%s\n%s", line, appName, line, debugMessage)
+	if got := strings.TrimSpace(stdout.String()); got != expected {
+		t.Fatalf("Unexpected output from debug. Got:\n%v\nExpected:\n%v", got, expected)
+	}
+	if got, expected := appTape.Play(), []interface{}{"Status", "Debug"}; !reflect.DeepEqual(expected, got) {
+		t.Errorf("invalid call sequence. Got %v, want %v", got, expected)
+	}
+}
diff --git a/services/device/device/delete.go b/services/device/device/delete.go
new file mode 100644
index 0000000..faff0df
--- /dev/null
+++ b/services/device/device/delete.go
@@ -0,0 +1,37 @@
+// 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 (
+	"fmt"
+
+	"v.io/v23/context"
+	"v.io/v23/services/device"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/v23cmd"
+)
+
+var cmdDelete = &cmdline.Command{
+	Runner:   v23cmd.RunnerFunc(runDelete),
+	Name:     "delete",
+	Short:    "Delete the given application instance.",
+	Long:     "Delete the given application instance.",
+	ArgsName: "<app instance>",
+	ArgsLong: `
+<app instance> is the vanadium object name of the application instance to delete.`,
+}
+
+func runDelete(ctx *context.T, env *cmdline.Env, args []string) error {
+	if expected, got := 1, len(args); expected != got {
+		return env.UsageErrorf("delete: incorrect number of arguments, expected %d, got %d", expected, got)
+	}
+	appName := args[0]
+
+	if err := device.ApplicationClient(appName).Delete(ctx); err != nil {
+		return fmt.Errorf("Delete failed: %v", err)
+	}
+	fmt.Fprintf(env.Stdout, "Delete succeeded\n")
+	return nil
+}
diff --git a/services/device/device/delete_test.go b/services/device/device/delete_test.go
new file mode 100644
index 0000000..f1530c9
--- /dev/null
+++ b/services/device/device/delete_test.go
@@ -0,0 +1,11 @@
+// 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_test
+
+import "testing"
+
+func TestDeleteCommand(t *testing.T) {
+	testHelper(t, "delete", "Delete")
+}
diff --git a/services/device/device/describe.go b/services/device/device/describe.go
new file mode 100644
index 0000000..1964dbd
--- /dev/null
+++ b/services/device/device/describe.go
@@ -0,0 +1,37 @@
+// 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 (
+	"fmt"
+
+	"v.io/v23/context"
+	"v.io/v23/services/device"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/v23cmd"
+)
+
+var cmdDescribe = &cmdline.Command{
+	Runner:   v23cmd.RunnerFunc(runDescribe),
+	Name:     "describe",
+	Short:    "Describe the device.",
+	Long:     "Describe the device.",
+	ArgsName: "<device>",
+	ArgsLong: `
+<device> is the vanadium object name of the device manager's device service.`,
+}
+
+func runDescribe(ctx *context.T, env *cmdline.Env, args []string) error {
+	if expected, got := 1, len(args); expected != got {
+		return env.UsageErrorf("describe: incorrect number of arguments, expected %d, got %d", expected, got)
+	}
+	deviceName := args[0]
+	if description, err := device.DeviceClient(deviceName).Describe(ctx); err != nil {
+		return fmt.Errorf("Describe failed: %v", err)
+	} else {
+		fmt.Fprintf(env.Stdout, "%+v\n", description)
+	}
+	return nil
+}
diff --git a/services/device/device/devicemanager_mock_test.go b/services/device/device/devicemanager_mock_test.go
new file mode 100644
index 0000000..4ca86c9
--- /dev/null
+++ b/services/device/device/devicemanager_mock_test.go
@@ -0,0 +1,315 @@
+// 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_test
+
+import (
+	"fmt"
+	"io/ioutil"
+	"log"
+	"os"
+	"path/filepath"
+	"testing"
+	"time"
+
+	"v.io/v23/context"
+	"v.io/v23/glob"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/security/access"
+	"v.io/v23/services/application"
+	"v.io/v23/services/binary"
+	"v.io/v23/services/device"
+	"v.io/v23/services/repository"
+
+	"v.io/x/ref/services/internal/binarylib"
+	"v.io/x/ref/services/internal/packages"
+	"v.io/x/ref/services/internal/servicetest"
+)
+
+type mockDeviceInvoker struct {
+	tape *servicetest.Tape
+	t    *testing.T
+}
+
+// Mock ListAssociations
+type ListAssociationResponse struct {
+	na  []device.Association
+	err error
+}
+
+func (mdi *mockDeviceInvoker) ListAssociations(ctx *context.T, _ rpc.ServerCall) (associations []device.Association, err error) {
+	ctx.VI(2).Infof("ListAssociations() was called")
+
+	ir := mdi.tape.Record("ListAssociations")
+	r := ir.(ListAssociationResponse)
+	return r.na, r.err
+}
+
+// Mock AssociateAccount
+type AddAssociationStimulus struct {
+	fun           string
+	identityNames []string
+	accountName   string
+}
+
+// simpleCore implements the core of all mock methods that take
+// arguments and return error.
+func (mdi *mockDeviceInvoker) simpleCore(callRecord interface{}, name string) error {
+	ri := mdi.tape.Record(callRecord)
+	switch r := ri.(type) {
+	case nil:
+		return nil
+	case error:
+		return r
+	}
+	log.Fatalf("%s (mock) response %v is of bad type", name, ri)
+	return nil
+}
+
+func (mdi *mockDeviceInvoker) AssociateAccount(_ *context.T, _ rpc.ServerCall, identityNames []string, accountName string) error {
+	return mdi.simpleCore(AddAssociationStimulus{"AssociateAccount", identityNames, accountName}, "AssociateAccount")
+}
+
+func (mdi *mockDeviceInvoker) Claim(_ *context.T, _ rpc.ServerCall, pairingToken string) error {
+	return mdi.simpleCore("Claim", "Claim")
+}
+
+func (*mockDeviceInvoker) Describe(*context.T, rpc.ServerCall) (device.Description, error) {
+	return device.Description{}, nil
+}
+
+func (*mockDeviceInvoker) IsRunnable(_ *context.T, _ rpc.ServerCall, description binary.Description) (bool, error) {
+	return false, nil
+}
+
+func (*mockDeviceInvoker) Reset(_ *context.T, _ rpc.ServerCall, deadline uint64) error { return nil }
+
+// Mock Install
+type InstallStimulus struct {
+	fun      string
+	appName  string
+	config   device.Config
+	packages application.Packages
+	envelope application.Envelope
+	// files holds a map from file  or package name to file or package size.
+	// The app binary  has the key "binary". Each of  the packages will have
+	// the key "package/<package name>". The override packages will have the
+	// key "overridepackage/<package name>".
+	files map[string]int64
+}
+
+type InstallResponse struct {
+	appId string
+	err   error
+}
+
+const (
+	// If provided with this app name, the mock device manager skips trying
+	// to fetch the envelope from the name.
+	appNameNoFetch = "skip-envelope-fetching"
+	// If provided with a fetcheable app name, the mock device manager sets
+	// the app name in the stimulus to this constant.
+	appNameAfterFetch = "envelope-fetched"
+	// The mock device manager sets the binary name in the envelope in the
+	// stimulus to this constant.
+	binaryNameAfterFetch = "binary-fetched"
+)
+
+func packageSize(pkgPath string) int64 {
+	info, err := os.Stat(pkgPath)
+	if err != nil {
+		return -1
+	}
+	if info.IsDir() {
+		infos, err := ioutil.ReadDir(pkgPath)
+		if err != nil {
+			return -1
+		}
+		var size int64
+		for _, i := range infos {
+			size += i.Size()
+		}
+		return size
+	} else {
+		return info.Size()
+	}
+}
+
+func fetchPackageSize(ctx *context.T, pkgVON string) (int64, error) {
+	dir, err := ioutil.TempDir("", "package")
+	if err != nil {
+		return 0, fmt.Errorf("failed to create temp package dir: %v", err)
+	}
+	defer os.RemoveAll(dir)
+	tmpFile := filepath.Join(dir, "downloaded")
+	if err := binarylib.DownloadToFile(ctx, pkgVON, tmpFile); err != nil {
+		return 0, fmt.Errorf("DownloadToFile failed: %v", err)
+	}
+	dst := filepath.Join(dir, "install")
+	if err := packages.Install(tmpFile, dst); err != nil {
+		return 0, fmt.Errorf("packages.Install failed: %v", err)
+	}
+	return packageSize(dst), nil
+}
+
+func (mdi *mockDeviceInvoker) Install(ctx *context.T, _ rpc.ServerCall, appName string, config device.Config, packages application.Packages) (string, error) {
+	is := InstallStimulus{"Install", appName, config, packages, application.Envelope{}, nil}
+	if appName != appNameNoFetch {
+		// Fetch the envelope and record it in the stimulus.
+		envelope, err := repository.ApplicationClient(appName).Match(ctx, []string{"test"})
+		if err != nil {
+			return "", err
+		}
+		binaryName := envelope.Binary.File
+		envelope.Binary.File = binaryNameAfterFetch
+		is.appName = appNameAfterFetch
+		is.files = make(map[string]int64)
+		// Fetch the binary and record its size in the stimulus.
+		data, mediaInfo, err := binarylib.Download(ctx, binaryName)
+		if err != nil {
+			return "", err
+		}
+		is.files["binary"] = int64(len(data))
+		if mediaInfo.Type != "application/octet-stream" {
+			return "", fmt.Errorf("unexpected media type: %v", mediaInfo)
+		}
+		// Iterate over the packages, download them, compute the size of
+		// the file(s) that make up each package, and record that in the
+		// stimulus.
+		for pkgLocalName, pkgVON := range envelope.Packages {
+			size, err := fetchPackageSize(ctx, pkgVON.File)
+			if err != nil {
+				return "", err
+			}
+			is.files[naming.Join("packages", pkgLocalName)] = size
+		}
+		envelope.Packages = nil
+		for pkgLocalName, pkg := range packages {
+			size, err := fetchPackageSize(ctx, pkg.File)
+			if err != nil {
+				return "", err
+			}
+			is.files[naming.Join("overridepackages", pkgLocalName)] = size
+		}
+		is.packages = nil
+		is.envelope = envelope
+	}
+	r := mdi.tape.Record(is).(InstallResponse)
+	return r.appId, r.err
+}
+
+func (mdi *mockDeviceInvoker) Run(*context.T, rpc.ServerCall) error {
+	return mdi.simpleCore("Run", "Run")
+}
+
+func (mdi *mockDeviceInvoker) Revert(*context.T, rpc.ServerCall) error {
+	return mdi.simpleCore("Revert", "Revert")
+}
+
+type InstantiateResponse struct {
+	err        error
+	instanceID string
+}
+
+func (mdi *mockDeviceInvoker) Instantiate(*context.T, rpc.StreamServerCall) (string, error) {
+	ir := mdi.tape.Record("Instantiate")
+	r := ir.(InstantiateResponse)
+	return r.instanceID, r.err
+}
+
+type KillStimulus struct {
+	fun   string
+	delta time.Duration
+}
+
+func (mdi *mockDeviceInvoker) Kill(_ *context.T, _ rpc.ServerCall, delta time.Duration) error {
+	return mdi.simpleCore(KillStimulus{"Kill", delta}, "Kill")
+}
+
+func (mdi *mockDeviceInvoker) Delete(*context.T, rpc.ServerCall) error {
+	return mdi.simpleCore("Delete", "Delete")
+}
+
+func (*mockDeviceInvoker) Uninstall(*context.T, rpc.ServerCall) error { return nil }
+
+func (mdi *mockDeviceInvoker) Update(*context.T, rpc.ServerCall) error {
+	return mdi.simpleCore("Update", "Update")
+}
+
+func (*mockDeviceInvoker) UpdateTo(*context.T, rpc.ServerCall, string) error { return nil }
+
+// Mock Permissions getting and setting
+type GetPermissionsResponse struct {
+	perms   access.Permissions
+	version string
+	err     error
+}
+
+type SetPermissionsStimulus struct {
+	fun     string
+	perms   access.Permissions
+	version string
+}
+
+func (mdi *mockDeviceInvoker) SetPermissions(_ *context.T, _ rpc.ServerCall, perms access.Permissions, version string) error {
+	return mdi.simpleCore(SetPermissionsStimulus{"SetPermissions", perms, version}, "SetPermissions")
+}
+
+func (mdi *mockDeviceInvoker) GetPermissions(*context.T, rpc.ServerCall) (access.Permissions, string, error) {
+	ir := mdi.tape.Record("GetPermissions")
+	r := ir.(GetPermissionsResponse)
+	return r.perms, r.version, r.err
+}
+
+func (mdi *mockDeviceInvoker) Debug(*context.T, rpc.ServerCall) (string, error) {
+	ir := mdi.tape.Record("Debug")
+	r := ir.(string)
+	return r, nil
+}
+
+func (mdi *mockDeviceInvoker) Status(*context.T, rpc.ServerCall) (device.Status, error) {
+	ir := mdi.tape.Record("Status")
+	switch r := ir.(type) {
+	case device.Status:
+		return r, nil
+	case error:
+		return nil, r
+	default:
+		log.Fatalf("Status (mock) response %v is of bad type", ir)
+		return nil, nil
+	}
+}
+
+type GlobStimulus struct {
+	pattern string
+}
+
+type GlobResponse struct {
+	results []string
+	err     error
+}
+
+func (mdi *mockDeviceInvoker) Glob__(_ *context.T, call rpc.GlobServerCall, g *glob.Glob) error {
+	gs := GlobStimulus{g.String()}
+	gr := mdi.tape.Record(gs).(GlobResponse)
+	for _, r := range gr.results {
+		call.SendStream().Send(naming.GlobReplyEntry{Value: naming.MountEntry{Name: r}})
+	}
+	return gr.err
+}
+
+type dispatcher struct {
+	tapes *servicetest.TapeMap
+	t     *testing.T
+}
+
+func newDispatcher(t *testing.T, tapes *servicetest.TapeMap) rpc.Dispatcher {
+	return &dispatcher{tapes: tapes, t: t}
+}
+
+func (d *dispatcher) Lookup(_ *context.T, suffix string) (interface{}, security.Authorizer, error) {
+	return &mockDeviceInvoker{tape: d.tapes.ForSuffix(suffix), t: d.t}, nil, nil
+}
diff --git a/services/device/device/doc.go b/services/device/device/doc.go
new file mode 100644
index 0000000..621d336
--- /dev/null
+++ b/services/device/device/doc.go
@@ -0,0 +1,514 @@
+// 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
+
+/*
+Command device facilitates interaction with the Vanadium device manager.
+
+Usage:
+   device <command>
+
+The device commands are:
+   install       Install the given application.
+   install-local Install the given application from the local system.
+   uninstall     Uninstall the given application installation.
+   associate     Tool for creating associations between Vanadium blessings and a
+                 system account
+   describe      Describe the device.
+   claim         Claim the device.
+   instantiate   Create an instance of the given application.
+   delete        Delete the given application instance.
+   run           Run the given application instance.
+   kill          Kill the given application instance.
+   revert        Revert the device manager or applications.
+   update        Update the device manager or applications.
+   status        Get device manager or application status.
+   debug         Debug the device.
+   acl           Tool for setting device manager Permissions
+   publish       Publish the given application(s).
+   ls            List applications.
+   help          Display help for commands or topics
+
+The global flags are:
+ -v23.namespace.root=[/(dev.v.io/role/vprod/service/mounttabled)@ns.dev.v.io:8101]
+   local namespace root; can be repeated to provided multiple roots
+ -v23.proxy=
+   object name of proxy service to use to export services across network
+   boundaries
+
+ -alsologtostderr=true
+   log to standard error as well as files
+ -log_backtrace_at=:0
+   when logging hits line file:N, emit a stack trace
+ -log_dir=
+   if non-empty, write log files to this directory
+ -logtostderr=false
+   log to standard error instead of files
+ -max_stack_buf_size=4292608
+   max size in bytes of the buffer to use for logging stack traces
+ -metadata=<just specify -metadata to activate>
+   Displays metadata for the program and exits.
+ -stderrthreshold=2
+   logs at or above this threshold go to stderr
+ -v=0
+   log level for V logs
+ -v23.credentials=
+   directory to use for storing security credentials
+ -v23.i18n-catalogue=
+   18n catalogue files to load, comma separated
+ -v23.permissions.file=map[]
+   specify a perms file as <name>:<permsfile>
+ -v23.permissions.literal=
+   explicitly specify the runtime perms as a JSON-encoded access.Permissions.
+   Overrides all --v23.permissions.file flags.
+ -v23.tcp.address=
+   address to listen on
+ -v23.tcp.protocol=wsh
+   protocol to listen with
+ -v23.vtrace.cache-size=1024
+   The number of vtrace traces to store in memory.
+ -v23.vtrace.collect-regexp=
+   Spans and annotations that match this regular expression will trigger trace
+   collection.
+ -v23.vtrace.dump-on-shutdown=true
+   If true, dump all stored traces on runtime shutdown.
+ -v23.vtrace.sample-rate=0
+   Rate (from 0.0 to 1.0) to sample vtrace traces.
+ -vmodule=
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
+
+Device install
+
+Install the given application and print the name of the new installation.
+
+Usage:
+   device install [flags] <device> <application>
+
+<device> is the vanadium object name of the device manager's app service.
+
+<application> is the vanadium object name of the application.
+
+The device install flags are:
+ -config={}
+   JSON-encoded device.Config object, of the form:
+   '{"flag1":"value1","flag2":"value2"}'
+ -packages={}
+   JSON-encoded application.Packages object, of the form:
+   '{"pkg1":{"File":"object name 1"},"pkg2":{"File":"object name 2"}}'
+
+Device install-local
+
+Install the given application specified using a local path, and print the name
+of the new installation.
+
+Usage:
+   device install-local [flags] <device> <title> [ENV=VAL ...] binary [--flag=val ...] [PACKAGES path ...]
+
+<device> is the vanadium object name of the device manager's app service.
+
+<title> is the app title.
+
+This is followed by an arbitrary number of environment variable settings, the
+local path for the binary to install, and arbitrary flag settings and args.
+Optionally, this can be followed by 'PACKAGES' and a list of local files and
+directories to be installed as packages for the app
+
+The device install-local flags are:
+ -config={}
+   JSON-encoded device.Config object, of the form:
+   '{"flag1":"value1","flag2":"value2"}'
+ -packages={}
+   JSON-encoded application.Packages object, of the form:
+   '{"pkg1":{"File":"local file path1"},"pkg2":{"File":"local file path 2"}}'
+
+Device uninstall
+
+Uninstall the given application installation.
+
+Usage:
+   device uninstall <installation>
+
+<installation> is the vanadium object name of the application installation to
+uninstall.
+
+Device associate - Tool for creating associations between Vanadium blessings and a system account
+
+The associate tool facilitates managing blessing to system account associations.
+
+Usage:
+   device associate <command>
+
+The device associate commands are:
+   list        Lists the account associations.
+   add         Add the listed blessings with the specified system account.
+   remove      Removes system accounts associated with the listed blessings.
+
+Device associate list
+
+Lists all account associations.
+
+Usage:
+   device associate list <devicemanager>.
+
+<devicemanager> is the name of the device manager to connect to.
+
+Device associate add
+
+Add the listed blessings with the specified system account.
+
+Usage:
+   device associate add <devicemanager> <systemName> <blessing>...
+
+<devicemanager> is the name of the device manager to connect to. <systemName> is
+the name of an account holder on the local system. <blessing>.. are the
+blessings to associate systemAccount with.
+
+Device associate remove
+
+Removes system accounts associated with the listed blessings.
+
+Usage:
+   device associate remove <devicemanager>  <blessing>...
+
+<devicemanager> is the name of the device manager to connect to. <blessing>...
+is a list of blessings.
+
+Device describe
+
+Describe the device.
+
+Usage:
+   device describe <device>
+
+<device> is the vanadium object name of the device manager's device service.
+
+Device claim
+
+Claim the device.
+
+Usage:
+   device claim <device> <grant extension> <pairing token> <device publickey>
+
+<device> is the vanadium object name of the device manager's device service.
+
+<grant extension> is used to extend the default blessing of the current
+principal when blessing the app instance.
+
+<pairing token> is a token that the device manager expects to be replayed during
+a claim operation on the device.
+
+<device publickey> is the marshalled public key of the device manager we are
+claiming.
+
+Device instantiate
+
+Create an instance of the given application, provide it with a blessing, and
+print the name of the new instance.
+
+Usage:
+   device instantiate <application installation> <grant extension>
+
+<application installation> is the vanadium object name of the application
+installation from which to create an instance.
+
+<grant extension> is used to extend the default blessing of the current
+principal when blessing the app instance.
+
+Device delete
+
+Delete the given application instance.
+
+Usage:
+   device delete <app instance>
+
+<app instance> is the vanadium object name of the application instance to
+delete.
+
+Device run
+
+Run the given application instance.
+
+Usage:
+   device run <app instance>
+
+<app instance> is the vanadium object name of the application instance to run.
+
+Device kill
+
+Kill the given application instance.
+
+Usage:
+   device kill <app instance>
+
+<app instance> is the vanadium object name of the application instance to kill.
+
+Device revert
+
+Revert the device manager or application instances and installations to a
+previous version of their current version
+
+Usage:
+   device revert [flags] <name patterns...>
+
+<name patterns...> are vanadium object names or glob name patterns corresponding
+to the device manager service, or to application installations and instances.
+
+The device revert flags are:
+ -installation-state=!Uninstalled
+   If non-empty, specifies allowed installation states (all others installations
+   get filtered out). The value of the flag is a comma-separated list of values
+   from among: [Active Uninstalled]. If the value is prefixed by '!', the list
+   acts as a blacklist (all matching installations get filtered out).
+ -instance-state=!Deleted
+   If non-empty, specifies allowed instance states (all other instances get
+   filtered out). The value of the flag is a comma-separated list of values from
+   among: [Launching Running Dying NotRunning Updating Deleted]. If the value is
+   prefixed by '!', the list acts as a blacklist (all matching instances get
+   filtered out).
+ -only-installations=false
+   If set, only consider installations.
+ -only-instances=false
+   If set, only consider instances.
+ -parallelism=BYKIND
+   Specifies the level of parallelism for the handler execution. One of: [BYKIND
+   FULL NONE].
+
+Device update
+
+Update the device manager or application instances and installations
+
+Usage:
+   device update [flags] <name patterns...>
+
+<name patterns...> are vanadium object names or glob name patterns corresponding
+to the device manager service, or to application installations and instances.
+
+The device update flags are:
+ -installation-state=!Uninstalled
+   If non-empty, specifies allowed installation states (all others installations
+   get filtered out). The value of the flag is a comma-separated list of values
+   from among: [Active Uninstalled]. If the value is prefixed by '!', the list
+   acts as a blacklist (all matching installations get filtered out).
+ -instance-state=!Deleted
+   If non-empty, specifies allowed instance states (all other instances get
+   filtered out). The value of the flag is a comma-separated list of values from
+   among: [Launching Running Dying NotRunning Updating Deleted]. If the value is
+   prefixed by '!', the list acts as a blacklist (all matching instances get
+   filtered out).
+ -only-installations=false
+   If set, only consider installations.
+ -only-instances=false
+   If set, only consider instances.
+ -parallelism=BYKIND
+   Specifies the level of parallelism for the handler execution. One of: [BYKIND
+   FULL NONE].
+
+Device status
+
+Get the status of the device manager or application instances and installations.
+
+Usage:
+   device status [flags] <name patterns...>
+
+<name patterns...> are vanadium object names or glob name patterns corresponding
+to the device manager service, or to application installations and instances.
+
+The device status flags are:
+ -installation-state=
+   If non-empty, specifies allowed installation states (all others installations
+   get filtered out). The value of the flag is a comma-separated list of values
+   from among: [Active Uninstalled]. If the value is prefixed by '!', the list
+   acts as a blacklist (all matching installations get filtered out).
+ -instance-state=
+   If non-empty, specifies allowed instance states (all other instances get
+   filtered out). The value of the flag is a comma-separated list of values from
+   among: [Launching Running Dying NotRunning Updating Deleted]. If the value is
+   prefixed by '!', the list acts as a blacklist (all matching instances get
+   filtered out).
+ -only-installations=false
+   If set, only consider installations.
+ -only-instances=false
+   If set, only consider instances.
+ -parallelism=FULL
+   Specifies the level of parallelism for the handler execution. One of: [BYKIND
+   FULL NONE].
+
+Device debug
+
+Get internal debug information about application installations and instances.
+
+Usage:
+   device debug [flags] <app name patterns...>
+
+<app name patterns...> are vanadium object names or glob name patterns
+corresponding to application installations and instances.
+
+The device debug flags are:
+ -installation-state=
+   If non-empty, specifies allowed installation states (all others installations
+   get filtered out). The value of the flag is a comma-separated list of values
+   from among: [Active Uninstalled]. If the value is prefixed by '!', the list
+   acts as a blacklist (all matching installations get filtered out).
+ -instance-state=
+   If non-empty, specifies allowed instance states (all other instances get
+   filtered out). The value of the flag is a comma-separated list of values from
+   among: [Launching Running Dying NotRunning Updating Deleted]. If the value is
+   prefixed by '!', the list acts as a blacklist (all matching instances get
+   filtered out).
+ -only-installations=false
+   If set, only consider installations.
+ -only-instances=false
+   If set, only consider instances.
+ -parallelism=FULL
+   Specifies the level of parallelism for the handler execution. One of: [BYKIND
+   FULL NONE].
+
+Device acl - Tool for setting device manager Permissions
+
+The acl tool manages Permissions on the device manger, installations and
+instances.
+
+Usage:
+   device acl <command>
+
+The device acl commands are:
+   get         Get Permissions for the given target.
+   set         Set Permissions for the given target.
+
+Device acl get
+
+Get Permissions for the given target.
+
+Usage:
+   device acl get <device manager name>
+
+<device manager name> can be a Vanadium name for a device manager, application
+installation or instance.
+
+Device acl set
+
+Set Permissions for the given target
+
+Usage:
+   device acl set [flags] <device manager name>  (<blessing> [!]<tag>(,[!]<tag>)*
+
+<device manager name> can be a Vanadium name for a device manager, application
+installation or instance.
+
+<blessing> is a blessing pattern. If the same pattern is repeated multiple times
+in the command, then the only the last occurrence will be honored.
+
+<tag> is a subset of defined access types ("Admin", "Read", "Write" etc.). If
+the access right is prefixed with a '!' then <blessing> is added to the NotIn
+list for that right. Using "^" as a "tag" causes all occurrences of <blessing>
+in the current AccessList to be cleared.
+
+Examples: set root/self ^ will remove "root/self" from the In and NotIn lists
+for all access rights.
+
+set root/self Read,!Write will add "root/self" to the In list for Read access
+and the NotIn list for Write access (and remove "root/self" from both the In and
+NotIn lists of all other access rights)
+
+The device acl set flags are:
+ -f=false
+   Instead of making the AccessLists additive, do a complete replacement based
+   on the specified settings.
+
+Device publish
+
+Publishes the given application(s) to the binary and application servers. The
+binaries should be in $V23_ROOT/release/go/bin/[<GOOS>_<GOARCH>] by default (can
+be overrriden with --from). By default the binary name is used as the name of
+the application envelope, and as the title in the envelope. However,
+<envelope-name> and <title> can be specified explicitly using :<envelope-name>
+and @<title>. The binary is published as <binserv>/<binary
+name>/<GOOS>-<GOARCH>/<TIMESTAMP>. The application envelope is published as
+<appserv>/<envelope-name>/0. Optionally, adds blessing patterns to the Read and
+Resolve AccessLists.
+
+Usage:
+   device publish [flags] <binary name>[:<envelope-name>][@<title>] ...
+
+The device publish flags are:
+ -add-publisher=true
+   If true, add a publisher blessing to the application envelope
+ -appserv=applications
+   Name of application service.
+ -binserv=binaries
+   Name of binary service.
+ -from=
+   Location of binaries to be published.  Defaults to
+   $V23_ROOT/release/go/bin/[<GOOS>_<GOARCH>]
+ -goarch=<runtime.GOARCH>
+   GOARCH for application.  The default is the value of runtime.GOARCH.
+ -goos=<runtime.GOOS>
+   GOOS for application.  The default is the value of runtime.GOOS.
+ -publisher-min-validity=30h0m0s
+   Publisher blessings that are valid for less than this amount of time are
+   considered invalid
+ -readers=dev.v.io
+   If non-empty, comma-separated blessing patterns to add to Read and Resolve
+   AccessList.
+
+Device ls
+
+List application installations or instances.
+
+Usage:
+   device ls [flags] <app name patterns...>
+
+<app name patterns...> are vanadium object names or glob name patterns
+corresponding to application installations and instances.
+
+The device ls flags are:
+ -installation-state=
+   If non-empty, specifies allowed installation states (all others installations
+   get filtered out). The value of the flag is a comma-separated list of values
+   from among: [Active Uninstalled]. If the value is prefixed by '!', the list
+   acts as a blacklist (all matching installations get filtered out).
+ -instance-state=
+   If non-empty, specifies allowed instance states (all other instances get
+   filtered out). The value of the flag is a comma-separated list of values from
+   among: [Launching Running Dying NotRunning Updating Deleted]. If the value is
+   prefixed by '!', the list acts as a blacklist (all matching instances get
+   filtered out).
+ -only-installations=false
+   If set, only consider installations.
+ -only-instances=false
+   If set, only consider instances.
+ -parallelism=FULL
+   Specifies the level of parallelism for the handler execution. One of: [BYKIND
+   FULL NONE].
+
+Device help - Display help for commands or topics
+
+Help with no args displays the usage of the parent command.
+
+Help with args displays the usage of the specified sub-command or help topic.
+
+"help ..." recursively displays help for all commands and topics.
+
+Usage:
+   device help [flags] [command/topic ...]
+
+[command/topic ...] optionally identifies a specific sub-command or help topic.
+
+The device help flags are:
+ -style=compact
+   The formatting style for help output:
+      compact - Good for compact cmdline output.
+      full    - Good for cmdline output, shows all global flags.
+      godoc   - Good for godoc processing.
+   Override the default by setting the CMDLINE_STYLE environment variable.
+ -width=<terminal width>
+   Format output to this target width in runes, or unlimited if width < 0.
+   Defaults to the terminal width if available.  Override the default by setting
+   the CMDLINE_WIDTH environment variable.
+*/
+package main
diff --git a/services/device/device/glob.go b/services/device/device/glob.go
new file mode 100644
index 0000000..ee0ac07
--- /dev/null
+++ b/services/device/device/glob.go
@@ -0,0 +1,505 @@
+// 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 (
+	"bytes"
+	"flag"
+	"fmt"
+	"io"
+	"regexp"
+	"sort"
+	"strings"
+	"sync"
+	"sync/atomic"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/services/device"
+	"v.io/v23/verror"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/v23cmd"
+	"v.io/x/ref/services/device/internal/errors"
+)
+
+// GlobHandler is implemented by each command that wants to execute against name
+// patterns.  The handler is expected to be invoked against each glob result,
+// and can be run concurrently. The handler should direct its output to the
+// given stdout and stderr writers.
+//
+// There are three usage patterns, depending on the desired level of control
+// over the execution flow and settings manipulation.
+//
+// (1) Most control
+//
+// func myCmdHandler(entry GlobResult, ctx *context.T, stdout, stderr io.Writer) error {
+//   output := myCmdProcessing(entry)
+//   fmt.Fprintf(stdout, output)
+//   ...
+// }
+//
+// func runMyCmd(ctx *context.T, env *cmdline.Env, args []string) error {
+//   ...
+//   err := Run(ctx, env, args, myCmdHandler, GlobSettings{})
+//   ...
+// }
+//
+// var cmdMyCmd = &cmdline.Command {
+//   Runner: v23cmd.RunnerFunc(runMyCmd)
+//   ...
+// }
+//
+// (2) Pre-packaged runner
+//
+// If all runMyCmd does is to call Run, you can use globRunner to avoid having
+// to define runMyCmd:
+//
+// var cmdMyCmd = &cmdline.Command {
+//   Runner: globRunner(myCmdHandler, &GlobSettings{}),
+//   Name: "mycmd",
+//   ...
+// }
+//
+// (3) Pre-packaged runner and glob settings flag configuration
+//
+// If, additionally, you want the GlobSettings to be configurable with
+// command-line flags, you can use globify instead:
+//
+// var cmdMyCmd = &cmdline.Command {
+//   Name: "mycmd",
+//   ...
+// }
+//
+// func init() {
+//   globify(cmdMyCmd, myCmdHandler, &GlobSettings{}),
+// }
+//
+// The GlobHandler identifier is exported for use in unit tests.
+type GlobHandler func(entry GlobResult, ctx *context.T, stdout, stderr io.Writer) error
+
+func globRunner(handler GlobHandler, s *GlobSettings) cmdline.Runner {
+	return v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline.Env, args []string) error {
+		return Run(ctx, env, args, handler, *s)
+	})
+}
+
+type objectKind int
+
+const (
+	ApplicationInstallationObject objectKind = iota
+	ApplicationInstanceObject
+	DeviceServiceObject
+	SentinelObjectKind // For invariant checking in testing.
+)
+
+var ObjectKinds = []objectKind{
+	ApplicationInstallationObject,
+	ApplicationInstanceObject,
+	DeviceServiceObject,
+}
+
+// GlobResult defines the input to a GlobHandler.
+// The identifier is exported for use in unit tests.
+type GlobResult struct {
+	Name   string
+	Status device.Status
+	Kind   objectKind
+}
+
+func NewGlobResult(name string, status device.Status) (*GlobResult, error) {
+	var kind objectKind
+	switch status.(type) {
+	case device.StatusInstallation:
+		kind = ApplicationInstallationObject
+	case device.StatusInstance:
+		kind = ApplicationInstanceObject
+	case device.StatusDevice:
+		kind = DeviceServiceObject
+	default:
+		return nil, fmt.Errorf("Status(%v) returned unrecognized status type %T\n", name, status)
+	}
+	return &GlobResult{
+		Name:   name,
+		Status: status,
+		Kind:   kind,
+	}, nil
+}
+
+type byTypeAndName []*GlobResult
+
+func (a byTypeAndName) Len() int      { return len(a) }
+func (a byTypeAndName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
+
+func (a byTypeAndName) Less(i, j int) bool {
+	r1, r2 := a[i], a[j]
+	if r1.Kind != r2.Kind {
+		return r1.Kind < r2.Kind
+	}
+	return r1.Name < r2.Name
+}
+
+// Run runs the given handler in parallel against each of the results obtained
+// by globbing args, after performing filtering based on type
+// (instance/installation) and state.  No de-duping of results is performed.
+// The outputs from each of the handlers are sorted: installations first, then
+// instances (and alphabetically by object name for each group).
+// The identifier is exported for use in unit tests.
+func Run(ctx *context.T, env *cmdline.Env, args []string, handler GlobHandler, s GlobSettings) error {
+	results := glob(ctx, env, args)
+	sort.Sort(byTypeAndName(results))
+	results = filterResults(results, s)
+	if len(results) == 0 {
+		return fmt.Errorf("no objects found")
+	}
+	stdouts, stderrs := make([]bytes.Buffer, len(results)), make([]bytes.Buffer, len(results))
+	var errorCounter uint32 = 0
+	perResult := func(r *GlobResult, index int) {
+		if err := handler(*r, ctx, &stdouts[index], &stderrs[index]); err != nil {
+			fmt.Fprintf(&stderrs[index], "ERROR for \"%s\": %v.\n", r.Name, err)
+			atomic.AddUint32(&errorCounter, 1)
+		}
+	}
+	switch s.HandlerParallelism {
+	case FullParallelism:
+		var wg sync.WaitGroup
+		for i, r := range results {
+			wg.Add(1)
+			go func(r *GlobResult, i int) {
+				perResult(r, i)
+				wg.Done()
+			}(r, i)
+		}
+		wg.Wait()
+	case NoParallelism:
+		for i, r := range results {
+			perResult(r, i)
+		}
+	case KindParallelism:
+		processed := 0
+		for _, k := range ObjectKinds {
+			var wg sync.WaitGroup
+			for i, r := range results {
+				if r.Kind != k {
+					continue
+				}
+				wg.Add(1)
+				processed++
+				go func(r *GlobResult, i int) {
+					perResult(r, i)
+					wg.Done()
+				}(r, i)
+			}
+			wg.Wait()
+		}
+		if processed != len(results) {
+			return fmt.Errorf("broken invariant: unhandled object kind")
+		}
+	}
+	for i := range results {
+		io.Copy(env.Stdout, &stdouts[i])
+		io.Copy(env.Stderr, &stderrs[i])
+	}
+	if errorCounter > 0 {
+		return fmt.Errorf("encountered a total of %d error(s)", errorCounter)
+	}
+	return nil
+}
+
+func filterResults(results []*GlobResult, s GlobSettings) []*GlobResult {
+	var ret []*GlobResult
+	for _, r := range results {
+		switch status := r.Status.(type) {
+		case device.StatusInstance:
+			if s.OnlyInstallations || !s.InstanceStateFilter.apply(status.Value.State) {
+				continue
+			}
+		case device.StatusInstallation:
+			if s.OnlyInstances || !s.InstallationStateFilter.apply(status.Value.State) {
+				continue
+			}
+		case device.StatusDevice:
+			if s.OnlyInstances || s.OnlyInstallations {
+				continue
+			}
+		}
+		ret = append(ret, r)
+	}
+	return ret
+}
+
+// TODO(caprita): We need to filter out debug objects under the app instances'
+// namespaces, so that the tool works with ... patterns.  We should change glob
+// on device manager to not return debug objects by default for apps and instead
+// put them under a __debug suffix (like it works for services).
+var debugNameRE = regexp.MustCompile("/apps/[^/]+/[^/]+/[^/]+/(logs|stats|pprof)(/|$)")
+
+func getStatus(ctx *context.T, env *cmdline.Env, name string, resultsCh chan<- *GlobResult) {
+	status, err := device.DeviceClient(name).Status(ctx)
+	// Skip non-instances/installations.
+	if verror.ErrorID(err) == errors.ErrInvalidSuffix.ID {
+		return
+	}
+	if err != nil {
+		fmt.Fprintf(env.Stderr, "Status(%v) failed: %v\n", name, err)
+		return
+	}
+	if r, err := NewGlobResult(name, status); err != nil {
+		fmt.Fprintf(env.Stderr, "%v\n", err)
+	} else {
+		resultsCh <- r
+	}
+}
+
+func globOne(ctx *context.T, env *cmdline.Env, pattern string, resultsCh chan<- *GlobResult) {
+	globCh, err := v23.GetNamespace(ctx).Glob(ctx, pattern)
+	if err != nil {
+		fmt.Fprintf(env.Stderr, "Glob(%v) failed: %v\n", pattern, err)
+		return
+	}
+	var wg sync.WaitGroup
+	// For each glob result.
+	for entry := range globCh {
+		switch v := entry.(type) {
+		case *naming.GlobReplyEntry:
+			name := v.Value.Name
+			// Skip debug objects.
+			if debugNameRE.MatchString(name) {
+				continue
+			}
+			wg.Add(1)
+			go func() {
+				getStatus(ctx, env, name, resultsCh)
+				wg.Done()
+			}()
+		case *naming.GlobReplyError:
+			fmt.Fprintf(env.Stderr, "Glob(%v) returned an error for %v: %v\n", pattern, v.Value.Name, v.Value.Error)
+		}
+	}
+	wg.Wait()
+}
+
+// glob globs the given arguments and returns the results in arbitrary order.
+func glob(ctx *context.T, env *cmdline.Env, args []string) []*GlobResult {
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
+	defer cancel()
+	var wg sync.WaitGroup
+	resultsCh := make(chan *GlobResult, 100)
+	// For each pattern.
+	for _, a := range args {
+		wg.Add(1)
+		go func(pattern string) {
+			globOne(ctx, env, pattern, resultsCh)
+			wg.Done()
+		}(a)
+	}
+	// Collect the glob results into a slice.
+	var results []*GlobResult
+	done := make(chan struct{})
+	go func() {
+		for r := range resultsCh {
+			results = append(results, r)
+		}
+		close(done)
+	}()
+	wg.Wait()
+	close(resultsCh)
+	<-done
+	return results
+}
+
+type genericStateFlag struct {
+	set     map[genericState]bool
+	exclude bool
+}
+
+// genericState interface is meant to abstract device.InstanceState and
+// device.InstallationState.  We only make use of the String method, but we
+// could also add Set and __VDLReflect to the method set to constrain the types
+// that can be used.  Ultimately, however, the state constructor passed into
+// genericStateFlag.fromString is the gatekeeper as to what can be allowed in
+// the genericStateFlag set.
+type genericState fmt.Stringer
+
+func (f *genericStateFlag) apply(state genericState) bool {
+	if len(f.set) == 0 {
+		return true
+	}
+	return f.exclude != f.set[state]
+}
+
+func (f *genericStateFlag) String() string {
+	states := make([]string, 0, len(f.set))
+	for s := range f.set {
+		states = append(states, s.String())
+	}
+	sort.Strings(states)
+	statesStr := strings.Join(states, ",")
+	if f.exclude {
+		return "!" + statesStr
+	}
+	return statesStr
+}
+
+func (f *genericStateFlag) fromString(s string, stateConstructor func(string) (genericState, error)) error {
+	if len(s) > 0 && s[0] == '!' {
+		f.exclude = true
+		s = s[1:]
+	}
+	states := strings.Split(s, ",")
+	for _, s := range states {
+		state, err := stateConstructor(s)
+		if err != nil {
+			return err
+		}
+		f.add(state)
+	}
+	return nil
+}
+
+func (f *genericStateFlag) add(s genericState) {
+	if f.set == nil {
+		f.set = make(map[genericState]bool)
+	}
+	f.set[s] = true
+}
+
+type instanceStateFlag struct {
+	genericStateFlag
+}
+
+func (f *instanceStateFlag) Set(s string) error {
+	return f.fromString(s, func(s string) (genericState, error) {
+		return device.InstanceStateFromString(s)
+	})
+}
+
+func InstanceStates(states ...device.InstanceState) (f instanceStateFlag) {
+	for _, s := range states {
+		f.add(s)
+	}
+	return
+}
+
+func ExcludeInstanceStates(states ...device.InstanceState) instanceStateFlag {
+	f := InstanceStates(states...)
+	f.exclude = true
+	return f
+}
+
+type installationStateFlag struct {
+	genericStateFlag
+}
+
+func (f *installationStateFlag) Set(s string) error {
+	return f.fromString(s, func(s string) (genericState, error) {
+		return device.InstallationStateFromString(s)
+	})
+}
+
+func InstallationStates(states ...device.InstallationState) (f installationStateFlag) {
+	for _, s := range states {
+		f.add(s)
+	}
+	return
+}
+
+func ExcludeInstallationStates(states ...device.InstallationState) installationStateFlag {
+	f := InstallationStates(states...)
+	f.exclude = true
+	return f
+}
+
+type parallelismFlag int
+
+const (
+	FullParallelism parallelismFlag = iota
+	NoParallelism
+	KindParallelism
+	sentinelParallelismFlag
+)
+
+var parallelismStrings = map[parallelismFlag]string{
+	FullParallelism: "FULL",
+	NoParallelism:   "NONE",
+	KindParallelism: "BYKIND",
+}
+
+func init() {
+	if len(parallelismStrings) != int(sentinelParallelismFlag) {
+		panic(fmt.Sprintf("broken invariant: mismatching number of parallelism types"))
+	}
+}
+
+func (p *parallelismFlag) String() string {
+	s, ok := parallelismStrings[*p]
+	if !ok {
+		return "UNKNOWN"
+	}
+	return s
+}
+
+func (p *parallelismFlag) Set(s string) error {
+	for k, v := range parallelismStrings {
+		if s == v {
+			*p = k
+			return nil
+		}
+	}
+	return fmt.Errorf("unrecognized parallelism type: %v", s)
+}
+
+// GlobSettings specifies flag-settable options and filters for globbing.
+// The identifier is exported for use in unit tests.
+type GlobSettings struct {
+	InstanceStateFilter     instanceStateFlag
+	InstallationStateFilter installationStateFlag
+	OnlyInstances           bool
+	OnlyInstallations       bool
+	HandlerParallelism      parallelismFlag
+	defaults                *GlobSettings
+}
+
+func (s *GlobSettings) reset() {
+	d := s.defaults
+	*s = *d
+	s.defaults = d
+}
+
+func (s *GlobSettings) setDefaults(d GlobSettings) {
+	s.defaults = new(GlobSettings)
+	*s.defaults = d
+}
+
+var allGlobSettings []*GlobSettings
+
+// ResetGlobSettings is meant for tests to restore the values of flag-configured
+// variables when running multiple commands in the same process.
+func ResetGlobSettings() {
+	for _, s := range allGlobSettings {
+		s.reset()
+	}
+}
+
+func defineGlobFlags(fs *flag.FlagSet, s *GlobSettings) {
+	fs.Var(&s.InstanceStateFilter, "instance-state", fmt.Sprintf("If non-empty, specifies allowed instance states (all other instances get filtered out). The value of the flag is a comma-separated list of values from among: %v. If the value is prefixed by '!', the list acts as a blacklist (all matching instances get filtered out).", device.InstanceStateAll))
+	fs.Var(&s.InstallationStateFilter, "installation-state", fmt.Sprintf("If non-empty, specifies allowed installation states (all others installations get filtered out). The value of the flag is a comma-separated list of values from among: %v. If the value is prefixed by '!', the list acts as a blacklist (all matching installations get filtered out).", device.InstallationStateAll))
+	fs.BoolVar(&s.OnlyInstances, "only-instances", false, "If set, only consider instances.")
+	fs.BoolVar(&s.OnlyInstallations, "only-installations", false, "If set, only consider installations.")
+	var parallelismValues []string
+	for _, v := range parallelismStrings {
+		parallelismValues = append(parallelismValues, v)
+	}
+	sort.Strings(parallelismValues)
+	fs.Var(&s.HandlerParallelism, "parallelism", fmt.Sprintf("Specifies the level of parallelism for the handler execution. One of: %v.", parallelismValues))
+}
+
+func globify(c *cmdline.Command, handler GlobHandler, s *GlobSettings) {
+	s.setDefaults(*s)
+	defineGlobFlags(&c.Flags, s)
+	c.Runner = globRunner(handler, s)
+	allGlobSettings = append(allGlobSettings, s)
+}
diff --git a/services/device/device/glob_test.go b/services/device/device/glob_test.go
new file mode 100644
index 0000000..25ac821
--- /dev/null
+++ b/services/device/device/glob_test.go
@@ -0,0 +1,529 @@
+// 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_test
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"math/rand"
+	"runtime"
+	"strings"
+	"sync"
+	"testing"
+	"time"
+
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/services/device"
+
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/xrpc"
+	"v.io/x/ref/test"
+
+	cmd_device "v.io/x/ref/services/device/device"
+	"v.io/x/ref/services/internal/servicetest"
+)
+
+func simplePrintHandler(entry cmd_device.GlobResult, _ *context.T, stdout, _ io.Writer) error {
+	fmt.Fprintf(stdout, "%v\n", entry)
+	return nil
+}
+
+func errOnInstallationsHandler(entry cmd_device.GlobResult, _ *context.T, stdout, _ io.Writer) error {
+	if entry.Kind == cmd_device.ApplicationInstallationObject {
+		return fmt.Errorf("handler complete failure")
+	}
+	fmt.Fprintf(stdout, "%v\n", entry)
+	return nil
+}
+
+// newEnforceFullParallelismHandler returns a handler that should be invoked in
+// parallel n times.
+func newEnforceFullParallelismHandler(t *testing.T, n int) cmd_device.GlobHandler {
+	// The WaitGroup is used to verify parallel execution: each run of the
+	// handler decrements the counter, and then waits for the counter to
+	// reach zero.  If any of the runs of the handler were sequential, the
+	// counter would never reach zero since a deadlock would ensue.  A
+	// timeout protects against the deadlock to ensure the test fails fast.
+	var wg sync.WaitGroup
+	wg.Add(n)
+	return func(entry cmd_device.GlobResult, ctx *context.T, stdout, stderr io.Writer) error {
+		wg.Done()
+		simplePrintHandler(entry, ctx, stdout, stderr)
+		waitDoneCh := make(chan struct{})
+		go func() {
+			wg.Wait()
+			close(waitDoneCh)
+		}()
+		select {
+		case <-waitDoneCh:
+		case <-time.After(5 * time.Second):
+			t.Errorf("Timed out waiting for WaitGroup.  Potential parallelism issue.")
+		}
+		return nil
+	}
+}
+
+// maybeGosched flips a coin to decide where to call Gosched.  It's used to
+// shuffle up the order of handler execution a bit (to prevent the goroutines
+// spawned by the glob library from always executing in a fixed order, e.g. the
+// order in which they're created).
+func maybeGosched() {
+	if rand.Intn(2) == 0 {
+		runtime.Gosched()
+	}
+}
+
+// newEnforceKindParallelismHandler returns a handler that should be invoked
+// nInstallations times in parallel for installations, then nInstances times in
+// parallel for instances, then nDevices times in parallel for device service
+// objects.
+func newEnforceKindParallelismHandler(t *testing.T, nInstallations, nInstances, nDevices int) cmd_device.GlobHandler {
+	// Each of these handlers ensures parallelism within each kind
+	// (installations, instances, device objects).
+	hInstallations := newEnforceFullParallelismHandler(t, nInstallations)
+	hInstances := newEnforceFullParallelismHandler(t, nInstances)
+	hDevices := newEnforceFullParallelismHandler(t, nDevices)
+
+	// These channels are used to verify that all installation handlers must
+	// execute before all instance handlers, which in turn must execute
+	// before all device handlers.
+	instancesCh := make(chan struct{}, nInstances)
+	devicesCh := make(chan struct{}, nDevices)
+	return func(entry cmd_device.GlobResult, ctx *context.T, stdout, stderr io.Writer) error {
+		maybeGosched()
+		switch entry.Kind {
+		case cmd_device.ApplicationInstallationObject:
+			select {
+			case <-instancesCh:
+				t.Errorf("Instance before installation")
+			case <-devicesCh:
+				t.Errorf("Device before installation")
+			default:
+			}
+			return hInstallations(entry, ctx, stdout, stderr)
+		case cmd_device.ApplicationInstanceObject:
+			select {
+			case <-devicesCh:
+				t.Errorf("Device before instance")
+			default:
+			}
+			instancesCh <- struct{}{}
+			return hInstances(entry, ctx, stdout, stderr)
+		case cmd_device.DeviceServiceObject:
+			devicesCh <- struct{}{}
+			return hDevices(entry, ctx, stdout, stderr)
+		}
+		t.Errorf("Unknown entry: %v", entry.Kind)
+		return nil
+	}
+}
+
+// newEnforceNoParallelismHandler returns a handler meant to be invoked sequentially
+// for each of the suffixes contained in the expected slice.
+func newEnforceNoParallelismHandler(t *testing.T, n int, expected []string) cmd_device.GlobHandler {
+	if n != len(expected) {
+		t.Errorf("Test invariant broken: %d != %d", n, len(expected))
+	}
+	orderedSuffixes := make(chan string, n)
+	for _, e := range expected {
+		orderedSuffixes <- e
+	}
+	return func(entry cmd_device.GlobResult, ctx *context.T, stdout, stderr io.Writer) error {
+		maybeGosched()
+		_, suffix := naming.SplitAddressName(entry.Name)
+		expect := <-orderedSuffixes
+		if suffix != expect {
+			t.Errorf("Expected %s, got %s", expect, suffix)
+		}
+		simplePrintHandler(entry, ctx, stdout, stderr)
+		return nil
+	}
+}
+
+// TestObjectKindInvariant ensures that the object kind enum and list are in
+// sync and have not been inadvertently updated independently of each other.
+func TestObjectKindInvariant(t *testing.T) {
+	if len(cmd_device.ObjectKinds) != int(cmd_device.SentinelObjectKind) {
+		t.Errorf("Broken invariant: mismatching number of object kinds")
+	}
+}
+
+// TestGlob tests the internals of the globbing support for the device tool.
+func TestGlob(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	tapes := servicetest.NewTapeMap()
+	rootTape := tapes.ForSuffix("")
+	server, err := xrpc.NewDispatchingServer(ctx, "", newDispatcher(t, tapes))
+	if err != nil {
+		t.Fatalf("NewServer failed: %v", err)
+	}
+	endpoint := server.Status().Endpoints[0]
+	appName := naming.JoinAddressName(endpoint.String(), "app")
+
+	allGlobArgs := []string{"glob1", "glob2"}
+	allGlobResponses := []GlobResponse{
+		{results: []string{"app/3", "app/4", "app/6", "app/5", "app/9", "app/7"}},
+		{results: []string{"app/2", "app/1", "app/8"}},
+	}
+	allStatusResponses := map[string][]interface{}{
+		"app/1": []interface{}{instanceRunning},
+		"app/2": []interface{}{installationUninstalled},
+		"app/3": []interface{}{instanceUpdating},
+		"app/4": []interface{}{installationActive},
+		"app/5": []interface{}{instanceNotRunning},
+		"app/6": []interface{}{deviceService},
+		"app/7": []interface{}{installationActive},
+		"app/8": []interface{}{deviceUpdating},
+		"app/9": []interface{}{instanceUpdating},
+	}
+	outLine := func(suffix string, s device.Status) string {
+		r, err := cmd_device.NewGlobResult(appName+"/"+suffix, s)
+		if err != nil {
+			t.Errorf("NewGlobResult failed: %v", err)
+			return ""
+		}
+		return fmt.Sprintf("%v", *r)
+	}
+	var (
+		runningIstc1Out     = outLine("1", instanceRunning)
+		uninstalledIstl2Out = outLine("2", installationUninstalled)
+		updatingIstc3Out    = outLine("3", instanceUpdating)
+		activeIstl4Out      = outLine("4", installationActive)
+		notRunningIstc5Out  = outLine("5", instanceNotRunning)
+		devc6Out            = outLine("6", deviceService)
+		activeIstl7Out      = outLine("7", installationActive)
+		updatingDevc8Out    = outLine("8", deviceUpdating)
+		updatingIstc9Out    = outLine("9", instanceUpdating)
+	)
+
+	noParallelismHandler := newEnforceNoParallelismHandler(t, len(allStatusResponses), []string{"app/2", "app/4", "app/7", "app/1", "app/3", "app/5", "app/9", "app/6", "app/8"})
+
+	for _, c := range []struct {
+		handler         cmd_device.GlobHandler
+		globResponses   []GlobResponse
+		statusResponses map[string][]interface{}
+		gs              cmd_device.GlobSettings
+		globPatterns    []string
+		expectedStdout  string
+		expectedStderr  string
+		expectedError   string
+	}{
+		// Verifies output is correct and in the expected order (first
+		// installations, then instances, then device services).
+		{
+			simplePrintHandler,
+			allGlobResponses,
+			allStatusResponses,
+			cmd_device.GlobSettings{},
+			allGlobArgs,
+			joinLines(uninstalledIstl2Out, activeIstl4Out, activeIstl7Out, runningIstc1Out, updatingIstc3Out, notRunningIstc5Out, updatingIstc9Out, devc6Out, updatingDevc8Out),
+			"",
+			"",
+		},
+		// Verifies that full parallelism runs all the handlers
+		// simultaneously.
+		{
+			newEnforceFullParallelismHandler(t, len(allStatusResponses)),
+			allGlobResponses,
+			allStatusResponses,
+			cmd_device.GlobSettings{HandlerParallelism: cmd_device.FullParallelism},
+			allGlobArgs,
+			joinLines(uninstalledIstl2Out, activeIstl4Out, activeIstl7Out, runningIstc1Out, updatingIstc3Out, notRunningIstc5Out, updatingIstc9Out, devc6Out, updatingDevc8Out),
+			"",
+			"",
+		},
+		// Verifies that "by kind" parallelism runs all installation
+		// handlers in parallel, then all instance handlers in parallel,
+		// then all device service handlers in parallel.
+		{
+			newEnforceKindParallelismHandler(t, 3, 4, 2),
+			allGlobResponses,
+			allStatusResponses,
+			cmd_device.GlobSettings{HandlerParallelism: cmd_device.KindParallelism},
+			allGlobArgs,
+			joinLines(uninstalledIstl2Out, activeIstl4Out, activeIstl7Out, runningIstc1Out, updatingIstc3Out, notRunningIstc5Out, updatingIstc9Out, devc6Out, updatingDevc8Out),
+			"",
+			"",
+		},
+		// Verifies that the no parallelism option runs all handlers
+		// sequentially.
+		{
+			noParallelismHandler,
+			allGlobResponses,
+			allStatusResponses,
+			cmd_device.GlobSettings{HandlerParallelism: cmd_device.NoParallelism},
+			allGlobArgs,
+			joinLines(uninstalledIstl2Out, activeIstl4Out, activeIstl7Out, runningIstc1Out, updatingIstc3Out, notRunningIstc5Out, updatingIstc9Out, devc6Out, updatingDevc8Out),
+			"",
+			"",
+		},
+		// Verifies "only instances" filter.
+		{
+			simplePrintHandler,
+			allGlobResponses,
+			allStatusResponses,
+			cmd_device.GlobSettings{OnlyInstances: true},
+			allGlobArgs,
+			joinLines(runningIstc1Out, updatingIstc3Out, notRunningIstc5Out, updatingIstc9Out),
+			"",
+			"",
+		},
+		// Verifies "only installations" filter.
+		{
+			simplePrintHandler,
+			allGlobResponses,
+			allStatusResponses,
+			cmd_device.GlobSettings{OnlyInstallations: true},
+			allGlobArgs,
+			joinLines(uninstalledIstl2Out, activeIstl4Out, activeIstl7Out),
+			"",
+			"",
+		},
+		// Verifies "instance state" filter.
+		{
+			simplePrintHandler,
+			allGlobResponses,
+			allStatusResponses,
+			cmd_device.GlobSettings{InstanceStateFilter: cmd_device.InstanceStates(device.InstanceStateUpdating)},
+			allGlobArgs,
+			joinLines(uninstalledIstl2Out, activeIstl4Out, activeIstl7Out, updatingIstc3Out, updatingIstc9Out, devc6Out, updatingDevc8Out),
+			"",
+			"",
+		},
+		// Verifies "instance state" filter with more than 1 state.
+		{
+			simplePrintHandler,
+			allGlobResponses,
+			allStatusResponses,
+			cmd_device.GlobSettings{InstanceStateFilter: cmd_device.InstanceStates(device.InstanceStateUpdating, device.InstanceStateRunning)},
+			allGlobArgs,
+			joinLines(uninstalledIstl2Out, activeIstl4Out, activeIstl7Out, runningIstc1Out, updatingIstc3Out, updatingIstc9Out, devc6Out, updatingDevc8Out),
+			"",
+			"",
+		},
+		// Verifies "instance state" filter with excluded state.
+		{
+			simplePrintHandler,
+			allGlobResponses,
+			allStatusResponses,
+			cmd_device.GlobSettings{InstanceStateFilter: cmd_device.ExcludeInstanceStates(device.InstanceStateUpdating)},
+			allGlobArgs,
+			joinLines(uninstalledIstl2Out, activeIstl4Out, activeIstl7Out, runningIstc1Out, notRunningIstc5Out, devc6Out, updatingDevc8Out),
+			"",
+			"",
+		},
+		// Verifies "instance state" filter with more than 1 excluded state.
+		{
+			simplePrintHandler,
+			allGlobResponses,
+			allStatusResponses,
+			cmd_device.GlobSettings{InstanceStateFilter: cmd_device.ExcludeInstanceStates(device.InstanceStateUpdating, device.InstanceStateRunning)},
+			allGlobArgs,
+			joinLines(uninstalledIstl2Out, activeIstl4Out, activeIstl7Out, notRunningIstc5Out, devc6Out, updatingDevc8Out),
+			"",
+			"",
+		},
+		// Verifies "installation state" filter.
+		{
+			simplePrintHandler,
+			allGlobResponses,
+			allStatusResponses,
+			cmd_device.GlobSettings{InstallationStateFilter: cmd_device.InstallationStates(device.InstallationStateActive)},
+			allGlobArgs,
+			joinLines(activeIstl4Out, activeIstl7Out, runningIstc1Out, updatingIstc3Out, notRunningIstc5Out, updatingIstc9Out, devc6Out, updatingDevc8Out),
+			"",
+			"",
+		},
+		// Verifies "installation state" filter with more than 1 state.
+		{
+			simplePrintHandler,
+			allGlobResponses,
+			allStatusResponses,
+			cmd_device.GlobSettings{InstallationStateFilter: cmd_device.InstallationStates(device.InstallationStateActive, device.InstallationStateUninstalled)},
+			allGlobArgs,
+			joinLines(uninstalledIstl2Out, activeIstl4Out, activeIstl7Out, runningIstc1Out, updatingIstc3Out, notRunningIstc5Out, updatingIstc9Out, devc6Out, updatingDevc8Out),
+			"",
+			"",
+		},
+		// Verifies "installation state" filter with excluded state.
+		{
+			simplePrintHandler,
+			allGlobResponses,
+			allStatusResponses,
+			cmd_device.GlobSettings{InstallationStateFilter: cmd_device.ExcludeInstallationStates(device.InstallationStateActive)},
+			allGlobArgs,
+			joinLines(uninstalledIstl2Out, runningIstc1Out, updatingIstc3Out, notRunningIstc5Out, updatingIstc9Out, devc6Out, updatingDevc8Out),
+			"",
+			"",
+		},
+		// Verifies "installation state" filter with more than 1 excluded state.
+		{
+			simplePrintHandler,
+			allGlobResponses,
+			allStatusResponses,
+			cmd_device.GlobSettings{InstallationStateFilter: cmd_device.ExcludeInstallationStates(device.InstallationStateActive, device.InstallationStateUninstalled)},
+			allGlobArgs,
+			joinLines(runningIstc1Out, updatingIstc3Out, notRunningIstc5Out, updatingIstc9Out, devc6Out, updatingDevc8Out),
+			"",
+			"",
+		},
+		// Verifies "installation state" filter + "only installations" filter.
+		{
+			simplePrintHandler,
+			allGlobResponses,
+			allStatusResponses,
+			cmd_device.GlobSettings{
+				InstallationStateFilter: cmd_device.InstallationStates(device.InstallationStateActive),
+				OnlyInstallations:       true,
+			},
+			allGlobArgs,
+			joinLines(activeIstl4Out, activeIstl7Out),
+			"",
+			"",
+		},
+		// Verifies "installation state" filter + "only instances" filter.
+		{
+			simplePrintHandler,
+			allGlobResponses,
+			allStatusResponses,
+			cmd_device.GlobSettings{
+				InstallationStateFilter: cmd_device.InstallationStates(device.InstallationStateActive),
+				OnlyInstances:           true,
+			},
+			allGlobArgs,
+			joinLines(runningIstc1Out, updatingIstc3Out, notRunningIstc5Out, updatingIstc9Out),
+			"",
+			"",
+		},
+		// Verifies "installation state" filter + "instance state" filter.
+		{
+			simplePrintHandler,
+			allGlobResponses,
+			allStatusResponses,
+			cmd_device.GlobSettings{
+				InstanceStateFilter:     cmd_device.InstanceStates(device.InstanceStateRunning),
+				InstallationStateFilter: cmd_device.InstallationStates(device.InstallationStateUninstalled),
+			},
+			allGlobArgs,
+			joinLines(uninstalledIstl2Out, runningIstc1Out, devc6Out, updatingDevc8Out),
+			"",
+			"",
+		},
+		// Verifies "only instances" filter + "only installations" filter -- no results.
+		{
+			simplePrintHandler,
+			allGlobResponses,
+			allStatusResponses,
+			cmd_device.GlobSettings{
+				OnlyInstallations: true,
+				OnlyInstances:     true,
+			},
+			allGlobArgs,
+			"",
+			"",
+			"no objects found",
+		},
+		// No glob arguments.
+		{
+			simplePrintHandler,
+			allGlobResponses,
+			allStatusResponses,
+			cmd_device.GlobSettings{},
+			[]string{},
+			"",
+			"",
+			"no objects found",
+		},
+		// No glob results.
+		{
+			simplePrintHandler,
+			make([]GlobResponse, 2),
+			allStatusResponses,
+			cmd_device.GlobSettings{},
+			allGlobArgs,
+			"",
+			"",
+			"no objects found",
+		},
+		// Error in glob.
+		{
+			simplePrintHandler,
+			[]GlobResponse{{results: []string{"app/3", "app/4"}}, {err: fmt.Errorf("glob utter failure")}},
+			allStatusResponses,
+			cmd_device.GlobSettings{},
+			[]string{"glob", "glob"},
+			joinLines(activeIstl4Out, updatingIstc3Out),
+			fmt.Sprintf("Glob(%v) returned an error for %v: device.test:\"\".__Glob: Internal error: glob utter failure", naming.JoinAddressName(endpoint.String(), "glob"), naming.JoinAddressName(endpoint.String(), "")),
+			"",
+		},
+		// Error in status.
+		{
+			simplePrintHandler,
+			[]GlobResponse{{results: []string{"app/4", "app/3"}}, {results: []string{"app/1", "app/2"}}},
+			map[string][]interface{}{
+				"app/1": []interface{}{instanceRunning},
+				"app/2": []interface{}{fmt.Errorf("status miserable failure")},
+				"app/3": []interface{}{instanceUpdating},
+				"app/4": []interface{}{installationActive},
+			},
+			cmd_device.GlobSettings{},
+			allGlobArgs,
+			joinLines(activeIstl4Out, runningIstc1Out, updatingIstc3Out),
+			fmt.Sprintf("Status(%v) failed: device.test:<rpc.Client>\"%v\".Status: Error: status miserable failure", appName+"/2", appName+"/2"),
+			"",
+		},
+		// Error in handler.
+		{
+			errOnInstallationsHandler,
+			[]GlobResponse{{results: []string{"app/4", "app/3"}}, {results: []string{"app/1", "app/2"}}},
+			map[string][]interface{}{
+				"app/1": []interface{}{instanceRunning},
+				"app/2": []interface{}{installationUninstalled},
+				"app/3": []interface{}{instanceUpdating},
+				"app/4": []interface{}{installationActive},
+			},
+			cmd_device.GlobSettings{},
+			allGlobArgs,
+			joinLines(runningIstc1Out, updatingIstc3Out),
+			joinLines(
+				fmt.Sprintf("ERROR for \"%v\": handler complete failure.", appName+"/2"),
+				fmt.Sprintf("ERROR for \"%v\": handler complete failure.", appName+"/4")),
+			"encountered a total of 2 error(s)",
+		},
+	} {
+		tapes.Rewind()
+		var rootTapeResponses []interface{}
+		for _, r := range c.globResponses {
+			rootTapeResponses = append(rootTapeResponses, r)
+		}
+		rootTape.SetResponses(rootTapeResponses...)
+		for n, r := range c.statusResponses {
+			tapes.ForSuffix(n).SetResponses(r...)
+		}
+		var stdout, stderr bytes.Buffer
+		env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
+		var args []string
+		for _, p := range c.globPatterns {
+			args = append(args, naming.JoinAddressName(endpoint.String(), p))
+		}
+		err := cmd_device.Run(ctx, env, args, c.handler, c.gs)
+		if err != nil {
+			if expected, got := c.expectedError, err.Error(); expected != got {
+				t.Errorf("Unexpected error. Got: %v. Expected: %v.", got, expected)
+			}
+		} else if c.expectedError != "" {
+			t.Errorf("Expected an error (%v) but got none.", c.expectedError)
+		}
+		if expected, got := c.expectedStdout, strings.TrimSpace(stdout.String()); got != expected {
+			t.Errorf("Unexpected stdout. Got:\n%v\nExpected:\n%v\n", got, expected)
+		}
+		if expected, got := c.expectedStderr, strings.TrimSpace(stderr.String()); got != expected {
+			t.Errorf("Unexpected stderr. Got:\n%v\nExpected:\n%v\n", got, expected)
+		}
+	}
+}
diff --git a/services/device/device/install.go b/services/device/device/install.go
new file mode 100644
index 0000000..b9f055f
--- /dev/null
+++ b/services/device/device/install.go
@@ -0,0 +1,83 @@
+// 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 (
+	"encoding/json"
+	"fmt"
+
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/services/application"
+	"v.io/v23/services/device"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/v23cmd"
+)
+
+type configFlag device.Config
+
+func (c *configFlag) String() string {
+	jsonConfig, _ := json.Marshal(c)
+	return string(jsonConfig)
+}
+func (c *configFlag) Set(s string) error {
+	if err := json.Unmarshal([]byte(s), c); err != nil {
+		return fmt.Errorf("Unmarshal(%v) failed: %v", s, err)
+	}
+	return nil
+}
+
+var configOverride configFlag = configFlag{}
+
+type packagesFlag application.Packages
+
+func (c *packagesFlag) String() string {
+	jsonPackages, _ := json.Marshal(c)
+	return string(jsonPackages)
+}
+func (c *packagesFlag) Set(s string) error {
+	if err := json.Unmarshal([]byte(s), c); err != nil {
+		return fmt.Errorf("Unmarshal(%v) failed: %v", s, err)
+	}
+	return nil
+}
+
+var packagesOverride packagesFlag = packagesFlag{}
+
+var cmdInstall = &cmdline.Command{
+	Runner:   v23cmd.RunnerFunc(runInstall),
+	Name:     "install",
+	Short:    "Install the given application.",
+	Long:     "Install the given application and print the name of the new installation.",
+	ArgsName: "<device> <application>",
+	ArgsLong: `
+<device> is the vanadium object name of the device manager's app service.
+
+<application> is the vanadium object name of the application.
+`,
+}
+
+func init() {
+	cmdInstall.Flags.Var(&configOverride, "config", "JSON-encoded device.Config object, of the form: '{\"flag1\":\"value1\",\"flag2\":\"value2\"}'")
+	cmdInstall.Flags.Var(&packagesOverride, "packages", "JSON-encoded application.Packages object, of the form: '{\"pkg1\":{\"File\":\"object name 1\"},\"pkg2\":{\"File\":\"object name 2\"}}'")
+}
+
+func runInstall(ctx *context.T, env *cmdline.Env, args []string) error {
+	if expected, got := 2, len(args); expected != got {
+		return env.UsageErrorf("install: incorrect number of arguments, expected %d, got %d", expected, got)
+	}
+	deviceName, appName := args[0], args[1]
+	appID, err := device.ApplicationClient(deviceName).Install(ctx, appName, device.Config(configOverride), application.Packages(packagesOverride))
+	// Reset the value for any future invocations of "install" or
+	// "install-local" (we run more than one command per process in unit
+	// tests).
+	configOverride = configFlag{}
+	packagesOverride = packagesFlag{}
+	if err != nil {
+		return fmt.Errorf("Install failed: %v", err)
+	}
+	fmt.Fprintf(env.Stdout, "%s\n", naming.Join(deviceName, appID))
+	return nil
+}
diff --git a/services/device/device/install_test.go b/services/device/device/install_test.go
new file mode 100644
index 0000000..61e766d
--- /dev/null
+++ b/services/device/device/install_test.go
@@ -0,0 +1,133 @@
+// 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_test
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"reflect"
+	"strings"
+	"testing"
+
+	"v.io/v23/naming"
+	"v.io/v23/services/application"
+	"v.io/v23/services/device"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/v23cmd"
+	"v.io/x/ref/lib/xrpc"
+	"v.io/x/ref/test"
+
+	cmd_device "v.io/x/ref/services/device/device"
+	"v.io/x/ref/services/internal/servicetest"
+)
+
+func TestInstallCommand(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	tapes := servicetest.NewTapeMap()
+	server, err := xrpc.NewDispatchingServer(ctx, "", newDispatcher(t, tapes))
+	if err != nil {
+		t.Fatalf("NewServer failed: %v", err)
+	}
+
+	// Setup the command-line.
+	cmd := cmd_device.CmdRoot
+	var stdout, stderr bytes.Buffer
+	env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
+	deviceName := server.Status().Endpoints[0].Name()
+	appId := "myBestAppID"
+	cfg := device.Config{"someflag": "somevalue"}
+	pkg := application.Packages{"pkg": application.SignedFile{File: "somename"}}
+	rootTape := tapes.ForSuffix("")
+	for i, c := range []struct {
+		args         []string
+		config       device.Config
+		packages     application.Packages
+		shouldErr    bool
+		tapeResponse interface{}
+		expectedTape interface{}
+	}{
+		{
+			[]string{"blech"},
+			nil,
+			nil,
+			true,
+			nil,
+			nil,
+		},
+		{
+			[]string{"blech1", "blech2", "blech3", "blech4"},
+			nil,
+			nil,
+			true,
+			nil,
+			nil,
+		},
+		{
+			[]string{deviceName, appNameNoFetch, "not-valid-json"},
+			nil,
+			nil,
+			true,
+			nil,
+			nil,
+		},
+		{
+			[]string{deviceName, appNameNoFetch},
+			nil,
+			nil,
+			false,
+			InstallResponse{appId, nil},
+			InstallStimulus{"Install", appNameNoFetch, nil, nil, application.Envelope{}, nil},
+		},
+		{
+			[]string{deviceName, appNameNoFetch},
+			cfg,
+			pkg,
+			false,
+			InstallResponse{appId, nil},
+			InstallStimulus{"Install", appNameNoFetch, cfg, pkg, application.Envelope{}, nil},
+		},
+	} {
+		rootTape.SetResponses(c.tapeResponse)
+		if c.config != nil {
+			jsonConfig, err := json.Marshal(c.config)
+			if err != nil {
+				t.Fatalf("test case %d: Marshal(%v) failed: %v", i, c.config, err)
+			}
+			c.args = append([]string{fmt.Sprintf("--config=%s", string(jsonConfig))}, c.args...)
+		}
+		if c.packages != nil {
+			jsonPackages, err := json.Marshal(c.packages)
+			if err != nil {
+				t.Fatalf("test case %d: Marshal(%v) failed: %v", i, c.packages, err)
+			}
+			c.args = append([]string{fmt.Sprintf("--packages=%s", string(jsonPackages))}, c.args...)
+		}
+		c.args = append([]string{"install"}, c.args...)
+		err := v23cmd.ParseAndRunForTest(cmd, ctx, env, c.args)
+		if c.shouldErr {
+			if err == nil {
+				t.Fatalf("test case %d: wrongly failed to receive a non-nil error.", i)
+			}
+			if got, expected := len(rootTape.Play()), 0; got != expected {
+				t.Errorf("test case %d: invalid call sequence. Got %v, want %v", i, got, expected)
+			}
+		} else {
+			if err != nil {
+				t.Fatalf("test case %d: %v", i, err)
+			}
+			if expected, got := naming.Join(deviceName, appId), strings.TrimSpace(stdout.String()); got != expected {
+				t.Fatalf("test case %d: Unexpected output from Install. Got %q, expected %q", i, got, expected)
+			}
+			if got, expected := rootTape.Play(), []interface{}{c.expectedTape}; !reflect.DeepEqual(expected, got) {
+				t.Errorf("test case %d: invalid call sequence. Got %#v, want %#v", i, got, expected)
+			}
+		}
+		rootTape.Rewind()
+		stdout.Reset()
+	}
+}
diff --git a/services/device/device/instantiate.go b/services/device/device/instantiate.go
new file mode 100644
index 0000000..f6c7b76
--- /dev/null
+++ b/services/device/device/instantiate.go
@@ -0,0 +1,81 @@
+// 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 (
+	"fmt"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/services/device"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/v23cmd"
+)
+
+var cmdInstantiate = &cmdline.Command{
+	Runner:   v23cmd.RunnerFunc(runInstantiate),
+	Name:     "instantiate",
+	Short:    "Create an instance of the given application.",
+	Long:     "Create an instance of the given application, provide it with a blessing, and print the name of the new instance.",
+	ArgsName: "<application installation> <grant extension>",
+	ArgsLong: `
+<application installation> is the vanadium object name of the
+application installation from which to create an instance.
+
+<grant extension> is used to extend the default blessing of the
+current principal when blessing the app instance.`,
+}
+
+type granter struct {
+	rpc.CallOpt
+	extension string
+}
+
+func (g *granter) Grant(ctx *context.T, call security.Call) (security.Blessings, error) {
+	p := call.LocalPrincipal()
+	return p.Bless(call.RemoteBlessings().PublicKey(), p.BlessingStore().Default(), g.extension, security.UnconstrainedUse())
+}
+
+func runInstantiate(ctx *context.T, env *cmdline.Env, args []string) error {
+	if expected, got := 2, len(args); expected != got {
+		return env.UsageErrorf("instantiate: incorrect number of arguments, expected %d, got %d", expected, got)
+	}
+	appInstallation, grant := args[0], args[1]
+
+	ctx, cancel := context.WithCancel(ctx)
+	defer cancel()
+	principal := v23.GetPrincipal(ctx)
+
+	call, err := device.ApplicationClient(appInstallation).Instantiate(ctx)
+	if err != nil {
+		return fmt.Errorf("Instantiate failed: %v", err)
+	}
+	for call.RecvStream().Advance() {
+		switch msg := call.RecvStream().Value().(type) {
+		case device.BlessServerMessageInstancePublicKey:
+			pubKey, err := security.UnmarshalPublicKey(msg.Value)
+			if err != nil {
+				return fmt.Errorf("Instantiate failed: %v", err)
+			}
+			// TODO(caprita,rthellend): Get rid of security.UnconstrainedUse().
+			blessings, err := principal.Bless(pubKey, principal.BlessingStore().Default(), grant, security.UnconstrainedUse())
+			if err != nil {
+				return fmt.Errorf("Instantiate failed: %v", err)
+			}
+			call.SendStream().Send(device.BlessClientMessageAppBlessings{Value: blessings})
+		default:
+			fmt.Fprintf(env.Stderr, "Received unexpected message: %#v\n", msg)
+		}
+	}
+	var instanceID string
+	if instanceID, err = call.Finish(); err != nil {
+		return fmt.Errorf("Instantiate failed: %v", err)
+	}
+	fmt.Fprintf(env.Stdout, "%s\n", naming.Join(appInstallation, instanceID))
+	return nil
+}
diff --git a/services/device/device/instantiate_test.go b/services/device/device/instantiate_test.go
new file mode 100644
index 0000000..53ff9ac
--- /dev/null
+++ b/services/device/device/instantiate_test.go
@@ -0,0 +1,100 @@
+// 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_test
+
+import (
+	"bytes"
+	"fmt"
+	"reflect"
+	"strings"
+	"testing"
+
+	"v.io/v23/verror"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/v23cmd"
+	"v.io/x/ref/lib/xrpc"
+	"v.io/x/ref/test"
+
+	cmd_device "v.io/x/ref/services/device/device"
+	"v.io/x/ref/services/internal/servicetest"
+)
+
+func TestInstantiateCommand(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	tapes := servicetest.NewTapeMap()
+	server, err := xrpc.NewDispatchingServer(ctx, "", newDispatcher(t, tapes))
+	if err != nil {
+		t.Fatalf("NewServer failed: %v", err)
+	}
+
+	// Setup the command-line.
+	cmd := cmd_device.CmdRoot
+	var stdout, stderr bytes.Buffer
+	env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
+	appName := server.Status().Endpoints[0].Name()
+
+	// Confirm that we correctly enforce the number of arguments.
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"instantiate", "nope"}); err == nil {
+		t.Fatalf("wrongly failed to receive a non-nil error.")
+	}
+	if expected, got := "ERROR: instantiate: incorrect number of arguments, expected 2, got 1", strings.TrimSpace(stderr.String()); !strings.HasPrefix(got, expected) {
+		t.Fatalf("Unexpected output from instantiate. Got %q, expected prefix %q", got, expected)
+	}
+	stdout.Reset()
+	stderr.Reset()
+	rootTape := tapes.ForSuffix("")
+	rootTape.Rewind()
+
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"instantiate", "nope", "nope", "nope"}); err == nil {
+		t.Fatalf("wrongly failed to receive a non-nil error.")
+	}
+	if expected, got := "ERROR: instantiate: incorrect number of arguments, expected 2, got 3", strings.TrimSpace(stderr.String()); !strings.HasPrefix(got, expected) {
+		t.Fatalf("Unexpected output from instantiate. Got %q, expected prefix %q", got, expected)
+	}
+	stdout.Reset()
+	stderr.Reset()
+	rootTape.Rewind()
+
+	// Correct operation.
+	rootTape.SetResponses(InstantiateResponse{
+		err:        nil,
+		instanceID: "app1",
+	})
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"instantiate", appName, "grant"}); err != nil {
+		t.Fatalf("instantiate %s %s failed: %v", appName, "grant", err)
+	}
+
+	b := new(bytes.Buffer)
+	fmt.Fprintf(b, "%s", appName+"/app1")
+	if expected, got := b.String(), strings.TrimSpace(stdout.String()); !strings.HasPrefix(got, expected) {
+		t.Fatalf("Unexpected output from instantiate. Got %q, expected prefix %q", got, expected)
+	}
+	expected := []interface{}{
+		"Instantiate",
+	}
+	if got := rootTape.Play(); !reflect.DeepEqual(expected, got) {
+		t.Errorf("unexpected result. Got %v want %v", got, expected)
+	}
+	rootTape.Rewind()
+	stdout.Reset()
+	stderr.Reset()
+
+	// Error operation.
+	rootTape.SetResponses(InstantiateResponse{
+		verror.New(errOops, nil),
+		"",
+	})
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"instantiate", appName, "grant"}); err == nil {
+		t.Fatalf("instantiate failed to detect error")
+	}
+	expected = []interface{}{
+		"Instantiate",
+	}
+	if got := rootTape.Play(); !reflect.DeepEqual(expected, got) {
+		t.Errorf("unexpected result. Got %v want %v", got, expected)
+	}
+}
diff --git a/services/device/device/kill.go b/services/device/device/kill.go
new file mode 100644
index 0000000..4f156df
--- /dev/null
+++ b/services/device/device/kill.go
@@ -0,0 +1,40 @@
+// 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 (
+	"fmt"
+	"time"
+
+	"v.io/v23/context"
+	"v.io/v23/services/device"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/v23cmd"
+)
+
+const killDeadline = 10 * time.Second
+
+var cmdKill = &cmdline.Command{
+	Runner:   v23cmd.RunnerFunc(runKill),
+	Name:     "kill",
+	Short:    "Kill the given application instance.",
+	Long:     "Kill the given application instance.",
+	ArgsName: "<app instance>",
+	ArgsLong: `
+<app instance> is the vanadium object name of the application instance to kill.`,
+}
+
+func runKill(ctx *context.T, env *cmdline.Env, args []string) error {
+	if expected, got := 1, len(args); expected != got {
+		return env.UsageErrorf("kill: incorrect number of arguments, expected %d, got %d", expected, got)
+	}
+	appName := args[0]
+
+	if err := device.ApplicationClient(appName).Kill(ctx, killDeadline); err != nil {
+		return fmt.Errorf("Kill failed: %v", err)
+	}
+	fmt.Fprintf(env.Stdout, "Kill succeeded\n")
+	return nil
+}
diff --git a/services/device/device/kill_test.go b/services/device/device/kill_test.go
new file mode 100644
index 0000000..3b37881
--- /dev/null
+++ b/services/device/device/kill_test.go
@@ -0,0 +1,91 @@
+// 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_test
+
+import (
+	"bytes"
+	"reflect"
+	"strings"
+	"testing"
+	"time"
+
+	"v.io/v23/naming"
+	"v.io/v23/verror"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/v23cmd"
+	"v.io/x/ref/lib/xrpc"
+	"v.io/x/ref/test"
+
+	cmd_device "v.io/x/ref/services/device/device"
+	"v.io/x/ref/services/internal/servicetest"
+)
+
+func TestKillCommand(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	tapes := servicetest.NewTapeMap()
+	server, err := xrpc.NewDispatchingServer(ctx, "", newDispatcher(t, tapes))
+	if err != nil {
+		t.Fatalf("NewServer failed: %v", err)
+	}
+
+	// Setup the command-line.
+	cmd := cmd_device.CmdRoot
+	var stdout, stderr bytes.Buffer
+	env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
+	appName := naming.JoinAddressName(server.Status().Endpoints[0].String(), "appname")
+
+	// Confirm that we correctly enforce the number of arguments.
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"kill"}); err == nil {
+		t.Fatalf("wrongly failed to receive a non-nil error.")
+	}
+	if expected, got := "ERROR: kill: incorrect number of arguments, expected 1, got 0", strings.TrimSpace(stderr.String()); !strings.HasPrefix(got, expected) {
+		t.Fatalf("Unexpected output from kill. Got %q, expected prefix %q", got, expected)
+	}
+	stdout.Reset()
+	stderr.Reset()
+	appTape := tapes.ForSuffix("appname")
+	appTape.Rewind()
+
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"kill", "nope", "nope"}); err == nil {
+		t.Fatalf("wrongly failed to receive a non-nil error.")
+	}
+	if expected, got := "ERROR: kill: incorrect number of arguments, expected 1, got 2", strings.TrimSpace(stderr.String()); !strings.HasPrefix(got, expected) {
+		t.Fatalf("Unexpected output from kill. Got %q, expected prefix %q", got, expected)
+	}
+	stdout.Reset()
+	stderr.Reset()
+	appTape.Rewind()
+
+	// Test the 'kill' command.
+	appTape.SetResponses(nil)
+
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"kill", appName}); err != nil {
+		t.Fatalf("kill failed when it shouldn't: %v", err)
+	}
+	if expected, got := "Kill succeeded", strings.TrimSpace(stdout.String()); got != expected {
+		t.Fatalf("Unexpected output from list. Got %q, expected %q", got, expected)
+	}
+	expected := []interface{}{
+		KillStimulus{"Kill", 10 * time.Second},
+	}
+	if got := appTape.Play(); !reflect.DeepEqual(expected, got) {
+		t.Errorf("invalid call sequence. Got %v, want %v", got, expected)
+	}
+	appTape.Rewind()
+	stderr.Reset()
+	stdout.Reset()
+
+	// Test kill with bad parameters.
+	appTape.SetResponses(verror.New(errOops, nil))
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"kill", appName}); err == nil {
+		t.Fatalf("wrongly didn't receive a non-nil error.")
+	}
+	// expected the same.
+	if got := appTape.Play(); !reflect.DeepEqual(expected, got) {
+		t.Errorf("invalid call sequence. Got %v, want %v", got, expected)
+	}
+}
diff --git a/services/device/device/local_install.go b/services/device/device/local_install.go
new file mode 100644
index 0000000..aba34b2
--- /dev/null
+++ b/services/device/device/local_install.go
@@ -0,0 +1,344 @@
+// 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 (
+	"crypto/md5"
+	"encoding/hex"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/security/access"
+	"v.io/v23/services/application"
+	"v.io/v23/services/binary"
+	"v.io/v23/services/device"
+	"v.io/v23/services/repository"
+	"v.io/v23/uniqueid"
+
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/v23cmd"
+	"v.io/x/ref/lib/xrpc"
+	"v.io/x/ref/services/internal/packages"
+)
+
+var cmdInstallLocal = &cmdline.Command{
+	Runner:   v23cmd.RunnerFunc(runInstallLocal),
+	Name:     "install-local",
+	Short:    "Install the given application from the local system.",
+	Long:     "Install the given application specified using a local path, and print the name of the new installation.",
+	ArgsName: "<device> <title> [ENV=VAL ...] binary [--flag=val ...] [PACKAGES path ...]",
+	ArgsLong: `
+<device> is the vanadium object name of the device manager's app service.
+
+<title> is the app title.
+
+This is followed by an arbitrary number of environment variable settings, the
+local path for the binary to install, and arbitrary flag settings and args.
+Optionally, this can be followed by 'PACKAGES' and a list of local files and
+directories to be installed as packages for the app`}
+
+func init() {
+	cmdInstallLocal.Flags.Var(&configOverride, "config", "JSON-encoded device.Config object, of the form: '{\"flag1\":\"value1\",\"flag2\":\"value2\"}'")
+	cmdInstallLocal.Flags.Var(&packagesOverride, "packages", "JSON-encoded application.Packages object, of the form: '{\"pkg1\":{\"File\":\"local file path1\"},\"pkg2\":{\"File\":\"local file path 2\"}}'")
+}
+
+type mapDispatcher map[string]interface{}
+
+func (d mapDispatcher) Lookup(_ *context.T, suffix string) (interface{}, security.Authorizer, error) {
+	o, ok := d[suffix]
+	if !ok {
+		return nil, nil, fmt.Errorf("suffix %s not found", suffix)
+	}
+	// TODO(caprita): Do not allow everyone, even for a short-lived server.
+	return o, security.AllowEveryone(), nil
+}
+
+type mapServer struct {
+	name       string
+	dispatcher mapDispatcher
+}
+
+func (ms *mapServer) serve(name string, object interface{}) (string, error) {
+	if _, ok := ms.dispatcher[name]; ok {
+		return "", fmt.Errorf("can't have more than one object with name %v", name)
+	}
+	ms.dispatcher[name] = object
+	return naming.Join(ms.name, name), nil
+}
+
+func createServer(ctx *context.T, stderr io.Writer) (*mapServer, func(), error) {
+	dispatcher := make(mapDispatcher)
+
+	var name string
+	if spec := v23.GetListenSpec(ctx); spec.Proxy != "" {
+		id, err := uniqueid.Random()
+		if err != nil {
+			return nil, nil, err
+		}
+		name = id.String()
+		// Disable listening on local addresses to avoid publishing
+		// local endpoints to the mount table.  The only thing published
+		// should be the proxied endpoint.
+		spec.Addrs = nil
+		ctx = v23.WithListenSpec(ctx, spec)
+	}
+	server, err := xrpc.NewDispatchingServer(ctx, name, dispatcher)
+	if err != nil {
+		return nil, nil, err
+	}
+	endpoints := server.Status().Endpoints
+	ctx.VI(1).Infof("Server listening on %v (%v)", endpoints, name)
+	cleanup := func() {
+		if err := server.Stop(); err != nil {
+			fmt.Fprintf(stderr, "server.Stop failed: %v", err)
+		}
+	}
+	if name != "" {
+		// Send a name rooted in our namespace root rather than the
+		// relative name (in case the device manager uses a different
+		// namespace root).
+		//
+		// TODO(caprita): Avoid relying on a mounttable altogether, and
+		// instead pull out the proxied address and just send that.
+		nsRoots := v23.GetNamespace(ctx).Roots()
+		if len(nsRoots) > 0 {
+			name = naming.Join(nsRoots[0], name)
+		}
+	} else if len(endpoints) > 0 {
+		name = endpoints[0].Name()
+	} else {
+		return nil, nil, fmt.Errorf("no endpoints")
+	}
+	return &mapServer{name: name, dispatcher: dispatcher}, cleanup, nil
+}
+
+var errNotImplemented = fmt.Errorf("method not implemented")
+
+type binaryInvoker string
+
+func (binaryInvoker) Create(*context.T, rpc.ServerCall, int32, repository.MediaInfo) error {
+	return errNotImplemented
+}
+
+func (binaryInvoker) Delete(*context.T, rpc.ServerCall) error {
+	return errNotImplemented
+}
+
+func (i binaryInvoker) Download(ctx *context.T, call repository.BinaryDownloadServerCall, _ int32) error {
+	fileName := string(i)
+	fStat, err := os.Stat(fileName)
+	if err != nil {
+		return err
+	}
+	ctx.VI(1).Infof("Download commenced for %v (%v bytes)", fileName, fStat.Size())
+	file, err := os.Open(fileName)
+	if err != nil {
+		return err
+	}
+	defer file.Close()
+	bufferLength := 4096
+	buffer := make([]byte, bufferLength)
+	sender := call.SendStream()
+	var sentTotal int64
+	const logChunk = 1 << 20
+	for {
+		n, err := file.Read(buffer)
+		switch err {
+		case io.EOF:
+			ctx.VI(1).Infof("Download complete for %v (%v bytes)", fileName, fStat.Size())
+			return nil
+		case nil:
+			if err := sender.Send(buffer[:n]); err != nil {
+				return err
+			}
+			if sentTotal/logChunk < (sentTotal+int64(n))/logChunk {
+				ctx.VI(1).Infof("Download progress for %v: %v/%v", fileName, sentTotal+int64(n), fStat.Size())
+			}
+			sentTotal += int64(n)
+		default:
+			return err
+		}
+	}
+}
+
+func (binaryInvoker) DownloadUrl(*context.T, rpc.ServerCall) (string, int64, error) {
+	return "", 0, errNotImplemented
+}
+
+func (i binaryInvoker) Stat(*context.T, rpc.ServerCall) ([]binary.PartInfo, repository.MediaInfo, error) {
+	fileName := string(i)
+	h := md5.New()
+	bytes, err := ioutil.ReadFile(fileName)
+	if err != nil {
+		return []binary.PartInfo{}, repository.MediaInfo{}, err
+	}
+	h.Write(bytes)
+	part := binary.PartInfo{Checksum: hex.EncodeToString(h.Sum(nil)), Size: int64(len(bytes))}
+	return []binary.PartInfo{part}, packages.MediaInfoForFileName(fileName), nil
+}
+
+func (binaryInvoker) Upload(*context.T, repository.BinaryUploadServerCall, int32) error {
+	return errNotImplemented
+}
+
+func (binaryInvoker) GetPermissions(*context.T, rpc.ServerCall) (perms access.Permissions, version string, err error) {
+	return nil, "", errNotImplemented
+}
+
+func (binaryInvoker) SetPermissions(_ *context.T, _ rpc.ServerCall, perms access.Permissions, version string) error {
+	return errNotImplemented
+}
+
+type envelopeInvoker application.Envelope
+
+func (i envelopeInvoker) Match(*context.T, rpc.ServerCall, []string) (application.Envelope, error) {
+	return application.Envelope(i), nil
+}
+
+func (envelopeInvoker) GetPermissions(*context.T, rpc.ServerCall) (perms access.Permissions, version string, err error) {
+	return nil, "", errNotImplemented
+}
+
+func (envelopeInvoker) SetPermissions(*context.T, rpc.ServerCall, access.Permissions, string) error {
+	return errNotImplemented
+}
+
+func (envelopeInvoker) TidyNow(*context.T, rpc.ServerCall) error {
+	return errNotImplemented
+}
+
+func servePackage(p string, ms *mapServer, tmpZipDir string) (string, string, error) {
+	info, err := os.Stat(p)
+	if os.IsNotExist(err) {
+		return "", "", fmt.Errorf("%v not found: %v", p, err)
+	} else if err != nil {
+		return "", "", fmt.Errorf("Stat(%v) failed: %v", p, err)
+	}
+	pkgName := naming.Join("packages", info.Name())
+	fileName := p
+	// Directory packages first get zip'ped.
+	if info.IsDir() {
+		fileName = filepath.Join(tmpZipDir, info.Name()+".zip")
+		if err := packages.CreateZip(fileName, p); err != nil {
+			return "", "", err
+		}
+	}
+	name, err := ms.serve(pkgName, repository.BinaryServer(binaryInvoker(fileName)))
+	return info.Name(), name, err
+}
+
+// runInstallLocal creates a new envelope on the fly from the provided
+// arguments, and then points the device manager back to itself for downloading
+// the app envelope and binary.
+//
+// It sets up an app and binary server that only lives for the duration of the
+// command, and listens on the profile's listen spec.  The caller should set the
+// --v23.proxy if the machine running the command is not accessible from the
+// device manager.
+//
+// TODO(caprita/ashankar): We should use bi-directional streams to get this
+// working over the same connection that the command makes to the device
+// manager.
+func runInstallLocal(ctx *context.T, env *cmdline.Env, args []string) error {
+	if expectedMin, got := 2, len(args); got < expectedMin {
+		return env.UsageErrorf("install-local: incorrect number of arguments, expected at least %d, got %d", expectedMin, got)
+	}
+	deviceName, title := args[0], args[1]
+	args = args[2:]
+	envelope := application.Envelope{Title: title}
+	// Extract the environment settings, binary, and arguments.
+	firstNonEnv := len(args)
+	for i, arg := range args {
+		if strings.Index(arg, "=") <= 0 {
+			firstNonEnv = i
+			break
+		}
+	}
+	envelope.Env = args[:firstNonEnv]
+	args = args[firstNonEnv:]
+	if len(args) == 0 {
+		return env.UsageErrorf("install-local: missing binary")
+	}
+	binary := args[0]
+	args = args[1:]
+	firstNonArg, firstPackage := len(args), len(args)
+	for i, arg := range args {
+		if arg == "PACKAGES" {
+			firstNonArg = i
+			firstPackage = i + 1
+			break
+		}
+	}
+	envelope.Args = args[:firstNonArg]
+	pkgs := args[firstPackage:]
+	if _, err := os.Stat(binary); err != nil {
+		return fmt.Errorf("binary %v not found: %v", binary, err)
+	}
+	server, cancel, err := createServer(ctx, env.Stderr)
+	if err != nil {
+		return fmt.Errorf("failed to create server: %v", err)
+	}
+	defer cancel()
+	envelope.Binary.File, err = server.serve("binary", repository.BinaryServer(binaryInvoker(binary)))
+	if err != nil {
+		return err
+	}
+	ctx.VI(1).Infof("binary %v serving as %v", binary, envelope.Binary.File)
+
+	// For each package dir/file specified in the arguments list, set up an
+	// object in the binary service to serve that package, and add the
+	// object name to the envelope's Packages map.
+	tmpZipDir, err := ioutil.TempDir("", "packages")
+	if err != nil {
+		return fmt.Errorf("failed to create a temp dir for zip packages: %v", err)
+	}
+	defer os.RemoveAll(tmpZipDir)
+	for _, p := range pkgs {
+		if envelope.Packages == nil {
+			envelope.Packages = make(application.Packages)
+		}
+		pname, oname, err := servePackage(p, server, tmpZipDir)
+		if err != nil {
+			return err
+		}
+		ctx.VI(1).Infof("package %v serving as %v", pname, oname)
+		envelope.Packages[pname] = application.SignedFile{File: oname}
+	}
+	packagesRewritten := application.Packages{}
+	for pname, pspec := range packagesOverride {
+		_, oname, err := servePackage(pspec.File, server, tmpZipDir)
+		if err != nil {
+			return err
+		}
+		ctx.VI(1).Infof("package %v serving as %v", pname, oname)
+		pspec.File = oname
+		packagesRewritten[pname] = pspec
+	}
+	appName, err := server.serve("application", repository.ApplicationServer(envelopeInvoker(envelope)))
+	if err != nil {
+		return err
+	}
+	ctx.VI(1).Infof("application serving envelope as %v", appName)
+	appID, err := device.ApplicationClient(deviceName).Install(ctx, appName, device.Config(configOverride), packagesRewritten)
+	// Reset the value for any future invocations of "install" or
+	// "install-local" (we run more than one command per process in unit
+	// tests).
+	configOverride = configFlag{}
+	packagesOverride = packagesFlag{}
+	if err != nil {
+		return fmt.Errorf("Install failed: %v", err)
+	}
+	fmt.Fprintf(env.Stdout, "%s\n", naming.Join(deviceName, appID))
+	return nil
+}
diff --git a/services/device/device/local_install_test.go b/services/device/device/local_install_test.go
new file mode 100644
index 0000000..aa0d615
--- /dev/null
+++ b/services/device/device/local_install_test.go
@@ -0,0 +1,257 @@
+// 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_test
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"reflect"
+	"strings"
+	"testing"
+
+	"v.io/v23/naming"
+	"v.io/v23/security"
+	"v.io/v23/services/application"
+	"v.io/v23/services/device"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/v23cmd"
+	"v.io/x/ref/lib/xrpc"
+	"v.io/x/ref/test"
+
+	cmd_device "v.io/x/ref/services/device/device"
+	"v.io/x/ref/services/internal/servicetest"
+)
+
+func createFile(t *testing.T, path string, contents string) {
+	if err := ioutil.WriteFile(path, []byte(contents), 0700); err != nil {
+		t.Fatalf("Failed to create %v: %v", path, err)
+	}
+}
+
+func TestInstallLocalCommand(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	tapes := servicetest.NewTapeMap()
+	server, err := xrpc.NewDispatchingServer(ctx, "", newDispatcher(t, tapes))
+	if err != nil {
+		t.Fatalf("NewServer failed: %v", err)
+	}
+	// Setup the command-line.
+	cmd := cmd_device.CmdRoot
+	var stdout, stderr bytes.Buffer
+	env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
+	deviceName := server.Status().Endpoints[0].Name()
+	const appTitle = "Appo di tutti Appi"
+	binary := os.Args[0]
+	fi, err := os.Stat(binary)
+	if err != nil {
+		t.Fatalf("Failed to stat %v: %v", binary, err)
+	}
+	binarySize := fi.Size()
+	rootTape := tapes.ForSuffix("")
+	for i, c := range []struct {
+		args         []string
+		stderrSubstr string
+	}{
+		{
+			[]string{deviceName}, "incorrect number of arguments",
+		},
+		{
+			[]string{deviceName, appTitle}, "missing binary",
+		},
+		{
+			[]string{deviceName, appTitle, "a=b"}, "missing binary",
+		},
+		{
+			[]string{deviceName, appTitle, "foo"}, "binary foo not found",
+		},
+		{
+			[]string{deviceName, appTitle, binary, "PACKAGES", "foo"}, "foo not found",
+		},
+	} {
+		c.args = append([]string{"install-local"}, c.args...)
+		if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, c.args); err == nil {
+			t.Fatalf("test case %d: wrongly failed to receive a non-nil error.", i)
+		} else {
+			fmt.Fprintln(&stderr, "ERROR:", err)
+			if want, got := c.stderrSubstr, stderr.String(); !strings.Contains(got, want) {
+				t.Errorf("test case %d: %q not found in stderr: %q", i, want, got)
+			}
+		}
+		if got, expected := len(rootTape.Play()), 0; got != expected {
+			t.Errorf("test case %d: invalid call sequence. Got %v, want %v", i, got, expected)
+		}
+		rootTape.Rewind()
+		stdout.Reset()
+		stderr.Reset()
+	}
+	emptySig := security.Signature{}
+	emptyBlessings := security.Blessings{}
+	cfg := device.Config{"someflag": "somevalue"}
+
+	testPackagesDir, err := ioutil.TempDir("", "testdir")
+	if err != nil {
+		t.Fatalf("Failed to create temp dir: %v", err)
+	}
+	defer os.RemoveAll(testPackagesDir)
+	pkgFile1 := filepath.Join(testPackagesDir, "file1.txt")
+	createFile(t, pkgFile1, "1234567")
+	pkgFile2 := filepath.Join(testPackagesDir, "file2")
+	createFile(t, pkgFile2, string([]byte{0x01, 0x02, 0x03, 0x04}))
+	pkgDir1 := filepath.Join(testPackagesDir, "dir1")
+	if err := os.Mkdir(pkgDir1, 0700); err != nil {
+		t.Fatalf("Failed to create dir1: %v", err)
+	}
+	createFile(t, filepath.Join(pkgDir1, "f1"), "123")
+	createFile(t, filepath.Join(pkgDir1, "f2"), "456")
+	createFile(t, filepath.Join(pkgDir1, "f3"), "7890")
+
+	pkgFile3 := filepath.Join(testPackagesDir, "file3")
+	createFile(t, pkgFile3, "12345")
+	pkgFile4 := filepath.Join(testPackagesDir, "file4")
+	createFile(t, pkgFile4, "123")
+	pkgDir2 := filepath.Join(testPackagesDir, "dir2")
+	if err := os.Mkdir(pkgDir2, 0700); err != nil {
+		t.Fatalf("Failed to create dir2: %v", err)
+	}
+	createFile(t, filepath.Join(pkgDir2, "f1"), "123456")
+	createFile(t, filepath.Join(pkgDir2, "f2"), "78")
+	pkg := application.Packages{
+		"overridepkg1": application.SignedFile{File: pkgFile3},
+		"overridepkg2": application.SignedFile{File: pkgFile4},
+		"overridepkg3": application.SignedFile{File: pkgDir2},
+	}
+
+	for i, c := range []struct {
+		args         []string
+		config       device.Config
+		packages     application.Packages
+		expectedTape interface{}
+	}{
+		{
+			[]string{deviceName, appTitle, binary},
+			nil,
+			nil,
+			InstallStimulus{
+				"Install",
+				appNameAfterFetch,
+				nil,
+				nil,
+				application.Envelope{
+					Title: appTitle,
+					Binary: application.SignedFile{
+						File:      binaryNameAfterFetch,
+						Signature: emptySig,
+					},
+					Publisher: emptyBlessings,
+				},
+				map[string]int64{"binary": binarySize}},
+		},
+		{
+			[]string{deviceName, appTitle, binary},
+			cfg,
+			nil,
+			InstallStimulus{
+				"Install",
+				appNameAfterFetch,
+				cfg,
+				nil,
+				application.Envelope{
+					Title: appTitle,
+					Binary: application.SignedFile{
+						File:      binaryNameAfterFetch,
+						Signature: emptySig,
+					},
+					Publisher: emptyBlessings,
+				},
+				map[string]int64{"binary": binarySize}},
+		},
+		{
+			[]string{deviceName, appTitle, "ENV1=V1", "ENV2=V2", binary, "FLAG1=V1", "FLAG2=V2"},
+			nil,
+			nil,
+			InstallStimulus{
+				"Install",
+				appNameAfterFetch,
+				nil,
+				nil,
+				application.Envelope{
+					Title: appTitle,
+					Binary: application.SignedFile{
+						File:      binaryNameAfterFetch,
+						Signature: emptySig,
+					},
+					Publisher: emptyBlessings,
+					Env:       []string{"ENV1=V1", "ENV2=V2"},
+					Args:      []string{"FLAG1=V1", "FLAG2=V2"},
+				},
+				map[string]int64{"binary": binarySize}},
+		},
+		{
+			[]string{deviceName, appTitle, "ENV=V", binary, "FLAG=V", "PACKAGES", pkgFile1, pkgFile2, pkgDir1},
+			nil,
+			pkg,
+			InstallStimulus{"Install",
+				appNameAfterFetch,
+				nil,
+				nil,
+				application.Envelope{
+					Title: appTitle,
+					Binary: application.SignedFile{
+						File:      binaryNameAfterFetch,
+						Signature: emptySig,
+					},
+					Publisher: emptyBlessings,
+					Env:       []string{"ENV=V"},
+					Args:      []string{"FLAG=V"},
+				},
+				map[string]int64{
+					"binary":                        binarySize,
+					"packages/file1.txt":            7,
+					"packages/file2":                4,
+					"packages/dir1":                 10,
+					"overridepackages/overridepkg1": 5,
+					"overridepackages/overridepkg2": 3,
+					"overridepackages/overridepkg3": 8,
+				},
+			},
+		},
+	} {
+		const appId = "myBestAppID"
+		rootTape.SetResponses(InstallResponse{appId, nil})
+		if c.config != nil {
+			jsonConfig, err := json.Marshal(c.config)
+			if err != nil {
+				t.Fatalf("test case %d: Marshal(%v) failed: %v", i, c.config, err)
+			}
+			c.args = append([]string{fmt.Sprintf("--config=%s", string(jsonConfig))}, c.args...)
+		}
+		if c.packages != nil {
+			jsonPackages, err := json.Marshal(c.packages)
+			if err != nil {
+				t.Fatalf("test case %d: Marshal(%v) failed: %v", i, c.packages, err)
+			}
+			c.args = append([]string{fmt.Sprintf("--packages=%s", string(jsonPackages))}, c.args...)
+		}
+		c.args = append([]string{"install-local"}, c.args...)
+		if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, c.args); err != nil {
+			t.Fatalf("test case %d: %v", i, err)
+		}
+		if expected, got := naming.Join(deviceName, appId), strings.TrimSpace(stdout.String()); got != expected {
+			t.Fatalf("test case %d: Unexpected output from Install. Got %q, expected %q", i, got, expected)
+		}
+		if got, expected := rootTape.Play(), []interface{}{c.expectedTape}; !reflect.DeepEqual(expected, got) {
+			t.Errorf("test case %d: Invalid call sequence. Got %#v, want %#v", i, got, expected)
+		}
+		rootTape.Rewind()
+		stdout.Reset()
+		stderr.Reset()
+	}
+}
diff --git a/services/device/device/ls.go b/services/device/device/ls.go
new file mode 100644
index 0000000..6487105
--- /dev/null
+++ b/services/device/device/ls.go
@@ -0,0 +1,32 @@
+// 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 (
+	"fmt"
+	"io"
+
+	"v.io/v23/context"
+
+	"v.io/x/lib/cmdline"
+)
+
+var cmdLs = &cmdline.Command{
+	Name:     "ls",
+	Short:    "List applications.",
+	Long:     "List application installations or instances.",
+	ArgsName: "<app name patterns...>",
+	ArgsLong: `
+<app name patterns...> are vanadium object names or glob name patterns corresponding to application installations and instances.`,
+}
+
+func init() {
+	globify(cmdLs, runLs, new(GlobSettings))
+}
+
+func runLs(entry GlobResult, _ *context.T, stdout, _ io.Writer) error {
+	fmt.Fprintf(stdout, "%v\n", entry.Name)
+	return nil
+}
diff --git a/services/device/device/ls_test.go b/services/device/device/ls_test.go
new file mode 100644
index 0000000..79974b1
--- /dev/null
+++ b/services/device/device/ls_test.go
@@ -0,0 +1,160 @@
+// 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_test
+
+import (
+	"bytes"
+	"strings"
+	"testing"
+
+	"v.io/v23/naming"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/v23cmd"
+	"v.io/x/ref/lib/xrpc"
+	"v.io/x/ref/test"
+
+	cmd_device "v.io/x/ref/services/device/device"
+	"v.io/x/ref/services/internal/servicetest"
+)
+
+// TestLsCommand verifies the device ls command.  It also acts as a test for the
+// glob functionality, by trying out various combinations of
+// instances/installations in glob results.
+func TestLsCommand(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	tapes := servicetest.NewTapeMap()
+	server, err := xrpc.NewDispatchingServer(ctx, "", newDispatcher(t, tapes))
+	if err != nil {
+		t.Fatalf("NewServer failed: %v", err)
+	}
+	cmd := cmd_device.CmdRoot
+	endpoint := server.Status().Endpoints[0]
+	appName := naming.JoinAddressName(endpoint.String(), "app")
+	rootTape := tapes.ForSuffix("")
+	cannedGlobResponses := [][]string{
+		[]string{"app/3", "app/4", "app/6", "app/5"},
+		[]string{"app/2", "app/1"},
+	}
+	cannedStatusResponses := map[string][]interface{}{
+		"app/1": []interface{}{instanceRunning},
+		"app/2": []interface{}{installationUninstalled},
+		"app/3": []interface{}{instanceUpdating},
+		"app/4": []interface{}{installationActive},
+		"app/5": []interface{}{instanceNotRunning},
+		"app/6": []interface{}{installationActive},
+	}
+	for _, c := range []struct {
+		globResponses   [][]string
+		statusResponses map[string][]interface{}
+		lsFlags         []string
+		globPatterns    []string
+		expected        string
+	}{
+		{
+			cannedGlobResponses,
+			cannedStatusResponses,
+			[]string{},
+			[]string{"glob1", "glob2"},
+			joinLines(appName+"/2", appName+"/4", appName+"/6", appName+"/1", appName+"/3", appName+"/5"),
+		},
+		{
+			cannedGlobResponses,
+			cannedStatusResponses,
+			[]string{"--only-instances"},
+			[]string{"glob1", "glob2"},
+			joinLines(appName+"/1", appName+"/3", appName+"/5"),
+		},
+		{
+			cannedGlobResponses,
+			cannedStatusResponses,
+			[]string{"--only-installations"},
+			[]string{"glob1", "glob2"},
+			joinLines(appName+"/2", appName+"/4", appName+"/6"),
+		},
+		{
+			cannedGlobResponses,
+			cannedStatusResponses,
+			[]string{"--instance-state=Running,Updating"},
+			[]string{"glob1", "glob2"},
+			joinLines(appName+"/2", appName+"/4", appName+"/6", appName+"/1", appName+"/3"),
+		},
+		{
+			cannedGlobResponses,
+			cannedStatusResponses,
+			[]string{"--installation-state=Active"},
+			[]string{"glob1", "glob2"},
+			joinLines(appName+"/4", appName+"/6", appName+"/1", appName+"/3", appName+"/5"),
+		},
+		{
+			cannedGlobResponses,
+			cannedStatusResponses,
+			[]string{"--only-installations", "--installation-state=Active"},
+			[]string{"glob1", "glob2"},
+			joinLines(appName+"/4", appName+"/6"),
+		},
+		{
+			cannedGlobResponses,
+			cannedStatusResponses,
+			[]string{"--only-instances", "--installation-state=Active"},
+			[]string{"glob1", "glob2"},
+			joinLines(appName+"/1", appName+"/3", appName+"/5"),
+		},
+		{
+			[][]string{
+				[]string{"app/1", "app/2"},
+				[]string{"app/2", "app/3"},
+				[]string{"app/2", "app/3"},
+			},
+			map[string][]interface{}{
+				"app/1": []interface{}{instanceRunning},
+				"app/2": []interface{}{installationUninstalled, installationUninstalled},
+				"app/3": []interface{}{instanceUpdating},
+			},
+			[]string{},
+			[]string{"glob1", "glob2"},
+			joinLines(appName+"/2", appName+"/2", appName+"/1", appName+"/3"),
+		},
+		{
+			[][]string{
+				[]string{"app/1", "app/2"},
+				[]string{"app/2", "app/3"},
+				[]string{"app/2", "app/3"},
+			},
+			map[string][]interface{}{
+				"app/1": []interface{}{instanceRunning},
+				"app/2": []interface{}{installationUninstalled, installationUninstalled, installationUninstalled},
+				"app/3": []interface{}{instanceUpdating, instanceUpdating},
+			},
+			[]string{"--only-installations"},
+			[]string{"glob1", "glob2", "glob3"},
+			joinLines(appName+"/2", appName+"/2", appName+"/2"),
+		},
+	} {
+		var stdout, stderr bytes.Buffer
+		env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
+		tapes.Rewind()
+		var rootTapeResponses []interface{}
+		for _, r := range c.globResponses {
+			rootTapeResponses = append(rootTapeResponses, GlobResponse{results: r})
+		}
+		rootTape.SetResponses(rootTapeResponses...)
+		for n, r := range c.statusResponses {
+			tapes.ForSuffix(n).SetResponses(r...)
+		}
+		args := append([]string{"ls"}, c.lsFlags...)
+		for _, p := range c.globPatterns {
+			args = append(args, naming.JoinAddressName(endpoint.String(), p))
+		}
+		if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, args); err != nil {
+			t.Errorf("%v", err)
+		}
+
+		if expected, got := c.expected, strings.TrimSpace(stdout.String()); got != expected {
+			t.Errorf("Unexpected output from ls. Got %q, expected %q", got, expected)
+		}
+		cmd_device.ResetGlobSettings()
+	}
+}
diff --git a/services/device/device/publish.go b/services/device/device/publish.go
new file mode 100644
index 0000000..fb5b6a9
--- /dev/null
+++ b/services/device/device/publish.go
@@ -0,0 +1,256 @@
+// 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 (
+	"fmt"
+	"os"
+	"path/filepath"
+	"regexp"
+	"runtime"
+	"strings"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/security"
+	"v.io/v23/security/access"
+	"v.io/v23/services/application"
+	"v.io/v23/services/permissions"
+	"v.io/v23/verror"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/v23cmd"
+	"v.io/x/ref/services/internal/binarylib"
+	"v.io/x/ref/services/repository"
+)
+
+// TODO(caprita): Add unit test.
+
+// TODO(caprita): Extend to include env, args, packages.
+
+var cmdPublish = &cmdline.Command{
+	Runner: v23cmd.RunnerFunc(runPublish),
+	Name:   "publish",
+	Short:  "Publish the given application(s).",
+	Long: `
+Publishes the given application(s) to the binary and application servers.
+The binaries should be in $V23_ROOT/release/go/bin/[<GOOS>_<GOARCH>] by default (can be overrriden with --from).
+By default the binary name is used as the name of the application envelope, and as the
+title in the envelope. However, <envelope-name> and <title> can be specified explicitly
+using :<envelope-name> and @<title>.
+The binary is published as <binserv>/<binary name>/<GOOS>-<GOARCH>/<TIMESTAMP>.
+The application envelope is published as <appserv>/<envelope-name>/0.
+Optionally, adds blessing patterns to the Read and Resolve AccessLists.`,
+	ArgsName: "<binary name>[:<envelope-name>][@<title>] ...",
+}
+
+var binaryService, applicationService, readBlessings, goarchFlag, goosFlag, fromFlag string
+var addPublisher bool
+var minValidPublisherDuration time.Duration
+
+func init() {
+	cmdPublish.Flags.StringVar(&binaryService, "binserv", "binaries", "Name of binary service.")
+	cmdPublish.Flags.StringVar(&applicationService, "appserv", "applications", "Name of application service.")
+	cmdPublish.Flags.StringVar(&goosFlag, "goos", runtime.GOOS, "GOOS for application.  The default is the value of runtime.GOOS.")
+	cmdPublish.Flags.Lookup("goos").DefValue = "<runtime.GOOS>"
+	cmdPublish.Flags.StringVar(&goarchFlag, "goarch", runtime.GOARCH, "GOARCH for application.  The default is the value of runtime.GOARCH.")
+	cmdPublish.Flags.Lookup("goarch").DefValue = "<runtime.GOARCH>"
+	cmdPublish.Flags.StringVar(&readBlessings, "readers", "dev.v.io", "If non-empty, comma-separated blessing patterns to add to Read and Resolve AccessList.")
+	cmdPublish.Flags.BoolVar(&addPublisher, "add-publisher", true, "If true, add a publisher blessing to the application envelope")
+	cmdPublish.Flags.DurationVar(&minValidPublisherDuration, "publisher-min-validity", 30*time.Hour, "Publisher blessings that are valid for less than this amount of time are considered invalid")
+	cmdPublish.Flags.StringVar(&fromFlag, "from", "", "Location of binaries to be published.  Defaults to $V23_ROOT/release/go/bin/[<GOOS>_<GOARCH>]")
+}
+
+func setAccessLists(ctx *context.T, env *cmdline.Env, von string) error {
+	if readBlessings == "" {
+		return nil
+	}
+	perms, version, err := permissions.ObjectClient(von).GetPermissions(ctx)
+	if err != nil {
+		// TODO(caprita): This is a workaround until we sort out the
+		// default AccessLists for applicationd (see issue #1317).  At that
+		// time, uncomment the line below.
+		//
+		//   return err
+		perms = make(access.Permissions)
+	}
+	for _, blessing := range strings.Split(readBlessings, ",") {
+		for _, tag := range []access.Tag{access.Read, access.Resolve} {
+			perms.Add(security.BlessingPattern(blessing), string(tag))
+		}
+	}
+	if err := permissions.ObjectClient(von).SetPermissions(ctx, perms, version); err != nil {
+		return err
+	}
+	fmt.Fprintf(env.Stdout, "Added patterns %q to Read,Resolve AccessList for %q\n", readBlessings, von)
+	return nil
+}
+
+func publishOne(ctx *context.T, env *cmdline.Env, binPath, binary string) error {
+	binaryName, envelopeName, title := binary, binary, binary
+	binaryRE := regexp.MustCompile(`^([^:@]+)(:[^@]+)?(@.+)?$`)
+	if parts := binaryRE.FindStringSubmatch(binary); len(parts) == 4 {
+		binaryName = parts[1]
+		envelopeName, title = binaryName, binaryName
+		if len(parts[2]) > 1 {
+			envelopeName = parts[2][1:]
+		}
+		if len(parts[3]) > 1 {
+			title = parts[3][1:]
+		}
+	} else {
+		return fmt.Errorf("invalid binary spec (%v)", binary)
+	}
+
+	// Step 1, upload the binary to the binary service.
+
+	// TODO(caprita): Instead of the current timestamp, use each binary's
+	// BuildTimestamp from the buildinfo.
+	timestamp := time.Now().UTC().Format(time.RFC3339)
+	binaryVON := naming.Join(binaryService, binaryName, fmt.Sprintf("%s-%s", goosFlag, goarchFlag), timestamp)
+	binaryFile := filepath.Join(binPath, binaryName)
+	var binarySig *security.Signature
+	var err error
+	for i := 0; ; i++ {
+		binarySig, err = binarylib.UploadFromFile(ctx, binaryVON, binaryFile)
+		if verror.ErrorID(err) == verror.ErrExist.ID {
+			newTS := fmt.Sprintf("%s-%d", timestamp, i+1)
+			binaryVON = naming.Join(binaryService, binaryName, fmt.Sprintf("%s-%s", goosFlag, goarchFlag), newTS)
+			continue
+		}
+		if err == nil {
+			break
+		}
+		return err
+	}
+	fmt.Fprintf(env.Stdout, "Binary %q uploaded from file %s\n", binaryVON, binaryFile)
+
+	// Step 2, set the perms for the uploaded binary.
+
+	if err := setAccessLists(ctx, env, binaryVON); err != nil {
+		return err
+	}
+
+	// Step 3, download existing envelope (or create a new one), update, and
+	// upload to application service.
+
+	// TODO(caprita): use the profile detection machinery and/or let user
+	// specify profiles by hand.
+	profiles := []string{fmt.Sprintf("%s-%s", goosFlag, goarchFlag)}
+	// TODO(caprita): use a label e.g. "prod" instead of "0".
+	appVON := naming.Join(applicationService, envelopeName, "0")
+	appClient := repository.ApplicationClient(appVON)
+	// NOTE: If profiles contains more than one entry, this will return only
+	// the first match.  But presumably that's ok, since we're going to set
+	// the envelopes for all the profiles to the same envelope anyway below.
+	envelope, err := appClient.Match(ctx, profiles)
+	if verror.ErrorID(err) == verror.ErrNoExist.ID {
+		// There was nothing published yet, create a new envelope.
+		envelope = application.Envelope{Title: title}
+	} else if err != nil {
+		return err
+	} else {
+		// We are going to be updating an existing envelope
+
+		// Complain if a title was specified explicitly and does not match the one in the
+		// envelope, because we are not going to update the one in the envelope
+		if title != binaryName && title != envelope.Title {
+			return fmt.Errorf("Specified title (%v) does not match title in existing envelope (%v)", title, envelope.Title)
+		}
+	}
+
+	envelope.Binary.File = binaryVON
+	if addPublisher {
+		publisher, err := getPublisherBlessing(ctx, "apps/published/"+title)
+		if err != nil {
+			return err
+		}
+		envelope.Publisher = publisher
+		envelope.Binary.Signature = *binarySig
+	} else {
+		// We must explicitly clear these fields because we might be trying to update
+		// an envelope that previously pointed at a signed binary.
+		envelope.Binary.Signature = security.Signature{}
+		envelope.Publisher = security.Blessings{}
+	}
+
+	if err := repository.ApplicationClient(appVON).Put(ctx, profiles, envelope); err != nil {
+		return err
+	}
+	fmt.Fprintf(env.Stdout, "Published %q\n", appVON)
+
+	// Step 4, set the perms for the uploaded envelope.
+
+	if err := setAccessLists(ctx, env, appVON); err != nil {
+		return err
+	}
+	return nil
+}
+
+func runPublish(ctx *context.T, env *cmdline.Env, args []string) error {
+	if expectedMin, got := 1, len(args); got < expectedMin {
+		return env.UsageErrorf("publish: incorrect number of arguments, expected at least %d, got %d", expectedMin, got)
+	}
+	binaries := args
+	binPath := fromFlag
+	if binPath == "" {
+		vroot := env.Vars["V23_ROOT"]
+		if vroot == "" {
+			return env.UsageErrorf("publish: $V23_ROOT environment variable should be set")
+		}
+		binPath = filepath.Join(vroot, "release/go/bin")
+		if goosFlag != runtime.GOOS || goarchFlag != runtime.GOARCH {
+			binPath = filepath.Join(binPath, fmt.Sprintf("%s_%s", goosFlag, goarchFlag))
+		}
+	}
+	if fi, err := os.Stat(binPath); err != nil {
+		return env.UsageErrorf("publish: failed to stat %v: %v", binPath, err)
+	} else if !fi.IsDir() {
+		return env.UsageErrorf("publish: %v is not a directory", binPath)
+	}
+	if binaryService == "" {
+		return env.UsageErrorf("publish: --binserv must point to a binary service name")
+	}
+	if applicationService == "" {
+		return env.UsageErrorf("publish: --appserv must point to an application service name")
+	}
+	var lastErr error
+	for _, b := range binaries {
+		if err := publishOne(ctx, env, binPath, b); err != nil {
+			fmt.Fprintf(env.Stderr, "Failed to publish %q: %v\n", b, err)
+			lastErr = err
+		}
+	}
+	return lastErr
+}
+
+func getPublisherBlessing(ctx *context.T, extension string) (security.Blessings, error) {
+	p := v23.GetPrincipal(ctx)
+	b, err := p.Bless(p.PublicKey(), p.BlessingStore().Default(), extension, security.UnconstrainedUse())
+
+	if err != nil {
+		return security.Blessings{}, err
+	}
+
+	// We need to make sure that the blessing is usable as a publisher blessing -- in
+	// practice this current means that it has no caveats other than expiration. We
+	// test this by putting it into a call object and verifying that the blessing will
+	// not be rejected
+	call := security.NewCall(&security.CallParams{
+		RemoteBlessings: b,
+		LocalBlessings:  p.BlessingStore().Default(),
+		LocalPrincipal:  p,
+		Timestamp:       time.Now().Add(minValidPublisherDuration),
+	})
+	accepted, rejected := security.RemoteBlessingNames(ctx, call)
+	if len(accepted) == 0 {
+		return security.Blessings{}, fmt.Errorf("All blessings are invalid: %v", rejected)
+	}
+	if len(rejected) > 0 {
+		fmt.Fprintf(os.Stderr, "Warning: Some invalid blessings are present: %v", rejected)
+	}
+	return b, nil
+}
diff --git a/services/device/device/root.go b/services/device/device/root.go
new file mode 100644
index 0000000..6caa02e
--- /dev/null
+++ b/services/device/device/root.go
@@ -0,0 +1,29 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go .
+
+package main
+
+import (
+	"regexp"
+
+	"v.io/x/lib/cmdline"
+	_ "v.io/x/ref/runtime/factories/static"
+)
+
+var CmdRoot = &cmdline.Command{
+	Name:  "device",
+	Short: "facilitates interaction with the Vanadium device manager",
+	Long: `
+Command device facilitates interaction with the Vanadium device manager.
+`,
+	Children: []*cmdline.Command{cmdInstall, cmdInstallLocal, cmdUninstall, cmdAssociate, cmdDescribe, cmdClaim, cmdInstantiate, cmdDelete, cmdRun, cmdKill, cmdRevert, cmdUpdate, cmdStatus, cmdDebug, cmdACL, cmdPublish, cmdLs},
+}
+
+func main() {
+	cmdline.HideGlobalFlagsExcept(regexp.MustCompile(`^((v23\.namespace\.root)|(v23\.proxy))$`))
+	cmdline.Main(CmdRoot)
+}
diff --git a/services/device/device/run.go b/services/device/device/run.go
new file mode 100644
index 0000000..6528708
--- /dev/null
+++ b/services/device/device/run.go
@@ -0,0 +1,37 @@
+// 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 (
+	"fmt"
+
+	"v.io/v23/context"
+	"v.io/v23/services/device"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/v23cmd"
+)
+
+var cmdRun = &cmdline.Command{
+	Runner:   v23cmd.RunnerFunc(runRun),
+	Name:     "run",
+	Short:    "Run the given application instance.",
+	Long:     "Run the given application instance.",
+	ArgsName: "<app instance>",
+	ArgsLong: `
+<app instance> is the vanadium object name of the application instance to run.`,
+}
+
+func runRun(ctx *context.T, env *cmdline.Env, args []string) error {
+	if expected, got := 1, len(args); expected != got {
+		return env.UsageErrorf("run: incorrect number of arguments, expected %d, got %d", expected, got)
+	}
+	appName := args[0]
+
+	if err := device.ApplicationClient(appName).Run(ctx); err != nil {
+		return fmt.Errorf("Run failed: %v,\nView log with:\n debug logs read `debug glob %s/logs/STDERR-*`", err, appName)
+	}
+	fmt.Fprintf(env.Stdout, "Run succeeded\n")
+	return nil
+}
diff --git a/services/device/device/run_test.go b/services/device/device/run_test.go
new file mode 100644
index 0000000..7d9f5d9
--- /dev/null
+++ b/services/device/device/run_test.go
@@ -0,0 +1,11 @@
+// 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_test
+
+import "testing"
+
+func TestRunCommand(t *testing.T) {
+	testHelper(t, "run", "Run")
+}
diff --git a/services/device/device/status.go b/services/device/device/status.go
new file mode 100644
index 0000000..bb576c9
--- /dev/null
+++ b/services/device/device/status.go
@@ -0,0 +1,41 @@
+// 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 (
+	"fmt"
+	"io"
+
+	"v.io/v23/context"
+	"v.io/v23/services/device"
+	"v.io/x/lib/cmdline"
+)
+
+var cmdStatus = &cmdline.Command{
+	Name:     "status",
+	Short:    "Get device manager or application status.",
+	Long:     "Get the status of the device manager or application instances and installations.",
+	ArgsName: "<name patterns...>",
+	ArgsLong: `
+<name patterns...> are vanadium object names or glob name patterns corresponding to the device manager service, or to application installations and instances.`,
+}
+
+func init() {
+	globify(cmdStatus, runStatus, new(GlobSettings))
+}
+
+func runStatus(entry GlobResult, _ *context.T, stdout, _ io.Writer) error {
+	switch s := entry.Status.(type) {
+	case device.StatusInstance:
+		fmt.Fprintf(stdout, "Instance %v [State:%v,Version:%v]\n", entry.Name, s.Value.State, s.Value.Version)
+	case device.StatusInstallation:
+		fmt.Fprintf(stdout, "Installation %v [State:%v,Version:%v]\n", entry.Name, s.Value.State, s.Value.Version)
+	case device.StatusDevice:
+		fmt.Fprintf(stdout, "Device Service %v [State:%v,Version:%v]\n", entry.Name, s.Value.State, s.Value.Version)
+	default:
+		return fmt.Errorf("Status returned unknown type: %T", s)
+	}
+	return nil
+}
diff --git a/services/device/device/status_test.go b/services/device/device/status_test.go
new file mode 100644
index 0000000..81a1410
--- /dev/null
+++ b/services/device/device/status_test.go
@@ -0,0 +1,76 @@
+// 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_test
+
+import (
+	"bytes"
+	"fmt"
+	"reflect"
+	"strings"
+	"testing"
+
+	"v.io/v23/naming"
+	"v.io/v23/services/device"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/v23cmd"
+	"v.io/x/ref/lib/xrpc"
+	"v.io/x/ref/test"
+
+	cmd_device "v.io/x/ref/services/device/device"
+	"v.io/x/ref/services/internal/servicetest"
+)
+
+func TestStatusCommand(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	tapes := servicetest.NewTapeMap()
+	server, err := xrpc.NewDispatchingServer(ctx, "", newDispatcher(t, tapes))
+	if err != nil {
+		t.Fatalf("NewServer failed: %v", err)
+	}
+
+	cmd := cmd_device.CmdRoot
+	addr := server.Status().Endpoints[0].String()
+	globName := naming.JoinAddressName(addr, "glob")
+	appName := naming.JoinAddressName(addr, "app")
+
+	rootTape, appTape := tapes.ForSuffix(""), tapes.ForSuffix("app")
+	for _, c := range []struct {
+		tapeResponse device.Status
+		expected     string
+	}{
+		{
+			installationUninstalled,
+			fmt.Sprintf("Installation %v [State:Uninstalled,Version:director's cut]", appName),
+		},
+		{
+			instanceUpdating,
+			fmt.Sprintf("Instance %v [State:Updating,Version:theatrical version]", appName),
+		},
+		{
+			deviceService,
+			fmt.Sprintf("Device Service %v [State:Running,Version:han shot first]", appName),
+		},
+	} {
+		var stdout, stderr bytes.Buffer
+		env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
+		tapes.Rewind()
+		rootTape.SetResponses(GlobResponse{results: []string{"app"}})
+		appTape.SetResponses(c.tapeResponse)
+		if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"status", globName}); err != nil {
+			t.Errorf("%v", err)
+		}
+		if expected, got := c.expected, strings.TrimSpace(stdout.String()); got != expected {
+			t.Errorf("Unexpected output from status. Got %q, expected %q", got, expected)
+		}
+		if got, expected := rootTape.Play(), []interface{}{GlobStimulus{"glob"}}; !reflect.DeepEqual(expected, got) {
+			t.Errorf("invalid call sequence. Got %v, want %v", got, expected)
+		}
+		if got, expected := appTape.Play(), []interface{}{"Status"}; !reflect.DeepEqual(expected, got) {
+			t.Errorf("invalid call sequence. Got %v, want %v", got, expected)
+		}
+		cmd_device.ResetGlobSettings()
+	}
+}
diff --git a/services/device/device/uninstall.go b/services/device/device/uninstall.go
new file mode 100644
index 0000000..9beb224
--- /dev/null
+++ b/services/device/device/uninstall.go
@@ -0,0 +1,38 @@
+// 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 (
+	"fmt"
+
+	"v.io/v23/context"
+	"v.io/v23/services/device"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/v23cmd"
+)
+
+var cmdUninstall = &cmdline.Command{
+	Runner:   v23cmd.RunnerFunc(runUninstall),
+	Name:     "uninstall",
+	Short:    "Uninstall the given application installation.",
+	Long:     "Uninstall the given application installation.",
+	ArgsName: "<installation>",
+	ArgsLong: `
+<installation> is the vanadium object name of the application installation to
+uninstall.
+`,
+}
+
+func runUninstall(ctx *context.T, env *cmdline.Env, args []string) error {
+	if expected, got := 1, len(args); expected != got {
+		return env.UsageErrorf("uninstall: incorrect number of arguments, expected %d, got %d", expected, got)
+	}
+	installName := args[0]
+	if err := device.ApplicationClient(installName).Uninstall(ctx); err != nil {
+		return fmt.Errorf("Uninstall failed: %v", err)
+	}
+	fmt.Fprintf(env.Stdout, "Successfully uninstalled: %q\n", installName)
+	return nil
+}
diff --git a/services/device/device/update.go b/services/device/device/update.go
new file mode 100644
index 0000000..e05b412
--- /dev/null
+++ b/services/device/device/update.go
@@ -0,0 +1,161 @@
+// 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
+
+// TODO(caprita): Rename to update_revert.go
+
+import (
+	"fmt"
+	"io"
+	"time"
+
+	"v.io/v23/context"
+	"v.io/v23/services/device"
+	"v.io/v23/verror"
+
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/services/device/internal/errors"
+)
+
+var cmdUpdate = &cmdline.Command{
+	Name:     "update",
+	Short:    "Update the device manager or applications.",
+	Long:     "Update the device manager or application instances and installations",
+	ArgsName: "<name patterns...>",
+	ArgsLong: `
+<name patterns...> are vanadium object names or glob name patterns corresponding to the device manager service, or to application installations and instances.`,
+}
+
+func init() {
+	globify(cmdUpdate, runUpdate, &GlobSettings{
+		HandlerParallelism:      KindParallelism,
+		InstanceStateFilter:     ExcludeInstanceStates(device.InstanceStateDeleted),
+		InstallationStateFilter: ExcludeInstallationStates(device.InstallationStateUninstalled),
+	})
+}
+
+var cmdRevert = &cmdline.Command{
+	Name:     "revert",
+	Short:    "Revert the device manager or applications.",
+	Long:     "Revert the device manager or application instances and installations to a previous version of their current version",
+	ArgsName: "<name patterns...>",
+	ArgsLong: `
+<name patterns...> are vanadium object names or glob name patterns corresponding to the device manager service, or to application installations and instances.`,
+}
+
+func init() {
+	globify(cmdRevert, runRevert, &GlobSettings{
+		HandlerParallelism:      KindParallelism,
+		InstanceStateFilter:     ExcludeInstanceStates(device.InstanceStateDeleted),
+		InstallationStateFilter: ExcludeInstallationStates(device.InstallationStateUninstalled),
+	})
+}
+
+func instanceIsRunning(ctx *context.T, von string) (bool, error) {
+	status, err := device.ApplicationClient(von).Status(ctx)
+	if err != nil {
+		return false, fmt.Errorf("Failed to get status for instance %q: %v", von, err)
+	}
+	s, ok := status.(device.StatusInstance)
+	if !ok {
+		return false, fmt.Errorf("Status for instance %q of wrong type (%T)", von, status)
+	}
+	return s.Value.State == device.InstanceStateRunning, nil
+}
+
+var revertOrUpdate = map[bool]string{true: "revert", false: "update"}
+var revertOrUpdateMethod = map[bool]string{true: "Revert", false: "Update"}
+var revertOrUpdateNoOp = map[bool]string{true: "no previous version available", false: "already up to date"}
+
+func changeVersionInstance(ctx *context.T, stdout, stderr io.Writer, name string, status device.StatusInstance, revert bool) (retErr error) {
+	if status.Value.State == device.InstanceStateRunning {
+		if err := device.ApplicationClient(name).Kill(ctx, killDeadline); err != nil {
+			// Check the app's state again in case we killed it,
+			// nevermind any errors.  The sleep is because Kill
+			// currently (4/29/15) returns asynchronously with the
+			// device manager shooting the app down.
+			time.Sleep(time.Second)
+			running, rerr := instanceIsRunning(ctx, name)
+			if rerr != nil {
+				return rerr
+			}
+			if running {
+				return fmt.Errorf("Kill failed: %v", err)
+			}
+			fmt.Fprintf(stderr, "WARNING for \"%s\": recovered from Kill error (%s). Proceeding with %s.\n", name, err, revertOrUpdate[revert])
+		}
+		// App was running, and we killed it, so we need to run it again
+		// after the update/revert.
+		defer func() {
+			if err := device.ApplicationClient(name).Run(ctx); err != nil {
+				err = fmt.Errorf("Run failed: %v", err)
+				if retErr == nil {
+					retErr = err
+				} else {
+					fmt.Fprintf(stderr, "ERROR for \"%s\": %v.\n", name, err)
+				}
+			}
+		}()
+	}
+	// Update/revert the instance.
+	var err error
+	if revert {
+		err = device.ApplicationClient(name).Revert(ctx)
+	} else {
+		err = device.ApplicationClient(name).Update(ctx)
+	}
+	switch {
+	case err == nil:
+		fmt.Fprintf(stdout, "Successful %s of version for instance \"%s\".\n", revertOrUpdate[revert], name)
+		return nil
+	case verror.ErrorID(err) == errors.ErrUpdateNoOp.ID:
+		// TODO(caprita): Ideally, we wouldn't even attempt a kill /
+		// restart if the update/revert is a no-op.
+		fmt.Fprintf(stdout, "Instance \"%s\": %s.\n", name, revertOrUpdateNoOp[revert])
+		return nil
+	default:
+		return fmt.Errorf("%s failed: %v", revertOrUpdateMethod[revert], err)
+	}
+}
+
+func changeVersionOne(ctx *context.T, what string, stdout, stderr io.Writer, name string, revert bool) error {
+	var err error
+	if revert {
+		err = device.ApplicationClient(name).Revert(ctx)
+	} else {
+		err = device.ApplicationClient(name).Update(ctx)
+	}
+	switch {
+	case err == nil:
+		fmt.Fprintf(stdout, "Successful %s of version for %s \"%s\".\n", revertOrUpdate[revert], what, name)
+		return nil
+	case verror.ErrorID(err) == errors.ErrUpdateNoOp.ID:
+		fmt.Fprintf(stdout, "%s \"%s\": %s.\n", what, name, revertOrUpdateNoOp[revert])
+		return nil
+	default:
+		return fmt.Errorf("%s failed: %v", revertOrUpdateMethod[revert], err)
+	}
+}
+
+func changeVersion(entry GlobResult, ctx *context.T, stdout, stderr io.Writer, revert bool) error {
+	switch entry.Kind {
+	case ApplicationInstanceObject:
+		return changeVersionInstance(ctx, stdout, stderr, entry.Name, entry.Status.(device.StatusInstance), revert)
+	case ApplicationInstallationObject:
+		return changeVersionOne(ctx, "installation", stdout, stderr, entry.Name, revert)
+	case DeviceServiceObject:
+		return changeVersionOne(ctx, "device service", stdout, stderr, entry.Name, revert)
+	default:
+		return fmt.Errorf("unhandled object kind %v", entry.Kind)
+	}
+}
+
+func runUpdate(entry GlobResult, ctx *context.T, stdout, stderr io.Writer) error {
+	return changeVersion(entry, ctx, stdout, stderr, false)
+}
+
+func runRevert(entry GlobResult, ctx *context.T, stdout, stderr io.Writer) error {
+	return changeVersion(entry, ctx, stdout, stderr, true)
+}
diff --git a/services/device/device/update_test.go b/services/device/device/update_test.go
new file mode 100644
index 0000000..338c358
--- /dev/null
+++ b/services/device/device/update_test.go
@@ -0,0 +1,159 @@
+// 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_test
+
+// TODO(caprita): Rename to update_revert_test.go
+
+import (
+	"bytes"
+	"fmt"
+	"reflect"
+	"strings"
+	"testing"
+	"time"
+	"unicode"
+	"unicode/utf8"
+
+	"v.io/v23/naming"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/v23cmd"
+	"v.io/x/ref/lib/xrpc"
+	"v.io/x/ref/test"
+
+	cmd_device "v.io/x/ref/services/device/device"
+	"v.io/x/ref/services/internal/servicetest"
+)
+
+func capitalize(s string) string {
+	r, size := utf8.DecodeRuneInString(s)
+	if r == utf8.RuneError {
+		return ""
+	}
+	return string(unicode.ToUpper(r)) + s[size:]
+}
+
+// TestUpdateAndRevertCommands verifies the device update and revert commands.
+func TestUpdateAndRevertCommands(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	tapes := servicetest.NewTapeMap()
+	server, err := xrpc.NewDispatchingServer(ctx, "", newDispatcher(t, tapes))
+	if err != nil {
+		t.Fatalf("NewServer failed: %v", err)
+	}
+	addr := server.Status().Endpoints[0].String()
+	root := cmd_device.CmdRoot
+	appName := naming.JoinAddressName(addr, "app")
+	rootTape := tapes.ForSuffix("")
+	globName := naming.JoinAddressName(addr, "glob")
+	// TODO(caprita): Move joinLines to a common place.
+	joinLines := func(args ...string) string {
+		return strings.Join(args, "\n")
+	}
+	for _, cmd := range []string{"update", "revert"} {
+		for _, c := range []struct {
+			globResponses   []string
+			statusResponses map[string][]interface{}
+			expectedStimuli map[string][]interface{}
+			expectedStdout  string
+			expectedStderr  string
+			expectedError   string
+		}{
+			{ // Everything succeeds.
+				[]string{"app/2", "app/1", "app/5", "app/3", "app/4"},
+				map[string][]interface{}{
+					"app/1": []interface{}{instanceRunning, nil, nil, nil},
+					"app/2": []interface{}{instanceNotRunning, nil},
+					"app/3": []interface{}{installationActive, nil},
+					// The uninstalled installation and the
+					// deleted instance should be excluded
+					// from the Update and Revert as per the
+					// default GlobSettings for the update
+					// and revert commands.
+					"app/4": []interface{}{installationUninstalled, nil},
+					"app/5": []interface{}{instanceDeleted, nil},
+				},
+				map[string][]interface{}{
+					"app/1": []interface{}{"Status", KillStimulus{"Kill", 10 * time.Second}, capitalize(cmd), "Run"},
+					"app/2": []interface{}{"Status", capitalize(cmd)},
+					"app/3": []interface{}{"Status", capitalize(cmd)},
+				},
+				joinLines(
+					fmt.Sprintf("Successful %s of version for installation \"%s/3\".", cmd, appName),
+					fmt.Sprintf("Successful %s of version for instance \"%s/1\".", cmd, appName),
+					fmt.Sprintf("Successful %s of version for instance \"%s/2\".", cmd, appName)),
+				"",
+				"",
+			},
+			{ // Assorted failure modes.
+				[]string{"app/1", "app/2", "app/3", "app/4", "app/5"},
+				map[string][]interface{}{
+					// Starts as running, fails Kill, but then
+					// recovers. This ultimately counts as a success.
+					"app/1": []interface{}{instanceRunning, fmt.Errorf("Simulate Kill failing"), instanceNotRunning, nil, nil},
+					// Starts as running, fails Kill, and stays running.
+					"app/2": []interface{}{instanceRunning, fmt.Errorf("Simulate Kill failing"), instanceRunning},
+					// Starts as running, Kill and Update succeed, but Run fails.
+					"app/3": []interface{}{instanceRunning, nil, nil, fmt.Errorf("Simulate Run failing")},
+					// Starts as running, Kill succeeds, Update fails, but Run succeeds.
+					"app/4": []interface{}{instanceRunning, nil, fmt.Errorf("Simulate %s failing", capitalize(cmd)), nil},
+					// Starts as running, Kill succeeds, Update fails, and Run fails.
+					"app/5": []interface{}{instanceRunning, nil, fmt.Errorf("Simulate %s failing", capitalize(cmd)), fmt.Errorf("Simulate Run failing")},
+				},
+				map[string][]interface{}{
+					"app/1": []interface{}{"Status", KillStimulus{"Kill", 10 * time.Second}, "Status", capitalize(cmd), "Run"},
+					"app/2": []interface{}{"Status", KillStimulus{"Kill", 10 * time.Second}, "Status"},
+					"app/3": []interface{}{"Status", KillStimulus{"Kill", 10 * time.Second}, capitalize(cmd), "Run"},
+					"app/4": []interface{}{"Status", KillStimulus{"Kill", 10 * time.Second}, capitalize(cmd), "Run"},
+					"app/5": []interface{}{"Status", KillStimulus{"Kill", 10 * time.Second}, capitalize(cmd), "Run"},
+				},
+				joinLines(
+					fmt.Sprintf("Successful %s of version for instance \"%s/1\".", cmd, appName),
+					fmt.Sprintf("Successful %s of version for instance \"%s/3\".", cmd, appName),
+				),
+				joinLines(
+					fmt.Sprintf("WARNING for \"%s/1\": recovered from Kill error (device.test:<rpc.Client>\"%s/1\".Kill: Error: Simulate Kill failing). Proceeding with %s.", appName, appName, cmd),
+					fmt.Sprintf("ERROR for \"%s/2\": Kill failed: device.test:<rpc.Client>\"%s/2\".Kill: Error: Simulate Kill failing.", appName, appName),
+					fmt.Sprintf("ERROR for \"%s/3\": Run failed: device.test:<rpc.Client>\"%s/3\".Run: Error: Simulate Run failing.", appName, appName),
+					fmt.Sprintf("ERROR for \"%s/4\": %s failed: device.test:<rpc.Client>\"%s/4\".%s: Error: Simulate %s failing.", appName, capitalize(cmd), appName, capitalize(cmd), capitalize(cmd)),
+					fmt.Sprintf("ERROR for \"%s/5\": Run failed: device.test:<rpc.Client>\"%s/5\".Run: Error: Simulate Run failing.", appName, appName),
+					fmt.Sprintf("ERROR for \"%s/5\": %s failed: device.test:<rpc.Client>\"%s/5\".%s: Error: Simulate %s failing.", appName, capitalize(cmd), appName, capitalize(cmd), capitalize(cmd)),
+				),
+				"encountered a total of 4 error(s)",
+			},
+		} {
+			var stdout, stderr bytes.Buffer
+			env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
+			tapes.Rewind()
+			rootTape.SetResponses(GlobResponse{results: c.globResponses})
+			for n, r := range c.statusResponses {
+				tapes.ForSuffix(n).SetResponses(r...)
+			}
+			args := []string{cmd, globName}
+			if err := v23cmd.ParseAndRunForTest(root, ctx, env, args); err != nil {
+				if want, got := c.expectedError, err.Error(); want != got {
+					t.Errorf("Unexpected error: want %v, got %v", want, got)
+				}
+			} else {
+				if c.expectedError != "" {
+					t.Errorf("Expected to get error %v, but didn't get any error.", c.expectedError)
+				}
+			}
+
+			if expected, got := c.expectedStdout, strings.TrimSpace(stdout.String()); got != expected {
+				t.Errorf("Unexpected stdout output from %s.\nGot:\n%v\nExpected:\n%v", cmd, got, expected)
+			}
+			if expected, got := c.expectedStderr, strings.TrimSpace(stderr.String()); got != expected {
+				t.Errorf("Unexpected stderr output from %s.\nGot:\n%v\nExpected:\n%v", cmd, got, expected)
+			}
+			for n, m := range c.expectedStimuli {
+				if want, got := m, tapes.ForSuffix(n).Play(); !reflect.DeepEqual(want, got) {
+					t.Errorf("Unexpected stimuli for %v. Want: %v, got %v.", n, want, got)
+				}
+			}
+			cmd_device.ResetGlobSettings()
+		}
+	}
+}
diff --git a/services/device/device/util_test.go b/services/device/device/util_test.go
new file mode 100644
index 0000000..29c79eb
--- /dev/null
+++ b/services/device/device/util_test.go
@@ -0,0 +1,129 @@
+// 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_test
+
+import (
+	"bytes"
+	"reflect"
+	"strings"
+	"testing"
+
+	"v.io/v23/naming"
+	"v.io/v23/services/device"
+	"v.io/v23/verror"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/v23cmd"
+	"v.io/x/ref/lib/xrpc"
+	"v.io/x/ref/test"
+
+	cmd_device "v.io/x/ref/services/device/device"
+	"v.io/x/ref/services/internal/servicetest"
+)
+
+//go:generate v23 test generate
+
+var (
+	installationUninstalled = device.StatusInstallation{device.InstallationStatus{
+		State:   device.InstallationStateUninstalled,
+		Version: "director's cut",
+	}}
+	installationActive = device.StatusInstallation{device.InstallationStatus{
+		State:   device.InstallationStateActive,
+		Version: "extended cut",
+	}}
+	instanceUpdating = device.StatusInstance{device.InstanceStatus{
+		State:   device.InstanceStateUpdating,
+		Version: "theatrical version",
+	}}
+	instanceRunning = device.StatusInstance{device.InstanceStatus{
+		State:   device.InstanceStateRunning,
+		Version: "tv version",
+	}}
+	instanceNotRunning = device.StatusInstance{device.InstanceStatus{
+		State:   device.InstanceStateNotRunning,
+		Version: "special edition",
+	}}
+	instanceDeleted = device.StatusInstance{device.InstanceStatus{
+		State:   device.InstanceStateDeleted,
+		Version: "mini series",
+	}}
+	deviceService = device.StatusDevice{device.DeviceStatus{
+		State:   device.InstanceStateRunning,
+		Version: "han shot first",
+	}}
+	deviceUpdating = device.StatusDevice{device.DeviceStatus{
+		State:   device.InstanceStateUpdating,
+		Version: "international release",
+	}}
+)
+
+func testHelper(t *testing.T, lower, upper string) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	tapes := servicetest.NewTapeMap()
+	server, err := xrpc.NewDispatchingServer(ctx, "", newDispatcher(t, tapes))
+	if err != nil {
+		t.Fatalf("NewServer failed: %v", err)
+	}
+	addr := server.Status().Endpoints[0].String()
+
+	// Setup the command-line.
+	cmd := cmd_device.CmdRoot
+	var stdout, stderr bytes.Buffer
+	env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
+	appName := naming.JoinAddressName(addr, "appname")
+
+	// Confirm that we correctly enforce the number of arguments.
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{lower}); err == nil {
+		t.Fatalf("wrongly failed to receive a non-nil error.")
+	}
+	if expected, got := "ERROR: "+lower+": incorrect number of arguments, expected 1, got 0", strings.TrimSpace(stderr.String()); !strings.HasPrefix(got, expected) {
+		t.Fatalf("Unexpected output from %s. Got %q, expected prefix %q", lower, got, expected)
+	}
+	stdout.Reset()
+	stderr.Reset()
+	appTape := tapes.ForSuffix("appname")
+	appTape.Rewind()
+
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{lower, "nope", "nope"}); err == nil {
+		t.Fatalf("wrongly failed to receive a non-nil error.")
+	}
+	if expected, got := "ERROR: "+lower+": incorrect number of arguments, expected 1, got 2", strings.TrimSpace(stderr.String()); !strings.HasPrefix(got, expected) {
+		t.Fatalf("Unexpected output from %s. Got %q, expected prefix %q", lower, got, expected)
+	}
+	stdout.Reset()
+	stderr.Reset()
+	appTape.Rewind()
+
+	// Correct operation.
+	appTape.SetResponses(nil)
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{lower, appName}); err != nil {
+		t.Fatalf("%s failed when it shouldn't: %v", lower, err)
+	}
+	if expected, got := upper+" succeeded", strings.TrimSpace(stdout.String()); got != expected {
+		t.Fatalf("Unexpected output from %s. Got %q, expected %q", lower, got, expected)
+	}
+	if expected, got := []interface{}{upper}, appTape.Play(); !reflect.DeepEqual(expected, got) {
+		t.Errorf("invalid call sequence. Got %v, want %v", got, expected)
+	}
+	appTape.Rewind()
+	stderr.Reset()
+	stdout.Reset()
+
+	// Test with bad parameters.
+	appTape.SetResponses(verror.New(errOops, nil))
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{lower, appName}); err == nil {
+		t.Fatalf("wrongly didn't receive a non-nil error.")
+	}
+	// expected the same.
+	if expected, got := []interface{}{upper}, appTape.Play(); !reflect.DeepEqual(expected, got) {
+		t.Errorf("invalid call sequence. Got %v, want %v", got, expected)
+	}
+}
+
+func joinLines(args ...string) string {
+	return strings.Join(args, "\n")
+}
diff --git a/services/device/device/v23_test.go b/services/device/device/v23_test.go
new file mode 100644
index 0000000..b307e06
--- /dev/null
+++ b/services/device/device/v23_test.go
@@ -0,0 +1,22 @@
+// 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 main_test
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	modules.DispatchAndExitIfChild()
+	os.Exit(m.Run())
+}
diff --git a/services/device/deviced/commands.go b/services/device/deviced/commands.go
new file mode 100644
index 0000000..4673b8c
--- /dev/null
+++ b/services/device/deviced/commands.go
@@ -0,0 +1,164 @@
+// 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 (
+	"fmt"
+	"os"
+
+	"v.io/v23/context"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/v23cmd"
+	"v.io/x/ref/services/device/deviced/internal/impl"
+	"v.io/x/ref/services/device/deviced/internal/installer"
+)
+
+var (
+	installFrom string
+	suidHelper  string
+	agent       string
+	initHelper  string
+	devUserName string
+	origin      string
+	singleUser  bool
+	sessionMode bool
+	initMode    bool
+)
+
+const deviceDirEnv = "V23_DEVICE_DIR"
+
+func installationDir(ctx *context.T, env *cmdline.Env) string {
+	if d := env.Vars[deviceDirEnv]; d != "" {
+		return d
+	}
+	if d, err := os.Getwd(); err != nil {
+		ctx.Errorf("Failed to get current dir: %v", err)
+		return ""
+	} else {
+		return d
+	}
+}
+
+var cmdInstall = &cmdline.Command{
+	Runner:   v23cmd.RunnerFunc(runInstall),
+	Name:     "install",
+	Short:    "Install the device manager.",
+	Long:     fmt.Sprintf("Performs installation of device manager into %s (if the env var set), or into the current dir otherwise", deviceDirEnv),
+	ArgsName: "[-- <arguments for device manager>]",
+	ArgsLong: `
+Arguments to be passed to the installed device manager`,
+}
+
+func init() {
+	cmdInstall.Flags.StringVar(&installFrom, "from", "", "if specified, performs the installation from the provided application envelope object name")
+	cmdInstall.Flags.StringVar(&suidHelper, "suid_helper", "", "path to suid helper")
+	cmdInstall.Flags.StringVar(&agent, "agent", "", "path to security agent")
+	cmdInstall.Flags.StringVar(&initHelper, "init_helper", "", "path to sysinit helper")
+	cmdInstall.Flags.StringVar(&origin, "origin", "", "if specified, self-updates will use this origin")
+	cmdInstall.Flags.StringVar(&devUserName, "devuser", "", "if specified, device manager will run as this user. Provided by devicex but ignored .")
+	cmdInstall.Flags.BoolVar(&singleUser, "single_user", false, "if set, performs the installation assuming a single-user system")
+	cmdInstall.Flags.BoolVar(&sessionMode, "session_mode", false, "if set, installs the device manager to run a single session. Otherwise, the device manager is configured to get restarted upon exit")
+	cmdInstall.Flags.BoolVar(&initMode, "init_mode", false, "if set, installs the device manager with the system init service manager")
+}
+
+func runInstall(ctx *context.T, env *cmdline.Env, args []string) error {
+	if installFrom != "" {
+		// TODO(caprita): Also pass args into InstallFrom.
+		if err := installer.InstallFrom(installFrom); err != nil {
+			ctx.Errorf("InstallFrom(%v) failed: %v", installFrom, err)
+			return err
+		}
+		return nil
+	}
+	if suidHelper == "" {
+		return env.UsageErrorf("--suid_helper must be set")
+	}
+	if agent == "" {
+		return env.UsageErrorf("--agent must be set")
+	}
+	if initMode && initHelper == "" {
+		return env.UsageErrorf("--init_helper must be set")
+	}
+	if err := installer.SelfInstall(ctx, installationDir(ctx, env), suidHelper, agent, initHelper, origin, singleUser, sessionMode, initMode, args, os.Environ(), env.Stderr, env.Stdout); err != nil {
+		ctx.Errorf("SelfInstall failed: %v", err)
+		return err
+	}
+	return nil
+}
+
+var cmdUninstall = &cmdline.Command{
+	Runner: v23cmd.RunnerFunc(runUninstall),
+	Name:   "uninstall",
+	Short:  "Uninstall the device manager.",
+	Long:   fmt.Sprintf("Removes the device manager installation from %s (if the env var set), or the current dir otherwise", deviceDirEnv),
+}
+
+func init() {
+	cmdUninstall.Flags.StringVar(&suidHelper, "suid_helper", "", "path to suid helper")
+}
+
+func runUninstall(ctx *context.T, env *cmdline.Env, _ []string) error {
+	if suidHelper == "" {
+		return env.UsageErrorf("--suid_helper must be set")
+	}
+	if err := installer.Uninstall(ctx, installationDir(ctx, env), suidHelper, env.Stderr, env.Stdout); err != nil {
+		ctx.Errorf("Uninstall failed: %v", err)
+		return err
+	}
+	return nil
+}
+
+var cmdStart = &cmdline.Command{
+	Runner: v23cmd.RunnerFunc(runStart),
+	Name:   "start",
+	Short:  "Start the device manager.",
+	Long:   fmt.Sprintf("Starts the device manager installed under from %s (if the env var set), or the current dir otherwise", deviceDirEnv),
+}
+
+func runStart(ctx *context.T, env *cmdline.Env, _ []string) error {
+	if err := installer.Start(ctx, installationDir(ctx, env), env.Stderr, env.Stdout); err != nil {
+		ctx.Errorf("Start failed: %v", err)
+		return err
+	}
+	return nil
+}
+
+var cmdStop = &cmdline.Command{
+	Runner: v23cmd.RunnerFunc(runStop),
+	Name:   "stop",
+	Short:  "Stop the device manager.",
+	Long:   fmt.Sprintf("Stops the device manager installed under from %s (if the env var set), or the current dir otherwise", deviceDirEnv),
+}
+
+func runStop(ctx *context.T, env *cmdline.Env, _ []string) error {
+	if err := installer.Stop(ctx, installationDir(ctx, env), env.Stderr, env.Stdout); err != nil {
+		ctx.Errorf("Stop failed: %v", err)
+		return err
+	}
+	return nil
+}
+
+var cmdProfile = &cmdline.Command{
+	Runner: v23cmd.RunnerFunc(runProfile),
+	Name:   "profile",
+	Short:  "Dumps profile for the device manager.",
+	Long:   "Prints the internal profile description for the device manager.",
+}
+
+func runProfile(ctx *context.T, env *cmdline.Env, _ []string) error {
+	spec, err := impl.ComputeDeviceProfile()
+	if err != nil {
+		ctx.Errorf("ComputeDeviceProfile failed: %v", err)
+		return err
+	}
+	fmt.Fprintf(env.Stdout, "Profile: %#v\n", spec)
+	desc, err := impl.Describe()
+	if err != nil {
+		ctx.Errorf("Describe failed: %v", err)
+		return err
+	}
+	fmt.Fprintf(env.Stdout, "Description: %#v\n", desc)
+	return nil
+}
diff --git a/services/device/deviced/doc.go b/services/device/deviced/doc.go
new file mode 100644
index 0000000..b89a60b
--- /dev/null
+++ b/services/device/deviced/doc.go
@@ -0,0 +1,206 @@
+// 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
+
+/*
+Command deviced is used to launch, configure and manage the deviced daemon,
+which implements the v.io/v23/services/device interfaces.
+
+Usage:
+   deviced
+   deviced <command>
+
+The deviced commands are:
+   install     Install the device manager.
+   uninstall   Uninstall the device manager.
+   start       Start the device manager.
+   stop        Stop the device manager.
+   profile     Dumps profile for the device manager.
+   help        Display help for commands or topics
+
+The global flags are:
+ -deviced-port=0
+   the port number of assign to the device manager service. The hostname/IP
+   address part of --v23.tcp.address is used along with this port. By default,
+   the port is assigned by the OS.
+ -name=
+   name to publish the device manager at
+ -neighborhood-name=
+   if provided, it will enable sharing with the local neighborhood with the
+   provided name. The address of the local mounttable will be published to the
+   neighboorhood and everything in the neighborhood will be visible on the local
+   mounttable.
+ -proxy-port=0
+   the port number to assign to the proxy service. 0 means no proxy service.
+ -restart-exit-code=0
+   exit code to return when device manager should be restarted
+ -use-pairing-token=false
+   generate a pairing token for the device manager that will need to be provided
+   when a device is claimed
+
+ -alsologtostderr=true
+   log to standard error as well as files
+ -chown=false
+   Change owner of files and directories given as command-line arguments to the
+   user specified by this flag
+ -dryrun=false
+   Elides root-requiring systemcalls.
+ -kill=false
+   Kill process ids given as command-line arguments.
+ -log_backtrace_at=:0
+   when logging hits line file:N, emit a stack trace
+ -log_dir=
+   if non-empty, write log files to this directory
+ -logdir=
+   Path to the log directory.
+ -logtostderr=false
+   log to standard error instead of files
+ -max_stack_buf_size=4292608
+   max size in bytes of the buffer to use for logging stack traces
+ -metadata=<just specify -metadata to activate>
+   Displays metadata for the program and exits.
+ -minuid=501
+   UIDs cannot be less than this number.
+ -progname=unnamed_app
+   Visible name of the application, used in argv[0]
+ -rm=false
+   Remove the file trees given as command-line arguments.
+ -run=
+   Path to the application to exec.
+ -stderrthreshold=2
+   logs at or above this threshold go to stderr
+ -username=
+   The UNIX user name used for the other functions of this tool.
+ -v=0
+   log level for V logs
+ -v23.credentials=
+   directory to use for storing security credentials
+ -v23.i18n-catalogue=
+   18n catalogue files to load, comma separated
+ -v23.namespace.root=[/(dev.v.io/role/vprod/service/mounttabled)@ns.dev.v.io:8101]
+   local namespace root; can be repeated to provided multiple roots
+ -v23.permissions.file=map[]
+   specify a perms file as <name>:<permsfile>
+ -v23.permissions.literal=
+   explicitly specify the runtime perms as a JSON-encoded access.Permissions.
+   Overrides all --v23.permissions.file flags.
+ -v23.proxy=
+   object name of proxy service to use to export services across network
+   boundaries
+ -v23.tcp.address=
+   address to listen on
+ -v23.tcp.protocol=wsh
+   protocol to listen with
+ -v23.vtrace.cache-size=1024
+   The number of vtrace traces to store in memory.
+ -v23.vtrace.collect-regexp=
+   Spans and annotations that match this regular expression will trigger trace
+   collection.
+ -v23.vtrace.dump-on-shutdown=true
+   If true, dump all stored traces on runtime shutdown.
+ -v23.vtrace.sample-rate=0
+   Rate (from 0.0 to 1.0) to sample vtrace traces.
+ -vmodule=
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
+ -workspace=
+   Path to the application's workspace directory.
+
+Deviced install
+
+Performs installation of device manager into V23_DEVICE_DIR (if the env var
+set), or into the current dir otherwise
+
+Usage:
+   deviced install [flags] [-- <arguments for device manager>]
+
+Arguments to be passed to the installed device manager
+
+The deviced install flags are:
+ -agent=
+   path to security agent
+ -devuser=
+   if specified, device manager will run as this user. Provided by devicex but
+   ignored .
+ -from=
+   if specified, performs the installation from the provided application
+   envelope object name
+ -init_helper=
+   path to sysinit helper
+ -init_mode=false
+   if set, installs the device manager with the system init service manager
+ -origin=
+   if specified, self-updates will use this origin
+ -session_mode=false
+   if set, installs the device manager to run a single session. Otherwise, the
+   device manager is configured to get restarted upon exit
+ -single_user=false
+   if set, performs the installation assuming a single-user system
+ -suid_helper=
+   path to suid helper
+
+Deviced uninstall
+
+Removes the device manager installation from V23_DEVICE_DIR (if the env var
+set), or the current dir otherwise
+
+Usage:
+   deviced uninstall [flags]
+
+The deviced uninstall flags are:
+ -suid_helper=
+   path to suid helper
+
+Deviced start
+
+Starts the device manager installed under from V23_DEVICE_DIR (if the env var
+set), or the current dir otherwise
+
+Usage:
+   deviced start
+
+Deviced stop
+
+Stops the device manager installed under from V23_DEVICE_DIR (if the env var
+set), or the current dir otherwise
+
+Usage:
+   deviced stop
+
+Deviced profile
+
+Prints the internal profile description for the device manager.
+
+Usage:
+   deviced profile
+
+Deviced help - Display help for commands or topics
+
+Help with no args displays the usage of the parent command.
+
+Help with args displays the usage of the specified sub-command or help topic.
+
+"help ..." recursively displays help for all commands and topics.
+
+Usage:
+   deviced help [flags] [command/topic ...]
+
+[command/topic ...] optionally identifies a specific sub-command or help topic.
+
+The deviced help flags are:
+ -style=compact
+   The formatting style for help output:
+      compact - Good for compact cmdline output.
+      full    - Good for cmdline output, shows all global flags.
+      godoc   - Good for godoc processing.
+   Override the default by setting the CMDLINE_STYLE environment variable.
+ -width=<terminal width>
+   Format output to this target width in runes, or unlimited if width < 0.
+   Defaults to the terminal width if available.  Override the default by setting
+   the CMDLINE_WIDTH environment variable.
+*/
+package main
diff --git a/services/device/deviced/internal/impl/app_service.go b/services/device/deviced/internal/impl/app_service.go
new file mode 100644
index 0000000..dca7113
--- /dev/null
+++ b/services/device/deviced/internal/impl/app_service.go
@@ -0,0 +1,1857 @@
+// 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 impl
+
+// The app invoker is responsible for managing the state of applications on the
+// device manager.  The device manager manages the applications it installs and
+// runs using the following directory structure.  Permissions and owners are
+// noted as parentheses enclosed octal perms with an 'a' or 'd' suffix for app
+// or device manager respectively.  For example: (755d)
+//
+// TODO(caprita): Not all is yet implemented.
+//
+// <config.Root>(711d)/
+//   app-<hash 1>(711d)/                  - the application dir is named using a hash of the application title
+//     installation-<id 1>(711d)/         - installations are labelled with ids
+//       acls(700d)/
+//         data(700d)                     - the AccessList data for this
+//                                          installation. Controls access to
+//                                          Instantiate, Uninstall, Update,
+//                                          UpdateTo and Revert.
+//         signature(700d)                - the signature for the AccessLists in data
+//       <status>(700d)                   - one of the values for InstallationState enum
+//       origin(700d)                     - object name for application envelope
+//       config(700d)                     - Config provided by the installer
+//       packages(700d)                   - set of packages specified by the installer
+//       pkg(700d)/                       - downloaded packages
+//         <pkg name>(700d)
+//         <pkg name>.__info(700d)
+//         ...
+//       <version 1 timestamp>(711d)/     - timestamp of when the version was downloaded
+//         bin(755d)                      - application binary
+//         previous                       - symbolic link to previous version directory
+//         envelope                       - application envelope (JSON-encoded)
+//         packages(755d)/                - installed packages (from envelope+installer)
+//           <pkg name>(755d)/
+//           ...
+//       <version 2 timestamp>(711d)
+//       ...
+//       current                          - symbolic link to the current version
+//       instances(711d)/
+//         instance-<id a>(711d)/         - instances are labelled with ids
+//           credentials(700d)/           - holds vanadium credentials (unless running
+//                                          through security agent)
+//           root(700a)/                  - workspace that the instance is run from
+//             packages                   - symbolic link to version's packages
+//           logs(755a)/                  - stderr/stdout and log files generated by instance
+//           info(700d)                   - metadata for the instance (such as app
+//                                          cycle manager name and process id)
+//           installation                 - symbolic link to installation for the instance
+//           version                      - symbolic link to installation version for the instance
+//           agent-sock-dir               - symbolic link to the agent socket dir
+//           acls(700d)/
+//             data(700d)                 - the AccessLists for this instance. These
+//                                          AccessLists control access to Run,
+//                                          Kill and Delete.
+//             signature(700d)            - the signature for these AccessLists.
+//           <status>(700d)               - one of the values for InstanceState enum
+//           systemname(700d)             - the system name used to execute this instance
+//           debugacls (711d)/
+//             data(644)/                 - the Permissions for Debug access to the application. Shared
+//                                          with the application.
+//             signature(644)/            - the signature for these Permissions.
+//         instance-<id b>(711d)
+//         ...
+//     installation-<id 2>(711d)
+//     ...
+//   app-<hash 2>(711d)
+//   ...
+//   socks(711d)/                         - agent sockets
+//     <id X>(711d)/                      - one for each app instance
+//       s(660d)                          - the socket file
+//     <id Y>
+//     ...
+//
+// The device manager uses the suid helper binary to invoke an application as a
+// specified user.  The path to the helper is specified as config.Helper.
+
+// When device manager starts up, it goes through all instances and launches the
+// ones that are not running.  If an instance fails to launch, it stays not
+// running.
+//
+// Instantiate creates an instance.  Run launches the process.  Kill kills the
+// process but leaves the workspace untouched.  Delete prevents future launches
+// (it also eventually gc's the workspace, logs, and other instance state).
+//
+// If the process dies on its own, it stays dead and is assumed not running.
+// TODO(caprita): Later, we'll add auto-restart option.
+//
+// Concurrency model: installations can be created independently of one another;
+// installations can be removed at any time (TODO(caprita): ensure all instances
+// are Deleted).  The first call to Uninstall will rename the installation dir
+// as a first step; subsequent Uninstall's will fail.  Instances can be created
+// independently of one another, as long as the installation exists (if it gets
+// Uninstall'ed during a Instantiate, the Instantiate call may fail).
+//
+// The status file present in each instance is used to flag the state of the
+// instance and prevent concurrent operations against the instance:
+//
+// - when an instance is created with Instantiate, it is placed in state
+// 'not-running'.
+//
+// - Run attempts to transition from 'not-running' to 'launching' (if the
+// instance was not in 'not-running' state, Run fails).  From 'launching', the
+// instance transitions to 'running' upon success or back to 'not-running' upon
+// failure.
+//
+// - Kill attempts to transition from 'running' to 'dying' (if the
+// instance was not in 'running' state, Kill fails).  From 'dying', the
+// instance transitions to 'not-running' upon success or back to 'running' upon
+// failure.
+//
+// - Delete transitions from 'not-running' to 'deleted'.  If the initial
+// state is not 'not-running', Delete fails.
+//
+// TODO(caprita): There is room for synergy between how device manager organizes
+// its own workspace and that for the applications it runs.  In particular,
+// previous, origin, and envelope could be part of a single config.  We'll
+// refine that later.
+
+import (
+	"bytes"
+	"crypto/md5"
+	"encoding/base64"
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"path"
+	"path/filepath"
+	"reflect"
+	"regexp"
+	"strconv"
+	"strings"
+	"text/template"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/glob"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/security/access"
+	"v.io/v23/services/appcycle"
+	"v.io/v23/services/application"
+	"v.io/v23/services/device"
+	"v.io/v23/verror"
+	"v.io/x/ref"
+	vexec "v.io/x/ref/lib/exec"
+	"v.io/x/ref/lib/mgmt"
+	vsecurity "v.io/x/ref/lib/security"
+	"v.io/x/ref/services/agent"
+	"v.io/x/ref/services/agent/agentlib"
+	"v.io/x/ref/services/agent/keymgr"
+	"v.io/x/ref/services/device/internal/config"
+	"v.io/x/ref/services/device/internal/errors"
+	"v.io/x/ref/services/internal/packages"
+	"v.io/x/ref/services/internal/pathperms"
+)
+
+// instanceInfo holds state about an instance.
+type instanceInfo struct {
+	AppCycleMgrName          string
+	Pid                      int
+	DeviceManagerPeerPattern string
+	// TODO(caprita): Change to [agent.PrincipalHandleByteSize]byte and
+	// remove handle() and setHandle() converters.
+	SecurityAgentHandle []byte
+	Restarts            int32
+	RestartWindowBegan  time.Time
+}
+
+func (i *instanceInfo) handle() (ret [agent.PrincipalHandleByteSize]byte) {
+	if len(i.SecurityAgentHandle) != agent.PrincipalHandleByteSize {
+		panic(fmt.Sprintf("Handle of unexpected length (%d): %v", len(i.SecurityAgentHandle), i.SecurityAgentHandle))
+	}
+	copy(ret[:], i.SecurityAgentHandle[0:agent.PrincipalHandleByteSize])
+	return
+}
+
+func (i *instanceInfo) setHandle(h [agent.PrincipalHandleByteSize]byte) {
+	i.SecurityAgentHandle = h[:]
+}
+
+func saveInstanceInfo(ctx *context.T, dir string, info *instanceInfo) error {
+	jsonInfo, err := json.Marshal(info)
+	if err != nil {
+		return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("Marshal(%v) failed: %v", info, err))
+	}
+	infoPath := filepath.Join(dir, "info")
+	if err := ioutil.WriteFile(infoPath, jsonInfo, 0600); err != nil {
+		return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("WriteFile(%v) failed: %v", infoPath, err))
+	}
+	return nil
+}
+
+func loadInstanceInfo(ctx *context.T, dir string) (*instanceInfo, error) {
+	infoPath := filepath.Join(dir, "info")
+	info := new(instanceInfo)
+	if infoBytes, err := ioutil.ReadFile(infoPath); err != nil {
+		return nil, verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("ReadFile(%v) failed: %v", infoPath, err))
+	} else if err := json.Unmarshal(infoBytes, info); err != nil {
+		return nil, verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("Unmarshal(%v) failed: %v", infoBytes, err))
+	}
+	return info, nil
+}
+
+type securityAgentState struct {
+	// Security agent key manager client.
+	keyMgr agent.KeyManager
+	// Deprecated: security agent key manager client based on pipe
+	// connections.
+	keyMgrAgent *keymgr.Agent
+}
+
+// appRunner is the subset of the appService object needed to
+// (re)start an application.
+type appRunner struct {
+	callback *callbackState
+	// securityAgent holds state related to the security agent (nil if not
+	// using the agent).
+	securityAgent *securityAgentState
+	// reap is the app process monitoring subsystem.
+	reap *reaper
+	// mtAddress is the address of the local mounttable.
+	mtAddress string
+	// appServiceName is a name by which the appService can be reached
+	appServiceName string
+	stats          *stats
+}
+
+// appService implements the Device manager's Application interface.
+type appService struct {
+	config *config.State
+	// suffix contains the name components of the current invocation name
+	// suffix.  It is used to identify an application, installation, or
+	// instance.
+	suffix     []string
+	uat        BlessingSystemAssociationStore
+	permsStore *pathperms.PathStore
+	// Reference to the devicemanager top-level AccessList list.
+	deviceAccessList access.Permissions
+	// State needed to (re)start an application.
+	runner *appRunner
+	stats  *stats
+}
+
+func saveEnvelope(ctx *context.T, dir string, envelope *application.Envelope) error {
+	jsonEnvelope, err := json.Marshal(envelope)
+	if err != nil {
+		return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("Marshal(%v) failed: %v", envelope, err))
+	}
+	path := filepath.Join(dir, "envelope")
+	if err := ioutil.WriteFile(path, jsonEnvelope, 0600); err != nil {
+		return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("WriteFile(%v) failed: %v", path, err))
+	}
+	return nil
+}
+
+func loadEnvelope(ctx *context.T, dir string) (*application.Envelope, error) {
+	path := filepath.Join(dir, "envelope")
+	envelope := new(application.Envelope)
+	if envelopeBytes, err := ioutil.ReadFile(path); err != nil {
+		return nil, verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("ReadFile(%v) failed: %v", path, err))
+	} else if err := json.Unmarshal(envelopeBytes, envelope); err != nil {
+		return nil, verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("Unmarshal(%v) failed: %v", envelopeBytes, err))
+	}
+	return envelope, nil
+}
+
+func loadEnvelopeForInstance(ctx *context.T, instanceDir string) (*application.Envelope, error) {
+	versionLink := filepath.Join(instanceDir, "version")
+	versionDir, err := filepath.EvalSymlinks(versionLink)
+	if err != nil {
+		return nil, verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("EvalSymlinks(%v) failed: %v", versionLink, err))
+	}
+	return loadEnvelope(ctx, versionDir)
+}
+
+func saveConfig(ctx *context.T, dir string, config device.Config) error {
+	jsonConfig, err := json.Marshal(config)
+	if err != nil {
+		return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("Marshal(%v) failed: %v", config, err))
+	}
+	path := filepath.Join(dir, "config")
+	if err := ioutil.WriteFile(path, jsonConfig, 0600); err != nil {
+		return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("WriteFile(%v) failed: %v", path, err))
+	}
+	return nil
+}
+
+func loadConfig(ctx *context.T, dir string) (device.Config, error) {
+	path := filepath.Join(dir, "config")
+	var config device.Config
+	if configBytes, err := ioutil.ReadFile(path); err != nil {
+		return nil, verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("ReadFile(%v) failed: %v", path, err))
+	} else if err := json.Unmarshal(configBytes, &config); err != nil {
+		return nil, verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("Unmarshal(%v) failed: %v", configBytes, err))
+	}
+	return config, nil
+}
+
+func savePackages(ctx *context.T, dir string, packages application.Packages) error {
+	jsonPackages, err := json.Marshal(packages)
+	if err != nil {
+		return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("Marshal(%v) failed: %v", packages, err))
+	}
+	path := filepath.Join(dir, "packages")
+	if err := ioutil.WriteFile(path, jsonPackages, 0600); err != nil {
+		return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("WriteFile(%v) failed: %v", path, err))
+	}
+	return nil
+}
+
+func loadPackages(ctx *context.T, dir string) (application.Packages, error) {
+	path := filepath.Join(dir, "packages")
+	var packages application.Packages
+	if packagesBytes, err := ioutil.ReadFile(path); err != nil {
+		return nil, verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("ReadFile(%v) failed: %v", path, err))
+	} else if err := json.Unmarshal(packagesBytes, &packages); err != nil {
+		return nil, verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("Unmarshal(%v) failed: %v", packagesBytes, err))
+	}
+	return packages, nil
+}
+
+func saveOrigin(ctx *context.T, dir, originVON string) error {
+	path := filepath.Join(dir, "origin")
+	if err := ioutil.WriteFile(path, []byte(originVON), 0600); err != nil {
+		return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("WriteFile(%v) failed: %v", path, err))
+	}
+	return nil
+}
+
+func loadOrigin(ctx *context.T, dir string) (string, error) {
+	path := filepath.Join(dir, "origin")
+	if originBytes, err := ioutil.ReadFile(path); err != nil {
+		return "", verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("ReadFile(%v) failed: %v", path, err))
+	} else {
+		return string(originBytes), nil
+	}
+}
+
+// generateID returns a new unique id string.  The uniqueness is based on the
+// current timestamp.  Not cryptographically secure.
+func generateID() string {
+	const timeFormat = "20060102-15:04:05.0000"
+	return time.Now().UTC().Format(timeFormat)
+}
+
+// TODO(caprita): Nothing prevents different applications from sharing the same
+// title, and thereby being installed in the same app dir.  Do we want to
+// prevent that for the same user or across users?
+
+const (
+	appDirPrefix       = "app-"
+	installationPrefix = "installation-"
+	instancePrefix     = "instance-"
+)
+
+// applicationDirName generates a cryptographic hash of the application title,
+// to be used as a directory name for installations of the application with the
+// given title.
+func applicationDirName(title string) string {
+	h := md5.New()
+	h.Write([]byte(title))
+	hash := strings.TrimRight(base64.URLEncoding.EncodeToString(h.Sum(nil)), "=")
+	return appDirPrefix + hash
+}
+
+func installationDirName(installationID string) string {
+	return installationPrefix + installationID
+}
+
+func instanceDirName(instanceID string) string {
+	return instancePrefix + instanceID
+}
+
+func mkdir(ctx *context.T, dir string) error {
+	return mkdirPerm(ctx, dir, 0700)
+}
+
+func mkdirPerm(ctx *context.T, dir string, permissions int) error {
+	perm := os.FileMode(permissions)
+	if err := os.MkdirAll(dir, perm); err != nil {
+		ctx.Errorf("MkdirAll(%v, %v) failed: %v", dir, perm, err)
+		return err
+	}
+	return nil
+}
+
+func sockPath(instanceDir string) (string, error) {
+	sockLink := filepath.Join(instanceDir, "agent-sock-dir")
+	sock, err := filepath.EvalSymlinks(sockLink)
+	if err != nil {
+		return "", err
+	}
+	return filepath.Join(sock, "s"), nil
+}
+
+func fetchAppEnvelope(ctx *context.T, origin string) (*application.Envelope, error) {
+	envelope, err := fetchEnvelope(ctx, origin)
+	if err != nil {
+		return nil, err
+	}
+	if envelope.Title == application.DeviceManagerTitle {
+		// Disallow device manager apps from being installed like a
+		// regular app.
+		return nil, verror.New(errors.ErrInvalidOperation, ctx, "DeviceManager apps cannot be installed")
+	}
+	return envelope, nil
+}
+
+// newVersion sets up the directory for a new application version.
+func newVersion(ctx *context.T, installationDir string, envelope *application.Envelope, oldVersionDir string) (string, error) {
+	versionDir := filepath.Join(installationDir, generateVersionDirName())
+	if err := mkdirPerm(ctx, versionDir, 0711); err != nil {
+		return "", verror.New(errors.ErrOperationFailed, ctx, err)
+	}
+	if err := saveEnvelope(ctx, versionDir, envelope); err != nil {
+		return versionDir, err
+	}
+	pkgDir := filepath.Join(versionDir, "pkg")
+	if err := mkdir(ctx, pkgDir); err != nil {
+		return "", verror.New(errors.ErrOperationFailed, ctx, err)
+	}
+	publisher := envelope.Publisher
+	// TODO(caprita): Share binaries if already existing locally.
+	if err := downloadBinary(ctx, publisher, &envelope.Binary, versionDir, "bin"); err != nil {
+		return versionDir, err
+	}
+	if err := downloadPackages(ctx, publisher, envelope.Packages, pkgDir); err != nil {
+		return versionDir, err
+	}
+	if err := installPackages(ctx, installationDir, versionDir); err != nil {
+		return versionDir, verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("installPackages(%v, %v) failed: %v", installationDir, versionDir, err))
+	}
+	if err := os.RemoveAll(pkgDir); err != nil {
+		return versionDir, verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("RemoveAll(%v) failed: %v", pkgDir, err))
+	}
+	if oldVersionDir != "" {
+		previousLink := filepath.Join(versionDir, "previous")
+		if err := os.Symlink(oldVersionDir, previousLink); err != nil {
+			return versionDir, verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("Symlink(%v, %v) failed: %v", oldVersionDir, previousLink, err))
+		}
+	}
+	// UpdateLink should be the last thing we do, after we've ensured the
+	// new version is viable (currently, that just means it installs
+	// properly).
+	return versionDir, UpdateLink(versionDir, filepath.Join(installationDir, "current"))
+}
+
+func (i *appService) Install(ctx *context.T, call rpc.ServerCall, applicationVON string, config device.Config, packages application.Packages) (string, error) {
+	if len(i.suffix) > 0 {
+		return "", verror.New(errors.ErrInvalidSuffix, ctx)
+	}
+	ctx, cancel := context.WithTimeout(ctx, rpcContextLongTimeout)
+	defer cancel()
+	envelope, err := fetchAppEnvelope(ctx, applicationVON)
+	if err != nil {
+		return "", err
+	}
+	installationID := generateID()
+	installationDir := filepath.Join(i.config.Root, applicationDirName(envelope.Title), installationDirName(installationID))
+	deferrer := func() {
+		CleanupDir(ctx, installationDir, "")
+	}
+	if err := mkdirPerm(ctx, installationDir, 0711); err != nil {
+		return "", verror.New(errors.ErrOperationFailed, nil)
+	}
+	defer func() {
+		if deferrer != nil {
+			deferrer()
+		}
+	}()
+	if newOrigin, ok := config[mgmt.AppOriginConfigKey]; ok {
+		delete(config, mgmt.AppOriginConfigKey)
+		applicationVON = newOrigin
+	}
+	if err := saveOrigin(ctx, installationDir, applicationVON); err != nil {
+		return "", err
+	}
+	if err := saveConfig(ctx, installationDir, config); err != nil {
+		return "", err
+	}
+	if err := savePackages(ctx, installationDir, packages); err != nil {
+		return "", err
+	}
+	pkgDir := filepath.Join(installationDir, "pkg")
+	if err := mkdir(ctx, pkgDir); err != nil {
+		return "", verror.New(errors.ErrOperationFailed, ctx, err)
+	}
+	// We use a zero value publisher, meaning that any signatures present in the
+	// package files are not verified.
+	// TODO(caprita): Issue warnings when signatures are present and ignored.
+	if err := downloadPackages(ctx, security.Blessings{}, packages, pkgDir); err != nil {
+		return "", err
+	}
+	if _, err := newVersion(ctx, installationDir, envelope, ""); err != nil {
+		return "", err
+	}
+	// TODO(caprita,rjkroege): Should the installation AccessLists really be
+	// seeded with the device AccessList? Instead, might want to hide the deviceAccessList
+	// from the app?
+	blessings, _ := security.RemoteBlessingNames(ctx, call.Security())
+	if err := i.initializeSubAccessLists(installationDir, blessings, i.deviceAccessList.Copy()); err != nil {
+		return "", err
+	}
+	if err := initializeInstallation(installationDir, device.InstallationStateActive); err != nil {
+		return "", err
+	}
+	deferrer = nil
+	// TODO(caprita): Using the title without cleaning out slashes
+	// introduces extra name components that mess up the device manager's
+	// apps object space.  We should fix this either by santizing the title,
+	// or disallowing slashes in titles to begin with.
+	return naming.Join(envelope.Title, installationID), nil
+}
+
+func openWriteFile(path string) (*os.File, error) {
+	perm := os.FileMode(0644)
+	return os.OpenFile(path, os.O_WRONLY|os.O_CREATE, perm)
+}
+
+// TODO(gauthamt): Make sure we pass the context to installationDirCore.
+func installationDirCore(components []string, root string) (string, error) {
+	if nComponents := len(components); nComponents != 2 {
+		return "", verror.New(errors.ErrInvalidSuffix, nil)
+	}
+	app, installation := components[0], components[1]
+	installationDir := filepath.Join(root, applicationDirName(app), installationDirName(installation))
+	if _, err := os.Stat(installationDir); err != nil {
+		if os.IsNotExist(err) {
+			return "", verror.New(verror.ErrNoExist, nil, naming.Join(components...))
+		}
+		return "", verror.New(errors.ErrOperationFailed, nil, fmt.Sprintf("Stat(%v) failed: %v", installationDir, err))
+	}
+	return installationDir, nil
+}
+
+// agentPrincipal creates a Principal backed by the given agent connection,
+// taking ownership of the connection.  The returned cancel function is to be
+// called when the Principal is no longer in use.
+func agentPrincipal(ctx *context.T, conn *os.File) (security.Principal, func(), error) {
+	agentctx, cancel := context.WithCancel(ctx)
+	var err error
+	if agentctx, err = v23.WithNewStreamManager(agentctx); err != nil {
+		cancel()
+		conn.Close()
+		return nil, nil, err
+	}
+	// TODO: This should use the same network as the agent we're using,
+	// not whatever this process was compiled with.
+	ep, err := v23.NewEndpoint(agentlib.AgentEndpoint(int(conn.Fd())))
+	if err != nil {
+		cancel()
+		conn.Close()
+		return nil, nil, err
+	}
+	p, err := agentlib.NewAgentPrincipal(agentctx, ep, v23.GetClient(agentctx))
+	if err != nil {
+		cancel()
+		conn.Close()
+		return nil, nil, err
+	}
+	conn.Close()
+	return p, cancel, nil
+}
+
+// setupPrincipal sets up the instance's principal, with the right blessings.
+func setupPrincipal(ctx *context.T, instanceDir string, call device.ApplicationInstantiateServerCall, securityAgent *securityAgentState, info *instanceInfo, rootDir string) error {
+	var p security.Principal
+	switch {
+	case securityAgent != nil && securityAgent.keyMgr != nil:
+		// TODO(caprita): Part of the cleanup upon destroying an
+		// instance, we should tell the agent to drop the principal.
+		handle, err := securityAgent.keyMgr.NewPrincipal(false)
+		if err != nil {
+			return verror.New(errors.ErrOperationFailed, ctx, "NewPrincipal() failed", err)
+		}
+		info.setHandle(handle)
+		sockDir, err := generateAgentSockDir(rootDir)
+		if err != nil {
+			return verror.New(errors.ErrOperationFailed, ctx, "generateAgentSockDir() failed", err)
+		}
+		sockLink := filepath.Join(instanceDir, "agent-sock-dir")
+		if err := os.Symlink(sockDir, sockLink); err != nil {
+			return verror.New(errors.ErrOperationFailed, ctx, "Symlink failed", err)
+		}
+		// TODO(caprita): Add a check to ensure that len(sockPath) < 108.
+		sockPath := filepath.Join(sockDir, "s")
+		if err := securityAgent.keyMgr.ServePrincipal(handle, sockPath); err != nil {
+			return verror.New(errors.ErrOperationFailed, ctx, "ServePrincipal failed", err)
+		}
+		defer func() {
+			if err := securityAgent.keyMgr.StopServing(handle); err != nil {
+				ctx.Errorf("StopServing failed: %v", err)
+			}
+		}()
+		if p, err = agentlib.NewAgentPrincipalX(sockPath); err != nil {
+			return verror.New(errors.ErrOperationFailed, ctx, "NewAgentPrincipalX failed", err)
+		}
+	case securityAgent != nil && securityAgent.keyMgrAgent != nil:
+		// This code path is deprecated in favor of the socket agent
+		// connection.
+
+		// TODO(caprita): Part of the cleanup upon destroying an
+		// instance, we should tell the agent to drop the principal.
+		handle, conn, err := securityAgent.keyMgrAgent.NewPrincipal(ctx, false)
+		if err != nil {
+			return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("NewPrincipal() failed %v", err))
+		}
+		var cancel func()
+		if p, cancel, err = agentPrincipal(ctx, conn); err != nil {
+			return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("agentPrincipal failed: %v", err))
+		}
+		defer cancel()
+		info.SecurityAgentHandle = handle
+		// conn will be closed when the connection to the agent is shut
+		// down, as a result of cancel() shutting down the stream
+		// manager.  No need to call conn.Close().
+	default:
+		credentialsDir := filepath.Join(instanceDir, "credentials")
+		// TODO(caprita): The app's system user id needs access to this dir.
+		// Use the suidhelper to chown it.
+		var err error
+		if p, err = vsecurity.CreatePersistentPrincipal(credentialsDir, nil); err != nil {
+			return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("CreatePersistentPrincipal(%v, nil) failed: %v", credentialsDir, err))
+		}
+	}
+	mPubKey, err := p.PublicKey().MarshalBinary()
+	if err != nil {
+		return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("PublicKey().MarshalBinary() failed: %v", err))
+	}
+	if err := call.SendStream().Send(device.BlessServerMessageInstancePublicKey{Value: mPubKey}); err != nil {
+		return err
+	}
+	if !call.RecvStream().Advance() {
+		return verror.New(errors.ErrInvalidBlessing, ctx, fmt.Sprintf("no blessings on stream: %v", call.RecvStream().Err()))
+	}
+	msg := call.RecvStream().Value()
+	appBlessingsFromInstantiator, ok := msg.(device.BlessClientMessageAppBlessings)
+	if !ok {
+		return verror.New(errors.ErrInvalidBlessing, ctx, fmt.Sprintf("wrong message type: %#v", msg))
+	}
+	// Should we move this after the addition of publisher blessings, and thus allow
+	// apps to run with only publisher blessings?
+	if appBlessingsFromInstantiator.Value.IsZero() {
+		return verror.New(errors.ErrInvalidBlessing, ctx)
+	}
+	if err := p.BlessingStore().SetDefault(appBlessingsFromInstantiator.Value); err != nil {
+		return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("BlessingStore.SetDefault() failed: %v", err))
+	}
+	// If there were any publisher blessings in the envelope, add those to the set of blessings
+	// sent to servers by default
+	appBlessings, err := addPublisherBlessings(ctx, instanceDir, p, appBlessingsFromInstantiator.Value)
+	if _, err := p.BlessingStore().Set(appBlessings, security.AllPrincipals); err != nil {
+		return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("BlessingStore.Set() failed: %v", err))
+	}
+	if err := p.AddToRoots(appBlessings); err != nil {
+		return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("AddToRoots() failed: %v", err))
+	}
+	// In addition, we give the app separate blessings for the purpose of
+	// communicating with the device manager.
+	//
+	// NOTE(caprita/ataly): Giving the app an unconstrained blessing from
+	// the device manager's default presents the app with a blessing that's
+	// potentially more powerful than what is strictly needed to allow
+	// communication between device manager and app (which could be more
+	// narrowly accomplished by using a custom-purpose self-signed blessing
+	// that the device manger produces solely to talk to the app).
+	dmPrincipal := v23.GetPrincipal(ctx)
+	dmBlessings, err := dmPrincipal.Bless(p.PublicKey(), dmPrincipal.BlessingStore().Default(), "callback", security.UnconstrainedUse())
+	// Put the names of the device manager's default blessings as patterns
+	// for the child, so that the child uses the right blessing when talking
+	// back to the device manager.
+	for n, _ := range dmPrincipal.BlessingsInfo(dmPrincipal.BlessingStore().Default()) {
+		if _, err := p.BlessingStore().Set(dmBlessings, security.BlessingPattern(n)); err != nil {
+			return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("BlessingStore.Set() failed: %v", err))
+		}
+	}
+	// We also want to override the app cycle manager's server blessing in
+	// the child (so that the device manager can send RPCs to it).  We
+	// signal to the child's app manager to use a randomly generated pattern
+	// to extract the right blessing to use from its store for this purpose.
+	randomPattern, err := generateRandomString()
+	if err != nil {
+		return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("generateRandomString() failed: %v", err))
+	}
+	if _, err := p.BlessingStore().Set(dmBlessings, security.BlessingPattern(randomPattern)); err != nil {
+		return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("BlessingStore.Set() failed: %v", err))
+	}
+	info.DeviceManagerPeerPattern = randomPattern
+	if err := p.AddToRoots(dmBlessings); err != nil {
+		return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("AddToRoots() failed: %v", err))
+	}
+	return nil
+}
+
+func addPublisherBlessings(ctx *context.T, instanceDir string, p security.Principal, b security.Blessings) (security.Blessings, error) {
+	// Load the envelope for the instance, and get the publisher blessings in it
+	envelope, err := loadEnvelopeForInstance(ctx, instanceDir)
+	if err != nil {
+		return security.Blessings{}, err
+	}
+
+	// Extend the device manager blessing with each publisher blessing provided
+	dmPrincipal := v23.GetPrincipal(ctx)
+
+	blessings, _ := publisherBlessingNames(ctx, *envelope)
+	for _, s := range blessings {
+		ctx.VI(2).Infof("adding publisher blessing %v for app %v", s, envelope.Title)
+		tmpBlessing, err := dmPrincipal.Bless(p.PublicKey(), dmPrincipal.BlessingStore().Default(), "a/"+s, security.UnconstrainedUse())
+		if b, err = security.UnionOfBlessings(b, tmpBlessing); err != nil {
+			return b, verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("UnionOfBlessings failed: %v %v", b, tmpBlessing))
+		}
+	}
+
+	return b, nil
+}
+
+// installationDir returns the path to the directory containing the app
+// installation referred to by the invoker's suffix.  Returns an error if the
+// suffix does not name an installation or if the named installation does not
+// exist.
+func (i *appService) installationDir() (string, error) {
+	return installationDirCore(i.suffix, i.config.Root)
+}
+
+// installPackages installs all the packages for a new version.
+func installPackages(ctx *context.T, installationDir, versionDir string) error {
+	overridePackages, err := loadPackages(ctx, installationDir)
+	if err != nil {
+		return err
+	}
+	envelope, err := loadEnvelope(ctx, versionDir)
+	if err != nil {
+		return err
+	}
+	for pkg, _ := range overridePackages {
+		delete(envelope.Packages, pkg)
+	}
+	packagesDir := filepath.Join(versionDir, "packages")
+	if err := os.MkdirAll(packagesDir, os.FileMode(0755)); err != nil {
+		return err
+	}
+	installFrom := func(pkgs application.Packages, sourceDir string) error {
+		for pkg, _ := range pkgs {
+			pkgFile := filepath.Join(sourceDir, "pkg", pkg)
+			dst := filepath.Join(packagesDir, pkg)
+			if err := packages.Install(pkgFile, dst); err != nil {
+				return err
+			}
+		}
+		return nil
+	}
+	if err := installFrom(envelope.Packages, versionDir); err != nil {
+		return err
+	}
+	return installFrom(overridePackages, installationDir)
+}
+
+// initializeSubAccessLists updates the provided perms for instance-specific
+// Permissions.
+func (i *appService) initializeSubAccessLists(instanceDir string, blessings []string, perms access.Permissions) error {
+	for _, b := range blessings {
+		b = b + string(security.ChainSeparator) + string(security.NoExtension)
+		for _, tag := range access.AllTypicalTags() {
+			perms.Add(security.BlessingPattern(b), string(tag))
+		}
+	}
+	permsDir := path.Join(instanceDir, "acls")
+	return i.permsStore.Set(permsDir, perms, "")
+}
+
+func (i *appService) newInstance(ctx *context.T, call device.ApplicationInstantiateServerCall) (string, string, error) {
+	installationDir, err := i.installationDir()
+	if err != nil {
+		return "", "", err
+	}
+	if !installationStateIs(installationDir, device.InstallationStateActive) {
+		return "", "", verror.New(errors.ErrInvalidOperation, ctx)
+	}
+	instanceID := generateID()
+	instanceDir := filepath.Join(installationDir, "instances", instanceDirName(instanceID))
+	// Set permissions for app to have access.
+	if mkdirPerm(ctx, instanceDir, 0711) != nil {
+		return "", "", verror.New(errors.ErrOperationFailed, ctx)
+	}
+	rootDir := filepath.Join(instanceDir, "root")
+	if err := mkdir(ctx, rootDir); err != nil {
+		return instanceDir, instanceID, verror.New(errors.ErrOperationFailed, ctx, err)
+	}
+	installationLink := filepath.Join(instanceDir, "installation")
+	if err := os.Symlink(installationDir, installationLink); err != nil {
+		return instanceDir, instanceID, verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("Symlink(%v, %v) failed: %v", installationDir, installationLink, err))
+	}
+	currLink := filepath.Join(installationDir, "current")
+	versionDir, err := filepath.EvalSymlinks(currLink)
+	if err != nil {
+		return instanceDir, instanceID, verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("EvalSymlinks(%v) failed: %v", currLink, err))
+	}
+	versionLink := filepath.Join(instanceDir, "version")
+	if err := os.Symlink(versionDir, versionLink); err != nil {
+		return instanceDir, instanceID, verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("Symlink(%v, %v) failed: %v", versionDir, versionLink, err))
+	}
+	packagesDir, packagesLink := filepath.Join(versionLink, "packages"), filepath.Join(rootDir, "packages")
+	if err := os.Symlink(packagesDir, packagesLink); err != nil {
+		return instanceDir, instanceID, verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("Symlink(%v, %v) failed: %v", packagesDir, packagesLink, err))
+	}
+	instanceInfo := new(instanceInfo)
+	if err := setupPrincipal(ctx, instanceDir, call, i.runner.securityAgent, instanceInfo, i.config.Root); err != nil {
+		return instanceDir, instanceID, err
+	}
+	if err := saveInstanceInfo(ctx, instanceDir, instanceInfo); err != nil {
+		return instanceDir, instanceID, err
+	}
+	blessings, _ := security.RemoteBlessingNames(ctx, call.Security())
+	permsCopy := i.deviceAccessList.Copy()
+	if err := i.initializeSubAccessLists(instanceDir, blessings, permsCopy); err != nil {
+		return instanceDir, instanceID, err
+	}
+	if err := initializeInstance(instanceDir, device.InstanceStateNotRunning); err != nil {
+		return instanceDir, instanceID, err
+	}
+	// TODO(rjkroege): Divide the permission lists into those used by the device manager
+	// and those used by the application itself.
+	dmBlessings := security.LocalBlessingNames(ctx, call.Security())
+	if err := setPermsForDebugging(dmBlessings, permsCopy, instanceDir, i.permsStore); err != nil {
+		return instanceDir, instanceID, err
+	}
+	return instanceDir, instanceID, nil
+}
+
+func genCmd(ctx *context.T, instanceDir string, nsRoot string) (*exec.Cmd, error) {
+	systemName, err := readSystemNameForInstance(instanceDir)
+	if err != nil {
+		return nil, err
+	}
+
+	versionLink := filepath.Join(instanceDir, "version")
+	versionDir, err := filepath.EvalSymlinks(versionLink)
+	if err != nil {
+		return nil, verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("EvalSymlinks(%v) failed: %v", versionLink, err))
+	}
+	envelope, err := loadEnvelope(ctx, versionDir)
+	if err != nil {
+		return nil, err
+	}
+	binPath := filepath.Join(versionDir, "bin")
+	if _, err := os.Stat(binPath); err != nil {
+		return nil, verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("Stat(%v) failed: %v", binPath, err))
+	}
+
+	saArgs := suidAppCmdArgs{targetUser: systemName, binpath: binPath}
+
+	// Pass the displayed name of the program (argv0 as seen in ps output)
+	// Envelope data comes from the user so we sanitize it for safety
+	_, relativeBinaryName := naming.SplitAddressName(envelope.Binary.File)
+	rawAppName := envelope.Title + "@" + relativeBinaryName + "/app"
+	sanitize := func(r rune) rune {
+		if strconv.IsPrint(r) {
+			return r
+		} else {
+			return '_'
+		}
+	}
+	appName := strings.Map(sanitize, rawAppName)
+	saArgs.progname = appName
+
+	// Set the app's default namespace root to the local namespace.
+	saArgs.env = []string{ref.EnvNamespacePrefix + "=" + nsRoot}
+	saArgs.env = append(saArgs.env, envelope.Env...)
+	rootDir := filepath.Join(instanceDir, "root")
+	saArgs.dir = rootDir
+	saArgs.workspace = rootDir
+
+	logDir := filepath.Join(instanceDir, "logs")
+	suidHelper.chownTree(ctx, suidHelper.getCurrentUser(), instanceDir, os.Stdout, os.Stdin)
+	if err := mkdirPerm(ctx, logDir, 0755); err != nil {
+		return nil, err
+	}
+	saArgs.logdir = logDir
+	timestamp := time.Now().UnixNano()
+
+	stdoutLog := filepath.Join(logDir, fmt.Sprintf("STDOUT-%d", timestamp))
+	if saArgs.stdout, err = openWriteFile(stdoutLog); err != nil {
+		return nil, verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("OpenFile(%v) failed: %v", stdoutLog, err))
+	}
+	stderrLog := filepath.Join(logDir, fmt.Sprintf("STDERR-%d", timestamp))
+	if saArgs.stderr, err = openWriteFile(stderrLog); err != nil {
+		return nil, verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("OpenFile(%v) failed: %v", stderrLog, err))
+	}
+
+	// Args to be passed by helper to the app.
+	appArgs := []string{"--log_dir=../logs"}
+	appArgs = append(appArgs, envelope.Args...)
+
+	saArgs.appArgs = appArgs
+	return suidHelper.getAppCmd(ctx, &saArgs)
+}
+
+// instanceNameFromDir returns the instance name, given the instanceDir.
+func instanceNameFromDir(ctx *context.T, instanceDir string) (string, error) {
+	_, _, installation, instance := parseInstanceDir(instanceDir)
+	if installation == "" || instance == "" {
+		return "", fmt.Errorf("Unable to parse instanceDir %v", instanceDir)
+	}
+
+	env, err := loadEnvelopeForInstance(ctx, instanceDir)
+	if err != nil {
+		return "", err
+	}
+	return env.Title + "/" + installation + "/" + instance, nil
+}
+
+func (i *appRunner) startCmd(ctx *context.T, instanceDir string, cmd *exec.Cmd) (int, error) {
+	info, err := loadInstanceInfo(ctx, instanceDir)
+	if err != nil {
+		return 0, err
+	}
+	// Setup up the child process callback.
+	callbackState := i.callback
+	listener := callbackState.listenFor(mgmt.AppCycleManagerConfigKey)
+	defer listener.cleanup()
+	cfg := vexec.NewConfig()
+	installationLink := filepath.Join(instanceDir, "installation")
+	installationDir, err := filepath.EvalSymlinks(installationLink)
+	if err != nil {
+		return 0, verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("EvalSymlinks(%v) failed: %v", installationLink, err))
+	}
+	config, err := loadConfig(ctx, installationDir)
+	if err != nil {
+		return 0, err
+	}
+	for k, v := range config {
+		cfg.Set(k, v)
+	}
+	cfg.Set(mgmt.ParentNameConfigKey, listener.name())
+	cfg.Set(mgmt.ProtocolConfigKey, "tcp")
+	cfg.Set(mgmt.AddressConfigKey, "127.0.0.1:0")
+	cfg.Set(mgmt.ParentBlessingConfigKey, info.DeviceManagerPeerPattern)
+	cfg.Set(mgmt.PublisherBlessingPrefixesKey,
+		v23.GetPrincipal(ctx).BlessingStore().Default().String())
+
+	if instanceName, err := instanceNameFromDir(ctx, instanceDir); err != nil {
+		return 0, err
+	} else {
+		cfg.Set(mgmt.InstanceNameKey, naming.Join(i.appServiceName, instanceName))
+	}
+
+	appPermsDir := filepath.Join(instanceDir, "debugacls", "data")
+	cfg.Set("v23.permissions.file", "runtime:"+appPermsDir)
+
+	// This adds to cmd.Extrafiles. The helper expects a fixed fd, so this call needs
+	// to go before anything that conditionally adds to Extrafiles, like the agent
+	// setup code immediately below.
+	var handshaker appHandshaker
+	handshaker.prepareToStart(ctx, cmd)
+	defer handshaker.cleanup()
+
+	// Set up any agent-specific state.
+	// NOTE(caprita): This ought to belong in genCmd.
+	var agentCleaner func()
+	stopServingAgentSocket := true
+	switch sa := i.securityAgent; {
+	case sa != nil && sa.keyMgr != nil:
+		sockPath, err := sockPath(instanceDir)
+		if err != nil {
+			return 0, verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("failed to obtain socket path: %v", err))
+		}
+		if err := sa.keyMgr.ServePrincipal(info.handle(), sockPath); err != nil {
+			// TODO(caprita): Consider only logging a warning for
+			// verror.ErrExist errors if the principal is already
+			// serving.  This may point to some unhandled corner
+			// cases, but at lest we'd not prevent the app from
+			// running.
+			return 0, verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("ServePrincipal failed: %v", err))
+		}
+		cfg.Set(mgmt.SecurityAgentPathConfigKey, sockPath)
+		defer func() {
+			if !stopServingAgentSocket {
+				return
+			}
+			if err := sa.keyMgr.StopServing(info.handle()); err != nil {
+				ctx.Errorf("StopServing failed: %v", err)
+			}
+		}()
+	case sa != nil && sa.keyMgrAgent != nil:
+		// This code path is deprecated in favor of the socket agent
+		// connection.
+		file, err := sa.keyMgrAgent.NewConnection(info.SecurityAgentHandle)
+		if err != nil {
+			ctx.Errorf("NewConnection(%v) failed: %v", info.SecurityAgentHandle, err)
+			return 0, err
+		}
+		agentCleaner = func() {
+			file.Close()
+		}
+		// We need to account for the file descriptors corresponding to
+		// std{err|out|in} as well as the implementation-specific pipes
+		// that the vexec library adds to ExtraFiles during
+		// handle.Start.  vexec.FileOffset properly offsets fd
+		// accordingly.
+		fd := len(cmd.ExtraFiles) + vexec.FileOffset
+		cmd.ExtraFiles = append(cmd.ExtraFiles, file)
+		ep := agentlib.AgentEndpoint(fd)
+		cfg.Set(mgmt.SecurityAgentEndpointConfigKey, ep)
+	default:
+		cmd.Env = append(cmd.Env, ref.EnvCredentials+"="+filepath.Join(instanceDir, "credentials"))
+	}
+	handle := vexec.NewParentHandle(cmd, vexec.ConfigOpt{Config: cfg})
+	defer func() {
+		if handle != nil {
+			if err := handle.Clean(); err != nil {
+				ctx.Errorf("Clean() failed: %v", err)
+			}
+		}
+	}()
+
+	// Start the child process.
+	startErr := handle.Start()
+	// Perform unconditional cleanup before dealing with any error from
+	// handle.Start()
+	if agentCleaner != nil {
+		agentCleaner()
+	}
+	// Now react to any error in handle.Start()
+	if startErr != nil {
+		return 0, verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("Start() failed: %v", err))
+	}
+
+	// Wait for the suidhelper to exit. This is blocking as we assume the
+	// helper won't get stuck.
+	if err := handle.Wait(0); err != nil {
+		return 0, verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("Wait() on suidhelper failed: %v", err))
+	}
+
+	pid, childName, err := handshaker.doHandshake(ctx, handle, listener)
+
+	if err != nil {
+		return 0, err
+	}
+
+	info.AppCycleMgrName, info.Pid = childName, pid
+	if err := saveInstanceInfo(ctx, instanceDir, info); err != nil {
+		return 0, err
+	}
+	handle = nil
+	stopServingAgentSocket = false
+	return pid, nil
+}
+
+func (i *appRunner) run(ctx *context.T, instanceDir string) error {
+	if err := transitionInstance(instanceDir, device.InstanceStateNotRunning, device.InstanceStateLaunching); err != nil {
+		return err
+	}
+	var pid int
+
+	cmd, err := genCmd(ctx, instanceDir, i.mtAddress)
+	if err == nil {
+		pid, err = i.startCmd(ctx, instanceDir, cmd)
+	}
+	// TODO(caprita): If startCmd fails, we never reach startWatching; this
+	// means that the restart policy never kicks in, and the app stays dead.
+	// We should allow the app to be considered for restart if startCmd
+	// fails after having successfully started the app process.
+	if err != nil {
+		transitionInstance(instanceDir, device.InstanceStateLaunching, device.InstanceStateNotRunning)
+		return err
+	}
+	if err := transitionInstance(instanceDir, device.InstanceStateLaunching, device.InstanceStateRunning); err != nil {
+		return err
+	}
+	i.reap.startWatching(instanceDir, pid)
+	return nil
+}
+
+func synchronizedShouldRestart(ctx *context.T, instanceDir string) bool {
+	info, err := loadInstanceInfo(nil, instanceDir)
+	if err != nil {
+		ctx.Error(err)
+		return false
+	}
+
+	envelope, err := loadEnvelopeForInstance(nil, instanceDir)
+	if err != nil {
+		ctx.Error(err)
+		return false
+	}
+
+	shouldRestart := newBasicRestartPolicy().decide(envelope, info)
+
+	if err := saveInstanceInfo(nil, instanceDir, info); err != nil {
+		ctx.Error(err)
+		return false
+	}
+	return shouldRestart
+}
+
+// restartAppIfNecessary restarts an application if its daemon
+// configuration indicates that it should be running but the reaping
+// functionality has previously determined that it is not.
+// TODO(rjkroege): This routine has a low-likelyhood race condition in
+// which it fails to restart an application when the app crashes and the
+// device manager then crashes between the reaper marking the app not
+// running and the go routine invoking this function having a chance to
+// complete.
+func (i *appRunner) restartAppIfNecessary(ctx *context.T, instanceDir string) {
+	if err := transitionInstance(instanceDir, device.InstanceStateNotRunning, device.InstanceStateLaunching); err != nil {
+		ctx.Error(err)
+		return
+	}
+	// TODO(caprita): Putting the StopServing call here means that the
+	// socket is still serving after the app instance has been transitioned
+	// in state 'not running'.  This creates the possibility of a Run()
+	// happening after the app state has changed to 'not running' in the
+	// reaper, but before restartAppIfNecessary has a chance to execute
+	// (resulting int he ServePrincipal call failing).  We should either
+	// move the StopServing call before we transition the instance to 'not
+	// running', or make the ServePrincipal robust w.r.t. already serving
+	// state.
+	if sa := i.securityAgent; sa != nil && sa.keyMgr != nil {
+		info, err := loadInstanceInfo(ctx, instanceDir)
+		if err != nil {
+			ctx.Errorf("Failed to load instance info: %v", err)
+		}
+		if err := sa.keyMgr.StopServing(info.handle()); err != nil {
+			ctx.Errorf("StopServing failed: %v", err)
+		}
+	}
+	shouldRestart := synchronizedShouldRestart(ctx, instanceDir)
+
+	if err := transitionInstance(instanceDir, device.InstanceStateLaunching, device.InstanceStateNotRunning); err != nil {
+		ctx.Error(err)
+		return
+	}
+
+	if !shouldRestart {
+		return
+	}
+
+	if instanceName, err := instanceNameFromDir(ctx, instanceDir); err != nil {
+		ctx.Error(err)
+		i.stats.incrRestarts("unknown")
+	} else {
+		i.stats.incrRestarts(instanceName)
+	}
+
+	if err := i.run(ctx, instanceDir); err != nil {
+		ctx.Error(err)
+	}
+}
+
+func (i *appService) Instantiate(ctx *context.T, call device.ApplicationInstantiateServerCall) (string, error) {
+	helper := i.config.Helper
+	instanceDir, instanceID, err := i.newInstance(ctx, call)
+	if err != nil {
+		CleanupDir(ctx, instanceDir, helper)
+		return "", err
+	}
+	systemName := suidHelper.usernameForPrincipal(ctx, call.Security(), i.uat)
+	if err := saveSystemNameForInstance(instanceDir, systemName); err != nil {
+		CleanupDir(ctx, instanceDir, helper)
+		return "", err
+	}
+	return instanceID, nil
+}
+
+// instanceDir returns the path to the directory containing the app instance
+// referred to by the given suffix relative to the given root directory.
+// TODO(gauthamt): Make sure we pass the context to instanceDir.
+func instanceDir(root string, suffix []string) (string, error) {
+	if nComponents := len(suffix); nComponents != 3 {
+		return "", verror.New(errors.ErrInvalidSuffix, nil)
+	}
+	app, installation, instance := suffix[0], suffix[1], suffix[2]
+	instancesDir := filepath.Join(root, applicationDirName(app), installationDirName(installation), "instances")
+	instanceDir := filepath.Join(instancesDir, instanceDirName(instance))
+	return instanceDir, nil
+}
+
+// parseInstanceDir is a partial inverse of instanceDir. It cannot retrieve the app name,
+// as that has been hashed so it returns an appDir instead.
+func parseInstanceDir(dir string) (prefix, appDir, installation, instance string) {
+	dirRE := regexp.MustCompile("(/.*)(/" + appDirPrefix + "[^/]+)/" + installationPrefix + "([^/]+)/" + "instances/" + instancePrefix + "([^/]+)$")
+	matches := dirRE.FindStringSubmatch(dir)
+	if len(matches) < 5 {
+		return "", "", "", ""
+	}
+	return matches[1], matches[2], matches[3], matches[4]
+}
+
+// instanceDir returns the path to the directory containing the app instance
+// referred to by the invoker's suffix, as well as the corresponding not-running
+// instance dir.  Returns an error if the suffix does not name an instance.
+func (i *appService) instanceDir() (string, error) {
+	return instanceDir(i.config.Root, i.suffix)
+}
+
+func (i *appService) Run(ctx *context.T, call rpc.ServerCall) error {
+	instanceDir, err := i.instanceDir()
+	if err != nil {
+		return err
+	}
+
+	systemName := suidHelper.usernameForPrincipal(ctx, call.Security(), i.uat)
+	startSystemName, err := readSystemNameForInstance(instanceDir)
+	if err != nil {
+		return err
+	}
+
+	if startSystemName != systemName {
+		return verror.New(verror.ErrNoAccess, ctx, "Not allowed to resume an application under a different system name.")
+	}
+
+	i.stats.incrRuns(naming.Join(i.suffix...))
+
+	// TODO(caprita): We should reset the Restarts and RestartWindowBegan
+	// fields in the instance info when the instance is started with Run.
+
+	return i.runner.run(ctx, instanceDir)
+}
+
+func stopAppRemotely(ctx *context.T, appVON string, deadline time.Duration) error {
+	appStub := appcycle.AppCycleClient(appVON)
+	ctx, cancel := context.WithTimeout(ctx, deadline)
+	defer cancel()
+	stream, err := appStub.Stop(ctx)
+	if err != nil {
+		return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("%v.Stop() failed: %v", appVON, err))
+	}
+	rstream := stream.RecvStream()
+	for rstream.Advance() {
+		ctx.VI(2).Infof("%v.Stop() task update: %v", appVON, rstream.Value())
+	}
+	if err := rstream.Err(); err != nil {
+		return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("Advance() failed: %v", err))
+	}
+	if err := stream.Finish(); err != nil {
+		return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("Finish() failed: %v", err))
+	}
+	return nil
+}
+
+func (i *appService) stop(ctx *context.T, instanceDir string, reap *reaper, deadline time.Duration) error {
+	info, err := loadInstanceInfo(ctx, instanceDir)
+	if err != nil {
+		return err
+	}
+	err = stopAppRemotely(ctx, info.AppCycleMgrName, deadline)
+	reap.forciblySuspend(instanceDir)
+	if err == nil {
+		reap.stopWatching(instanceDir)
+		if sa := i.runner.securityAgent; sa != nil && sa.keyMgr != nil {
+			if err := sa.keyMgr.StopServing(info.handle()); err != nil {
+				ctx.Errorf("StopServing failed: %v", err)
+			}
+		}
+	}
+	return err
+}
+
+func (i *appService) Delete(ctx *context.T, _ rpc.ServerCall) error {
+	instanceDir, err := i.instanceDir()
+	if err != nil {
+		return err
+	}
+	return transitionInstance(instanceDir, device.InstanceStateNotRunning, device.InstanceStateDeleted)
+}
+
+func (i *appService) Kill(ctx *context.T, _ rpc.ServerCall, deadline time.Duration) error {
+	instanceDir, err := i.instanceDir()
+	if err != nil {
+		return err
+	}
+	if err := transitionInstance(instanceDir, device.InstanceStateRunning, device.InstanceStateDying); err != nil {
+		return err
+	}
+	if err := i.stop(ctx, instanceDir, i.runner.reap, deadline); err != nil {
+		transitionInstance(instanceDir, device.InstanceStateDying, device.InstanceStateRunning)
+		return err
+	}
+	return transitionInstance(instanceDir, device.InstanceStateDying, device.InstanceStateNotRunning)
+}
+
+func (i *appService) Uninstall(*context.T, rpc.ServerCall) error {
+	installationDir, err := i.installationDir()
+	if err != nil {
+		return err
+	}
+	return transitionInstallation(installationDir, device.InstallationStateActive, device.InstallationStateUninstalled)
+}
+
+func updateInstance(ctx *context.T, instanceDir string) (err error) {
+	// Only not-running instances can be updated.
+	if err := transitionInstance(instanceDir, device.InstanceStateNotRunning, device.InstanceStateUpdating); err != nil {
+		return err
+	}
+	defer func() {
+		terr := transitionInstance(instanceDir, device.InstanceStateUpdating, device.InstanceStateNotRunning)
+		if err == nil {
+			err = terr
+		}
+	}()
+	// Check if a newer version of the installation is available.
+	versionLink := filepath.Join(instanceDir, "version")
+	versionDir, err := filepath.EvalSymlinks(versionLink)
+	if err != nil {
+		return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("EvalSymlinks(%v) failed: %v", versionLink, err))
+	}
+	latestVersionLink := filepath.Join(instanceDir, "installation", "current")
+	latestVersionDir, err := filepath.EvalSymlinks(latestVersionLink)
+	if err != nil {
+		return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("EvalSymlinks(%v) failed: %v", latestVersionLink, err))
+	}
+	if versionDir == latestVersionDir {
+		return verror.New(errors.ErrUpdateNoOp, ctx)
+	}
+	// Update to the newer version.  Note, this is the only mutation
+	// performed to the instance, and, since it's atomic, the state of the
+	// instance is consistent at all times.
+	return UpdateLink(latestVersionDir, versionLink)
+}
+
+func updateInstallation(ctx *context.T, installationDir string) error {
+	if !installationStateIs(installationDir, device.InstallationStateActive) {
+		return verror.New(errors.ErrInvalidOperation, ctx)
+	}
+	originVON, err := loadOrigin(ctx, installationDir)
+	if err != nil {
+		return err
+	}
+	ctx, cancel := context.WithTimeout(ctx, rpcContextLongTimeout)
+	defer cancel()
+	newEnvelope, err := fetchAppEnvelope(ctx, originVON)
+	if err != nil {
+		return err
+	}
+	currLink := filepath.Join(installationDir, "current")
+	oldVersionDir, err := filepath.EvalSymlinks(currLink)
+	if err != nil {
+		return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("EvalSymlinks(%v) failed: %v", currLink, err))
+	}
+	// NOTE(caprita): A race can occur between two competing updates, where
+	// both use the old version as their baseline.  This can result in both
+	// updates succeeding even if they are updating the app installation to
+	// the same new envelope.  This will result in one of the updates
+	// becoming the new 'current'.  Both versions will point their
+	// 'previous' link to the old version.  This doesn't appear to be of
+	// practical concern, so we avoid the complexity of synchronizing
+	// updates.
+	oldEnvelope, err := loadEnvelope(ctx, oldVersionDir)
+	if err != nil {
+		return err
+	}
+	if oldEnvelope.Title != newEnvelope.Title {
+		return verror.New(errors.ErrAppTitleMismatch, ctx)
+	}
+	if reflect.DeepEqual(oldEnvelope, newEnvelope) {
+		return verror.New(errors.ErrUpdateNoOp, ctx)
+	}
+	versionDir, err := newVersion(ctx, installationDir, newEnvelope, oldVersionDir)
+	if err != nil {
+		CleanupDir(ctx, versionDir, "")
+		return err
+	}
+	return nil
+}
+
+func (i *appService) Update(ctx *context.T, _ rpc.ServerCall) error {
+	if installationDir, err := i.installationDir(); err == nil {
+		return updateInstallation(ctx, installationDir)
+	}
+	if instanceDir, err := i.instanceDir(); err == nil {
+		return updateInstance(ctx, instanceDir)
+	}
+	return verror.New(errors.ErrInvalidSuffix, nil)
+}
+
+func (*appService) UpdateTo(_ *context.T, _ rpc.ServerCall, von string) error {
+	// TODO(jsimsa): Implement.
+	return nil
+}
+
+func revertInstance(ctx *context.T, instanceDir string) (err error) {
+	// Only not-running instances can be reverted.
+	if err := transitionInstance(instanceDir, device.InstanceStateNotRunning, device.InstanceStateUpdating); err != nil {
+		return err
+	}
+	defer func() {
+		terr := transitionInstance(instanceDir, device.InstanceStateUpdating, device.InstanceStateNotRunning)
+		if err == nil {
+			err = terr
+		}
+	}()
+	versionLink := filepath.Join(instanceDir, "version")
+	versionDir, err := filepath.EvalSymlinks(versionLink)
+	if err != nil {
+		return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("EvalSymlinks(%v) failed: %v", versionLink, err))
+	}
+	previousLink := filepath.Join(versionDir, "previous")
+	if _, err := os.Lstat(previousLink); err != nil {
+		if os.IsNotExist(err) {
+			// No 'previous' link -- must be the first version.
+			return verror.New(errors.ErrUpdateNoOp, ctx)
+		}
+		return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("Lstat(%v) failed: %v", previousLink, err))
+	}
+	prevVersionDir, err := filepath.EvalSymlinks(previousLink)
+	if err != nil {
+		return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("EvalSymlinks(%v) failed: %v", previousLink, err))
+	}
+	return UpdateLink(prevVersionDir, versionLink)
+}
+
+func revertInstallation(ctx *context.T, installationDir string) error {
+	if !installationStateIs(installationDir, device.InstallationStateActive) {
+		return verror.New(errors.ErrInvalidOperation, ctx)
+	}
+	// NOTE(caprita): A race can occur between an update and a revert, where
+	// both use the same current version as their starting point.  This will
+	// render the update inconsequential.  This doesn't appear to be of
+	// practical concern, so we avoid the complexity of synchronizing
+	// updates and revert operations.
+	currLink := filepath.Join(installationDir, "current")
+	currVersionDir, err := filepath.EvalSymlinks(currLink)
+	if err != nil {
+		return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("EvalSymlinks(%v) failed: %v", currLink, err))
+	}
+	previousLink := filepath.Join(currVersionDir, "previous")
+	if _, err := os.Lstat(previousLink); err != nil {
+		if os.IsNotExist(err) {
+			// No 'previous' link -- must be the first version.
+			return verror.New(errors.ErrUpdateNoOp, ctx)
+		}
+		return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("Lstat(%v) failed: %v", previousLink, err))
+	}
+	prevVersionDir, err := filepath.EvalSymlinks(previousLink)
+	if err != nil {
+		return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("EvalSymlinks(%v) failed: %v", previousLink, err))
+	}
+	return UpdateLink(prevVersionDir, currLink)
+}
+
+func (i *appService) Revert(ctx *context.T, _ rpc.ServerCall) error {
+	if installationDir, err := i.installationDir(); err == nil {
+		return revertInstallation(ctx, installationDir)
+	}
+	if instanceDir, err := i.instanceDir(); err == nil {
+		return revertInstance(ctx, instanceDir)
+	}
+	return verror.New(errors.ErrInvalidSuffix, nil)
+}
+
+type treeNode struct {
+	children map[string]*treeNode
+}
+
+func newTreeNode() *treeNode {
+	return &treeNode{children: make(map[string]*treeNode)}
+}
+
+func (n *treeNode) find(names []string, create bool) *treeNode {
+	for {
+		if len(names) == 0 {
+			return n
+		}
+		if next, ok := n.children[names[0]]; ok {
+			n = next
+			names = names[1:]
+			continue
+		}
+		if create {
+			nn := newTreeNode()
+			n.children[names[0]] = nn
+			n = nn
+			names = names[1:]
+			continue
+		}
+		return nil
+	}
+}
+
+func (i *appService) scanEnvelopes(ctx *context.T, tree *treeNode, appDir string) {
+	// Find all envelopes, extract installID.
+	envGlob := []string{i.config.Root, appDir, installationPrefix + "*", "*", "envelope"}
+	envelopes, err := filepath.Glob(filepath.Join(envGlob...))
+	if err != nil {
+		ctx.Errorf("unexpected error: %v", err)
+		return
+	}
+	for _, path := range envelopes {
+		env, err := loadEnvelope(ctx, filepath.Dir(path))
+		if err != nil {
+			continue
+		}
+		relpath, _ := filepath.Rel(i.config.Root, path)
+		elems := strings.Split(relpath, string(filepath.Separator))
+		if len(elems) != len(envGlob)-1 {
+			ctx.Errorf("unexpected number of path components: %q (%q)", elems, path)
+			continue
+		}
+		installID := strings.TrimPrefix(elems[1], installationPrefix)
+		tree.find([]string{env.Title, installID}, true)
+	}
+	return
+}
+
+func (i *appService) scanInstances(ctx *context.T, tree *treeNode) {
+	if len(i.suffix) < 2 {
+		return
+	}
+	title := i.suffix[0]
+	installDir, err := installationDirCore(i.suffix[:2], i.config.Root)
+	if err != nil {
+		return
+	}
+	// Add the node corresponding to the installation itself.
+	tree.find(i.suffix[:2], true)
+	// Find all instances.
+	infoGlob := []string{installDir, "instances", instancePrefix + "*", "info"}
+	instances, err := filepath.Glob(filepath.Join(infoGlob...))
+	if err != nil {
+		ctx.Errorf("unexpected error: %v", err)
+		return
+	}
+	for _, path := range instances {
+		instanceDir := filepath.Dir(path)
+		i.scanInstance(ctx, tree, title, instanceDir)
+	}
+	return
+}
+
+func (i *appService) scanInstance(ctx *context.T, tree *treeNode, title, instanceDir string) {
+	if _, err := loadInstanceInfo(ctx, instanceDir); err != nil {
+		return
+	}
+	rootDir, _, installID, instanceID := parseInstanceDir(instanceDir)
+	if installID == "" || instanceID == "" || filepath.Clean(i.config.Root) != filepath.Clean(rootDir) {
+		ctx.Errorf("failed to parse instanceDir %v (got: %v %v %v)", instanceDir, rootDir, installID, instanceID)
+		return
+	}
+
+	tree.find([]string{title, installID, instanceID, "logs"}, true)
+	if instanceStateIs(instanceDir, device.InstanceStateRunning) {
+		for _, obj := range []string{"pprof", "stats"} {
+			tree.find([]string{title, installID, instanceID, obj}, true)
+		}
+	}
+}
+
+func (i *appService) GlobChildren__(ctx *context.T, call rpc.GlobChildrenServerCall, m *glob.Element) error {
+	tree := newTreeNode()
+	switch len(i.suffix) {
+	case 0:
+		i.scanEnvelopes(ctx, tree, appDirPrefix+"*")
+	case 1:
+		appDir := applicationDirName(i.suffix[0])
+		i.scanEnvelopes(ctx, tree, appDir)
+	case 2:
+		i.scanInstances(ctx, tree)
+	case 3:
+		dir, err := i.instanceDir()
+		if err != nil {
+			break
+		}
+		i.scanInstance(ctx, tree, i.suffix[0], dir)
+	default:
+		return verror.New(verror.ErrNoExist, nil, i.suffix)
+	}
+	n := tree.find(i.suffix, false)
+	if n == nil {
+		return verror.New(errors.ErrInvalidSuffix, nil)
+	}
+	for child, _ := range n.children {
+		if m.Match(child) {
+			call.SendStream().Send(naming.GlobChildrenReplyName{Value: child})
+		}
+	}
+	return nil
+}
+
+// TODO(rjkroege): Refactor to eliminate redundancy with newAppSpecificAuthorizer.
+func dirFromSuffix(ctx *context.T, suffix []string, root string) (string, bool, error) {
+	if len(suffix) == 2 {
+		p, err := installationDirCore(suffix, root)
+		if err != nil {
+			ctx.Errorf("dirFromSuffix failed: %v", err)
+			return "", false, err
+		}
+		return p, false, nil
+	} else if len(suffix) > 2 {
+		p, err := instanceDir(root, suffix[0:3])
+		if err != nil {
+			ctx.Errorf("dirFromSuffix failed: %v", err)
+			return "", false, err
+		}
+		return p, true, nil
+	}
+	return "", false, verror.New(errors.ErrInvalidSuffix, nil)
+}
+
+// TODO(rjkroege): Consider maintaining an in-memory Permissions cache.
+func (i *appService) SetPermissions(ctx *context.T, call rpc.ServerCall, perms access.Permissions, version string) error {
+	dir, isInstance, err := dirFromSuffix(ctx, i.suffix, i.config.Root)
+	if err != nil {
+		return err
+	}
+	if isInstance {
+		dmBlessings := security.LocalBlessingNames(ctx, call.Security())
+		if err := setPermsForDebugging(dmBlessings, perms, dir, i.permsStore); err != nil {
+			return err
+		}
+	}
+	return i.permsStore.Set(path.Join(dir, "acls"), perms, version)
+}
+
+func (i *appService) GetPermissions(ctx *context.T, _ rpc.ServerCall) (perms access.Permissions, version string, err error) {
+	dir, _, err := dirFromSuffix(ctx, i.suffix, i.config.Root)
+	if err != nil {
+		return nil, "", err
+	}
+	return i.permsStore.Get(path.Join(dir, "acls"))
+}
+
+func (i *appService) Debug(ctx *context.T, call rpc.ServerCall) (string, error) {
+	switch len(i.suffix) {
+	case 2:
+		return i.installationDebug(ctx)
+	case 3:
+		return i.instanceDebug(ctx, call.Security())
+	default:
+		return "", verror.New(errors.ErrInvalidSuffix, nil)
+	}
+}
+
+func (i *appService) installationDebug(ctx *context.T) (string, error) {
+	const installationDebug = `Installation dir: {{.InstallationDir}}
+
+Origin: {{.Origin}}
+
+Envelope: {{printf "%+v" .Envelope}}
+
+Config: {{printf "%+v" .Config}}
+`
+	installationDebugTemplate, err := template.New("installation-debug").Parse(installationDebug)
+	if err != nil {
+		return "", err
+	}
+
+	installationDir, err := i.installationDir()
+	if err != nil {
+		return "", err
+	}
+	debugInfo := struct {
+		InstallationDir, Origin string
+		Envelope                *application.Envelope
+		Config                  device.Config
+	}{}
+	debugInfo.InstallationDir = installationDir
+
+	if origin, err := loadOrigin(ctx, installationDir); err != nil {
+		return "", err
+	} else {
+		debugInfo.Origin = origin
+	}
+
+	currLink := filepath.Join(installationDir, "current")
+	if envelope, err := loadEnvelope(ctx, currLink); err != nil {
+		return "", err
+	} else {
+		debugInfo.Envelope = envelope
+	}
+
+	if config, err := loadConfig(ctx, installationDir); err != nil {
+		return "", err
+	} else {
+		debugInfo.Config = config
+	}
+
+	var buf bytes.Buffer
+	if err := installationDebugTemplate.Execute(&buf, debugInfo); err != nil {
+		return "", err
+	}
+	return buf.String(), nil
+
+}
+
+func (i *appService) instanceDebug(ctx *context.T, call security.Call) (string, error) {
+	const instanceDebug = `Instance dir: {{.InstanceDir}}
+
+System name / start system name: {{.SystemName}} / {{.StartSystemName}}
+
+Cmd: {{printf "%+v" .Cmd}}
+
+Envelope: {{printf "%+v" .Envelope}}
+
+Info: {{printf "%+v" .Info}}
+
+Principal: {{.PrincipalType}}
+Public Key: {{.Principal.PublicKey}}
+Blessing Store: {{.Principal.BlessingStore.DebugString}}
+Roots: {{.Principal.Roots.DebugString}}
+`
+	instanceDebugTemplate, err := template.New("instance-debug").Parse(instanceDebug)
+	if err != nil {
+		return "", err
+	}
+
+	instanceDir, err := i.instanceDir()
+	if err != nil {
+		return "", err
+	}
+	debugInfo := struct {
+		InstanceDir, SystemName, StartSystemName string
+		Cmd                                      *exec.Cmd
+		Envelope                                 *application.Envelope
+		Info                                     *instanceInfo
+		Principal                                security.Principal
+		PrincipalType                            string
+	}{}
+	debugInfo.InstanceDir = instanceDir
+
+	debugInfo.SystemName = suidHelper.usernameForPrincipal(ctx, call, i.uat)
+	if startSystemName, err := readSystemNameForInstance(instanceDir); err != nil {
+		return "", err
+	} else {
+		debugInfo.StartSystemName = startSystemName
+	}
+
+	if info, err := loadInstanceInfo(ctx, instanceDir); err != nil {
+		return "", err
+	} else {
+		debugInfo.Info = info
+	}
+	if cmd, err := genCmd(ctx, instanceDir, i.runner.mtAddress); err != nil {
+		return "", err
+	} else {
+		debugInfo.Cmd = cmd
+	}
+
+	if envelope, err := loadEnvelopeForInstance(ctx, instanceDir); err != nil {
+		return "", err
+	} else {
+		debugInfo.Envelope = envelope
+	}
+
+	switch sa := i.runner.securityAgent; {
+	case sa != nil && sa.keyMgr != nil:
+		// Try connecting to principal, if fails try serving, then
+		// connect, then stop serving.
+		// TODO(caprita): This is brittle.
+		sockPath, err := sockPath(instanceDir)
+		if err != nil {
+			return "", err
+		}
+		if debugInfo.Principal, err = agentlib.NewAgentPrincipalX(sockPath); err != nil {
+			agentHandle := debugInfo.Info.handle()
+			// TODO(caprita): This will interfere with the
+			// ServePrincipal call when Run'ning the instance.  We
+			// should instead ref count the principal and
+			// StopServing when the last user goes away.
+			if err := sa.keyMgr.ServePrincipal(agentHandle, sockPath); err != nil {
+				return "", err
+			}
+			if debugInfo.Principal, err = agentlib.NewAgentPrincipalX(sockPath); err != nil {
+				return "", err
+			}
+			defer func() {
+				if err := sa.keyMgr.StopServing(agentHandle); err != nil {
+					ctx.Errorf("StopServing failed: %v", err)
+				}
+			}()
+		}
+		debugInfo.PrincipalType = "Agent-based"
+	case sa != nil && sa.keyMgrAgent != nil:
+		file, err := sa.keyMgrAgent.NewConnection(debugInfo.Info.SecurityAgentHandle)
+		if err != nil {
+			ctx.Errorf("NewConnection(%v) failed: %v", debugInfo.Info.SecurityAgentHandle, err)
+			return "", err
+		}
+		var cancel func()
+		if debugInfo.Principal, cancel, err = agentPrincipal(ctx, file); err != nil {
+			return "", err
+		}
+		defer cancel()
+		debugInfo.PrincipalType = "Agent-based-deprecated"
+	default:
+		credentialsDir := filepath.Join(instanceDir, "credentials")
+		var err error
+		if debugInfo.Principal, err = vsecurity.LoadPersistentPrincipal(credentialsDir, nil); err != nil {
+			return "", err
+		}
+		debugInfo.PrincipalType = fmt.Sprintf("Credentials dir-based (%v)", credentialsDir)
+	}
+	var buf bytes.Buffer
+	if err := instanceDebugTemplate.Execute(&buf, debugInfo); err != nil {
+		return "", err
+	}
+	return buf.String(), nil
+}
+
+func (i *appService) Status(ctx *context.T, _ rpc.ServerCall) (device.Status, error) {
+	switch len(i.suffix) {
+	case 2:
+		status, err := i.installationStatus(ctx)
+		return device.StatusInstallation{Value: status}, err
+	case 3:
+		status, err := i.instanceStatus(ctx)
+		return device.StatusInstance{Value: status}, err
+	default:
+		return nil, verror.New(errors.ErrInvalidSuffix, ctx)
+	}
+}
+
+func (i *appService) installationStatus(ctx *context.T) (device.InstallationStatus, error) {
+	installationDir, err := i.installationDir()
+	if err != nil {
+		return device.InstallationStatus{}, err
+	}
+	state, err := getInstallationState(installationDir)
+	if err != nil {
+		return device.InstallationStatus{}, err
+	}
+	versionLink := filepath.Join(installationDir, "current")
+	versionDir, err := filepath.EvalSymlinks(versionLink)
+	if err != nil {
+		return device.InstallationStatus{}, verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("EvalSymlinks(%v) failed: %v", versionLink, err))
+	}
+	return device.InstallationStatus{
+		State:   state,
+		Version: filepath.Base(versionDir),
+	}, nil
+}
+
+func (i *appService) instanceStatus(ctx *context.T) (device.InstanceStatus, error) {
+	instanceDir, err := i.instanceDir()
+	if err != nil {
+		return device.InstanceStatus{}, err
+	}
+	state, err := getInstanceState(instanceDir)
+	if err != nil {
+		return device.InstanceStatus{}, err
+	}
+	versionLink := filepath.Join(instanceDir, "version")
+	versionDir, err := filepath.EvalSymlinks(versionLink)
+	if err != nil {
+		return device.InstanceStatus{}, verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("EvalSymlinks(%v) failed: %v", versionLink, err))
+	}
+	return device.InstanceStatus{
+		State:   state,
+		Version: filepath.Base(versionDir),
+	}, nil
+}
diff --git a/services/device/deviced/internal/impl/app_starting_util.go b/services/device/deviced/internal/impl/app_starting_util.go
new file mode 100644
index 0000000..03277e8
--- /dev/null
+++ b/services/device/deviced/internal/impl/app_starting_util.go
@@ -0,0 +1,182 @@
+// 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 impl
+
+// TODO -- Ideally the code in this file would be integrated with the instance reaping,
+// so we avoid having two process polling loops. This code is currently separate because
+// the actions taken when the app dies (or is caught lying about its pid) prior to being
+// considered running are fairly different from what's currently done by the reaper in
+// handling deaths that occur after the app started successfully.
+
+import (
+	"encoding/binary"
+	"fmt"
+	"os"
+	"os/exec"
+	"syscall"
+	"time"
+
+	"v.io/v23/context"
+	"v.io/v23/verror"
+	vexec "v.io/x/ref/lib/exec"
+	"v.io/x/ref/services/device/internal/errors"
+	"v.io/x/ref/services/device/internal/suid"
+)
+
+// appWatcher watches the pid of a running app until either the pid exits or stop()
+// is called
+type appWatcher struct {
+	pid      int           // Pid to watch
+	callback func()        // Called if the pid exits or if stop() is invoked
+	stopper  chan struct{} // Used to stop the appWatcher
+}
+
+func newAppWatcher(pidToWatch int, callOnPidExit func()) *appWatcher {
+	return &appWatcher{
+		pid:      pidToWatch,
+		callback: callOnPidExit,
+		stopper:  make(chan struct{}, 1),
+	}
+}
+
+func (a *appWatcher) stop() {
+	close(a.stopper)
+}
+
+func (a *appWatcher) watchAppPid(ctx *context.T) {
+	defer a.callback()
+
+	ticker := time.NewTicker(1 * time.Second)
+	defer ticker.Stop()
+
+	for {
+		select {
+		case <-ticker.C:
+			if err := syscall.Kill(a.pid, 0); err != nil && err != syscall.EPERM {
+				ctx.Errorf("App died in startup: pid=%d: %v", a.pid, err)
+				return
+			} else {
+				ctx.VI(2).Infof("App pid %d is alive", a.pid)
+			}
+
+		case <-a.stopper:
+			ctx.Errorf("AppWatcher was stopped")
+			return
+		}
+	}
+	// Not reached.
+}
+
+// appHandshaker is a utility to do the app handshake for a newly started app while
+// reacting quickly if the app crashes. appHandshaker reads two pids from the app (one
+// from the helper that forked the app, and the other from the app itself). If the app
+// appears to be lying about its own pid, it will kill the app.
+type appHandshaker struct {
+	helperRead, helperWrite *os.File
+}
+
+func (a *appHandshaker) cleanup() {
+	if a.helperRead != nil {
+		a.helperRead.Close()
+		a.helperRead = nil
+	}
+	if a.helperWrite != nil {
+		a.helperWrite.Close()
+		a.helperWrite = nil
+	}
+}
+
+// prepareToStart sets up the pipe used to talk to the helper. It must be called before
+// the app is started so that the app will inherit the file descriptor
+func (a *appHandshaker) prepareToStart(ctx *context.T, cmd *exec.Cmd) error {
+	if suid.PipeToParentFD != (len(cmd.ExtraFiles) + vexec.FileOffset) {
+		return verror.New(errors.ErrOperationFailed, ctx,
+			fmt.Sprintf("FD expected by helper (%v) was not available (%v) (%v)",
+				suid.PipeToParentFD, len(cmd.ExtraFiles), vexec.FileOffset))
+	}
+	var err error
+	a.helperRead, a.helperWrite, err = os.Pipe()
+	if err != nil {
+		ctx.Errorf("Failed to create pipe: %v", err)
+		return err
+	}
+	cmd.ExtraFiles = append(cmd.ExtraFiles, a.helperWrite)
+	return nil
+}
+
+// doAppHandshake executes the startup handshake for the app. Upon success, it returns the
+// pid and appCycle manager name for the started app.
+//
+// handle should have been set up to use a helper for the app and handle.Start()
+// and handle.Wait() should already have been called (so we know the helper is done)
+func (a *appHandshaker) doHandshake(ctx *context.T, handle *vexec.ParentHandle, listener callbackListener) (int, string, error) {
+	// Close our copy of helperWrite to make helperRead return EOF once the
+	// helper's copy of helperWrite is closed.
+	a.helperWrite.Close()
+	a.helperWrite = nil
+
+	// Get the app pid from the helper. This won't block as the helper is done
+	var pid32 int32
+	if err := binary.Read(a.helperRead, binary.LittleEndian, &pid32); err != nil {
+		ctx.Errorf("Error reading app pid from child: %v", err)
+		return 0, "", verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("failed to read pid from helper: %v", err))
+	}
+	pidFromHelper := int(pid32)
+	ctx.VI(1).Infof("read app pid %v from child", pidFromHelper)
+
+	// Watch the app pid in case it exits.
+	pidExitedChan := make(chan struct{}, 1)
+	watcher := newAppWatcher(pidFromHelper, func() {
+		listener.stop()
+		close(pidExitedChan)
+	})
+	go watcher.watchAppPid(ctx)
+	defer watcher.stop()
+
+	// Wait for the child to say it's ready and provide its own pid via the init handshake
+	childReadyErrChan := make(chan error, 1)
+	go func() {
+		if err := handle.WaitForReady(childReadyTimeout); err != nil {
+			childReadyErrChan <- verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("WaitForReady(%v) failed: %v", childReadyTimeout, err))
+		}
+		childReadyErrChan <- nil
+	}()
+
+	// Wait until we get the pid from the app, but return early if
+	// the watcher notices that the app failed
+	pidFromChild := 0
+
+	select {
+	case <-pidExitedChan:
+		return 0, "", verror.New(errors.ErrOperationFailed, ctx,
+			fmt.Sprintf("App exited (pid %d)", pidFromHelper))
+
+	case err := <-childReadyErrChan:
+		if err != nil {
+			return 0, "", err
+		}
+		// Note: handle.Pid() is the pid of the helper, rather than that
+		// of the app that the helper then forked. ChildPid is the pid
+		// received via the app startup handshake
+		pidFromChild = handle.ChildPid()
+	}
+
+	if pidFromHelper != pidFromChild {
+		// Something nasty is going on (the child may be lying).
+		suidHelper.terminatePid(ctx, pidFromHelper, nil, nil)
+		return 0, "", verror.New(errors.ErrOperationFailed, ctx,
+			fmt.Sprintf("Child pids do not match! (%d != %d)", pidFromHelper, pidFromChild))
+	}
+
+	// The appWatcher will stop the listener if the pid dies while waiting below
+	childName, err := listener.waitForValue(childReadyTimeout)
+	if err != nil {
+		suidHelper.terminatePid(ctx, pidFromHelper, nil, nil)
+		return 0, "", verror.New(errors.ErrOperationFailed, ctx,
+			fmt.Sprintf("Waiting for child name: %v", err))
+	}
+
+	return pidFromHelper, childName, nil
+}
diff --git a/services/device/deviced/internal/impl/app_state.go b/services/device/deviced/internal/impl/app_state.go
new file mode 100644
index 0000000..98936d2
--- /dev/null
+++ b/services/device/deviced/internal/impl/app_state.go
@@ -0,0 +1,91 @@
+// 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 impl
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+
+	"v.io/v23/services/device"
+	"v.io/v23/verror"
+	"v.io/x/ref/services/device/internal/errors"
+)
+
+func getInstallationState(installationDir string) (device.InstallationState, error) {
+	for i := 0; i < 2; i++ {
+		// TODO(caprita): This is racy w.r.t. instances that are
+		// transitioning states.  We currently do a retry because of
+		// this, which in practice should be sufficient; a more
+		// deterministic solution would involve changing the way we
+		// store status.
+		for _, s := range device.InstallationStateAll {
+			if installationStateIs(installationDir, s) {
+				return s, nil
+			}
+		}
+	}
+	return device.InstallationStateActive, verror.New(errors.ErrOperationFailed, nil, fmt.Sprintf("failed to determine state for installation in dir %v", installationDir))
+}
+
+func installationStateIs(installationDir string, state device.InstallationState) bool {
+	if _, err := os.Stat(filepath.Join(installationDir, state.String())); err != nil {
+		return false
+	}
+	return true
+}
+
+func transitionInstallation(installationDir string, initial, target device.InstallationState) error {
+	return transitionState(installationDir, initial, target)
+}
+
+func initializeInstallation(installationDir string, initial device.InstallationState) error {
+	return initializeState(installationDir, initial)
+}
+
+func getInstanceState(instanceDir string) (device.InstanceState, error) {
+	for _, s := range device.InstanceStateAll {
+		if instanceStateIs(instanceDir, s) {
+			return s, nil
+		}
+	}
+	return device.InstanceStateLaunching, verror.New(errors.ErrOperationFailed, nil, fmt.Sprintf("failed to determine state for instance in dir %v", instanceDir))
+}
+
+func instanceStateIs(instanceDir string, state device.InstanceState) bool {
+	if _, err := os.Stat(filepath.Join(instanceDir, state.String())); err != nil {
+		return false
+	}
+	return true
+}
+
+func transitionInstance(instanceDir string, initial, target device.InstanceState) error {
+	return transitionState(instanceDir, initial, target)
+}
+
+func initializeInstance(instanceDir string, initial device.InstanceState) error {
+	return initializeState(instanceDir, initial)
+}
+
+func transitionState(dir string, initial, target fmt.Stringer) error {
+	initialState := filepath.Join(dir, initial.String())
+	targetState := filepath.Join(dir, target.String())
+	if err := os.Rename(initialState, targetState); err != nil {
+		if os.IsNotExist(err) {
+			return verror.New(errors.ErrInvalidOperation, nil, err)
+		}
+		return verror.New(errors.ErrOperationFailed, nil, fmt.Sprintf("Rename(%v, %v) failed: %v", initialState, targetState, err))
+	}
+	return nil
+}
+
+func initializeState(dir string, initial fmt.Stringer) error {
+	initialStatus := filepath.Join(dir, initial.String())
+	if err := ioutil.WriteFile(initialStatus, []byte("status"), 0600); err != nil {
+		return verror.New(errors.ErrOperationFailed, nil, fmt.Sprintf("WriteFile(%v) failed: %v", initialStatus, err))
+	}
+	return nil
+}
diff --git a/services/device/deviced/internal/impl/app_state_test.go b/services/device/deviced/internal/impl/app_state_test.go
new file mode 100644
index 0000000..614c647
--- /dev/null
+++ b/services/device/deviced/internal/impl/app_state_test.go
@@ -0,0 +1,97 @@
+// 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 impl
+
+import (
+	"io/ioutil"
+	"os"
+	"testing"
+
+	"v.io/v23/services/device"
+)
+
+// TestInstallationState verifies the state transition logic for app installations.
+func TestInstallationState(t *testing.T) {
+	dir, err := ioutil.TempDir("", "installation")
+	if err != nil {
+		t.Fatalf("Failed to create temp dir: %v", err)
+	}
+	defer os.RemoveAll(dir)
+	// Uninitialized state.
+	if transitionInstallation(dir, device.InstallationStateActive, device.InstallationStateUninstalled) == nil {
+		t.Fatalf("transitionInstallation should have failed")
+	}
+	if s, err := getInstallationState(dir); err == nil {
+		t.Fatalf("getInstallationState should have failed, got state %v instead", s)
+	}
+	if isActive, isUninstalled := installationStateIs(dir, device.InstallationStateActive), installationStateIs(dir, device.InstallationStateUninstalled); isActive || isUninstalled {
+		t.Fatalf("isActive, isUninstalled = %t, %t (expected false, false)", isActive, isUninstalled)
+	}
+	// Initialize.
+	if err := initializeInstallation(dir, device.InstallationStateActive); err != nil {
+		t.Fatalf("initializeInstallation failed: %v", err)
+	}
+	if !installationStateIs(dir, device.InstallationStateActive) {
+		t.Fatalf("Installation state expected to be %v", device.InstallationStateActive)
+	}
+	if s, err := getInstallationState(dir); s != device.InstallationStateActive || err != nil {
+		t.Fatalf("getInstallationState expected (%v, %v), got (%v, %v) instead", device.InstallationStateActive, nil, s, err)
+	}
+	if err := transitionInstallation(dir, device.InstallationStateActive, device.InstallationStateUninstalled); err != nil {
+		t.Fatalf("transitionInstallation failed: %v", err)
+	}
+	if !installationStateIs(dir, device.InstallationStateUninstalled) {
+		t.Fatalf("Installation state expected to be %v", device.InstallationStateUninstalled)
+	}
+	if s, err := getInstallationState(dir); s != device.InstallationStateUninstalled || err != nil {
+		t.Fatalf("getInstallationState expected (%v, %v), got (%v, %v) instead", device.InstallationStateUninstalled, nil, s, err)
+	}
+	// Invalid transition: wrong initial state.
+	if transitionInstallation(dir, device.InstallationStateActive, device.InstallationStateUninstalled) == nil {
+		t.Fatalf("transitionInstallation should have failed")
+	}
+	if !installationStateIs(dir, device.InstallationStateUninstalled) {
+		t.Fatalf("Installation state expected to be %v", device.InstallationStateUninstalled)
+	}
+}
+
+// TestInstanceState verifies the state transition logic for app instances.
+func TestInstanceState(t *testing.T) {
+	dir, err := ioutil.TempDir("", "instance")
+	if err != nil {
+		t.Fatalf("Failed to create temp dir: %v", err)
+	}
+	defer os.RemoveAll(dir)
+	// Uninitialized state.
+	if transitionInstance(dir, device.InstanceStateLaunching, device.InstanceStateRunning) == nil {
+		t.Fatalf("transitionInstance should have failed")
+	}
+	if s, err := getInstanceState(dir); err == nil {
+		t.Fatalf("getInstanceState should have failed, got state %v instead", s)
+	}
+	// Initialize.
+	if err := initializeInstance(dir, device.InstanceStateDying); err != nil {
+		t.Fatalf("initializeInstance failed: %v", err)
+	}
+	if s, err := getInstanceState(dir); s != device.InstanceStateDying || err != nil {
+		t.Fatalf("getInstanceState expected (%v, %v), got (%v, %v) instead", device.InstanceStateDying, nil, s, err)
+	}
+	if err := transitionInstance(dir, device.InstanceStateDying, device.InstanceStateNotRunning); err != nil {
+		t.Fatalf("transitionInstance failed: %v", err)
+	}
+	if s, err := getInstanceState(dir); s != device.InstanceStateNotRunning || err != nil {
+		t.Fatalf("getInstanceState expected (%v, %v), got (%v, %v) instead", device.InstanceStateNotRunning, nil, s, err)
+	}
+	// Invalid transition: wrong initial state.
+	if transitionInstance(dir, device.InstanceStateDying, device.InstanceStateNotRunning) == nil {
+		t.Fatalf("transitionInstance should have failed")
+	}
+	if err := transitionInstance(dir, device.InstanceStateNotRunning, device.InstanceStateDeleted); err != nil {
+		t.Fatalf("transitionInstance failed: %v", err)
+	}
+	if s, err := getInstanceState(dir); s != device.InstanceStateDeleted || err != nil {
+		t.Fatalf("getInstanceState expected (%v, %v), got (%v, %v) instead", device.InstanceStateDeleted, nil, s, err)
+	}
+}
diff --git a/services/device/deviced/internal/impl/applife/app_life_test.go b/services/device/deviced/internal/impl/applife/app_life_test.go
new file mode 100644
index 0000000..ec3c526
--- /dev/null
+++ b/services/device/deviced/internal/impl/applife/app_life_test.go
@@ -0,0 +1,667 @@
+// 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 applife_test
+
+import (
+	"crypto/md5"
+	"encoding/base64"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"regexp"
+	"strings"
+	"syscall"
+	"testing"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/security"
+	"v.io/v23/services/application"
+	"v.io/v23/services/device"
+	"v.io/x/ref"
+	"v.io/x/ref/lib/mgmt"
+	vsecurity "v.io/x/ref/lib/security"
+	"v.io/x/ref/services/device/deviced/internal/impl/utiltest"
+	"v.io/x/ref/services/device/deviced/internal/versioning"
+	"v.io/x/ref/services/device/internal/errors"
+	"v.io/x/ref/services/internal/servicetest"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/testutil"
+)
+
+func instanceDirForApp(root, appID, instanceID string) string {
+	applicationDirName := func(title string) string {
+		h := md5.New()
+		h.Write([]byte(title))
+		hash := strings.TrimRight(base64.URLEncoding.EncodeToString(h.Sum(nil)), "=")
+		return "app-" + hash
+	}
+	components := strings.Split(appID, "/")
+	appTitle, installationID := components[0], components[1]
+	return filepath.Join(root, applicationDirName(appTitle), "installation-"+installationID, "instances", "instance-"+instanceID)
+}
+
+func verifyAppWorkspace(t *testing.T, root, appID, instanceID string) {
+	// HACK ALERT: for now, we peek inside the device manager's directory
+	// structure (which ought to be opaque) to check for what the app has
+	// written to its local root.
+	//
+	// TODO(caprita): add support to device manager to browse logs/app local
+	// root.
+	rootDir := filepath.Join(instanceDirForApp(root, appID, instanceID), "root")
+	testFile := filepath.Join(rootDir, "testfile")
+	if read, err := ioutil.ReadFile(testFile); err != nil {
+		t.Fatalf("Failed to read %v: %v", testFile, err)
+	} else if want, got := "goodbye world", string(read); want != got {
+		t.Fatalf("Expected to read %v, got %v instead", want, got)
+	}
+	// END HACK
+}
+
+// TestLifeOfAnApp installs an app, instantiates, runs, kills, and deletes
+// several instances, and performs updates.
+func TestLifeOfAnApp(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	// Get app publisher context (used later to publish apps)
+	var pubCtx *context.T
+	var err error
+	if pubCtx, err = setupPublishingCredentials(ctx); err != nil {
+		t.Fatal(err)
+	}
+
+	sh, deferFn := servicetest.CreateShellAndMountTable(t, ctx, nil)
+	defer deferFn()
+
+	// Set up mock application and binary repositories.
+	envelope, cleanup := utiltest.StartMockRepos(t, ctx)
+	defer cleanup()
+
+	root, cleanup := servicetest.SetupRootDir(t, "devicemanager")
+	defer cleanup()
+	if err := versioning.SaveCreatorInfo(ctx, root); err != nil {
+		t.Fatal(err)
+	}
+
+	// Create a script wrapping the test target that implements suidhelper.
+	helperPath := utiltest.GenerateSuidHelperScript(t, root)
+
+	// Set up the device manager.  Since we won't do device manager updates,
+	// don't worry about its application envelope and current link.
+	dmh := servicetest.RunCommand(t, sh, nil, utiltest.DeviceManager, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
+	servicetest.ReadPID(t, dmh)
+	utiltest.ClaimDevice(t, ctx, "claimable", "dm", "mydevice", utiltest.NoPairingToken)
+
+	// Create the local server that the app uses to let us know it's ready.
+	pingCh, cleanup := utiltest.SetupPingServer(t, ctx)
+	defer cleanup()
+
+	utiltest.Resolve(t, ctx, "pingserver", 1, true)
+
+	// Create an envelope for a first version of the app.
+	e, err := utiltest.SignedEnvelopeFromShell(pubCtx, sh, []string{utiltest.TestEnvVarName + "=env-val-envelope"}, utiltest.App, "google naps", 0, 0, fmt.Sprintf("--%s=flag-val-envelope", utiltest.TestFlagName), "appV1")
+	if err != nil {
+		t.Fatalf("Unable to get signed envelope: %v", err)
+	}
+	*envelope = e
+
+	// Install the app.  The config-specified flag value for testFlagName
+	// should override the value specified in the envelope above, and the
+	// config-specified value for origin should override the value in the
+	// Install rpc argument.
+	mtName, ok := sh.GetVar(ref.EnvNamespacePrefix)
+	if !ok {
+		t.Fatalf("failed to get namespace root var from shell")
+	}
+	// This rooted name should be equivalent to the relative name "ar", but
+	// we want to test that the config override for origin works.
+	rootedAppRepoName := naming.Join(mtName, "ar")
+	appID := utiltest.InstallApp(t, ctx, device.Config{utiltest.TestFlagName: "flag-val-install", mgmt.AppOriginConfigKey: rootedAppRepoName})
+	v1 := utiltest.VerifyState(t, ctx, device.InstallationStateActive, appID)
+	installationDebug := utiltest.Debug(t, ctx, appID)
+	// We spot-check a couple pieces of information we expect in the debug
+	// output.
+	// TODO(caprita): Is there a way to verify more without adding brittle
+	// logic that assumes too much about the format?  This may be one
+	// argument in favor of making the output of Debug a struct instead of
+	// free-form string.
+	if !strings.Contains(installationDebug, fmt.Sprintf("Origin: %v", rootedAppRepoName)) {
+		t.Fatalf("debug response doesn't contain expected info: %v", installationDebug)
+	}
+	if !strings.Contains(installationDebug, "Config: map[random_test_flag:flag-val-install]") {
+		t.Fatalf("debug response doesn't contain expected info: %v", installationDebug)
+	}
+
+	// Start requires the caller to bless the app instance.
+	expectedErr := "bless failed"
+	if _, err := utiltest.LaunchAppImpl(t, ctx, appID, ""); err == nil || err.Error() != expectedErr {
+		t.Fatalf("Start(%v) expected to fail with %v, got %v instead", appID, expectedErr, err)
+	}
+
+	// Start an instance of the app.
+	instance1ID := utiltest.LaunchApp(t, ctx, appID)
+	if v := utiltest.VerifyState(t, ctx, device.InstanceStateRunning, appID, instance1ID); v != v1 {
+		t.Fatalf("Instance version expected to be %v, got %v instead", v1, v)
+	}
+
+	instanceDebug := utiltest.Debug(t, ctx, appID, instance1ID)
+
+	// Verify the app's default blessings.
+	if !strings.Contains(instanceDebug, fmt.Sprintf("Default Blessings                %s/forapp", v23.GetPrincipal(ctx).BlessingStore().Default().String())) {
+		t.Fatalf("debug response doesn't contain expected info: %v", instanceDebug)
+	}
+
+	// Verify the "..." blessing, which will include the publisher blessings
+	verifyAppPeerBlessings(t, ctx, pubCtx, instanceDebug, envelope)
+
+	// Wait until the app pings us that it's ready.
+	pingResult := pingCh.VerifyPingArgs(t, utiltest.UserName(t), "flag-val-install", "env-val-envelope")
+	v1EP1 := utiltest.Resolve(t, ctx, "appV1", 1, true)[0]
+
+	// Check that the instance name handed to the app looks plausible
+	nameRE := regexp.MustCompile(".*/apps/google naps/[^/]+/[^/]+$")
+	if nameRE.FindString(pingResult.InstanceName) == "" {
+		t.Fatalf("Unexpected instance name: %v", pingResult.InstanceName)
+	}
+
+	// There should be at least one publisher blessing prefix, and all prefixes should
+	// end in "/mydevice" because they are just the device manager's blessings
+	prefixes := strings.Split(pingResult.PubBlessingPrefixes, ",")
+	if len(prefixes) == 0 {
+		t.Fatalf("No publisher blessing prefixes found: %v", pingResult)
+	}
+	for _, p := range prefixes {
+		if !strings.HasSuffix(p, "/mydevice") {
+			t.Fatalf("publisher Blessing prefixes don't look right: %v", pingResult.PubBlessingPrefixes)
+		}
+	}
+
+	// We used a signed envelope, so there should have been some publisher blessings
+	if !hasPrefixMatches(pingResult.PubBlessingPrefixes, pingResult.DefaultPeerBlessings) {
+		t.Fatalf("Publisher Blessing Prefixes are not as expected: %v vs %v", pingResult.PubBlessingPrefixes, pingResult.DefaultPeerBlessings)
+	}
+
+	// Stop the app instance.
+	utiltest.KillApp(t, ctx, appID, instance1ID)
+	utiltest.VerifyState(t, ctx, device.InstanceStateNotRunning, appID, instance1ID)
+	utiltest.ResolveExpectNotFound(t, ctx, "appV1", true)
+
+	utiltest.RunApp(t, ctx, appID, instance1ID)
+	utiltest.VerifyState(t, ctx, device.InstanceStateRunning, appID, instance1ID)
+	pingCh.VerifyPingArgs(t, utiltest.UserName(t), "flag-val-install", "env-val-envelope") // Wait until the app pings us that it's ready.
+	oldV1EP1 := v1EP1
+	if v1EP1 = utiltest.Resolve(t, ctx, "appV1", 1, true)[0]; v1EP1 == oldV1EP1 {
+		t.Fatalf("Expected a new endpoint for the app after kill/run")
+	}
+
+	// Start a second instance.
+	instance2ID := utiltest.LaunchApp(t, ctx, appID)
+	pingCh.VerifyPingArgs(t, utiltest.UserName(t), "flag-val-install", "env-val-envelope") // Wait until the app pings us that it's ready.
+
+	// There should be two endpoints mounted as "appV1", one for each
+	// instance of the app.
+	endpoints := utiltest.Resolve(t, ctx, "appV1", 2, true)
+	v1EP2 := endpoints[0]
+	if endpoints[0] == v1EP1 {
+		v1EP2 = endpoints[1]
+		if v1EP2 == v1EP1 {
+			t.Fatalf("Both endpoints are the same")
+		}
+	} else if endpoints[1] != v1EP1 {
+		t.Fatalf("Second endpoint should have been v1EP1: %v, %v", endpoints, v1EP1)
+	}
+
+	// TODO(caprita): verify various non-standard combinations (kill when
+	// canceled; run while still running).
+
+	// Kill the first instance.
+	utiltest.KillApp(t, ctx, appID, instance1ID)
+	// Only the second instance should still be running and mounted.
+	// In this case, we don't want to retry since we shouldn't need to.
+	if want, got := v1EP2, utiltest.Resolve(t, ctx, "appV1", 1, false)[0]; want != got {
+		t.Fatalf("Resolve(%v): want: %v, got %v", "appV1", want, got)
+	}
+
+	// Updating the installation to itself is a no-op.
+	utiltest.UpdateAppExpectError(t, ctx, appID, errors.ErrUpdateNoOp.ID)
+
+	// Updating the installation should not work with a mismatched title.
+	*envelope = utiltest.EnvelopeFromShell(sh, nil, utiltest.App, "bogus", 0, 0)
+
+	utiltest.UpdateAppExpectError(t, ctx, appID, errors.ErrAppTitleMismatch.ID)
+
+	// Create a second version of the app and update the app to it.
+	*envelope = utiltest.EnvelopeFromShell(sh, []string{utiltest.TestEnvVarName + "=env-val-envelope"}, utiltest.App, "google naps", 0, 0, "appV2")
+
+	utiltest.UpdateApp(t, ctx, appID)
+
+	v2 := utiltest.VerifyState(t, ctx, device.InstallationStateActive, appID)
+	if v1 == v2 {
+		t.Fatalf("Version did not change for %v: %v", appID, v1)
+	}
+
+	// Second instance should still be running, don't retry.
+	if want, got := v1EP2, utiltest.Resolve(t, ctx, "appV1", 1, false)[0]; want != got {
+		t.Fatalf("Resolve(%v): want: %v, got %v", "appV1", want, got)
+	}
+	if v := utiltest.VerifyState(t, ctx, device.InstanceStateRunning, appID, instance2ID); v != v1 {
+		t.Fatalf("Instance version expected to be %v, got %v instead", v1, v)
+	}
+
+	// Resume first instance.
+	utiltest.RunApp(t, ctx, appID, instance1ID)
+	if v := utiltest.VerifyState(t, ctx, device.InstanceStateRunning, appID, instance1ID); v != v1 {
+		t.Fatalf("Instance version expected to be %v, got %v instead", v1, v)
+	}
+	pingCh.VerifyPingArgs(t, utiltest.UserName(t), "flag-val-install", "env-val-envelope") // Wait until the app pings us that it's ready.
+	// Both instances should still be running the first version of the app.
+	// Check that the mounttable contains two endpoints, one of which is
+	// v1EP2.
+	endpoints = utiltest.Resolve(t, ctx, "appV1", 2, true)
+	if endpoints[0] == v1EP2 {
+		if endpoints[1] == v1EP2 {
+			t.Fatalf("Both endpoints are the same")
+		}
+	} else if endpoints[1] != v1EP2 {
+		t.Fatalf("Second endpoint should have been v1EP2: %v, %v", endpoints, v1EP2)
+	}
+
+	// Trying to update first instance while it's running should fail.
+	utiltest.UpdateInstanceExpectError(t, ctx, appID, instance1ID, errors.ErrInvalidOperation.ID)
+	// Stop first instance and try again.
+	utiltest.KillApp(t, ctx, appID, instance1ID)
+	// Only the second instance should still be running and mounted, don't retry.
+	if want, got := v1EP2, utiltest.Resolve(t, ctx, "appV1", 1, false)[0]; want != got {
+		t.Fatalf("Resolve(%v): want: %v, got %v", "appV1", want, got)
+	}
+	// Update succeeds now.
+	utiltest.UpdateInstance(t, ctx, appID, instance1ID)
+	if v := utiltest.VerifyState(t, ctx, device.InstanceStateNotRunning, appID, instance1ID); v != v2 {
+		t.Fatalf("Instance version expected to be %v, got %v instead", v2, v)
+	}
+	// Resume the first instance and verify it's running v2 now.
+	utiltest.RunApp(t, ctx, appID, instance1ID)
+	pingResult = pingCh.VerifyPingArgs(t, utiltest.UserName(t), "flag-val-install", "env-val-envelope")
+	utiltest.Resolve(t, ctx, "appV1", 1, false)
+	utiltest.Resolve(t, ctx, "appV2", 1, false)
+
+	// Although v2 does not have a signed envelope, this was an update of v1, which did.
+	// This app's config still includes publisher blessing prefixes, and it should still
+	// have the publisher blessing it acquired earlier.
+	//
+	// TODO: This behavior is non-ideal. A reasonable requirement in future would be that
+	// the publisher blessing string remain unchanged on updates to an installation, just as the
+	// title is not allowed to change.
+	if !hasPrefixMatches(pingResult.PubBlessingPrefixes, pingResult.DefaultPeerBlessings) {
+		t.Fatalf("Publisher Blessing Prefixes are not as expected: %v vs %v", pingResult.PubBlessingPrefixes, pingResult.DefaultPeerBlessings)
+	}
+
+	// Reverting first instance fails since it's still running.
+	utiltest.RevertAppExpectError(t, ctx, appID+"/"+instance1ID, errors.ErrInvalidOperation.ID)
+	// Stop first instance and try again.
+	utiltest.KillApp(t, ctx, appID, instance1ID)
+	verifyAppWorkspace(t, root, appID, instance1ID)
+	utiltest.ResolveExpectNotFound(t, ctx, "appV2", true)
+	utiltest.RevertApp(t, ctx, appID+"/"+instance1ID)
+	// Resume the first instance and verify it's running v1 now.
+	utiltest.RunApp(t, ctx, appID, instance1ID)
+	pingCh.VerifyPingArgs(t, utiltest.UserName(t), "flag-val-install", "env-val-envelope")
+	utiltest.Resolve(t, ctx, "appV1", 2, false)
+	utiltest.TerminateApp(t, ctx, appID, instance1ID)
+	utiltest.Resolve(t, ctx, "appV1", 1, false)
+
+	// Start a third instance.
+	instance3ID := utiltest.LaunchApp(t, ctx, appID)
+	if v := utiltest.VerifyState(t, ctx, device.InstanceStateRunning, appID, instance3ID); v != v2 {
+		t.Fatalf("Instance version expected to be %v, got %v instead", v2, v)
+	}
+	// Wait until the app pings us that it's ready.
+	pingResult = pingCh.VerifyPingArgs(t, utiltest.UserName(t), "flag-val-install", "env-val-envelope")
+	// This app should not have publisher blessings. It was started from an installation
+	// that did not have a signed envelope.
+	if hasPrefixMatches(pingResult.PubBlessingPrefixes, pingResult.DefaultPeerBlessings) {
+		t.Fatalf("Publisher Blessing Prefixes are not as expected: %v vs %v", pingResult.PubBlessingPrefixes, pingResult.DefaultPeerBlessings)
+	}
+
+	utiltest.Resolve(t, ctx, "appV2", 1, true)
+
+	// Suspend second instance.
+	utiltest.KillApp(t, ctx, appID, instance2ID)
+	utiltest.ResolveExpectNotFound(t, ctx, "appV1", true)
+
+	// Reverting second instance is a no-op since it's already running v1.
+	utiltest.RevertAppExpectError(t, ctx, appID+"/"+instance2ID, errors.ErrUpdateNoOp.ID)
+
+	// Stop third instance.
+	utiltest.TerminateApp(t, ctx, appID, instance3ID)
+	utiltest.ResolveExpectNotFound(t, ctx, "appV2", true)
+
+	// Revert the app.
+	utiltest.RevertApp(t, ctx, appID)
+	if v := utiltest.VerifyState(t, ctx, device.InstallationStateActive, appID); v != v1 {
+		t.Fatalf("Installation version expected to be %v, got %v instead", v1, v)
+	}
+
+	// Start a fourth instance.  It should be running from version 1.
+	instance4ID := utiltest.LaunchApp(t, ctx, appID)
+	if v := utiltest.VerifyState(t, ctx, device.InstanceStateRunning, appID, instance4ID); v != v1 {
+		t.Fatalf("Instance version expected to be %v, got %v instead", v1, v)
+	}
+	pingCh.VerifyPingArgs(t, utiltest.UserName(t), "flag-val-install", "env-val-envelope") // Wait until the app pings us that it's ready.
+	utiltest.Resolve(t, ctx, "appV1", 1, true)
+	utiltest.TerminateApp(t, ctx, appID, instance4ID)
+	utiltest.ResolveExpectNotFound(t, ctx, "appV1", true)
+
+	// We are already on the first version, no further revert possible.
+	utiltest.RevertAppExpectError(t, ctx, appID, errors.ErrUpdateNoOp.ID)
+
+	// Uninstall the app.
+	utiltest.UninstallApp(t, ctx, appID)
+	utiltest.VerifyState(t, ctx, device.InstallationStateUninstalled, appID)
+
+	// Updating the installation should no longer be allowed.
+	utiltest.UpdateAppExpectError(t, ctx, appID, errors.ErrInvalidOperation.ID)
+
+	// Reverting the installation should no longer be allowed.
+	utiltest.RevertAppExpectError(t, ctx, appID, errors.ErrInvalidOperation.ID)
+
+	// Starting new instances should no longer be allowed.
+	utiltest.LaunchAppExpectError(t, ctx, appID, errors.ErrInvalidOperation.ID)
+
+	// Make sure that Kill will actually kill an app that doesn't exit
+	// cleanly Do this by installing, instantiating, running, and killing
+	// hangingApp, which sleeps (rather than exits) after being asked to
+	// Stop()
+	*envelope = utiltest.EnvelopeFromShell(sh, nil, utiltest.HangingApp, "hanging ap", 0, 0, "hAppV1")
+	hAppID := utiltest.InstallApp(t, ctx)
+	hInstanceID := utiltest.LaunchApp(t, ctx, hAppID)
+	hangingPid := pingCh.WaitForPingArgs(t).Pid
+	if err := syscall.Kill(hangingPid, 0); err != nil && err != syscall.EPERM {
+		t.Fatalf("Pid of hanging app (%v) is not live", hangingPid)
+	}
+	utiltest.KillApp(t, ctx, hAppID, hInstanceID)
+	pidIsAlive := true
+	for i := 0; i < 10 && pidIsAlive; i++ {
+		if err := syscall.Kill(hangingPid, 0); err == nil || err == syscall.EPERM {
+			time.Sleep(time.Second) // pid is still alive
+		} else {
+			pidIsAlive = false
+		}
+	}
+	if pidIsAlive {
+		t.Fatalf("Pid of hanging app (%d) has not exited after Stop() call", hangingPid)
+	}
+
+	// In the first pass, TidyNow (below), finds that everything should be too
+	// young to be tidied becasue TidyNow's first call to MockableNow()
+	// provides the current time.
+	shouldKeepInstances := keepAll(t, root, filepath.Join(root, "app*", "installation*", "instances", "instance*"))
+	shouldKeepInstallations := keepAll(t, root, filepath.Join(root, "app*", "installation*"))
+	shouldKeepLogFiles := keepAll(t, root, filepath.Join(root, "app*", "installation*", "instances", "instance*", "logs", "*"))
+
+	if err := utiltest.DeviceStub("dm").TidyNow(ctx); err != nil {
+		t.Fatalf("TidyNow failed: %v", err)
+	}
+
+	verifyTidying(t, root, filepath.Join(root, "app*", "installation*", "instances", "instance*"), shouldKeepInstances)
+	verifyTidying(t, root, filepath.Join(root, "app*", "installation*"), shouldKeepInstallations)
+	verifyTidying(t, root, filepath.Join(root, "app*", "installation*", "instances", "instance*", "logs", "*"), shouldKeepLogFiles)
+
+	// In the second pass, TidyNow() (below) calls MockableNow() again
+	// which has advanced to tomorrow so it should find that all items have
+	// become old enough to tidy.
+	shouldKeepInstances = determineShouldKeep(t, root, filepath.Join(root, "app*", "installation*", "instances", "instance*"), "Deleted")
+	shouldKeepInstallations = addBackLinks(t, root, determineShouldKeep(t, root, filepath.Join(root, "app*", "installation*"), "Uninstalled"))
+	shouldKeepLogFiles = determineLogFilesToKeep(t, shouldKeepInstances)
+
+	if err := utiltest.DeviceStub("dm").TidyNow(ctx); err != nil {
+		t.Fatalf("TidyNow failed: %v", err)
+	}
+
+	verifyTidying(t, root, filepath.Join(root, "app*", "installation*", "instances", "instance*"), shouldKeepInstances)
+	verifyTidying(t, root, filepath.Join(root, "app*", "installation*"), shouldKeepInstallations)
+	verifyTidying(t, root, filepath.Join(root, "app*", "installation*", "instances", "instance*", "logs", "*"), shouldKeepLogFiles)
+
+	// Cleanly shut down the device manager.
+	defer utiltest.VerifyNoRunningProcesses(t)
+	syscall.Kill(dmh.Pid(), syscall.SIGINT)
+	dmh.Expect("dm terminated")
+	dmh.ExpectEOF()
+}
+
+func keepAll(t *testing.T, root, globpath string) map[string]bool {
+	paths, err := filepath.Glob(globpath)
+	if err != nil {
+		t.Errorf("keepAll %v", err)
+	}
+	shouldKeep := make(map[string]bool)
+	for _, idir := range paths {
+		shouldKeep[idir] = true
+	}
+	return shouldKeep
+}
+
+func determineShouldKeep(t *testing.T, root, globpath, state string) map[string]bool {
+	paths, err := filepath.Glob(globpath)
+	if err != nil {
+		t.Errorf("determineShouldKeep %v", err)
+	}
+
+	shouldKeep := make(map[string]bool)
+	for _, idir := range paths {
+		p := filepath.Join(idir, state)
+		_, err := os.Stat(p)
+		if os.IsNotExist(err) {
+			shouldKeep[idir] = true
+		} else if err == nil {
+			shouldKeep[idir] = false
+		} else {
+			t.Errorf("determineShouldKeep Stat(%s) failed: %v", p, err)
+		}
+	}
+	return shouldKeep
+
+}
+
+func addBackLinks(t *testing.T, root string, installationShouldKeep map[string]bool) map[string]bool {
+	paths, err := filepath.Glob(filepath.Join(root, "app*", "installation*", "instances", "instance*", "installation"))
+	if err != nil {
+		t.Errorf("addBackLinks %v", err)
+	}
+
+	for _, idir := range paths {
+		pth, err := os.Readlink(idir)
+		if err != nil {
+			t.Errorf("addBackLinks %v", err)
+			continue
+		}
+		if _, ok := installationShouldKeep[pth]; ok {
+			// An instance symlinks to this pth so must be kept.
+			installationShouldKeep[pth] = true
+		}
+	}
+	return installationShouldKeep
+}
+
+// determineLogFilesToKeep produces a map of the log files that
+// should remain after tidying. It returns a map to be compatible
+// with the verifyTidying.
+func determineLogFilesToKeep(t *testing.T, instances map[string]bool) map[string]bool {
+	shouldKeep := make(map[string]bool)
+	for idir, keep := range instances {
+		if !keep {
+			continue
+		}
+
+		paths, err := filepath.Glob(filepath.Join(idir, "logs", "*"))
+		if err != nil {
+			t.Errorf("determineLogFilesToKeep filepath.Glob(%s) failed: %v", idir, err)
+			return shouldKeep
+		}
+
+		for _, p := range paths {
+			fi, err := os.Stat(p)
+			if err != nil {
+				t.Errorf("determineLogFilesToKeep os.Stat(%s): %v", p, err)
+				return shouldKeep
+			}
+
+			if fi.Mode()&os.ModeSymlink == 0 {
+				continue
+			}
+
+			shouldKeep[p] = true
+			target, err := os.Readlink(p)
+			if err != nil {
+				t.Errorf("determineLogFilesToKeep os.Readlink(%s): %v", p, err)
+				return shouldKeep
+			}
+			shouldKeep[target] = true
+		}
+	}
+	return shouldKeep
+}
+
+func verifyTidying(t *testing.T, root, globpath string, shouldKeep map[string]bool) {
+	paths, err := filepath.Glob(globpath)
+	if err != nil {
+		t.Errorf("verifyTidying %v", err)
+	}
+
+	// TidyUp adds nothing: pth should be a subset of shouldKeep.
+	for _, pth := range paths {
+		if !shouldKeep[pth] {
+			t.Errorf("TidyUp (%s) wrongly added path: %s", globpath, pth)
+			return
+		}
+	}
+
+	// Tidy should not leave unkept instances: shouldKeep ^ pth should be entirely true.
+	for _, pth := range paths {
+		if !shouldKeep[pth] {
+			t.Errorf("TidyUp (%s) failed to delete: %s", globpath, pth)
+			return
+		}
+	}
+
+	// Tidy must not delete any kept instances.
+	for k, v := range shouldKeep {
+		if v {
+			if _, err := os.Stat(k); os.IsNotExist(err) {
+				t.Errorf("TidyUp (%s) deleted an instance it shouldn't have: %s", globpath, k)
+			}
+		}
+	}
+}
+
+// setupPublishingCredentials creates two principals, which, in addition to the one passed in
+// (which is "the user") allow us to have an "identity provider", and a "publisher". The
+// user and the publisher are both blessed by the identity provider. The return value is
+// a context that can be used to publish an envelope with a signed binary.
+func setupPublishingCredentials(ctx *context.T) (*context.T, error) {
+	IDPPrincipal := testutil.NewPrincipal("identitypro")
+	IDPBlessing := IDPPrincipal.BlessingStore().Default()
+
+	PubPrincipal := testutil.NewPrincipal()
+	UserPrincipal := v23.GetPrincipal(ctx)
+
+	var b security.Blessings
+	var c security.Caveat
+	var err error
+	if c, err = security.NewExpiryCaveat(time.Now().Add(time.Hour * 24 * 30)); err != nil {
+		return nil, err
+	}
+	if b, err = IDPPrincipal.Bless(UserPrincipal.PublicKey(), IDPBlessing, "u/alice", c); err != nil {
+		return nil, err
+	}
+	if err := vsecurity.SetDefaultBlessings(UserPrincipal, b); err != nil {
+		return nil, err
+	}
+
+	if b, err = IDPPrincipal.Bless(PubPrincipal.PublicKey(), IDPBlessing, "m/publisher", security.UnconstrainedUse()); err != nil {
+		return nil, err
+	}
+	if err := vsecurity.SetDefaultBlessings(PubPrincipal, b); err != nil {
+		return nil, err
+	}
+
+	var pubCtx *context.T
+	if pubCtx, err = v23.WithPrincipal(ctx, PubPrincipal); err != nil {
+		return nil, err
+	}
+
+	return pubCtx, nil
+}
+
+// findPrefixMatches takes a set of comma-separated prefixes, and a set of comma-separated
+// strings, and checks if any of the strings match any of the prefixes
+func hasPrefixMatches(prefixList, stringList string) bool {
+	prefixes := strings.Split(prefixList, ",")
+	inStrings := strings.Split(stringList, ",")
+
+	for _, s := range inStrings {
+		for _, p := range prefixes {
+			if strings.HasPrefix(s, p) {
+				return true
+			}
+		}
+	}
+	return false
+}
+
+// verifyAppPeerBlessings checks the instanceDebug string to ensure that the app is running with
+// the expected blessings for peer "..." (i.e. security.AllPrincipals) .
+//
+// The app should have one blessing that came from the user, of the form
+// <base_blessing>/forapp. It should also have one or more publisher blessings, that are the
+// cross product of the device manager blessings and the publisher blessings in the app
+// envelope.
+func verifyAppPeerBlessings(t *testing.T, ctx, pubCtx *context.T, instanceDebug string, e *application.Envelope) {
+	// Extract the blessings from the debug output
+	//
+	// TODO(caprita): This is flaky, since the '...' peer pattern may not be
+	// the first one in the sorted pattern list.  See v.io/i/680
+	blessingRE := regexp.MustCompile(`Blessings\s?\n\s?\.\.\.\s*([^\n]+)`)
+	blessingMatches := blessingRE.FindStringSubmatch(instanceDebug)
+	if len(blessingMatches) < 2 {
+		t.Fatalf("Failed to match blessing regex: [%v] [%v]", blessingMatches, instanceDebug)
+	}
+	blessingList := strings.Split(blessingMatches[1], ",")
+
+	// Compute a map of the blessings we expect to find
+	expBlessings := make(map[string]bool)
+	baseBlessing := v23.GetPrincipal(ctx).BlessingStore().Default().String()
+	expBlessings[baseBlessing+"/forapp"] = false
+
+	// App blessings should be the cross product of device manager and publisher blessings
+
+	// dmBlessings below is a slice even though we have just one entry because we'll likely
+	// want it to have more than one in future. (Today, a device manager typically has a
+	// blessing from its claimer, but in many cases there might be other blessings too, such
+	// as one from the manufacturer, or one from the organization that owns the device.)
+	dmBlessings := []string{baseBlessing + "/mydevice"}
+	pubBlessings := strings.Split(e.Publisher.String(), ",")
+	for _, dmb := range dmBlessings {
+		for _, pb := range pubBlessings {
+			expBlessings[dmb+"/a/"+pb] = false
+		}
+	}
+
+	// Check the list of blessings against the map of expected blessings
+	matched := 0
+	for _, b := range blessingList {
+		if seen, ok := expBlessings[b]; ok && !seen {
+			expBlessings[b] = true
+			matched++
+		}
+	}
+	if matched != len(expBlessings) {
+		t.Fatalf("Missing some blessings in set %v. App blessings were: %v", expBlessings, blessingList)
+	}
+}
diff --git a/services/device/deviced/internal/impl/applife/doc.go b/services/device/deviced/internal/impl/applife/doc.go
new file mode 100644
index 0000000..e1b872a
--- /dev/null
+++ b/services/device/deviced/internal/impl/applife/doc.go
@@ -0,0 +1,7 @@
+// 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 applife
+
+// Test code for application life cycle and reap reconcilliation.
diff --git a/services/device/deviced/internal/impl/applife/impl_test.go b/services/device/deviced/internal/impl/applife/impl_test.go
new file mode 100644
index 0000000..0808c65
--- /dev/null
+++ b/services/device/deviced/internal/impl/applife/impl_test.go
@@ -0,0 +1,19 @@
+// 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 applife_test
+
+import (
+	"testing"
+
+	"v.io/x/ref/services/device/deviced/internal/impl/utiltest"
+)
+
+func TestMain(m *testing.M) {
+	utiltest.TestMainImpl(m)
+}
+
+func TestSuidHelper(t *testing.T) {
+	utiltest.TestSuidHelperImpl(t)
+}
diff --git a/services/device/deviced/internal/impl/applife/instance_reaping_test.go b/services/device/deviced/internal/impl/applife/instance_reaping_test.go
new file mode 100644
index 0000000..86d4c73
--- /dev/null
+++ b/services/device/deviced/internal/impl/applife/instance_reaping_test.go
@@ -0,0 +1,80 @@
+// 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 applife_test
+
+import (
+	"syscall"
+	"testing"
+
+	"v.io/v23/naming"
+	"v.io/v23/services/device"
+	"v.io/v23/services/stats"
+	"v.io/v23/vdl"
+
+	"v.io/x/ref/services/device/deviced/internal/impl/utiltest"
+	"v.io/x/ref/services/internal/servicetest"
+)
+
+func TestReaperNoticesAppDeath(t *testing.T) {
+	cleanup, ctx, sh, envelope, root, helperPath, _ := utiltest.StartupHelper(t)
+	defer cleanup()
+
+	// Set up the device manager.  Since we won't do device manager updates,
+	// don't worry about its application envelope and current link.
+	dmh := servicetest.RunCommand(t, sh, nil, utiltest.DeviceManager, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
+	servicetest.ReadPID(t, dmh)
+	utiltest.ClaimDevice(t, ctx, "claimable", "dm", "mydevice", utiltest.NoPairingToken)
+
+	// Create the local server that the app uses to let us know it's ready.
+	pingCh, cleanup := utiltest.SetupPingServer(t, ctx)
+	defer cleanup()
+
+	utiltest.Resolve(t, ctx, "pingserver", 1, true)
+
+	// Create an envelope for a first version of the app.
+	*envelope = utiltest.EnvelopeFromShell(sh, nil, utiltest.App, "google naps", 0, 0, "appV1")
+
+	// Install the app.  The config-specified flag value for testFlagName
+	// should override the value specified in the envelope above.
+	appID := utiltest.InstallApp(t, ctx)
+
+	// Start an instance of the app.
+	instance1ID := utiltest.LaunchApp(t, ctx, appID)
+
+	// Wait until the app pings us that it's ready.
+	pingCh.VerifyPingArgs(t, utiltest.UserName(t), "default", "")
+
+	// Get application pid.
+	name := naming.Join("dm", "apps/"+appID+"/"+instance1ID+"/stats/system/pid")
+	c := stats.StatsClient(name)
+	v, err := c.Value(ctx)
+	if err != nil {
+		t.Fatalf("Value() failed: %v\n", err)
+	}
+	var pid int
+	if err := vdl.Convert(&pid, v); err != nil {
+		t.Fatalf("pid returned from stats interface is not an int: %v", err)
+	}
+
+	utiltest.VerifyState(t, ctx, device.InstanceStateRunning, appID, instance1ID)
+	syscall.Kill(int(pid), 9)
+
+	// Start a second instance of the app which will force polling to happen.
+	instance2ID := utiltest.LaunchApp(t, ctx, appID)
+	pingCh.VerifyPingArgs(t, utiltest.UserName(t), "default", "")
+
+	utiltest.VerifyState(t, ctx, device.InstanceStateRunning, appID, instance2ID)
+
+	utiltest.TerminateApp(t, ctx, appID, instance2ID)
+	utiltest.VerifyState(t, ctx, device.InstanceStateNotRunning, appID, instance1ID)
+
+	// TODO(rjkroege): Exercise the polling loop code.
+
+	// Cleanly shut down the device manager.
+	utiltest.VerifyNoRunningProcesses(t)
+	syscall.Kill(dmh.Pid(), syscall.SIGINT)
+	dmh.Expect("dm terminated")
+	dmh.ExpectEOF()
+}
diff --git a/services/device/deviced/internal/impl/associate_instance_test.go b/services/device/deviced/internal/impl/associate_instance_test.go
new file mode 100644
index 0000000..3078cb5
--- /dev/null
+++ b/services/device/deviced/internal/impl/associate_instance_test.go
@@ -0,0 +1,32 @@
+// 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 impl
+
+import (
+	"io/ioutil"
+	"os"
+	"testing"
+)
+
+func TestSystemNameState(t *testing.T) {
+	dir, err := ioutil.TempDir("", "instance")
+	if err != nil {
+		t.Fatalf("Failed to create temp dir: %v", err)
+	}
+	defer os.RemoveAll(dir)
+
+	expected := "vanadium-user"
+	if err := saveSystemNameForInstance(dir, expected); err != nil {
+		t.Fatalf("saveSystemNameForInstance(%v, %v) failed: %v", dir, expected, err)
+	}
+
+	got, err := readSystemNameForInstance(dir)
+	if err != nil {
+		t.Fatalf("readSystemNameForInstance(%v) failed: ", err)
+	}
+	if got != expected {
+		t.Fatalf("got %v, expected %v", got, expected)
+	}
+}
diff --git a/services/device/deviced/internal/impl/association_instance.go b/services/device/deviced/internal/impl/association_instance.go
new file mode 100644
index 0000000..0f15505
--- /dev/null
+++ b/services/device/deviced/internal/impl/association_instance.go
@@ -0,0 +1,34 @@
+// 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 impl
+
+// Code to manage the persistence of which systemName is associated with
+// a given application instance.
+
+import (
+	"fmt"
+	"io/ioutil"
+	"path/filepath"
+
+	"v.io/v23/verror"
+	"v.io/x/ref/services/device/internal/errors"
+)
+
+func saveSystemNameForInstance(dir, systemName string) error {
+	snp := filepath.Join(dir, "systemname")
+	if err := ioutil.WriteFile(snp, []byte(systemName), 0600); err != nil {
+		return verror.New(errors.ErrOperationFailed, nil, fmt.Sprintf("WriteFile(%v, %v) failed: %v", snp, systemName, err))
+	}
+	return nil
+}
+
+func readSystemNameForInstance(dir string) (string, error) {
+	snp := filepath.Join(dir, "systemname")
+	name, err := ioutil.ReadFile(snp)
+	if err != nil {
+		return "", verror.New(errors.ErrOperationFailed, nil, fmt.Sprintf("ReadFile(%v) failed: %v", snp, err))
+	}
+	return string(name), nil
+}
diff --git a/services/device/deviced/internal/impl/association_state.go b/services/device/deviced/internal/impl/association_state.go
new file mode 100644
index 0000000..2f25912
--- /dev/null
+++ b/services/device/deviced/internal/impl/association_state.go
@@ -0,0 +1,128 @@
+// 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 impl
+
+import (
+	"encoding/json"
+	"os"
+	"path/filepath"
+	"sync"
+
+	"v.io/v23/services/device"
+	"v.io/v23/verror"
+)
+
+// BlessingSystemAssociationStore manages a persisted association between
+// Vanadium blessings and system account names.
+type BlessingSystemAssociationStore interface {
+	// SystemAccountForBlessings returns a system name from the blessing to
+	// system name association store if one exists for any of the listed
+	// blessings.
+	SystemAccountForBlessings(blessings []string) (string, bool)
+
+	// AllBlessingSystemAssociations returns all of the current Blessing to system
+	// account associations.
+	AllBlessingSystemAssociations() ([]device.Association, error)
+
+	// AssociateSystemAccountForBlessings associates the provided systenName with each
+	// provided blessing.
+	AssociateSystemAccountForBlessings(blessings []string, systemName string) error
+
+	// DisassociateSystemAccountForBlessings removes associations for the provided blessings.
+	DisassociateSystemAccountForBlessings(blessings []string) error
+}
+
+type association struct {
+	data     map[string]string
+	filename string
+	sync.Mutex
+}
+
+func (u *association) SystemAccountForBlessings(blessings []string) (string, bool) {
+	u.Lock()
+	defer u.Unlock()
+
+	systemName := ""
+	present := false
+
+	for _, n := range blessings {
+		if systemName, present = u.data[n]; present {
+			break
+		}
+	}
+	return systemName, present
+}
+
+func (u *association) AllBlessingSystemAssociations() ([]device.Association, error) {
+	u.Lock()
+	defer u.Unlock()
+	assocs := make([]device.Association, 0)
+
+	for k, v := range u.data {
+		assocs = append(assocs, device.Association{k, v})
+	}
+	return assocs, nil
+}
+
+func (u *association) serialize() (err error) {
+	f, err := os.OpenFile(u.filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
+	if err != nil {
+		return verror.New(verror.ErrNoExist, nil, "Could not open association file for writing", u.filename, err)
+	}
+	defer func() {
+		if closerr := f.Close(); closerr != nil {
+			err = closerr
+		}
+	}()
+
+	enc := json.NewEncoder(f)
+	return enc.Encode(u.data)
+}
+
+func (u *association) AssociateSystemAccountForBlessings(blessings []string, systemName string) error {
+	u.Lock()
+	defer u.Unlock()
+
+	for _, n := range blessings {
+		u.data[n] = systemName
+	}
+	return u.serialize()
+}
+
+func (u *association) DisassociateSystemAccountForBlessings(blessings []string) error {
+	u.Lock()
+	defer u.Unlock()
+
+	for _, n := range blessings {
+		delete(u.data, n)
+	}
+	return u.serialize()
+}
+
+func NewBlessingSystemAssociationStore(root string) (BlessingSystemAssociationStore, error) {
+	nddir := filepath.Join(root, "device-manager", "device-data")
+	if err := os.MkdirAll(nddir, os.FileMode(0700)); err != nil {
+		return nil, verror.New(verror.ErrNoExist, nil, "Could not create device-data directory", nddir, err)
+	}
+	msf := filepath.Join(nddir, "associated.accounts")
+
+	f, err := os.Open(msf)
+	if err != nil && os.IsExist(err) {
+		return nil, verror.New(verror.ErrNoExist, nil, "Could not open association file", msf, err)
+
+	}
+	defer f.Close()
+
+	a := &association{filename: msf, data: make(map[string]string)}
+
+	if err == nil {
+		dec := json.NewDecoder(f)
+		err := dec.Decode(&a.data)
+		if err != nil {
+			return nil, verror.New(verror.ErrNoExist, nil, "Could not read association file", msf, err)
+		}
+	}
+	return BlessingSystemAssociationStore(a), nil
+}
diff --git a/services/device/deviced/internal/impl/association_state_test.go b/services/device/deviced/internal/impl/association_state_test.go
new file mode 100644
index 0000000..0e586c5
--- /dev/null
+++ b/services/device/deviced/internal/impl/association_state_test.go
@@ -0,0 +1,170 @@
+// 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 impl_test
+
+import (
+	"io"
+	"io/ioutil"
+	"os"
+	"path"
+	"testing"
+
+	"v.io/v23/services/device"
+
+	"v.io/x/ref/services/device/deviced/internal/impl"
+	"v.io/x/ref/services/device/deviced/internal/impl/utiltest"
+)
+
+// TestAssociationPersistance verifies correct operation of association
+// persistance code.
+func TestAssociationPersistance(t *testing.T) {
+	td, err := ioutil.TempDir("", "device_test")
+	if err != nil {
+		t.Fatalf("TempDir failed: %v", err)
+	}
+	defer os.RemoveAll(td)
+	nbsa1, err := impl.NewBlessingSystemAssociationStore(td)
+	if err != nil {
+		t.Fatalf("NewBlessingSystemAssociationStore failed: %v", err)
+	}
+
+	// Insert an association.
+	err = nbsa1.AssociateSystemAccountForBlessings([]string{"alice", "bob"}, "alice_account")
+	if err != nil {
+		t.Fatalf("AssociateSystemAccount failed: %v", err)
+	}
+
+	got1, err := nbsa1.AllBlessingSystemAssociations()
+	if err != nil {
+		t.Fatalf("AllBlessingSystemAssociations failed: %v", err)
+	}
+
+	utiltest.CompareAssociations(t, got1, []device.Association{
+		{
+			"alice",
+			"alice_account",
+		},
+		{
+			"bob",
+			"alice_account",
+		},
+	})
+
+	nbsa2, err := impl.NewBlessingSystemAssociationStore(td)
+	if err != nil {
+		t.Fatalf("NewBlessingSystemAssociationStore failed: %v", err)
+	}
+
+	got2, err := nbsa2.AllBlessingSystemAssociations()
+	if err != nil {
+		t.Fatalf("AllBlessingSystemAssociations failed: %v", err)
+	}
+	utiltest.CompareAssociations(t, got1, got2)
+
+	sysacc, have := nbsa2.SystemAccountForBlessings([]string{"bob"})
+	if expected := true; have != expected {
+		t.Fatalf("SystemAccountForBlessings failed. got %v, expected %v", have, expected)
+	}
+	if expected := "alice_account"; sysacc != expected {
+		t.Fatalf("SystemAccountForBlessings failed. got %v, expected %v", sysacc, expected)
+	}
+
+	sysacc, have = nbsa2.SystemAccountForBlessings([]string{"doug"})
+	if expected := false; have != expected {
+		t.Fatalf("SystemAccountForBlessings failed. got %v, expected %v", have, expected)
+	}
+	if expected := ""; sysacc != expected {
+		t.Fatalf("SystemAccountForBlessings failed. got %v, expected %v", sysacc, expected)
+	}
+
+	// Remove "bob".
+	err = nbsa1.DisassociateSystemAccountForBlessings([]string{"bob"})
+	if err != nil {
+		t.Fatalf("DisassociateSystemAccountForBlessings failed: %v", err)
+	}
+
+	// Verify that "bob" has been removed.
+	got1, err = nbsa1.AllBlessingSystemAssociations()
+	if err != nil {
+		t.Fatalf("AllBlessingSystemAssociations failed: %v", err)
+	}
+	utiltest.CompareAssociations(t, got1, []device.Association{
+		{
+			"alice",
+			"alice_account",
+		},
+	})
+
+	err = nbsa1.AssociateSystemAccountForBlessings([]string{"alice", "bob"}, "alice_other_account")
+	if err != nil {
+		t.Fatalf("AssociateSystemAccount failed: %v", err)
+	}
+	// Verify that "bob" and "alice" have new values.
+	got1, err = nbsa1.AllBlessingSystemAssociations()
+	if err != nil {
+		t.Fatalf("AllBlessingSystemAssociations failed: %v", err)
+	}
+	utiltest.CompareAssociations(t, got1, []device.Association{
+		{
+			"alice",
+			"alice_other_account",
+		},
+		{
+			"bob",
+			"alice_other_account",
+		},
+	})
+
+	// Make future serialization attempts fail.
+	if err := os.RemoveAll(td); err != nil {
+		t.Fatalf("os.RemoveAll: couldn't delete %s: %v", td, err)
+	}
+	err = nbsa1.AssociateSystemAccountForBlessings([]string{"doug"}, "alice_account")
+	if err == nil {
+		t.Fatalf("AssociateSystemAccount should have failed but didn't")
+	}
+}
+
+func TestAssociationPersistanceDetectsBadStartingConditions(t *testing.T) {
+	dir := "/i-am-hoping-that-there-is-no-such-directory"
+	nbsa1, err := impl.NewBlessingSystemAssociationStore(dir)
+	if nbsa1 != nil || err == nil {
+		t.Fatalf("bad root directory %s ought to have caused an error", dir)
+	}
+
+	// Create a NewBlessingSystemAssociationStore directory as a side-effect.
+	dir, err = ioutil.TempDir("", "bad-starting-conditions")
+	if err != nil {
+		t.Fatalf("TempDir failed: %v", err)
+	}
+	nbsa1, err = impl.NewBlessingSystemAssociationStore(dir)
+	defer os.RemoveAll(dir)
+	if err != nil {
+		t.Fatalf("NewBlessingSystemAssociationStore failed: %v", err)
+	}
+
+	tpath := path.Join(dir, "device-manager", "device-data", "associated.accounts")
+	f, err := os.Create(tpath)
+	if err != nil {
+		t.Fatalf("could not open backing file for setup: %v", err)
+	}
+
+	if _, err := io.WriteString(f, "bad-json\""); err != nil {
+		t.Fatalf("could not write to test file  %s: %v", tpath, err)
+	}
+	f.Close()
+
+	nbsa1, err = impl.NewBlessingSystemAssociationStore(dir)
+	if nbsa1 != nil || err == nil {
+		t.Fatalf("invalid JSON ought to have caused an error")
+	}
+
+	// This test will fail if executed as root or if your system is configured oddly.
+	unreadableFile := "/dev/autofs"
+	nbsa1, err = impl.NewBlessingSystemAssociationStore(unreadableFile)
+	if nbsa1 != nil || err == nil {
+		t.Fatalf("unreadable file %s ought to have caused an error", unreadableFile)
+	}
+}
diff --git a/services/device/deviced/internal/impl/callback.go b/services/device/deviced/internal/impl/callback.go
new file mode 100644
index 0000000..1f7aad6
--- /dev/null
+++ b/services/device/deviced/internal/impl/callback.go
@@ -0,0 +1,35 @@
+// 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 impl
+
+import (
+	"v.io/v23/context"
+	"v.io/v23/verror"
+	"v.io/x/ref/lib/exec"
+	"v.io/x/ref/lib/mgmt"
+	"v.io/x/ref/services/device"
+)
+
+// InvokeCallback provides the parent device manager with the given name (which
+// is expected to be this device manager's object name).
+func InvokeCallback(ctx *context.T, name string) {
+	handle, err := exec.GetChildHandle()
+	if err == nil {
+		// Device manager was started by self-update, notify the parent.
+		callbackName, err := handle.Config.Get(mgmt.ParentNameConfigKey)
+		if err != nil {
+			// Device manager was not started by self-update, return silently.
+			return
+		}
+		client := device.ConfigClient(callbackName)
+		ctx, cancel := context.WithTimeout(ctx, rpcContextTimeout)
+		defer cancel()
+		if err := client.Set(ctx, mgmt.ChildNameConfigKey, name); err != nil {
+			ctx.Fatalf("Set(%v, %v) failed: %v", mgmt.ChildNameConfigKey, name, err)
+		}
+	} else if verror.ErrorID(err) != exec.ErrNoVersion.ID {
+		ctx.Fatalf("GetChildHandle() failed: %v", err)
+	}
+}
diff --git a/services/device/deviced/internal/impl/config_service.go b/services/device/deviced/internal/impl/config_service.go
new file mode 100644
index 0000000..f4b975f
--- /dev/null
+++ b/services/device/deviced/internal/impl/config_service.go
@@ -0,0 +1,156 @@
+// 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 impl
+
+// The config invoker is responsible for answering calls to the config service
+// run as part of the device manager.  The config invoker converts RPCs to
+// messages on channels that are used to listen on callbacks coming from child
+// application instances.
+
+import (
+	"fmt"
+	"strconv"
+	"sync"
+	"time"
+
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+	"v.io/v23/verror"
+	"v.io/x/ref/services/device/internal/errors"
+)
+
+type callbackState struct {
+	sync.Mutex
+	// channels maps callback identifiers and config keys to channels that
+	// are used to communicate corresponding config values from child
+	// processes.
+	channels map[string]map[string]chan<- string
+	// nextCallbackID provides the next callback identifier to use as a key
+	// for the channels map.
+	nextCallbackID int64
+	// name is the object name for making calls against the device manager's
+	// config service.
+	name string
+}
+
+func newCallbackState(name string) *callbackState {
+	return &callbackState{
+		channels: make(map[string]map[string]chan<- string),
+		name:     name,
+	}
+}
+
+// callbackListener abstracts out listening for values provided via the
+// callback mechanism for a given key.
+type callbackListener interface {
+	// waitForValue blocks until the value that this listener is expecting
+	// arrives, until the timeout expires, or until stop() is called
+	waitForValue(timeout time.Duration) (string, error)
+	// stop makes waitForValue return early
+	stop()
+	// cleanup cleans up any state used by the listener.  Should be called
+	// when the listener is no longer needed.
+	cleanup()
+	// name returns the object name for the config service object that
+	// handles the key that the listener is listening for.
+	name() string
+}
+
+// listener implements callbackListener
+type listener struct {
+	id      string
+	cs      *callbackState
+	ch      <-chan string
+	n       string
+	stopper chan struct{}
+}
+
+func (l *listener) waitForValue(timeout time.Duration) (string, error) {
+	select {
+	case value := <-l.ch:
+		return value, nil
+	case <-time.After(timeout):
+		return "", verror.New(errors.ErrOperationFailed, nil, fmt.Sprintf("Waiting for callback timed out after %v", timeout))
+	case <-l.stopper:
+		return "", verror.New(errors.ErrOperationFailed, nil, fmt.Sprintf("Stopped while waiting for callack"))
+	}
+}
+
+func (l *listener) stop() {
+	close(l.stopper)
+}
+
+func (l *listener) cleanup() {
+	l.cs.unregister(l.id)
+}
+
+func (l *listener) name() string {
+	return l.n
+}
+
+func (c *callbackState) listenFor(key string) callbackListener {
+	id := c.generateID()
+	callbackName := naming.Join(c.name, configSuffix, id)
+	// Make the channel buffered to avoid blocking the Set method when
+	// nothing is receiving on the channel.  This happens e.g. when
+	// unregisterCallbacks executes before Set is called.
+	callbackChan := make(chan string, 1)
+	c.register(id, key, callbackChan)
+	stopchan := make(chan struct{}, 1)
+	return &listener{
+		id:      id,
+		cs:      c,
+		ch:      callbackChan,
+		n:       callbackName,
+		stopper: stopchan,
+	}
+}
+
+func (c *callbackState) generateID() string {
+	c.Lock()
+	defer c.Unlock()
+	c.nextCallbackID++
+	return strconv.FormatInt(c.nextCallbackID-1, 10)
+}
+
+func (c *callbackState) register(id, key string, channel chan<- string) {
+	c.Lock()
+	defer c.Unlock()
+	if _, ok := c.channels[id]; !ok {
+		c.channels[id] = make(map[string]chan<- string)
+	}
+	c.channels[id][key] = channel
+}
+
+func (c *callbackState) unregister(id string) {
+	c.Lock()
+	defer c.Unlock()
+	delete(c.channels, id)
+}
+
+// configService implements the Device manager's Config interface.
+type configService struct {
+	callback *callbackState
+	// Suffix contains an identifier for the channel corresponding to the
+	// request.
+	suffix string
+}
+
+func (i *configService) Set(_ *context.T, _ rpc.ServerCall, key, value string) error {
+	id := i.suffix
+	i.callback.Lock()
+	if _, ok := i.callback.channels[id]; !ok {
+		i.callback.Unlock()
+		return verror.New(errors.ErrInvalidSuffix, nil)
+	}
+	channel, ok := i.callback.channels[id][key]
+	i.callback.Unlock()
+	if !ok {
+		return nil
+	}
+	channel <- value
+	return nil
+}
diff --git a/services/device/deviced/internal/impl/daemonreap/daemon_reaping_test.go b/services/device/deviced/internal/impl/daemonreap/daemon_reaping_test.go
new file mode 100644
index 0000000..72f816a
--- /dev/null
+++ b/services/device/deviced/internal/impl/daemonreap/daemon_reaping_test.go
@@ -0,0 +1,96 @@
+// 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 daemonreap_test
+
+import (
+	"syscall"
+	"testing"
+	"time"
+
+	"v.io/v23/services/device"
+
+	"v.io/x/ref/services/device/deviced/internal/impl/utiltest"
+	"v.io/x/ref/services/internal/servicetest"
+)
+
+func TestDaemonRestart(t *testing.T) {
+	if raceEnabled {
+		t.Skip("Test is flaky when run with -race.  Disabling until v.io/i/573 is fixed.")
+	}
+	cleanup, ctx, sh, envelope, root, helperPath, _ := utiltest.StartupHelper(t)
+	defer cleanup()
+
+	// Set up the device manager.  Since we won't do device manager updates,
+	// don't worry about its application envelope and current link.
+	dmh := servicetest.RunCommand(t, sh, nil, utiltest.DeviceManager, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
+	servicetest.ReadPID(t, dmh)
+	utiltest.ClaimDevice(t, ctx, "claimable", "dm", "mydevice", utiltest.NoPairingToken)
+
+	// Create the local server that the app uses to let us know it's ready.
+	pingCh, cleanup := utiltest.SetupPingServer(t, ctx)
+	defer cleanup()
+
+	utiltest.Resolve(t, ctx, "pingserver", 1, true)
+
+	// Create an envelope for a first version of the app that will be restarted once.
+	*envelope = utiltest.EnvelopeFromShell(sh, nil, utiltest.App, "google naps", 1, 10*time.Minute, "appV1")
+	appID := utiltest.InstallApp(t, ctx)
+
+	// Start an instance of the app.
+	instance1ID := utiltest.LaunchApp(t, ctx, appID)
+
+	// Wait until the app pings us that it's ready.
+	pingCh.VerifyPingArgs(t, utiltest.UserName(t), "default", "")
+
+	// Get application pid.
+	pid := utiltest.GetPid(t, ctx, appID, instance1ID)
+
+	utiltest.VerifyState(t, ctx, device.InstanceStateRunning, appID, instance1ID)
+	syscall.Kill(int(pid), 9)
+	utiltest.PollingWait(t, int(pid))
+
+	// Start a second instance of the app which will force polling to happen.
+	// During this polling, the reaper will restart app instance1
+	instance2ID := utiltest.LaunchApp(t, ctx, appID)
+	pingCh.VerifyPingArgs(t, utiltest.UserName(t), "default", "")
+	utiltest.VerifyState(t, ctx, device.InstanceStateRunning, appID, instance2ID)
+
+	// Stop the second instance of the app which will also force polling. By this point,
+	// instance1 should be live.
+	utiltest.KillApp(t, ctx, appID, instance2ID)
+
+	// instance2ID is not running.
+	utiltest.VerifyState(t, ctx, device.InstanceStateNotRunning, appID, instance2ID)
+
+	// Be sure to get the ping from the restarted application so that the app is running
+	// again before we ask for its status.
+	pingCh.WaitForPingArgs(t)
+
+	// instance1ID was restarted automatically.
+	utiltest.VerifyState(t, ctx, device.InstanceStateRunning, appID, instance1ID)
+
+	// Get application pid.
+	pid = utiltest.GetPid(t, ctx, appID, instance1ID)
+	// Kill the application again.
+	syscall.Kill(int(pid), 9)
+	utiltest.PollingWait(t, int(pid))
+
+	// Start and stop instance 2 again to force two polling cycles.
+	utiltest.RunApp(t, ctx, appID, instance2ID)
+	pingCh.VerifyPingArgs(t, utiltest.UserName(t), "default", "")
+	utiltest.KillApp(t, ctx, appID, instance2ID)
+
+	// instance1ID is not running because it exceeded its restart limit.
+	utiltest.VerifyState(t, ctx, device.InstanceStateNotRunning, appID, instance1ID)
+
+	// instance2ID is not running.
+	utiltest.VerifyState(t, ctx, device.InstanceStateNotRunning, appID, instance2ID)
+
+	// Cleanly shut down the device manager.
+	utiltest.VerifyNoRunningProcesses(t)
+	syscall.Kill(dmh.Pid(), syscall.SIGINT)
+	dmh.Expect("dm terminated")
+	dmh.ExpectEOF()
+}
diff --git a/services/device/deviced/internal/impl/daemonreap/doc.go b/services/device/deviced/internal/impl/daemonreap/doc.go
new file mode 100644
index 0000000..c0def53
--- /dev/null
+++ b/services/device/deviced/internal/impl/daemonreap/doc.go
@@ -0,0 +1,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.
+
+package daemonreap
+
+// Test code for the device manager's facility to run daemon
+// processes and reconcile processes via kill.
diff --git a/services/device/deviced/internal/impl/daemonreap/impl_test.go b/services/device/deviced/internal/impl/daemonreap/impl_test.go
new file mode 100644
index 0000000..5f0ed8f
--- /dev/null
+++ b/services/device/deviced/internal/impl/daemonreap/impl_test.go
@@ -0,0 +1,19 @@
+// 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 daemonreap_test
+
+import (
+	"testing"
+
+	"v.io/x/ref/services/device/deviced/internal/impl/utiltest"
+)
+
+func TestMain(m *testing.M) {
+	utiltest.TestMainImpl(m)
+}
+
+func TestSuidHelper(t *testing.T) {
+	utiltest.TestSuidHelperImpl(t)
+}
diff --git a/services/device/deviced/internal/impl/daemonreap/instance_reaping_kill_test.go b/services/device/deviced/internal/impl/daemonreap/instance_reaping_kill_test.go
new file mode 100644
index 0000000..c2383f6
--- /dev/null
+++ b/services/device/deviced/internal/impl/daemonreap/instance_reaping_kill_test.go
@@ -0,0 +1,121 @@
+// 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 daemonreap_test
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"syscall"
+	"testing"
+
+	"v.io/v23/services/device"
+	"v.io/x/ref"
+	"v.io/x/ref/services/device/deviced/internal/impl/utiltest"
+	"v.io/x/ref/services/internal/servicetest"
+)
+
+func TestReapReconciliationViaKill(t *testing.T) {
+	cleanup, ctx, sh, envelope, root, helperPath, _ := utiltest.StartupHelper(t)
+	defer cleanup()
+
+	// Start a device manager.
+	// (Since it will be restarted, use the VeyronCredentials environment
+	// to maintain the same set of credentials across runs)
+	dmCreds, err := ioutil.TempDir("", "TestReapReconciliationViaKill")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(dmCreds)
+	dmEnv := []string{fmt.Sprintf("%v=%v", ref.EnvCredentials, dmCreds)}
+
+	dmh := servicetest.RunCommand(t, sh, dmEnv, utiltest.DeviceManager, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
+	servicetest.ReadPID(t, dmh)
+	utiltest.ClaimDevice(t, ctx, "claimable", "dm", "mydevice", utiltest.NoPairingToken)
+
+	// Create the local server that the app uses to let us know it's ready.
+	pingCh, cleanup := utiltest.SetupPingServer(t, ctx)
+	defer cleanup()
+	utiltest.Resolve(t, ctx, "pingserver", 1, true)
+
+	// Create an envelope for the app.
+	*envelope = utiltest.EnvelopeFromShell(sh, nil, utiltest.App, "google naps", 0, 0, "appV1")
+
+	// Install the app.
+	appID := utiltest.InstallApp(t, ctx)
+
+	// Start three app instances.
+	instances := make([]string, 3)
+	for i, _ := range instances {
+		instances[i] = utiltest.LaunchApp(t, ctx, appID)
+		pingCh.VerifyPingArgs(t, utiltest.UserName(t), "default", "")
+	}
+
+	// Get pid of instance[0]
+	pid := utiltest.GetPid(t, ctx, appID, instances[0])
+
+	// Shutdown the first device manager.
+	syscall.Kill(dmh.Pid(), syscall.SIGINT)
+	dmh.Expect("dm terminated")
+	dmh.ExpectEOF()
+	dmh.Shutdown(os.Stderr, os.Stderr)
+	utiltest.ResolveExpectNotFound(t, ctx, "dm", false) // Ensure a clean slate.
+
+	// Kill instance[0] and wait until it exits before proceeding.
+	syscall.Kill(pid, 9)
+	utiltest.PollingWait(t, pid)
+
+	// Run another device manager to replace the dead one.
+	dmh = servicetest.RunCommand(t, sh, dmEnv, utiltest.DeviceManager, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
+	servicetest.ReadPID(t, dmh)
+	utiltest.Resolve(t, ctx, "dm", 1, true) // Verify the device manager has published itself.
+
+	// By now, we've reconciled the state of the tree with which processes
+	// are actually alive. instance-0 is not alive.
+	expected := []device.InstanceState{device.InstanceStateNotRunning, device.InstanceStateRunning, device.InstanceStateRunning}
+	for i, _ := range instances {
+		utiltest.VerifyState(t, ctx, expected[i], appID, instances[i])
+	}
+
+	// Start instance[0] over-again to show that an app marked not running
+	// by reconciliation can be restarted.
+	utiltest.RunApp(t, ctx, appID, instances[0])
+	pingCh.VerifyPingArgs(t, utiltest.UserName(t), "default", "")
+
+	// Kill instance[1]
+	pid = utiltest.GetPid(t, ctx, appID, instances[1])
+	syscall.Kill(pid, 9)
+
+	// Make a fourth instance. This forces a polling of processes so that
+	// the state is updated.
+	instances = append(instances, utiltest.LaunchApp(t, ctx, appID))
+	pingCh.VerifyPingArgs(t, utiltest.UserName(t), "default", "")
+
+	// Stop the fourth instance to make sure that there's no way we could
+	// still be running the polling loop before doing the below.
+	utiltest.TerminateApp(t, ctx, appID, instances[3])
+
+	// Verify that reaper picked up the previous instances and was watching
+	// instance[1]
+	expected = []device.InstanceState{device.InstanceStateRunning, device.InstanceStateNotRunning, device.InstanceStateRunning, device.InstanceStateDeleted}
+	for i, _ := range instances {
+		utiltest.VerifyState(t, ctx, expected[i], appID, instances[i])
+	}
+
+	utiltest.TerminateApp(t, ctx, appID, instances[2])
+
+	expected = []device.InstanceState{device.InstanceStateRunning, device.InstanceStateNotRunning, device.InstanceStateDeleted, device.InstanceStateDeleted}
+	for i, _ := range instances {
+		utiltest.VerifyState(t, ctx, expected[i], appID, instances[i])
+	}
+	utiltest.TerminateApp(t, ctx, appID, instances[0])
+
+	// TODO(rjkroege): Should be in a defer to ensure that the device
+	// manager is cleaned up even if the test fails in an exceptional way.
+	utiltest.VerifyNoRunningProcesses(t)
+	syscall.Kill(dmh.Pid(), syscall.SIGINT)
+	dmh.Expect("dm terminated")
+	dmh.ExpectEOF()
+}
diff --git a/services/device/deviced/internal/impl/daemonreap/persistent_daemon_kill_test.go b/services/device/deviced/internal/impl/daemonreap/persistent_daemon_kill_test.go
new file mode 100644
index 0000000..58f511f
--- /dev/null
+++ b/services/device/deviced/internal/impl/daemonreap/persistent_daemon_kill_test.go
@@ -0,0 +1,91 @@
+// 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 daemonreap_test
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"syscall"
+	"testing"
+	"time"
+
+	"v.io/v23/services/device"
+	"v.io/x/ref"
+	"v.io/x/ref/services/device/deviced/internal/impl/utiltest"
+	"v.io/x/ref/services/internal/servicetest"
+)
+
+func TestReapRestartsDaemonMode(t *testing.T) {
+	// TODO(rjkroege): Enable this test once v.io/i/573 is fixed.
+	t.Skip("Test is flaky. Disabling until v.io/i/573 is fixed.")
+
+	cleanup, ctx, sh, envelope, root, helperPath, _ := utiltest.StartupHelper(t)
+	defer cleanup()
+
+	// Start a device manager.
+	// (Since it will be restarted, use the VeyronCredentials environment
+	// to maintain the same set of credentials across runs)
+	dmCreds, err := ioutil.TempDir("", "TestReapReconciliationViaKill")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(dmCreds)
+	dmEnv := []string{fmt.Sprintf("%v=%v", ref.EnvCredentials, dmCreds)}
+
+	dmh := servicetest.RunCommand(t, sh, dmEnv, utiltest.DeviceManager, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
+	servicetest.ReadPID(t, dmh)
+	utiltest.ClaimDevice(t, ctx, "claimable", "dm", "mydevice", utiltest.NoPairingToken)
+
+	// Create the local server that the app uses to let us know it's ready.
+	pingCh, cleanup := utiltest.SetupPingServer(t, ctx)
+	defer cleanup()
+	utiltest.Resolve(t, ctx, "pingserver", 1, true)
+
+	// Create an envelope for a daemon app.
+	*envelope = utiltest.EnvelopeFromShell(sh, nil, utiltest.App, "google naps", 10, time.Hour, "appV1")
+
+	// Install the app.
+	appID := utiltest.InstallApp(t, ctx)
+
+	instance1 := utiltest.LaunchApp(t, ctx, appID)
+	pingCh.VerifyPingArgs(t, utiltest.UserName(t), "default", "")
+
+	// Get pid of first instance.
+	pid := utiltest.GetPid(t, ctx, appID, instance1)
+
+	// Shutdown the first device manager.
+	syscall.Kill(dmh.Pid(), syscall.SIGINT)
+	dmh.Expect("dm terminated")
+	dmh.ExpectEOF()
+	dmh.Shutdown(os.Stderr, os.Stderr)
+	utiltest.ResolveExpectNotFound(t, ctx, "dm", false) // Ensure a clean slate.
+
+	// Kill instance[0] and wait until it exits before proceeding.
+	syscall.Kill(pid, 9)
+	utiltest.PollingWait(t, int(pid))
+
+	// Run another device manager to replace the dead one.
+	dmh = servicetest.RunCommand(t, sh, dmEnv, utiltest.DeviceManager, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
+
+	defer func() {
+		utiltest.TerminateApp(t, ctx, appID, instance1)
+		utiltest.VerifyNoRunningProcesses(t)
+		syscall.Kill(dmh.Pid(), syscall.SIGINT)
+		dmh.Expect("dm terminated")
+		dmh.ExpectEOF()
+	}()
+
+	servicetest.ReadPID(t, dmh)
+	utiltest.Resolve(t, ctx, "dm", 1, true) // Verify the device manager has published itself.
+
+	// The app will ping us. Wait for it.
+	pingCh.VerifyPingArgs(t, utiltest.UserName(t), "default", "")
+
+	// By now, we've reconciled the state of the tree with which processes
+	// are actually alive. instance1 was not alive but since it is configured as a
+	// daemon, it will have been restarted.
+	utiltest.VerifyState(t, ctx, device.InstanceStateRunning, appID, instance1)
+}
diff --git a/services/device/deviced/internal/impl/daemonreap/race0_test.go b/services/device/deviced/internal/impl/daemonreap/race0_test.go
new file mode 100644
index 0000000..07e0deb
--- /dev/null
+++ b/services/device/deviced/internal/impl/daemonreap/race0_test.go
@@ -0,0 +1,9 @@
+// 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 !race
+
+package daemonreap_test
+
+const raceEnabled = false
diff --git a/services/device/deviced/internal/impl/daemonreap/race_test.go b/services/device/deviced/internal/impl/daemonreap/race_test.go
new file mode 100644
index 0000000..053fdb1
--- /dev/null
+++ b/services/device/deviced/internal/impl/daemonreap/race_test.go
@@ -0,0 +1,9 @@
+// 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 race
+
+package daemonreap_test
+
+const raceEnabled = true
diff --git a/services/device/deviced/internal/impl/device_service.go b/services/device/deviced/internal/impl/device_service.go
new file mode 100644
index 0000000..76a1a7f
--- /dev/null
+++ b/services/device/deviced/internal/impl/device_service.go
@@ -0,0 +1,648 @@
+// 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 impl
+
+// The device invoker is responsible for managing the state of the device
+// manager itself.  The implementation expects that the device manager
+// installations are all organized in the following directory structure:
+//
+// <config.Root>/
+//   device-manager/
+//     info                    - metadata for the device manager (such as object
+//                               name and process id)
+//     logs/                   - device manager logs
+//       STDERR-<timestamp>    - one for each execution of device manager
+//       STDOUT-<timestamp>    - one for each execution of device manager
+//     <version 1 timestamp>/  - timestamp of when the version was downloaded
+//       deviced               - the device manager binary
+//       deviced.sh            - a shell script to start the binary
+//     <version 2 timestamp>
+//     ...
+//     device-data/
+//       acls/
+//         data
+//         signature
+//     associated.accounts
+//       persistent-args       - list of persistent arguments for the device
+//                               manager (json encoded)
+//
+// The device manager is always expected to be started through the symbolic link
+// passed in as config.CurrentLink, which is monitored by an init daemon. This
+// provides for simple and robust updates.
+//
+// To update the device manager to a newer version, a new workspace is created
+// and the symlink is updated to the new deviced.sh script. Similarly, to revert
+// the device manager to a previous version, all that is required is to update
+// the symlink to point to the previous deviced.sh script.
+
+import (
+	"bufio"
+	"encoding/json"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"reflect"
+	"strings"
+	"sync"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/security/access"
+	"v.io/v23/services/application"
+	"v.io/v23/services/binary"
+	"v.io/v23/services/device"
+	"v.io/v23/verror"
+	"v.io/x/ref"
+	vexec "v.io/x/ref/lib/exec"
+	"v.io/x/ref/lib/mgmt"
+	vsecurity "v.io/x/ref/lib/security"
+	"v.io/x/ref/services/agent/agentlib"
+	"v.io/x/ref/services/device/internal/config"
+	"v.io/x/ref/services/device/internal/errors"
+	"v.io/x/ref/services/profile"
+)
+
+type updatingState struct {
+	// updating is a flag that records whether this instance of device
+	// manager is being updated.
+	updating bool
+	// updatingMutex is a lock for coordinating concurrent access to
+	// <updating>.
+	updatingMutex sync.Mutex
+}
+
+func newUpdatingState() *updatingState {
+	return new(updatingState)
+}
+
+func (u *updatingState) testAndSetUpdating() bool {
+	u.updatingMutex.Lock()
+	defer u.updatingMutex.Unlock()
+	if u.updating {
+		return true
+	}
+	u.updating = true
+	return false
+}
+
+func (u *updatingState) unsetUpdating() {
+	u.updatingMutex.Lock()
+	u.updating = false
+	u.updatingMutex.Unlock()
+}
+
+// deviceService implements the Device manager's Device interface.
+type deviceService struct {
+	updating       *updatingState
+	restartHandler func()
+	callback       *callbackState
+	config         *config.State
+	disp           *dispatcher
+	uat            BlessingSystemAssociationStore
+	securityAgent  *securityAgentState
+	tidying        chan<- tidyRequests
+}
+
+// ManagerInfo holds state about a running device manager or a running agentd
+type ManagerInfo struct {
+	Pid int
+}
+
+func SaveManagerInfo(dir string, info *ManagerInfo) error {
+	jsonInfo, err := json.Marshal(info)
+	if err != nil {
+		return verror.New(errors.ErrOperationFailed, nil, fmt.Sprintf("Marshal(%v) failed: %v", info, err))
+	}
+	if err := os.MkdirAll(dir, os.FileMode(0700)); err != nil {
+		return verror.New(errors.ErrOperationFailed, nil, fmt.Sprintf("MkdirAll(%v) failed: %v", dir, err))
+	}
+	infoPath := filepath.Join(dir, "info")
+	if err := ioutil.WriteFile(infoPath, jsonInfo, 0600); err != nil {
+		return verror.New(errors.ErrOperationFailed, nil, fmt.Sprintf("WriteFile(%v) failed: %v", infoPath, err))
+	}
+	return nil
+}
+
+func LoadManagerInfo(dir string) (*ManagerInfo, error) {
+	infoPath := filepath.Join(dir, "info")
+	info := new(ManagerInfo)
+	if infoBytes, err := ioutil.ReadFile(infoPath); err != nil {
+		return nil, verror.New(errors.ErrOperationFailed, nil, fmt.Sprintf("ReadFile(%v) failed: %v", infoPath, err))
+	} else if err := json.Unmarshal(infoBytes, info); err != nil {
+		return nil, verror.New(errors.ErrOperationFailed, nil, fmt.Sprintf("Unmarshal(%v) failed: %v", infoBytes, err))
+	}
+	return info, nil
+}
+
+func SavePersistentArgs(root string, args []string) error {
+	dir := filepath.Join(root, "device-manager", "device-data")
+	if err := os.MkdirAll(dir, 0700); err != nil {
+		return fmt.Errorf("MkdirAll(%q) failed: %v", dir, err)
+	}
+	data, err := json.Marshal(args)
+	if err != nil {
+		return fmt.Errorf("Marshal(%v) failed: %v", args, err)
+	}
+	fileName := filepath.Join(dir, "persistent-args")
+	return ioutil.WriteFile(fileName, data, 0600)
+}
+
+func loadPersistentArgs(root string) ([]string, error) {
+	fileName := filepath.Join(root, "device-manager", "device-data", "persistent-args")
+	bytes, err := ioutil.ReadFile(fileName)
+	if err != nil {
+		return nil, err
+	}
+	args := []string{}
+	if err := json.Unmarshal(bytes, &args); err != nil {
+		return nil, fmt.Errorf("json.Unmarshal(%v) failed: %v", bytes, err)
+	}
+	return args, nil
+}
+
+func (*deviceService) Describe(*context.T, rpc.ServerCall) (device.Description, error) {
+	return Describe()
+}
+
+func (*deviceService) IsRunnable(_ *context.T, _ rpc.ServerCall, description binary.Description) (bool, error) {
+	deviceProfile, err := ComputeDeviceProfile()
+	if err != nil {
+		return false, err
+	}
+	binaryProfiles := make([]*profile.Specification, 0)
+	for name, _ := range description.Profiles {
+		profile, err := getProfile(name)
+		if err != nil {
+			return false, err
+		}
+		binaryProfiles = append(binaryProfiles, profile)
+	}
+	result := matchProfiles(deviceProfile, binaryProfiles)
+	return len(result.Profiles) > 0, nil
+}
+
+func (*deviceService) Reset(_ *context.T, _ rpc.ServerCall, deadline time.Duration) error {
+	// TODO(jsimsa): Implement.
+	return nil
+}
+
+// getCurrentFileInfo returns the os.FileInfo for both the symbolic link
+// CurrentLink, and the device script in the workspace that this link points to.
+func (s *deviceService) getCurrentFileInfo() (os.FileInfo, string, error) {
+	path := s.config.CurrentLink
+	link, err := os.Lstat(path)
+	if err != nil {
+		return nil, "", verror.New(errors.ErrOperationFailed, nil, fmt.Sprintf("Lstat(%v) failed: %v", path, err))
+	}
+	scriptPath, err := filepath.EvalSymlinks(path)
+	if err != nil {
+		return nil, "", verror.New(errors.ErrOperationFailed, nil, fmt.Sprintf("EvalSymlinks(%v) failed: %v", path, err))
+	}
+	return link, scriptPath, nil
+}
+
+func (s *deviceService) revertDeviceManager(ctx *context.T) error {
+	if err := UpdateLink(s.config.Previous, s.config.CurrentLink); err != nil {
+		return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("UpdateLink failed: %v", err))
+	}
+	if s.restartHandler != nil {
+		s.restartHandler()
+	}
+	v23.GetAppCycle(ctx).Stop(ctx)
+	return nil
+}
+
+func (s *deviceService) newLogfile(prefix string) (*os.File, error) {
+	d := filepath.Join(s.config.Root, "device_test_logs")
+	if _, err := os.Stat(d); err != nil {
+		if err := os.MkdirAll(d, 0700); err != nil {
+			return nil, err
+		}
+	}
+	f, err := ioutil.TempFile(d, "__device_impl_test__"+prefix)
+	if err != nil {
+		return nil, err
+	}
+	return f, nil
+}
+
+// TODO(cnicolaou): would this be better implemented using the modules
+// framework now that it exists?
+func (s *deviceService) testDeviceManager(ctx *context.T, workspace string, envelope *application.Envelope) error {
+	path := filepath.Join(workspace, "deviced.sh")
+	cmd := exec.Command(path)
+	cmd.Env = []string{"DEVICE_MANAGER_DONT_REDIRECT_STDOUT_STDERR=1"}
+
+	for k, v := range map[string]*io.Writer{
+		"stdout": &cmd.Stdout,
+		"stderr": &cmd.Stderr,
+	} {
+		// Using a log file makes it less likely that stdout and stderr
+		// output will be lost if the child crashes.
+		file, err := s.newLogfile(fmt.Sprintf("deviced-test-%s", k))
+		if err != nil {
+			return err
+		}
+		fName := file.Name()
+		defer os.Remove(fName)
+		*v = file
+
+		defer func(k string) {
+			if f, err := os.Open(fName); err == nil {
+				scanner := bufio.NewScanner(f)
+				for scanner.Scan() {
+					ctx.Infof("[testDeviceManager %s] %s", k, scanner.Text())
+				}
+			}
+		}(k)
+	}
+
+	// Setup up the child process callback.
+	callbackState := s.callback
+	listener := callbackState.listenFor(mgmt.ChildNameConfigKey)
+	defer listener.cleanup()
+	cfg := vexec.NewConfig()
+
+	cfg.Set(mgmt.ParentNameConfigKey, listener.name())
+	cfg.Set(mgmt.ProtocolConfigKey, "tcp")
+	cfg.Set(mgmt.AddressConfigKey, "127.0.0.1:0")
+
+	var p security.Principal
+	var agentHandle []byte
+
+	switch sa := s.securityAgent; {
+	case sa != nil && sa.keyMgr != nil:
+		handle, err := sa.keyMgr.NewPrincipal(true)
+		if err != nil {
+			return verror.New(errors.ErrOperationFailed, ctx, "NewPrincipal() failed", err)
+		}
+		sockDir, err := generateAgentSockDir(s.config.Root)
+		if err != nil {
+			return verror.New(errors.ErrOperationFailed, ctx, "generateAgentSockDir() failed", err)
+		}
+		// TODO(caprita): Add a check to ensure that len(sockPath) < 108.
+		sockPath := filepath.Join(sockDir, "s")
+		if err := sa.keyMgr.ServePrincipal(handle, sockPath); err != nil {
+			return verror.New(errors.ErrOperationFailed, ctx, "ServePrincipal failed", err)
+		}
+		cfg.Set(mgmt.SecurityAgentPathConfigKey, sockPath)
+		defer func() {
+			if err := sa.keyMgr.StopServing(handle); err != nil {
+				ctx.Errorf("StopServing failed: %v", err)
+			}
+			if err := sa.keyMgr.DeletePrincipal(handle); err != nil {
+				ctx.Errorf("DeletePrincipal failed: %v", err)
+			}
+		}()
+		if p, err = agentlib.NewAgentPrincipalX(sockPath); err != nil {
+			return verror.New(errors.ErrOperationFailed, ctx, "NewAgentPrincipalX failed", err)
+		}
+	case sa != nil && sa.keyMgrAgent != nil:
+		// This code path is deprecated in favor of the socket agent
+		// connection.
+		handle, conn, err := sa.keyMgrAgent.NewPrincipal(ctx, true)
+		if err != nil {
+			return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("NewPrincipal() failed %v", err))
+		}
+		agentHandle = handle
+		var cancel func()
+		if p, cancel, err = agentPrincipal(ctx, conn); err != nil {
+			return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("agentPrincipal failed: %v", err))
+		}
+		defer cancel()
+	default:
+		credentialsDir := filepath.Join(workspace, "credentials")
+		var err error
+		if p, err = vsecurity.CreatePersistentPrincipal(credentialsDir, nil); err != nil {
+			return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("CreatePersistentPrincipal(%v, nil) failed: %v", credentialsDir, err))
+		}
+		cmd.Env = append(cmd.Env, ref.EnvCredentials+"="+credentialsDir)
+	}
+	dmPrincipal := v23.GetPrincipal(ctx)
+	dmBlessings, err := dmPrincipal.Bless(p.PublicKey(), dmPrincipal.BlessingStore().Default(), "testdm", security.UnconstrainedUse())
+	if err := p.BlessingStore().SetDefault(dmBlessings); err != nil {
+		return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("BlessingStore.SetDefault() failed: %v", err))
+	}
+	if _, err := p.BlessingStore().Set(dmBlessings, security.AllPrincipals); err != nil {
+		return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("BlessingStore.Set() failed: %v", err))
+	}
+	if err := p.AddToRoots(dmBlessings); err != nil {
+		return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("AddToRoots() failed: %v", err))
+	}
+
+	if s.securityAgent != nil && s.securityAgent.keyMgrAgent != nil {
+		// This code path is deprecated in favor of the socket agent
+		// connection.
+		file, err := s.securityAgent.keyMgrAgent.NewConnection(agentHandle)
+		if err != nil {
+			return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("NewConnection(%v) failed: %v", agentHandle, err))
+		}
+		defer file.Close()
+
+		fd := len(cmd.ExtraFiles) + vexec.FileOffset
+		cmd.ExtraFiles = append(cmd.ExtraFiles, file)
+		// TODO: This should use the same network as the agent we're using,
+		// not whatever this process was compiled with.
+		ep := agentlib.AgentEndpoint(fd)
+		cfg.Set(mgmt.SecurityAgentEndpointConfigKey, ep)
+	}
+
+	handle := vexec.NewParentHandle(cmd, vexec.ConfigOpt{Config: cfg})
+	// Start the child process.
+	if err := handle.Start(); err != nil {
+		ctx.Errorf("Start() failed: %v", err)
+		return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("Start() failed: %v", err))
+	}
+	defer func() {
+		if err := handle.Clean(); err != nil {
+			ctx.Errorf("Clean() failed: %v", err)
+		}
+	}()
+
+	// Wait for the child process to start.
+	if err := handle.WaitForReady(childReadyTimeout); err != nil {
+		return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("WaitForReady(%v) failed: %v", childReadyTimeout, err))
+	}
+
+	// Watch for the exit of the child. Failures could cause it to happen at any time
+	waitchan := make(chan error, 1)
+	go func() {
+		// Wait timeout needs to be long enough to give the rest of the operations time to run
+		err := handle.Wait(2*childReadyTimeout + childWaitTimeout)
+		if err != nil {
+			waitchan <- verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("new device manager failed to exit cleanly: %v", err))
+		}
+		close(waitchan)
+		listener.stop()
+	}()
+
+	childName, err := listener.waitForValue(childReadyTimeout)
+	if err != nil {
+		return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("waitForValue(%v) failed: %v", childReadyTimeout, err))
+	}
+	// Check that invoking Delete() succeeds.
+	childName = naming.Join(childName, "device")
+	dmClient := device.DeviceClient(childName)
+	if err := dmClient.Delete(ctx); err != nil {
+		return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("Delete() failed: %v", err))
+	}
+	if err := <-waitchan; err != nil {
+		return err
+	}
+	return nil
+}
+
+// TODO(caprita): Move this to util.go since device_installer is also using it now.
+
+func GenerateScript(workspace string, configSettings []string, envelope *application.Envelope, logs string) error {
+	// TODO(caprita): Remove this snippet of code, it doesn't seem to serve
+	// any purpose.
+	path, err := filepath.EvalSymlinks(os.Args[0])
+	if err != nil {
+		return verror.New(errors.ErrOperationFailed, nil, fmt.Sprintf("EvalSymlinks(%v) failed: %v", os.Args[0], err))
+	}
+
+	if err := os.MkdirAll(logs, 0711); err != nil {
+		return verror.New(errors.ErrOperationFailed, nil, fmt.Sprintf("MkdirAll(%v) failed: %v", logs, err))
+	}
+	stderrLog, stdoutLog := filepath.Join(logs, "STDERR"), filepath.Join(logs, "STDOUT")
+
+	output := "#!/bin/bash\n"
+	output += "if [ -z \"$DEVICE_MANAGER_DONT_REDIRECT_STDOUT_STDERR\" ]; then\n"
+	output += fmt.Sprintf("  TIMESTAMP=$(%s)\n", DateCommand)
+	output += fmt.Sprintf("  exec > %s-$TIMESTAMP 2> %s-$TIMESTAMP\n", stdoutLog, stderrLog)
+	output += "  LOG_TO_STDERR=false\n"
+	output += "else\n"
+	output += "  LOG_TO_STDERR=true\n"
+	output += "fi\n"
+	output += strings.Join(config.QuoteEnv(append(envelope.Env, configSettings...)), " ") + " "
+	// Escape the path to the binary; %q uses Go-syntax escaping, but it's
+	// close enough to Bash that we're using it as an approximation.
+	//
+	// TODO(caprita/rthellend): expose and use shellEscape (from
+	// v.io/x/ref/services/debug/debug/impl.go) instead.
+	output += fmt.Sprintf("exec %q", filepath.Join(workspace, "deviced")) + " "
+	output += fmt.Sprintf("--log_dir=%q ", logs)
+	output += "--logtostderr=${LOG_TO_STDERR} "
+	output += strings.Join(envelope.Args, " ")
+
+	path = filepath.Join(workspace, "deviced.sh")
+	if err := ioutil.WriteFile(path, []byte(output), 0700); err != nil {
+		return verror.New(errors.ErrOperationFailed, nil, fmt.Sprintf("WriteFile(%v) failed: %v", path, err))
+	}
+	return nil
+}
+
+func (s *deviceService) updateDeviceManager(ctx *context.T) error {
+	if len(s.config.Origin) == 0 {
+		return verror.New(errors.ErrUpdateNoOp, ctx)
+	}
+	envelope, err := fetchEnvelope(ctx, s.config.Origin)
+	if err != nil {
+		return err
+	}
+	if envelope.Title != application.DeviceManagerTitle {
+		return verror.New(errors.ErrAppTitleMismatch, ctx, fmt.Sprintf("app title mismatch. Got %q, expected %q.", envelope.Title, application.DeviceManagerTitle))
+	}
+	// Read and merge persistent args, if present.
+	if args, err := loadPersistentArgs(s.config.Root); err == nil {
+		envelope.Args = append(envelope.Args, args...)
+	}
+	if s.config.Envelope != nil && reflect.DeepEqual(envelope, s.config.Envelope) {
+		return verror.New(errors.ErrUpdateNoOp, ctx)
+	}
+	// Create new workspace.
+	workspace := filepath.Join(s.config.Root, "device-manager", generateVersionDirName())
+	perm := os.FileMode(0700)
+	if err := os.MkdirAll(workspace, perm); err != nil {
+		return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("MkdirAll(%v, %v) failed: %v", workspace, perm, err))
+	}
+
+	deferrer := func() {
+		CleanupDir(ctx, workspace, "")
+	}
+	defer func() {
+		if deferrer != nil {
+			deferrer()
+		}
+	}()
+
+	// Populate the new workspace with a device manager binary.
+	// TODO(caprita): match identical binaries on binary signature
+	// rather than binary object name.
+	sameBinary := s.config.Envelope != nil && envelope.Binary.File == s.config.Envelope.Binary.File
+	if sameBinary {
+		if err := LinkSelf(workspace, "deviced"); err != nil {
+			return err
+		}
+	} else {
+		if err := downloadBinary(ctx, envelope.Publisher, &envelope.Binary, workspace, "deviced"); err != nil {
+			return err
+		}
+	}
+
+	// Populate the new workspace with a device manager script.
+	configSettings, err := s.config.Save(envelope)
+	if err != nil {
+		return verror.New(errors.ErrOperationFailed, ctx, err)
+	}
+
+	logs := filepath.Join(s.config.Root, "device-manager", "logs")
+	if err := GenerateScript(workspace, configSettings, envelope, logs); err != nil {
+		return err
+	}
+
+	if err := s.testDeviceManager(ctx, workspace, envelope); err != nil {
+		return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("testDeviceManager failed: %v", err))
+	}
+
+	if err := UpdateLink(filepath.Join(workspace, "deviced.sh"), s.config.CurrentLink); err != nil {
+		return err
+	}
+
+	if s.restartHandler != nil {
+		s.restartHandler()
+	}
+	v23.GetAppCycle(ctx).Stop(ctx)
+	deferrer = nil
+	return nil
+}
+
+func (*deviceService) Install(ctx *context.T, _ rpc.ServerCall, _ string, _ device.Config, _ application.Packages) (string, error) {
+	return "", verror.New(errors.ErrInvalidSuffix, ctx)
+}
+
+func (*deviceService) Run(ctx *context.T, _ rpc.ServerCall) error {
+	return verror.New(errors.ErrInvalidSuffix, ctx)
+}
+
+func (s *deviceService) Revert(ctx *context.T, _ rpc.ServerCall) error {
+	if s.config.Previous == "" {
+		return verror.New(errors.ErrUpdateNoOp, ctx, fmt.Sprintf("Revert failed: no previous version"))
+	}
+	updatingState := s.updating
+	if updatingState.testAndSetUpdating() {
+		return verror.New(errors.ErrOperationInProgress, ctx, fmt.Sprintf("Revert failed: already in progress"))
+	}
+	err := s.revertDeviceManager(ctx)
+	if err != nil {
+		updatingState.unsetUpdating()
+	}
+	return err
+}
+
+func (*deviceService) Instantiate(ctx *context.T, _ device.ApplicationInstantiateServerCall) (string, error) {
+	return "", verror.New(errors.ErrInvalidSuffix, ctx)
+}
+
+func (*deviceService) Delete(ctx *context.T, _ rpc.ServerCall) error {
+	v23.GetAppCycle(ctx).Stop(ctx)
+	return nil
+}
+
+func (s *deviceService) Kill(ctx *context.T, _ rpc.ServerCall, _ time.Duration) error {
+	if s.restartHandler != nil {
+		s.restartHandler()
+	}
+	v23.GetAppCycle(ctx).Stop(ctx)
+	return nil
+}
+
+func (*deviceService) Uninstall(ctx *context.T, _ rpc.ServerCall) error {
+	return verror.New(errors.ErrInvalidSuffix, ctx)
+}
+
+func (s *deviceService) Update(ctx *context.T, _ rpc.ServerCall) error {
+	ctx, cancel := context.WithTimeout(ctx, rpcContextLongTimeout)
+	defer cancel()
+
+	updatingState := s.updating
+	if updatingState.testAndSetUpdating() {
+		return verror.New(errors.ErrOperationInProgress, ctx)
+	}
+
+	err := s.updateDeviceManager(ctx)
+	if err != nil {
+		updatingState.unsetUpdating()
+	}
+	return err
+}
+
+func (*deviceService) UpdateTo(*context.T, rpc.ServerCall, string) error {
+	// TODO(jsimsa): Implement.
+	return nil
+}
+
+func (s *deviceService) SetPermissions(_ *context.T, _ rpc.ServerCall, perms access.Permissions, version string) error {
+	d := PermsDir(s.disp.config)
+	return s.disp.permsStore.Set(d, perms, version)
+}
+
+func (s *deviceService) GetPermissions(*context.T, rpc.ServerCall) (perms access.Permissions, version string, err error) {
+	d := PermsDir(s.disp.config)
+	return s.disp.permsStore.Get(d)
+}
+
+// TODO(rjkroege): Make it possible for users on the same system to also
+// associate their accounts with their identities.
+func (s *deviceService) AssociateAccount(_ *context.T, _ rpc.ServerCall, identityNames []string, accountName string) error {
+	if accountName == "" {
+		return s.uat.DisassociateSystemAccountForBlessings(identityNames)
+	}
+	// TODO(rjkroege): Optionally verify here that the required uname is a valid.
+	return s.uat.AssociateSystemAccountForBlessings(identityNames, accountName)
+}
+
+func (s *deviceService) ListAssociations(ctx *context.T, call rpc.ServerCall) (associations []device.Association, err error) {
+	// Temporary code. Dump this.
+	if ctx.V(2) {
+		b, r := security.RemoteBlessingNames(ctx, call.Security())
+		ctx.Infof("ListAssociations given blessings: %v\n", b)
+		if len(r) > 0 {
+			ctx.Infof("ListAssociations rejected blessings: %v\n", r)
+		}
+	}
+	return s.uat.AllBlessingSystemAssociations()
+}
+
+func (*deviceService) Debug(*context.T, rpc.ServerCall) (string, error) {
+	return "Not implemented", nil
+}
+
+func (s *deviceService) Status(*context.T, rpc.ServerCall) (device.Status, error) {
+	state := device.InstanceStateRunning
+	if s.updating.updating {
+		state = device.InstanceStateUpdating
+	}
+	// Extract the version from the current link path.
+	//
+	// TODO(caprita): make the version available in the device's directory.
+	scriptPath, err := filepath.EvalSymlinks(s.config.CurrentLink)
+	if err != nil {
+		return nil, err
+	}
+	dir := filepath.Dir(scriptPath)
+	versionDir := filepath.Base(dir)
+	if versionDir == "." {
+		versionDir = "base"
+	}
+	return device.StatusDevice{Value: device.DeviceStatus{
+		State:   state,
+		Version: versionDir,
+	}}, nil
+}
+
+func (s *deviceService) TidyNow(ctx *context.T, _ rpc.ServerCall) error {
+	ec := make(chan error)
+	s.tidying <- tidyRequests{ctx: ctx, bc: ec}
+	return <-ec
+}
diff --git a/services/device/deviced/internal/impl/dispatcher.go b/services/device/deviced/internal/impl/dispatcher.go
new file mode 100644
index 0000000..f02dec4
--- /dev/null
+++ b/services/device/deviced/internal/impl/dispatcher.go
@@ -0,0 +1,413 @@
+// 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 impl
+
+import (
+	"fmt"
+	"os"
+	"path"
+	"path/filepath"
+	"strings"
+	"sync"
+
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/security/access"
+	"v.io/v23/services/device"
+	"v.io/v23/services/pprof"
+	libstats "v.io/v23/services/stats"
+	"v.io/v23/vdl"
+	"v.io/v23/vdlroot/signature"
+	"v.io/v23/verror"
+	"v.io/x/ref"
+	"v.io/x/ref/services/agent/keymgr"
+	s_device "v.io/x/ref/services/device"
+	"v.io/x/ref/services/device/internal/config"
+	"v.io/x/ref/services/device/internal/errors"
+	"v.io/x/ref/services/internal/logreaderlib"
+	"v.io/x/ref/services/internal/pathperms"
+)
+
+// internalState wraps state shared between different device manager
+// invocations.
+type internalState struct {
+	callback       *callbackState
+	updating       *updatingState
+	securityAgent  *securityAgentState
+	restartHandler func()
+	stats          *stats
+	testMode       bool
+	// runner is responsible for running app instances.
+	runner *appRunner
+	// tidying is the automatic state tidying subsystem.
+	tidying chan<- tidyRequests
+}
+
+// dispatcher holds the state of the device manager dispatcher.
+type dispatcher struct {
+	// internal holds the state that persists across RPC method invocations.
+	internal *internalState
+	// config holds the device manager's (immutable) configuration state.
+	config *config.State
+	// dispatcherMutex is a lock for coordinating concurrent access to some
+	// dispatcher methods.
+	mu sync.RWMutex
+	// TODO(rjkroege): Consider moving this inside internal.
+	uat        BlessingSystemAssociationStore
+	permsStore *pathperms.PathStore
+	// Namespace
+	mtAddress string // The address of the local mounttable.
+}
+
+var _ rpc.Dispatcher = (*dispatcher)(nil)
+
+const (
+	appsSuffix   = "apps"
+	deviceSuffix = "device"
+	configSuffix = "cfg"
+
+	// TODO(caprita): the value of pkgPath corresponds to the previous
+	// package where the error ids were defined.  Updating error ids needs
+	// to be carefully coordinated between clients and servers, so we should
+	// do it when we settle on the final location for these error
+	// definitions.
+	pkgPath = "v.io/x/ref/services/device/internal/impl"
+)
+
+var (
+	errInvalidConfig          = verror.Register(pkgPath+".errInvalidConfig", verror.NoRetry, "{1:}{2:} invalid config {3}{:_}")
+	errCantCreateAccountStore = verror.Register(pkgPath+".errCantCreateAccountStore", verror.NoRetry, "{1:}{2:} cannot create persistent store for identity to system account associations{:_}")
+	errCantCreateAppWatcher   = verror.Register(pkgPath+".errCantCreateAppWatcher", verror.NoRetry, "{1:}{2:} cannot create app status watcher{:_}")
+	errNewAgentFailed         = verror.Register(pkgPath+".errNewAgentFailed", verror.NoRetry, "{1:}{2:} NewAgent() failed{:_}")
+)
+
+// NewDispatcher is the device manager dispatcher factory.  It returns a new
+// dispatcher as well as a shutdown function, to be called when the dispatcher
+// is no longer needed.
+func NewDispatcher(ctx *context.T, config *config.State, mtAddress string, testMode bool, restartHandler func(), permStore *pathperms.PathStore) (rpc.Dispatcher, func(), error) {
+	if err := config.Validate(); err != nil {
+		return nil, nil, verror.New(errInvalidConfig, ctx, config, err)
+	}
+	uat, err := NewBlessingSystemAssociationStore(config.Root)
+	if err != nil {
+		return nil, nil, verror.New(errCantCreateAccountStore, ctx, err)
+	}
+	InitSuidHelper(ctx, config.Helper)
+	d := &dispatcher{
+		internal: &internalState{
+			callback:       newCallbackState(config.Name),
+			updating:       newUpdatingState(),
+			restartHandler: restartHandler,
+			stats:          newStats(),
+			testMode:       testMode,
+			tidying:        newTidyingDaemon(ctx, config.Root),
+		},
+		config:     config,
+		uat:        uat,
+		permsStore: permStore,
+		mtAddress:  mtAddress,
+	}
+
+	// If we're in 'security agent mode', set up the key manager agent.
+	if path := os.Getenv(ref.EnvAgentPath); len(path) > 0 {
+		// TODO(caprita): Making the decision to be in agent mode based
+		// on the presence of this env var is approximate for two
+		// reasons:
+		//
+		// 1. the device manager may have its agent path set using a
+		// config key/value instead of an env var.
+		//
+		// 2. the device manager may have this env var set, but a
+		// higher-precedence setting like the V23_CREDENTIALS env var
+		// may still have configured the device manager to operate in
+		// non-agent mode.
+		//
+		// We ought to hook into the logic inside rt/security.go when
+		// deciding if we're in agent mode, and figuring out what path
+		// the agent socket is at.
+		if km, err := keymgr.NewKeyManager(path); err != nil {
+			return nil, nil, verror.New(errNewAgentFailed, ctx, err)
+		} else {
+			d.internal.securityAgent = &securityAgentState{
+				keyMgr: km,
+			}
+		}
+	} else if len(os.Getenv(ref.EnvAgentEndpoint)) > 0 {
+		// This code path is deprecated in favor of socket agent
+		// connection.
+		if keyMgrAgent, err := keymgr.NewAgent(); err != nil {
+			return nil, nil, verror.New(errNewAgentFailed, ctx, err)
+		} else {
+			d.internal.securityAgent = &securityAgentState{
+				keyMgrAgent: keyMgrAgent,
+			}
+		}
+	}
+	runner := &appRunner{
+		callback:       d.internal.callback,
+		securityAgent:  d.internal.securityAgent,
+		appServiceName: naming.Join(d.config.Name, appsSuffix),
+		mtAddress:      d.mtAddress,
+		stats:          d.internal.stats,
+	}
+	d.internal.runner = runner
+	reap, err := newReaper(ctx, config.Root, runner)
+	if err != nil {
+		return nil, nil, verror.New(errCantCreateAppWatcher, ctx, err)
+	}
+	runner.reap = reap
+
+	if testMode {
+		return &testModeDispatcher{d}, reap.shutdown, nil
+	}
+	return d, reap.shutdown, nil
+}
+
+// Logging invoker that logs any error messages before returning.
+func newLoggingInvoker(ctx *context.T, obj interface{}) (rpc.Invoker, error) {
+	if invoker, ok := obj.(rpc.Invoker); ok {
+		return &loggingInvoker{invoker: invoker}, nil
+	}
+	invoker, err := rpc.ReflectInvoker(obj)
+	if err != nil {
+		ctx.Errorf("rpc.ReflectInvoker returned error: %v", err)
+		return nil, err
+	}
+	return &loggingInvoker{invoker: invoker}, nil
+}
+
+type loggingInvoker struct {
+	invoker rpc.Invoker
+}
+
+func (l *loggingInvoker) Prepare(ctx *context.T, method string, numArgs int) (argptrs []interface{}, tags []*vdl.Value, err error) {
+	argptrs, tags, err = l.invoker.Prepare(ctx, method, numArgs)
+	if err != nil {
+		ctx.Errorf("Prepare(%s %d) returned error: %v", method, numArgs, err)
+	}
+	return
+}
+
+func (l *loggingInvoker) Invoke(ctx *context.T, call rpc.StreamServerCall, method string, argptrs []interface{}) (results []interface{}, err error) {
+	results, err = l.invoker.Invoke(ctx, call, method, argptrs)
+	if err != nil {
+		ctx.Errorf("Invoke(method:%s argptrs:%v) returned error: %v", method, argptrs, err)
+	}
+	return
+}
+
+func (l *loggingInvoker) Signature(ctx *context.T, call rpc.ServerCall) ([]signature.Interface, error) {
+	sig, err := l.invoker.Signature(ctx, call)
+	if err != nil {
+		ctx.Errorf("Signature returned error: %v", err)
+	}
+	return sig, err
+}
+
+func (l *loggingInvoker) MethodSignature(ctx *context.T, call rpc.ServerCall, method string) (signature.Method, error) {
+	methodSig, err := l.invoker.MethodSignature(ctx, call, method)
+	if err != nil {
+		ctx.Errorf("MethodSignature(%s) returned error: %v", method, err)
+	}
+	return methodSig, err
+}
+
+func (l *loggingInvoker) Globber() *rpc.GlobState {
+	return l.invoker.Globber()
+}
+
+// DISPATCHER INTERFACE IMPLEMENTATION
+func (d *dispatcher) Lookup(ctx *context.T, suffix string) (interface{}, security.Authorizer, error) {
+	invoker, auth, err := d.internalLookup(suffix)
+	if err != nil {
+		return nil, nil, err
+	}
+	loggingInvoker, err := newLoggingInvoker(ctx, invoker)
+	if err != nil {
+		return nil, nil, err
+	}
+	return loggingInvoker, auth, nil
+}
+
+func newTestableHierarchicalAuth(testMode bool, rootDir, childDir string, get pathperms.PermsGetter) (security.Authorizer, error) {
+	if testMode {
+		// In test mode, the device manager will not be able to read the
+		// Permissions, because they were signed with the key of the real device
+		// manager. It's not a problem because the testModeDispatcher overrides the
+		// authorizer anyway.
+		return nil, nil
+	}
+	return pathperms.NewHierarchicalAuthorizer(rootDir, childDir, get, nil)
+}
+
+func (d *dispatcher) internalLookup(suffix string) (interface{}, security.Authorizer, error) {
+	components := strings.Split(suffix, "/")
+	for i := 0; i < len(components); i++ {
+		if len(components[i]) == 0 {
+			components = append(components[:i], components[i+1:]...)
+			i--
+		}
+	}
+
+	// TODO(rjkroege): Permit the root Permissions to diverge for the device and
+	// app sub-namespaces of the device manager after claiming.
+	auth, err := newTestableHierarchicalAuth(d.internal.testMode, PermsDir(d.config), PermsDir(d.config), d.permsStore)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	if len(components) == 0 {
+		return rpc.ChildrenGlobberInvoker(deviceSuffix, appsSuffix), auth, nil
+	}
+	// The implementation of the device manager is split up into several
+	// invokers, which are instantiated depending on the receiver name
+	// prefix.
+	switch components[0] {
+	case deviceSuffix:
+		receiver := device.DeviceServer(&deviceService{
+			callback:       d.internal.callback,
+			updating:       d.internal.updating,
+			restartHandler: d.internal.restartHandler,
+			config:         d.config,
+			disp:           d,
+			uat:            d.uat,
+			securityAgent:  d.internal.securityAgent,
+			tidying:        d.internal.tidying,
+		})
+		return receiver, auth, nil
+	case appsSuffix:
+		// Requests to apps/*/*/*/logs are handled locally by LogFileService.
+		// Requests to apps/*/*/*/pprof are proxied to the apps' __debug/pprof object.
+		// Requests to apps/*/*/*/stats are proxied to the apps' __debug/stats object.
+		// Everything else is handled by the Application server.
+		if len(components) >= 5 {
+			appInstanceDir, err := instanceDir(d.config.Root, components[1:4])
+			if err != nil {
+				return nil, nil, err
+			}
+			switch kind := components[4]; kind {
+			case "logs":
+				logsDir := filepath.Join(appInstanceDir, "logs")
+				suffix := naming.Join(components[5:]...)
+				appSpecificAuthorizer, err := newAppSpecificAuthorizer(auth, d.config, components[1:], d.permsStore)
+				if err != nil {
+					return nil, nil, err
+				}
+				return logreaderlib.NewLogFileService(logsDir, suffix), appSpecificAuthorizer, nil
+			case "pprof", "stats":
+				info, err := loadInstanceInfo(nil, appInstanceDir)
+				if err != nil {
+					return nil, nil, err
+				}
+				if !instanceStateIs(appInstanceDir, device.InstanceStateRunning) {
+					return nil, nil, verror.New(errors.ErrInvalidSuffix, nil)
+				}
+				var desc []rpc.InterfaceDesc
+				switch kind {
+				case "pprof":
+					desc = pprof.PProfServer(nil).Describe__()
+				case "stats":
+					desc = libstats.StatsServer(nil).Describe__()
+				}
+				suffix := naming.Join("__debug", naming.Join(components[4:]...))
+				remote := naming.JoinAddressName(info.AppCycleMgrName, suffix)
+
+				// Use hierarchical auth with debugacls under debug access.
+				appSpecificAuthorizer, err := newAppSpecificAuthorizer(auth, d.config, components[1:], d.permsStore)
+				if err != nil {
+					return nil, nil, err
+				}
+				return newProxyInvoker(remote, access.Debug, desc), appSpecificAuthorizer, nil
+			}
+		}
+		receiver := device.ApplicationServer(&appService{
+			config:     d.config,
+			suffix:     components[1:],
+			uat:        d.uat,
+			permsStore: d.permsStore,
+			runner:     d.internal.runner,
+			stats:      d.internal.stats,
+		})
+		appSpecificAuthorizer, err := newAppSpecificAuthorizer(auth, d.config, components[1:], d.permsStore)
+		if err != nil {
+			return nil, nil, err
+		}
+		return receiver, appSpecificAuthorizer, nil
+	case configSuffix:
+		if len(components) != 2 {
+			return nil, nil, verror.New(errors.ErrInvalidSuffix, nil)
+		}
+		receiver := s_device.ConfigServer(&configService{
+			callback: d.internal.callback,
+			suffix:   components[1],
+		})
+		// The nil authorizer ensures that only principals blessed by
+		// the device manager can talk back to it.  All apps started by
+		// the device manager should fall in that category.
+		//
+		// TODO(caprita,rjkroege): We should further refine this, by
+		// only allowing the app to update state referring to itself
+		// (and not other apps).
+		return receiver, nil, nil
+	default:
+		return nil, nil, verror.New(errors.ErrInvalidSuffix, nil)
+	}
+}
+
+// testModeDispatcher is a wrapper around the real dispatcher. It returns the
+// exact same object as the real dispatcher, but the authorizer only allows
+// calls to "device".Delete().
+type testModeDispatcher struct {
+	realDispatcher rpc.Dispatcher
+}
+
+func (d *testModeDispatcher) Lookup(ctx *context.T, suffix string) (interface{}, security.Authorizer, error) {
+	obj, _, err := d.realDispatcher.Lookup(ctx, suffix)
+	return obj, d, err
+}
+
+func (testModeDispatcher) Authorize(ctx *context.T, call security.Call) error {
+	if call.Suffix() == deviceSuffix && call.Method() == "Delete" {
+		ctx.Infof("testModeDispatcher.Authorize: Allow %q.%s()", call.Suffix(), call.Method())
+		return nil
+	}
+	ctx.Infof("testModeDispatcher.Authorize: Reject %q.%s()", call.Suffix(), call.Method())
+	return verror.New(errors.ErrInvalidSuffix, nil)
+}
+
+func newAppSpecificAuthorizer(sec security.Authorizer, config *config.State, suffix []string, getter pathperms.PermsGetter) (security.Authorizer, error) {
+	// TODO(rjkroege): This does not support <appname>.Start() to start all
+	// instances. Correct this.
+
+	// If we are attempting a method invocation against "apps/", we use the root
+	// Permissions.
+	if len(suffix) == 0 || len(suffix) == 1 {
+		return sec, nil
+	}
+	// Otherwise, we require a per-installation and per-instance Permissions file.
+	if len(suffix) == 2 {
+		p, err := installationDirCore(suffix, config.Root)
+		if err != nil {
+			return nil, verror.New(errors.ErrOperationFailed, nil, fmt.Sprintf("newAppSpecificAuthorizer failed: %v", err))
+		}
+		return pathperms.NewHierarchicalAuthorizer(PermsDir(config), path.Join(p, "acls"), getter, nil)
+	}
+	// Use the special debugacls for instance/logs, instance/pprof, instance/stats.
+	if len(suffix) > 3 && (suffix[3] == "logs" || suffix[3] == "pprof" || suffix[3] == "stats") {
+		p, err := instanceDir(config.Root, suffix[0:3])
+		if err != nil {
+			return nil, verror.New(errors.ErrOperationFailed, nil, fmt.Sprintf("newAppSpecificAuthorizer failed: %v", err))
+		}
+		return pathperms.NewHierarchicalAuthorizer(PermsDir(config), path.Join(p, "debugacls"), getter, nil)
+	}
+
+	p, err := instanceDir(config.Root, suffix[0:3])
+	if err != nil {
+		return nil, verror.New(errors.ErrOperationFailed, nil, fmt.Sprintf("newAppSpecificAuthorizer failed: %v", err))
+	}
+	return pathperms.NewHierarchicalAuthorizer(PermsDir(config), path.Join(p, "acls"), getter, nil)
+}
diff --git a/services/device/deviced/internal/impl/globsuid/args_darwin_test.go b/services/device/deviced/internal/impl/globsuid/args_darwin_test.go
new file mode 100644
index 0000000..8925295
--- /dev/null
+++ b/services/device/deviced/internal/impl/globsuid/args_darwin_test.go
@@ -0,0 +1,10 @@
+// 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 globsuid_test
+
+const (
+	testUserName        = "_uucp"
+	anotherTestUserName = "_lp"
+)
diff --git a/services/device/deviced/internal/impl/globsuid/args_linux_test.go b/services/device/deviced/internal/impl/globsuid/args_linux_test.go
new file mode 100644
index 0000000..e7e4aa6
--- /dev/null
+++ b/services/device/deviced/internal/impl/globsuid/args_linux_test.go
@@ -0,0 +1,10 @@
+// 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 globsuid_test
+
+const (
+	testUserName        = "uucp"
+	anotherTestUserName = "lp"
+)
diff --git a/services/device/deviced/internal/impl/globsuid/doc.go b/services/device/deviced/internal/impl/globsuid/doc.go
new file mode 100644
index 0000000..4e210e6
--- /dev/null
+++ b/services/device/deviced/internal/impl/globsuid/doc.go
@@ -0,0 +1,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.
+
+package globsuid
+
+// Test code for the device manager's globbing, suidhelper and
+// signature matching functionality.
diff --git a/services/device/deviced/internal/impl/globsuid/glob_test.go b/services/device/deviced/internal/impl/globsuid/glob_test.go
new file mode 100644
index 0000000..24af25e
--- /dev/null
+++ b/services/device/deviced/internal/impl/globsuid/glob_test.go
@@ -0,0 +1,113 @@
+// 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 globsuid_test
+
+import (
+	"path"
+	"syscall"
+	"testing"
+
+	"v.io/x/ref/services/device/deviced/internal/impl/utiltest"
+	"v.io/x/ref/services/device/deviced/internal/versioning"
+	"v.io/x/ref/services/internal/servicetest"
+	"v.io/x/ref/test"
+)
+
+func TestDeviceManagerGlobAndDebug(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	sh, deferFn := servicetest.CreateShellAndMountTable(t, ctx, nil)
+	defer deferFn()
+
+	// Set up mock application and binary repositories.
+	envelope, cleanup := utiltest.StartMockRepos(t, ctx)
+	defer cleanup()
+
+	root, cleanup := servicetest.SetupRootDir(t, "devicemanager")
+	defer cleanup()
+	if err := versioning.SaveCreatorInfo(ctx, root); err != nil {
+		t.Fatal(err)
+	}
+
+	// Create a script wrapping the test target that implements suidhelper.
+	helperPath := utiltest.GenerateSuidHelperScript(t, root)
+
+	// Set up the device manager.  Since we won't do device manager updates,
+	// don't worry about its application envelope and current link.
+	dmh := servicetest.RunCommand(t, sh, nil, utiltest.DeviceManager, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
+	pid := servicetest.ReadPID(t, dmh)
+	defer syscall.Kill(pid, syscall.SIGINT)
+
+	// Create the local server that the app uses to let us know it's ready.
+	pingCh, cleanup := utiltest.SetupPingServer(t, ctx)
+	defer cleanup()
+
+	// Create the envelope for the first version of the app.
+	*envelope = utiltest.EnvelopeFromShell(sh, nil, utiltest.App, "google naps", 0, 0, "appV1")
+
+	// Device must be claimed before applications can be installed.
+	utiltest.ClaimDevice(t, ctx, "claimable", "dm", "mydevice", utiltest.NoPairingToken)
+	// Install the app.
+	appID := utiltest.InstallApp(t, ctx)
+	install1ID := path.Base(appID)
+
+	// Start an instance of the app.
+	instance1ID := utiltest.LaunchApp(t, ctx, appID)
+	defer utiltest.TerminateApp(t, ctx, appID, instance1ID)
+
+	// Wait until the app pings us that it's ready.
+	pingCh.WaitForPingArgs(t)
+
+	app2ID := utiltest.InstallApp(t, ctx)
+	install2ID := path.Base(app2ID)
+
+	// Base name of argv[0] that the app should have when it executes
+	// It will be path.Base(envelope.Title + "@" + envelope.Binary.File + "/app").
+	// Note the suffix, which ensures that the result is always "app" at the moment.
+	// Someday in future we may remove that and have binary names that reflect the app name.
+	const appName = "app"
+
+	testcases := []utiltest.GlobTestVector{
+		{"dm", "...", []string{
+			"",
+			"apps",
+			"apps/google naps",
+			"apps/google naps/" + install1ID,
+			"apps/google naps/" + install1ID + "/" + instance1ID,
+			"apps/google naps/" + install1ID + "/" + instance1ID + "/logs",
+			"apps/google naps/" + install1ID + "/" + instance1ID + "/logs/STDERR-<timestamp>",
+			"apps/google naps/" + install1ID + "/" + instance1ID + "/logs/STDOUT-<timestamp>",
+			"apps/google naps/" + install1ID + "/" + instance1ID + "/logs/" + appName + ".INFO",
+			"apps/google naps/" + install1ID + "/" + instance1ID + "/logs/" + appName + ".<*>.INFO.<timestamp>",
+			"apps/google naps/" + install1ID + "/" + instance1ID + "/pprof",
+			"apps/google naps/" + install1ID + "/" + instance1ID + "/stats",
+			"apps/google naps/" + install1ID + "/" + instance1ID + "/stats/rpc",
+			"apps/google naps/" + install1ID + "/" + instance1ID + "/stats/system",
+			"apps/google naps/" + install1ID + "/" + instance1ID + "/stats/system/start-time-rfc1123",
+			"apps/google naps/" + install1ID + "/" + instance1ID + "/stats/system/start-time-unix",
+			"apps/google naps/" + install2ID,
+			"device",
+		}},
+		{"dm/apps", "*", []string{"google naps"}},
+		{"dm/apps/google naps", "*", []string{install1ID, install2ID}},
+		{"dm/apps/google naps/" + install1ID, "*", []string{instance1ID}},
+		{"dm/apps/google naps/" + install1ID + "/" + instance1ID, "*", []string{"logs", "pprof", "stats"}},
+		{"dm/apps/google naps/" + install1ID + "/" + instance1ID + "/logs", "*", []string{
+			"STDERR-<timestamp>",
+			"STDOUT-<timestamp>",
+			appName + ".INFO",
+			appName + ".<*>.INFO.<timestamp>",
+		}},
+		{"dm/apps/google naps/" + install1ID + "/" + instance1ID + "/stats/system", "start-time*", []string{"start-time-rfc1123", "start-time-unix"}},
+	}
+
+	res := utiltest.NewGlobTestRegexHelper(appName)
+
+	utiltest.VerifyGlob(t, ctx, appName, testcases, res)
+	utiltest.VerifyLog(t, ctx, "dm", "apps/google naps", install1ID, instance1ID, "logs", "*")
+	utiltest.VerifyStatsValues(t, ctx, "dm", "apps/google naps", install1ID, instance1ID, "stats/system/start-time*")
+	utiltest.VerifyPProfCmdLine(t, ctx, appName, "dm", "apps/google naps", install1ID, instance1ID, "pprof")
+}
diff --git a/services/device/deviced/internal/impl/globsuid/impl_test.go b/services/device/deviced/internal/impl/globsuid/impl_test.go
new file mode 100644
index 0000000..783a31f
--- /dev/null
+++ b/services/device/deviced/internal/impl/globsuid/impl_test.go
@@ -0,0 +1,19 @@
+// 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 globsuid_test
+
+import (
+	"testing"
+
+	"v.io/x/ref/services/device/deviced/internal/impl/utiltest"
+)
+
+func TestMain(m *testing.M) {
+	utiltest.TestMainImpl(m)
+}
+
+func TestSuidHelper(t *testing.T) {
+	utiltest.TestSuidHelperImpl(t)
+}
diff --git a/services/device/deviced/internal/impl/globsuid/signature_match_test.go b/services/device/deviced/internal/impl/globsuid/signature_match_test.go
new file mode 100644
index 0000000..8dbd380
--- /dev/null
+++ b/services/device/deviced/internal/impl/globsuid/signature_match_test.go
@@ -0,0 +1,161 @@
+// 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 globsuid_test
+
+import (
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"syscall"
+	"testing"
+
+	"v.io/v23"
+	"v.io/v23/naming"
+	"v.io/v23/security"
+	"v.io/v23/services/application"
+	"v.io/v23/services/device"
+	"v.io/v23/services/repository"
+	"v.io/v23/verror"
+	"v.io/x/ref/services/device/deviced/internal/impl/utiltest"
+	"v.io/x/ref/services/device/deviced/internal/versioning"
+	"v.io/x/ref/services/device/internal/errors"
+	"v.io/x/ref/services/internal/binarylib"
+	"v.io/x/ref/services/internal/servicetest"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/testutil"
+)
+
+func TestDownloadSignatureMatch(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	rg := testutil.NewRandGenerator(t.Logf)
+
+	sh, deferFn := servicetest.CreateShellAndMountTable(t, ctx, nil)
+	defer deferFn()
+
+	binaryVON := "binary"
+	pkgVON := naming.Join(binaryVON, "testpkg")
+	defer utiltest.StartRealBinaryRepository(t, ctx, binaryVON)()
+
+	up := rg.RandomBytes(rg.RandomIntn(5 << 20))
+	mediaInfo := repository.MediaInfo{Type: "application/octet-stream"}
+	sig, err := binarylib.Upload(ctx, naming.Join(binaryVON, "testbinary"), up, mediaInfo)
+	if err != nil {
+		t.Fatalf("Upload(%v) failed:%v", binaryVON, err)
+	}
+
+	// Upload packages for this application
+	tmpdir, err := ioutil.TempDir("", "test-package-")
+	if err != nil {
+		t.Fatalf("ioutil.TempDir failed: %v", err)
+	}
+	defer os.RemoveAll(tmpdir)
+	pkgContents := rg.RandomBytes(rg.RandomIntn(5 << 20))
+	if err := ioutil.WriteFile(filepath.Join(tmpdir, "pkg.txt"), pkgContents, 0600); err != nil {
+		t.Fatalf("ioutil.WriteFile failed: %v", err)
+	}
+	pkgSig, err := binarylib.UploadFromDir(ctx, pkgVON, tmpdir)
+	if err != nil {
+		t.Fatalf("binarylib.UploadFromDir failed: %v", err)
+	}
+
+	// Start the application repository
+	envelope, serverStop := utiltest.StartApplicationRepository(ctx)
+	defer serverStop()
+
+	root, cleanup := servicetest.SetupRootDir(t, "devicemanager")
+	defer cleanup()
+	if err := versioning.SaveCreatorInfo(ctx, root); err != nil {
+		t.Fatal(err)
+	}
+
+	// Create a script wrapping the test target that implements suidhelper.
+	helperPath := utiltest.GenerateSuidHelperScript(t, root)
+
+	// Set up the device manager.  Since we won't do device manager updates,
+	// don't worry about its application envelope and current link.
+	dmh := servicetest.RunCommand(t, sh, nil, utiltest.DeviceManager, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
+	pid := servicetest.ReadPID(t, dmh)
+	defer syscall.Kill(pid, syscall.SIGINT)
+	utiltest.ClaimDevice(t, ctx, "claimable", "dm", "mydevice", utiltest.NoPairingToken)
+
+	p := v23.GetPrincipal(ctx)
+	publisher, err := p.BlessSelf("publisher")
+	if err != nil {
+		t.Fatalf("Failed to generate publisher blessings:%v", err)
+	}
+	*envelope = application.Envelope{
+		Binary: application.SignedFile{
+			File:      naming.Join(binaryVON, "testbinary"),
+			Signature: *sig,
+		},
+		Publisher: publisher,
+		Packages: map[string]application.SignedFile{
+			"pkg": application.SignedFile{
+				File:      pkgVON,
+				Signature: *pkgSig,
+			},
+		},
+	}
+
+	// Using the publisher should fail, because blessing "publisher" is not covered by the
+	// trusted roots of the device manager's principal
+	if _, err := utiltest.AppStub().Install(ctx, utiltest.MockApplicationRepoName, device.Config{}, nil); verror.ErrorID(err) != errors.ErrOperationFailed.ID {
+		t.Fatalf("Unexpected error installing app:%v (expected ErrOperationFailed)", err)
+	}
+
+	// Changing the publisher blessing to one that is covered by the DM roots, should
+	// allow the app installation to succeed.
+	envelope.Publisher, err = p.Bless(p.PublicKey(), p.BlessingStore().Default(), "publisher", security.UnconstrainedUse())
+	if err != nil {
+		t.Fatalf("Failed to generate trusted publisher blessings: %v", err)
+	}
+
+	if _, err := utiltest.AppStub().Install(ctx, utiltest.MockApplicationRepoName, device.Config{}, nil); err != nil {
+		t.Fatalf("Failed to Install app:%v", err)
+	}
+
+	// Verify that when the binary is corrupted, signature verification fails.
+	up[0] = up[0] ^ 0xFF
+	if err := binarylib.Delete(ctx, naming.Join(binaryVON, "testbinary")); err != nil {
+		t.Fatalf("Delete(%v) failed:%v", binaryVON, err)
+	}
+	if _, err := binarylib.Upload(ctx, naming.Join(binaryVON, "testbinary"), up, mediaInfo); err != nil {
+		t.Fatalf("Upload(%v) failed:%v", binaryVON, err)
+	}
+	if _, err := utiltest.AppStub().Install(ctx, utiltest.MockApplicationRepoName, device.Config{}, nil); verror.ErrorID(err) != errors.ErrOperationFailed.ID {
+		t.Fatalf("Failed to verify signature mismatch for binary:%v. Got errorid=%v[%v], want errorid=%v", binaryVON, verror.ErrorID(err), err, errors.ErrOperationFailed.ID)
+	}
+
+	// Restore the binary and verify that installation succeeds.
+	up[0] = up[0] ^ 0xFF
+	if err := binarylib.Delete(ctx, naming.Join(binaryVON, "testbinary")); err != nil {
+		t.Fatalf("Delete(%v) failed:%v", binaryVON, err)
+	}
+	if _, err := binarylib.Upload(ctx, naming.Join(binaryVON, "testbinary"), up, mediaInfo); err != nil {
+		t.Fatalf("Upload(%v) failed:%v", binaryVON, err)
+	}
+	if _, err := utiltest.AppStub().Install(ctx, utiltest.MockApplicationRepoName, device.Config{}, nil); err != nil {
+		t.Fatalf("Failed to Install app:%v", err)
+	}
+
+	// Verify that when the package contents are corrupted, signature verification fails.
+	pkgContents[0] = pkgContents[0] ^ 0xFF
+	if err := binarylib.Delete(ctx, pkgVON); err != nil {
+		t.Fatalf("Delete(%v) failed:%v", pkgVON, err)
+	}
+	if err := os.Remove(filepath.Join(tmpdir, "pkg.txt")); err != nil {
+		t.Fatalf("Remove(%v) failed:%v", filepath.Join(tmpdir, "pkg.txt"), err)
+	}
+	if err := ioutil.WriteFile(filepath.Join(tmpdir, "pkg.txt"), pkgContents, 0600); err != nil {
+		t.Fatalf("ioutil.WriteFile failed: %v", err)
+	}
+	if _, err = binarylib.UploadFromDir(ctx, pkgVON, tmpdir); err != nil {
+		t.Fatalf("binarylib.UploadFromDir failed: %v", err)
+	}
+	if _, err := utiltest.AppStub().Install(ctx, utiltest.MockApplicationRepoName, device.Config{}, nil); verror.ErrorID(err) != errors.ErrOperationFailed.ID {
+		t.Fatalf("Failed to verify signature mismatch for package:%v", pkgVON)
+	}
+}
diff --git a/services/device/deviced/internal/impl/globsuid/suid_test.go b/services/device/deviced/internal/impl/globsuid/suid_test.go
new file mode 100644
index 0000000..a0b2c6c
--- /dev/null
+++ b/services/device/deviced/internal/impl/globsuid/suid_test.go
@@ -0,0 +1,203 @@
+// 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 globsuid_test
+
+import (
+	"flag"
+	"fmt"
+	"os"
+	"reflect"
+	"syscall"
+	"testing"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/security"
+	"v.io/v23/security/access"
+	"v.io/v23/services/device"
+	"v.io/v23/verror"
+	"v.io/x/ref/services/device/deviced/internal/impl"
+	"v.io/x/ref/services/device/deviced/internal/impl/utiltest"
+	"v.io/x/ref/services/device/deviced/internal/versioning"
+	"v.io/x/ref/services/internal/servicetest"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/testutil"
+)
+
+var mockIsSetuid = flag.Bool("mocksetuid", false, "set flag to pretend to have a helper with setuid permissions")
+
+func possiblyMockIsSetuid(ctx *context.T, fileStat os.FileInfo) bool {
+	ctx.VI(2).Infof("Mock isSetuid is reporting: %v", *mockIsSetuid)
+	return *mockIsSetuid
+}
+
+func init() {
+	impl.IsSetuid = possiblyMockIsSetuid
+}
+
+func TestAppWithSuidHelper(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	// Identity provider used to ensure that all processes recognize each
+	// others' blessings.
+	idp := testutil.NewIDProvider("root")
+	if err := idp.Bless(v23.GetPrincipal(ctx), "self"); err != nil {
+		t.Fatal(err)
+	}
+
+	sh, deferFn := servicetest.CreateShellAndMountTable(t, ctx, nil)
+	defer deferFn()
+
+	// Set up mock application and binary repositories.
+	envelope, cleanup := utiltest.StartMockRepos(t, ctx)
+	defer cleanup()
+
+	root, cleanup := servicetest.SetupRootDir(t, "devicemanager")
+	defer cleanup()
+	if err := versioning.SaveCreatorInfo(ctx, root); err != nil {
+		t.Fatal(err)
+	}
+
+	selfCtx := ctx
+	otherCtx := utiltest.CtxWithNewPrincipal(t, selfCtx, idp, "other")
+
+	// Create a script wrapping the test target that implements suidhelper.
+	helperPath := utiltest.GenerateSuidHelperScript(t, root)
+
+	dmh := servicetest.RunCommand(t, sh, nil, utiltest.DeviceManager, "-mocksetuid", "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
+	pid := servicetest.ReadPID(t, dmh)
+	defer syscall.Kill(pid, syscall.SIGINT)
+	defer utiltest.VerifyNoRunningProcesses(t)
+	// Claim the devicemanager with selfCtx as root/self/alice
+	utiltest.ClaimDevice(t, selfCtx, "claimable", "dm", "alice", utiltest.NoPairingToken)
+
+	deviceStub := device.DeviceClient("dm/device")
+
+	// Create the local server that the app uses to tell us which system
+	// name the device manager wished to run it as.
+	pingCh, cleanup := utiltest.SetupPingServer(t, ctx)
+	defer cleanup()
+
+	// Create an envelope for a first version of the app.
+	*envelope = utiltest.EnvelopeFromShell(sh, []string{utiltest.TestEnvVarName + "=env-var"}, utiltest.App, "google naps", 0, 0, fmt.Sprintf("--%s=flag-val-envelope", utiltest.TestFlagName), "appV1")
+
+	// Install and start the app as root/self.
+	appID := utiltest.InstallApp(t, selfCtx)
+
+	ctx.VI(2).Infof("Validate that the created app has the right permission lists.")
+	perms, _, err := utiltest.AppStub(appID).GetPermissions(selfCtx)
+	if err != nil {
+		t.Fatalf("GetPermissions on appID: %v failed %v", appID, err)
+	}
+	expected := make(access.Permissions)
+	for _, tag := range access.AllTypicalTags() {
+		expected[string(tag)] = access.AccessList{In: []security.BlessingPattern{"root/self/$"}}
+	}
+	if got, want := perms.Normalize(), expected.Normalize(); !reflect.DeepEqual(got, want) {
+		t.Errorf("got %#v, expected %#v", got, want)
+	}
+
+	// Start an instance of the app but this time it should fail: we do not
+	// have an associated uname for the invoking identity.
+	utiltest.LaunchAppExpectError(t, selfCtx, appID, verror.ErrNoAccess.ID)
+
+	// Create an association for selfCtx
+	if err := deviceStub.AssociateAccount(selfCtx, []string{"root/self"}, testUserName); err != nil {
+		t.Fatalf("AssociateAccount failed %v", err)
+	}
+
+	instance1ID := utiltest.LaunchApp(t, selfCtx, appID)
+	pingCh.VerifyPingArgs(t, testUserName, "flag-val-envelope", "env-var") // Wait until the app pings us that it's ready.
+	utiltest.TerminateApp(t, selfCtx, appID, instance1ID)
+
+	ctx.VI(2).Infof("other attempting to run an app without access. Should fail.")
+	utiltest.LaunchAppExpectError(t, otherCtx, appID, verror.ErrNoAccess.ID)
+
+	// Self will now let other also install apps.
+	if err := deviceStub.AssociateAccount(selfCtx, []string{"root/other"}, testUserName); err != nil {
+		t.Fatalf("AssociateAccount failed %v", err)
+	}
+	// Add Start to the AccessList list for root/other.
+	newAccessList, _, err := deviceStub.GetPermissions(selfCtx)
+	if err != nil {
+		t.Fatalf("GetPermissions failed %v", err)
+	}
+	newAccessList.Add("root/other", string(access.Write))
+	if err := deviceStub.SetPermissions(selfCtx, newAccessList, ""); err != nil {
+		t.Fatalf("SetPermissions failed %v", err)
+	}
+
+	// With the introduction of per installation and per instance AccessLists,
+	// while other now has administrator permissions on the device manager,
+	// other doesn't have execution permissions for the app. So this will
+	// fail.
+	ctx.VI(2).Infof("other attempting to run an app still without access. Should fail.")
+	utiltest.LaunchAppExpectError(t, otherCtx, appID, verror.ErrNoAccess.ID)
+
+	// But self can give other permissions  to start applications.
+	ctx.VI(2).Infof("self attempting to give other permission to start %s", appID)
+	newAccessList, _, err = utiltest.AppStub(appID).GetPermissions(selfCtx)
+	if err != nil {
+		t.Fatalf("GetPermissions on appID: %v failed %v", appID, err)
+	}
+	newAccessList.Add("root/other", string(access.Read))
+	if err = utiltest.AppStub(appID).SetPermissions(selfCtx, newAccessList, ""); err != nil {
+		t.Fatalf("SetPermissions on appID: %v failed: %v", appID, err)
+	}
+
+	ctx.VI(2).Infof("other attempting to run an app with access. Should succeed.")
+	instance2ID := utiltest.LaunchApp(t, otherCtx, appID)
+	pingCh.VerifyPingArgs(t, testUserName, "flag-val-envelope", "env-var") // Wait until the app pings us that it's ready.
+
+	ctx.VI(2).Infof("Validate that created instance has the right permissions.")
+	expected = make(access.Permissions)
+	for _, tag := range access.AllTypicalTags() {
+		expected[string(tag)] = access.AccessList{In: []security.BlessingPattern{"root/other/$"}}
+	}
+	perms, _, err = utiltest.AppStub(appID, instance2ID).GetPermissions(selfCtx)
+	if err != nil {
+		t.Fatalf("GetPermissions on instance %v/%v failed: %v", appID, instance2ID, err)
+	}
+	if got, want := perms.Normalize(), expected.Normalize(); !reflect.DeepEqual(got, want) {
+		t.Errorf("got %#v, expected %#v ", got, want)
+	}
+
+	// Shutdown the app.
+	utiltest.KillApp(t, otherCtx, appID, instance2ID)
+
+	ctx.VI(2).Infof("Verify that Run with the same systemName works.")
+	utiltest.RunApp(t, otherCtx, appID, instance2ID)
+	pingCh.VerifyPingArgs(t, testUserName, "flag-val-envelope", "env-var") // Wait until the app pings us that it's ready.
+	utiltest.KillApp(t, otherCtx, appID, instance2ID)
+
+	ctx.VI(2).Infof("Verify that other can install and run applications.")
+	otherAppID := utiltest.InstallApp(t, otherCtx)
+
+	ctx.VI(2).Infof("other attempting to run an app that other installed. Should succeed.")
+	instance4ID := utiltest.LaunchApp(t, otherCtx, otherAppID)
+	pingCh.VerifyPingArgs(t, testUserName, "flag-val-envelope", "env-var") // Wait until the app pings us that it's ready.
+
+	// Clean up.
+	utiltest.TerminateApp(t, otherCtx, otherAppID, instance4ID)
+
+	// Change the associated system name.
+	if err := deviceStub.AssociateAccount(selfCtx, []string{"root/other"}, anotherTestUserName); err != nil {
+		t.Fatalf("AssociateAccount failed %v", err)
+	}
+
+	ctx.VI(2).Infof("Show that Run with a different systemName fails.")
+	utiltest.RunAppExpectError(t, otherCtx, appID, instance2ID, verror.ErrNoAccess.ID)
+
+	// Clean up.
+	utiltest.DeleteApp(t, otherCtx, appID, instance2ID)
+
+	ctx.VI(2).Infof("Show that Start with different systemName works.")
+	instance3ID := utiltest.LaunchApp(t, otherCtx, appID)
+	pingCh.VerifyPingArgs(t, anotherTestUserName, "flag-val-envelope", "env-var") // Wait until the app pings us that it's ready.
+
+	// Clean up.
+	utiltest.TerminateApp(t, otherCtx, appID, instance3ID)
+}
diff --git a/services/device/deviced/internal/impl/helper_manager.go b/services/device/deviced/internal/impl/helper_manager.go
new file mode 100644
index 0000000..45829d0
--- /dev/null
+++ b/services/device/deviced/internal/impl/helper_manager.go
@@ -0,0 +1,183 @@
+// 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 impl
+
+import (
+	"fmt"
+	"io"
+	"os"
+	"os/exec"
+	"os/user"
+	"strconv"
+
+	"v.io/v23/context"
+	"v.io/v23/security"
+	"v.io/v23/verror"
+	"v.io/x/ref/services/device/internal/errors"
+)
+
+type suidHelperState struct {
+	dmUser     string // user that the device manager is running as
+	helperPath string // path to the suidhelper binary
+}
+
+var suidHelper *suidHelperState
+
+func InitSuidHelper(ctx *context.T, helperPath string) {
+	if suidHelper != nil || helperPath == "" {
+		return
+	}
+
+	u, err := user.Current()
+	if err != nil {
+		ctx.Panicf("devicemanager has no current user: %v", err)
+	}
+	suidHelper = &suidHelperState{
+		dmUser:     u.Username,
+		helperPath: helperPath,
+	}
+}
+
+func (s suidHelperState) getCurrentUser() string {
+	return s.dmUser
+}
+
+// terminatePid sends a SIGKILL to the target pid
+func (s suidHelperState) terminatePid(ctx *context.T, pid int, stdout, stderr io.Writer) error {
+	if err := s.internalModalOp(ctx, stdout, stderr, "--kill", strconv.Itoa(pid)); err != nil {
+		return fmt.Errorf("devicemanager's invocation of suidhelper to kill pid %v failed: %v", pid, err)
+	}
+	return nil
+}
+
+func DeleteFileTree(ctx *context.T, dirOrFile string, stdout, stderr io.Writer) error {
+	return suidHelper.deleteFileTree(ctx, dirOrFile, stdout, stderr)
+}
+
+// deleteFileTree deletes a file or directory
+func (s suidHelperState) deleteFileTree(ctx *context.T, dirOrFile string, stdout, stderr io.Writer) error {
+	if err := s.internalModalOp(ctx, stdout, stderr, "--rm", dirOrFile); err != nil {
+		return fmt.Errorf("devicemanager's invocation of suidhelper delete %v failed: %v", dirOrFile, err)
+	}
+	return nil
+}
+
+// chown files or directories
+func (s suidHelperState) chownTree(ctx *context.T, username string, dirOrFile string, stdout, stderr io.Writer) error {
+	args := []string{"--chown", "--username", username, dirOrFile}
+
+	if err := s.internalModalOp(ctx, stdout, stderr, args...); err != nil {
+		return fmt.Errorf("devicemanager's invocation of suidhelper chown %v failed: %v", dirOrFile, err)
+	}
+	return nil
+}
+
+type suidAppCmdArgs struct {
+	// args to helper
+	targetUser, progname, workspace, logdir, binpath string
+	// fields in exec.Cmd
+	env            []string
+	stdout, stderr io.Writer
+	dir            string
+	// arguments passed to app
+	appArgs []string
+}
+
+// getAppCmd produces an exec.Cmd that can be used to start an app
+func (s suidHelperState) getAppCmd(ctx *context.T, a *suidAppCmdArgs) (*exec.Cmd, error) {
+	if a.targetUser == "" || a.progname == "" || a.binpath == "" || a.workspace == "" || a.logdir == "" {
+		return nil, fmt.Errorf("Invalid args passed to getAppCmd: %+v", a)
+	}
+
+	cmd := exec.Command(s.helperPath)
+
+	switch yes, err := s.suidhelperEnabled(ctx, a.targetUser); {
+	case err != nil:
+		return nil, err
+	case yes:
+		cmd.Args = append(cmd.Args, "--username", a.targetUser)
+	case !yes:
+		cmd.Args = append(cmd.Args, "--username", a.targetUser, "--dryrun")
+	}
+
+	cmd.Args = append(cmd.Args, "--progname", a.progname)
+	cmd.Args = append(cmd.Args, "--workspace", a.workspace)
+	cmd.Args = append(cmd.Args, "--logdir", a.logdir)
+
+	cmd.Args = append(cmd.Args, "--run", a.binpath)
+	cmd.Args = append(cmd.Args, "--")
+
+	cmd.Args = append(cmd.Args, a.appArgs...)
+
+	cmd.Env = a.env
+	cmd.Stdout = a.stdout
+	cmd.Stderr = a.stderr
+	cmd.Dir = a.dir
+
+	return cmd, nil
+}
+
+// internalModalOp is a convenience routine containing the common part of all
+// modal operations. Only other routines implementing specific suidhelper operations
+// (like terminatePid and deleteFileTree) should call this directly.
+func (s suidHelperState) internalModalOp(ctx *context.T, stdout, stderr io.Writer, arg ...string) error {
+	cmd := exec.Command(s.helperPath)
+	cmd.Args = append(cmd.Args, arg...)
+	if stderr != nil {
+		cmd.Stderr = stderr
+	}
+	if stdout != nil {
+		cmd.Stdout = stdout
+	}
+
+	if err := cmd.Run(); err != nil {
+		ctx.Errorf("failed calling helper with args (%v):%v", arg, err)
+		return err
+	}
+	return nil
+}
+
+// IsSetuid is defined like this so we can override its
+// implementation for tests.
+var IsSetuid = func(ctx *context.T, fileStat os.FileInfo) bool {
+	ctx.VI(2).Infof("running the original isSetuid")
+	return fileStat.Mode()&os.ModeSetuid == os.ModeSetuid
+}
+
+// suidhelperEnabled determines if the suidhelper must exist and be
+// setuid to run an application as system user targetUser. If false, the
+// setuidhelper must be invoked with the --dryrun flag to skip making
+// system calls that will fail or provide apps with a trivial
+// priviledge escalation.
+func (s suidHelperState) suidhelperEnabled(ctx *context.T, targetUser string) (bool, error) {
+	helperStat, err := os.Stat(s.helperPath)
+	if err != nil {
+		return false, verror.New(errors.ErrOperationFailed, nil, fmt.Sprintf("Stat(%v) failed: %v. helper is required.", s.helperPath, err))
+	}
+	haveHelper := IsSetuid(ctx, helperStat)
+
+	switch {
+	case haveHelper && s.dmUser != targetUser:
+		return true, nil
+	case haveHelper && s.dmUser == targetUser:
+		return false, verror.New(verror.ErrNoAccess, nil, fmt.Sprintf("suidhelperEnabled failed: %q == %q", s.dmUser, targetUser))
+	default:
+		return false, nil
+	}
+}
+
+// usernameForPrincipal returns the system name that the
+// devicemanager will use to invoke apps.
+// TODO(rjkroege): This code assumes a desktop target and will need
+// to be reconsidered for embedded contexts.
+func (s suidHelperState) usernameForPrincipal(ctx *context.T, call security.Call, uat BlessingSystemAssociationStore) string {
+	identityNames, _ := security.RemoteBlessingNames(ctx, call)
+	systemName, present := uat.SystemAccountForBlessings(identityNames)
+	if present {
+		return systemName
+	} else {
+		return s.dmUser
+	}
+}
diff --git a/services/device/deviced/internal/impl/impl_helper_test.go b/services/device/deviced/internal/impl/impl_helper_test.go
new file mode 100644
index 0000000..f500671
--- /dev/null
+++ b/services/device/deviced/internal/impl/impl_helper_test.go
@@ -0,0 +1,54 @@
+// 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 impl_test
+
+// Separate from impl_test to avoid contributing further to impl_test bloat.
+// TODO(rjkroege): Move all helper-related tests to here.
+
+import (
+	"io/ioutil"
+	"os"
+	"path"
+	"testing"
+
+	"v.io/v23/context"
+	"v.io/x/ref/internal/logger"
+	"v.io/x/ref/services/device/deviced/internal/impl"
+	"v.io/x/ref/services/device/deviced/internal/impl/utiltest"
+)
+
+func TestBaseCleanupDir(t *testing.T) {
+	ctx, _ := context.RootContext()
+	ctx = context.WithLogger(ctx, logger.Global())
+	dir, err := ioutil.TempDir("", "impl_helper_test")
+	if err != nil {
+		t.Fatalf("ioutil.TempDir() failed: %v", err)
+	}
+	defer os.RemoveAll(dir)
+
+	// Setup some files to delete.
+	helperTarget := path.Join(dir, "helper_target")
+	if err := os.MkdirAll(helperTarget, os.FileMode(0700)); err != nil {
+		t.Fatalf("os.MkdirAll(%s) failed: %v", helperTarget, err)
+	}
+
+	nohelperTarget := path.Join(dir, "nohelper_target")
+	if err := os.MkdirAll(nohelperTarget, os.FileMode(0700)); err != nil {
+		t.Fatalf("os.MkdirAll(%s) failed: %v", nohelperTarget, err)
+	}
+
+	// Setup a helper.
+	helper := utiltest.GenerateSuidHelperScript(t, dir)
+
+	impl.BaseCleanupDir(ctx, helperTarget, helper)
+	if _, err := os.Stat(helperTarget); err == nil || os.IsExist(err) {
+		t.Fatalf("%s should be missing but isn't", helperTarget)
+	}
+
+	impl.BaseCleanupDir(ctx, nohelperTarget, "")
+	if _, err := os.Stat(nohelperTarget); err == nil || os.IsExist(err) {
+		t.Fatalf("%s should be missing but isn't", nohelperTarget)
+	}
+}
diff --git a/services/device/deviced/internal/impl/impl_test.go b/services/device/deviced/internal/impl/impl_test.go
new file mode 100644
index 0000000..81fdeba
--- /dev/null
+++ b/services/device/deviced/internal/impl/impl_test.go
@@ -0,0 +1,582 @@
+// 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.
+
+// TODO(rjkroege): Add a more extensive unit test case to exercise AccessList logic.
+
+package impl_test
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strings"
+	"syscall"
+	"testing"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/services/application"
+	"v.io/v23/services/device"
+	"v.io/x/ref"
+	"v.io/x/ref/services/device/deviced/internal/impl"
+	"v.io/x/ref/services/device/deviced/internal/impl/utiltest"
+	"v.io/x/ref/services/device/deviced/internal/installer"
+	"v.io/x/ref/services/device/deviced/internal/versioning"
+	"v.io/x/ref/services/device/internal/config"
+	"v.io/x/ref/services/device/internal/errors"
+	"v.io/x/ref/services/internal/binarylib"
+	"v.io/x/ref/services/internal/servicetest"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/expect"
+	"v.io/x/ref/test/testutil"
+)
+
+func TestMain(m *testing.M) {
+	utiltest.TestMainImpl(m)
+}
+
+// TestSuidHelper is testing boilerplate for suidhelper that does not
+// create a runtime because the suidhelper is not a Vanadium application.
+func TestSuidHelper(t *testing.T) {
+	utiltest.TestSuidHelperImpl(t)
+}
+
+// TODO(rjkroege): generateDeviceManagerScript and generateSuidHelperScript have
+// code similarity that might benefit from refactoring.
+// generateDeviceManagerScript is very similar in behavior to generateScript in
+// device_invoker.go.  However, we chose to re-implement it here for two
+// reasons: (1) avoid making generateScript public; and (2) how the test choses
+// to invoke the device manager subprocess the first time should be independent
+// of how device manager implementation sets up its updated versions.
+func generateDeviceManagerScript(t *testing.T, root string, args, env []string) string {
+	env = impl.VanadiumEnvironment(env)
+	output := "#!/bin/bash\n"
+	output += strings.Join(config.QuoteEnv(env), " ") + " exec "
+	output += strings.Join(args, " ")
+	if err := os.MkdirAll(filepath.Join(root, "factory"), 0755); err != nil {
+		t.Fatalf("MkdirAll failed: %v", err)
+	}
+	// Why pigeons? To show that the name we choose for the initial script
+	// doesn't matter and in particular is independent of how device manager
+	// names its updated version scripts (deviced.sh).
+	path := filepath.Join(root, "factory", "pigeons.sh")
+	if err := ioutil.WriteFile(path, []byte(output), 0755); err != nil {
+		t.Fatalf("WriteFile(%v) failed: %v", path, err)
+	}
+	return path
+}
+
+// TestDeviceManagerUpdateAndRevert makes the device manager go through the
+// motions of updating itself to newer versions (twice), and reverting itself
+// back (twice). It also checks that update and revert fail when they're
+// supposed to. The initial device manager is running 'by hand' via a module
+// command. Further versions are running through the soft link that the device
+// manager itself updates.
+func TestDeviceManagerUpdateAndRevert(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	sh, deferFn := servicetest.CreateShellAndMountTable(t, ctx, v23.GetPrincipal(ctx))
+	defer deferFn()
+
+	// Set up mock application and binary repositories.
+	envelope, cleanup := utiltest.StartMockRepos(t, ctx)
+	defer cleanup()
+
+	root, cleanup := servicetest.SetupRootDir(t, "devicemanager")
+	defer cleanup()
+	if err := versioning.SaveCreatorInfo(ctx, root); err != nil {
+		t.Fatal(err)
+	}
+
+	// Current link does not have to live in the root dir, but it's
+	// convenient to put it there so we have everything in one place.
+	currLink := filepath.Join(root, "current_link")
+
+	// Since the device manager will be restarting, use the
+	// VeyronCredentials environment variable to maintain the same set of
+	// credentials across runs.
+	// Without this, authentication/authorizatin state - such as the blessings
+	// of the device manager and the signatures used for AccessList integrity checks
+	// - will not carry over between updates to the binary, which would not
+	// be reflective of intended use.
+	dmCreds, err := ioutil.TempDir("", "TestDeviceManagerUpdateAndRevert")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(dmCreds)
+	dmEnv := []string{fmt.Sprintf("%v=%v", ref.EnvCredentials, dmCreds)}
+	dmArgs := []string{"factoryDM", root, "unused_helper", utiltest.MockApplicationRepoName, currLink}
+	args, env := sh.ProgramEnvelope(dmEnv, utiltest.DeviceManager, dmArgs...)
+	scriptPathFactory := generateDeviceManagerScript(t, root, args, env)
+
+	if err := os.Symlink(scriptPathFactory, currLink); err != nil {
+		t.Fatalf("Symlink(%q, %q) failed: %v", scriptPathFactory, currLink, err)
+	}
+
+	// We instruct the initial device manager that we run to pause before
+	// stopping its service, so that we get a chance to verify that
+	// attempting an update while another one is ongoing will fail.
+	dmPauseBeforeStopEnv := append(dmEnv, "PAUSE_BEFORE_STOP=1")
+
+	// Start the initial version of the device manager, the so-called
+	// "factory" version. We use the modules-generated command to start it.
+	// We could have also used the scriptPathFactory to start it, but this
+	// demonstrates that the initial device manager could be running by hand
+	// as long as the right initial configuration is passed into the device
+	// manager implementation.
+	dmh := servicetest.RunCommand(t, sh, dmPauseBeforeStopEnv, utiltest.DeviceManager, dmArgs...)
+	defer func() {
+		syscall.Kill(dmh.Pid(), syscall.SIGINT)
+		utiltest.VerifyNoRunningProcesses(t)
+	}()
+
+	servicetest.ReadPID(t, dmh)
+	utiltest.Resolve(t, ctx, "claimable", 1, true)
+	// Brand new device manager must be claimed first.
+	utiltest.ClaimDevice(t, ctx, "claimable", "factoryDM", "mydevice", utiltest.NoPairingToken)
+
+	if v := utiltest.VerifyDeviceState(t, ctx, device.InstanceStateRunning, "factoryDM"); v != "factory" {
+		t.Errorf("Expected factory version, got %v instead", v)
+	}
+
+	// Simulate an invalid envelope in the application repository.
+	*envelope = utiltest.EnvelopeFromShell(sh, dmPauseBeforeStopEnv, utiltest.DeviceManager, "bogus", 0, 0, dmArgs...)
+
+	utiltest.UpdateDeviceExpectError(t, ctx, "factoryDM", errors.ErrAppTitleMismatch.ID)
+	utiltest.RevertDeviceExpectError(t, ctx, "factoryDM", errors.ErrUpdateNoOp.ID)
+
+	// Set up a second version of the device manager. The information in the
+	// envelope will be used by the device manager to stage the next
+	// version.
+	*envelope = utiltest.EnvelopeFromShell(sh, dmEnv, utiltest.DeviceManager, application.DeviceManagerTitle, 0, 0, "v2DM")
+	utiltest.UpdateDevice(t, ctx, "factoryDM")
+
+	// Current link should have been updated to point to v2.
+	evalLink := func() string {
+		path, err := filepath.EvalSymlinks(currLink)
+		if err != nil {
+			t.Fatalf("EvalSymlinks(%v) failed: %v", currLink, err)
+		}
+		return path
+	}
+	scriptPathV2 := evalLink()
+	if scriptPathFactory == scriptPathV2 {
+		t.Fatalf("current link didn't change")
+	}
+	v2 := utiltest.VerifyDeviceState(t, ctx, device.InstanceStateUpdating, "factoryDM")
+
+	utiltest.UpdateDeviceExpectError(t, ctx, "factoryDM", errors.ErrOperationInProgress.ID)
+
+	dmh.CloseStdin()
+
+	dmh.Expect("restart handler")
+	dmh.Expect("factoryDM terminated")
+	dmh.Shutdown(os.Stderr, os.Stderr)
+
+	// A successful update means the device manager has stopped itself.  We
+	// relaunch it from the current link.
+	utiltest.ResolveExpectNotFound(t, ctx, "v2DM", false) // Ensure a clean slate.
+
+	dmh = servicetest.RunCommand(t, sh, dmEnv, utiltest.ExecScript, currLink)
+
+	servicetest.ReadPID(t, dmh)
+	utiltest.Resolve(t, ctx, "v2DM", 1, true) // Current link should have been launching v2.
+
+	// Try issuing an update without changing the envelope in the
+	// application repository: this should fail, and current link should be
+	// unchanged.
+	utiltest.UpdateDeviceExpectError(t, ctx, "v2DM", errors.ErrUpdateNoOp.ID)
+	if evalLink() != scriptPathV2 {
+		t.Fatalf("script changed")
+	}
+
+	// Try issuing an update with a binary that has a different major version
+	// number. It should fail.
+	utiltest.ResolveExpectNotFound(t, ctx, "v2.5DM", false) // Ensure a clean slate.
+	*envelope = utiltest.EnvelopeFromShell(sh, dmEnv, utiltest.DeviceManagerV10, application.DeviceManagerTitle, 0, 0, "v2.5DM")
+	utiltest.UpdateDeviceExpectError(t, ctx, "v2DM", errors.ErrOperationFailed.ID)
+
+	if evalLink() != scriptPathV2 {
+		t.Fatalf("script changed")
+	}
+
+	// Create a third version of the device manager and issue an update.
+	*envelope = utiltest.EnvelopeFromShell(sh, dmEnv, utiltest.DeviceManager, application.DeviceManagerTitle, 0, 0, "v3DM")
+	utiltest.UpdateDevice(t, ctx, "v2DM")
+
+	scriptPathV3 := evalLink()
+	if scriptPathV3 == scriptPathV2 {
+		t.Fatalf("current link didn't change")
+	}
+
+	dmh.Expect("restart handler")
+	dmh.Expect("v2DM terminated")
+
+	dmh.Shutdown(os.Stderr, os.Stderr)
+
+	utiltest.ResolveExpectNotFound(t, ctx, "v3DM", false) // Ensure a clean slate.
+
+	// Re-lanuch the device manager from current link.  We instruct the
+	// device manager to pause before stopping its server, so that we can
+	// verify that a second revert fails while a revert is in progress.
+	dmh = servicetest.RunCommand(t, sh, dmPauseBeforeStopEnv, utiltest.ExecScript, currLink)
+
+	servicetest.ReadPID(t, dmh)
+	utiltest.Resolve(t, ctx, "v3DM", 1, true) // Current link should have been launching v3.
+	v3 := utiltest.VerifyDeviceState(t, ctx, device.InstanceStateRunning, "v3DM")
+	if v2 == v3 {
+		t.Fatalf("version didn't change")
+	}
+
+	// Revert the device manager to its previous version (v2).
+	utiltest.RevertDevice(t, ctx, "v3DM")
+	utiltest.RevertDeviceExpectError(t, ctx, "v3DM", errors.ErrOperationInProgress.ID) // Revert already in progress.
+	dmh.CloseStdin()
+	dmh.Expect("restart handler")
+	dmh.Expect("v3DM terminated")
+	if evalLink() != scriptPathV2 {
+		t.Fatalf("current link was not reverted correctly")
+	}
+	dmh.Shutdown(os.Stderr, os.Stderr)
+
+	utiltest.ResolveExpectNotFound(t, ctx, "v2DM", false) // Ensure a clean slate.
+
+	dmh = servicetest.RunCommand(t, sh, dmEnv, utiltest.ExecScript, currLink)
+	servicetest.ReadPID(t, dmh)
+	utiltest.Resolve(t, ctx, "v2DM", 1, true) // Current link should have been launching v2.
+
+	// Revert the device manager to its previous version (factory).
+	utiltest.RevertDevice(t, ctx, "v2DM")
+	dmh.Expect("restart handler")
+	dmh.Expect("v2DM terminated")
+	if evalLink() != scriptPathFactory {
+		t.Fatalf("current link was not reverted correctly")
+	}
+	dmh.Shutdown(os.Stderr, os.Stderr)
+
+	utiltest.ResolveExpectNotFound(t, ctx, "factoryDM", false) // Ensure a clean slate.
+
+	dmh = servicetest.RunCommand(t, sh, dmEnv, utiltest.ExecScript, currLink)
+	servicetest.ReadPID(t, dmh)
+	utiltest.Resolve(t, ctx, "factoryDM", 1, true) // Current link should have been launching factory version.
+	utiltest.ShutdownDevice(t, ctx, "factoryDM")
+	dmh.Expect("factoryDM terminated")
+	dmh.ExpectEOF()
+
+	// Re-launch the device manager, to exercise the behavior of Stop.
+	utiltest.ResolveExpectNotFound(t, ctx, "factoryDM", false) // Ensure a clean slate.
+	dmh = servicetest.RunCommand(t, sh, dmEnv, utiltest.ExecScript, currLink)
+	servicetest.ReadPID(t, dmh)
+	utiltest.Resolve(t, ctx, "factoryDM", 1, true)
+	utiltest.KillDevice(t, ctx, "factoryDM")
+	dmh.Expect("restart handler")
+	dmh.Expect("factoryDM terminated")
+	dmh.ExpectEOF()
+}
+
+type simpleRW chan []byte
+
+func (s simpleRW) Write(p []byte) (n int, err error) {
+	s <- p
+	return len(p), nil
+}
+func (s simpleRW) Read(p []byte) (n int, err error) {
+	return copy(p, <-s), nil
+}
+
+// TestDeviceManagerInstallation verifies the 'self install' and 'uninstall'
+// functionality of the device manager: it runs SelfInstall in a child process,
+// then runs the executable from the soft link that the installation created.
+// This should bring up a functioning device manager.  In the end it runs
+// Uninstall and verifies that the installation is gone.
+func TestDeviceManagerInstallation(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	sh, deferFn := servicetest.CreateShellAndMountTable(t, ctx, nil)
+	defer deferFn()
+	testDir, cleanup := servicetest.SetupRootDir(t, "devicemanager")
+	defer cleanup()
+	// No need to call SaveCreatorInfo() here because that's part of SelfInstall below
+
+	// Create a script wrapping the test target that implements suidhelper.
+	suidHelperPath := utiltest.GenerateSuidHelperScript(t, testDir)
+	// Create a dummy script mascarading as the security agent.
+	agentPath := utiltest.GenerateAgentScript(t, testDir)
+	initHelperPath := ""
+
+	// Create an 'envelope' for the device manager that we can pass to the
+	// installer, to ensure that the device manager that the installer
+	// configures can run.
+	dmargs, dmenv := sh.ProgramEnvelope(nil, utiltest.DeviceManager, "dm")
+	dmDir := filepath.Join(testDir, "dm")
+	// TODO(caprita): Add test logic when initMode = true.
+	singleUser, sessionMode, initMode := true, true, false
+	if err := installer.SelfInstall(ctx, dmDir, suidHelperPath, agentPath, initHelperPath, "", singleUser, sessionMode, initMode, dmargs[1:], dmenv, os.Stderr, os.Stdout); err != nil {
+		t.Fatalf("SelfInstall failed: %v", err)
+	}
+
+	utiltest.ResolveExpectNotFound(t, ctx, "dm", false)
+	// Start the device manager.
+	stdout := make(simpleRW, 100)
+	defer os.Setenv(utiltest.RedirectEnv, os.Getenv(utiltest.RedirectEnv))
+	os.Setenv(utiltest.RedirectEnv, "1")
+	if err := installer.Start(ctx, dmDir, os.Stderr, stdout); err != nil {
+		t.Fatalf("Start failed: %v", err)
+	}
+	dms := expect.NewSession(t, stdout, servicetest.ExpectTimeout)
+	servicetest.ReadPID(t, dms)
+	utiltest.ClaimDevice(t, ctx, "claimable", "dm", "mydevice", utiltest.NoPairingToken)
+	utiltest.RevertDeviceExpectError(t, ctx, "dm", errors.ErrUpdateNoOp.ID) // No previous version available.
+
+	// Stop the device manager.
+	if err := installer.Stop(ctx, dmDir, os.Stderr, os.Stdout); err != nil {
+		t.Fatalf("Stop failed: %v", err)
+	}
+	dms.Expect("dm terminated")
+
+	// Uninstall.
+	if err := installer.Uninstall(ctx, dmDir, suidHelperPath, os.Stderr, os.Stdout); err != nil {
+		t.Fatalf("Uninstall failed: %v", err)
+	}
+	// Ensure that the installation is gone.
+	if files, err := ioutil.ReadDir(dmDir); err != nil || len(files) > 0 {
+		var finfo []string
+		for _, f := range files {
+			finfo = append(finfo, f.Name())
+		}
+		t.Fatalf("ReadDir returned (%v, %v)", err, finfo)
+	}
+}
+
+// TODO(caprita): We need better test coverage for how updating/reverting apps
+// affects the package configured for the app.
+func TestDeviceManagerPackages(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	sh, deferFn := servicetest.CreateShellAndMountTable(t, ctx, nil)
+	defer deferFn()
+
+	// Set up mock application and binary repositories.
+	envelope, cleanup := utiltest.StartMockRepos(t, ctx)
+	defer cleanup()
+
+	binaryVON := "realbin"
+	defer utiltest.StartRealBinaryRepository(t, ctx, binaryVON)()
+
+	// upload package to binary repository
+	tmpdir, err := ioutil.TempDir("", "test-package-")
+	if err != nil {
+		t.Fatalf("ioutil.TempDir failed: %v", err)
+	}
+	defer os.RemoveAll(tmpdir)
+	createFile := func(name, contents string) {
+		if err := ioutil.WriteFile(filepath.Join(tmpdir, name), []byte(contents), 0600); err != nil {
+			t.Fatalf("ioutil.WriteFile failed: %v", err)
+		}
+	}
+	createFile("hello.txt", "Hello World!")
+	if _, err := binarylib.UploadFromDir(ctx, naming.Join(binaryVON, "testpkg"), tmpdir); err != nil {
+		t.Fatalf("binarylib.UploadFromDir failed: %v", err)
+	}
+	createAndUpload := func(von, contents string) {
+		createFile("tempfile", contents)
+		if _, err := binarylib.UploadFromFile(ctx, naming.Join(binaryVON, von), filepath.Join(tmpdir, "tempfile")); err != nil {
+			t.Fatalf("binarylib.UploadFromFile failed: %v", err)
+		}
+	}
+	createAndUpload("testfile", "Goodbye World!")
+	createAndUpload("leftshark", "Left shark")
+	createAndUpload("rightshark", "Right shark")
+	createAndUpload("beachball", "Beach ball")
+
+	root, cleanup := servicetest.SetupRootDir(t, "devicemanager")
+	defer cleanup()
+	if err := versioning.SaveCreatorInfo(ctx, root); err != nil {
+		t.Fatal(err)
+	}
+
+	// Create a script wrapping the test target that implements suidhelper.
+	helperPath := utiltest.GenerateSuidHelperScript(t, root)
+
+	// Set up the device manager.  Since we won't do device manager updates,
+	// don't worry about its application envelope and current link.
+	dmh := servicetest.RunCommand(t, sh, nil, utiltest.DeviceManager, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
+	pid := servicetest.ReadPID(t, dmh)
+	defer syscall.Kill(pid, syscall.SIGINT)
+	defer utiltest.VerifyNoRunningProcesses(t)
+
+	// Create the local server that the app uses to let us know it's ready.
+	pingCh, cleanup := utiltest.SetupPingServer(t, ctx)
+	defer cleanup()
+
+	// Create the envelope for the first version of the app.
+	*envelope = utiltest.EnvelopeFromShell(sh, nil, utiltest.App, "google naps", 0, 0, "appV1")
+	envelope.Packages = map[string]application.SignedFile{
+		"test": application.SignedFile{
+			File: "realbin/testpkg",
+		},
+		"test2": application.SignedFile{
+			File: "realbin/testfile",
+		},
+		"shark": application.SignedFile{
+			File: "realbin/leftshark",
+		},
+	}
+
+	// These are install-time overrides for packages.
+	// Specifically, we override the 'shark' package and add a new
+	// 'ball' package on top of what's specified in the envelope.
+	packages := application.Packages{
+		"shark": application.SignedFile{
+			File: "realbin/rightshark",
+		},
+		"ball": application.SignedFile{
+			File: "realbin/beachball",
+		},
+	}
+	// Device must be claimed before apps can be installed.
+	utiltest.ClaimDevice(t, ctx, "claimable", "dm", "mydevice", utiltest.NoPairingToken)
+	// Install the app.
+	appID := utiltest.InstallApp(t, ctx, packages)
+
+	// Start an instance of the app.
+	instance1ID := utiltest.LaunchApp(t, ctx, appID)
+	defer utiltest.TerminateApp(t, ctx, appID, instance1ID)
+
+	// Wait until the app pings us that it's ready.
+	pingCh.WaitForPingArgs(t)
+
+	for _, c := range []struct {
+		path, content string
+	}{
+		{
+			filepath.Join("test", "hello.txt"),
+			"Hello World!",
+		},
+		{
+			"test2",
+			"Goodbye World!",
+		},
+		{
+			"shark",
+			"Right shark",
+		},
+		{
+			"ball",
+			"Beach ball",
+		},
+	} {
+		// Ask the app to cat the file.
+		file := filepath.Join("packages", c.path)
+		name := "appV1"
+		content, err := utiltest.Cat(ctx, name, file)
+		if err != nil {
+			t.Errorf("utiltest.Cat(%q, %q) failed: %v", name, file, err)
+		}
+		if expected := c.content; content != expected {
+			t.Errorf("unexpected content: expected %q, got %q", expected, content)
+		}
+	}
+}
+
+func listAndVerifyAssociations(t *testing.T, ctx *context.T, stub device.DeviceClientMethods, expected []device.Association) {
+	assocs, err := stub.ListAssociations(ctx)
+	if err != nil {
+		t.Fatalf("ListAssociations failed %v", err)
+	}
+	utiltest.CompareAssociations(t, assocs, expected)
+}
+
+// TODO(rjkroege): Verify that associations persist across restarts once
+// permanent storage is added.
+func TestAccountAssociation(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	sh, deferFn := servicetest.CreateShellAndMountTable(t, ctx, nil)
+	defer deferFn()
+
+	root, cleanup := servicetest.SetupRootDir(t, "devicemanager")
+	defer cleanup()
+	if err := versioning.SaveCreatorInfo(ctx, root); err != nil {
+		t.Fatal(err)
+	}
+
+	// By default, the two processes (selfCtx and octx) will have blessings
+	// generated based on the username/machine name running this process.
+	// Since these blessings will appear in AccessLists, give them
+	// recognizable names.
+	idp := testutil.NewIDProvider("root")
+	selfCtx := utiltest.CtxWithNewPrincipal(t, ctx, idp, "self")
+	otherCtx := utiltest.CtxWithNewPrincipal(t, selfCtx, idp, "other")
+	// Both the "external" processes must recognize the root mounttable's
+	// blessings, otherwise they will not talk to it.
+	for _, c := range []*context.T{selfCtx, otherCtx} {
+		v23.GetPrincipal(c).AddToRoots(v23.GetPrincipal(ctx).BlessingStore().Default())
+	}
+
+	dmh := servicetest.RunCommand(t, sh, nil, utiltest.DeviceManager, "dm", root, "unused_helper", "unused_app_repo_name", "unused_curr_link")
+	pid := servicetest.ReadPID(t, dmh)
+	defer syscall.Kill(pid, syscall.SIGINT)
+	defer utiltest.VerifyNoRunningProcesses(t)
+
+	// Attempt to list associations on the device manager without having
+	// claimed it.
+	if list, err := device.DeviceClient("claimable").ListAssociations(otherCtx); err == nil {
+		t.Fatalf("ListAssociations should fail on unclaimed device manager but did not: (%v, %v)", list, err)
+	}
+
+	// self claims the device manager.
+	utiltest.ClaimDevice(t, selfCtx, "claimable", "dm", "alice", utiltest.NoPairingToken)
+
+	ctx.VI(2).Info("Verify that associations start out empty.")
+	deviceStub := device.DeviceClient("dm/device")
+	listAndVerifyAssociations(t, selfCtx, deviceStub, []device.Association(nil))
+
+	if err := deviceStub.AssociateAccount(selfCtx, []string{"root/self", "root/other"}, "alice_system_account"); err != nil {
+		t.Fatalf("ListAssociations failed %v", err)
+	}
+	ctx.VI(2).Info("Added association should appear.")
+	listAndVerifyAssociations(t, selfCtx, deviceStub, []device.Association{
+		{
+			"root/self",
+			"alice_system_account",
+		},
+		{
+			"root/other",
+			"alice_system_account",
+		},
+	})
+
+	if err := deviceStub.AssociateAccount(selfCtx, []string{"root/self", "root/other"}, "alice_other_account"); err != nil {
+		t.Fatalf("AssociateAccount failed %v", err)
+	}
+	ctx.VI(2).Info("Change the associations and the change should appear.")
+	listAndVerifyAssociations(t, selfCtx, deviceStub, []device.Association{
+		{
+			"root/self",
+			"alice_other_account",
+		},
+		{
+			"root/other",
+			"alice_other_account",
+		},
+	})
+
+	if err := deviceStub.AssociateAccount(selfCtx, []string{"root/other"}, ""); err != nil {
+		t.Fatalf("AssociateAccount failed %v", err)
+	}
+	ctx.VI(2).Info("Verify that we can remove an association.")
+	listAndVerifyAssociations(t, selfCtx, deviceStub, []device.Association{
+		{
+			"root/self",
+			"alice_other_account",
+		},
+	})
+}
diff --git a/services/device/deviced/internal/impl/instance_reaping.go b/services/device/deviced/internal/impl/instance_reaping.go
new file mode 100644
index 0000000..d787567
--- /dev/null
+++ b/services/device/deviced/internal/impl/instance_reaping.go
@@ -0,0 +1,351 @@
+// 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 impl
+
+import (
+	"os"
+	"path/filepath"
+	"sync"
+	"syscall"
+	"time"
+
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/services/device"
+	libstats "v.io/v23/services/stats"
+	"v.io/v23/vdl"
+	"v.io/v23/verror"
+)
+
+const (
+	AppcycleReconciliation = "V23_APPCYCLE_RECONCILIATION"
+)
+
+var (
+	errPIDIsNotInteger = verror.Register(pkgPath+".errPIDIsNotInteger", verror.NoRetry, "{1:}{2:} __debug/stats/system/pid isn't an integer{:_}")
+
+	v23PIDMgmt = true
+)
+
+func init() {
+	// TODO(rjkroege): Environment variables do not survive device manager updates.
+	// Use an alternative mechanism.
+	if os.Getenv(AppcycleReconciliation) != "" {
+		v23PIDMgmt = false
+	}
+}
+
+type pidInstanceDirPair struct {
+	instanceDir string
+	pid         int
+}
+
+type reaper struct {
+	c   chan pidInstanceDirPair
+	ctx *context.T
+}
+
+var stashedPidMap map[string]int
+
+func newReaper(ctx *context.T, root string, appRunner *appRunner) (*reaper, error) {
+	pidMap, restartCandidates, err := findAllTheInstances(ctx, root)
+
+	// Used only by the testing code that verifies that all processes
+	// have been shutdown.
+	stashedPidMap = pidMap
+	if err != nil {
+		return nil, err
+	}
+
+	r := &reaper{
+		c:   make(chan pidInstanceDirPair),
+		ctx: ctx,
+	}
+	go r.processStatusPolling(ctx, pidMap, appRunner)
+
+	// Restart daemon jobs if they're not running (say because the machine crashed.)
+	for _, idir := range restartCandidates {
+		go appRunner.restartAppIfNecessary(ctx, idir)
+	}
+	return r, nil
+}
+
+func markNotRunning(ctx *context.T, idir string) {
+	if err := transitionInstance(idir, device.InstanceStateRunning, device.InstanceStateNotRunning); err != nil {
+		// This may fail under two circumstances.
+		// 1. The app has crashed between where startCmd invokes
+		// startWatching and where the invoker sets the state to running.
+		// 2. Remove experiences a failure (e.g. filesystem becoming R/O)
+		// 3. The app is in the process of being Kill'ed when the reaper poll
+		// finds the process dead and attempts a restart.
+		ctx.Errorf("reaper transitionInstance(%v, %v, %v) failed: %v\n", idir, device.InstanceStateRunning, device.InstanceStateNotRunning, err)
+	}
+}
+
+// processStatusPolling polls for the continued existence of a set of
+// tracked pids. TODO(rjkroege): There are nicer ways to provide this
+// functionality. For example, use the kevent facility in darwin or
+// replace init. See http://www.incenp.org/dvlpt/wait4.html for
+// inspiration.
+func (r *reaper) processStatusPolling(ctx *context.T, trackedPids map[string]int, appRunner *appRunner) {
+	poll := func(ctx *context.T) {
+		for idir, pid := range trackedPids {
+			switch err := syscall.Kill(pid, 0); err {
+			case syscall.ESRCH:
+				// No such PID.
+				ctx.VI(2).Infof("processStatusPolling discovered pid %d ended", pid)
+				markNotRunning(ctx, idir)
+				go appRunner.restartAppIfNecessary(ctx, idir)
+				delete(trackedPids, idir)
+			case nil, syscall.EPERM:
+				ctx.VI(2).Infof("processStatusPolling saw live pid: %d", pid)
+				// The task exists and is running under the same uid as
+				// the device manager or the task exists and is running
+				// under a different uid as would be the case if invoked
+				// via suidhelper. In this case do, nothing.
+
+				// This implementation cannot detect if a process exited
+				// and was replaced by an arbitrary non-Vanadium process
+				// within the polling interval.
+				// TODO(rjkroege): Probe the appcycle service of the app
+				// to confirm that its pid is valid iff v23PIDMgmt
+				// is false.
+
+				// TODO(rjkroege): if we can't connect to the app here via
+				// the appcycle manager, the app was probably started under
+				// a different agent and cannot be managed. Perhaps we should
+				// then kill the app and restart it?
+			default:
+				// The kill system call manpage says that this can only happen
+				// if the kernel claims that 0 is an invalid signal.
+				// Only a deeply confused kernel would say this so just give
+				// up.
+				ctx.Panicf("processStatusPolling: unanticpated result from sys.Kill: %v", err)
+			}
+		}
+	}
+
+	for {
+		select {
+		case p, ok := <-r.c:
+			switch {
+			case !ok:
+				return
+			case p.pid == -1: // stop watching this instance
+				delete(trackedPids, p.instanceDir)
+				poll(ctx)
+			case p.pid == -2: // kill the process
+				if pid, ok := trackedPids[p.instanceDir]; ok {
+					if err := suidHelper.terminatePid(ctx, pid, nil, nil); err != nil {
+						ctx.Errorf("Failure to kill: %v", err)
+					}
+				}
+			case p.pid < 0:
+				ctx.Panicf("invalid pid %v", p.pid)
+			default:
+				trackedPids[p.instanceDir] = p.pid
+				poll(ctx)
+			}
+		case <-time.After(time.Second):
+			// Poll once / second.
+			poll(ctx)
+		}
+	}
+}
+
+// startWatching begins watching process pid's state. This routine
+// assumes that pid already exists. Since pid is delivered to the device
+// manager by RPC callback, this seems reasonable.
+func (r *reaper) startWatching(idir string, pid int) {
+	r.c <- pidInstanceDirPair{instanceDir: idir, pid: pid}
+}
+
+// stopWatching stops watching process pid's state.
+func (r *reaper) stopWatching(idir string) {
+	r.c <- pidInstanceDirPair{instanceDir: idir, pid: -1}
+}
+
+// forciblySuspend terminates the process pid
+func (r *reaper) forciblySuspend(idir string) {
+	r.c <- pidInstanceDirPair{instanceDir: idir, pid: -2}
+}
+
+func (r *reaper) shutdown() {
+	close(r.c)
+}
+
+type pidErrorTuple struct {
+	ipath        string
+	pid          int
+	err          error
+	mightRestart bool
+}
+
+// In seconds.
+const appCycleTimeout = 5
+
+// processStatusViaAppCycleMgr updates the status based on getting the
+// pid from the AppCycleMgr because the data in the instance info might
+// be outdated: the app may have exited and an arbitrary non-Vanadium
+// process may have been executed with the same pid.
+func processStatusViaAppCycleMgr(ctx *context.T, c chan<- pidErrorTuple, instancePath string, info *instanceInfo, state device.InstanceState) {
+	nctx, _ := context.WithTimeout(ctx, appCycleTimeout*time.Second)
+
+	name := naming.Join(info.AppCycleMgrName, "__debug/stats/system/pid")
+	sclient := libstats.StatsClient(name)
+	v, err := sclient.Value(nctx)
+	if err != nil {
+		ctx.Infof("Instance: %v error: %v", instancePath, err)
+		// No process is actually running for this instance.
+		ctx.VI(2).Infof("perinstance stats fetching failed: %v", err)
+		if err := transitionInstance(instancePath, state, device.InstanceStateNotRunning); err != nil {
+			ctx.Errorf("transitionInstance(%s,%s,%s) failed: %v", instancePath, state, device.InstanceStateNotRunning, err)
+		}
+		// We only want to restart apps that were Running or Launching.
+		if state == device.InstanceStateLaunching || state == device.InstanceStateRunning {
+			c <- pidErrorTuple{ipath: instancePath, err: err, mightRestart: true}
+		} else {
+			c <- pidErrorTuple{ipath: instancePath, err: err}
+		}
+		return
+	}
+	// Convert the stat value from *vdl.Value into an int pid.
+	var pid int
+	if err := vdl.Convert(&pid, v); err != nil {
+		err = verror.New(errPIDIsNotInteger, ctx, err)
+		ctx.Errorf(err.Error())
+		c <- pidErrorTuple{ipath: instancePath, err: err}
+		return
+	}
+
+	ptuple := pidErrorTuple{ipath: instancePath, pid: pid}
+
+	// Update the instance info.
+	if info.Pid != pid {
+		info.Pid = pid
+		ptuple.err = saveInstanceInfo(ctx, instancePath, info)
+	}
+
+	// The instance was found to be running, so update its state accordingly
+	// (in case the device restarted while the instance was in one of the
+	// transitional states like launching, dying, etc).
+	if err := transitionInstance(instancePath, state, device.InstanceStateRunning); err != nil {
+		ctx.Errorf("transitionInstance(%s,%v,%s) failed: %v", instancePath, state, device.InstanceStateRunning, err)
+	}
+
+	ctx.VI(0).Infof("perInstance go routine for %v ending", instancePath)
+	c <- ptuple
+}
+
+// processStatusViaKill updates the status based on sending a kill signal
+// to the process. This assumes that most processes on the system are
+// likely to be managed by the device manager and a live process is not
+// responsive because the agent has been restarted rather than being
+// created through a different means.
+func processStatusViaKill(ctx *context.T, c chan<- pidErrorTuple, instancePath string, info *instanceInfo, state device.InstanceState) {
+	pid := info.Pid
+
+	switch err := syscall.Kill(pid, 0); err {
+	case syscall.ESRCH:
+		// No such PID.
+		if err := transitionInstance(instancePath, state, device.InstanceStateNotRunning); err != nil {
+			ctx.Errorf("transitionInstance(%s,%s,%s) failed: %v", instancePath, state, device.InstanceStateNotRunning, err)
+		}
+		// We only want to restart apps that were Running or Launching.
+		if state == device.InstanceStateLaunching || state == device.InstanceStateRunning {
+			c <- pidErrorTuple{ipath: instancePath, err: err, pid: pid, mightRestart: true}
+		} else {
+			c <- pidErrorTuple{ipath: instancePath, err: err, pid: pid}
+		}
+	case nil, syscall.EPERM:
+		// The instance was found to be running, so update its state.
+		if err := transitionInstance(instancePath, state, device.InstanceStateRunning); err != nil {
+			ctx.Errorf("transitionInstance(%s,%v, %v) failed: %v", instancePath, state, device.InstanceStateRunning, err)
+		}
+		ctx.VI(0).Infof("perInstance go routine for %v ending", instancePath)
+		c <- pidErrorTuple{ipath: instancePath, pid: pid}
+	}
+}
+
+func perInstance(ctx *context.T, instancePath string, c chan<- pidErrorTuple, wg *sync.WaitGroup) {
+	defer wg.Done()
+	ctx.Infof("Instance: %v", instancePath)
+	state, err := getInstanceState(instancePath)
+	switch state {
+	// Ignore apps already in deleted and not running states.
+	case device.InstanceStateNotRunning:
+		c <- pidErrorTuple{ipath: instancePath}
+		return
+	case device.InstanceStateDeleted:
+		return
+	// If the app was updating, it means it was already not running, so just
+	// update its state back to not running.
+	case device.InstanceStateUpdating:
+		if err := transitionInstance(instancePath, state, device.InstanceStateNotRunning); err != nil {
+			ctx.Errorf("transitionInstance(%s,%s,%s) failed: %v", instancePath, state, device.InstanceStateNotRunning, err)
+		}
+		return
+	}
+	ctx.VI(2).Infof("perInstance firing up on %s", instancePath)
+
+	// Read the instance data.
+	info, err := loadInstanceInfo(ctx, instancePath)
+	if err != nil {
+		ctx.Errorf("loadInstanceInfo failed: %v", err)
+		// Something has gone badly wrong.
+		// TODO(rjkroege,caprita): Consider removing the instance or at
+		// least set its state to something indicating error?
+		c <- pidErrorTuple{err: err, ipath: instancePath}
+		return
+	}
+
+	// Remaining states: Launching, Running, Dying. Of these,
+	// daemon mode will restart Launching and Running if the process
+	// is not alive.
+	if !v23PIDMgmt {
+		processStatusViaAppCycleMgr(ctx, c, instancePath, info, state)
+		return
+	}
+	processStatusViaKill(ctx, c, instancePath, info, state)
+}
+
+// Digs through the directory hierarchy
+func findAllTheInstances(ctx *context.T, root string) (map[string]int, []string, error) {
+	paths, err := filepath.Glob(filepath.Join(root, "app*", "installation*", "instances", "instance*"))
+	if err != nil {
+		return nil, nil, err
+	}
+
+	pidmap := make(map[string]int)
+	pidchan := make(chan pidErrorTuple, len(paths))
+	var wg sync.WaitGroup
+
+	for _, pth := range paths {
+		wg.Add(1)
+		go perInstance(ctx, pth, pidchan, &wg)
+	}
+	wg.Wait()
+	close(pidchan)
+
+	restartCandidates := make([]string, len(paths))
+	for p := range pidchan {
+		if p.err != nil {
+			ctx.Errorf("instance at %s had an error: %v", p.ipath, p.err)
+		}
+		if p.pid > 0 {
+			pidmap[p.ipath] = p.pid
+		}
+		if p.mightRestart {
+			restartCandidates = append(restartCandidates, p.ipath)
+		}
+	}
+	return pidmap, restartCandidates, nil
+}
+
+// RunningChildrenProcesses uses the reaper to verify that a test has
+// successfully shut down all processes.
+func RunningChildrenProcesses() bool {
+	return len(stashedPidMap) > 0
+}
diff --git a/services/device/deviced/internal/impl/only_for_test.go b/services/device/deviced/internal/impl/only_for_test.go
new file mode 100644
index 0000000..9b02bb7
--- /dev/null
+++ b/services/device/deviced/internal/impl/only_for_test.go
@@ -0,0 +1,31 @@
+// 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 impl
+
+import (
+	"fmt"
+
+	"v.io/v23/rpc"
+)
+
+// This file contains code in the impl package that we only want built for tests
+// (it exposes public API methods that we don't want to normally expose).
+
+func (c *callbackState) leaking() bool {
+	c.Lock()
+	defer c.Unlock()
+	return len(c.channels) > 0
+}
+
+func DispatcherLeaking(d rpc.Dispatcher) bool {
+	switch obj := d.(type) {
+	case *dispatcher:
+		return obj.internal.callback.leaking()
+	case *testModeDispatcher:
+		return obj.realDispatcher.(*dispatcher).internal.callback.leaking()
+	default:
+		panic(fmt.Sprintf("unexpected type: %T", d))
+	}
+}
diff --git a/services/device/deviced/internal/impl/perms/debug_perms_test.go b/services/device/deviced/internal/impl/perms/debug_perms_test.go
new file mode 100644
index 0000000..5affa34
--- /dev/null
+++ b/services/device/deviced/internal/impl/perms/debug_perms_test.go
@@ -0,0 +1,264 @@
+// 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 perms_test
+
+import (
+	"io/ioutil"
+	"syscall"
+	"testing"
+
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/security"
+	"v.io/v23/security/access"
+	"v.io/v23/services/permissions"
+	"v.io/v23/verror"
+
+	"v.io/x/ref/services/device/deviced/internal/impl/utiltest"
+	"v.io/x/ref/services/internal/servicetest"
+	"v.io/x/ref/test/testutil"
+)
+
+func updateAccessList(t *testing.T, ctx *context.T, blessing, right string, name ...string) {
+	accessStub := permissions.ObjectClient(naming.Join(name...))
+	perms, version, err := accessStub.GetPermissions(ctx)
+	if err != nil {
+		t.Fatalf(testutil.FormatLogLine(2, "GetPermissions(%v) failed %v", name, err))
+	}
+	perms.Add(security.BlessingPattern(blessing), right)
+	if err = accessStub.SetPermissions(ctx, perms, version); err != nil {
+		t.Fatalf(testutil.FormatLogLine(2, "SetPermissions(%v, %v, %v) failed: %v", name, blessing, right, err))
+	}
+}
+
+func testAccessFail(t *testing.T, expected verror.ID, ctx *context.T, who string, name ...string) {
+	if _, err := utiltest.StatsStub(name...).Value(ctx); verror.ErrorID(err) != expected {
+		t.Fatalf(testutil.FormatLogLine(2, "%s got error %v but expected %v", who, err, expected))
+	}
+}
+
+func TestDebugPermissionsPropagation(t *testing.T) {
+	cleanup, ctx, sh, envelope, root, helperPath, idp := utiltest.StartupHelper(t)
+	defer cleanup()
+
+	// Set up the device manager.
+	dmh := servicetest.RunCommand(t, sh, nil, utiltest.DeviceManager, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
+	servicetest.ReadPID(t, dmh)
+	utiltest.ClaimDevice(t, ctx, "claimable", "dm", "mydevice", utiltest.NoPairingToken)
+
+	// Create the local server that the app uses to let us know it's ready.
+	pingCh, cleanup := utiltest.SetupPingServer(t, ctx)
+	defer cleanup()
+	utiltest.Resolve(t, ctx, "pingserver", 1, true)
+
+	// Make some users.
+	selfCtx := ctx
+	bobCtx := utiltest.CtxWithNewPrincipal(t, selfCtx, idp, "bob")
+	hjCtx := utiltest.CtxWithNewPrincipal(t, selfCtx, idp, "hackerjoe")
+	aliceCtx := utiltest.CtxWithNewPrincipal(t, selfCtx, idp, "alice")
+
+	// TODO(rjkroege): Set AccessLists here that conflict with the one provided by the device
+	// manager and show that the one set here is overridden.
+	// Create the envelope for the first version of the app.
+	*envelope = utiltest.EnvelopeFromShell(sh, nil, utiltest.App, "google naps", 0, 0, "appV1")
+
+	// Install the app.
+	appID := utiltest.InstallApp(t, ctx)
+
+	// Give bob rights to start an app.
+	updateAccessList(t, selfCtx, "root/bob/$", string(access.Read), "dm/apps", appID)
+
+	// Bob starts an instance of the app.
+	bobApp := utiltest.LaunchApp(t, bobCtx, appID)
+	pingCh.VerifyPingArgs(t, utiltest.UserName(t), "default", "")
+
+	// Bob permits Alice to read from his app.
+	updateAccessList(t, bobCtx, "root/alice/$", string(access.Read), "dm/apps", appID, bobApp)
+
+	// Create some globbing test vectors.
+	globtests := []utiltest.GlobTestVector{
+		{naming.Join("dm", "apps", appID, bobApp), "*",
+			[]string{"logs", "pprof", "stats"},
+		},
+		{naming.Join("dm", "apps", appID, bobApp, "stats", "system"),
+			"start-time*",
+			[]string{"start-time-rfc1123", "start-time-unix"},
+		},
+		{naming.Join("dm", "apps", appID, bobApp, "logs"),
+			"*",
+			[]string{
+				"STDERR-<timestamp>",
+				"STDOUT-<timestamp>",
+				"app.INFO",
+				"app.<*>.INFO.<timestamp>",
+			},
+		},
+	}
+	appGlobtests := []utiltest.GlobTestVector{
+		{naming.Join("appV1", "__debug"), "*",
+			[]string{"logs", "pprof", "stats", "vtrace"},
+		},
+		{naming.Join("appV1", "__debug", "stats", "system"),
+			"start-time*",
+			[]string{"start-time-rfc1123", "start-time-unix"},
+		},
+		{naming.Join("appV1", "__debug", "logs"),
+			"*",
+			[]string{
+				"STDERR-<timestamp>",
+				"STDOUT-<timestamp>",
+				"app.INFO",
+				"app.<*>.INFO.<timestamp>",
+			},
+		},
+	}
+	globtestminus := globtests[1:]
+	res := utiltest.NewGlobTestRegexHelper("app")
+
+	// Confirm that self can access __debug names.
+	utiltest.VerifyGlob(t, selfCtx, "app", globtests, res)
+	utiltest.VerifyStatsValues(t, selfCtx, "dm", "apps", appID, bobApp, "stats/system/start-time*")
+	utiltest.VerifyLog(t, selfCtx, "dm", "apps", appID, bobApp, "logs", "*")
+	utiltest.VerifyPProfCmdLine(t, selfCtx, "app", "dm", "apps", appID, bobApp, "pprof")
+
+	// Bob started the app so selfCtx can't connect to the app.
+	utiltest.VerifyFailGlob(t, selfCtx, appGlobtests)
+	testAccessFail(t, verror.ErrNoAccess.ID, selfCtx, "self", "appV1", "__debug", "stats/system/pid")
+
+	// hackerjoe (for example) can't either.
+	utiltest.VerifyFailGlob(t, hjCtx, appGlobtests)
+	testAccessFail(t, verror.ErrNoAccess.ID, hjCtx, "hackerjoe", "appV1", "__debug", "stats/system/pid")
+
+	// Bob has an issue with his app and tries to use the debug output to figure it out.
+	utiltest.VerifyGlob(t, bobCtx, "app", globtests, res)
+	utiltest.VerifyStatsValues(t, bobCtx, "dm", "apps", appID, bobApp, "stats/system/start-time*")
+	utiltest.VerifyLog(t, bobCtx, "dm", "apps", appID, bobApp, "logs", "*")
+	utiltest.VerifyPProfCmdLine(t, bobCtx, "app", "dm", "apps", appID, bobApp, "pprof")
+
+	// Bob can also connect directly to his app.
+	utiltest.VerifyGlob(t, bobCtx, "app", appGlobtests, res)
+	utiltest.VerifyStatsValues(t, bobCtx, "appV1", "__debug", "stats/system/start-time*")
+
+	// But Bob can't figure it out and hopes that hackerjoe can debug it.
+	updateAccessList(t, bobCtx, "root/hackerjoe/$", string(access.Debug), "dm/apps", appID, bobApp)
+
+	// Fortunately the device manager permits hackerjoe to access the stats.
+	// But hackerjoe can't solve Bob's problem.
+	// Because hackerjoe has Debug, hackerjoe can glob the __debug resources
+	// of Bob's app but can't glob Bob's app.
+	utiltest.VerifyGlob(t, hjCtx, "app", globtestminus, res)
+	utiltest.VerifyFailGlob(t, hjCtx, globtests[0:1])
+	utiltest.VerifyStatsValues(t, hjCtx, "dm", "apps", appID, bobApp, "stats", "system/start-time*")
+	utiltest.VerifyLog(t, hjCtx, "dm", "apps", appID, bobApp, "logs", "*")
+	utiltest.VerifyPProfCmdLine(t, hjCtx, "app", "dm", "apps", appID, bobApp, "pprof")
+
+	// Permissions are propagated to the app so hackerjoe can connect
+	// directly to the app too.
+	utiltest.VerifyGlob(t, hjCtx, "app", globtestminus, res)
+	utiltest.VerifyStatsValues(t, hjCtx, "appV1", "__debug", "stats/system/start-time*")
+
+	// Alice might be able to help but Bob didn't give Alice access to the debug Permissionss.
+	testAccessFail(t, verror.ErrNoAccess.ID, aliceCtx, "Alice", "dm", "apps", appID, bobApp, "stats/system/pid")
+
+	// Bob forgets that Alice can't read the stats when he can.
+	utiltest.VerifyGlob(t, bobCtx, "app", globtests, res)
+	utiltest.VerifyStatsValues(t, bobCtx, "dm", "apps", appID, bobApp, "stats/system/start-time*")
+
+	// So Bob changes the permissions so that Alice can help debug too.
+	updateAccessList(t, bobCtx, "root/alice/$", string(access.Debug), "dm/apps", appID, bobApp)
+
+	// Alice can access __debug content.
+	utiltest.VerifyGlob(t, aliceCtx, "app", globtestminus, res)
+	utiltest.VerifyFailGlob(t, aliceCtx, globtests[0:1])
+	utiltest.VerifyStatsValues(t, aliceCtx, "dm", "apps", appID, bobApp, "stats", "system/start-time*")
+	utiltest.VerifyLog(t, aliceCtx, "dm", "apps", appID, bobApp, "logs", "*")
+	utiltest.VerifyPProfCmdLine(t, aliceCtx, "app", "dm", "apps", appID, bobApp, "pprof")
+
+	// Alice can also now connect directly to the app.
+	utiltest.VerifyGlob(t, aliceCtx, "app", globtestminus, res)
+	utiltest.VerifyStatsValues(t, aliceCtx, "appV1", "__debug", "stats/system/start-time*")
+
+	// Bob is glum because no one can help him fix his app so he terminates
+	// it.
+	utiltest.TerminateApp(t, bobCtx, appID, bobApp)
+
+	// Cleanly shut down the device manager.
+	syscall.Kill(dmh.Pid(), syscall.SIGINT)
+	dmh.Expect("dm terminated")
+	dmh.ExpectEOF()
+}
+
+func TestClaimSetsDebugPermissions(t *testing.T) {
+	cleanup, ctx, sh, _, root, helperPath, idp := utiltest.StartupHelper(t)
+	defer cleanup()
+
+	extraLogDir, err := ioutil.TempDir(root, "testlogs")
+	if err != nil {
+		t.Fatalf("ioutil.TempDir failed: %v", err)
+	}
+
+	// Set up the device manager.
+	dmh := servicetest.RunCommand(t, sh, nil, utiltest.DeviceManager, "--log_dir="+extraLogDir, "dm", root, helperPath, "unused", "unused_curr_link")
+	servicetest.ReadPID(t, dmh)
+
+	// Make some users.
+	selfCtx := ctx
+	bobCtx := utiltest.CtxWithNewPrincipal(t, selfCtx, idp, "bob")
+	aliceCtx := utiltest.CtxWithNewPrincipal(t, selfCtx, idp, "alice")
+	hjCtx := utiltest.CtxWithNewPrincipal(t, selfCtx, idp, "hackerjoe")
+
+	// Bob claims the device manager.
+	utiltest.ClaimDevice(t, bobCtx, "claimable", "dm", "mydevice", utiltest.NoPairingToken)
+
+	// Create some globbing test vectors.
+	dmGlobtests := []utiltest.GlobTestVector{
+		{naming.Join("dm", "__debug"), "*",
+			[]string{"logs", "pprof", "stats", "vtrace"},
+		},
+		{naming.Join("dm", "__debug", "stats", "system"),
+			"start-time*",
+			[]string{"start-time-rfc1123", "start-time-unix"},
+		},
+		{naming.Join("dm", "__debug", "logs"),
+			"*",
+			[]string{
+				// STDERR and STDOUT are not handled through the log package so
+				// are not included here.
+				"perms.test.INFO",
+				"perms.test.<*>.INFO.<timestamp>",
+			},
+		},
+	}
+	res := utiltest.NewGlobTestRegexHelper(`perms\.test`)
+
+	// Bob claimed the DM so can access it.
+	utiltest.VerifyGlob(t, bobCtx, "perms.test", dmGlobtests, res)
+	utiltest.VerifyStatsValues(t, bobCtx, "dm", "__debug", "stats/system/start-time*")
+
+	// Without permissions, hackerjoe can't access the device manager.
+	utiltest.VerifyFailGlob(t, hjCtx, dmGlobtests)
+	testAccessFail(t, verror.ErrNoAccess.ID, hjCtx, "hackerjoe", "dm", "__debug", "stats/system/pid")
+
+	// Bob gives system administrator Alice admin access to the dm and hence Alice
+	// can access the __debug space.
+	updateAccessList(t, bobCtx, "root/alice/$", string(access.Admin), "dm", "device")
+
+	// Alice is an adminstrator and so can can access device manager __debug
+	// values.
+	utiltest.VerifyGlob(t, aliceCtx, "perms.test", dmGlobtests, res)
+	utiltest.VerifyStatsValues(t, aliceCtx, "dm", "__debug", "stats/system/start-time*")
+
+	// Bob gives debug access to the device manager to hackerjoe
+	updateAccessList(t, bobCtx, "root/hackerjoe/$", string(access.Debug), "dm", "device")
+
+	// hackerjoe can now access the device manager
+	utiltest.VerifyGlob(t, hjCtx, "perms.test", dmGlobtests, res)
+	utiltest.VerifyStatsValues(t, hjCtx, "dm", "__debug", "stats/system/start-time*")
+
+	// Cleanly shut down the device manager.
+	syscall.Kill(dmh.Pid(), syscall.SIGINT)
+	dmh.Expect("dm terminated")
+	dmh.ExpectEOF()
+}
diff --git a/services/device/deviced/internal/impl/perms/doc.go b/services/device/deviced/internal/impl/perms/doc.go
new file mode 100644
index 0000000..afd8566
--- /dev/null
+++ b/services/device/deviced/internal/impl/perms/doc.go
@@ -0,0 +1,7 @@
+// 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 perms
+
+// Test code for claiming and permission list code in the device manager.
diff --git a/services/device/deviced/internal/impl/perms/impl_test.go b/services/device/deviced/internal/impl/perms/impl_test.go
new file mode 100644
index 0000000..a6ac9ad
--- /dev/null
+++ b/services/device/deviced/internal/impl/perms/impl_test.go
@@ -0,0 +1,19 @@
+// 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 perms_test
+
+import (
+	"testing"
+
+	"v.io/x/ref/services/device/deviced/internal/impl/utiltest"
+)
+
+func TestMain(m *testing.M) {
+	utiltest.TestMainImpl(m)
+}
+
+func TestSuidHelper(t *testing.T) {
+	utiltest.TestSuidHelperImpl(t)
+}
diff --git a/services/device/deviced/internal/impl/perms/perms_test.go b/services/device/deviced/internal/impl/perms/perms_test.go
new file mode 100644
index 0000000..0d3f838
--- /dev/null
+++ b/services/device/deviced/internal/impl/perms/perms_test.go
@@ -0,0 +1,192 @@
+// 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 perms_test
+
+import (
+	"bytes"
+	"crypto/md5"
+	"encoding/hex"
+	"syscall"
+	"testing"
+
+	"v.io/v23"
+	"v.io/v23/security"
+	"v.io/v23/security/access"
+	"v.io/v23/services/device"
+	"v.io/v23/verror"
+	"v.io/x/ref/services/device/deviced/internal/impl/utiltest"
+	"v.io/x/ref/services/device/deviced/internal/versioning"
+	"v.io/x/ref/services/device/internal/errors"
+	"v.io/x/ref/services/internal/servicetest"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/testutil"
+)
+
+// TestDeviceManagerClaim claims a devicemanager and tests AccessList permissions on
+// its methods.
+func TestDeviceManagerClaim(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	// root blessing provider so that the principals of all the contexts
+	// recognize each other.
+	idp := testutil.NewIDProvider("root")
+	if err := idp.Bless(v23.GetPrincipal(ctx), "ctx"); err != nil {
+		t.Fatal(err)
+	}
+
+	sh, deferFn := servicetest.CreateShellAndMountTable(t, ctx, nil)
+	defer deferFn()
+
+	// Set up mock application and binary repositories.
+	envelope, cleanup := utiltest.StartMockRepos(t, ctx)
+	defer cleanup()
+
+	root, cleanup := servicetest.SetupRootDir(t, "devicemanager")
+	defer cleanup()
+	if err := versioning.SaveCreatorInfo(ctx, root); err != nil {
+		t.Fatal(err)
+	}
+
+	// Create a script wrapping the test target that implements suidhelper.
+	helperPath := utiltest.GenerateSuidHelperScript(t, root)
+
+	// Set up the device manager.  Since we won't do device manager updates,
+	// don't worry about its application envelope and current link.
+	pairingToken := "abcxyz"
+	dmh := servicetest.RunCommand(t, sh, nil, utiltest.DeviceManager, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link", pairingToken)
+	pid := servicetest.ReadPID(t, dmh)
+	defer syscall.Kill(pid, syscall.SIGINT)
+
+	*envelope = utiltest.EnvelopeFromShell(sh, nil, utiltest.App, "google naps", 0, 0, "trapp")
+
+	claimantCtx := utiltest.CtxWithNewPrincipal(t, ctx, idp, "claimant")
+	octx, err := v23.WithPrincipal(ctx, testutil.NewPrincipal("other"))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// Unclaimed devices cannot do anything but be claimed.
+	// TODO(ashankar,caprita): The line below will currently fail with
+	// ErrUnclaimedDevice != NotTrusted. NotTrusted can be avoided by
+	// passing options.SkipServerEndpointAuthorization{} to the "Install" RPC.
+	// Refactor the helper function to make this possible.
+	//installAppExpectError(t, octx, impl.ErrUnclaimedDevice.ID)
+
+	// Claim the device with an incorrect pairing token should fail.
+	utiltest.ClaimDeviceExpectError(t, claimantCtx, "claimable", "mydevice", "badtoken", errors.ErrInvalidPairingToken.ID)
+	// But succeed with a valid pairing token
+	utiltest.ClaimDevice(t, claimantCtx, "claimable", "dm", "mydevice", pairingToken)
+
+	// Installation should succeed since claimantRT is now the "owner" of
+	// the devicemanager.
+	appID := utiltest.InstallApp(t, claimantCtx)
+
+	// octx will not install the app now since it doesn't recognize the
+	// device's blessings. The error returned will be ErrNoServers as that
+	// is what the IPC stack does when there are no authorized servers.
+	utiltest.InstallAppExpectError(t, octx, verror.ErrNoServers.ID)
+	// Even if it does recognize the device (by virtue of recognizing the
+	// claimant), the device will not allow it to install.
+	if err := v23.GetPrincipal(octx).AddToRoots(v23.GetPrincipal(claimantCtx).BlessingStore().Default()); err != nil {
+		t.Fatal(err)
+	}
+	utiltest.InstallAppExpectError(t, octx, verror.ErrNoAccess.ID)
+
+	// Create the local server that the app uses to let us know it's ready.
+	pingCh, cleanup := utiltest.SetupPingServer(t, claimantCtx)
+	defer cleanup()
+
+	// Start an instance of the app.
+	instanceID := utiltest.LaunchApp(t, claimantCtx, appID)
+
+	// Wait until the app pings us that it's ready.
+	pingCh.WaitForPingArgs(t)
+	utiltest.Resolve(t, ctx, "trapp", 1, false)
+	utiltest.KillApp(t, claimantCtx, appID, instanceID)
+
+	// TODO(gauthamt): Test that AccessLists persist across devicemanager restarts
+}
+
+func TestDeviceManagerUpdateAccessList(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	// Identity provider to ensure that all processes recognize each
+	// others' blessings.
+	idp := testutil.NewIDProvider("root")
+	ctx = utiltest.CtxWithNewPrincipal(t, ctx, idp, "self")
+
+	sh, deferFn := servicetest.CreateShellAndMountTable(t, ctx, nil)
+	defer deferFn()
+
+	// Set up mock application and binary repositories.
+	envelope, cleanup := utiltest.StartMockRepos(t, ctx)
+	defer cleanup()
+
+	root, cleanup := servicetest.SetupRootDir(t, "devicemanager")
+	defer cleanup()
+	if err := versioning.SaveCreatorInfo(ctx, root); err != nil {
+		t.Fatal(err)
+	}
+
+	selfCtx := ctx
+	octx := utiltest.CtxWithNewPrincipal(t, selfCtx, idp, "other")
+
+	// Set up the device manager.  Since we won't do device manager updates,
+	// don't worry about its application envelope and current link.
+	dmh := servicetest.RunCommand(t, sh, nil, utiltest.DeviceManager, "dm", root, "unused_helper", "unused_app_repo_name", "unused_curr_link")
+	pid := servicetest.ReadPID(t, dmh)
+	defer syscall.Kill(pid, syscall.SIGINT)
+	defer utiltest.VerifyNoRunningProcesses(t)
+
+	// Create an envelope for an app.
+	*envelope = utiltest.EnvelopeFromShell(sh, nil, utiltest.App, "google naps", 0, 0)
+
+	// On an unclaimed device manager, there will be no AccessLists.
+	if _, _, err := device.DeviceClient("claimable").GetPermissions(selfCtx); err == nil {
+		t.Fatalf("GetPermissions should have failed but didn't.")
+	}
+
+	// Claim the devicemanager as "root/self/mydevice"
+	utiltest.ClaimDevice(t, selfCtx, "claimable", "dm", "mydevice", utiltest.NoPairingToken)
+	expectedAccessList := make(access.Permissions)
+	for _, tag := range access.AllTypicalTags() {
+		expectedAccessList[string(tag)] = access.AccessList{In: []security.BlessingPattern{"root/$", "root/self/$", "root/self/mydevice/$"}}
+	}
+	var b bytes.Buffer
+	if err := access.WritePermissions(&b, expectedAccessList); err != nil {
+		t.Fatalf("Failed to save AccessList:%v", err)
+	}
+	// Note, "version" below refers to the Permissions version, not the device
+	// manager version.
+	md5hash := md5.Sum(b.Bytes())
+	expectedVersion := hex.EncodeToString(md5hash[:])
+	deviceStub := device.DeviceClient("dm/device")
+	perms, version, err := deviceStub.GetPermissions(selfCtx)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if version != expectedVersion {
+		t.Fatalf("getAccessList expected:%v(%v), got:%v(%v)", expectedAccessList, expectedVersion, perms, version)
+	}
+	// Install from octx should fail, since it does not match the AccessList.
+	utiltest.InstallAppExpectError(t, octx, verror.ErrNoAccess.ID)
+
+	newAccessList := make(access.Permissions)
+	for _, tag := range access.AllTypicalTags() {
+		newAccessList.Add("root/other", string(tag))
+	}
+	if err := deviceStub.SetPermissions(selfCtx, newAccessList, "invalid"); err == nil {
+		t.Fatalf("SetPermissions should have failed with invalid version")
+	}
+	if err := deviceStub.SetPermissions(selfCtx, newAccessList, version); err != nil {
+		t.Fatal(err)
+	}
+	// Install should now fail with selfCtx, which no longer matches the
+	// AccessLists but succeed with octx, which does.
+	utiltest.InstallAppExpectError(t, selfCtx, verror.ErrNoAccess.ID)
+	utiltest.InstallApp(t, octx)
+}
diff --git a/services/device/deviced/internal/impl/perms_propagator.go b/services/device/deviced/internal/impl/perms_propagator.go
new file mode 100644
index 0000000..c1f87f6
--- /dev/null
+++ b/services/device/deviced/internal/impl/perms_propagator.go
@@ -0,0 +1,44 @@
+// 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 impl
+
+import (
+	"path/filepath"
+
+	"v.io/v23/security"
+	"v.io/v23/security/access"
+	"v.io/x/ref/services/internal/pathperms"
+)
+
+// computePath builds the desired path for the debug perms.
+func computePath(path string) string {
+	return filepath.Join(path, "debugacls")
+}
+
+// setPermsForDebugging constructs a Permissions file for use by applications
+// that permits principals with a Debug right on an application instance to
+// access names in the app's __debug space.
+func setPermsForDebugging(blessings []string, perms access.Permissions, instancePath string, permsStore *pathperms.PathStore) error {
+	path := computePath(instancePath)
+	newPerms := make(access.Permissions)
+
+	// Add blessings for the DM so that it can access the app too.
+
+	set := func(bl security.BlessingPattern) {
+		for _, tag := range []access.Tag{access.Resolve, access.Debug} {
+			newPerms.Add(bl, string(tag))
+		}
+	}
+
+	for _, b := range blessings {
+		set(security.BlessingPattern(b))
+	}
+
+	// add Resolve for every blessing that has debug
+	for _, v := range perms["Debug"].In {
+		set(v)
+	}
+	return permsStore.SetShareable(path, newPerms, "", true)
+}
diff --git a/services/device/deviced/internal/impl/profile.go b/services/device/deviced/internal/impl/profile.go
new file mode 100644
index 0000000..9908666
--- /dev/null
+++ b/services/device/deviced/internal/impl/profile.go
@@ -0,0 +1,192 @@
+// 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 impl
+
+import (
+	"bytes"
+	"errors"
+	"os/exec"
+	"runtime"
+	"strings"
+
+	"v.io/v23/services/build"
+	"v.io/v23/services/device"
+	"v.io/x/ref/services/internal/profiles"
+	"v.io/x/ref/services/profile"
+)
+
+// ComputeDeviceProfile generates a description of the runtime
+// environment (supported file format, OS, architecture, libraries) of
+// the host device.
+//
+// TODO(jsimsa): Avoid computing the host device description from
+// scratch if a recent cached copy exists.
+func ComputeDeviceProfile() (*profile.Specification, error) {
+	result := profile.Specification{}
+
+	// Find out what the supported operating system, file format, and
+	// architecture is.
+	var os build.OperatingSystem
+	if err := os.SetFromGoOS(runtime.GOOS); err != nil {
+		return nil, err
+	}
+	result.Os = os
+	switch os {
+	case build.OperatingSystemDarwin:
+		result.Format = build.FormatMach
+	case build.OperatingSystemLinux:
+		result.Format = build.FormatElf
+	case build.OperatingSystemWindows:
+		result.Format = build.FormatPe
+	default:
+		return nil, errors.New("Unsupported operating system: " + os.String())
+	}
+	var arch build.Architecture
+	if err := arch.SetFromGoArch(runtime.GOARCH); err != nil {
+		return nil, err
+	}
+	result.Arch = arch
+
+	// Find out what the installed dynamically linked libraries are.
+	switch runtime.GOOS {
+	case "linux":
+		// For Linux, we identify what dynamically linked libraries are
+		// installed by parsing the output of "ldconfig -p".
+		command := exec.Command("/sbin/ldconfig", "-p")
+		output, err := command.CombinedOutput()
+		if err != nil {
+			return nil, err
+		}
+		buf := bytes.NewBuffer(output)
+		// Throw away the first line of output from ldconfig.
+		if _, err := buf.ReadString('\n'); err != nil {
+			return nil, errors.New("Could not identify libraries.")
+		}
+		// Extract the library name and version from every subsequent line.
+		result.Libraries = make(map[profile.Library]struct{})
+		line, err := buf.ReadString('\n')
+		for err == nil {
+			words := strings.Split(strings.Trim(line, " \t\n"), " ")
+			if len(words) > 0 {
+				tokens := strings.Split(words[0], ".so")
+				if len(tokens) != 2 {
+					return nil, errors.New("Could not identify library: " + words[0])
+				}
+				name := strings.TrimPrefix(tokens[0], "lib")
+				major, minor := "", ""
+				tokens = strings.SplitN(tokens[1], ".", 3)
+				if len(tokens) >= 2 {
+					major = tokens[1]
+				}
+				if len(tokens) >= 3 {
+					minor = tokens[2]
+				}
+				result.Libraries[profile.Library{Name: name, MajorVersion: major, MinorVersion: minor}] = struct{}{}
+			}
+			line, err = buf.ReadString('\n')
+		}
+	case "darwin":
+		// TODO(jsimsa): Implement.
+	case "windows":
+		// TODO(jsimsa): Implement.
+	default:
+		return nil, errors.New("Unsupported operating system: " + runtime.GOOS)
+	}
+	return &result, nil
+}
+
+// getProfile gets a profile description for the given profile.
+//
+// TODO(jsimsa): Avoid retrieving the list of known profiles from a
+// remote server if a recent cached copy exists.
+func getProfile(name string) (*profile.Specification, error) {
+	profiles, err := profiles.GetKnownProfiles()
+	if err != nil {
+		return nil, err
+	}
+	for _, p := range profiles {
+		if p.Label == name {
+			return p, nil
+		}
+	}
+	return nil, nil
+
+	// TODO(jsimsa): This function assumes the existence of a profile
+	// server from which the profiles can be retrieved. The profile
+	// server is a work in progress. When it exists, the commented out
+	// code below should work.
+	/*
+		var profile profile.Specification
+				client, err := r.NewClient()
+				if err != nil {
+					return nil, verror.New(ErrOperationFailed, nil, fmt.Sprintf("NewClient() failed: %v", err))
+				}
+				defer client.Close()
+			  server := // TODO
+				method := "Specification"
+				inputs := make([]interface{}, 0)
+				call, err := client.StartCall(server + "/" + name, method, inputs)
+				if err != nil {
+					return nil, verror.New(ErrOperationFailed, nil, fmt.Sprintf("StartCall(%s, %q, %v) failed: %v\n", server + "/" + name, method, inputs, err))
+				}
+				if err := call.Finish(&profiles); err != nil {
+					return nil, verror.New(ErrOperationFailed, nil, fmt.Sprintf("Finish(%v) failed: %v\n", &profiles, err))
+				}
+		return &profile, nil
+	*/
+}
+
+// matchProfiles inputs a profile that describes the host device and a
+// set of publicly known profiles and outputs a device description that
+// identifies the publicly known profiles supported by the host device.
+func matchProfiles(p *profile.Specification, known []*profile.Specification) device.Description {
+	result := device.Description{Profiles: make(map[string]struct{})}
+loop:
+	for _, profile := range known {
+		if profile.Format != p.Format {
+			continue
+		}
+		if profile.Os != p.Os {
+			continue
+		}
+		if profile.Arch != p.Arch {
+			continue
+		}
+		for library := range profile.Libraries {
+			// Current implementation requires exact library name and version match.
+			if _, found := p.Libraries[library]; !found {
+				continue loop
+			}
+		}
+		result.Profiles[profile.Label] = struct{}{}
+	}
+	return result
+}
+
+// Describe returns a Description containing the profile that matches the
+// current device.  It's declared as a variable so we can override it for
+// testing.
+var Describe = func() (device.Description, error) {
+	empty := device.Description{}
+	deviceProfile, err := ComputeDeviceProfile()
+	if err != nil {
+		return empty, err
+	}
+	knownProfiles, err := profiles.GetKnownProfiles()
+	if err != nil {
+		return empty, err
+	}
+	result := matchProfiles(deviceProfile, knownProfiles)
+	if len(result.Profiles) == 0 {
+		// For now, return "unknown" as the profile, if no known profile
+		// matches the device's profile.
+		//
+		// TODO(caprita): Get rid of this crutch once we have profiles
+		// defined for our supported systems; for now it helps us make
+		// the integration test work on e.g. Mac.
+		result.Profiles["unknown"] = struct{}{}
+	}
+	return result, nil
+}
diff --git a/services/device/deviced/internal/impl/proxy_invoker.go b/services/device/deviced/internal/impl/proxy_invoker.go
new file mode 100644
index 0000000..d15dec1
--- /dev/null
+++ b/services/device/deviced/internal/impl/proxy_invoker.go
@@ -0,0 +1,251 @@
+// 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 impl
+
+import (
+	"fmt"
+	"io"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/glob"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+	"v.io/v23/security/access"
+	"v.io/v23/vdl"
+	"v.io/v23/vdlroot/signature"
+	"v.io/v23/verror"
+)
+
+var (
+	errCantUpgradeServerCall = verror.Register(pkgPath+".errCantUpgradeServerCall", verror.NoRetry, "{1:}{2:} couldn't upgrade rpc.ServerCall to rpc.StreamServerCall{:_}")
+	errBadNumberOfResults    = verror.Register(pkgPath+".errBadNumberOfResults", verror.NoRetry, "{1:}{2:} unexpected number of result values. Got {3}, want 2.{:_}")
+	errBadErrorType          = verror.Register(pkgPath+".errBadErrorType", verror.NoRetry, "{1:}{2:} unexpected error type. Got {3}, want error.{:_}")
+	errWantSigInterfaceSlice = verror.Register(pkgPath+".errWantSigInterfaceSlice", verror.NoRetry, "{1:}{2:} unexpected result value type. Got {3}, want []signature.Interface.{:_}")
+	errWantSigMethod         = verror.Register(pkgPath+".errWantSigMethod", verror.NoRetry, "{1:}{2:} unexpected result value type. Got {3}, want signature.Method.{:_}")
+	errUnknownMethod         = verror.Register(pkgPath+".errUnknownMethod", verror.NoRetry, "{1:}{2:} unknown method{:_}")
+)
+
+// proxyInvoker is an rpc.Invoker implementation that proxies all requests
+// to a remote object, i.e. requests to <suffix> are forwarded to
+// <remote> transparently.
+//
+// remote is the name of the remote object.
+// access is the access tag require to access the object.
+// desc is used to determine the number of results for a given method.
+func newProxyInvoker(remote string, access access.Tag, desc []rpc.InterfaceDesc) *proxyInvoker {
+	methodNumResults := make(map[string]int)
+	for _, iface := range desc {
+		for _, method := range iface.Methods {
+			methodNumResults[method.Name] = len(method.OutArgs)
+		}
+	}
+	return &proxyInvoker{remote, access, methodNumResults}
+}
+
+type proxyInvoker struct {
+	remote           string
+	access           access.Tag
+	methodNumResults map[string]int
+}
+
+var _ rpc.Invoker = (*proxyInvoker)(nil)
+
+func (p *proxyInvoker) Prepare(_ *context.T, method string, numArgs int) (argptrs []interface{}, tags []*vdl.Value, _ error) {
+	// TODO(toddw): Change argptrs to be filled in with *vdl.Value, to avoid
+	// unnecessary type lookups.
+	argptrs = make([]interface{}, numArgs)
+	for i, _ := range argptrs {
+		var x interface{}
+		argptrs[i] = &x
+	}
+	tags = []*vdl.Value{vdl.ValueOf(p.access)}
+	return
+}
+
+func (p *proxyInvoker) Invoke(ctx *context.T, inCall rpc.StreamServerCall, method string, argptrs []interface{}) (results []interface{}, err error) {
+	// We accept any values as argument and pass them through to the remote
+	// server.
+	args := make([]interface{}, len(argptrs))
+	for i, ap := range argptrs {
+		args[i] = ap
+	}
+	client := v23.GetClient(ctx)
+
+	outCall, err := client.StartCall(ctx, p.remote, method, args)
+	if err != nil {
+		return nil, err
+	}
+
+	// Each RPC has a bi-directional stream, and there is no way to know in
+	// advance how much data will be sent in either direction, if any.
+	//
+	// This method (Invoke) must return when the remote server is done with
+	// the RPC, which is when outCall.Recv() returns EOF. When that happens,
+	// we need to call outCall.Finish() to get the return values, and then
+	// return these values to the client.
+	//
+	// While we are forwarding data from the server to the client, we must
+	// also forward data from the client to the server. This happens in a
+	// separate goroutine. This goroutine may return after Invoke has
+	// returned if the client doesn't call CloseSend() explicitly.
+	//
+	// Any error, other than EOF, will be returned to the client, if
+	// possible. The only situation where it is not possible to send an
+	// error to the client is when the error comes from forwarding data from
+	// the client to the server and Invoke has already returned or is about
+	// to return. In this case, the error is lost. So, it is possible that
+	// the client could successfully Send() data that the server doesn't
+	// actually receive if the server terminates the RPC while the data is
+	// in the proxy.
+	fwd := func(src, dst rpc.Stream, errors chan<- error) {
+		for {
+			var obj interface{}
+			switch err := src.Recv(&obj); err {
+			case io.EOF:
+				if call, ok := src.(rpc.ClientCall); ok {
+					if err := call.CloseSend(); err != nil {
+						errors <- err
+					}
+				}
+				return
+			case nil:
+				break
+			default:
+				errors <- err
+				return
+			}
+			if err := dst.Send(obj); err != nil {
+				errors <- err
+				return
+			}
+		}
+	}
+	errors := make(chan error, 2)
+	go fwd(inCall, outCall, errors)
+	fwd(outCall, inCall, errors)
+	select {
+	case err := <-errors:
+		return nil, err
+	default:
+	}
+
+	nResults, err := p.numResults(ctx, method)
+	if err != nil {
+		return nil, err
+	}
+
+	// We accept any return values, without type checking, and return them
+	// to the client.
+	res := make([]interface{}, nResults)
+	for i := 0; i < len(res); i++ {
+		var foo interface{}
+		res[i] = &foo
+	}
+	err = outCall.Finish(res...)
+	results = make([]interface{}, len(res))
+	for i, r := range res {
+		results[i] = *r.(*interface{})
+	}
+	return results, err
+}
+
+// TODO(toddw): Expose a helper function that performs all error checking based
+// on reflection, to simplify the repeated logic processing results.
+func (p *proxyInvoker) Signature(ctx *context.T, call rpc.ServerCall) ([]signature.Interface, error) {
+	streamCall, ok := call.(rpc.StreamServerCall)
+	if !ok {
+		return nil, verror.New(errCantUpgradeServerCall, ctx)
+	}
+	results, err := p.Invoke(ctx, streamCall, rpc.ReservedSignature, nil)
+	if err != nil {
+		return nil, err
+	}
+	if len(results) != 2 {
+		return nil, verror.New(errBadNumberOfResults, ctx, len(results))
+	}
+	if results[1] != nil {
+		err, ok := results[1].(error)
+		if !ok {
+			return nil, verror.New(errBadErrorType, ctx, fmt.Sprintf("%T", err))
+		}
+		return nil, err
+	}
+	var res []signature.Interface
+	if results[0] != nil {
+		sig, ok := results[0].([]signature.Interface)
+		if !ok {
+			return nil, verror.New(errWantSigInterfaceSlice, ctx, fmt.Sprintf("%T", sig))
+		}
+	}
+	return res, nil
+}
+
+func (p *proxyInvoker) MethodSignature(ctx *context.T, call rpc.ServerCall, method string) (signature.Method, error) {
+	empty := signature.Method{}
+	streamCall, ok := call.(rpc.StreamServerCall)
+	if !ok {
+		return empty, verror.New(errCantUpgradeServerCall, ctx)
+	}
+	results, err := p.Invoke(ctx, streamCall, rpc.ReservedMethodSignature, []interface{}{&method})
+	if err != nil {
+		return empty, err
+	}
+	if len(results) != 2 {
+		return empty, verror.New(errBadNumberOfResults, ctx, len(results))
+	}
+	if results[1] != nil {
+		err, ok := results[1].(error)
+		if !ok {
+			return empty, verror.New(errBadErrorType, ctx, fmt.Sprintf("%T", err))
+		}
+		return empty, err
+	}
+	var res signature.Method
+	if results[0] != nil {
+		sig, ok := results[0].(signature.Method)
+		if !ok {
+			return empty, verror.New(errWantSigMethod, ctx, fmt.Sprintf("%T", sig))
+		}
+	}
+	return res, nil
+}
+
+func (p *proxyInvoker) Globber() *rpc.GlobState {
+	return &rpc.GlobState{AllGlobber: p}
+}
+
+type call struct {
+	rpc.GlobServerCall
+}
+
+func (c *call) Recv(v interface{}) error {
+	return io.EOF
+}
+
+func (c *call) Send(v interface{}) error {
+	return c.SendStream().Send(v.(naming.GlobReply))
+}
+
+func (p *proxyInvoker) Glob__(ctx *context.T, serverCall rpc.GlobServerCall, g *glob.Glob) error {
+	pattern := g.String()
+	p.Invoke(ctx, &call{serverCall}, rpc.GlobMethod, []interface{}{&pattern})
+	return nil
+}
+
+// numResults returns the number of result values for the given method.
+func (p *proxyInvoker) numResults(ctx *context.T, method string) (int, error) {
+	switch method {
+	case rpc.GlobMethod:
+		return 1, nil
+	case rpc.ReservedSignature, rpc.ReservedMethodSignature:
+		return 2, nil
+	}
+	num, ok := p.methodNumResults[method]
+	if !ok {
+		return 0, verror.New(errUnknownMethod, ctx, method)
+	}
+	return num, nil
+}
diff --git a/services/device/deviced/internal/impl/proxy_invoker_test.go b/services/device/deviced/internal/impl/proxy_invoker_test.go
new file mode 100644
index 0000000..9c46e21
--- /dev/null
+++ b/services/device/deviced/internal/impl/proxy_invoker_test.go
@@ -0,0 +1,78 @@
+// 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 impl
+
+import (
+	"reflect"
+	"testing"
+
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/security/access"
+	libstats "v.io/v23/services/stats"
+	"v.io/x/ref/lib/xrpc"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/testutil"
+)
+
+// TODO(toddw): Add tests of Signature and MethodSignature.
+
+func TestProxyInvoker(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	// server1 is a normal server
+	server1, err := xrpc.NewServer(ctx, "", &dummy{}, nil)
+	if err != nil {
+		t.Fatalf("NewServer: %v", err)
+	}
+
+	// server2 proxies requests to <suffix> to server1/__debug/stats/<suffix>
+	disp := &proxyDispatcher{
+		remote: naming.JoinAddressName(server1.Status().Endpoints[0].String(), "__debug/stats"),
+		desc:   libstats.StatsServer(nil).Describe__(),
+	}
+	server2, err := xrpc.NewDispatchingServer(ctx, "", disp)
+	if err != nil {
+		t.Fatalf("NewServer: %v", err)
+	}
+	addr2 := server2.Status().Endpoints[0].String()
+
+	// Call Value()
+	name := naming.JoinAddressName(addr2, "system/start-time-rfc1123")
+	c := libstats.StatsClient(name)
+	if _, err := c.Value(ctx); err != nil {
+		t.Fatalf("%q.Value() error: %v", name, err)
+	}
+
+	// Call Glob()
+	results, _, err := testutil.GlobName(ctx, naming.JoinAddressName(addr2, "system"), "start-time-*")
+	if err != nil {
+		t.Fatalf("Glob failed: %v", err)
+	}
+	expected := []string{
+		"start-time-rfc1123",
+		"start-time-unix",
+	}
+	if !reflect.DeepEqual(results, expected) {
+		t.Errorf("unexpected results. Got %q, want %q", results, expected)
+	}
+}
+
+type dummy struct{}
+
+func (*dummy) Method(*context.T, rpc.ServerCall) error { return nil }
+
+type proxyDispatcher struct {
+	remote string
+	desc   []rpc.InterfaceDesc
+}
+
+func (d *proxyDispatcher) Lookup(ctx *context.T, suffix string) (interface{}, security.Authorizer, error) {
+	ctx.Infof("LOOKUP(%s): remote .... %s", suffix, d.remote)
+	return newProxyInvoker(naming.Join(d.remote, suffix), access.Debug, d.desc), nil, nil
+}
diff --git a/services/device/deviced/internal/impl/reaping/doc.go b/services/device/deviced/internal/impl/reaping/doc.go
new file mode 100644
index 0000000..b791c77
--- /dev/null
+++ b/services/device/deviced/internal/impl/reaping/doc.go
@@ -0,0 +1,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.
+
+package reaping
+
+// Test code for the device manager's facility to watch if applications
+// have stopped operation.
diff --git a/services/device/deviced/internal/impl/reaping/impl_test.go b/services/device/deviced/internal/impl/reaping/impl_test.go
new file mode 100644
index 0000000..9db563f
--- /dev/null
+++ b/services/device/deviced/internal/impl/reaping/impl_test.go
@@ -0,0 +1,19 @@
+// 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 reaping_test
+
+import (
+	"testing"
+
+	"v.io/x/ref/services/device/deviced/internal/impl/utiltest"
+)
+
+func TestMain(m *testing.M) {
+	utiltest.TestMainImpl(m)
+}
+
+func TestSuidHelper(t *testing.T) {
+	utiltest.TestSuidHelperImpl(t)
+}
diff --git a/services/device/deviced/internal/impl/reaping/instance_reaping_test.go b/services/device/deviced/internal/impl/reaping/instance_reaping_test.go
new file mode 100644
index 0000000..009050c
--- /dev/null
+++ b/services/device/deviced/internal/impl/reaping/instance_reaping_test.go
@@ -0,0 +1,131 @@
+// 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 reaping_test
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"syscall"
+	"testing"
+	"time"
+
+	"v.io/v23/services/device"
+	"v.io/x/ref"
+	"v.io/x/ref/services/device/deviced/internal/impl"
+	"v.io/x/ref/services/device/deviced/internal/impl/utiltest"
+	"v.io/x/ref/services/internal/servicetest"
+)
+
+func TestReapReconciliationViaAppCycle(t *testing.T) {
+	cleanup, ctx, sh, envelope, root, helperPath, _ := utiltest.StartupHelper(t)
+	defer cleanup()
+
+	// Start a device manager.
+	// (Since it will be restarted, use the VeyronCredentials environment
+	// to maintain the same set of credentials across runs)
+	dmCreds, err := ioutil.TempDir("", "TestReapReconciliationViaAppCycle")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(dmCreds)
+	dmEnv := []string{fmt.Sprintf("%v=%v", ref.EnvCredentials, dmCreds), fmt.Sprintf("%v=%v", impl.AppcycleReconciliation, "1")}
+
+	dmh := servicetest.RunCommand(t, sh, dmEnv, utiltest.DeviceManager, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
+	servicetest.ReadPID(t, dmh)
+	utiltest.ClaimDevice(t, ctx, "claimable", "dm", "mydevice", utiltest.NoPairingToken)
+
+	// Create the local server that the app uses to let us know it's ready.
+	pingCh, cleanup := utiltest.SetupPingServer(t, ctx)
+	defer cleanup()
+	utiltest.Resolve(t, ctx, "pingserver", 1, true)
+
+	// Create an envelope for the app.
+	*envelope = utiltest.EnvelopeFromShell(sh, nil, utiltest.App, "google naps", 0, 0, "appV1")
+
+	// Install the app.
+	appID := utiltest.InstallApp(t, ctx)
+
+	// Start three app instances.
+	instances := make([]string, 3)
+	for i, _ := range instances {
+		instances[i] = utiltest.LaunchApp(t, ctx, appID)
+		pingCh.VerifyPingArgs(t, utiltest.UserName(t), "default", "")
+	}
+
+	// Get pid of instance[0]
+	pid := utiltest.GetPid(t, ctx, appID, instances[0])
+
+	// Shutdown the first device manager.
+	syscall.Kill(dmh.Pid(), syscall.SIGINT)
+	dmh.Expect("dm terminated")
+	dmh.ExpectEOF()
+	dmh.Shutdown(os.Stderr, os.Stderr)
+	utiltest.ResolveExpectNotFound(t, ctx, "dm", false) // Ensure a clean slate.
+
+	// Kill instance[0] and wait until it exits before proceeding.
+	syscall.Kill(pid, 9)
+	timeOut := time.After(5 * time.Second)
+	for syscall.Kill(pid, 0) == nil {
+		select {
+		case <-timeOut:
+			t.Fatalf("Timed out waiting for PID %v to terminate", pid)
+		case <-time.After(time.Millisecond):
+			// Try again.
+		}
+	}
+
+	// Run another device manager to replace the dead one.
+	dmh = servicetest.RunCommand(t, sh, dmEnv, utiltest.DeviceManager, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
+	servicetest.ReadPID(t, dmh)
+	utiltest.Resolve(t, ctx, "dm", 1, true) // Verify the device manager has published itself.
+
+	// By now, we've reconciled the state of the tree with which processes
+	// are actually alive. instance-0 is not alive.
+	expected := []device.InstanceState{device.InstanceStateNotRunning, device.InstanceStateRunning, device.InstanceStateRunning}
+	for i, _ := range instances {
+		utiltest.VerifyState(t, ctx, expected[i], appID, instances[i])
+	}
+
+	// Start instance[0] over-again to show that an app marked not running
+	// by reconciliation can be restarted.
+	utiltest.RunApp(t, ctx, appID, instances[0])
+	pingCh.VerifyPingArgs(t, utiltest.UserName(t), "default", "")
+
+	// Kill instance[1]
+	pid = utiltest.GetPid(t, ctx, appID, instances[1])
+	syscall.Kill(pid, 9)
+
+	// Make a fourth instance. This forces a polling of processes so that
+	// the state is updated.
+	instances = append(instances, utiltest.LaunchApp(t, ctx, appID))
+	pingCh.VerifyPingArgs(t, utiltest.UserName(t), "default", "")
+
+	// Stop the fourth instance to make sure that there's no way we could
+	// still be running the polling loop before doing the below.
+	utiltest.TerminateApp(t, ctx, appID, instances[3])
+
+	// Verify that reaper picked up the previous instances and was watching
+	// instance[1]
+	expected = []device.InstanceState{device.InstanceStateRunning, device.InstanceStateNotRunning, device.InstanceStateRunning, device.InstanceStateDeleted}
+	for i, _ := range instances {
+		utiltest.VerifyState(t, ctx, expected[i], appID, instances[i])
+	}
+
+	utiltest.TerminateApp(t, ctx, appID, instances[2])
+
+	expected = []device.InstanceState{device.InstanceStateRunning, device.InstanceStateNotRunning, device.InstanceStateDeleted, device.InstanceStateDeleted}
+	for i, _ := range instances {
+		utiltest.VerifyState(t, ctx, expected[i], appID, instances[i])
+	}
+	utiltest.TerminateApp(t, ctx, appID, instances[0])
+
+	// TODO(rjkroege): Should be in a defer to ensure that the device
+	// manager is cleaned up even if the test fails in an exceptional way.
+	utiltest.VerifyNoRunningProcesses(t)
+	syscall.Kill(dmh.Pid(), syscall.SIGINT)
+	dmh.Expect("dm terminated")
+	dmh.ExpectEOF()
+}
diff --git a/services/device/deviced/internal/impl/restart_policy.go b/services/device/deviced/internal/impl/restart_policy.go
new file mode 100644
index 0000000..dc4a8af
--- /dev/null
+++ b/services/device/deviced/internal/impl/restart_policy.go
@@ -0,0 +1,55 @@
+// 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 impl
+
+import (
+	"time"
+
+	"v.io/v23/services/application"
+)
+
+// RestartPolicy instances provide a policy for deciding if an
+// application should be restarted on failure.
+type restartPolicy interface {
+	// decide determines if this application instance should be (re)started, returning
+	// true if the application should be be (re)started.
+	decide(envelope *application.Envelope, instance *instanceInfo) bool
+}
+
+type basicDecisionPolicy struct {
+}
+
+func newBasicRestartPolicy() restartPolicy {
+	return new(basicDecisionPolicy)
+}
+
+func (rp *basicDecisionPolicy) decide(envelope *application.Envelope, instance *instanceInfo) bool {
+	if envelope.Restarts == 0 {
+		return false
+	}
+	if envelope.Restarts < 0 {
+		return true
+	}
+
+	if instance.Restarts == 0 {
+		instance.RestartWindowBegan = time.Now()
+	}
+
+	endOfWindow := instance.RestartWindowBegan.Add(envelope.RestartTimeWindow)
+	if time.Now().After(endOfWindow) {
+		instance.Restarts = 1
+		instance.RestartWindowBegan = time.Now()
+		return true
+	}
+
+	if instance.Restarts < envelope.Restarts {
+		instance.Restarts++
+		instance.RestartWindowBegan = time.Now()
+		return true
+	}
+
+	return false
+
+}
diff --git a/services/device/deviced/internal/impl/restart_policy_test.go b/services/device/deviced/internal/impl/restart_policy_test.go
new file mode 100644
index 0000000..046377e
--- /dev/null
+++ b/services/device/deviced/internal/impl/restart_policy_test.go
@@ -0,0 +1,133 @@
+// 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 impl
+
+import (
+	"testing"
+	"time"
+
+	"v.io/v23/services/application"
+)
+
+// TestRestartPolicy verifies that the daemon mode restart policy operates
+// as intended.
+func TestRestartPolicy(t *testing.T) {
+	nbr := newBasicRestartPolicy()
+
+	type tV struct {
+		envelope *application.Envelope
+		info     *instanceInfo
+		wantInfo *instanceInfo
+		decision bool
+	}
+
+	testVectors := []tV{
+		// -1 means always restart.
+		{
+			&application.Envelope{
+				Restarts: -1,
+			},
+			&instanceInfo{
+				Restarts: 0,
+			},
+			&instanceInfo{
+				Restarts: 0,
+			},
+			true,
+		},
+		// 0 means restart exactly 0 times.
+		{
+			&application.Envelope{
+				Restarts: 0,
+			},
+			&instanceInfo{
+				Restarts: 0,
+			},
+			&instanceInfo{
+				Restarts: 0,
+			},
+			false,
+		},
+		// 1 means restart once (2 invocations total)
+		{
+			&application.Envelope{
+				Restarts:          1,
+				RestartTimeWindow: time.Hour,
+			},
+			&instanceInfo{
+				Restarts: 0,
+			},
+			&instanceInfo{
+				Restarts:           1,
+				RestartWindowBegan: time.Now(),
+			},
+			true,
+		},
+		// but only ever once.
+		{
+			&application.Envelope{
+				Restarts:          1,
+				RestartTimeWindow: time.Hour,
+			},
+			&instanceInfo{
+				Restarts:           1,
+				RestartWindowBegan: time.Date(2015, time.December, 25, 12, 0, 0, 0, time.UTC),
+			},
+			&instanceInfo{
+				Restarts:           1,
+				RestartWindowBegan: time.Date(2015, time.December, 25, 12, 0, 0, 0, time.UTC),
+			},
+			false,
+		},
+		// after time window, restart count is reset.
+		{
+			&application.Envelope{
+				Restarts:          1,
+				RestartTimeWindow: time.Minute,
+			},
+			&instanceInfo{
+				Restarts:           1,
+				RestartWindowBegan: time.Now().Add(-time.Hour),
+			},
+			&instanceInfo{
+				Restarts:           1,
+				RestartWindowBegan: time.Now(),
+			},
+			true,
+		},
+		// Every time a restart happens, the beginning of the window
+		// should be reset.
+		{
+			&application.Envelope{
+				Restarts:          2,
+				RestartTimeWindow: time.Minute,
+			},
+			&instanceInfo{
+				Restarts:           1,
+				RestartWindowBegan: time.Now().Add(-10 * time.Second),
+			},
+			&instanceInfo{
+				Restarts:           2,
+				RestartWindowBegan: time.Now(),
+			},
+			true,
+		},
+	}
+
+	for ti, tv := range testVectors {
+		if got, want := nbr.decide(tv.envelope, tv.info), tv.decision; got != want {
+			t.Errorf("Test case #%d: basicDecisionPolicy decide: got %v, want %v", ti, got, want)
+		}
+
+		if got, want := tv.info.Restarts, tv.wantInfo.Restarts; got != want {
+			t.Errorf("basicDecisionPolicy instanceInfo Restarts update got %v, want %v", got, want)
+		}
+
+		// Times should be "nearly" same.
+		if got, want := tv.info.RestartWindowBegan, tv.wantInfo.RestartWindowBegan; !((got.Sub(want) < time.Second) && (got.Sub(want) >= 0)) {
+			t.Errorf("Test case #%d: basicDecisionPolicy instanceInfo RestartTimeBegan got %v, want %v", ti, got, want)
+		}
+	}
+}
diff --git a/services/device/deviced/internal/impl/shell_darwin.go b/services/device/deviced/internal/impl/shell_darwin.go
new file mode 100644
index 0000000..fc0d92a
--- /dev/null
+++ b/services/device/deviced/internal/impl/shell_darwin.go
@@ -0,0 +1,9 @@
+// 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 impl
+
+const (
+	DateCommand = "/bin/date +%s.$RANDOM"
+)
diff --git a/services/device/deviced/internal/impl/shell_linux.go b/services/device/deviced/internal/impl/shell_linux.go
new file mode 100644
index 0000000..8e02672
--- /dev/null
+++ b/services/device/deviced/internal/impl/shell_linux.go
@@ -0,0 +1,9 @@
+// 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 impl
+
+const (
+	DateCommand = "/bin/date +%s%N"
+)
diff --git a/services/device/deviced/internal/impl/stats.go b/services/device/deviced/internal/impl/stats.go
new file mode 100644
index 0000000..caa8dbb
--- /dev/null
+++ b/services/device/deviced/internal/impl/stats.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.
+
+package impl
+
+import (
+	"sync"
+
+	"v.io/v23/naming"
+	libstats "v.io/x/ref/lib/stats"
+	"v.io/x/ref/lib/stats/counter"
+)
+
+// stats contains various exported stats we maintain for the device manager.
+type stats struct {
+	sync.Mutex
+	// How many times apps were run via the Run rpc.
+	runs *counter.Counter
+	// How many times apps were auto-restarted by the reaper.
+	restarts *counter.Counter
+	// Same as above, but broken down by instance.
+	// This is not of type lib/stats::Map since we want the detailed rate
+	// and delta stats per instance.
+	// TODO(caprita): Garbage-collect old instances?
+	runsPerInstance     map[string]*counter.Counter
+	restartsPerInstance map[string]*counter.Counter
+}
+
+func newStats() *stats {
+	return &stats{
+		runs:                libstats.NewCounter("runs"),
+		runsPerInstance:     make(map[string]*counter.Counter),
+		restarts:            libstats.NewCounter("restarts"),
+		restartsPerInstance: make(map[string]*counter.Counter),
+	}
+}
+
+func (s *stats) incrRestarts(instance string) {
+	s.Lock()
+	defer s.Unlock()
+	s.restarts.Incr(1)
+	perInstanceCtr, ok := s.restartsPerInstance[instance]
+	if !ok {
+		perInstanceCtr = libstats.NewCounter(naming.Join("restarts", instance))
+		s.restartsPerInstance[instance] = perInstanceCtr
+	}
+	perInstanceCtr.Incr(1)
+}
+
+func (s *stats) incrRuns(instance string) {
+	s.Lock()
+	defer s.Unlock()
+	s.runs.Incr(1)
+	perInstanceCtr, ok := s.runsPerInstance[instance]
+	if !ok {
+		perInstanceCtr = libstats.NewCounter(naming.Join("runs", instance))
+		s.runsPerInstance[instance] = perInstanceCtr
+	}
+	perInstanceCtr.Incr(1)
+}
diff --git a/services/device/deviced/internal/impl/tidyup.go b/services/device/deviced/internal/impl/tidyup.go
new file mode 100644
index 0000000..f9cdfc0
--- /dev/null
+++ b/services/device/deviced/internal/impl/tidyup.go
@@ -0,0 +1,257 @@
+// 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 impl
+
+import (
+	"fmt"
+	"os"
+	"path/filepath"
+	"strings"
+	"time"
+
+	"v.io/v23/context"
+	"v.io/v23/services/device"
+	"v.io/v23/verror"
+	"v.io/x/ref/services/device/internal/errors"
+)
+
+// This file contains the various routines that the device manager uses
+// to tidy up its persisted but no longer necessary state.
+
+const aboutOneDay = time.Hour * 24
+
+func oldEnoughToTidy(fi os.FileInfo, now time.Time) bool {
+	return fi.ModTime().Add(aboutOneDay).Before(now)
+}
+
+// AutomaticTidyingInterval defaults to 1 day.
+// Settable for tests.
+var AutomaticTidyingInterval = time.Hour * 24
+
+func shouldDelete(idir, suffix string, now time.Time) (bool, error) {
+	fi, err := os.Stat(filepath.Join(idir, suffix))
+	if err != nil {
+		return false, err
+	}
+
+	return oldEnoughToTidy(fi, now), nil
+}
+
+// Exposed for replacability in tests.
+var MockableNow = time.Now
+
+// shouldDeleteInstallation returns true if the tidying policy holds
+// for this installation.
+func shouldDeleteInstallation(idir string, now time.Time) (bool, error) {
+	return shouldDelete(idir, device.InstallationStateUninstalled.String(), now)
+}
+
+// shouldDeleteInstance returns true if the tidying policy holds
+// that the instance should be deleted.
+func shouldDeleteInstance(idir string, now time.Time) (bool, error) {
+	return shouldDelete(idir, device.InstanceStateDeleted.String(), now)
+}
+
+type pthError struct {
+	pth string
+	err error
+}
+
+func pruneDeletedInstances(ctx *context.T, root string, now time.Time) error {
+	paths, err := filepath.Glob(filepath.Join(root, "app*", "installation*", "instances", "instance*"))
+	if err != nil {
+		return err
+	}
+
+	allerrors := make([]pthError, 0)
+
+	for _, pth := range paths {
+		state, err := getInstanceState(pth)
+		if err != nil {
+			allerrors = append(allerrors, pthError{pth, err})
+			continue
+		}
+		if state != device.InstanceStateDeleted {
+			continue
+		}
+
+		shouldDelete, err := shouldDeleteInstance(pth, now)
+		if err != nil {
+			allerrors = append(allerrors, pthError{pth, err})
+			continue
+		}
+
+		if shouldDelete {
+			if err := suidHelper.deleteFileTree(ctx, pth, nil, nil); err != nil {
+				allerrors = append(allerrors, pthError{pth, err})
+			}
+		}
+	}
+	return processErrors(ctx, allerrors)
+}
+
+func processErrors(ctx *context.T, allerrors []pthError) error {
+	if len(allerrors) > 0 {
+		errormessages := make([]string, 0, len(allerrors))
+		for _, ep := range allerrors {
+			errormessages = append(errormessages, fmt.Sprintf("path: %s failed: %v", ep.pth, ep.err))
+		}
+		return verror.New(errors.ErrOperationFailed, ctx, "Some older instances could not be deleted: %s", strings.Join(errormessages, ", "))
+	}
+	return nil
+}
+
+func pruneUninstalledInstallations(ctx *context.T, root string, now time.Time) error {
+	// Read all the Uninstalled installations into a map.
+	installationPaths, err := filepath.Glob(filepath.Join(root, "app*", "installation*"))
+	if err != nil {
+		return err
+	}
+	pruneCandidates := make(map[string]struct{}, len(installationPaths))
+	for _, p := range installationPaths {
+		state, err := getInstallationState(p)
+		if err != nil {
+			return err
+		}
+
+		if state != device.InstallationStateUninstalled {
+			continue
+		}
+
+		pruneCandidates[p] = struct{}{}
+	}
+
+	instancePaths, err := filepath.Glob(filepath.Join(root, "app*", "installation*", "instances", "instance*", "installation"))
+	if err != nil {
+		return err
+	}
+
+	allerrors := make([]pthError, 0)
+
+	// Filter out installations that are still owned by an instance. Note
+	// that pruneUninstalledInstallations runs after
+	// pruneDeletedInstances so that freshly-pruned Instances will not
+	// retain the Installation.
+	for _, idir := range instancePaths {
+		installPath, err := os.Readlink(idir)
+		if err != nil {
+			allerrors = append(allerrors, pthError{idir, err})
+			continue
+		}
+
+		if _, ok := pruneCandidates[installPath]; ok {
+			delete(pruneCandidates, installPath)
+		}
+	}
+
+	// All remaining entries in pruneCandidates are not referenced by
+	// any instance.
+	for pth, _ := range pruneCandidates {
+		shouldDelete, err := shouldDeleteInstallation(pth, now)
+		if err != nil {
+			allerrors = append(allerrors, pthError{pth, err})
+			continue
+		}
+
+		if shouldDelete {
+			if err := suidHelper.deleteFileTree(ctx, pth, nil, nil); err != nil {
+				allerrors = append(allerrors, pthError{pth, err})
+			}
+		}
+	}
+	return processErrors(ctx, allerrors)
+}
+
+// pruneOldLogs removes logs more than a day old. Symlinks (the
+// cannonical log file name) the (newest) log files that they point to
+// are preserved.
+func pruneOldLogs(ctx *context.T, root string, now time.Time) error {
+	logPaths, err := filepath.Glob(filepath.Join(root, "app*", "installation*", "instances", "instance*", "logs", "*"))
+	if err != nil {
+		return err
+	}
+
+	pruneCandidates := make(map[string]struct{}, len(logPaths))
+	for _, p := range logPaths {
+		pruneCandidates[p] = struct{}{}
+	}
+
+	allerrors := make([]pthError, 0)
+	for p, _ := range pruneCandidates {
+		fi, err := os.Stat(p)
+		if err != nil {
+			allerrors = append(allerrors, pthError{p, err})
+			delete(pruneCandidates, p)
+			continue
+		}
+
+		if fi.Mode()&os.ModeSymlink != 0 {
+			delete(pruneCandidates, p)
+			target, err := os.Readlink(p)
+			if err != nil {
+				allerrors = append(allerrors, pthError{p, err})
+				continue
+			}
+			delete(pruneCandidates, target)
+			continue
+		}
+
+		if !oldEnoughToTidy(fi, now) {
+			delete(pruneCandidates, p)
+		}
+	}
+
+	for pth, _ := range pruneCandidates {
+		if err := suidHelper.deleteFileTree(ctx, pth, nil, nil); err != nil {
+			allerrors = append(allerrors, pthError{pth, err})
+		}
+	}
+	return processErrors(ctx, allerrors)
+}
+
+// tidyHarness runs device manager cleanup operations
+func tidyHarness(ctx *context.T, root string) error {
+	now := MockableNow()
+
+	if err := pruneDeletedInstances(ctx, root, now); err != nil {
+		return err
+	}
+
+	if err := pruneUninstalledInstallations(ctx, root, now); err != nil {
+		return err
+	}
+
+	return pruneOldLogs(ctx, root, now)
+}
+
+// tidyDaemon runs in a Go routine, processing requests to tidy
+// or tidying on a schedule.
+func tidyDaemon(ctx *context.T, c <-chan tidyRequests, root string) {
+	for {
+		select {
+		case req, ok := <-c:
+			if !ok {
+				return
+			}
+			req.bc <- tidyHarness(req.ctx, root)
+		case <-time.After(AutomaticTidyingInterval):
+			if err := tidyHarness(nil, root); err != nil {
+				ctx.Errorf("tidyDaemon failed to tidy: %v", err)
+			}
+		}
+
+	}
+}
+
+type tidyRequests struct {
+	ctx *context.T
+	bc  chan<- error
+}
+
+func newTidyingDaemon(ctx *context.T, root string) chan<- tidyRequests {
+	c := make(chan tidyRequests)
+	go tidyDaemon(ctx, c, root)
+	return c
+}
diff --git a/services/device/deviced/internal/impl/util.go b/services/device/deviced/internal/impl/util.go
new file mode 100644
index 0000000..fc0bc8a
--- /dev/null
+++ b/services/device/deviced/internal/impl/util.go
@@ -0,0 +1,247 @@
+// 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 impl
+
+import (
+	"crypto/rand"
+	"crypto/sha256"
+	"encoding/base64"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"reflect"
+	"regexp"
+	"strings"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/security"
+	"v.io/v23/services/application"
+	"v.io/v23/services/repository"
+	"v.io/v23/verror"
+	"v.io/x/ref/services/device/internal/config"
+	"v.io/x/ref/services/device/internal/errors"
+	"v.io/x/ref/services/internal/binarylib"
+)
+
+// TODO(caprita): Set these timeout in a more principled manner.
+const (
+	childReadyTimeout     = 40 * time.Second
+	childWaitTimeout      = 40 * time.Second
+	rpcContextTimeout     = time.Minute
+	rpcContextLongTimeout = 5 * time.Minute
+)
+
+func verifySignature(data []byte, publisher security.Blessings, sig security.Signature) error {
+	if !publisher.IsZero() {
+		h := sha256.Sum256(data)
+		if !sig.Verify(publisher.PublicKey(), h[:]) {
+			return verror.New(errors.ErrOperationFailed, nil)
+		}
+	}
+	return nil
+}
+
+func downloadBinary(ctx *context.T, publisher security.Blessings, bin *application.SignedFile, workspace, fileName string) error {
+	// TODO(gauthamt): Reduce the number of passes we make over the binary/package
+	// data to verify its checksum and signature.
+	data, _, err := binarylib.Download(ctx, bin.File)
+	if err != nil {
+		return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("Download(%v) failed: %v", bin.File, err))
+	}
+	if err := verifySignature(data, publisher, bin.Signature); err != nil {
+		return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("Publisher binary(%v) signature verification failed", bin.File))
+	}
+	path, perm := filepath.Join(workspace, fileName), os.FileMode(0755)
+	if err := ioutil.WriteFile(path, data, perm); err != nil {
+		return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("WriteFile(%v, %v) failed: %v", path, perm, err))
+	}
+	return nil
+}
+
+// TODO(caprita): share code between downloadBinary and downloadPackages.
+func downloadPackages(ctx *context.T, publisher security.Blessings, packages application.Packages, pkgDir string) error {
+	for localPkg, pkgName := range packages {
+		if localPkg == "" || localPkg[0] == '.' || strings.Contains(localPkg, string(filepath.Separator)) {
+			return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("invalid local package name: %q", localPkg))
+		}
+		path := filepath.Join(pkgDir, localPkg)
+		if err := binarylib.DownloadToFile(ctx, pkgName.File, path); err != nil {
+			return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("DownloadToFile(%q, %q) failed: %v", pkgName, path, err))
+		}
+		data, err := ioutil.ReadFile(path)
+		if err != nil {
+			return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("ReadPackage(%v) failed: %v", path, err))
+		}
+		// If a nonempty signature is present, verify it. (i.e., we accept unsigned packages.)
+		if !reflect.DeepEqual(pkgName.Signature, security.Signature{}) {
+			if err := verifySignature(data, publisher, pkgName.Signature); err != nil {
+				return verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("Publisher package(%v:%v) signature verification failed", localPkg, pkgName))
+			}
+		}
+	}
+	return nil
+}
+
+func fetchEnvelope(ctx *context.T, origin string) (*application.Envelope, error) {
+	stub := repository.ApplicationClient(origin)
+	profilesSet, err := Describe()
+	if err != nil {
+		return nil, verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("Failed to obtain profile labels: %v", err))
+	}
+	var profiles []string
+	for label := range profilesSet.Profiles {
+		profiles = append(profiles, label)
+	}
+	envelope, err := stub.Match(ctx, profiles)
+	if err != nil {
+		return nil, verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("Match(%v) failed: %v", profiles, err))
+	}
+	// If a publisher blessing is present, it must be from a publisher we recognize. If not,
+	// reject the envelope. Note that unsigned envelopes are accepted by this check.
+	// TODO: Implment a real ACL check based on publisher
+	names, rejected := publisherBlessingNames(ctx, envelope)
+	if len(names) == 0 && len(rejected) > 0 {
+		return nil, verror.New(errors.ErrOperationFailed, ctx, fmt.Sprintf("publisher %v in envelope %v was not recognized", rejected, envelope.Title))
+	}
+	return &envelope, nil
+}
+
+func publisherBlessingNames(ctx *context.T, env application.Envelope) ([]string, []security.RejectedBlessing) {
+	call := security.NewCall(&security.CallParams{
+		RemoteBlessings: env.Publisher,
+		LocalBlessings:  v23.GetPrincipal(ctx).BlessingStore().Default(),
+		LocalPrincipal:  v23.GetPrincipal(ctx),
+		Timestamp:       time.Now(),
+	})
+	names, rejected := security.RemoteBlessingNames(ctx, call)
+	if len(rejected) > 0 {
+		ctx.Infof("For envelope %v, rejected publisher blessings: %v", env.Title, rejected)
+	}
+	ctx.VI(2).Infof("accepted publisher blessings: %v", names)
+	return names, rejected
+}
+
+// LinkSelf creates a link to the current binary.
+func LinkSelf(workspace, fileName string) error {
+	path := filepath.Join(workspace, fileName)
+	self := os.Args[0]
+	if err := os.Link(self, path); err != nil {
+		return verror.New(errors.ErrOperationFailed, nil, fmt.Sprintf("Link(%v, %v) failed: %v", self, path, err))
+	}
+	return nil
+}
+
+func generateVersionDirName() string {
+	// TODO(caprita): Use generateID instead.
+	return time.Now().Format(time.RFC3339Nano)
+}
+
+func UpdateLink(target, link string) error {
+	newLink := link + ".new"
+	fi, err := os.Lstat(newLink)
+	if err == nil {
+		if err := os.Remove(fi.Name()); err != nil {
+			return verror.New(errors.ErrOperationFailed, nil, fmt.Sprintf("Remove(%v) failed: %v", fi.Name(), err))
+		}
+	}
+	if err := os.Symlink(target, newLink); err != nil {
+		return verror.New(errors.ErrOperationFailed, nil, fmt.Sprintf("Symlink(%v, %v) failed: %v", target, newLink, err))
+	}
+	if err := os.Rename(newLink, link); err != nil {
+		return verror.New(errors.ErrOperationFailed, nil, fmt.Sprintf("Rename(%v, %v) failed: %v", newLink, link, err))
+	}
+	return nil
+}
+
+func BaseCleanupDir(ctx *context.T, path, helper string) {
+	if helper != "" {
+		out, err := exec.Command(helper, "--rm", path).CombinedOutput()
+		if err != nil {
+			ctx.Errorf("exec.Command(%s %s %s).CombinedOutput() failed: %v", helper, "--rm", path, err)
+			return
+		}
+		if len(out) != 0 {
+			ctx.Errorf("exec.Command(%s %s %s).CombinedOutput() generated output: %v", helper, "--rm", path, string(out))
+		}
+	} else {
+		if err := os.RemoveAll(path); err != nil {
+			ctx.Errorf("RemoveAll(%v) failed: %v", path, err)
+		}
+	}
+}
+
+func PermsDir(c *config.State) string {
+	return filepath.Join(c.Root, "device-manager", "device-data", "acls")
+}
+
+// CleanupDir is defined like this so we can override its implementation for
+// tests. CleanupDir will use the helper to delete application state possibly
+// owned by different accounts if helper is provided.
+var CleanupDir = BaseCleanupDir
+
+// VanadiumEnvironment returns only the environment variables that are specific
+// to the Vanadium system.
+func VanadiumEnvironment(env []string) []string {
+	return filterEnvironment(env, allowedVarsRE, deniedVarsRE)
+}
+
+var allowedVarsRE = regexp.MustCompile("V23_.*|PAUSE_BEFORE_STOP|TMPDIR")
+
+var deniedVarsRE = regexp.MustCompile("V23_EXEC_VERSION")
+
+// filterEnvironment returns only the environment variables, specified by
+// the env parameter, whose names match the supplied regexp.
+func filterEnvironment(env []string, allow, deny *regexp.Regexp) []string {
+	var ret []string
+	for _, e := range env {
+		if eqIdx := strings.Index(e, "="); eqIdx > 0 {
+			key := e[:eqIdx]
+			if deny.MatchString(key) {
+				continue
+			}
+			if allow.MatchString(key) {
+				ret = append(ret, e)
+			}
+		}
+	}
+	return ret
+}
+
+// generateRandomString returns a cryptographically-strong random string.
+func generateRandomString() (string, error) {
+	b := make([]byte, 16)
+	_, err := rand.Read(b)
+	if err != nil {
+		return "", err
+	}
+	return base64.URLEncoding.EncodeToString(b), nil
+}
+
+// generateAgentSockDir returns the name of a newly created directory where to
+// create an agent socket.
+func generateAgentSockDir(rootDir string) (string, error) {
+	randomPattern, err := generateRandomString()
+	if err != nil {
+		return "", err
+	}
+	// We keep the socket files close to the root dir of the device
+	// manager installation to ensure that the socket file path is
+	// shorter than 108 characters (a requirement on Linux).
+	sockDir := filepath.Join(rootDir, "socks", randomPattern)
+	// TODO(caprita): For multi-user mode, we should chown the
+	// socket dir to the app user, and set up a unix group to permit
+	// access to the socket dir to the agent and device manager.
+	// For now, 'security' hinges on the fact that the name of the
+	// socket dir is unknown to everyone except the device manager,
+	// the agent, and the app.
+	if err := os.MkdirAll(sockDir, 0711); err != nil {
+		return "", fmt.Errorf("MkdirAll(%q) failed: %v", sockDir, err)
+	}
+	return sockDir, nil
+}
diff --git a/services/device/deviced/internal/impl/utiltest/app.go b/services/device/deviced/internal/impl/utiltest/app.go
new file mode 100644
index 0000000..3d36f15
--- /dev/null
+++ b/services/device/deviced/internal/impl/utiltest/app.go
@@ -0,0 +1,204 @@
+// 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 utiltest
+
+import (
+	"encoding/json"
+	"flag"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strings"
+	"testing"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/x/lib/vlog"
+	"v.io/x/ref/lib/exec"
+	"v.io/x/ref/lib/mgmt"
+	"v.io/x/ref/lib/signals"
+	"v.io/x/ref/lib/xrpc"
+	"v.io/x/ref/services/device/internal/suid"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
+	"v.io/x/ref/test/testutil"
+)
+
+const (
+	TestFlagName = "random_test_flag"
+)
+
+var flagValue = flag.String(TestFlagName, "default", "")
+
+func init() {
+	// The installer sets this flag on the installed device manager, so we
+	// need to ensure it's defined.
+	flag.String("name", "", "")
+}
+
+// appService defines a test service that the test app should be running.
+// TODO(caprita): Use this to make calls to the app and verify how Kill
+// interacts with an active service.
+type appService struct{}
+
+func (appService) Echo(_ *context.T, _ rpc.ServerCall, message string) (string, error) {
+	return message, nil
+}
+
+func (appService) Cat(_ *context.T, _ rpc.ServerCall, file string) (string, error) {
+	if file == "" || file[0] == filepath.Separator || file[0] == '.' {
+		return "", fmt.Errorf("illegal file name: %q", file)
+	}
+	bytes, err := ioutil.ReadFile(file)
+	if err != nil {
+		return "", err
+	}
+	return string(bytes), nil
+}
+
+type PingArgs struct {
+	Username, FlagValue, EnvValue,
+	DefaultPeerBlessings, PubBlessingPrefixes, InstanceName string
+	Pid int
+}
+
+// ping makes a RPC from the App back to the invoking device manager
+// carrying a PingArgs instance.
+func ping(ctx *context.T, flagValue string) {
+
+	ctx.Errorf("ping flagValue: %s", flagValue)
+
+	helperEnv := os.Getenv(suid.SavedArgs)
+	d := json.NewDecoder(strings.NewReader(helperEnv))
+	var savedArgs suid.ArgsSavedForTest
+	if err := d.Decode(&savedArgs); err != nil {
+		ctx.Fatalf("Failed to decode preserved argument %v: %v", helperEnv, err)
+	}
+	args := &PingArgs{
+		// TODO(rjkroege): Consider validating additional parameters
+		// from helper.
+		Username:             savedArgs.Uname,
+		FlagValue:            flagValue,
+		EnvValue:             os.Getenv(TestEnvVarName),
+		Pid:                  os.Getpid(),
+		DefaultPeerBlessings: v23.GetPrincipal(ctx).BlessingStore().ForPeer("nonexistent").String(),
+	}
+
+	if handle, err := exec.GetChildHandle(); err != nil {
+		vlog.Fatalf("Couldn't get Child Handle: %v", err)
+	} else {
+		args.PubBlessingPrefixes, _ = handle.Config.Get(mgmt.PublisherBlessingPrefixesKey)
+		args.InstanceName, _ = handle.Config.Get(mgmt.InstanceNameKey)
+	}
+
+	client := v23.GetClient(ctx)
+	if call, err := client.StartCall(ctx, "pingserver", "Ping", []interface{}{args}); err != nil {
+		ctx.Fatalf("StartCall failed: %v", err)
+	} else if err := call.Finish(); err != nil {
+		ctx.Fatalf("Finish failed: %v", err)
+	}
+}
+
+// Cat is an RPC invoked from the test harness process to the application process.
+func Cat(ctx *context.T, name, file string) (string, error) {
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
+	defer cancel()
+	client := v23.GetClient(ctx)
+	call, err := client.StartCall(ctx, name, "Cat", []interface{}{file})
+	if err != nil {
+		return "", err
+	}
+	var content string
+	if err := call.Finish(&content); err != nil {
+		return "", err
+	}
+	return content, nil
+}
+
+// App is a test application. It pings the invoking device manager with state information.
+var App = modules.Register(appFunc, "App")
+
+func appFunc(env *modules.Env, args ...string) error {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	if expected, got := 1, len(args); expected != got {
+		ctx.Fatalf("Unexpected number of arguments: expected %d, got %d", expected, got)
+	}
+	publishName := args[0]
+
+	_, err := xrpc.NewServer(ctx, publishName, new(appService), nil)
+	if err != nil {
+		ctx.Fatalf("NewServer(%v) failed: %v", publishName, err)
+	}
+	// Some of our tests look for log files, so make sure they are flushed
+	// to ensure that at least the files exist.
+	ctx.FlushLog()
+	ping(ctx, *flagValue)
+
+	<-signals.ShutdownOnSignals(ctx)
+	if err := ioutil.WriteFile("testfile", []byte("goodbye world"), 0600); err != nil {
+		ctx.Fatalf("Failed to write testfile: %v", err)
+	}
+	return nil
+}
+
+type PingServer struct {
+	ing chan PingArgs
+}
+
+// TODO(caprita): Set the timeout in a more principled manner.
+const pingTimeout = 60 * time.Second
+
+func (p PingServer) Ping(_ *context.T, _ rpc.ServerCall, arg PingArgs) error {
+	p.ing <- arg
+	return nil
+}
+
+// SetupPingServer creates a server listening for a ping from a child app; it
+// returns a channel on which the app's ping message is returned, and a cleanup
+// function.
+func SetupPingServer(t *testing.T, ctx *context.T) (PingServer, func()) {
+	pingCh := make(chan PingArgs, 1)
+	server, err := xrpc.NewServer(ctx, "pingserver", PingServer{pingCh}, security.AllowEveryone())
+	if err != nil {
+		t.Fatalf("NewServer(%q, <dispatcher>) failed: %v", "pingserver", err)
+	}
+	return PingServer{pingCh}, func() {
+		if err := server.Stop(); err != nil {
+			t.Fatalf("Stop() failed: %v", err)
+		}
+	}
+}
+
+func (p PingServer) WaitForPingArgs(t *testing.T) PingArgs {
+	var args PingArgs
+	select {
+	case args = <-p.ing:
+	case <-time.After(pingTimeout):
+		t.Fatalf(testutil.FormatLogLine(2, "failed to get ping"))
+	}
+	return args
+}
+
+func (p PingServer) VerifyPingArgs(t *testing.T, username, flagValue, envValue string) PingArgs {
+	args := p.WaitForPingArgs(t)
+	if args.Username != username || args.FlagValue != flagValue || args.EnvValue != envValue {
+		t.Fatalf(testutil.FormatLogLine(2, "got ping args %q, expected [username = %v, flag value = %v, env value = %v]", args, username, flagValue, envValue))
+	}
+	return args // Useful for tests that want to check other values in the PingArgs result
+}
+
+// HangingApp is the same as App, except that it does not exit properly after
+// being stopped.
+var HangingApp = modules.Register(func(env *modules.Env, args ...string) error {
+	err := appFunc(env, args...)
+	time.Sleep(24 * time.Hour)
+	return err
+}, "HangingApp")
diff --git a/services/device/deviced/internal/impl/utiltest/helpers.go b/services/device/deviced/internal/impl/utiltest/helpers.go
new file mode 100644
index 0000000..3c1a163
--- /dev/null
+++ b/services/device/deviced/internal/impl/utiltest/helpers.go
@@ -0,0 +1,803 @@
+// 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 utiltest
+
+import (
+	"errors"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"os/user"
+	"path/filepath"
+	"reflect"
+	"regexp"
+	"sort"
+	"strings"
+	"syscall"
+	"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/v23/services/application"
+	"v.io/v23/services/device"
+	"v.io/v23/services/logreader"
+	"v.io/v23/services/pprof"
+	"v.io/v23/services/stats"
+	"v.io/v23/verror"
+	"v.io/x/ref/internal/logger"
+	"v.io/x/ref/lib/xrpc"
+	_ "v.io/x/ref/runtime/factories/roaming"
+	"v.io/x/ref/services/device/deviced/internal/impl"
+	"v.io/x/ref/services/device/deviced/internal/versioning"
+	"v.io/x/ref/services/internal/binarylib"
+	"v.io/x/ref/services/internal/servicetest"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
+	"v.io/x/ref/test/testutil"
+)
+
+const (
+	// TODO(caprita): Set the timeout in a more principled manner.
+	killTimeout = 20 * time.Second
+)
+
+func init() {
+	impl.Describe = func() (descr device.Description, err error) {
+		return device.Description{Profiles: map[string]struct{}{"test-profile": struct{}{}}}, nil
+	}
+
+	impl.CleanupDir = func(ctx *context.T, dir, helper string) {
+		if dir == "" {
+			return
+		}
+		parentDir, base := filepath.Dir(dir), filepath.Base(dir)
+		var renamed string
+		if helper != "" {
+			renamed = filepath.Join(parentDir, "helper_deleted_"+base)
+		} else {
+			renamed = filepath.Join(parentDir, "deleted_"+base)
+		}
+		if err := os.Rename(dir, renamed); err != nil {
+			ctx.Errorf("Rename(%v, %v) failed: %v", dir, renamed, err)
+		}
+	}
+
+	// Return a sequence of times separated by 25 hours.
+	impl.MockableNow = func() time.Time {
+		now := time.Now()
+		impl.MockableNow = func() time.Time {
+			now = now.Add(time.Hour * 25)
+			return now
+		}
+		return now
+	}
+
+}
+
+func EnvelopeFromShell(sh *modules.Shell, env []string, prog modules.Program, title string, retries int, window time.Duration, args ...string) application.Envelope {
+	args, nenv := sh.ProgramEnvelope(env, prog, args...)
+	return application.Envelope{
+		Title: title,
+		Args:  args[1:],
+		// TODO(caprita): revisit how the environment is sanitized for arbirary
+		// apps.
+		Env:               impl.VanadiumEnvironment(nenv),
+		Binary:            application.SignedFile{File: MockBinaryRepoName},
+		Restarts:          int32(retries),
+		RestartTimeWindow: window,
+	}
+}
+
+func SignedEnvelopeFromShell(ctx *context.T, sh *modules.Shell, env []string, prog modules.Program, title string, retries int, window time.Duration, args ...string) (application.Envelope, error) {
+	envelope := EnvelopeFromShell(sh, env, prog, title, retries, window, args...)
+	reader, cleanup, err := mockBinaryBytesReader()
+	defer cleanup()
+	sig, err := binarylib.Sign(ctx, reader)
+	if err != nil {
+		return application.Envelope{}, err
+	}
+	envelope.Binary.Signature = *sig
+
+	// Add a publisher blessing
+	p := v23.GetPrincipal(ctx)
+	publisher, err := p.Bless(p.PublicKey(), p.BlessingStore().Default(), "angryapp.v10", security.UnconstrainedUse())
+	if err != nil {
+		return application.Envelope{}, err
+	}
+	envelope.Publisher = publisher
+	return envelope, nil
+}
+
+// ResolveExpectNotFound verifies that the given name is not in the mounttable.
+func ResolveExpectNotFound(t *testing.T, ctx *context.T, name string, retry bool) {
+	expectErr := naming.ErrNoSuchName.ID
+	for {
+		me, err := v23.GetNamespace(ctx).Resolve(ctx, name)
+		if err == nil || verror.ErrorID(err) != expectErr {
+			if retry {
+				time.Sleep(10 * time.Millisecond)
+				continue
+			}
+			if err == nil {
+				t.Fatalf(testutil.FormatLogLine(2, "Resolve(%v) succeeded with results %v when it was expected to fail", name, me.Names()))
+			} else {
+				t.Fatalf(testutil.FormatLogLine(2, "Resolve(%v) failed with error %v, expected error ID %v", name, err, expectErr))
+			}
+		} else {
+			return
+		}
+	}
+}
+
+// Resolve looks up the given name in the mounttable.
+func Resolve(t *testing.T, ctx *context.T, name string, replicas int, retry bool) []string {
+	for {
+		me, err := v23.GetNamespace(ctx).Resolve(ctx, name)
+		if err != nil {
+			if retry {
+				time.Sleep(10 * time.Millisecond)
+				continue
+			} else {
+				t.Fatalf("Resolve(%v) failed: %v", name, err)
+			}
+		}
+
+		filteredResults := []string{}
+		for _, r := range me.Names() {
+			if strings.Index(r, "@tcp") != -1 {
+				filteredResults = append(filteredResults, r)
+			}
+		}
+		// We are going to get a websocket and a tcp endpoint for each replica.
+		if want, got := replicas, len(filteredResults); want != got {
+			t.Fatalf("Resolve(%v) expected %d result(s), got %d instead", name, want, got)
+		}
+		return filteredResults
+	}
+}
+
+// The following set of functions are convenience wrappers around Update and
+// Revert for device manager.
+
+func DeviceStub(name string) device.DeviceClientMethods {
+	deviceName := naming.Join(name, "device")
+	return device.DeviceClient(deviceName)
+}
+
+func ClaimDevice(t *testing.T, ctx *context.T, claimableName, deviceName, extension, pairingToken string) {
+	// Setup blessings to be granted to the claimed device
+	g := &granter{extension: extension}
+	s := options.SkipServerEndpointAuthorization{}
+	// Call the Claim RPC: Skip server authorization because the unclaimed
+	// device presents nothing that can be used to recognize it.
+	if err := device.ClaimableClient(claimableName).Claim(ctx, pairingToken, g, s); err != nil {
+		t.Fatalf(testutil.FormatLogLine(2, "%q.Claim(%q) failed: %v [%v]", claimableName, pairingToken, verror.ErrorID(err), err))
+	}
+	// Wait for the device to remount itself with the device service after
+	// being claimed.
+	start := time.Now()
+	for {
+		_, err := v23.GetNamespace(ctx).Resolve(ctx, deviceName)
+		if err == nil {
+			return
+		}
+		ctx.VI(4).Infof("Resolve(%q) failed: %v", err)
+		time.Sleep(time.Millisecond)
+		if elapsed := time.Since(start); elapsed > time.Minute {
+			t.Fatalf("Device hasn't remounted itself in %v since it was claimed", elapsed)
+		}
+	}
+}
+
+func ClaimDeviceExpectError(t *testing.T, ctx *context.T, name, extension, pairingToken string, errID verror.ID) {
+	// Setup blessings to be granted to the claimed device
+	g := &granter{extension: extension}
+	s := options.SkipServerEndpointAuthorization{}
+	// Call the Claim RPC
+	if err := device.ClaimableClient(name).Claim(ctx, pairingToken, g, s); verror.ErrorID(err) != errID {
+		t.Fatalf(testutil.FormatLogLine(2, "%q.Claim(%q) expected to fail with %v, got %v [%v]", name, pairingToken, errID, verror.ErrorID(err), err))
+	}
+}
+
+func UpdateDeviceExpectError(t *testing.T, ctx *context.T, name string, errID verror.ID) {
+	if err := DeviceStub(name).Update(ctx); verror.ErrorID(err) != errID {
+		t.Fatalf(testutil.FormatLogLine(2, "%q.Update expected to fail with %v, got %v [%v]", name, errID, verror.ErrorID(err), err))
+	}
+}
+
+func UpdateDevice(t *testing.T, ctx *context.T, name string) {
+	if err := DeviceStub(name).Update(ctx); err != nil {
+		t.Fatalf(testutil.FormatLogLine(2, "%q.Update() failed: %v [%v]", name, verror.ErrorID(err), err))
+	}
+}
+
+func RevertDeviceExpectError(t *testing.T, ctx *context.T, name string, errID verror.ID) {
+	if err := DeviceStub(name).Revert(ctx); verror.ErrorID(err) != errID {
+		t.Fatalf(testutil.FormatLogLine(2, "%q.Revert() expected to fail with %v, got %v [%v]", name, errID, verror.ErrorID(err), err))
+	}
+}
+
+func RevertDevice(t *testing.T, ctx *context.T, name string) {
+	if err := DeviceStub(name).Revert(ctx); err != nil {
+		t.Fatalf(testutil.FormatLogLine(2, "%q.Revert() failed: %v [%v]", name, verror.ErrorID(err), err))
+	}
+}
+
+func KillDevice(t *testing.T, ctx *context.T, name string) {
+	if err := DeviceStub(name).Kill(ctx, killTimeout); err != nil {
+		t.Fatalf(testutil.FormatLogLine(2, "%q.Kill(%v) failed: %v [%v]", name, killTimeout, verror.ErrorID(err), err))
+	}
+}
+
+func ShutdownDevice(t *testing.T, ctx *context.T, name string) {
+	if err := DeviceStub(name).Delete(ctx); err != nil {
+		t.Fatalf(testutil.FormatLogLine(2, "%q.Delete() failed: %v [%v]", name, verror.ErrorID(err), err))
+	}
+}
+
+// The following set of functions are convenience wrappers around various app
+// management methods.
+
+func Ocfg(opt []interface{}) device.Config {
+	for _, o := range opt {
+		if c, ok := o.(device.Config); ok {
+			return c
+		}
+	}
+	return device.Config{}
+}
+
+func Opkg(opt []interface{}) application.Packages {
+	for _, o := range opt {
+		if c, ok := o.(application.Packages); ok {
+			return c
+		}
+	}
+	return application.Packages{}
+}
+
+func AppStub(nameComponents ...string) device.ApplicationClientMethods {
+	appsName := "dm/apps"
+	appName := naming.Join(append([]string{appsName}, nameComponents...)...)
+	return device.ApplicationClient(appName)
+}
+
+func StatsStub(nameComponents ...string) stats.StatsClientMethods {
+	statsName := naming.Join(nameComponents...)
+	return stats.StatsClient(statsName)
+}
+
+func InstallApp(t *testing.T, ctx *context.T, opt ...interface{}) string {
+	appID, err := AppStub().Install(ctx, MockApplicationRepoName, Ocfg(opt), Opkg(opt))
+	if err != nil {
+		t.Fatalf(testutil.FormatLogLine(2, "Install failed: %v [%v]", verror.ErrorID(err), err))
+	}
+	return appID
+}
+
+func InstallAppExpectError(t *testing.T, ctx *context.T, expectedError verror.ID, opt ...interface{}) {
+	if _, err := AppStub().Install(ctx, MockApplicationRepoName, Ocfg(opt), Opkg(opt)); err == nil || verror.ErrorID(err) != expectedError {
+		t.Fatalf(testutil.FormatLogLine(2, "Install expected to fail with %v, got %v [%v]", expectedError, verror.ErrorID(err), err))
+	}
+}
+
+type granter struct {
+	rpc.CallOpt
+	p         security.Principal
+	extension string
+}
+
+func (g *granter) Grant(ctx *context.T, call security.Call) (security.Blessings, error) {
+	p := call.LocalPrincipal()
+	return p.Bless(call.RemoteBlessings().PublicKey(), p.BlessingStore().Default(), g.extension, security.UnconstrainedUse())
+}
+
+func LaunchAppImpl(t *testing.T, ctx *context.T, appID, grant string) (string, error) {
+	instanceID, err := NewInstanceImpl(t, ctx, appID, grant)
+	if err != nil {
+		return "", err
+	}
+	return instanceID, AppStub(appID, instanceID).Run(ctx)
+}
+
+func NewInstanceImpl(t *testing.T, ctx *context.T, appID, grant string) (string, error) {
+	ctx, delete := context.WithCancel(ctx)
+	defer delete()
+
+	call, err := AppStub(appID).Instantiate(ctx)
+	if err != nil {
+		return "", err
+	}
+	for call.RecvStream().Advance() {
+		switch msg := call.RecvStream().Value().(type) {
+		case device.BlessServerMessageInstancePublicKey:
+			p := v23.GetPrincipal(ctx)
+			pubKey, err := security.UnmarshalPublicKey(msg.Value)
+			if err != nil {
+				return "", err
+			}
+			blessings, err := p.Bless(pubKey, p.BlessingStore().Default(), grant, security.UnconstrainedUse())
+			if err != nil {
+				return "", errors.New("bless failed")
+			}
+			call.SendStream().Send(device.BlessClientMessageAppBlessings{Value: blessings})
+		default:
+			return "", fmt.Errorf("newInstanceImpl: received unexpected message: %#v", msg)
+		}
+	}
+	var instanceID string
+	if instanceID, err = call.Finish(); err != nil {
+		return "", err
+	}
+	return instanceID, nil
+}
+
+func LaunchApp(t *testing.T, ctx *context.T, appID string) string {
+	instanceID, err := LaunchAppImpl(t, ctx, appID, "forapp")
+	if err != nil {
+		t.Fatalf(testutil.FormatLogLine(2, "launching %v failed: %v [%v]", appID, verror.ErrorID(err), err))
+	}
+	return instanceID
+}
+
+func LaunchAppExpectError(t *testing.T, ctx *context.T, appID string, expectedError verror.ID) {
+	if _, err := LaunchAppImpl(t, ctx, appID, "forapp"); err == nil || verror.ErrorID(err) != expectedError {
+		t.Fatalf(testutil.FormatLogLine(2, "launching %v expected to fail with %v, got %v [%v]", appID, expectedError, verror.ErrorID(err), err))
+	}
+}
+
+func TerminateApp(t *testing.T, ctx *context.T, appID, instanceID string) {
+	if err := AppStub(appID, instanceID).Kill(ctx, killTimeout); err != nil {
+		t.Fatalf(testutil.FormatLogLine(2, "Kill(%v/%v) failed: %v [%v]", appID, instanceID, verror.ErrorID(err), err))
+	}
+	if err := AppStub(appID, instanceID).Delete(ctx); err != nil {
+		t.Fatalf(testutil.FormatLogLine(2, "Delete(%v/%v) failed: %v [%v]", appID, instanceID, verror.ErrorID(err), err))
+	}
+}
+
+func KillApp(t *testing.T, ctx *context.T, appID, instanceID string) {
+	if err := AppStub(appID, instanceID).Kill(ctx, killTimeout); err != nil {
+		t.Fatalf(testutil.FormatLogLine(2, "Kill(%v/%v) failed: %v [%v]", appID, instanceID, verror.ErrorID(err), err))
+	}
+}
+
+func DeleteApp(t *testing.T, ctx *context.T, appID, instanceID string) {
+	if err := AppStub(appID, instanceID).Delete(ctx); err != nil {
+		t.Fatalf(testutil.FormatLogLine(2, "Delete(%v/%v) failed: %v [%v]", appID, instanceID, verror.ErrorID(err), err))
+	}
+}
+
+func RunApp(t *testing.T, ctx *context.T, appID, instanceID string) {
+	if err := AppStub(appID, instanceID).Run(ctx); err != nil {
+		t.Fatalf(testutil.FormatLogLine(2, "Run(%v/%v) failed: %v [%v]", appID, instanceID, verror.ErrorID(err), err))
+	}
+}
+
+func RunAppExpectError(t *testing.T, ctx *context.T, appID, instanceID string, expectedError verror.ID) {
+	if err := AppStub(appID, instanceID).Run(ctx); err == nil || verror.ErrorID(err) != expectedError {
+		t.Fatalf(testutil.FormatLogLine(2, "Run(%v/%v) expected to fail with %v, got %v [%v]", appID, instanceID, expectedError, verror.ErrorID(err), err))
+	}
+}
+
+func UpdateInstance(t *testing.T, ctx *context.T, appID, instanceID string) {
+	if err := AppStub(appID, instanceID).Update(ctx); err != nil {
+		t.Fatalf(testutil.FormatLogLine(2, "Update(%v/%v) failed: %v [%v]", appID, instanceID, verror.ErrorID(err), err))
+	}
+}
+
+func UpdateInstanceExpectError(t *testing.T, ctx *context.T, appID, instanceID string, expectedError verror.ID) {
+	if err := AppStub(appID, instanceID).Update(ctx); err == nil || verror.ErrorID(err) != expectedError {
+		t.Fatalf(testutil.FormatLogLine(2, "Update(%v/%v) expected to fail with %v, got %v [%v]", appID, instanceID, expectedError, verror.ErrorID(err), err))
+	}
+}
+
+func UpdateApp(t *testing.T, ctx *context.T, appID string) {
+	if err := AppStub(appID).Update(ctx); err != nil {
+		t.Fatalf(testutil.FormatLogLine(2, "Update(%v) failed: %v [%v]", appID, verror.ErrorID(err), err))
+	}
+}
+
+func UpdateAppExpectError(t *testing.T, ctx *context.T, appID string, expectedError verror.ID) {
+	if err := AppStub(appID).Update(ctx); err == nil || verror.ErrorID(err) != expectedError {
+		t.Fatalf(testutil.FormatLogLine(2, "Update(%v) expected to fail with %v, got %v [%v]", appID, expectedError, verror.ErrorID(err), err))
+	}
+}
+
+func RevertApp(t *testing.T, ctx *context.T, appID string) {
+	if err := AppStub(appID).Revert(ctx); err != nil {
+		t.Fatalf(testutil.FormatLogLine(2, "Revert(%v) failed: %v [%v]", appID, verror.ErrorID(err), err))
+	}
+}
+
+func RevertAppExpectError(t *testing.T, ctx *context.T, appID string, expectedError verror.ID) {
+	if err := AppStub(appID).Revert(ctx); err == nil || verror.ErrorID(err) != expectedError {
+		t.Fatalf(testutil.FormatLogLine(2, "Revert(%v) expected to fail with %v, got %v [%v]", appID, expectedError, verror.ErrorID(err), err))
+	}
+}
+
+func UninstallApp(t *testing.T, ctx *context.T, appID string) {
+	if err := AppStub(appID).Uninstall(ctx); err != nil {
+		t.Fatalf(testutil.FormatLogLine(2, "Uninstall(%v) failed: %v [%v]", appID, verror.ErrorID(err), err))
+	}
+}
+
+func Debug(t *testing.T, ctx *context.T, nameComponents ...string) string {
+	dbg, err := AppStub(nameComponents...).Debug(ctx)
+	if err != nil {
+		t.Fatalf(testutil.FormatLogLine(2, "Debug(%v) failed: %v [%v]", nameComponents, verror.ErrorID(err), err))
+	}
+	return dbg
+}
+
+func VerifyDeviceState(t *testing.T, ctx *context.T, want device.InstanceState, name string) string {
+	s, err := DeviceStub(name).Status(ctx)
+	if err != nil {
+		t.Fatalf(testutil.FormatLogLine(2, "Status(%v) failed: %v [%v]", name, verror.ErrorID(err), err))
+	}
+	status, ok := s.(device.StatusDevice)
+	if !ok {
+		t.Fatalf(testutil.FormatLogLine(2, "Status(%v) returned unknown type: %T", name, s))
+	}
+	if status.Value.State != want {
+		t.Fatalf(testutil.FormatLogLine(2, "Status(%v) state: wanted %v, got %v", name, want, status.Value.State))
+	}
+	return status.Value.Version
+}
+
+func Status(t *testing.T, ctx *context.T, nameComponents ...string) device.Status {
+	s, err := AppStub(nameComponents...).Status(ctx)
+	if err != nil {
+		t.Errorf(testutil.FormatLogLine(3, "Status(%v) failed: %v [%v]", nameComponents, verror.ErrorID(err), err))
+	}
+	return s
+}
+
+func VerifyState(t *testing.T, ctx *context.T, want interface{}, nameComponents ...string) string {
+	s := Status(t, ctx, nameComponents...)
+	var (
+		state   interface{}
+		version string
+	)
+	switch s := s.(type) {
+	case device.StatusInstance:
+		state = s.Value.State
+		version = s.Value.Version
+	case device.StatusInstallation:
+		state = s.Value.State
+		version = s.Value.Version
+	default:
+		t.Errorf(testutil.FormatLogLine(2, "Status(%v) returned unknown type: %T", nameComponents, s))
+	}
+	if state != want {
+		t.Errorf(testutil.FormatLogLine(2, "Status(%v) state: wanted %v (%T), got %v (%T)", nameComponents, want, want, state, state))
+	}
+	return version
+}
+
+// Code to make Association lists sortable.
+type byIdentity []device.Association
+
+func (a byIdentity) Len() int           { return len(a) }
+func (a byIdentity) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
+func (a byIdentity) Less(i, j int) bool { return a[i].IdentityName < a[j].IdentityName }
+
+func CompareAssociations(t *testing.T, got, expected []device.Association) {
+	sort.Sort(byIdentity(got))
+	sort.Sort(byIdentity(expected))
+	if !reflect.DeepEqual(got, expected) {
+		t.Errorf("ListAssociations() got %v, expected %v", got, expected)
+	}
+}
+
+// GenerateSuidHelperScript builds a script to execute the test target as
+// a suidhelper instance and returns the path to the script.
+func GenerateSuidHelperScript(t *testing.T, root string) string {
+	output := "#!/bin/bash\n"
+	output += "V23_SUIDHELPER_TEST=1"
+	output += " "
+	output += "exec " + os.Args[0] + " -minuid=1 -test.run=TestSuidHelper \"$@\""
+	output += "\n"
+
+	logger.Global().VI(1).Infof("script\n%s", output)
+
+	if err := os.MkdirAll(root, 0755); err != nil {
+		t.Fatalf("MkdirAll failed: %v", err)
+	}
+	path := filepath.Join(root, "helper.sh")
+	if err := ioutil.WriteFile(path, []byte(output), 0755); err != nil {
+		t.Fatalf("WriteFile(%v) failed: %v", path, err)
+	}
+	return path
+}
+
+// GenerateAgentScript creates a simple script that acts as the security agent
+// for tests.  It blackholes arguments meant for the agent.
+func GenerateAgentScript(t *testing.T, root string) string {
+	output := `
+#!/bin/bash
+ARGS=$*
+for ARG in ${ARGS[@]}; do
+  if [[ ${ARG} = -- ]]; then
+    ARGS=(${ARGS[@]/$ARG})
+    break
+  elif [[ ${ARG} == --* ]]; then
+    ARGS=(${ARGS[@]/$ARG})
+  else
+    break
+  fi
+done
+
+exec ${ARGS[@]}
+`
+	if err := os.MkdirAll(root, 0755); err != nil {
+		t.Fatalf("MkdirAll failed: %v", err)
+	}
+	path := filepath.Join(root, "agenthelper.sh")
+	if err := ioutil.WriteFile(path, []byte(output), 0755); err != nil {
+		t.Fatalf("WriteFile(%v) failed: %v", path, err)
+	}
+	return path
+}
+
+func CtxWithNewPrincipal(t *testing.T, ctx *context.T, idp *testutil.IDProvider, extension string) *context.T {
+	ret, err := v23.WithPrincipal(ctx, testutil.NewPrincipal())
+	if err != nil {
+		t.Fatalf(testutil.FormatLogLine(2, "v23.WithPrincipal failed: %v", err))
+	}
+	if err := idp.Bless(v23.GetPrincipal(ret), extension); err != nil {
+		t.Fatalf(testutil.FormatLogLine(2, "idp.Bless(?, %q) failed: %v", extension, err))
+	}
+	return ret
+}
+
+// TODO(rjkroege): This helper is generally useful. Use it to reduce
+// boiler plate across all device manager tests.
+func StartupHelper(t *testing.T) (_ func(), ctx *context.T, _ *modules.Shell, _ *application.Envelope, _ string, _ string, _ *testutil.IDProvider) {
+	ctx, shutdown := test.V23Init()
+
+	// Make a new identity context.
+	idp := testutil.NewIDProvider("root")
+	ctx = CtxWithNewPrincipal(t, ctx, idp, "self")
+
+	sh, deferFn := servicetest.CreateShellAndMountTable(t, ctx, nil)
+
+	// Set up mock application and binary repositories.
+	envelope, envCleanup := StartMockRepos(t, ctx)
+
+	root, rootCleanup := servicetest.SetupRootDir(t, "devicemanager")
+	if err := versioning.SaveCreatorInfo(ctx, root); err != nil {
+		t.Fatal(err)
+	}
+
+	// Create a script wrapping the test target that implements suidhelper.
+	helperPath := GenerateSuidHelperScript(t, root)
+
+	return func() {
+		rootCleanup()
+		envCleanup()
+		deferFn()
+		shutdown()
+	}, ctx, sh, envelope, root, helperPath, idp
+}
+
+type GlobTestVector struct {
+	Name, Pattern string
+	Expected      []string
+}
+
+type globTestRegexHelper struct {
+	logFileTimeStampRE               *regexp.Regexp
+	logFileTrimInfoRE                *regexp.Regexp
+	logFileRemoveErrorFatalWarningRE *regexp.Regexp
+	statsTrimRE                      *regexp.Regexp
+}
+
+func NewGlobTestRegexHelper(appName string) *globTestRegexHelper {
+	return &globTestRegexHelper{
+		logFileTimeStampRE:               regexp.MustCompile("(STDOUT|STDERR)-[0-9]+$"),
+		logFileTrimInfoRE:                regexp.MustCompile(appName + `\..*\.INFO\.[0-9.-]+$`),
+		logFileRemoveErrorFatalWarningRE: regexp.MustCompile("(ERROR|FATAL|WARNING)"),
+		statsTrimRE:                      regexp.MustCompile("/stats/(rpc|system(/start-time.*)?)$"),
+	}
+}
+
+// VerifyGlob verifies that for each GlobTestVector instance that the
+// pattern returns the expected matches.
+func VerifyGlob(t *testing.T, ctx *context.T, appName string, testcases []GlobTestVector, res *globTestRegexHelper) {
+	for _, tc := range testcases {
+		results, _, err := testutil.GlobName(ctx, tc.Name, tc.Pattern)
+		if err != nil {
+			t.Errorf(testutil.FormatLogLine(2, "unexpected glob error for (%q, %q): %v", tc.Name, tc.Pattern, err))
+			continue
+		}
+		filteredResults := []string{}
+		for _, name := range results {
+			// Keep only the stats object names that match this RE.
+			if strings.Contains(name, "/stats/") && !res.statsTrimRE.MatchString(name) {
+				continue
+			}
+			// Remove ERROR, WARNING, FATAL log files because
+			// they're not consistently there.
+			if res.logFileRemoveErrorFatalWarningRE.MatchString(name) {
+				continue
+			}
+			name = res.logFileTimeStampRE.ReplaceAllString(name, "$1-<timestamp>")
+			name = res.logFileTrimInfoRE.ReplaceAllString(name, appName+".<*>.INFO.<timestamp>")
+			filteredResults = append(filteredResults, name)
+		}
+		sort.Strings(filteredResults)
+		sort.Strings(tc.Expected)
+		if !reflect.DeepEqual(filteredResults, tc.Expected) {
+			t.Errorf(testutil.FormatLogLine(2, "unexpected result for (%q, %q). Got %q, want %q", tc.Name, tc.Pattern, filteredResults, tc.Expected))
+		}
+	}
+}
+
+// VerifyFailGlob verifies that for each GlobTestVector instance that the
+// pattern returns no matches.
+func VerifyFailGlob(t *testing.T, ctx *context.T, testcases []GlobTestVector) {
+	for _, tc := range testcases {
+		results, _, _ := testutil.GlobName(ctx, tc.Name, tc.Pattern)
+		if len(results) != 0 {
+			t.Errorf(testutil.FormatLogLine(2, "verifyFailGlob should have failed for %q, %q", tc.Name, tc.Pattern))
+		}
+	}
+}
+
+// VerifyLog calls Size() on a selection of log file objects to
+// demonstrate that the log files are accessible and have been written by
+// the application.
+func VerifyLog(t *testing.T, ctx *context.T, nameComponents ...string) {
+	a := nameComponents
+	pattern, prefix := a[len(a)-1], a[:len(a)-1]
+	path := naming.Join(prefix...)
+	files, _, err := testutil.GlobName(ctx, path, pattern)
+	if err != nil {
+		t.Errorf(testutil.FormatLogLine(2, "unexpected glob error: %v", err))
+	}
+	if want, got := 4, len(files); got < want {
+		t.Errorf(testutil.FormatLogLine(2, "Unexpected number of matches. Got %d, want at least %d", got, want))
+	}
+	for _, file := range files {
+		name := naming.Join(path, file)
+		c := logreader.LogFileClient(name)
+		if _, err := c.Size(ctx); err != nil {
+			t.Errorf(testutil.FormatLogLine(2, "Size(%q) failed: %v", name, err))
+		}
+	}
+}
+
+// VerifyStatsValues call Value() on some of the stats objects to prove
+// that they are correctly being proxied to the device manager.
+func VerifyStatsValues(t *testing.T, ctx *context.T, nameComponents ...string) {
+	a := nameComponents
+	pattern, prefix := a[len(a)-1], a[:len(a)-1]
+	path := naming.Join(prefix...)
+	objects, _, err := testutil.GlobName(ctx, path, pattern)
+
+	if err != nil {
+		t.Errorf(testutil.FormatLogLine(2, "unexpected glob error: %v", err))
+	}
+	if want, got := 2, len(objects); got != want {
+		t.Errorf(testutil.FormatLogLine(2, "Unexpected number of matches. Got %d, want %d", got, want))
+	}
+	for _, obj := range objects {
+		name := naming.Join(path, obj)
+		c := stats.StatsClient(name)
+		if _, err := c.Value(ctx); err != nil {
+			t.Errorf(testutil.FormatLogLine(2, "Value(%q) failed: %v", name, err))
+		}
+	}
+}
+
+// VerifyPProfCmdLine calls CmdLine() on the pprof object to validate
+// that it the proxy correctly accessess pprof names.
+func VerifyPProfCmdLine(t *testing.T, ctx *context.T, appName string, nameComponents ...string) {
+	name := naming.Join(nameComponents...)
+	c := pprof.PProfClient(name)
+	v, err := c.CmdLine(ctx)
+	if err != nil {
+		t.Errorf(testutil.FormatLogLine(2, "CmdLine(%q) failed: %v", name, err))
+	}
+	if len(v) == 0 {
+		t.Errorf("Unexpected empty cmdline: %v", v)
+	}
+	if got, want := filepath.Base(v[0]), appName; got != want {
+		t.Errorf(testutil.FormatLogLine(2, "Unexpected value for argv[0]. Got %v, want %v", got, want))
+	}
+
+}
+
+func VerifyNoRunningProcesses(t *testing.T) {
+	if impl.RunningChildrenProcesses() {
+		t.Errorf("device manager incorrectly terminating with child processes still running")
+	}
+}
+
+func SetNamespaceRootsForUnclaimedDevice(ctx *context.T) (*context.T, error) {
+	origroots := v23.GetNamespace(ctx).Roots()
+	roots := make([]string, len(origroots))
+	for i, orig := range origroots {
+		addr, suffix := naming.SplitAddressName(orig)
+		origep, err := v23.NewEndpoint(addr)
+		if err != nil {
+			return nil, err
+		}
+		ep := naming.FormatEndpoint(
+			origep.Addr().Network(),
+			origep.Addr().String(),
+			origep.RoutingID(),
+			naming.ServesMountTable(origep.ServesMountTable()))
+		roots[i] = naming.JoinAddressName(ep, suffix)
+	}
+	ctx.Infof("Changing namespace roots from %v to %v", origroots, roots)
+	ctx, _, err := v23.WithNewNamespace(ctx, roots...)
+	return ctx, err
+}
+
+func UserName(t *testing.T) string {
+	u, err := user.Current()
+	if err != nil {
+		t.Fatalf("user.Current() failed: %v", err)
+	}
+	return u.Username
+}
+
+func StartRealBinaryRepository(t *testing.T, ctx *context.T, von string) func() {
+	rootDir, err := binarylib.SetupRootDir("")
+	if err != nil {
+		t.Fatalf("binarylib.SetupRootDir failed: %v", err)
+	}
+	state, err := binarylib.NewState(rootDir, "", 3)
+	if err != nil {
+		t.Fatalf("binarylib.NewState failed: %v", err)
+	}
+	d, err := binarylib.NewDispatcher(ctx, state)
+	if err != nil {
+		t.Fatalf("server.NewDispatcher failed: %v", err)
+	}
+	server, err := xrpc.NewDispatchingServer(ctx, von, d)
+	if err != nil {
+		t.Fatalf("server.ServeDispatcher failed: %v", err)
+	}
+	return func() {
+		if err := server.Stop(); err != nil {
+			t.Fatalf("server.Stop failed: %v", err)
+		}
+		if err := os.RemoveAll(rootDir); err != nil {
+			t.Fatalf("os.RemoveAll(%q) failed: %v", rootDir, err)
+		}
+	}
+}
+
+func GetPid(t *testing.T, ctx *context.T, appID, instanceID string) int {
+	name := naming.Join("dm", "apps/"+appID+"/"+instanceID+"/stats/system/pid")
+	c := stats.StatsClient(name)
+	v, err := c.Value(ctx)
+	if err != nil {
+		t.Fatalf("Value() failed: %v", err)
+	}
+	return int(v.Int())
+}
+
+// PollingWait polls a given process to make sure that it has exited
+// before continuing or fails with a time-out.
+func PollingWait(t *testing.T, pid int) {
+	timeOut := time.After(5 * time.Second)
+	for syscall.Kill(pid, 0) == nil {
+		select {
+		case <-timeOut:
+			syscall.Kill(pid, 9)
+			t.Fatalf("Timed out waiting for PID %v to terminate", pid)
+		case <-time.After(time.Millisecond):
+			// Try again.
+		}
+	}
+}
diff --git a/services/device/deviced/internal/impl/utiltest/mock_repo.go b/services/device/deviced/internal/impl/utiltest/mock_repo.go
new file mode 100644
index 0000000..c096d6d
--- /dev/null
+++ b/services/device/deviced/internal/impl/utiltest/mock_repo.go
@@ -0,0 +1,189 @@
+// 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 utiltest
+
+import (
+	"crypto/md5"
+	"encoding/hex"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"reflect"
+	"testing"
+
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/security/access"
+	"v.io/v23/services/application"
+	"v.io/v23/services/binary"
+	"v.io/v23/services/repository"
+	"v.io/v23/verror"
+	"v.io/x/ref/lib/xrpc"
+)
+
+const MockBinaryRepoName = "br"
+const MockApplicationRepoName = "ar"
+
+func StartMockRepos(t *testing.T, ctx *context.T) (*application.Envelope, func()) {
+	envelope, appCleanup := StartApplicationRepository(ctx)
+	binaryCleanup := StartBinaryRepository(ctx)
+
+	return envelope, func() {
+		binaryCleanup()
+		appCleanup()
+	}
+}
+
+// StartApplicationRepository sets up a server running the application
+// repository.  It returns a pointer to the envelope that the repository returns
+// to clients (so that it can be changed).  It also returns a cleanup function.
+func StartApplicationRepository(ctx *context.T) (*application.Envelope, func()) {
+	invoker := new(arInvoker)
+	name := MockApplicationRepoName
+	server, err := xrpc.NewServer(ctx, name, repository.ApplicationServer(invoker), security.AllowEveryone())
+	if err != nil {
+		ctx.Fatalf("NewServer(%v) failed: %v", name, err)
+	}
+	return &invoker.envelope, func() {
+		if err := server.Stop(); err != nil {
+			ctx.Fatalf("Stop() failed: %v", err)
+		}
+	}
+}
+
+// arInvoker holds the state of an application repository invocation mock.  The
+// mock returns the value of the wrapped envelope, which can be subsequently be
+// changed at any time.  Client is responsible for synchronization if desired.
+type arInvoker struct {
+	envelope application.Envelope
+}
+
+// APPLICATION REPOSITORY INTERFACE IMPLEMENTATION
+func (i *arInvoker) Match(ctx *context.T, _ rpc.ServerCall, profiles []string) (application.Envelope, error) {
+	ctx.VI(1).Infof("Match()")
+	if want := []string{"test-profile"}; !reflect.DeepEqual(profiles, want) {
+		return application.Envelope{}, fmt.Errorf("Expected profiles %v, got %v", want, profiles)
+	}
+	return i.envelope, nil
+}
+
+func (i *arInvoker) GetPermissions(ctx *context.T, _ rpc.ServerCall) (perms access.Permissions, version string, err error) {
+	return nil, "", nil
+}
+
+func (i *arInvoker) SetPermissions(ctx *context.T, _ rpc.ServerCall, perms access.Permissions, version string) error {
+	return nil
+}
+
+func (i *arInvoker) TidyNow(_ *context.T, _ rpc.ServerCall) error {
+	return nil
+}
+
+// brInvoker holds the state of a binary repository invocation mock.  It always
+// serves the current running binary.
+type brInvoker struct{}
+
+// StartBinaryRepository sets up a server running the binary repository and
+// returns a cleanup function.
+func StartBinaryRepository(ctx *context.T) func() {
+	name := MockBinaryRepoName
+	server, err := xrpc.NewServer(ctx, name, repository.BinaryServer(new(brInvoker)), security.AllowEveryone())
+	if err != nil {
+		ctx.Fatalf("Serve(%q) failed: %v", name, err)
+	}
+	return func() {
+		if err := server.Stop(); err != nil {
+			ctx.Fatalf("Stop() failed: %v", err)
+		}
+	}
+}
+
+// BINARY REPOSITORY INTERFACE IMPLEMENTATION
+
+// TODO(toddw): Move the errors from dispatcher.go into a common location.
+const pkgPath = "v.io/x/ref/services/device/deviced/internal/impl/utiltest"
+
+var ErrOperationFailed = verror.Register(pkgPath+".OperationFailed", verror.NoRetry, "")
+
+func (*brInvoker) Create(ctx *context.T, _ rpc.ServerCall, _ int32, _ repository.MediaInfo) error {
+	ctx.VI(1).Infof("Create()")
+	return nil
+}
+
+func (i *brInvoker) Delete(ctx *context.T, _ rpc.ServerCall) error {
+	ctx.VI(1).Infof("Delete()")
+	return nil
+}
+
+func mockBinaryBytesReader() (io.Reader, func(), error) {
+	file, err := os.Open(os.Args[0])
+	if err != nil {
+		return nil, nil, err
+	}
+	cleanup := func() {
+		file.Close()
+	}
+	return file, cleanup, nil
+}
+
+func (i *brInvoker) Download(ctx *context.T, call repository.BinaryDownloadServerCall, _ int32) error {
+	ctx.VI(1).Infof("Download()")
+	file, cleanup, err := mockBinaryBytesReader()
+	if err != nil {
+		ctx.Errorf("Open() failed: %v", err)
+		return verror.New(ErrOperationFailed, ctx)
+	}
+	defer cleanup()
+	bufferLength := 4096
+	buffer := make([]byte, bufferLength)
+	sender := call.SendStream()
+	for {
+		n, err := file.Read(buffer)
+		switch err {
+		case io.EOF:
+			return nil
+		case nil:
+			if err := sender.Send(buffer[:n]); err != nil {
+				ctx.Errorf("Send() failed: %v", err)
+				return verror.New(ErrOperationFailed, ctx)
+			}
+		default:
+			ctx.Errorf("Read() failed: %v", err)
+			return verror.New(ErrOperationFailed, ctx)
+		}
+	}
+}
+
+func (*brInvoker) DownloadUrl(ctx *context.T, _ rpc.ServerCall) (string, int64, error) {
+	ctx.VI(1).Infof("DownloadUrl()")
+	return "", 0, nil
+}
+
+func (*brInvoker) Stat(ctx *context.T, call rpc.ServerCall) ([]binary.PartInfo, repository.MediaInfo, error) {
+	ctx.VI(1).Infof("Stat()")
+	h := md5.New()
+	bytes, err := ioutil.ReadFile(os.Args[0])
+	if err != nil {
+		return []binary.PartInfo{}, repository.MediaInfo{}, verror.New(ErrOperationFailed, ctx)
+	}
+	h.Write(bytes)
+	part := binary.PartInfo{Checksum: hex.EncodeToString(h.Sum(nil)), Size: int64(len(bytes))}
+	return []binary.PartInfo{part}, repository.MediaInfo{Type: "application/octet-stream"}, nil
+}
+
+func (i *brInvoker) Upload(ctx *context.T, _ repository.BinaryUploadServerCall, _ int32) error {
+	ctx.VI(1).Infof("Upload()")
+	return nil
+}
+
+func (i *brInvoker) GetPermissions(*context.T, rpc.ServerCall) (perms access.Permissions, version string, err error) {
+	return nil, "", nil
+}
+
+func (i *brInvoker) SetPermissions(_ *context.T, _ rpc.ServerCall, perms access.Permissions, version string) error {
+	return nil
+}
diff --git a/services/device/deviced/internal/impl/utiltest/modules.go b/services/device/deviced/internal/impl/utiltest/modules.go
new file mode 100644
index 0000000..3f42f83
--- /dev/null
+++ b/services/device/deviced/internal/impl/utiltest/modules.go
@@ -0,0 +1,169 @@
+// 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 utiltest
+
+import (
+	"fmt"
+	"os"
+	goexec "os/exec"
+	"strings"
+	"testing"
+
+	"v.io/v23"
+	"v.io/v23/rpc"
+	"v.io/x/ref/internal/logger"
+	"v.io/x/ref/lib/signals"
+	"v.io/x/ref/services/device/deviced/internal/starter"
+	"v.io/x/ref/services/device/deviced/internal/versioning"
+	"v.io/x/ref/services/device/internal/config"
+	"v.io/x/ref/services/device/internal/suid"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
+)
+
+const (
+	RedirectEnv    = "DEVICE_MANAGER_DONT_REDIRECT_STDOUT_STDERR"
+	TestEnvVarName = "V23_RANDOM_ENV_VALUE"
+	NoPairingToken = ""
+)
+
+// ExecScript launches the script passed as argument.
+var ExecScript = modules.Register(func(env *modules.Env, args ...string) error {
+	if want, got := 1, len(args); want != got {
+		logger.Global().Fatalf("ExecScript expected %d arguments, got %d instead", want, got)
+	}
+	script := args[0]
+	osenv := []string{RedirectEnv + "=1"}
+	if env.Vars["PAUSE_BEFORE_STOP"] == "1" {
+		osenv = append(osenv, "PAUSE_BEFORE_STOP=1")
+	}
+
+	cmd := goexec.Cmd{
+		Path:   script,
+		Env:    osenv,
+		Stdin:  env.Stdin,
+		Stderr: env.Stderr,
+		Stdout: env.Stdout,
+	}
+	return cmd.Run()
+}, "ExecScript")
+
+// DeviceManager sets up a device manager server.  It accepts the name to
+// publish the server under as an argument.  Additional arguments can optionally
+// specify device manager config settings.
+var DeviceManager = modules.Register(deviceManagerFunc, "DeviceManager")
+
+func deviceManagerFunc(env *modules.Env, args ...string) error {
+	ctx, shutdown := test.V23Init()
+	if len(args) == 0 {
+		ctx.Fatalf("deviceManager expected at least an argument")
+	}
+	publishName := args[0]
+	args = args[1:]
+	defer fmt.Fprintf(env.Stdout, "%v terminated\n", publishName)
+	defer ctx.VI(1).Infof("%v terminated", publishName)
+	defer shutdown()
+
+	// Satisfy the contract described in doc.go by passing the config state
+	// through to the device manager dispatcher constructor.
+	configState, err := config.Load()
+	if err != nil {
+		ctx.Fatalf("Failed to decode config state: %v", err)
+	}
+
+	// This exemplifies how to override or set specific config fields, if,
+	// for example, the device manager is invoked 'by hand' instead of via a
+	// script prepared by a previous version of the device manager.
+	var pairingToken string
+	if len(args) > 0 {
+		if want, got := 4, len(args); want > got {
+			ctx.Fatalf("expected atleast %d additional arguments, got %d instead: %q", want, got, args)
+		}
+		configState.Root, configState.Helper, configState.Origin, configState.CurrentLink = args[0], args[1], args[2], args[3]
+		if len(args) > 4 {
+			pairingToken = args[4]
+		}
+	}
+	// We grab the shutdown channel at this point in order to ensure that we
+	// register a listener for the app cycle manager Stop before we start
+	// running the device manager service.  Otherwise, any device manager
+	// method that calls Stop on the app cycle manager (e.g. the Stop RPC)
+	// will precipitate an immediate process exit.
+	shutdownChan := signals.ShutdownOnSignals(ctx)
+	localhost := []struct{ Protocol, Address string }{{"tcp", "127.0.0.1:0"}}
+	claimableName, stop, err := starter.Start(ctx, starter.Args{
+		Namespace: starter.NamespaceArgs{
+			ListenSpec: rpc.ListenSpec{Addrs: rpc.ListenAddrs(localhost)},
+		},
+		Device: starter.DeviceArgs{
+			Name:            publishName,
+			ListenSpec:      rpc.ListenSpec{Addrs: rpc.ListenAddrs(localhost)},
+			ConfigState:     configState,
+			TestMode:        strings.HasSuffix(fmt.Sprint(v23.GetPrincipal(ctx).BlessingStore().Default()), "/testdm"),
+			RestartCallback: func() { fmt.Println("restart handler") },
+			PairingToken:    pairingToken,
+		},
+		// TODO(rthellend): Wire up the local mounttable like the real device
+		// manager, i.e. mount the device manager and the apps on it, and mount
+		// the local mounttable in the global namespace.
+		// MountGlobalNamespaceInLocalNamespace: true,
+	})
+	if err != nil {
+		ctx.Errorf("starter.Start failed: %v", err)
+		return err
+	}
+	defer stop()
+	// Update the namespace roots to remove the server blessing from the
+	// endpoints.  This is needed to be able to publish into the 'global'
+	// mounttable before we have compatible credentials.
+	ctx, err = SetNamespaceRootsForUnclaimedDevice(ctx)
+	if err != nil {
+		return err
+	}
+	// Manually mount the claimable service in the 'global' mounttable.
+	v23.GetNamespace(ctx).Mount(ctx, "claimable", claimableName, 0)
+	fmt.Fprintf(env.Stdout, "ready:%d\n", os.Getpid())
+
+	<-shutdownChan
+	if val, present := env.Vars["PAUSE_BEFORE_STOP"]; present && val == "1" {
+		modules.WaitForEOF(env.Stdin)
+	}
+	// TODO(ashankar): Figure out a way to incorporate this check in the test.
+	// if impl.DispatcherLeaking(dispatcher) {
+	//	ctx.Fatalf("device manager leaking resources")
+	// }
+	return nil
+}
+
+// This is the same as DeviceManager above, except that it has a different major version number
+var DeviceManagerV10 = modules.Register(func(env *modules.Env, args ...string) error {
+	versioning.CurrentVersion = versioning.Version{10, 0} // Set the version number to 10.0
+	return deviceManagerFunc(env, args...)
+}, "DeviceManagerV10")
+
+func TestMainImpl(m *testing.M) {
+	test.Init()
+	isSuidHelper := len(os.Getenv("V23_SUIDHELPER_TEST")) > 0
+	if modules.IsChildProcess() && !isSuidHelper {
+		if err := modules.Dispatch(); err != nil {
+			fmt.Fprintf(os.Stderr, "modules.Dispatch failed: %v\n", err)
+			os.Exit(1)
+		}
+		return
+	}
+	os.Exit(m.Run())
+}
+
+// TestSuidHelper is testing boilerplate for suidhelper that does not
+// create a runtime because the suidhelper is not a Vanadium application.
+func TestSuidHelperImpl(t *testing.T) {
+	if os.Getenv("V23_SUIDHELPER_TEST") != "1" {
+		return
+	}
+	logger.Global().VI(1).Infof("TestSuidHelper starting")
+	if err := suid.Run(os.Environ()); err != nil {
+		logger.Global().Fatalf("Failed to Run() setuidhelper: %v", err)
+	}
+}
diff --git a/services/device/deviced/internal/installer/device_installer.go b/services/device/deviced/internal/installer/device_installer.go
new file mode 100644
index 0000000..fbdc36b
--- /dev/null
+++ b/services/device/deviced/internal/installer/device_installer.go
@@ -0,0 +1,402 @@
+// 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 installer contains logic responsible for managing the device manager
+// server, including setting it up / tearing it down, and starting / stopping
+// it.
+package installer
+
+// When setting up the device manager installation, the installer creates a
+// directory structure from which the device manager can be run.  It sets up:
+//
+// <installDir>                - provided when installer is invoked
+//   dmroot/                   - created/owned by the installation
+//     device-manager/         - will be the root for the device manager server;
+//                               set as <Config.Root> (see comment in
+//                               device_service.go for what goes under here)
+//       info                  - json-encoded info about the running device manager (currently just the pid)
+//       base/                 - initial installation of device manager
+//         deviced             - link to deviced (self)
+//         deviced.sh          - script to start the device manager
+//       device-data/
+//         persistent-args     - list of persistent arguments for the device
+//                               manager (json encoded)
+//       logs/                 - device manager logs will go here
+//     current                 - set as <Config.CurrentLink>
+//     creation_info           - json-encoded info about the binary that created the directory tree
+//     agent_deviced.sh        - script to launch device manager under agent
+//     security/               - security agent keeps credentials here
+//       keys/
+//       principal/
+//     agent_logs/             - security agent logs
+//       STDERR-<timestamp>
+//       STDOUT-<timestamp>
+//     service_description     - json-encoded sysinit device manager config
+//     inithelper              - soft link to init helper
+//
+// TODO: we should probably standardize on '-' vs '_' for multi-word filename separators. Note any change
+// in the name of creation_info will require some care to ensure the version check continues to work.
+
+import (
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"os/user"
+	"path/filepath"
+	"syscall"
+	"time"
+
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/services/application"
+	"v.io/x/ref"
+	"v.io/x/ref/services/device/deviced/internal/impl"
+	"v.io/x/ref/services/device/deviced/internal/versioning"
+	"v.io/x/ref/services/device/internal/config"
+	"v.io/x/ref/services/device/internal/sysinit"
+)
+
+// restartExitCode is the exit code that the device manager should return when it
+// wants to be restarted by its parent (i.e., the security agent).
+// This number is picked quasi-arbitrarily from the set of
+// exit codes without prior special meanings.
+const restartExitCode = 140
+
+// dmRoot is the directory name where the device manager installs itself.
+const dmRoot = "dmroot"
+
+// InstallFrom takes a vanadium object name denoting an application service where
+// a device manager application envelope can be obtained.  It downloads the
+// latest version of the device manager and installs it.
+func InstallFrom(origin string) error {
+	// TODO(caprita): Implement.
+	return nil
+}
+
+// initCommand verifies if init mode is enabled, and if so executes the
+// appropriate sysinit command.  Returns whether init mode was detected, as well
+// as any error encountered.
+func initCommand(root, command string, stderr, stdout io.Writer) (bool, error) {
+	sdFile := filepath.Join(root, "service_description")
+	if _, err := os.Stat(sdFile); err != nil {
+		if os.IsNotExist(err) {
+			return false, nil
+		}
+		return false, fmt.Errorf("Stat(%v) failed: %v", sdFile, err)
+	}
+	helperLink := filepath.Join(root, "inithelper")
+	cmd := exec.Command(helperLink, fmt.Sprintf("--service_description=%s", sdFile), command)
+	if stderr != nil {
+		cmd.Stderr = stderr
+	}
+	if stdout != nil {
+		cmd.Stdout = stdout
+	}
+	if err := cmd.Run(); err != nil {
+		return true, fmt.Errorf("Running init helper %v failed: %v", command, err)
+	}
+	return true, nil
+}
+
+// SelfInstall installs the device manager and configures it using the
+// environment and the supplied command-line flags.
+func SelfInstall(ctx *context.T, installDir, suidHelper, agent, initHelper, origin string, singleUser, sessionMode, init bool, args, env []string, stderr, stdout io.Writer) error {
+	if os.Getenv(ref.EnvCredentials) != "" {
+		return fmt.Errorf("Attempting to install device manager under agent with the %q environment variable set.", ref.EnvCredentials)
+	}
+	root := filepath.Join(installDir, dmRoot)
+	if _, err := os.Stat(root); err == nil || !os.IsNotExist(err) {
+		return fmt.Errorf("%v already exists", root)
+	}
+	deviceDir := filepath.Join(root, "device-manager", "base")
+	perm := os.FileMode(0711)
+	if err := os.MkdirAll(deviceDir, perm); err != nil {
+		return fmt.Errorf("MkdirAll(%v, %v) failed: %v", deviceDir, perm, err)
+	}
+
+	// save info about the binary creating this tree
+	if err := versioning.SaveCreatorInfo(ctx, root); err != nil {
+		return err
+	}
+
+	currLink := filepath.Join(root, "current")
+	configState := &config.State{
+		Name:        "dummy", // So that Validate passes.
+		Root:        root,
+		Origin:      origin,
+		CurrentLink: currLink,
+		Helper:      suidHelper,
+	}
+	if err := configState.Validate(); err != nil {
+		return fmt.Errorf("invalid config %v: %v", configState, err)
+	}
+	var extraArgs []string
+	if name, err := os.Hostname(); err == nil {
+		extraArgs = append(extraArgs, fmt.Sprintf("--name=%q", naming.Join("devices", name)))
+	}
+	if !sessionMode {
+		extraArgs = append(extraArgs, fmt.Sprintf("--restart-exit-code=%d", restartExitCode))
+	}
+	envelope := &application.Envelope{
+		Args: append(extraArgs, args...),
+		// TODO(caprita): Cleaning up env vars to avoid picking up all
+		// the garbage from the user's env.
+		// Alternatively, pass the env vars meant specifically for the
+		// device manager in a different way.
+		Env: impl.VanadiumEnvironment(env),
+	}
+	if err := impl.SavePersistentArgs(root, envelope.Args); err != nil {
+		return err
+	}
+	if err := impl.LinkSelf(deviceDir, "deviced"); err != nil {
+		return err
+	}
+	configSettings, err := configState.Save(nil)
+	if err != nil {
+		return fmt.Errorf("failed to serialize config %v: %v", configState, err)
+	}
+	logs := filepath.Join(root, "device-manager", "logs")
+	if err := impl.GenerateScript(deviceDir, configSettings, envelope, logs); err != nil {
+		return err
+	}
+
+	// TODO(caprita): Test the device manager we just installed.
+	if err := impl.UpdateLink(filepath.Join(deviceDir, "deviced.sh"), currLink); err != nil {
+		return err
+	}
+
+	if err := generateAgentScript(root, agent, currLink, singleUser, sessionMode); err != nil {
+		return err
+	}
+	if init {
+		agentScript := filepath.Join(root, "agent_deviced.sh")
+		currentUser, err := user.Current()
+		if err != nil {
+			return err
+		}
+		sd := &sysinit.ServiceDescription{
+			Service:     "deviced",
+			Description: "Vanadium Device Manager",
+			Binary:      agentScript,
+			Command:     []string{agentScript},
+			User:        currentUser.Username,
+		}
+		sdFile := filepath.Join(root, "service_description")
+		if err := sd.SaveTo(sdFile); err != nil {
+			return fmt.Errorf("SaveTo for %v failed: %v", sd, err)
+		}
+		helperLink := filepath.Join(root, "inithelper")
+		if err := os.Symlink(initHelper, helperLink); err != nil {
+			return fmt.Errorf("Symlink(%v, %v) failed: %v", initHelper, helperLink, err)
+		}
+		if initMode, err := initCommand(root, "install", stderr, stdout); err != nil {
+			return err
+		} else if !initMode {
+			return fmt.Errorf("enabling init mode failed")
+		}
+	}
+	return nil
+}
+
+func generateAgentScript(workspace, agent, currLink string, singleUser, sessionMode bool) error {
+	securityDir := filepath.Join(workspace, "security")
+	principalDir := filepath.Join(securityDir, "principal")
+	keyDir := filepath.Join(securityDir, "keys")
+	perm := os.FileMode(0700)
+	if err := os.MkdirAll(principalDir, perm); err != nil {
+		return fmt.Errorf("MkdirAll(%v, %v) failed: %v", principalDir, perm, err)
+	}
+	if err := os.MkdirAll(keyDir, perm); err != nil {
+		return fmt.Errorf("MkdirAll(%v, %v) failed: %v", keyDir, perm, err)
+	}
+	logs := filepath.Join(workspace, "agent_logs")
+	if err := os.MkdirAll(logs, perm); err != nil {
+		return fmt.Errorf("MkdirAll(%v, %v) failed: %v", logs, perm, err)
+	}
+	stdoutLog, stderrLog := filepath.Join(logs, "STDOUT"), filepath.Join(logs, "STDERR")
+	// TODO(caprita): Switch all our generated bash scripts to use templates.
+	output := "#!/bin/bash\n"
+	output += "if [ -z \"$DEVICE_MANAGER_DONT_REDIRECT_STDOUT_STDERR\" ]; then\n"
+	output += fmt.Sprintf("  TIMESTAMP=$(%s)\n", impl.DateCommand)
+	output += fmt.Sprintf("  exec > %s-$TIMESTAMP 2> %s-$TIMESTAMP\n", stdoutLog, stderrLog)
+	output += "fi\n"
+	output += fmt.Sprintf("%s=%q ", ref.EnvCredentials, principalDir)
+	// Escape the path to the binary; %q uses Go-syntax escaping, but it's
+	// close enough to Bash that we're using it as an approximation.
+	//
+	// TODO(caprita/rthellend): expose and use shellEscape (from
+	// v.io/x/ref/services/debug/debug/impl.go) instead.
+	output += fmt.Sprintf("exec %q --log_dir=%q ", agent, logs)
+	if singleUser {
+		output += "--no-passphrase "
+	}
+	if !sessionMode {
+		output += fmt.Sprintf("--restart-exit-code=!0 ")
+	}
+	output += fmt.Sprintf("--additional-principals=%q %q", keyDir, currLink)
+	path := filepath.Join(workspace, "agent_deviced.sh")
+	if err := ioutil.WriteFile(path, []byte(output), 0700); err != nil {
+		return fmt.Errorf("WriteFile(%v) failed: %v", path, err)
+	}
+	// TODO(caprita): Put logs under dmroot/device-manager/logs.
+	return nil
+}
+
+// Uninstall undoes SelfInstall, removing the device manager's installation
+// directory.
+func Uninstall(ctx *context.T, installDir, helperPath string, stdout, stderr io.Writer) error {
+	// TODO(caprita): ensure device is stopped?
+
+	root := filepath.Join(installDir, dmRoot)
+	if _, err := initCommand(root, "uninstall", stdout, stderr); err != nil {
+		return err
+	}
+	impl.InitSuidHelper(ctx, helperPath)
+	return impl.DeleteFileTree(ctx, root, stdout, stderr)
+}
+
+// Start starts the device manager.
+func Start(ctx *context.T, installDir string, stderr, stdout io.Writer) error {
+	// TODO(caprita): make sure it's not already running?
+
+	root := filepath.Join(installDir, dmRoot)
+
+	if initMode, err := initCommand(root, "start", stderr, stdout); err != nil {
+		return err
+	} else if initMode {
+		return nil
+	}
+
+	if os.Getenv(ref.EnvCredentials) != "" {
+		return fmt.Errorf("Attempting to run device manager under agent with the %q environment variable set.", ref.EnvCredentials)
+	}
+	agentScript := filepath.Join(root, "agent_deviced.sh")
+	cmd := exec.Command(agentScript)
+	if stderr != nil {
+		cmd.Stderr = stderr
+	}
+	if stdout != nil {
+		cmd.Stdout = stdout
+	}
+	if err := cmd.Start(); err != nil {
+		return fmt.Errorf("Start failed: %v", err)
+	}
+
+	// Save away the agent's pid to be used for stopping later ...
+	if cmd.Process.Pid == 0 {
+		fmt.Fprintf(stderr, "Unable to get a pid for successfully-started agent!")
+		return nil // We tolerate the error, at the expense of being able to stop later
+	}
+	mi := &impl.ManagerInfo{
+		Pid: cmd.Process.Pid,
+	}
+	if err := impl.SaveManagerInfo(filepath.Join(root, "agent-deviced"), mi); err != nil {
+		return fmt.Errorf("failed to save info for agent-deviced: %v", err)
+	}
+
+	return nil
+}
+
+// Stop stops the device manager.
+func Stop(ctx *context.T, installDir string, stderr, stdout io.Writer) error {
+	root := filepath.Join(installDir, dmRoot)
+	if initMode, err := initCommand(root, "stop", stderr, stdout); err != nil {
+		return err
+	} else if initMode {
+		return nil
+	}
+
+	agentPid, devmgrPid := 0, 0
+
+	// Load the agent pid
+	info, err := impl.LoadManagerInfo(filepath.Join(root, "agent-deviced"))
+	if err != nil {
+		return fmt.Errorf("loadManagerInfo failed for agent-deviced: %v", err)
+	}
+	if syscall.Kill(info.Pid, 0) == nil { // Save the pid if it's currently live
+		agentPid = info.Pid
+	}
+
+	// Load the device manager pid
+	info, err = impl.LoadManagerInfo(filepath.Join(root, "device-manager"))
+	if err != nil {
+		return fmt.Errorf("loadManagerInfo failed for device-manager: %v", err)
+	}
+	if syscall.Kill(info.Pid, 0) == nil { // Save the pid if it's currently live
+		devmgrPid = info.Pid
+	}
+
+	if agentPid == 0 && devmgrPid == 0 {
+		return fmt.Errorf("stop could not find any live pids to stop")
+	}
+
+	// Set up waiters for each nonzero pid. This ensures that exiting processes are reaped when
+	// the agent or device manager happen to be children of this process. (Not commonly the case,
+	// but it does occur in the impl test.)
+	if agentPid != 0 {
+		go func() {
+			if p, err := os.FindProcess(agentPid); err == nil {
+				p.Wait()
+			}
+		}()
+	}
+	if devmgrPid != 0 {
+		go func() {
+			if p, err := os.FindProcess(devmgrPid); err == nil {
+				p.Wait()
+			}
+		}()
+	}
+
+	// First, send SIGINT to the agent. We expect both the agent and the device manager to
+	// exit as a result within 15 seconds
+	if agentPid != 0 {
+		if err = syscall.Kill(agentPid, syscall.SIGINT); err != nil {
+			return fmt.Errorf("sending SIGINT to %d: %v", agentPid, err)
+		}
+		for i := 0; i < 30 && syscall.Kill(agentPid, 0) == nil; i++ {
+			time.Sleep(500 * time.Millisecond)
+			if i%5 == 4 {
+				fmt.Fprintf(stderr, "waiting for agent (pid %d) to die...\n", agentPid)
+			}
+		}
+		if syscall.Kill(agentPid, 0) == nil { // agent is still alive, resort to brute force
+			fmt.Fprintf(stderr, "sending SIGKILL to agent %d\n", agentPid)
+			if err = syscall.Kill(agentPid, syscall.SIGKILL); err != nil {
+				fmt.Fprintf(stderr, "Sending SIGKILL to %d: %v\n", agentPid, err)
+				// not returning here, so that we check & kill the device manager too
+			}
+		}
+	}
+
+	// If the device manager is still alive, forcibly kill it
+	if syscall.Kill(devmgrPid, 0) == nil {
+		fmt.Fprintf(stderr, "sending SIGKILL to device manager %d\n", devmgrPid)
+		if err = syscall.Kill(devmgrPid, syscall.SIGKILL); err != nil {
+			return fmt.Errorf("sending SIGKILL to device manager %d: %v", devmgrPid, err)
+		}
+	}
+
+	// By now, nothing should be alive. Check and report
+	if agentPid != 0 && syscall.Kill(agentPid, 0) == nil {
+		return fmt.Errorf("multiple attempts to kill agent pid %d have failed", agentPid)
+	}
+	if devmgrPid != 0 && syscall.Kill(devmgrPid, 0) == nil {
+		return fmt.Errorf("multiple attempts to kill device manager pid %d have failed", devmgrPid)
+	}
+
+	// Should we remove the agentd and deviced info files here? Not removing them
+	// increases the chances that we later rerun stop and shoot some random process. Removing
+	// them makes it impossible to run stop a second time (although that shouldn't be necessary)
+	// and also introduces the potential for a race condition if a new agent/deviced are started
+	// right after these ones get killed.
+	//
+	// TODO: Reconsider this when we add stronger protection to make sure that the pids being
+	// signalled are in fact the agent and/or device manager
+
+	// Process was killed succesfully
+	return nil
+}
diff --git a/services/device/deviced/internal/starter/starter.go b/services/device/deviced/internal/starter/starter.go
new file mode 100644
index 0000000..afd6e1e
--- /dev/null
+++ b/services/device/deviced/internal/starter/starter.go
@@ -0,0 +1,382 @@
+// 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 starter provides a single function that starts up servers for a
+// mounttable and a device manager that is mounted on it.
+package starter
+
+import (
+	"encoding/base64"
+	"net"
+	"os"
+	"path/filepath"
+	"strconv"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/verror"
+	"v.io/x/ref/lib/xrpc"
+	"v.io/x/ref/runtime/factories/roaming"
+	"v.io/x/ref/services/debug/debuglib"
+	"v.io/x/ref/services/device/deviced/internal/impl"
+	"v.io/x/ref/services/device/deviced/internal/versioning"
+	"v.io/x/ref/services/device/internal/claim"
+	"v.io/x/ref/services/device/internal/config"
+	"v.io/x/ref/services/internal/pathperms"
+	"v.io/x/ref/services/mounttable/mounttablelib"
+)
+
+const pkgPath = "v.io/x/ref/services/device/deviced/internal/starter"
+
+var (
+	errCantSaveInfo      = verror.Register(pkgPath+".errCantSaveInfo", verror.NoRetry, "{1:}{2:} failed to save info{:_}")
+	errBadPort           = verror.Register(pkgPath+".errBadPort", verror.NoRetry, "{1:}{2:} invalid port{:_}")
+	errCantCreateProxy   = verror.Register(pkgPath+".errCantCreateProxy", verror.NoRetry, "{1:}{2:} Failed to create proxy{:_}")
+	errNoEndpointToClaim = verror.Register(pkgPath+".errNoEndpointToClaim", verror.NoRetry, "{1:}{2:} failed to find an endpoint for claiming{:_}")
+)
+
+type NamespaceArgs struct {
+	Name            string         // Name to publish the mounttable service under (after claiming).
+	ListenSpec      rpc.ListenSpec // ListenSpec for the server.
+	PermissionsFile string         // Path to the Permissions file used by the mounttable.
+	PersistenceDir  string         // Path to the directory holding persistent acls.
+	// Name in the local neighborhood on which to make the mounttable
+	// visible. If empty, the mounttable will not be visible in the local
+	// neighborhood.
+	Neighborhood string
+}
+
+type DeviceArgs struct {
+	Name            string         // Name to publish the device service under (after claiming).
+	ListenSpec      rpc.ListenSpec // ListenSpec for the device server.
+	ConfigState     *config.State  // Configuration for the device.
+	TestMode        bool           // Whether the device is running in test mode or not.
+	RestartCallback func()         // Callback invoked when the device service is restarted.
+	PairingToken    string         // PairingToken that a claimer needs to provide.
+}
+
+func (d *DeviceArgs) name(mt string) string {
+	if d.Name != "" {
+		return d.Name
+	}
+	return naming.Join(mt, "devmgr")
+}
+
+type ProxyArgs struct {
+	Port int
+}
+
+type Args struct {
+	Namespace NamespaceArgs
+	Device    DeviceArgs
+	Proxy     ProxyArgs
+
+	// If true, the global namespace will be made available on the
+	// mounttable server under "global/".
+	MountGlobalNamespaceInLocalNamespace bool
+}
+
+// Start creates servers for the mounttable and device services and links them together.
+//
+// Returns the object name for the claimable service (empty if already claimed),
+// a callback to be invoked to shutdown the services on success, or an error on
+// failure.
+func Start(ctx *context.T, args Args) (string, func(), error) {
+	// Is this binary compatible with the state on disk?
+	if err := versioning.CheckCompatibility(ctx, args.Device.ConfigState.Root); err != nil {
+		return "", nil, err
+	}
+	// In test mode, we skip writing the info file to disk, and we skip
+	// attempting to start the claimable service: the device must have been
+	// claimed already to enable updates anyway, and checking for perms in
+	// NewClaimableDispatcher needlessly prints a perms signature
+	// verification error to the logs.
+	if args.Device.TestMode {
+		cleanup, err := startClaimedDevice(ctx, args)
+		return "", cleanup, err
+	}
+
+	// TODO(caprita): use some mechanism (a file lock or presence of entry
+	// in mounttable) to ensure only one device manager is running in an
+	// installation?
+	mi := &impl.ManagerInfo{
+		Pid: os.Getpid(),
+	}
+	if err := impl.SaveManagerInfo(filepath.Join(args.Device.ConfigState.Root, "device-manager"), mi); err != nil {
+		return "", nil, verror.New(errCantSaveInfo, ctx, err)
+	}
+
+	// If the device has not yet been claimed, start the mounttable and
+	// claimable service and wait for it to be claimed.
+	// Once a device is claimed, close any previously running servers and
+	// start a new mounttable and device service.
+	claimable, claimed := claim.NewClaimableDispatcher(ctx, impl.PermsDir(args.Device.ConfigState), args.Device.PairingToken, security.AllowEveryone())
+	if claimable == nil {
+		// Device has already been claimed, bypass claimable service
+		// stage.
+		cleanup, err := startClaimedDevice(ctx, args)
+		return "", cleanup, err
+	}
+	epName, stopClaimable, err := startClaimableDevice(ctx, claimable, args)
+	if err != nil {
+		return "", nil, err
+	}
+	stop := make(chan struct{})
+	stopped := make(chan struct{})
+	go waitToBeClaimedAndStartClaimedDevice(ctx, stopClaimable, claimed, stop, stopped, args)
+	return epName, func() {
+		close(stop)
+		<-stopped
+	}, nil
+}
+
+func startClaimableDevice(ctx *context.T, dispatcher rpc.Dispatcher, args Args) (string, func(), error) {
+	// TODO(caprita,ashankar): We create a context with a new stream manager
+	// that we can cancel once the device has been claimed. This gets around
+	// the following issue: if we publish the claimable server to the local
+	// mounttable, and then (following claim) we restart the mounttable
+	// server on the same port, we fail to publish the device service to the
+	// (new) mounttable server (Mount fails with "VC handshake failed:
+	// remote end closed VC(VCs not accepted)".  Presumably, something to do
+	// with caching connections (following the claim, the mounttable comes
+	// back on the same port as before, and the client-side of the mount
+	// gets confused trying to reuse the old connection and doesn't attempt
+	// to create a new connection).  We should get to the bottom of it.
+	ctx, cancel := context.WithCancel(ctx)
+	var err error
+	if ctx, err = v23.WithNewStreamManager(ctx); err != nil {
+		cancel()
+		return "", nil, err
+	}
+	ctx = v23.WithListenSpec(ctx, args.Device.ListenSpec)
+	server, err := xrpc.NewDispatchingServer(ctx, "", dispatcher)
+	if err != nil {
+		cancel()
+		return "", nil, err
+	}
+	shutdown := func() {
+		ctx.Infof("Stopping claimable server...")
+		server.Stop()
+		ctx.Infof("Stopped claimable server.")
+		cancel()
+	}
+	endpoints := server.Status().Endpoints
+	publicKey, err := v23.GetPrincipal(ctx).PublicKey().MarshalBinary()
+	if err != nil {
+		shutdown()
+		return "", nil, err
+	}
+	var epName string
+	if args.Device.ListenSpec.Proxy != "" {
+		for {
+			p := server.Status().Proxies
+			if len(p) == 0 {
+				ctx.Infof("Waiting for proxy address to appear...")
+				time.Sleep(time.Second)
+				continue
+			}
+			epName = p[0].Endpoint.Name()
+			ctx.Infof("Proxied address: %s", epName)
+			break
+		}
+	} else {
+		if len(endpoints) == 0 {
+			return "", nil, verror.New(errNoEndpointToClaim, ctx, err)
+		}
+		epName = endpoints[0].Name()
+	}
+	ctx.Infof("Unclaimed device manager (%v) with public_key: %s", epName, base64.URLEncoding.EncodeToString(publicKey))
+	ctx.FlushLog()
+	return epName, shutdown, nil
+}
+
+func waitToBeClaimedAndStartClaimedDevice(ctx *context.T, stopClaimable func(), claimed, stop <-chan struct{}, stopped chan<- struct{}, args Args) {
+	// Wait for either the claimable service to complete, or be stopped
+	defer close(stopped)
+	select {
+	case <-claimed:
+		stopClaimable()
+	case <-stop:
+		stopClaimable()
+		return
+	}
+	shutdown, err := startClaimedDevice(ctx, args)
+	if err != nil {
+		ctx.Errorf("Failed to start device service after it was claimed: %v", err)
+		v23.GetAppCycle(ctx).Stop(ctx)
+		return
+	}
+	defer shutdown()
+	<-stop // Wait to be stopped
+}
+
+func startClaimedDevice(ctx *context.T, args Args) (func(), error) {
+	ctx.Infof("Starting claimed device services...")
+	permStore := pathperms.NewPathStore(ctx)
+	permsDir := impl.PermsDir(args.Device.ConfigState)
+	debugAuth, err := pathperms.NewHierarchicalAuthorizer(permsDir, permsDir, permStore, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	debugDisp := debuglib.NewDispatcher(debugAuth)
+
+	ctx = v23.WithReservedNameDispatcher(ctx, debugDisp)
+
+	ctx.Infof("Starting mount table...")
+	mtName, stopMT, err := startMounttable(ctx, args.Namespace)
+	if err != nil {
+		ctx.Errorf("Failed to start mounttable service: %v", err)
+		return nil, err
+	} else {
+		ctx.Infof("Started mount table.")
+	}
+	// TODO(caprita): We link in a proxy server into the device manager so that we
+	// can bootstrap with install-local before we can install an actual proxy app.
+	// Once support is added to the RPC layer to allow install-local to serve on
+	// the same connection it established to the device manager (see TODO in
+	// v.io/x/ref/services/device/device/local_install.go), we can get rid of this
+	// local proxy altogether.
+	ctx.Infof("Starting proxy service...")
+	stopProxy, err := startProxyServer(ctx, args.Proxy, mtName)
+	if err != nil {
+		ctx.Errorf("Failed to start proxy service: %v", err)
+		stopMT()
+		return nil, err
+	} else {
+		ctx.Infof("Started proxy service.")
+	}
+	ctx.Infof("Starting device service...")
+	stopDevice, err := startDeviceServer(ctx, args.Device, mtName, permStore)
+	if err != nil {
+		ctx.Errorf("Failed to start device service: %v", err)
+		stopProxy()
+		stopMT()
+		return nil, err
+	} else {
+		ctx.Infof("Started device service.")
+	}
+	if args.MountGlobalNamespaceInLocalNamespace {
+		ctx.Infof("Mounting %v ...", mtName)
+		mountGlobalNamespaceInLocalNamespace(ctx, mtName)
+		ctx.Infof("Mounted %v", mtName)
+	}
+
+	impl.InvokeCallback(ctx, args.Device.ConfigState.Name)
+
+	ctx.Infof("Started claimed device services.")
+	return func() {
+		stopDevice()
+		stopProxy()
+		stopMT()
+	}, nil
+}
+
+func startProxyServer(ctx *context.T, p ProxyArgs, localMT string) (func(), error) {
+	switch port := p.Port; {
+	case port == 0:
+		return func() {}, nil
+	case port < 0:
+		return nil, verror.New(errBadPort, ctx, port)
+	}
+	port := strconv.Itoa(p.Port)
+	protocol, addr := "tcp", net.JoinHostPort("", port)
+	// Attempt to get a publicly accessible address for the proxy to publish
+	// under.
+	ls := v23.GetListenSpec(ctx)
+	ls.Addrs = rpc.ListenAddrs{{protocol, addr}}
+	// TODO(ashankar): Revisit this choice of security.AllowEveryone
+	// See: https://v.io/i/387
+	shutdown, ep, err := roaming.NewProxy(ctx, ls, security.AllowEveryone())
+	if err != nil {
+		return nil, verror.New(errCantCreateProxy, ctx, err)
+	}
+	ctx.Infof("Local proxy (%v)", ep.Name())
+	return func() {
+		ctx.Infof("Stopping proxy...")
+		shutdown()
+		ctx.Infof("Stopped proxy.")
+	}, nil
+}
+
+func startMounttable(ctx *context.T, n NamespaceArgs) (string, func(), error) {
+	mtName, stopMT, err := mounttablelib.StartServers(ctx, n.ListenSpec, n.Name, n.Neighborhood, n.PermissionsFile, n.PersistenceDir, "mounttable")
+	if err != nil {
+		ctx.Errorf("mounttablelib.StartServers(%#v) failed: %v", n, err)
+	} else {
+		ctx.Infof("Local mounttable (%v) published as %q", mtName, n.Name)
+	}
+	return mtName, func() {
+		ctx.Infof("Stopping mounttable...")
+		stopMT()
+		ctx.Infof("Stopped mounttable.")
+	}, err
+}
+
+// startDeviceServer creates an rpc.Server and sets it up to server the Device service.
+//
+// ls: ListenSpec for the server
+// configState: configuration for the Device service dispatcher
+// mt: Object address of the mounttable
+// dm: Name to publish the device service under
+// testMode: whether the service is to be run in test mode
+// restarted: callback invoked when the device manager is restarted.
+//
+// Returns:
+// (1) Function to be called to force the service to shutdown
+// (2) Any errors in starting the service (in which case, (1) will be nil)
+func startDeviceServer(ctx *context.T, args DeviceArgs, mt string, permStore *pathperms.PathStore) (shutdown func(), err error) {
+	server, err := v23.NewServer(ctx)
+	if err != nil {
+		return nil, err
+	}
+	shutdown = func() { server.Stop() }
+	endpoints, err := server.Listen(args.ListenSpec)
+	if err != nil {
+		shutdown()
+		return nil, err
+	}
+	args.ConfigState.Name = endpoints[0].Name()
+
+	dispatcher, dShutdown, err := impl.NewDispatcher(ctx, args.ConfigState, mt, args.TestMode, args.RestartCallback, permStore)
+	if err != nil {
+		shutdown()
+		return nil, err
+	}
+
+	shutdown = func() {
+		// TODO(caprita): Capture the Dying state by feeding it back to
+		// the dispatcher and exposing it in Status.
+		ctx.Infof("Stopping device server...")
+		server.Stop()
+		dShutdown()
+		ctx.Infof("Stopped device.")
+	}
+	if err := server.ServeDispatcher(args.name(mt), dispatcher); err != nil {
+		shutdown()
+		return nil, err
+	}
+	ctx.Infof("Device manager (%v) published as %v", args.ConfigState.Name, args.name(mt))
+	return shutdown, nil
+}
+
+func mountGlobalNamespaceInLocalNamespace(ctx *context.T, localMT string) {
+	ns := v23.GetNamespace(ctx)
+	for _, root := range ns.Roots() {
+		go func(r string) {
+			for {
+				err := ns.Mount(ctx, naming.Join(localMT, "global"), r, 0 /* forever */, naming.ServesMountTable(true))
+				if err == nil {
+					break
+				}
+				ctx.Infof("Failed to Mount global namespace: %v", err)
+				time.Sleep(time.Second)
+			}
+		}(root)
+	}
+}
diff --git a/services/device/deviced/internal/versioning/creator_info.go b/services/device/deviced/internal/versioning/creator_info.go
new file mode 100644
index 0000000..117de75
--- /dev/null
+++ b/services/device/deviced/internal/versioning/creator_info.go
@@ -0,0 +1,85 @@
+// 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 versioning handles device manager versioning.  Device manager
+// binaries need to be compatible with the existing device manager installation.
+package versioning
+
+import (
+	"encoding/json"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+
+	"v.io/v23/context"
+	"v.io/v23/verror"
+	"v.io/x/lib/metadata"
+	"v.io/x/ref/services/device/internal/errors"
+)
+
+// Version info for this device manager binary. Increment as appropriate when the binary changes.
+// The major version number should be incremented whenever a change to the binary makes it incompatible
+// with on-disk state created by a binary from a different major version.
+type Version struct{ Major, Minor int }
+
+var CurrentVersion = Version{1, 0}
+
+// CreationInfo holds data about the binary that originally created the device manager on-disk state
+type CreatorInfo struct {
+	Version  Version
+	MetaData string
+}
+
+func SaveCreatorInfo(ctx *context.T, dir string) error {
+	info := CreatorInfo{
+		Version:  CurrentVersion,
+		MetaData: metadata.ToXML(),
+	}
+	jsonInfo, err := json.Marshal(info)
+	if err != nil {
+		ctx.Errorf("Marshal(%v) failed: %v", info, err)
+		return verror.New(errors.ErrOperationFailed, nil)
+	}
+	if err := os.MkdirAll(dir, os.FileMode(0700)); err != nil {
+		ctx.Errorf("MkdirAll(%v) failed: %v", dir, err)
+		return verror.New(errors.ErrOperationFailed, nil)
+	}
+	infoPath := filepath.Join(dir, "creation_info")
+	if err := ioutil.WriteFile(infoPath, jsonInfo, 0600); err != nil {
+		ctx.Errorf("WriteFile(%v) failed: %v", infoPath, err)
+		return verror.New(errors.ErrOperationFailed, nil)
+	}
+	// Make the file read-only as we don't want anyone changing it
+	if err := os.Chmod(infoPath, 0400); err != nil {
+		ctx.Errorf("Chmod(0400, %v) failed: %v", infoPath, err)
+		return verror.New(errors.ErrOperationFailed, nil)
+	}
+	return nil
+}
+
+func loadCreatorInfo(ctx *context.T, dir string) (*CreatorInfo, error) {
+	infoPath := filepath.Join(dir, "creation_info")
+	info := new(CreatorInfo)
+	if infoBytes, err := ioutil.ReadFile(infoPath); err != nil {
+		ctx.Errorf("ReadFile(%v) failed: %v", infoPath, err)
+		return nil, verror.New(errors.ErrOperationFailed, nil)
+	} else if err := json.Unmarshal(infoBytes, info); err != nil {
+		ctx.Errorf("Unmarshal(%v) failed: %v", infoBytes, err)
+		return nil, verror.New(errors.ErrOperationFailed, nil)
+	}
+	return info, nil
+}
+
+// Checks the compatibilty of the running binary against the device manager directory on disk
+func CheckCompatibility(ctx *context.T, dir string) error {
+	if infoOnDisk, err := loadCreatorInfo(ctx, dir); err != nil {
+		ctx.Errorf("Failed to load creator info from %s", dir)
+		return verror.New(errors.ErrOperationFailed, nil)
+	} else if CurrentVersion.Major != infoOnDisk.Version.Major {
+		ctx.Errorf("Device Manager binary vs disk major version mismatch (%+v vs %+v)",
+			CurrentVersion, infoOnDisk.Version)
+		return verror.New(errors.ErrOperationFailed, nil)
+	}
+	return nil
+}
diff --git a/services/device/deviced/main.go b/services/device/deviced/main.go
new file mode 100644
index 0000000..59b6f37
--- /dev/null
+++ b/services/device/deviced/main.go
@@ -0,0 +1,36 @@
+// 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.
+
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go .
+
+package main
+
+import (
+	"os"
+	"runtime"
+
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/v23cmd"
+)
+
+func main() {
+	// TODO(caprita): Remove this once we have a way to set the GOMAXPROCS
+	// environment variable persistently for device manager.
+	if os.Getenv("GOMAXPROCS") == "" {
+		runtime.GOMAXPROCS(runtime.NumCPU())
+	}
+
+	rootCmd := &cmdline.Command{
+		Name:  "deviced",
+		Short: "launch, configure and manage the deviced daemon",
+		Long: `
+Command deviced is used to launch, configure and manage the deviced daemon,
+which implements the v.io/v23/services/device interfaces.
+`,
+		Children: []*cmdline.Command{cmdInstall, cmdUninstall, cmdStart, cmdStop, cmdProfile},
+		Runner:   v23cmd.RunnerFunc(runServer),
+	}
+	cmdline.Main(rootCmd)
+}
diff --git a/services/device/deviced/server.go b/services/device/deviced/server.go
new file mode 100644
index 0000000..d659743
--- /dev/null
+++ b/services/device/deviced/server.go
@@ -0,0 +1,151 @@
+// 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 (
+	"crypto/rand"
+	"encoding/base64"
+	"flag"
+	"net"
+	"os"
+	"path/filepath"
+	"regexp"
+	"strconv"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/verror"
+	"v.io/x/lib/cmdline"
+	vexec "v.io/x/ref/lib/exec"
+	"v.io/x/ref/lib/mgmt"
+	"v.io/x/ref/lib/signals"
+	_ "v.io/x/ref/runtime/factories/roaming"
+	"v.io/x/ref/services/device/deviced/internal/starter"
+	"v.io/x/ref/services/device/internal/config"
+)
+
+const pkgPath = "v.io/x/ref/services/device/deviced"
+
+var (
+	errSplitHostPortFailed = verror.Register(pkgPath+".errSplitHostPortFailed", verror.NoRetry, "{1:}{2:} net.SplitHostPort({3}) failed{:_}")
+)
+
+var (
+	// TODO(caprita): publishAs and restartExitCode should be provided by the
+	// config?
+	publishAs       = flag.String("name", "", "name to publish the device manager at")
+	restartExitCode = flag.Int("restart-exit-code", 0, "exit code to return when device manager should be restarted")
+	nhName          = flag.String("neighborhood-name", "", `if provided, it will enable sharing with the local neighborhood with the provided name. The address of the local mounttable will be published to the neighboorhood and everything in the neighborhood will be visible on the local mounttable.`)
+	dmPort          = flag.Int("deviced-port", 0, "the port number of assign to the device manager service. The hostname/IP address part of --v23.tcp.address is used along with this port. By default, the port is assigned by the OS.")
+	proxyPort       = flag.Int("proxy-port", 0, "the port number to assign to the proxy service. 0 means no proxy service.")
+	usePairingToken = flag.Bool("use-pairing-token", false, "generate a pairing token for the device manager that will need to be provided when a device is claimed")
+)
+
+func init() {
+	cmdline.HideGlobalFlagsExcept(regexp.MustCompile(`^((name)|(restart-exit-code)|(neighborhood-name)|(deviced-port)|(proxy-port)|(use-pairing-token))$`))
+}
+
+func runServer(ctx *context.T, _ *cmdline.Env, _ []string) error {
+	var testMode bool
+	// If this device manager was started by another device manager, it must
+	// be part of a self update to test that this binary works. In that
+	// case, we need to disable a lot of functionality.
+	if handle, err := vexec.GetChildHandle(); err == nil {
+		if _, err := handle.Config.Get(mgmt.ParentNameConfigKey); err == nil {
+			testMode = true
+			ctx.Infof("TEST MODE")
+		}
+	}
+	configState, err := config.Load()
+	if err != nil {
+		ctx.Errorf("Failed to load config passed from parent: %v", err)
+		return err
+	}
+	mtPermsDir := filepath.Join(configState.Root, "mounttable")
+	if err := os.MkdirAll(mtPermsDir, 0700); err != nil {
+		ctx.Errorf("os.MkdirAll(%q) failed: %v", mtPermsDir, err)
+		return err
+	}
+
+	// TODO(ashankar,caprita): Use channels/locks to synchronize the
+	// setting and getting of exitErr.
+	var exitErr error
+	ns := starter.NamespaceArgs{
+		PermissionsFile: filepath.Join(mtPermsDir, "acls"),
+	}
+	if testMode {
+		ns.ListenSpec = rpc.ListenSpec{Addrs: rpc.ListenAddrs{{"tcp", "127.0.0.1:0"}}}
+	} else {
+		ns.ListenSpec = v23.GetListenSpec(ctx)
+		ns.Name = *publishAs
+		ns.Neighborhood = *nhName
+	}
+	// TODO(caprita): Move pairing token generation and printing into the
+	// claimable service setup.
+	var pairingToken string
+	if *usePairingToken {
+		var token [8]byte
+		if _, err := rand.Read(token[:]); err != nil {
+			ctx.Errorf("unable to generate pairing token: %v", err)
+			return err
+		}
+		pairingToken = base64.URLEncoding.EncodeToString(token[:])
+		ctx.VI(0).Infof("Device manager pairing token: %v", pairingToken)
+		ctx.FlushLog()
+	}
+	dev := starter.DeviceArgs{
+		ConfigState:     configState,
+		TestMode:        testMode,
+		RestartCallback: func() { exitErr = cmdline.ErrExitCode(*restartExitCode) },
+		PairingToken:    pairingToken,
+	}
+	if testMode {
+		dev.ListenSpec = rpc.ListenSpec{Addrs: rpc.ListenAddrs{{"tcp", "127.0.0.1:0"}}}
+	} else {
+		if dev.ListenSpec, err = derivedListenSpec(ctx, ns.ListenSpec, *dmPort); err != nil {
+			return err
+		}
+	}
+	var proxy starter.ProxyArgs
+	if !testMode {
+		proxy.Port = *proxyPort
+	}
+	// We grab the shutdown channel at this point in order to ensure that we
+	// register a listener for the app cycle manager Stop before we start
+	// running the device manager service.  Otherwise, any device manager
+	// method that calls Stop on the app cycle manager (e.g. the Stop RPC)
+	// will precipitate an immediate process exit.
+	shutdownChan := signals.ShutdownOnSignals(ctx)
+	_, stop, err := starter.Start(ctx, starter.Args{Namespace: ns, Device: dev, MountGlobalNamespaceInLocalNamespace: true, Proxy: proxy})
+	if err != nil {
+		return err
+	}
+	defer stop()
+
+	// Wait until shutdown.  Ignore duplicate signals (sent by agent and
+	// received as part of process group).
+	signals.SameSignalTimeWindow = 500 * time.Millisecond
+	ctx.Info("Shutting down due to: ", <-shutdownChan)
+	return exitErr
+}
+
+// derivedListenSpec returns a copy of ls, with the ports changed to port.
+func derivedListenSpec(ctx *context.T, ls rpc.ListenSpec, port int) (rpc.ListenSpec, error) {
+	orig := ls.Addrs
+	ls.Addrs = nil
+	for _, a := range orig {
+		host, _, err := net.SplitHostPort(a.Address)
+		if err != nil {
+			err = verror.New(errSplitHostPortFailed, ctx, a.Address, err)
+			ctx.Errorf(err.Error())
+			return ls, err
+		}
+		a.Address = net.JoinHostPort(host, strconv.Itoa(port))
+		ls.Addrs = append(ls.Addrs, a)
+	}
+	return ls, nil
+}
diff --git a/services/device/devicex b/services/device/devicex
new file mode 100755
index 0000000..e288dbe
--- /dev/null
+++ b/services/device/devicex
@@ -0,0 +1,406 @@
+#!/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.
+#
+# Administers a device manager installation.
+#
+# This script is a thin wrapper on top of the deviced commands.  Its main
+# purpose is to set up the installation by fetching the binaries required for a
+# device manager installation from a few possible sources and setting up the
+# setuid helper.
+
+set -e
+
+usage() {
+  echo "usage:"
+  echo
+  echo "Install device manager:"
+  echo "V23_DEVICE_DIR=<installation dir> ./devicex install [<binary source>] [ args for installer... ] [ -- args for device manager...]"
+  echo "  Possible values for <binary source>:"
+  echo "     unspecified: get binaries from local repository"
+  echo "     /path/to/binaries: get binaries from local filesystem"
+  echo "     http://host/path: get binaries from HTTP server"
+  echo
+  echo "Uninstall device manager:"
+  echo "V23_DEVICE_DIR=<installation dir> ./devicex uninstall"
+  echo
+  echo "Start device manager:"
+  echo "V23_DEVICE_DIR=<installation dir> ./devicex start"
+  echo
+  echo "Stop device manager:"
+  echo "V23_DEVICE_DIR=<installation dir> ./devicex stop"
+  echo "V23_DEVICE_DIR should be 0711 when running in multi-user"
+  echo "mode and all of its parents directories need to be at least"
+  echo "0511."
+}
+
+###############################################################################
+# Wrapper around chown that works differently on Mac and Linux
+# Arguments:
+#   arguments to chown command
+# Returns:
+#   None
+###############################################################################
+portable_chown() {
+  case "$(uname)" in
+    "Darwin")
+      sudo /usr/sbin/chown "$@"
+      ;;
+    "Linux")
+      sudo chown "$@"
+      ;;
+  esac
+}
+
+###############################################################################
+# Sets up the target to be owned by root with the suid bit on.
+# Arguments:
+#   path to target
+# Returns:
+#   None
+###############################################################################
+make_suid() {
+  local -r target="$1"
+  local root_group="root"
+  if [[ "$(uname)" == "Darwin" ]]; then
+    # Group root not available on Darwin.
+    root_group="wheel"
+  fi
+  portable_chown "root:${root_group}" "${target}"
+  sudo chmod 4551 "${target}"
+}
+
+###############################################################################
+# Runs a command as the device manager user.  Assumes V23_DEVICE_DIR exists
+# and gets the device manager user from the owner of that directory.
+# Globals:
+#   V23_DEVICE_DIR
+# Arguments:
+#   command to run and its arguments
+# Returns:
+#   None
+###############################################################################
+run() {
+  local -r devmgr_user=$(getdevowner)
+  if [[ "${devmgr_user}" == $(whoami) ]]; then
+    "$@"
+  elif [[ "$(uname)" == "Darwin" ]]; then
+     # We use su -u on Darwin because Darwin su is different from Linux su
+      # and is not found in GCE or EC2 images.
+    sudo -u "${devmgr_user}" \
+         V23_NAMESPACE="${V23_NAMESPACE}" \
+         V23_DEVICE_DIR="${V23_DEVICE_DIR}" \
+         "$@"
+  else 
+    # We use sudo/su rather than just sudo -u because the latter is often
+    # set up to require a password in common GCE and EC2 images.
+    sudo V23_NAMESPACE="${V23_NAMESPACE}"  V23_DEVICE_DIR="${V23_DEVICE_DIR}" \
+      su "${devmgr_user}" -s /bin/bash -c \
+      "$*"
+  fi
+}
+
+readonly BIN_NAMES=(deviced suidhelper agentd inithelper)
+
+###############################################################################
+# Copies one binary from source to destination.
+# Arguments:
+#   name of the binary
+#   source dir of binary
+#   destination dir of binary
+# Returns:
+#   None
+###############################################################################
+copy_binary() {
+  local -r BIN_NAME="$1"
+  local -r BIN_SRC_DIR="$2"
+  local -r BIN_DEST_DIR="$3"
+  local -r SOURCE="${BIN_SRC_DIR}/${BIN_NAME}"
+  if [[ -x "${SOURCE}" ]]; then
+    local -r DESTINATION="${BIN_DEST_DIR}/${BIN_NAME}"
+    cp "${SOURCE}" "${DESTINATION}"
+    chmod 700 "${DESTINATION}"
+  else
+    echo "couldn't find ${SOURCE}"
+    exit 1
+  fi
+}
+
+###############################################################################
+# Fetches binaries needed by device manager installation.
+# Globals:
+#   BIN_NAMES
+#   V23_ROOT
+# Arguments:
+#   destination for binaries
+#   source of binaries
+# Returns:
+#   None
+###############################################################################
+get_binaries() {
+  local -r BIN_INSTALL="$1"
+  local -r BIN_SOURCE="$2"
+
+  local bin_names_str=""
+  for bin_name in "${BIN_NAMES[@]}"; do
+    bin_names_str+=" ${bin_name}"
+  done
+
+  # If source is not specified, try to look for it in the repository.
+  if [[ -z "${BIN_SOURCE}" ]]; then
+    if [[ -z "${V23_ROOT}" ]]; then
+      echo 'ERROR: binary source not specified and no local repository available'
+      exit 1
+    fi
+    local -r REPO_BIN_DIR="${V23_ROOT}/release/go/bin"
+    echo "Fetching binaries:${bin_names_str} from build repository: ${REPO_BIN_DIR} ..."
+    for bin_name in "${BIN_NAMES[@]}"; do
+      copy_binary "${bin_name}" "${REPO_BIN_DIR}" "${BIN_INSTALL}"
+    done
+    return
+  fi
+
+  # If the source is specified as an existing local filesystem path,
+  # look for the binaries there.
+  if [[ -d "${BIN_SOURCE}" ]]; then
+      echo "Fetching binaries:${bin_names_str} locally from: ${BIN_SOURCE} ..."
+      for bin_name in "${BIN_NAMES[@]}"; do
+        copy_binary "${bin_name}" "${BIN_SOURCE}" "${BIN_INSTALL}"
+      done
+      return
+  fi
+
+  # If the source looks like a URL, use HTTP to fetch.
+  local -r URL_REGEXP='^(https?|ftp|file)://'
+  if [[ "${BIN_SOURCE}" =~ ${URL_REGEXP} ]]; then
+    echo "Fetching binaries:${bin_names_str} remotely from: ${BIN_SOURCE} ..."
+    for bin_name in "${BIN_NAMES[@]}"; do
+      local DEST="${BIN_INSTALL}/${bin_name}"
+      curl -f -o "${DEST}" "${BIN_SOURCE}/${bin_name}"
+      chmod 700 "${DEST}"
+    done
+    return
+  fi
+
+  echo 'ERROR: couldn'"'"'t fetch binaries.'
+  exit 1
+}
+
+###############################################################################
+# Installs device manager: fetches binaries, configures suidhelper, calls the
+# install command on deviced.
+# Globals:
+#   V23_DEVICE_DIR
+# Arguments:
+#   source of binaries (optional)
+#   args for install command and for device manager (optional)
+# Returns:
+#   None
+###############################################################################
+install() {
+  if [[ -e "${V23_DEVICE_DIR}" ]]; then
+    echo "${V23_DEVICE_DIR} already exists!"
+    exit 1
+  fi
+  mkdir -p -m 711 "${V23_DEVICE_DIR}"
+  local -r BIN_INSTALL="${V23_DEVICE_DIR}/bin"
+  mkdir -m 700 "${BIN_INSTALL}"
+
+  # Fetch the binaries.
+  if [[ $# = 0 || "$1" == --* ]]; then
+    local -r BIN_SOURCE=""
+  else
+    local -r BIN_SOURCE="$1"
+    shift
+  fi
+  get_binaries "${BIN_INSTALL}" "${BIN_SOURCE}"
+  for bin_name in "${BIN_NAMES[@]}"; do
+    local BINARY="${BIN_INSTALL}/${bin_name}"
+    if [[ ! -s "${BINARY}" ]]; then
+      echo "${BINARY} is empty."
+      exit 1
+    fi
+  done
+  echo "Binaries are in ${BIN_INSTALL}."
+
+  # Set up the suidhelper.
+  echo "Configuring helpers ..."
+  local SINGLE_USER=false
+  local INIT_MODE=false
+  local DEVMGR_USER=$(whoami)
+  for ARG in $*; do
+    if [[ ${ARG} = "--" ]]; then
+      break
+    elif [[ ${ARG} = "--single_user" || ${ARG} = "--single_user=true" ]]; then
+      SINGLE_USER=true
+    elif [[ ${ARG} = "--init_mode" || ${ARG} = "--init_mode=true" ]]; then
+      INIT_MODE=true
+    elif [[ ${ARG} =~ --devuser=(.*) ]]; then
+      DEVMGR_USER="${BASH_REMATCH[1]}"
+    fi
+  done
+
+  if [[ ${SINGLE_USER} == false && ${DEVMGR_USER} == $(whoami) ]]; then
+    echo "Running in multi-user mode requires a --devuser=<user>"
+    echo "argument. This limits the following unfortunate chain of events:"
+    echo "install the device manager as yourself, associate an external blessee"
+    echo "with your local user name and the external blessee can invoke an app"
+    echo "which, because it has the same system name as the device manager,"
+    echo "can use suidhelper to give itself root priviledge."
+    exit 1
+  fi
+  if [[ ${SINGLE_USER}} == true && ${DEVMGR_USER} != $(whoami) ]]; then
+    echo "The --devuser flag is unnecessary in single-user mode because"
+    echo "all processes run as $(whoami)."
+    exit 1
+  fi
+  local -r SETUID_SCRIPT="${BIN_INSTALL}/suidhelper"
+  if [[ ${SINGLE_USER} == false ]]; then
+    portable_chown -R "${DEVMGR_USER}:bin" "${V23_DEVICE_DIR}"
+    make_suid "${SETUID_SCRIPT}"
+  fi
+  local -r INIT_SCRIPT="${BIN_INSTALL}/inithelper"
+  if [[ ${INIT_MODE} == true ]]; then
+    make_suid "${INIT_SCRIPT}"
+  fi
+  echo "Helpers configured."
+
+  # Install the device manager.
+  echo "Installing device manager under ${V23_DEVICE_DIR} ..."
+  echo "V23_DEVICE_DIR=${V23_DEVICE_DIR}"
+  run "${BIN_INSTALL}/deviced" install \
+    --suid_helper="${SETUID_SCRIPT}" \
+    --agent="${BIN_INSTALL}/agentd" \
+    --init_helper="${INIT_SCRIPT}" "$@"
+  echo "Device manager installed."
+}
+
+###############################################################################
+# Determines the owner of the device manager
+# Globals:
+#   V23_DEVICE_DIR
+# Arguments:
+#   None
+# Returns:
+#  user owning the device manager
+###############################################################################
+getdevowner() {
+  case "$(uname)" in
+    "Darwin")
+      ls -dl  "${V23_DEVICE_DIR}" | awk '{print $3}'
+      ;;
+    "Linux")
+      stat -c "%U" "${V23_DEVICE_DIR}"
+      ;;
+  esac
+}
+
+###############################################################################
+# Uninstalls device manager: calls the uninstall command of deviced and removes
+# the installation.
+# Globals:
+#   V23_DEVICE_DIR
+# Arguments:
+#   None
+# Returns:
+#   None
+###############################################################################
+uninstall() {
+  if [[ ! -d "${V23_DEVICE_DIR}" ]]; then
+    echo "${V23_DEVICE_DIR} does not exist or is not a directory!"
+    exit 1
+  fi
+  local -r BIN_INSTALL="${V23_DEVICE_DIR}/bin"
+  local -r SETUID_SCRIPT="${BIN_INSTALL}/suidhelper"
+  echo "Uninstalling device manager from ${V23_DEVICE_DIR} ..."
+  run "${BIN_INSTALL}/deviced" uninstall \
+    --suid_helper="${SETUID_SCRIPT}"
+
+   echo "Device manager uninstalled."
+   # Any data created underneath "${V23_DEVICE_DIR}" by the "deviced
+   # install" command would have been cleaned up already by "deviced uninstall".
+   # However, install() created "${V23_DEVICE_DIR}", so uninstall() needs
+   # to remove it (as well as data created by install(), like bin/*).
+
+   run rm -rf "${V23_DEVICE_DIR}/bin"
+   rmdir "${V23_DEVICE_DIR}"
+   echo "Removed ${V23_DEVICE_DIR}"
+}
+
+###############################################################################
+# Starts device manager: calls the start command of deviced.
+# Globals:
+#   V23_DEVICE_DIR
+# Arguments:
+#   None
+# Returns:
+#   None
+###############################################################################
+start() {
+  if [[ ! -d "${V23_DEVICE_DIR}" ]]; then
+    echo "${V23_DEVICE_DIR} does not exist or is not a directory!"
+    exit 1
+  fi
+  local -r BIN_INSTALL="${V23_DEVICE_DIR}/bin"
+    run "${BIN_INSTALL}/deviced" start  
+}
+
+###############################################################################
+# Stops device manager: calls the stop command of deviced.
+# Globals:
+#   V23_DEVICE_DIR
+# Arguments:
+#   None
+# Returns:
+#   None
+###############################################################################
+stop() {
+  if [[ ! -d "${V23_DEVICE_DIR}" ]]; then
+    echo "${V23_DEVICE_DIR} does not exist or is not a directory!"
+    exit 1
+  fi
+  local -r BIN_INSTALL="${V23_DEVICE_DIR}/bin"
+  run "${BIN_INSTALL}/deviced" stop
+}
+
+main() {
+  if [[ -z "${V23_DEVICE_DIR}" ]]; then
+    echo 'No local device installation dir specified!'
+    usage
+    exit 1
+  fi
+  if [[ -e "${V23_DEVICE_DIR}" && ! -d "${V23_DEVICE_DIR}" ]]; then
+    echo "${V23_DEVICE_DIR} is not a directory!"
+    usage
+    exit 1
+  fi
+
+  if [[ $# = 0 ]]; then
+    echo 'No command specified!'
+    usage
+    exit 1
+  fi
+  local -r COMMAND="$1"
+  shift
+  case "${COMMAND}" in
+    install)
+      install "$@"
+      ;;
+    uninstall)
+      uninstall
+      ;;
+    start)
+      start
+      ;;
+    stop)
+      stop
+      ;;
+    *)
+      echo "Unrecognized command: ${COMMAND}!"
+      usage
+      exit 1
+  esac
+}
+
+main "$@"
diff --git a/services/device/dmrun/dmrun.go b/services/device/dmrun/dmrun.go
new file mode 100644
index 0000000..3d5b5fa
--- /dev/null
+++ b/services/device/dmrun/dmrun.go
@@ -0,0 +1,322 @@
+// Command dmrun runs a binary on a remote GCE instance using device manager.
+//
+// dmrun creates the GCE instance, installs and starts device manager on it, and
+// then installs and starts an app from the specified binary.
+//
+// dmrun uses the credentials it is running with in order to claim the device
+// manager and provide the app with blessings.  To specify credentials for
+// dmrun, use the V23_CREDENTIALS environment variable instead of the
+// --v23.credentials flag.
+//
+// Usage:
+//   dmrun [ENV=VAL ...] path/to/binary [--flag=val ...]
+//
+// All flags and environment variable settings are passed to the app.
+package main
+
+import (
+	"archive/zip"
+	"flag"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"net"
+	"os"
+	"os/exec"
+	"os/user"
+	"path"
+	"path/filepath"
+	"regexp"
+	"strings"
+	"time"
+
+	"v.io/x/ref"
+)
+
+var (
+	workDir        string
+	vcloud         string
+	device         string
+	cleanupOnDeath func()
+)
+
+var dmBins = [...]string{
+	"v.io/x/ref/services/device/deviced",
+	"v.io/x/ref/services/agent/agentd",
+	"v.io/x/ref/services/device/inithelper",
+	"v.io/x/ref/services/device/suidhelper",
+}
+
+const (
+	vcloudBin   = "v.io/x/devtools/vcloud"
+	deviceBin   = "v.io/x/ref/services/device/device"
+	devicexRepo = "release.go.x.ref"
+	devicex     = "services/device/devicex"
+)
+
+func die(format string, args ...interface{}) {
+	fmt.Fprintf(os.Stderr, format, args...)
+	fmt.Fprintln(os.Stderr)
+	if cleanupOnDeath != nil {
+		savedCleanupFn := cleanupOnDeath
+		cleanupOnDeath = func() {
+			fmt.Fprintf(os.Stderr, "Avoided recursive call to cleanup in die()\n")
+		}
+		savedCleanupFn()
+	}
+	os.Exit(1)
+}
+
+func dieIfErr(err error, format string, args ...interface{}) {
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "Encountered error: %v\n", err)
+		die(format, args...)
+	}
+}
+
+// getPath returns the filesystem path to a file specified by a repository and a
+// repository-relative path.
+func getPath(repo, file string) string {
+	cmd := exec.Command("v23", "project", "list")
+	output, err := cmd.CombinedOutput()
+	out := string(output)
+	dieIfErr(err, "Running %v failed. Output:\n%v", strings.Join(cmd.Args, " "), out)
+	var projectPathRE = regexp.MustCompile(fmt.Sprintf("project=\"%s\" path=\"(.+)\"", repo))
+	matches := projectPathRE.FindStringSubmatch(out)
+	if matches == nil {
+		die("Couldn't extract project path from %s", out)
+	}
+	return filepath.Join(matches[1], filepath.FromSlash(file))
+}
+
+// setupWorkDir creates a directory for all the local files created by this
+// tool.
+func setupWorkDir() {
+	var err error
+	workDir, err = ioutil.TempDir("", filepath.Base(os.Args[0]))
+	dieIfErr(err, "Couldn't set up work dir")
+	dieIfErr(os.Chmod(workDir, 0777), "Couldn't chmod work dir")
+	fmt.Println("Working dir: %s", workDir)
+}
+
+// buildV23Binary builds the specified binary and returns the path to the
+// executable.
+func buildV23Binary(pkg string) string {
+	fmt.Println("Building", pkg)
+	dest := filepath.Join(workDir, path.Base(pkg))
+	cmd := exec.Command("v23", "go", "build", "-x", "-o", dest, pkg)
+	output, err := cmd.CombinedOutput()
+	dieIfErr(err, "Running build command %v failed. Output:\n%v", strings.Join(cmd.Args, " "), string(output))
+	return dest
+}
+
+// buildDMBinaries builds the binaries required for a device manager
+// installation and returns the paths to the executables.
+func buildDMBinaries() (ret []string) {
+	for _, b := range dmBins {
+		ret = append(ret, buildV23Binary(b))
+	}
+	return
+}
+
+// createArchive creates a zip archive from the given files.
+func createArchive(files []string) string {
+	zipFile := filepath.Join(workDir, "dm.zip")
+	z, err := os.OpenFile(zipFile, os.O_CREATE|os.O_WRONLY, os.FileMode(0644))
+	dieIfErr(err, "Couldn't create zip archive file")
+	defer z.Close()
+	w := zip.NewWriter(z)
+	for _, file := range files {
+		info, err := os.Stat(file)
+		dieIfErr(err, "Couldn't stat %v", file)
+		fh, err := zip.FileInfoHeader(info)
+		dieIfErr(err, "Couldn't set up file info header")
+		fh.Method = zip.Deflate
+		fwrite, err := w.CreateHeader(fh)
+		dieIfErr(err, "Couldn't create writer")
+		fread, err := os.Open(file)
+		dieIfErr(err, "Couldn't creater reader")
+		_, err = io.Copy(fwrite, fread)
+		dieIfErr(err, "Couldn't write to archive")
+		dieIfErr(fread.Close(), "Couldn't close reader")
+	}
+	dieIfErr(w.Close(), "Couldn't close zip archive")
+	return zipFile
+}
+
+// setupInstance creates a new GCE instance and returns its name and IP address.
+func setupInstance() (string, string) {
+	currUser, err := user.Current()
+	dieIfErr(err, "Couldn't obtain current user")
+	instanceName := fmt.Sprintf("%s-%s", currUser.Username, time.Now().UTC().Format("20060102-150405"))
+	// TODO(caprita): Allow project and zone to be customized.
+	cmd := exec.Command(vcloud, "node", "create", "--project=google.com:veyron", "--zone=us-central1-c", instanceName)
+	output, err := cmd.CombinedOutput()
+	dieIfErr(err, "Setting up new GCE instance (%v) failed. Output:\n%v", strings.Join(cmd.Args, " "), string(output))
+	cmd = exec.Command(vcloud, "list", "--project=google.com:veyron", "--noheader", "--fields=EXTERNAL_IP", instanceName)
+	output, err = cmd.CombinedOutput()
+	dieIfErr(err, "Listing instances (%v) failed. Output:\n%v", strings.Join(cmd.Args, " "), string(output))
+	instanceIP := strings.TrimSpace(string(output))
+	if net.ParseIP(instanceIP) == nil {
+		die("Not a valid IP address: %v", instanceIP)
+	}
+	// Install unzip so we can unpack the archive.
+	// TODO(caprita): Use tar instead.
+	cmd = exec.Command(vcloud, "sh", "--project=google.com:veyron", instanceName, "sudo", "apt-get", "install", "unzip")
+	output, err = cmd.CombinedOutput()
+	dieIfErr(err, "Installing unzip (%v) failed. Output:\n%v", strings.Join(cmd.Args, " "), string(output))
+	fmt.Println("Created GCE instance", instanceName, "with IP", instanceIP)
+	return instanceName, instanceIP
+}
+
+// installArchive ships the archive to the GCE instance and unpacks it.
+func installArchive(archive, instance string) {
+	cmd := exec.Command("gcloud", "compute", "--project=google.com:veyron", "copy-files", archive, fmt.Sprintf("veyron@%s:/tmp/", instance), "--zone=us-central1-c")
+	output, err := cmd.CombinedOutput()
+	dieIfErr(err, "Copying archive (%v) failed. Output:\n%v", strings.Join(cmd.Args, " "), string(output))
+	cmd = exec.Command(vcloud, "sh", "--project=google.com:veyron", instance, "unzip", path.Join("/tmp", filepath.Base(archive)), "-d", "/tmp/unpacked")
+	output, err = cmd.CombinedOutput()
+	dieIfErr(err, "Extracting archive (%v) failed. Output:\n%v", strings.Join(cmd.Args, " "), string(output))
+}
+
+// installDevice installs and starts device manager, and returns the public key
+// and pairing token needed for claiming.
+func installDevice(instance string) (string, string) {
+	fmt.Println("Installing device manager...")
+	defer fmt.Println("Done installing device manager...")
+	cmd := exec.Command(vcloud, "sh", "--project=google.com:veyron", instance, "V23_DEVICE_DIR=/tmp/dm", "/tmp/unpacked/devicex", "install", "/tmp/unpacked", "--single_user", "--", "--v23.tcp.address=:8151", "--deviced-port=8150", "--proxy-port=8160", "--use-pairing-token")
+	output, err := cmd.CombinedOutput()
+	dieIfErr(err, "Installing device manager (%v) failed. Output:\n%v", strings.Join(cmd.Args, " "), string(output))
+	cmd = exec.Command(vcloud, "sh", "--project=google.com:veyron", instance, "V23_DEVICE_DIR=/tmp/dm", "/tmp/unpacked/devicex", "start")
+	output, err = cmd.CombinedOutput()
+	dieIfErr(err, "Starting device manager (%v) failed. Output:\n%v", strings.Join(cmd.Args, " "), string(output))
+	// Grab the token and public key from the device manager log.
+	dieAfter := time.After(5 * time.Second)
+	firstIteration := true
+	for {
+		if !firstIteration {
+			select {
+			case <-dieAfter:
+				die("Failed to find token and public key in log: %v", string(output))
+			case <-time.After(100 * time.Millisecond):
+			}
+		} else {
+			firstIteration = false
+		}
+		cmd = exec.Command(vcloud, "sh", "--project=google.com:veyron", instance, "cat", "/tmp/dm/dmroot/device-manager/logs/deviced.INFO")
+		output, err = cmd.CombinedOutput()
+		dieIfErr(err, "Reading device manager log (%v) failed. Output:\n%v", strings.Join(cmd.Args, " "), string(output))
+		pairingTokenRE := regexp.MustCompile("Device manager pairing token: (.*)")
+		matches := pairingTokenRE.FindSubmatch(output)
+		if matches == nil {
+			continue
+		}
+		pairingToken := string(matches[1])
+		publicKeyRE := regexp.MustCompile("public_key: (.*)")
+		matches = publicKeyRE.FindSubmatch(output)
+		if matches == nil {
+			continue
+		}
+		publicKey := string(matches[1])
+		return publicKey, pairingToken
+	}
+}
+
+// setCredentialsEnv sets the command's environment to share the principal of
+// dmrun.
+func setCredentialsEnv(cmd *exec.Cmd) {
+	// TODO(caprita): This doesn't work with --v23.credentials.
+	if creds := os.Getenv(ref.EnvCredentials); len(creds) > 0 {
+		cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", ref.EnvCredentials, creds))
+	} else if agentCreds := os.Getenv(ref.EnvAgentEndpoint); len(agentCreds) > 0 {
+		cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", ref.EnvAgentEndpoint, agentCreds))
+	}
+}
+
+// claimDevice claims the device manager, blessing it with extension.
+func claimDevice(deviceName, ip, publicKey, pairingToken, extension string) {
+	fmt.Println("claiming device manager ...")
+	cmd := exec.Command(device, "claim", deviceName, extension, pairingToken, publicKey)
+	setCredentialsEnv(cmd)
+	output, err := cmd.CombinedOutput()
+	dieIfErr(err, "Claiming device manager (%v) failed. Output:\n%v", strings.Join(cmd.Args, " "), string(output))
+	cmd = exec.Command(device, "acl", "get", fmt.Sprintf("/%s:8151/devmgr/device", ip))
+	setCredentialsEnv(cmd)
+	output, err = cmd.CombinedOutput()
+	dieIfErr(err, "Getting device manager acls (%v) failed. Output:\n%v", strings.Join(cmd.Args, " "), string(output))
+	fmt.Printf("Done claiming device manager. Device manager ACLs:\n%s", string(output))
+}
+
+// installApp installs the binary specified on the command-line and returns the
+// Vanadium name for the installation object.
+func installApp(deviceName, ip string) string {
+	args := []string{fmt.Sprintf("--v23.proxy=/%s:8160", ip), "install-local", deviceName + "/apps", "app"}
+	args = append(args, flag.Args()...)
+	cmd := exec.Command(device, args...)
+	setCredentialsEnv(cmd)
+	cmd.Env = append(cmd.Env, fmt.Sprintf("V23_NAMESPACE=/%s:8151", ip))
+	output, err := cmd.CombinedOutput()
+	dieIfErr(err, "Installing app (%v) failed. Output:\n%v", strings.Join(cmd.Args, " "), string(output))
+	installationName := strings.TrimSpace(string(output))
+	fmt.Println("Installed", installationName)
+	return installationName
+}
+
+// startApp creates and launches an instance of the given installation, blessing
+// it with extension.  It returns the Vanadium name for the instance object.
+func startApp(installationName, extension string) string {
+	cmd := exec.Command(device, "instantiate", installationName, extension)
+	setCredentialsEnv(cmd)
+	output, err := cmd.CombinedOutput()
+	dieIfErr(err, "Instantiating app (%v) failed. Output:\n%v", strings.Join(cmd.Args, " "), string(output))
+	instanceName := strings.TrimSpace(string(output))
+	fmt.Println("Instantiated", instanceName)
+	cmd = exec.Command(device, "run", instanceName)
+	setCredentialsEnv(cmd)
+	output, err = cmd.CombinedOutput()
+	dieIfErr(err, "Starting app (%v) failed. Output:\n%v", strings.Join(cmd.Args, " "), string(output))
+	return instanceName
+}
+
+func main() {
+	flag.Parse()
+	if len(flag.Args()) == 0 {
+		fmt.Fprintf(os.Stderr, "Usage: %s <app> <arguments ... >\n", os.Args[0])
+		os.Exit(1)
+	}
+	setupWorkDir()
+	cleanupOnDeath = func() {
+		os.RemoveAll(workDir)
+	}
+	defer os.RemoveAll(workDir)
+	vcloud = buildV23Binary(vcloudBin)
+	device = buildV23Binary(deviceBin)
+	dmBins := buildDMBinaries()
+	archive := createArchive(append(dmBins, getPath(devicexRepo, devicex)))
+	gceInstanceName, gceInstanceIP := setupInstance()
+	cleanupOnDeath = func() {
+		fmt.Fprintf(os.Stderr, "Deleting GCE instance ...\n")
+		cmd := exec.Command(vcloud, "node", "delete", "--project=google.com:veyron", "--zone=us-central1-c", gceInstanceName)
+		output, err := cmd.CombinedOutput()
+		fmt.Fprintf(os.Stderr, "Removing tmp files ...\n")
+		os.RemoveAll(workDir)
+		dieIfErr(err, "Deleting GCE instance (%v) failed. Output:\n%v", strings.Join(cmd.Args, " "), string(output))
+	}
+	installArchive(archive, gceInstanceName)
+	publicKey, pairingToken := installDevice(gceInstanceName)
+	deviceAddr := net.JoinHostPort(gceInstanceIP, "8150")
+	deviceName := "/" + deviceAddr
+	claimDevice(deviceName, gceInstanceIP, publicKey, pairingToken, gceInstanceName)
+	installationName := installApp(deviceName, gceInstanceIP)
+	instanceName := startApp(installationName, "app")
+	fmt.Println("Launched app.")
+	fmt.Println("-------------")
+	fmt.Println("See its status:")
+	fmt.Printf("\t${V23_ROOT}/release/go/bin/device status %s\n", instanceName)
+	fmt.Println("See the logs:")
+	fmt.Printf("\t${V23_ROOT}/release/go/bin/debug glob %s/logs/*\n", instanceName)
+	fmt.Println("Dump e.g. the INFO log:")
+	fmt.Printf("\t${V23_ROOT}/release/go/bin/debug logs read %s/logs/app.INFO\n", instanceName)
+	fmt.Println("Clean up by deleting the GCE instance:")
+	fmt.Printf("\t${V23_ROOT}/release/go/bin/vcloud node delete --project=google.com:veyron --zone=us-central1-c %s\n", gceInstanceName)
+}
diff --git a/services/device/doc.go b/services/device/doc.go
new file mode 100644
index 0000000..6309631
--- /dev/null
+++ b/services/device/doc.go
@@ -0,0 +1,40 @@
+// 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 device defines interfaces for configuration of the Vanadium device
+// manager.  The subdirectories implement the v.io/v23/services/device
+// interfaces.
+//
+// The device manager is a server that is expected to run on every
+// Vanadium-enabled device, and it handles both device management and management
+// of the applications running on the device.
+//
+// The device manager is responsible for installing, updating, and launching
+// applications.  It therefore sets up a footprint on the local filesystem, both
+// to maintain its internal state, and to provide applications with their own
+// private workspaces.
+//
+// The device manager is responsible for updating itself.  The mechanism to do
+// so is implementation-dependent, though each device manager expects to be
+// supplied with the file path of a symbolic link file, which the device manager
+// will then update to point to the updated version of itself before terminating
+// itself.  The device manager should therefore be launched via this symbolic
+// link to enable auto-updates.  To enable updates, in addition to the symbolic
+// link path, the device manager needs to be told what its application metadata
+// is (such as command-line arguments and environment variables, i.e. the
+// application envelope defined in the v.io/v23/services/application package),
+// as well as the object name for where it can fetch an updated envelope, and
+// the local filesystem path for its previous version (for rollbacks).
+//
+// Finally, the device manager needs to know its own object name, so it can pass
+// that along to the applications that it starts.
+//
+// The impl subpackage contains the implementation of the device manager
+// service.
+//
+// The config subpackage encapsulates the configuration settings that form the
+// device manager service's 'contract' with its environment.
+//
+// The deviced subpackage contains the main driver.
+package device
diff --git a/services/device/inithelper/main.go b/services/device/inithelper/main.go
new file mode 100644
index 0000000..7e3301e
--- /dev/null
+++ b/services/device/inithelper/main.go
@@ -0,0 +1,111 @@
+// 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.
+
+// Command inithelper manages services for a variety of platforms and init
+// systems, such as upstart, systemd etc.
+package main
+
+// TODO(caprita): The separation of responsibilities between root/non-root code
+// can be shifted away from root a bit more, by having the init daemon files
+// created as non-root and root just installs files and runs a specific set of
+// commands.  Figure out if worth it.  Also consider combining with suidhelper.
+// For now they're separate since we don't always need both at the same time.
+
+// TODO(caprita): Add unit test.
+
+import (
+	"bytes"
+	"flag"
+	"fmt"
+	"os"
+
+	"v.io/x/ref/services/device/internal/sysinit"
+)
+
+func usage() {
+	const usage = `Usage:
+%s [flags] [command]
+
+ Flags:
+%s
+ Command:
+  print: prints the file that would be installed
+  install: installs the service
+  uninstall: uninstalls the service
+  start: starts the service
+  stop: stops the service
+`
+	var flagDefaults bytes.Buffer
+	flag.CommandLine.SetOutput(&flagDefaults)
+	flag.CommandLine.PrintDefaults()
+	flag.CommandLine.SetOutput(nil)
+	fmt.Fprintf(os.Stderr, usage, os.Args[0], flagDefaults.String())
+}
+
+func main() {
+	fmt.Fprintln(os.Stderr, os.Args)
+	if os.Geteuid() != 0 && os.Getuid() != 0 {
+		fmt.Fprintln(os.Stderr, "uid is ", os.Getuid(), ", effective uid is ", os.Geteuid())
+		fmt.Fprintln(os.Stderr, "inithelper is not root. Is your filesystem mounted with nosuid?")
+		os.Exit(1)
+	}
+
+	flag.Usage = usage
+	sdFlag := flag.String("service_description", "", "File containing a JSON-encoded sysinit.ServiceDescription object.")
+	systemFlag := flag.String("system", sysinit.InitSystem(), "System label, to select the appropriate sysinit mechanism.")
+	flag.Parse()
+	if *sdFlag == "" {
+		fmt.Fprintf(os.Stderr, "--service_description must be set.\n")
+		flag.Usage()
+		os.Exit(1)
+	}
+	if *systemFlag == "" {
+		fmt.Fprintf(os.Stderr, "--system must be set.\n")
+		flag.Usage()
+		os.Exit(1)
+	}
+	var sd sysinit.ServiceDescription
+	if err := sd.LoadFrom(*sdFlag); err != nil {
+		fmt.Fprintf(os.Stderr, "LoadFrom(%v) failed: %v\n", *sdFlag, err)
+		os.Exit(2)
+	}
+	si := sysinit.New(*systemFlag, &sd)
+	args := flag.Args()
+	if len(args) != 1 {
+		fmt.Fprintf(os.Stderr, "Command must be specified.\n")
+		flag.Usage()
+		os.Exit(1)
+	}
+	switch args[0] {
+	case "print":
+		if err := si.Print(); err != nil {
+			fmt.Fprintf(os.Stderr, "Failed to print %v: %s\n", si, err)
+			os.Exit(2)
+		}
+	case "install":
+		if err := si.Install(); err != nil {
+			fmt.Fprintf(os.Stderr, "Failed to install %v: %s\n", si, err)
+			os.Exit(2)
+		}
+	case "uninstall":
+		if err := si.Uninstall(); err != nil {
+			fmt.Fprintf(os.Stderr, "Failed to uninstall %v: %s\n", si, err)
+			os.Exit(2)
+		}
+	case "start":
+		if err := si.Start(); err != nil {
+			fmt.Fprintf(os.Stderr, "Failed to start %v: %s\n", si, err)
+			os.Exit(2)
+		}
+	case "stop":
+		if err := si.Stop(); err != nil {
+			fmt.Fprintf(os.Stderr, "Failed to stop %v: %s\n", si, err)
+			os.Exit(2)
+		}
+	default:
+		fmt.Fprintf(os.Stderr, "Invalid command: %s\n", args[0])
+		flag.Usage()
+		os.Exit(1)
+	}
+}
diff --git a/services/device/internal/claim/claim.go b/services/device/internal/claim/claim.go
new file mode 100644
index 0000000..55202d8
--- /dev/null
+++ b/services/device/internal/claim/claim.go
@@ -0,0 +1,125 @@
+// 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 claim
+
+import (
+	"crypto/subtle"
+	"os"
+	"sync"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/security/access"
+	"v.io/v23/services/device"
+	"v.io/v23/verror"
+	"v.io/x/ref/services/device/internal/errors"
+	"v.io/x/ref/services/internal/pathperms"
+)
+
+// NewClaimableDispatcher returns an rpc.Dispatcher that allows the device to
+// be Claimed if it hasn't been already and a channel that will be closed once
+// the device has been claimed.
+//
+// It returns (nil, nil) if the device is no longer claimable.
+func NewClaimableDispatcher(ctx *context.T, permsDir, pairingToken string, auth security.Authorizer) (rpc.Dispatcher, <-chan struct{}) {
+	permsStore := pathperms.NewPathStore(ctx)
+	if _, _, err := permsStore.Get(permsDir); !os.IsNotExist(err) {
+		// The device is claimable only if Claim hasn't been called before. The
+		// existence of the Permissions file is an indication of a successful prior
+		// call to Claim.
+		return nil, nil
+	}
+	notify := make(chan struct{})
+	return &claimable{token: pairingToken, permsStore: permsStore, permsDir: permsDir, notify: notify, auth: auth}, notify
+}
+
+// claimable implements the device.Claimable RPC interface and the
+// rpc.Dispatcher and security.Authorizer to serve it.
+//
+// It allows the Claim RPC to be successfully invoked exactly once.
+type claimable struct {
+	token      string
+	permsStore *pathperms.PathStore
+	permsDir   string
+	notify     chan struct{} // GUARDED_BY(mu)
+	auth       security.Authorizer
+
+	// Lock used to ensure that a successful claim can happen at most once.
+	// This is done by allowing only a single goroutine to execute the
+	// meaty parts of Claim at a time.
+	mu sync.Mutex
+}
+
+func (c *claimable) Claim(ctx *context.T, call rpc.ServerCall, pairingToken string) error {
+	// Verify that the claimer pairing tokens match that of the device manager.
+	if subtle.ConstantTimeCompare([]byte(pairingToken), []byte(c.token)) != 1 {
+		return verror.New(errors.ErrInvalidPairingToken, ctx)
+	}
+	var (
+		granted   = call.GrantedBlessings() // blessings granted by the claimant
+		principal = v23.GetPrincipal(ctx)
+		store     = principal.BlessingStore()
+	)
+	if granted.IsZero() {
+		return verror.New(errors.ErrInvalidBlessing, ctx)
+	}
+	c.mu.Lock()
+	defer c.mu.Unlock()
+	if c.notify == nil {
+		// Device has already been claimed (by a concurrent
+		// RPC perhaps), it cannot be reclaimed
+		return verror.New(errors.ErrDeviceAlreadyClaimed, ctx)
+	}
+	// TODO(ashankar): If the claim fails, would make sense
+	// to remove from roots as well.
+	if err := principal.AddToRoots(granted); err != nil {
+		return verror.New(errors.ErrInvalidBlessing, ctx)
+	}
+	if _, err := store.Set(granted, security.AllPrincipals); err != nil {
+		return verror.New(errors.ErrInvalidBlessing, ctx, err)
+	}
+	if err := store.SetDefault(granted); err != nil {
+		return verror.New(errors.ErrInvalidBlessing, ctx, err)
+	}
+
+	// Create Permissions with all the granted blessings (which are now the default blessings)
+	// (irrespective of caveats).
+	patterns := security.DefaultBlessingPatterns(principal)
+	if len(patterns) == 0 {
+		return verror.New(errors.ErrInvalidBlessing, ctx)
+	}
+
+	// Create Permissions that allow principals with the caller's blessings to
+	// administer and use the device.
+	perms := make(access.Permissions)
+	for _, bp := range patterns {
+		// TODO(caprita,ataly,ashankar): Do we really need the
+		// NonExtendable restriction below?
+		patterns := bp.MakeNonExtendable().PrefixPatterns()
+		for _, p := range patterns {
+			for _, tag := range access.AllTypicalTags() {
+				perms.Add(p, string(tag))
+			}
+		}
+	}
+	if err := c.permsStore.Set(c.permsDir, perms, ""); err != nil {
+		return verror.New(errors.ErrOperationFailed, ctx)
+	}
+	ctx.Infof("Device claimed and Permissions set to: %v", perms)
+	close(c.notify)
+	c.notify = nil
+	return nil
+}
+
+// TODO(ashankar): Remove this and use Serve instead of ServeDispatcher to setup
+// the Claiming service. Shouldn't need the "device" suffix.
+func (c *claimable) Lookup(_ *context.T, suffix string) (interface{}, security.Authorizer, error) {
+	if suffix != "" && suffix != "device" {
+		return nil, nil, verror.New(errors.ErrUnclaimedDevice, nil)
+	}
+	return device.ClaimableServer(c), c.auth, nil
+}
diff --git a/services/device/internal/config/config.go b/services/device/internal/config/config.go
new file mode 100644
index 0000000..491c2e3
--- /dev/null
+++ b/services/device/internal/config/config.go
@@ -0,0 +1,165 @@
+// 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 config handles configuration state passed across instances of the
+// device manager.
+//
+// The State object captures setting that the device manager needs to be aware
+// of when it starts.  This is passed to the first invocation of the device
+// manager, and then passed down from old device manager to new device manager
+// upon update.  The device manager has an implementation-dependent mechanism
+// for parsing and passing state, which is encapsulated by the state sub-package
+// (currently, the mechanism uses environment variables).  When instantiating a
+// new instance of the device manager service, the developer needs to pass in a
+// copy of State.  They can obtain this by calling Load, which captures any
+// config state passed by a previous version of device manager during update.
+// Any new version of the device manager must be able to decode a previous
+// version's config state, even if the new version changes the mechanism for
+// passing this state (that is, device manager implementations must be
+// backward-compatible as far as accepting and passing config state goes).
+// TODO(caprita): add config state versioning?
+package config
+
+import (
+	"encoding/json"
+	"fmt"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"v.io/v23/services/application"
+	"v.io/v23/verror"
+	"v.io/x/ref"
+)
+
+const pkgPath = "v.io/x/ref/services/device/internal/config"
+
+var (
+	errNeedName           = verror.Register(pkgPath+".errNeedName", verror.NoRetry, "{1:}{2:} Name cannot be empty{:_}")
+	errNeedRoot           = verror.Register(pkgPath+".errNeedRoot", verror.NoRetry, "{1:}{2:} Root cannot be empty{:_}")
+	errNeedCurrentLink    = verror.Register(pkgPath+".errNeedCurrentLink", verror.NoRetry, "{1:}{2:} CurrentLink cannot be empty{:_}")
+	errNeedHelper         = verror.Register(pkgPath+".errNeedHelper", verror.NoRetry, "{1:}{2:} Helper must be specified{:_}")
+	errCantDecodeEnvelope = verror.Register(pkgPath+".errCantDecodeEnvelope", verror.NoRetry, "{1:}{2:} failed to decode envelope from {3}{:_}")
+	errCantEncodeEnvelope = verror.Register(pkgPath+".errCantEncodeEnvelope", verror.NoRetry, "{1:}{2:} failed to encode envelope {3}{:_}")
+	errEvalSymlinksFailed = verror.Register(pkgPath+".errEvalSymlinksFailed", verror.NoRetry, "{1:}{2:} EvalSymlinks failed{:_}")
+)
+
+// State specifies how the device manager is configured.  This should
+// encapsulate what the device manager needs to know and/or be able to mutate
+// about its environment.
+type State struct {
+	// Name is the device manager's object name.  Must be non-empty.
+	Name string
+	// Envelope is the device manager's application envelope.  If nil, any
+	// envelope fetched from the application repository will trigger an
+	// update.
+	Envelope *application.Envelope
+	// Previous holds the local path to the previous version of the device
+	// manager.  If empty, revert is disabled.
+	Previous string
+	// Root is the directory on the local filesystem that contains
+	// the applications' workspaces.  Must be non-empty.
+	Root string
+	// Origin is the application repository object name for the device
+	// manager application.  If empty, update is disabled.
+	Origin string
+	// CurrentLink is the local filesystem soft link that should point to
+	// the version of the device manager binary/script through which device
+	// manager is started.  Device manager is expected to mutate this during
+	// a self-update.  Must be non-empty.
+	CurrentLink string
+	// Helper is the path to the setuid helper for running applications as
+	// specific users.
+	Helper string
+}
+
+// Validate checks the config state.
+func (c *State) Validate() error {
+	if c.Name == "" {
+		return verror.New(errNeedName, nil)
+	}
+	if c.Root == "" {
+		return verror.New(errNeedRoot, nil)
+	}
+	if c.CurrentLink == "" {
+		return verror.New(errNeedCurrentLink, nil)
+	}
+	if c.Helper == "" {
+		return verror.New(errNeedHelper, nil)
+	}
+	return nil
+}
+
+// Load reconstructs the config state passed to the device manager (presumably
+// by the parent device manager during an update).  Currently, this is done via
+// environment variables.
+func Load() (*State, error) {
+	var env *application.Envelope
+	if jsonEnvelope := os.Getenv(EnvelopeEnv); jsonEnvelope != "" {
+		env = new(application.Envelope)
+		if err := json.Unmarshal([]byte(jsonEnvelope), env); err != nil {
+			return nil, verror.New(errCantDecodeEnvelope, nil, jsonEnvelope, err)
+		}
+	}
+	return &State{
+		Envelope:    env,
+		Previous:    os.Getenv(PreviousEnv),
+		Root:        os.Getenv(RootEnv),
+		Origin:      os.Getenv(OriginEnv),
+		CurrentLink: os.Getenv(CurrentLinkEnv),
+		Helper:      os.Getenv(HelperEnv),
+	}, nil
+}
+
+// Save serializes the config state meant to be passed to a child device manager
+// during an update, returning a slice of "key=value" strings, which are
+// expected to be stuffed into environment variable settings by the caller.
+func (c *State) Save(envelope *application.Envelope) ([]string, error) {
+	var jsonEnvelope []byte
+	if envelope != nil {
+		var err error
+		if jsonEnvelope, err = json.Marshal(envelope); err != nil {
+			return nil, verror.New(errCantEncodeEnvelope, nil, envelope, err)
+		}
+	}
+	var currScript string
+	if _, err := os.Lstat(c.CurrentLink); !os.IsNotExist(err) {
+		if currScript, err = filepath.EvalSymlinks(c.CurrentLink); err != nil {
+			return nil, verror.New(errEvalSymlinksFailed, nil, err)
+		}
+	}
+	settings := map[string]string{
+		EnvelopeEnv:    string(jsonEnvelope),
+		PreviousEnv:    currScript,
+		RootEnv:        c.Root,
+		OriginEnv:      c.Origin,
+		CurrentLinkEnv: c.CurrentLink,
+		HelperEnv:      c.Helper,
+	}
+	// We need to manually pass the namespace roots to the child, since we
+	// currently don't have a way for the child to obtain this information
+	// from a config service at start-up.
+	roots, _ := ref.EnvNamespaceRoots()
+	var ret []string
+	for k, v := range roots {
+		ret = append(ret, k+"="+v)
+	}
+	for k, v := range settings {
+		ret = append(ret, k+"="+v)
+	}
+	return ret, nil
+}
+
+// QuoteEnv wraps environment variable values in double quotes, making them
+// suitable for inclusion in a bash script.
+func QuoteEnv(env []string) (ret []string) {
+	for _, e := range env {
+		if eqIdx := strings.Index(e, "="); eqIdx > 0 {
+			ret = append(ret, fmt.Sprintf("%s=%q", e[:eqIdx], e[eqIdx+1:]))
+		} else {
+			ret = append(ret, e)
+		}
+	}
+	return
+}
diff --git a/services/device/internal/config/config_test.go b/services/device/internal/config/config_test.go
new file mode 100644
index 0000000..968bda8
--- /dev/null
+++ b/services/device/internal/config/config_test.go
@@ -0,0 +1,135 @@
+// 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 config_test
+
+import (
+	"os"
+	"path/filepath"
+	"reflect"
+	"strings"
+	"testing"
+
+	"v.io/x/ref/services/device/internal/config"
+
+	"v.io/v23/services/application"
+)
+
+// TestState checks that encoding/decoding State to child/from parent works
+// as expected.
+func TestState(t *testing.T) {
+	var err error
+	currScript := filepath.Join(os.TempDir(), "fido/was/here")
+	if err := os.MkdirAll(currScript, 0700); err != nil {
+		t.Fatalf("MkdirAll(%v) failed: %v", currScript, err)
+	}
+	defer os.RemoveAll(currScript)
+	// On some operating systems (e.g. darwin) os.TempDir() can
+	// return a symlink. To avoid having to account for this
+	// eventuality later, evaluate the symlink.
+	currScript, err = filepath.EvalSymlinks(currScript)
+	if err != nil {
+		t.Fatalf("EvalSymlinks() failed: %v", err)
+	}
+	currLink := filepath.Join(os.TempDir(), "familydog")
+	if err := os.Symlink(currScript, currLink); err != nil {
+		t.Fatalf("Symlink(%v, %v) failed: %v", currScript, currLink, err)
+	}
+	defer os.Remove(currLink)
+	// For the same reasons mentioned above, evaluate the symlink.
+	currLink, err = filepath.EvalSymlinks(currLink)
+	if err != nil {
+		t.Fatalf("EvalSymlinks() failed: %v", err)
+	}
+	state := &config.State{
+		Name:        "fido",
+		Previous:    "doesn't matter",
+		Root:        "fidos/doghouse",
+		Origin:      "pet/store",
+		CurrentLink: currLink,
+		Helper:      "santas/little/helper",
+	}
+	if err := state.Validate(); err != nil {
+		t.Errorf("Config state %v failed to validate: %v", state, err)
+	}
+	encoded, err := state.Save(&application.Envelope{
+		Title: "dog",
+		Args:  []string{"roll-over", "play-dead"},
+	})
+	if err != nil {
+		t.Errorf("Config state %v Save failed: %v", state, err)
+	}
+	for _, e := range encoded {
+		pair := strings.SplitN(e, "=", 2)
+		os.Setenv(pair[0], pair[1])
+	}
+	decodedState, err := config.Load()
+	if err != nil {
+		t.Errorf("Config state Load failed: %v", err)
+	}
+	expectedState := state
+	expectedState.Envelope = &application.Envelope{
+		Title: "dog",
+		Args:  []string{"roll-over", "play-dead"},
+	}
+	expectedState.Name = ""
+	expectedState.Previous = currScript
+	if !reflect.DeepEqual(decodedState, expectedState) {
+		t.Errorf("Decode state: want %#v, got %#v", expectedState, decodedState)
+	}
+}
+
+// TestValidate checks the Validate method of State.
+func TestValidate(t *testing.T) {
+	state := &config.State{
+		Name:        "schweinsteiger",
+		Previous:    "a",
+		Root:        "b",
+		Origin:      "c",
+		CurrentLink: "d",
+		Helper:      "e",
+	}
+	if err := state.Validate(); err != nil {
+		t.Errorf("Config state %v failed to validate: %v", state, err)
+	}
+	state.Root = ""
+	if err := state.Validate(); err == nil {
+		t.Errorf("Config state %v should have failed to validate.", state)
+	}
+	state.Root, state.CurrentLink = "a", ""
+	if err := state.Validate(); err == nil {
+		t.Errorf("Confi stateg %v should have failed to validate.", state)
+	}
+	state.CurrentLink, state.Name = "d", ""
+	if err := state.Validate(); err == nil {
+		t.Errorf("Config state %v should have failed to validate.", state)
+	}
+	state.Name, state.Helper = "anything", ""
+	if err := state.Validate(); err == nil {
+		t.Errorf("Config state %v should have failed to validate.", state)
+	}
+}
+
+// TestQuoteEnv checks the QuoteEnv method.
+func TestQuoteEnv(t *testing.T) {
+	cases := []struct {
+		before, after string
+	}{
+		{`a=b`, `a="b"`},
+		{`a=`, `a=""`},
+		{`a`, `a`},
+		{`a=x y`, `a="x y"`},
+		{`a="x y"`, `a="\"x y\""`},
+		{`a='x y'`, `a="'x y'"`},
+	}
+	var input []string
+	var want []string
+	for _, c := range cases {
+		input = append(input, c.before)
+		want = append(want, c.after)
+	}
+	if got := config.QuoteEnv(input); !reflect.DeepEqual(want, got) {
+		t.Errorf("QuoteEnv(%v) wanted %v, got %v instead", input, want, got)
+	}
+}
diff --git a/services/device/internal/config/const.go b/services/device/internal/config/const.go
new file mode 100644
index 0000000..e6d33ba
--- /dev/null
+++ b/services/device/internal/config/const.go
@@ -0,0 +1,28 @@
+// 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 config
+
+const (
+	// EnvelopeEnv is the name of the environment variable that holds the
+	// serialized device manager application envelope.
+	EnvelopeEnv = "V23_DM_ENVELOPE"
+	// PreviousEnv is the name of the environment variable that holds the
+	// path to the previous version of the device manager.
+	PreviousEnv = "V23_DM_PREVIOUS"
+	// OriginEnv is the name of the environment variable that holds the
+	// object name of the application repository that can be used to
+	// retrieve the device manager application envelope.
+	OriginEnv = "V23_DM_ORIGIN"
+	// RootEnv is the name of the environment variable that holds the
+	// path to the directory in which device manager workspaces are
+	// created.
+	RootEnv = "V23_DM_ROOT"
+	// CurrentLinkEnv is the name of the environment variable that holds
+	// the path to the soft link that points to the current device manager.
+	CurrentLinkEnv = "V23_DM_CURRENT"
+	// HelperEnv is the name of the environment variable that holds the path
+	// to the suid helper used to start apps as specific system users.
+	HelperEnv = "V23_DM_HELPER"
+)
diff --git a/services/device/internal/errors/errors.go b/services/device/internal/errors/errors.go
new file mode 100644
index 0000000..f268f18
--- /dev/null
+++ b/services/device/internal/errors/errors.go
@@ -0,0 +1,31 @@
+// 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.
+
+// TODO(caprita): Consider moving these to v23 if they're meant to be public
+// beyond the deviced and device tool implementations.
+
+// Package errors defines the error ids that are shared between server and
+// client-side.
+package errors
+
+import "v.io/v23/verror"
+
+// TODO(caprita): the value of pkgPath corresponds to the previous package where
+// the error ids were defined.  Updating error ids needs to be carefully
+// coordinated between clients and servers, so we should do it when we settle on
+// the final location for these error definitions.
+const pkgPath = "v.io/x/ref/services/device/internal/impl"
+
+var (
+	ErrInvalidSuffix        = verror.Register(pkgPath+".InvalidSuffix", verror.NoRetry, "{1:}{2:} invalid suffix{:_}")
+	ErrOperationFailed      = verror.Register(pkgPath+".OperationFailed", verror.NoRetry, "{1:}{2:} operation failed{:_}")
+	ErrOperationInProgress  = verror.Register(pkgPath+".OperationInProgress", verror.NoRetry, "{1:}{2:} operation in progress{:_}")
+	ErrAppTitleMismatch     = verror.Register(pkgPath+".AppTitleMismatch", verror.NoRetry, "{1:}{2:} app title mismatch{:_}")
+	ErrUpdateNoOp           = verror.Register(pkgPath+".UpdateNoOp", verror.NoRetry, "{1:}{2:} update is no op{:_}")
+	ErrInvalidOperation     = verror.Register(pkgPath+".InvalidOperation", verror.NoRetry, "{1:}{2:} invalid operation{:_}")
+	ErrInvalidBlessing      = verror.Register(pkgPath+".InvalidBlessing", verror.NoRetry, "{1:}{2:} invalid blessing{:_}")
+	ErrInvalidPairingToken  = verror.Register(pkgPath+".InvalidPairingToken", verror.NoRetry, "{1:}{2:} pairing token mismatch{:_}")
+	ErrUnclaimedDevice      = verror.Register(pkgPath+".UnclaimedDevice", verror.NoRetry, "{1:}{2:} device needs to be claimed first")
+	ErrDeviceAlreadyClaimed = verror.Register(pkgPath+".AlreadyClaimed", verror.NoRetry, "{1:}{2:} device has already been claimed")
+)
diff --git a/services/device/internal/suid/args.go b/services/device/internal/suid/args.go
new file mode 100644
index 0000000..1473654
--- /dev/null
+++ b/services/device/internal/suid/args.go
@@ -0,0 +1,228 @@
+// 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 suid
+
+import (
+	"bytes"
+	"encoding/json"
+	"flag"
+	"fmt"
+	"os"
+	"os/user"
+	"strconv"
+	"strings"
+
+	"v.io/v23/verror"
+)
+
+const pkgPath = "v.io/x/ref/services/device/internal/suid"
+
+var (
+	errUserNameMissing = verror.Register(pkgPath+".errUserNameMissing", verror.NoRetry, "{1:}{2:} --username missing{:_}")
+	errUnknownUser     = verror.Register(pkgPath+".errUnknownUser", verror.NoRetry, "{1:}{2:} --username {3}: unknown user{:_}")
+	errInvalidUID      = verror.Register(pkgPath+".errInvalidUID", verror.NoRetry, "{1:}{2:} user.Lookup() returned an invalid uid {3}{:_}")
+	errInvalidGID      = verror.Register(pkgPath+".errInvalidGID", verror.NoRetry, "{1:}{2:} user.Lookup() returned an invalid gid {3}{:_}")
+	errUIDTooLow       = verror.Register(pkgPath+".errUIDTooLow", verror.NoRetry, "{1:}{2:} suidhelper uid {3} is not permitted because it is less than {4}{:_}")
+	errAtoiFailed      = verror.Register(pkgPath+".errAtoiFailed", verror.NoRetry, "{1:}{2:} strconv.Atoi({3}) failed{:_}")
+	errInvalidFlags    = verror.Register(pkgPath+".errInvalidFlags", verror.NoRetry, "{1:}{2:} invalid flags ({3} are set){:_}")
+)
+
+type WorkParameters struct {
+	uid       int
+	gid       int
+	workspace string
+	logDir    string
+	argv0     string
+	argv      []string
+	envv      []string
+	dryrun    bool
+	remove    bool
+	chown     bool
+	kill      bool
+	killPids  []int
+}
+
+type ArgsSavedForTest struct {
+	Uname    string
+	Workpace string
+	Run      string
+	LogDir   string
+}
+
+const SavedArgs = "V23_SAVED_ARGS"
+
+var (
+	flagUsername, flagWorkspace, flagLogDir, flagRun, flagProgName *string
+	flagMinimumUid                                                 *int64
+	flagRemove, flagKill, flagChown, flagDryrun                    *bool
+)
+
+func init() {
+	setupFlags(flag.CommandLine)
+}
+
+func setupFlags(fs *flag.FlagSet) {
+	const uidThreshold = 501
+	flagUsername = fs.String("username", "", "The UNIX user name used for the other functions of this tool.")
+	flagWorkspace = fs.String("workspace", "", "Path to the application's workspace directory.")
+	flagLogDir = fs.String("logdir", "", "Path to the log directory.")
+	flagRun = fs.String("run", "", "Path to the application to exec.")
+	flagProgName = fs.String("progname", "unnamed_app", "Visible name of the application, used in argv[0]")
+	flagMinimumUid = fs.Int64("minuid", uidThreshold, "UIDs cannot be less than this number.")
+	flagRemove = fs.Bool("rm", false, "Remove the file trees given as command-line arguments.")
+	flagKill = fs.Bool("kill", false, "Kill process ids given as command-line arguments.")
+	flagChown = fs.Bool("chown", false, "Change owner of files and directories given as command-line arguments to the user specified by this flag")
+	flagDryrun = fs.Bool("dryrun", false, "Elides root-requiring systemcalls.")
+}
+
+func cleanEnv(env []string) []string {
+	nenv := []string{}
+	for _, e := range env {
+		if !strings.HasPrefix(e, "V23_SUIDHELPER_TEST") {
+			nenv = append(nenv, e)
+		}
+	}
+	return nenv
+}
+
+// checkFlagCombinations makes sure that a valid combination of flags has been
+// specified for rm/kill/chown
+//
+// --rm and --kill are modal. Complain if any other flag is set along with one of
+// those.  --chown allows specification of --username, --dryrun, and --minuid,
+// but nothing else
+func checkFlagCombinations(fs *flag.FlagSet) error {
+	if !(*flagRemove || *flagKill || *flagChown) {
+		return nil
+	}
+
+	// Count flags that are set. The device manager test always sets --minuid=1
+	// and --test.run=TestSuidHelper so when in a test, tolerate those.
+	flagsToIgnore := map[string]string{}
+	if os.Getenv("V23_SUIDHELPER_TEST") != "" {
+		flagsToIgnore["minuid"] = "1"
+		flagsToIgnore["test.run"] = "TestSuidHelper"
+	}
+	if *flagChown {
+		// Allow all values of --username, --dryrun, and --minuid
+		flagsToIgnore["username"] = "*"
+		flagsToIgnore["dryrun"] = "*"
+		flagsToIgnore["minuid"] = "*"
+	}
+
+	counter := 0
+	fs.Visit(func(f *flag.Flag) {
+		if flagsToIgnore[f.Name] != f.Value.String() && flagsToIgnore[f.Name] != "*" {
+			counter++
+		}
+	})
+
+	if counter > 1 {
+		return verror.New(errInvalidFlags, nil, counter, "--rm and --kill cannot be used with any other flag. --chown can only be used with --username, --dryrun, and --minuid")
+	}
+	return nil
+}
+
+// warnMissingSuidPrivs makes it a little easier to debug when suid privs are required but
+// are not present. It's not a comprehensive check -- e.g. we may be running as user
+// <username> and suppress the warning, but still fail to chown a file owned by some other user.
+func warnMissingSuidPrivs(uid int) {
+	osUid, osEuid := os.Getuid(), os.Geteuid()
+	if osUid == 0 || osEuid == 0 || osUid == uid || osEuid == uid {
+		return
+	}
+
+	fmt.Fprintln(os.Stderr, "uid is ", os.Getuid(), ", effective uid is ", os.Geteuid())
+	fmt.Fprintln(os.Stderr, "WARNING: suidhelper is not root. Is your filesystem mounted with nosuid?")
+}
+
+// ParseArguments populates the WorkParameter object from the provided args
+// and env strings.
+func (wp *WorkParameters) ProcessArguments(fs *flag.FlagSet, env []string) error {
+	if err := checkFlagCombinations(fs); err != nil {
+		return err
+	}
+
+	if *flagRemove {
+		wp.remove = true
+		wp.argv = fs.Args()
+		return nil
+	}
+
+	if *flagKill {
+		wp.kill = true
+		for _, p := range fs.Args() {
+			pid, err := strconv.Atoi(p)
+			if err != nil {
+				wp.killPids = nil
+				return verror.New(errAtoiFailed, nil, p, err)
+			}
+			wp.killPids = append(wp.killPids, pid)
+		}
+		return nil
+	}
+
+	username := *flagUsername
+	if username == "" {
+		return verror.New(errUserNameMissing, nil)
+	}
+
+	usr, err := user.Lookup(username)
+	if err != nil {
+		return verror.New(errUnknownUser, nil, username)
+	}
+
+	uid, err := strconv.ParseInt(usr.Uid, 0, 32)
+	if err != nil {
+		return verror.New(errInvalidUID, nil, usr.Uid)
+	}
+	gid, err := strconv.ParseInt(usr.Gid, 0, 32)
+	if err != nil {
+		return verror.New(errInvalidGID, nil, usr.Gid)
+	}
+	warnMissingSuidPrivs(int(uid))
+
+	// Uids less than 501 can be special so we forbid running as them.
+	if uid < *flagMinimumUid {
+		return verror.New(errUIDTooLow, nil,
+			uid, *flagMinimumUid)
+	}
+	wp.uid = int(uid)
+	wp.gid = int(gid)
+
+	wp.dryrun = *flagDryrun
+
+	// At this point, all flags allowed by --chown have been processed
+	if *flagChown {
+		wp.chown = true
+		wp.argv = fs.Args()
+		return nil
+	}
+
+	// Preserve the arguments for examination by the test harness if executed
+	// in the course of a test.
+	if os.Getenv("V23_SUIDHELPER_TEST") != "" {
+		env = cleanEnv(env)
+		b := new(bytes.Buffer)
+		enc := json.NewEncoder(b)
+		enc.Encode(ArgsSavedForTest{
+			Uname:    *flagUsername,
+			Workpace: *flagWorkspace,
+			Run:      *flagRun,
+			LogDir:   *flagLogDir,
+		})
+		env = append(env, SavedArgs+"="+b.String())
+		wp.dryrun = true
+	}
+
+	wp.workspace = *flagWorkspace
+	wp.argv0 = *flagRun
+	wp.logDir = *flagLogDir
+	wp.argv = append([]string{*flagProgName}, fs.Args()...)
+	// TODO(rjkroege): Reduce the environment to the absolute minimum needed.
+	wp.envv = env
+
+	return nil
+}
diff --git a/services/device/internal/suid/args_darwin_test.go b/services/device/internal/suid/args_darwin_test.go
new file mode 100644
index 0000000..f8eeacb
--- /dev/null
+++ b/services/device/internal/suid/args_darwin_test.go
@@ -0,0 +1,11 @@
+// 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 suid
+
+const (
+	testUserName = "_uucp"
+	testUid      = 4
+	testGid      = 4
+)
diff --git a/services/device/internal/suid/args_linux_test.go b/services/device/internal/suid/args_linux_test.go
new file mode 100644
index 0000000..9e66342
--- /dev/null
+++ b/services/device/internal/suid/args_linux_test.go
@@ -0,0 +1,11 @@
+// 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 suid
+
+const (
+	testUserName = "uucp"
+	testUid      = 10
+	testGid      = 10
+)
diff --git a/services/device/internal/suid/args_test.go b/services/device/internal/suid/args_test.go
new file mode 100644
index 0000000..491a171
--- /dev/null
+++ b/services/device/internal/suid/args_test.go
@@ -0,0 +1,192 @@
+// 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 suid
+
+import (
+	"flag"
+	"reflect"
+	"testing"
+
+	"v.io/v23/verror"
+)
+
+func TestParseArguments(t *testing.T) {
+	cases := []struct {
+		cmdline  []string
+		env      []string
+		errID    verror.ID
+		expected WorkParameters
+	}{
+
+		{
+			[]string{"setuidhelper"},
+			[]string{},
+			errUserNameMissing.ID,
+			WorkParameters{},
+		},
+
+		{
+			[]string{"setuidhelper", "--minuid", "1", "--username", testUserName},
+			[]string{"A=B"},
+			"",
+			WorkParameters{
+				uid:       testUid,
+				gid:       testGid,
+				workspace: "",
+				logDir:    "",
+				argv0:     "",
+				argv:      []string{"unnamed_app"},
+				envv:      []string{"A=B"},
+				dryrun:    false,
+				remove:    false,
+				chown:     false,
+				kill:      false,
+				killPids:  nil,
+			},
+		},
+
+		{
+			[]string{"setuidhelper", "--minuid", "1", "--username", testUserName, "--workspace", "/hello",
+				"--logdir", "/logging", "--run", "/bin/v23", "--", "one", "two"},
+			[]string{"A=B"},
+			"",
+			WorkParameters{
+				uid:       testUid,
+				gid:       testGid,
+				workspace: "/hello",
+				logDir:    "/logging",
+				argv0:     "/bin/v23",
+				argv:      []string{"unnamed_app", "one", "two"},
+				envv:      []string{"A=B"},
+				dryrun:    false,
+				remove:    false,
+				chown:     false,
+				kill:      false,
+				killPids:  nil,
+			},
+		},
+
+		{
+			[]string{"setuidhelper", "--username", testUserName},
+			[]string{"A=B"},
+			errUIDTooLow.ID,
+			WorkParameters{},
+		},
+
+		{
+			[]string{"setuidhelper", "--rm", "hello", "vanadium"},
+			[]string{"A=B"},
+			"",
+			WorkParameters{
+				uid:       0,
+				gid:       0,
+				workspace: "",
+				logDir:    "",
+				argv0:     "",
+				argv:      []string{"hello", "vanadium"},
+				envv:      nil,
+				dryrun:    false,
+				remove:    true,
+				chown:     false,
+				kill:      false,
+				killPids:  nil,
+			},
+		},
+
+		{
+			[]string{"setuidhelper", "--chown", "--username", testUserName, "--dryrun", "--minuid", "1", "/tmp/foo", "/tmp/bar"},
+			[]string{"A=B"},
+			"",
+			WorkParameters{
+				uid:       testUid,
+				gid:       testGid,
+				workspace: "",
+				logDir:    "",
+				argv0:     "",
+				argv:      []string{"/tmp/foo", "/tmp/bar"},
+				envv:      nil,
+				dryrun:    true,
+				remove:    false,
+				chown:     true,
+				kill:      false,
+				killPids:  nil,
+			},
+		},
+
+		{
+			[]string{"setuidhelper", "--kill", "235", "451"},
+			[]string{"A=B"},
+			"",
+			WorkParameters{
+				uid:       0,
+				gid:       0,
+				workspace: "",
+				logDir:    "",
+				argv0:     "",
+				argv:      nil,
+				envv:      nil,
+				dryrun:    false,
+				remove:    false,
+				chown:     false,
+				kill:      true,
+				killPids:  []int{235, 451},
+			},
+		},
+
+		{
+			[]string{"setuidhelper", "--kill", "235", "451oops"},
+			[]string{"A=B"},
+			errAtoiFailed.ID,
+			WorkParameters{
+				uid:       0,
+				gid:       0,
+				workspace: "",
+				logDir:    "",
+				argv0:     "",
+				argv:      nil,
+				envv:      nil,
+				dryrun:    false,
+				remove:    false,
+				chown:     false,
+				kill:      true,
+				killPids:  nil,
+			},
+		},
+
+		{
+			[]string{"setuidhelper", "--minuid", "1", "--username", testUserName, "--workspace", "/hello", "--progname", "binaryd/vanadium/app/testapp",
+				"--logdir", "/logging", "--run", "/bin/v23", "--dryrun", "--", "one", "two"},
+			[]string{"A=B"},
+			"",
+			WorkParameters{
+				uid:       testUid,
+				gid:       testGid,
+				workspace: "/hello",
+				logDir:    "/logging",
+				argv0:     "/bin/v23",
+				argv:      []string{"binaryd/vanadium/app/testapp", "one", "two"},
+				envv:      []string{"A=B"},
+				dryrun:    true,
+				remove:    false,
+				chown:     false,
+				kill:      false,
+				killPids:  nil,
+			},
+		},
+	}
+
+	for _, c := range cases {
+		var wp WorkParameters
+		fs := flag.NewFlagSet(c.cmdline[0], flag.ExitOnError)
+		setupFlags(fs)
+		fs.Parse(c.cmdline[1:])
+		if err := wp.ProcessArguments(fs, c.env); (err != nil || c.errID != "") && verror.ErrorID(err) != c.errID {
+			t.Fatalf("got %s (%v), expected %q error", verror.ErrorID(err), err, c.errID)
+		}
+		if !reflect.DeepEqual(wp, c.expected) {
+			t.Fatalf("got %#v expected %#v", wp, c.expected)
+		}
+	}
+}
diff --git a/services/device/internal/suid/constants.go b/services/device/internal/suid/constants.go
new file mode 100644
index 0000000..178603e
--- /dev/null
+++ b/services/device/internal/suid/constants.go
@@ -0,0 +1,11 @@
+// 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 suid
+
+const (
+	// fd of the pipe to be used to return the pid of the forked child to the
+	// device manager.
+	PipeToParentFD = 5
+)
diff --git a/services/device/internal/suid/run.go b/services/device/internal/suid/run.go
new file mode 100644
index 0000000..3814353
--- /dev/null
+++ b/services/device/internal/suid/run.go
@@ -0,0 +1,30 @@
+// 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 suid
+
+import (
+	"flag"
+)
+
+func Run(environ []string) error {
+	var work WorkParameters
+	if err := work.ProcessArguments(flag.CommandLine, environ); err != nil {
+		return err
+	}
+
+	if work.remove {
+		return work.Remove()
+	}
+
+	if work.kill {
+		return work.Kill()
+	}
+
+	if err := work.Chown(); err != nil {
+		return err
+	}
+
+	return work.Exec()
+}
diff --git a/services/device/internal/suid/system.go b/services/device/internal/suid/system.go
new file mode 100644
index 0000000..c647558
--- /dev/null
+++ b/services/device/internal/suid/system.go
@@ -0,0 +1,134 @@
+// 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 linux darwin
+
+package suid
+
+import (
+	"encoding/binary"
+	"log"
+	"os"
+	"path/filepath"
+	"syscall"
+
+	"v.io/v23/verror"
+)
+
+var (
+	errChownFailed        = verror.Register(pkgPath+".errChownFailed", verror.NoRetry, "{1:}{2:} os.Chown({3}, {4}, {5}) failed{:_}")
+	errGetwdFailed        = verror.Register(pkgPath+".errGetwdFailed", verror.NoRetry, "{1:}{2:} os.Getwd failed{:_}")
+	errStartProcessFailed = verror.Register(pkgPath+".errStartProcessFailed", verror.NoRetry, "{1:}{2:} syscall.StartProcess({3}) failed{:_}")
+	errRemoveAllFailed    = verror.Register(pkgPath+".errRemoveAllFailed", verror.NoRetry, "{1:}{2:} os.RemoveAll({3}) failed{:_}")
+	errFindProcessFailed  = verror.Register(pkgPath+".errFindProcessFailed", verror.NoRetry, "{1:}{2:} os.FindProcess({3}) failed{:_}")
+	errKillFailed         = verror.Register(pkgPath+".errKillFailed", verror.NoRetry, "{1:}{2:} os.Process.Kill({3}) failed{:_}")
+)
+
+// Chown is only availabe on UNIX platforms so this file has a build
+// restriction.
+func (hw *WorkParameters) Chown() error {
+	chown := func(path string, _ os.FileInfo, inerr error) error {
+		if inerr != nil {
+			return inerr
+		}
+		if hw.dryrun {
+			log.Printf("[dryrun] os.Chown(%s, %d, %d)", path, hw.uid, hw.gid)
+			return nil
+		}
+		return os.Chown(path, hw.uid, hw.gid)
+	}
+
+	chownPaths := hw.argv
+	if !hw.chown {
+		// Chown was invoked as part of regular suid execution, rather than directly
+		// via --chown. In that case, we chown the workspace and log directory
+		// TODO(rjkroege): Ensure that the device manager can read log entries.
+		chownPaths = []string{hw.workspace, hw.logDir}
+	}
+
+	for _, p := range chownPaths {
+		if err := filepath.Walk(p, chown); err != nil {
+			return verror.New(errChownFailed, nil, p, hw.uid, hw.gid, err)
+		}
+	}
+	return nil
+}
+
+func (hw *WorkParameters) Exec() error {
+	attr := new(syscall.ProcAttr)
+
+	dir, err := os.Getwd()
+	if err != nil {
+		log.Printf("error Getwd(): %v", err)
+		return verror.New(errGetwdFailed, nil, err)
+	}
+	attr.Dir = dir
+	attr.Env = hw.envv
+	attr.Files = []uintptr{
+		uintptr(syscall.Stdin),
+		uintptr(syscall.Stdout),
+		uintptr(syscall.Stderr),
+	}
+
+	attr.Sys = new(syscall.SysProcAttr)
+	attr.Sys.Setsid = true
+	if hw.dryrun {
+		log.Printf("[dryrun] syscall.Setgid(%d)", hw.gid)
+		log.Printf("[dryrun] syscall.Setuid(%d)", hw.uid)
+	} else {
+		attr.Sys.Credential = new(syscall.Credential)
+		attr.Sys.Credential.Gid = uint32(hw.gid)
+		attr.Sys.Credential.Uid = uint32(hw.uid)
+	}
+
+	// Make sure the child won't talk on the fd we use to talk back to the parent
+	syscall.CloseOnExec(PipeToParentFD)
+
+	// Start the child process
+	pid, _, err := syscall.StartProcess(hw.argv0, hw.argv, attr)
+	if err != nil {
+		if !hw.dryrun {
+			log.Printf("StartProcess failed: attr: %#v, attr.Sys: %#v, attr.Sys.Cred: %#v error: %v", attr, attr.Sys, attr.Sys.Credential, err)
+		} else {
+			log.Printf("StartProcess failed: %v", err)
+		}
+		return verror.New(errStartProcessFailed, nil, hw.argv0, err)
+	}
+
+	// Return the pid of the new child process
+	pipeToParent := os.NewFile(PipeToParentFD, "pipe_to_parent_wr")
+	if err = binary.Write(pipeToParent, binary.LittleEndian, int32(pid)); err != nil {
+		log.Printf("Problem returning pid to parent: %v", err)
+	} else {
+		log.Printf("Returned pid %v to parent", pid)
+	}
+
+	os.Exit(0)
+	return nil // Not reached.
+}
+
+func (hw *WorkParameters) Remove() error {
+	for _, p := range hw.argv {
+		if err := os.RemoveAll(p); err != nil {
+			return verror.New(errRemoveAllFailed, nil, p, err)
+		}
+	}
+	return nil
+}
+
+func (hw *WorkParameters) Kill() error {
+	for _, pid := range hw.killPids {
+
+		switch err := syscall.Kill(pid, 9); err {
+		case syscall.ESRCH:
+			// No such PID.
+			log.Printf("process pid %d already killed", pid)
+		default:
+			// Something went wrong.
+			return verror.New(errKillFailed, nil, pid, err)
+		}
+
+	}
+	return nil
+}
diff --git a/services/device/internal/suid/system_test.go b/services/device/internal/suid/system_test.go
new file mode 100644
index 0000000..98c3091
--- /dev/null
+++ b/services/device/internal/suid/system_test.go
@@ -0,0 +1,74 @@
+// 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 suid
+
+import (
+	"bytes"
+	"io/ioutil"
+	"log"
+	"os"
+	"path"
+	"testing"
+)
+
+func TestChown(t *testing.T) {
+	dir, err := ioutil.TempDir("", "chown_test")
+	if err != nil {
+		t.Fatalf("ioutil.TempDir() failed: %v", err)
+	}
+	defer os.RemoveAll(dir)
+
+	for _, p := range []string{"a/b/c", "c/d"} {
+		fp := path.Join(dir, p)
+		if err := os.MkdirAll(fp, os.FileMode(0700)); err != nil {
+			t.Fatalf("os.MkdirAll(%s) failed: %v", fp, err)
+		}
+	}
+
+	wp := &WorkParameters{
+		uid:       42,
+		gid:       7,
+		logDir:    path.Join(dir, "a"),
+		workspace: path.Join(dir, "c"),
+
+		dryrun: true,
+	}
+
+	// Collect the log entries.
+	b := new(bytes.Buffer)
+	log.SetOutput(b)
+	log.SetFlags(0)
+	defer log.SetOutput(os.Stderr)
+	defer log.SetFlags(log.LstdFlags)
+
+	// Mock-chown the tree.
+	if err := wp.Chown(); err != nil {
+		t.Fatalf("wp.Chown() wrongly failed: %v", err)
+	}
+
+	// Edit the log buffer to remove the invocation dependent output.
+	pb := bytes.TrimSpace(bytes.Replace(b.Bytes(), []byte(dir), []byte("$PATH"), -1))
+
+	cmds := bytes.Split(pb, []byte{'\n'})
+	for i, _ := range cmds {
+		cmds[i] = bytes.TrimSpace(cmds[i])
+	}
+
+	expected := []string{
+		"[dryrun] os.Chown($PATH/c, 42, 7)",
+		"[dryrun] os.Chown($PATH/c/d, 42, 7)",
+		"[dryrun] os.Chown($PATH/a, 42, 7)",
+		"[dryrun] os.Chown($PATH/a/b, 42, 7)",
+		"[dryrun] os.Chown($PATH/a/b/c, 42, 7)",
+	}
+	if got, expected := len(cmds), len(expected); got != expected {
+		t.Fatalf("bad length. got: %d, expected %d", got, expected)
+	}
+	for i, _ := range expected {
+		if expected, got := expected[i], string(cmds[i]); expected != got {
+			t.Fatalf("wp.Chown output %d: got %v, expected %v", i, got, expected)
+		}
+	}
+}
diff --git a/services/device/internal/sysinit/init_darwin.go b/services/device/internal/sysinit/init_darwin.go
new file mode 100644
index 0000000..b1ae822
--- /dev/null
+++ b/services/device/internal/sysinit/init_darwin.go
@@ -0,0 +1,13 @@
+// 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 sysinit
+
+func InitSystem() string {
+	panic("Darwin not supported yet")
+}
+
+func New(system string, sd *ServiceDescription) InstallSystemInit {
+	panic("Darwin not supported yet")
+}
diff --git a/services/device/internal/sysinit/init_linux.go b/services/device/internal/sysinit/init_linux.go
new file mode 100644
index 0000000..1d41ba6
--- /dev/null
+++ b/services/device/internal/sysinit/init_linux.go
@@ -0,0 +1,319 @@
+// 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 sysinit
+
+// TODO(cnicolaou): will need to figure out a simple of way of handling the
+// different init systems supported by various versions of linux. One simple
+// option is to just include them in the name when installing - e.g.
+// simplevns-upstart.
+
+import (
+	"fmt"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"syscall"
+)
+
+// action is a var so we can override it for testing.
+var action = func(command, action, service string) error {
+	cmd := exec.Command(command, action, service)
+	if os.Geteuid() == 0 && os.Getuid() > 0 {
+		// Set uid to root (e.g. when running from a suid binary),
+		// otherwise initctl doesn't work.
+		sysProcAttr := new(syscall.SysProcAttr)
+		sysProcAttr.Credential = new(syscall.Credential)
+		sysProcAttr.Credential.Gid = uint32(0)
+		sysProcAttr.Credential.Uid = uint32(0)
+		cmd.SysProcAttr = sysProcAttr
+	}
+	// Clear env.  In particular, initctl doesn't like USER being set to
+	// something other than root.
+	cmd.Env = []string{}
+	output, err := cmd.CombinedOutput()
+	fmt.Fprintf(os.Stderr, "%s output: for %s %s: %s\n", command, action, service, output)
+	return err
+}
+
+var (
+	upstartDir        = "/etc/init"
+	upstartBin        = "/sbin/initctl"
+	systemdDir        = "/lib/systemd/system" // This works for both rpi and edison (/usr/lib does not)
+	systemdTmpFileDir = "/usr/lib/tmpfiles.d"
+	dockerDir         = "/home/veyron/init"
+)
+
+// InitSystem attempts to determine what kind of init system is in use on
+// the platform that it is run on. It recognises upstart and systemd by
+// testing for the presence of the initctl and systemctl commands. upstart
+// is tested for first and hence is preferred in the unlikely case that both
+// are installed. Docker containers do not support upstart and systemd and
+// for them we have our own init system that uses the daemon command to
+// start/stop/respawn jobs.
+func InitSystem() string {
+	// NOTE(spetrovic): This check is kind of a hack. Ideally, we would
+	// detect a docker system by looking at the "container=lxc" environment
+	// variable.  However, we run sysinit during image creation, at which
+	// point we're on a native system and this variable isn't set.
+	if fi, err := os.Stat("/home/veyron/init"); err == nil && fi.Mode().IsDir() {
+		return "docker"
+	}
+	if fi, err := os.Stat(upstartBin); err == nil {
+		if (fi.Mode() & 0100) != 0 {
+			return "upstart"
+		}
+	}
+
+	if findSystemdSystemCtl() != "" {
+		return "systemd"
+	}
+
+	return ""
+}
+
+// New returns the appropriate implementation of InstallSystemInit for the
+// underlying system.
+func New(system string, sd *ServiceDescription) InstallSystemInit {
+	switch system {
+	case "docker":
+		return (*DockerService)(sd)
+	case "upstart":
+		return (*UpstartService)(sd)
+	case "systemd":
+		return (*SystemdService)(sd)
+	default:
+		return nil
+	}
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Upstart support
+
+// See http://upstart.ubuntu.com/cookbook/ for info on upstart
+
+// UpstartService is the implementation of InstallSystemInit interfacing with
+// the Upstart system.
+type UpstartService ServiceDescription
+
+var upstartTemplate = `# This file was auto-generated by the Vanadium SysInit tool.
+# Date: {{.Date}}
+#
+# {{.Service}} - {{.Description}}
+#
+# Upstart config for Ubuntu-GCE
+
+description	"{{.Description}}"
+
+start on runlevel [2345]
+stop on runlevel [!2345]
+{{if .Environment}}
+# Environment variables
+{{range $var, $value := .Environment}}
+env {{$var}}={{$value}}{{end}}
+{{end}}
+respawn
+respawn limit 10 5
+umask 022
+
+pre-start script
+    test -x {{.Binary}} || { stop; exit 0; }
+    mkdir -p -m0755 /var/log/veyron
+    chown -R {{.User}} /var/log/veyron
+end script
+
+script
+  set -e
+  echo '{{.Service}} starting'
+  exec sudo -u {{.User}} {{range $cmd := .Command}} {{$cmd}}{{end}}
+end script
+`
+
+// Implements the InstallSystemInit method.
+func (u *UpstartService) Install() error {
+	file := fmt.Sprintf("%s/%s.conf", upstartDir, u.Service)
+	return (*ServiceDescription)(u).writeTemplate(upstartTemplate, file)
+}
+
+// Print implements the InstallSystemInit method.
+func (u *UpstartService) Print() error {
+	return (*ServiceDescription)(u).writeTemplate(upstartTemplate, "")
+}
+
+// Implements the InstallSystemInit method.
+func (u *UpstartService) Uninstall() error {
+	// For now, ignore any errors returned by Stop, since Stop complains
+	// when there is no instance to stop.
+	// TODO(caprita): Only call Stop if there are running instances.
+	u.Stop()
+	file := fmt.Sprintf("%s/%s.conf", upstartDir, u.Service)
+	return os.Remove(file)
+}
+
+// Start implements the InstallSystemInit method.
+func (u *UpstartService) Start() error {
+	return action(upstartBin, "start", u.Service)
+}
+
+// Stop implements the InstallSystemInit method.
+func (u *UpstartService) Stop() error {
+	return action(upstartBin, "stop", u.Service)
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Systemd support
+
+// SystemdService is the implementation of InstallSystemInit interfacing with
+// the Systemd system.
+type SystemdService ServiceDescription
+
+const systemdTemplate = `# This file was auto-generated by the Vanadium SysInit tool.
+# Date: {{.Date}}
+#
+# {{.Service}} - {{.Description}}
+#
+[Unit]
+Description={{.Description}}
+After=openntpd.service
+
+[Service]
+User={{.User}}{{if .Environment}}{{println}}Environment={{range $var, $value := .Environment}}"{{$var}}={{$value}}" {{end}}{{end}}
+ExecStart={{range $cmd := .Command}}{{$cmd}} {{end}}
+ExecReload=/bin/kill -HUP $MAINPID
+KillMode=process
+Restart=always
+RestartSecs=10
+StandardOutput=syslog
+
+
+[Install]
+WantedBy=multi-user.target
+`
+
+// Install implements the InstallSystemInit method.
+func (s *SystemdService) Install() error {
+	file := fmt.Sprintf("%s/%s.service", systemdDir, s.Service)
+	if err := (*ServiceDescription)(s).writeTemplate(systemdTemplate, file); err != nil {
+		return fmt.Errorf("failed to write template (uid= %d, euid= %d): %v", os.Getuid(), os.Geteuid(), err)
+	}
+	file = fmt.Sprintf("%s/veyron.conf", systemdTmpFileDir)
+	f, err := os.Create(file)
+	if err != nil {
+		return err
+	}
+	f.WriteString("d /var/log/veyron 0755 veyron veyron\n")
+	f.Close()
+	err = action("systemd-tmpfiles", "--create", file)
+	if err != nil {
+		return err
+	}
+
+	// First call disable to get rid of any symlink lingering around from a previous install
+	// We don't care about the return status on the disable action.
+	action(findSystemdSystemCtl(), "disable", s.Service)
+	return action(findSystemdSystemCtl(), "enable", s.Service)
+}
+
+// Print implements the InstallSystemInit method.
+func (s *SystemdService) Print() error {
+	return (*ServiceDescription)(s).writeTemplate(systemdTemplate, "")
+}
+
+// Uninstall implements the InstallSystemInit method.
+func (s *SystemdService) Uninstall() error {
+	if err := s.Stop(); err != nil {
+		return err
+	}
+	if err := action(findSystemdSystemCtl(), "disable", s.Service); err != nil {
+		return err
+	}
+	file := fmt.Sprintf("%s/%s.service", systemdDir, s.Service)
+	return os.Remove(file)
+}
+
+// Start implements the InstallSystemInit method.
+func (s *SystemdService) Start() error {
+	return action(findSystemdSystemCtl(), "start", s.Service)
+}
+
+// Stop implements the InstallSystemInit method.
+func (s *SystemdService) Stop() error {
+	return action(findSystemdSystemCtl(), "stop", s.Service)
+}
+
+// This is a variable so it can be overridden for testing.
+var findSystemdSystemCtl = func() string {
+	// Systems using systemd may have systemctl in one of several possible places. This finds it.
+	paths := []string{"/sbin", "/bin", "/usr/bin", "/usr/sbin"}
+
+	for _, path := range paths {
+		testpath := filepath.Join(path, "systemctl")
+		if fi, err := os.Stat(testpath); err == nil && (fi.Mode()&0100) != 0 {
+			return testpath
+		}
+	}
+
+	return ""
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Docker support
+
+// DockerService is the implementation of InstallSystemInit interfacing with
+// Docker.
+type DockerService ServiceDescription
+
+const dockerTemplate = `#!/bin/bash
+# This file was auto-generated by the Vanadium SysInit tool.
+# Date: {{.Date}}
+#
+# {{.Service}} - {{.Description}}
+#
+set -e
+{{if .Environment}}
+# Environment variables
+{{range $var, $value := .Environment}}export {{$var}}={{$value}}{{end}}
+{{end}}
+echo '{{.Service}} setup.'
+test -x {{.Binary}} || { stop; exit 0; }
+mkdir -p -m0755 /var/log/veyron
+chown -R {{.User}} /var/log/veyron
+
+echo '{{.Service}} starting'
+exec daemon -n {{.Service}} -r -A 2 -L 10 -M 5 -X '{{range $cmd := .Command}} {{$cmd}}{{end}}' &
+`
+
+// Install implements the InstallSystemInit method.
+func (s *DockerService) Install() error {
+	file := fmt.Sprintf("%s/%s.sh", dockerDir, s.Service)
+	if err := (*ServiceDescription)(s).writeTemplate(dockerTemplate, file); err != nil {
+		return err
+	}
+	os.Chmod(file, 0755)
+	return nil
+}
+
+// Print implements the InstallSystemInit method.
+func (s *DockerService) Print() error {
+	return (*ServiceDescription)(s).writeTemplate(dockerTemplate, "")
+}
+
+// Uninstall implements the InstallSystemInit method.
+func (s *DockerService) Uninstall() error {
+	if err := s.Stop(); err != nil {
+		return err
+	}
+	file := fmt.Sprintf("%s/%s.sh", dockerDir, s.Service)
+	return os.Remove(file)
+}
+
+// Start implements the InstallSystemInit method.
+func (s *DockerService) Start() error {
+	return action(fmt.Sprintf("%s/%s.sh", dockerDir, s.Service), "", s.Service)
+}
+
+// Stop implements the InstallSystemInit method.
+func (s *DockerService) Stop() error {
+	return action("daemon", fmt.Sprintf("-n %s --stop", s.Service), s.Service)
+}
diff --git a/services/device/internal/sysinit/linux_test.go b/services/device/internal/sysinit/linux_test.go
new file mode 100644
index 0000000..06be016
--- /dev/null
+++ b/services/device/internal/sysinit/linux_test.go
@@ -0,0 +1,129 @@
+// 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 linux
+
+package sysinit
+
+import (
+	"bufio"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strings"
+	"testing"
+	"time"
+)
+
+func TestUpstart(t *testing.T) {
+	oldTemplate := upstartTemplate
+	upstartTemplate = `{{.Date}}
+{{.Service}}
+{{.Description}}
+{{.Binary}}
+{{.Command}}
+`
+	oldUpstartDir := upstartDir
+	upstartDir, _ = ioutil.TempDir(".", "etc-init")
+
+	defer func() {
+		upstartTemplate = oldTemplate
+		upstartDir = oldUpstartDir
+	}()
+
+	defer os.RemoveAll(upstartDir)
+	u := &UpstartService{
+		Service:     "tester",
+		Description: "my test",
+		Binary:      "/bin/echo",
+		Command:     []string{"/bin/echo -n foo"},
+	}
+	if err := u.Install(); err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	rc, _ := os.Open(upstartDir + "/tester.conf")
+	lines := bufio.NewScanner(rc)
+	lines.Scan()
+	timestr := lines.Text()
+	_, err := time.Parse(dateFormat, timestr)
+	if err != nil {
+		t.Fatalf("unexpected error parsing time: %v, err: %v", t, err)
+	}
+	lines.Scan()
+	if lines.Text() != "tester" {
+		t.Fatalf("unexpected output: %s", lines.Text())
+	}
+	lines.Scan()
+	lines.Scan()
+	if lines.Text() != "/bin/echo" {
+		t.Fatalf("unexpected output: %s", lines.Text())
+	}
+	lines.Scan()
+	if lines.Scan() {
+		t.Fatalf("failed to find end of file")
+	}
+}
+
+func TestSystemd(t *testing.T) {
+	s := &SystemdService{
+		Service:     "tester",
+		Description: "my test",
+		Binary:      "/bin/echo",
+		Command:     []string{"/bin/echo", "-n", "foo"},
+	}
+
+	oldSystemdDir := systemdDir
+	oldSystemdTmpFileDir := systemdTmpFileDir
+	oldAction := action
+	oldFindSystemdSystemCtl := findSystemdSystemCtl
+
+	systemdDir, _ = ioutil.TempDir(".", "usr-lib-systemd-system")
+	defer os.RemoveAll(systemdDir)
+	systemdTmpFileDir, _ = ioutil.TempDir(".", "usr-lib-tmpfiles.d")
+	defer os.RemoveAll(systemdTmpFileDir)
+
+	var cmd, act, srv string
+	action = func(command, action, service string) error {
+		cmd, act, srv = command, action, service
+		return nil
+	}
+
+	findSystemdSystemCtl = func() string {
+		return "systemctl"
+	}
+
+	defer func() {
+		systemdDir = oldSystemdDir
+		systemdTmpFileDir = oldSystemdTmpFileDir
+		action = oldAction
+		findSystemdSystemCtl = oldFindSystemdSystemCtl
+	}()
+
+	if err := s.Install(); err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+
+	if want, got := "systemctl", cmd; want != got {
+		t.Errorf("action command: want %q, got %q", want, got)
+	}
+	if want, got := "enable", act; want != got {
+		t.Errorf("action action: want %q, got %q", want, got)
+	}
+	if want, got := "tester", srv; want != got {
+		t.Errorf("action service: want %q, got %q", want, got)
+	}
+
+	c, err := ioutil.ReadFile(filepath.Join(systemdDir, "tester.service"))
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	contents := string(c)
+	if !strings.Contains(contents, "Description=my test") {
+		t.Errorf("bad Description in generated service spec: %v", contents)
+	}
+	if !strings.Contains(contents, "ExecStart=/bin/echo -n foo") {
+		t.Errorf("bad ExecStart in generated service spec: %v", contents)
+	}
+}
diff --git a/services/device/internal/sysinit/service_description.go b/services/device/internal/sysinit/service_description.go
new file mode 100644
index 0000000..42a7f27
--- /dev/null
+++ b/services/device/internal/sysinit/service_description.go
@@ -0,0 +1,85 @@
+// 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 sysinit
+
+import (
+	"encoding/json"
+	"io/ioutil"
+	"os"
+	"text/template"
+	"time"
+
+	"v.io/v23/verror"
+)
+
+const pkgPath = "v.io/x/ref/services/device/internal/sysinit"
+
+var (
+	errMarshalFailed   = verror.Register(pkgPath+".errMarshalFailed", verror.NoRetry, "{1:}{2:} Marshal({3}) failed{:_}")
+	errWriteFileFailed = verror.Register(pkgPath+".errWriteFileFailed", verror.NoRetry, "{1:}{2:} WriteFile({3}) failed{:_}")
+	errReadFileFailed  = verror.Register(pkgPath+".errReadFileFailed", verror.NoRetry, "{1:}{2:} ReadFile({3}) failed{:_}")
+	errUnmarshalFailed = verror.Register(pkgPath+".errUnmarshalFailed", verror.NoRetry, "{1:}{2:} Unmarshal({3}) failed{:_}")
+)
+
+const dateFormat = "Jan 2 2006 at 15:04:05 (MST)"
+
+// ServiceDescription is a generic service description that represents the
+// common configuration details for specific systems.
+type ServiceDescription struct {
+	Service     string            // The name of the Service
+	Description string            // A description of the Service
+	Environment map[string]string // Environment variables needed by the service
+	Binary      string            // The binary to be run
+	Command     []string          // The script/binary and command line options to use to start/stop the binary
+	User        string            // The username this service is to run as
+}
+
+// TODO(caprita): Unit test.
+
+// SaveTo serializes the service description object to a file.
+func (sd *ServiceDescription) SaveTo(fName string) error {
+	jsonSD, err := json.Marshal(sd)
+	if err != nil {
+		return verror.New(errMarshalFailed, nil, sd, err)
+	}
+	if err := ioutil.WriteFile(fName, jsonSD, 0600); err != nil {
+		return verror.New(errWriteFileFailed, nil, fName, err)
+	}
+	return nil
+}
+
+// LoadFrom de-serializes the service description object from a file created by
+// SaveTo.
+func (sd *ServiceDescription) LoadFrom(fName string) error {
+	if sdBytes, err := ioutil.ReadFile(fName); err != nil {
+		return verror.New(errReadFileFailed, nil, fName, err)
+	} else if err := json.Unmarshal(sdBytes, sd); err != nil {
+		return verror.New(errUnmarshalFailed, nil, sdBytes, err)
+	}
+	return nil
+}
+
+func (sd *ServiceDescription) writeTemplate(templateContents, file string) error {
+	conf, err := template.New(sd.Service + ".template").Parse(templateContents)
+	if err != nil {
+		return err
+	}
+	w := os.Stdout
+	if len(file) > 0 {
+		w, err = os.Create(file)
+		if err != nil {
+			return err
+		}
+	}
+	type tmp struct {
+		*ServiceDescription
+		Date string
+	}
+	data := &tmp{
+		ServiceDescription: sd,
+		Date:               time.Now().Format(dateFormat),
+	}
+	return conf.Execute(w, &data)
+}
diff --git a/services/device/internal/sysinit/sysinit.go b/services/device/internal/sysinit/sysinit.go
new file mode 100644
index 0000000..67c4335
--- /dev/null
+++ b/services/device/internal/sysinit/sysinit.go
@@ -0,0 +1,17 @@
+// 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 sysinit provides config generation for a variety of platforms and
+// "init" systems such as upstart, systemd etc. It is intended purely for
+// bootstrapping into the Vanadium system proper.
+package sysinit
+
+// InstallSystemInit defines the interface that all configs must implement.
+type InstallSystemInit interface {
+	Print() error
+	Install() error
+	Uninstall() error
+	Start() error
+	Stop() error
+}
diff --git a/services/device/mgmt_v23_test.go b/services/device/mgmt_v23_test.go
new file mode 100644
index 0000000..58ab3ef
--- /dev/null
+++ b/services/device/mgmt_v23_test.go
@@ -0,0 +1,580 @@
+// 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.
+
+// Test the device manager and related services and tools.
+//
+// By default, this script tests the device manager in a fashion amenable
+// to automatic testing: the --single_user is passed to the device
+// manager so that all device manager components run as the same user and
+// no user input (such as an agent pass phrase) is needed.
+//
+// This script can exercise the device manager in two different modes. It
+// can be executed like so:
+//
+// v23 go test -v . --v23.tests
+//
+// This will exercise the device manager's single user mode where all
+// processes run as the same invoking user.
+//
+// Alternatively, the device manager can be executed in multiple account
+// mode by providing the --deviceuser <deviceuser> and --appuser
+// <appuser> flags. In this case, the device manager will run as user
+// <devicemgr> and the test will run applications as user <appuser>. If
+// executed in this fashion, root permissions will be required to install
+// and it may require configuring an agent passphrase. For example:
+//
+//   v23 go test -v . --v23.tests --deviceuser devicemanager --appuser  vana
+//
+// NB: the accounts provided as arguments to this test must already exist.
+// Also, the --v23.tests.shell-on-fail flag is useful to enable debugging
+// output. Note that this flag does not work for some shells. Set
+// $SHELL in that case.
+
+package device_test
+
+//go:generate v23 test generate .
+
+import (
+	"errors"
+	"flag"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"math/rand"
+	"os"
+	"os/user"
+	"path/filepath"
+	"regexp"
+	"strings"
+	"sync"
+	"time"
+
+	"v.io/x/ref"
+	_ "v.io/x/ref/runtime/factories/generic"
+	"v.io/x/ref/test/v23tests"
+)
+
+var (
+	appUserFlag    string
+	deviceUserFlag string
+	hostname       string
+	errTimeout     = errors.New("timeout")
+)
+
+func init() {
+	flag.StringVar(&appUserFlag, "appuser", "", "launch apps as the specified user")
+	flag.StringVar(&deviceUserFlag, "deviceuser", "", "run the device manager as the specified user")
+	name, err := os.Hostname()
+	if err != nil {
+		panic(fmt.Sprintf("Hostname() failed: %v", err))
+	}
+	hostname = name
+}
+
+func V23TestDeviceManager(i *v23tests.T) {
+	u, err := user.Current()
+	if err != nil {
+		i.Fatalf("couldn't get the current user: %v", err)
+	}
+	testCore(i, u.Username, "", false)
+}
+
+func V23TestDeviceManagerMultiUser(i *v23tests.T) {
+	u, err := user.Current()
+	if err != nil {
+		i.Fatalf("couldn't get the current user: %v", err)
+	}
+
+	if u.Username == "veyron" && runTestOnThisPlatform {
+		// We are running on the builder so run the multiuser
+		// test with default user names. These will be created as
+		// required.
+		makeTestAccounts(i)
+		testCore(i, "vana", "devicemanager", true)
+		return
+	}
+
+	if len(deviceUserFlag) > 0 && len(appUserFlag) > 0 {
+		testCore(i, appUserFlag, deviceUserFlag, true)
+	} else {
+		i.Logf("Test skipped because running in multiuser mode requires --appuser and --deviceuser flags")
+	}
+}
+
+func testCore(i *v23tests.T, appUser, deviceUser string, withSuid bool) {
+	defer fmt.Fprintf(os.Stderr, "--------------- SHUTDOWN ---------------\n")
+	tempDir := ""
+
+	if withSuid {
+		// When running --with_suid, TMPDIR must grant the
+		// invoking user rwx permissions and world x permissions for
+		// all parent directories back to /. Otherwise, the
+		// with_suid user will not be able to use absolute paths.
+		// On Darwin, TMPDIR defaults to a directory hieararchy
+		// in /var that is 0700. This is unworkable so force
+		// TMPDIR to /tmp in this case.
+		tempDir = "/tmp"
+	}
+
+	var (
+		workDir       = i.NewTempDir(tempDir)
+		binStagingDir = mkSubdir(i, workDir, "bin")
+		dmInstallDir  = filepath.Join(workDir, "dm")
+
+		// Most vanadium command-line utilities will be run by a
+		// principal that has "root/u/alice" as its blessing.
+		// (Where "root" comes from i.Principal().BlessingStore().Default()).
+		// Create those credentials and options to use to setup the
+		// binaries with them.
+		aliceCreds, _ = i.Shell().NewChildCredentials("u/alice")
+		aliceOpts     = i.Shell().DefaultStartOpts().ExternalProgram().WithCustomCredentials(aliceCreds)
+
+		// Build all the command-line tools and set them up to run as alice.
+		// applicationd/binaryd servers will be run by alice too.
+		// TODO: applicationd/binaryd should run as a separate "service" role, as
+		// alice is just a user.
+		namespaceBin    = i.BuildV23Pkg("v.io/x/ref/cmd/namespace").WithStartOpts(aliceOpts)
+		deviceBin       = i.BuildV23Pkg("v.io/x/ref/services/device/device").WithStartOpts(aliceOpts)
+		binarydBin      = i.BuildV23Pkg("v.io/x/ref/services/binary/binaryd").WithStartOpts(aliceOpts)
+		applicationdBin = i.BuildV23Pkg("v.io/x/ref/services/application/applicationd").WithStartOpts(aliceOpts)
+
+		// The devicex script is not provided with any credentials, it
+		// will generate its own.  This means that on "devicex start"
+		// the device will have no useful credentials and until "device
+		// claim" is invoked (as alice), it will just sit around
+		// waiting to be claimed.
+		//
+		// Other binaries, like applicationd and binaryd will be run by alice.
+		deviceScript = i.BinaryFromPath("./devicex").WithEnv("V23_DEVICE_DIR=" + dmInstallDir)
+
+		mtName = "devices/" + hostname // Name under which the device manager will publish itself.
+	)
+
+	// We also need some tools running with different sets of credentials...
+
+	// Administration tasks will be performed with a blessing that represents a corporate
+	// adminstrator (which is usually a role account)
+	adminCreds, err := i.Shell().NewChildCredentials("r/admin")
+	if err != nil {
+		i.Fatalf("generating admin creds: %v", err)
+	}
+	adminOpts := i.Shell().DefaultStartOpts().ExternalProgram().WithCustomCredentials(adminCreds)
+	adminDeviceBin := deviceBin.WithStartOpts(adminOpts)
+	debugBin := i.BuildV23Pkg("v.io/x/ref/services/debug/debug").WithStartOpts(adminOpts)
+
+	// A special set of credentials will be used to give two blessings to the device manager
+	// when claiming it -- one blessing will be from the corporate administrator role who owns
+	// the machine, and the other will be a manufacturer blessing. (This is a hack until
+	// there's a way to separately supply a manufacturer blessing. Eventually, the claim
+	// would really be done by the administator, and the adminstrator's blessing would get
+	// added to the manufacturer's blessing, which would already be present.)
+	claimCreds, err := i.Shell().AddToChildCredentials(adminCreds, "m/orange/zphone5/ime-i007")
+	if err != nil {
+		i.Fatalf("adding the mfr blessing to admin creds: %v", err)
+	}
+	claimOpts := i.Shell().DefaultStartOpts().ExternalProgram().WithCustomCredentials(claimCreds)
+	claimDeviceBin := deviceBin.WithStartOpts(claimOpts)
+
+	// Another set of credentials be used to represent the application publisher, who
+	// signs and pushes binaries
+	pubCreds, err := i.Shell().NewChildCredentials("a/rovio")
+	if err != nil {
+		i.Fatalf("generating publisher creds: %v", err)
+	}
+	pubOpts := i.Shell().DefaultStartOpts().ExternalProgram().WithCustomCredentials(pubCreds)
+	pubDeviceBin := deviceBin.WithStartOpts(pubOpts)
+	applicationBin := i.BuildV23Pkg("v.io/x/ref/services/application/application").WithStartOpts(pubOpts)
+	binaryBin := i.BuildV23Pkg("v.io/x/ref/services/binary/binary").WithStartOpts(pubOpts)
+
+	if withSuid {
+		// In multiuser mode, deviceUserFlag needs execute access to
+		// tempDir.
+		if err := os.Chmod(workDir, 0711); err != nil {
+			i.Fatalf("os.Chmod() failed: %v", err)
+		}
+	}
+
+	v23tests.RunRootMT(i, "--v23.tcp.address=127.0.0.1:0")
+	buildAndCopyBinaries(
+		i,
+		binStagingDir,
+		"v.io/x/ref/services/device/deviced",
+		"v.io/x/ref/services/agent/agentd",
+		"v.io/x/ref/services/device/suidhelper",
+		"v.io/x/ref/services/device/inithelper")
+
+	appDName := "applications"
+	devicedAppName := filepath.Join(appDName, "deviced", "test")
+
+	deviceScriptArguments := []string{
+		"install",
+		binStagingDir,
+	}
+
+	if withSuid {
+		deviceScriptArguments = append(deviceScriptArguments, "--devuser="+deviceUser)
+	} else {
+		deviceScriptArguments = append(deviceScriptArguments, "--single_user")
+	}
+
+	deviceScriptArguments = append(deviceScriptArguments, []string{
+		"--origin=" + devicedAppName,
+		"--",
+		"--v23.tcp.address=127.0.0.1:0",
+		"--neighborhood-name=" + fmt.Sprintf("%s-%d-%d", hostname, os.Getpid(), rand.Int()),
+	}...)
+
+	deviceScript.Start(deviceScriptArguments...).WaitOrDie(os.Stdout, os.Stderr)
+	deviceScript.Start("start").WaitOrDie(os.Stdout, os.Stderr)
+	dmLog := filepath.Join(dmInstallDir, "dmroot/device-manager/logs/deviced.INFO")
+	stopDevMgr := func() {
+		deviceScript.Run("stop")
+		if dmLogF, err := os.Open(dmLog); err != nil {
+			i.Errorf("Failed to read dm log: %v", err)
+		} else {
+			fmt.Fprintf(os.Stderr, "--------------- START DM LOG ---------------\n")
+			defer dmLogF.Close()
+			if _, err := io.Copy(os.Stderr, dmLogF); err != nil {
+				i.Errorf("Error dumping dm log: %v", err)
+			}
+			fmt.Fprintf(os.Stderr, "--------------- END DM LOG ---------------\n")
+		}
+	}
+	var stopDevMgrOnce sync.Once
+	defer stopDevMgrOnce.Do(stopDevMgr)
+	// Grab the endpoint for the claimable service from the device manager's
+	// log.
+	var claimableEP string
+	expiry := time.Now().Add(30 * time.Second)
+	for {
+		if time.Now().After(expiry) {
+			i.Fatalf("Timed out looking for claimable endpoint in %v", dmLog)
+		}
+		startLog, err := ioutil.ReadFile(dmLog)
+		if err != nil {
+			i.Logf("Couldn't read log %v: %v", dmLog, err)
+			time.Sleep(time.Second)
+			continue
+		}
+		re := regexp.MustCompile(`Unclaimed device manager \((.*)\)`)
+		matches := re.FindSubmatch(startLog)
+		if len(matches) == 0 {
+			i.Logf("Couldn't find match in %v [%v]", dmLog, startLog)
+			time.Sleep(time.Second)
+			continue
+		}
+		if len(matches) != 2 {
+			i.Fatalf("Wrong match in %v (%d) %v", dmLog, len(matches), string(matches[0]))
+		}
+		claimableEP = string(matches[1])
+		break
+	}
+	// Claim the device as "root/u/alice/myworkstation".
+	claimDeviceBin.Start("claim", claimableEP, "myworkstation")
+
+	resolve := func(name string) string {
+		resolver := func() (interface{}, error) {
+			// Use Start, rather than Run, since it's ok for 'namespace resolve'
+			// to fail with 'name doesn't exist'
+			inv := namespaceBin.Start("resolve", name)
+			// Cleanup after ourselves to avoid leaving a ton of invocations
+			// lying around which obscure logging output.
+			defer inv.Wait(nil, os.Stderr)
+			if r := strings.TrimRight(inv.Output(), "\n"); len(r) > 0 {
+				return r, nil
+			}
+			return nil, nil
+		}
+		return i.WaitFor(resolver, 100*time.Millisecond, time.Minute).(string)
+	}
+
+	// Wait for the device manager to publish its mount table entry.
+	mtEP := resolve(mtName)
+	adminDeviceBin.Run("acl", "set", mtName+"/devmgr/device", "root/u/alice", "Read,Resolve,Write")
+
+	if withSuid {
+		adminDeviceBin.Start("associate", "add", mtName+"/devmgr/device", appUser, "root/u/alice")
+
+		aai := adminDeviceBin.Start("associate", "list", mtName+"/devmgr/device")
+		if got, expected := strings.Trim(aai.Output(), "\n "), "root/u/alice "+appUser; got != expected {
+			i.Fatalf("association test, got %v, expected %v", got, expected)
+		}
+	}
+
+	// Verify the device's default blessing is as expected.
+	mfrBlessing := "root/m/orange/zphone5/ime-i007/myworkstation"
+	ownerBlessing := "root/r/admin/myworkstation"
+	inv := debugBin.Start("stats", "read", mtName+"/devmgr/__debug/stats/security/principal/*/blessingstore")
+	inv.ExpectSetEventuallyRE(".*Default Blessings[ ]+" + mfrBlessing + "," + ownerBlessing)
+
+	// Get the device's profile, which should be set to non-empty string
+	inv = adminDeviceBin.Start("describe", mtName+"/devmgr/device")
+
+	parts := inv.ExpectRE(`{Profiles:map\[(.*):{}\]}`, 1)
+	expectOneMatch := func(parts [][]string) string {
+		if len(parts) != 1 || len(parts[0]) != 2 {
+			loc := v23tests.Caller(1)
+			i.Fatalf("%s: failed to match profile: %#v", loc, parts)
+		}
+		return parts[0][1]
+	}
+	deviceProfile := expectOneMatch(parts)
+	if len(deviceProfile) == 0 {
+		i.Fatalf("failed to get profile")
+	}
+
+	// Start a binaryd server that will serve the binary for the test
+	// application to be installed on the device.
+	binarydName := "binaries"
+	binarydBin.Start(
+		"--name="+binarydName,
+		"--root-dir="+filepath.Join(workDir, "binstore"),
+		"--v23.tcp.address=127.0.0.1:0",
+		"--http=127.0.0.1:0")
+	// Allow publishers to update binaries
+	deviceBin.Run("acl", "set", binarydName, "root/a", "Write")
+
+	// We are also going to use the binaryd binary as our test app binary. Once our test app
+	// binary is published to the binaryd server started above, this (augmented with a
+	// timestamp) is the name the test app binary will have.
+	sampleAppBinName := binarydName + "/binaryd"
+
+	// Start an applicationd server that will serve the application
+	// envelope for the test application to be installed on the device.
+	applicationdBin.Start(
+		"--name="+appDName,
+		"--store="+mkSubdir(i, workDir, "appstore"),
+		"--v23.tcp.address=127.0.0.1:0",
+	)
+	// Allow publishers to create and update envelopes
+	deviceBin.Run("acl", "set", appDName, "root/a", "Read,Write,Resolve")
+
+	sampleAppName := appDName + "/testapp/0"
+	appPubName := "testbinaryd"
+	appEnvelopeFilename := filepath.Join(workDir, "app.envelope")
+	appEnvelope := fmt.Sprintf("{\"Title\":\"BINARYD\", \"Args\":[\"--name=%s\", \"--root-dir=./binstore\", \"--v23.tcp.address=127.0.0.1:0\", \"--http=127.0.0.1:0\"], \"Binary\":{\"File\":%q}, \"Env\":[]}", appPubName, sampleAppBinName)
+	ioutil.WriteFile(appEnvelopeFilename, []byte(appEnvelope), 0666)
+	defer os.Remove(appEnvelopeFilename)
+
+	output := applicationBin.Run("put", sampleAppName, deviceProfile, appEnvelopeFilename)
+	if got, want := output, "Application envelope added successfully."; got != want {
+		i.Fatalf("got %q, want %q", got, want)
+	}
+
+	// Verify that the envelope we uploaded shows up with glob.
+	inv = applicationBin.Start("match", sampleAppName, deviceProfile)
+	parts = inv.ExpectSetEventuallyRE(`"Title": "(.*)",`, `"File": "(.*)",`)
+	if got, want := len(parts), 2; got != want {
+		i.Fatalf("got %d, want %d", got, want)
+	}
+	for line, want := range []string{"BINARYD", sampleAppBinName} {
+		if got := parts[line][1]; got != want {
+			i.Fatalf("got %q, want %q", got, want)
+		}
+	}
+
+	// Publish the app (This uses the binarydBin binary and the testapp envelope from above)
+	pubDeviceBin.Start("publish", "-from", filepath.Dir(binarydBin.Path()), "-readers", "root/r/admin", filepath.Base(binarydBin.Path())+":testapp").WaitOrDie(os.Stdout, os.Stderr)
+	if got := namespaceBin.Run("glob", sampleAppBinName); len(got) == 0 {
+		i.Fatalf("glob failed for %q", sampleAppBinName)
+	}
+
+	// Install the app on the device.
+	inv = deviceBin.Start("install", mtName+"/devmgr/apps", sampleAppName)
+	installationName := inv.ReadLine()
+	if installationName == "" {
+		i.Fatalf("got empty installation name from install")
+	}
+
+	// Verify that the installation shows up when globbing the device manager.
+	output = namespaceBin.Run("glob", mtName+"/devmgr/apps/BINARYD/*")
+	if got, want := output, installationName; got != want {
+		i.Fatalf("got %q, want %q", got, want)
+	}
+
+	// Start an instance of the app, granting it blessing extension myapp.
+	inv = deviceBin.Start("instantiate", installationName, "myapp")
+	instanceName := inv.ReadLine()
+	if instanceName == "" {
+		i.Fatalf("got empty instance name from new")
+	}
+	deviceBin.Start("run", instanceName)
+
+	resolve(mtName + "/" + appPubName)
+
+	// Verify that the instance shows up when globbing the device manager.
+	output = namespaceBin.Run("glob", mtName+"/devmgr/apps/BINARYD/*/*")
+	if got, want := output, instanceName; got != want {
+		i.Fatalf("got %q, want %q", got, want)
+	}
+
+	inv = debugBin.Start("stats", "read", instanceName+"/stats/system/pid")
+	pid := inv.ExpectRE("[0-9]+$", 1)[0][0]
+	uname, err := getUserForPid(i, pid)
+	if err != nil {
+		i.Errorf("getUserForPid could not determine the user running pid %v", pid)
+	} else if uname != appUser {
+		i.Errorf("app expected to be running as %v but is running as %v", appUser, uname)
+	}
+
+	// Verify the app's blessings. We check the default blessing, as well as the
+	// "..." blessing, which should be the default blessing plus a publisher blessing.
+	userBlessing := "root/u/alice/myapp"
+	pubBlessing := "root/a/rovio/apps/published/binaryd"
+	appBlessing := mfrBlessing + "/a/" + pubBlessing + "," + ownerBlessing + "/a/" + pubBlessing
+	inv = debugBin.Start("stats", "read", instanceName+"/stats/security/principal/*/blessingstore")
+	inv.ExpectSetEventuallyRE(".*Default Blessings[ ]+"+userBlessing+"$", "[.][.][.][ ]+"+userBlessing+","+appBlessing)
+
+	// Kill and delete the instance.
+	deviceBin.Run("kill", instanceName)
+	deviceBin.Run("delete", instanceName)
+
+	// Verify that logs, but not stats, show up when globbing the
+	// not-running instance.
+	if output = namespaceBin.Run("glob", instanceName+"/stats/..."); len(output) > 0 {
+		i.Fatalf("no output expected for glob %s/stats/..., got %q", output, instanceName)
+	}
+	if output = namespaceBin.Run("glob", instanceName+"/logs/..."); len(output) == 0 {
+		i.Fatalf("output expected for glob %s/logs/..., but got none", instanceName)
+	}
+
+	// TODO: The deviced binary should probably be published by someone other than rovio :-)
+	// Maybe publishing the deviced binary should eventually use "device publish" too?
+	// For now, it uses the "application" and "binary" tools directly to ensure that those work
+
+	// Upload a deviced binary
+	devicedAppBinName := binarydName + "/deviced"
+	binaryBin.Run("upload", devicedAppBinName, i.BuildGoPkg("v.io/x/ref/services/device/deviced").Path())
+	// Allow root/r/admin and its devices to read the binary
+	deviceBin.Run("acl", "set", devicedAppBinName, "root/r/admin", "Read")
+
+	// Upload a device manager envelope.
+	devicedEnvelopeFilename := filepath.Join(workDir, "deviced.envelope")
+	devicedEnvelope := fmt.Sprintf("{\"Title\":\"device manager\", \"Binary\":{\"File\":%q}}", devicedAppBinName)
+	ioutil.WriteFile(devicedEnvelopeFilename, []byte(devicedEnvelope), 0666)
+	defer os.Remove(devicedEnvelopeFilename)
+	applicationBin.Run("put", devicedAppName, deviceProfile, devicedEnvelopeFilename)
+	// Allow root/r/admin and its devices to read the envelope
+	deviceBin.Run("acl", "set", devicedAppName, "root/r/admin", "Read")
+
+	// Update the device manager.
+	adminDeviceBin.Run("update", mtName+"/devmgr/device")
+	resolveChange := func(name, old string) string {
+		resolver := func() (interface{}, error) {
+			inv := namespaceBin.Start("resolve", name)
+			defer inv.Wait(nil, os.Stderr)
+			if r := strings.TrimRight(inv.Output(), "\n"); len(r) > 0 && r != old {
+				return r, nil
+			}
+			return nil, nil
+		}
+		return i.WaitFor(resolver, 100*time.Millisecond, time.Minute).(string)
+	}
+	mtEP = resolveChange(mtName, mtEP)
+
+	// Verify that device manager's mounttable is still published under the
+	// expected name (hostname).
+	if namespaceBin.Run("glob", mtName) == "" {
+		i.Fatalf("failed to glob %s", mtName)
+	}
+
+	// Revert the device manager
+	// The argument to "device revert" is a glob pattern. So we need to
+	// wait for devmgr to be mounted before running the command.
+	resolve(mtEP + "/devmgr")
+	adminDeviceBin.Run("revert", mtName+"/devmgr/device")
+	mtEP = resolveChange(mtName, mtEP)
+
+	// Verify that device manager's mounttable is still published under the
+	// expected name (hostname).
+	if namespaceBin.Run("glob", mtName) == "" {
+		i.Fatalf("failed to glob %s", mtName)
+	}
+
+	// Verify that the local mounttable exists, and that the device manager,
+	// the global namespace, and the neighborhood are mounted on it.
+	resolve(mtEP + "/devmgr")
+	resolve(mtEP + "/nh")
+	resolve(mtEP + "/global")
+
+	namespaceRoot, _ := i.GetVar(ref.EnvNamespacePrefix)
+	if got, want := namespaceBin.Run("resolve", mtEP+"/global"), namespaceRoot; got != want {
+		i.Fatalf("got %q, want %q", got, want)
+	}
+
+	// Kill the device manager (which causes it to be restarted), wait for
+	// the endpoint to change.
+	deviceBin.Run("kill", mtName+"/devmgr/device")
+	mtEP = resolveChange(mtName, mtEP)
+
+	// Shut down the device manager.
+	stopDevMgrOnce.Do(stopDevMgr)
+
+	// Wait for the mounttable entry to go away.
+	resolveGone := func(name string) string {
+		resolver := func() (interface{}, error) {
+			inv := namespaceBin.Start("resolve", name)
+			defer inv.Wait(nil, os.Stderr)
+			if r := strings.TrimRight(inv.Output(), "\n"); len(r) == 0 {
+				return r, nil
+			}
+			return nil, nil
+		}
+		return i.WaitFor(resolver, 100*time.Millisecond, time.Minute).(string)
+	}
+	resolveGone(mtName)
+
+	var fi []os.FileInfo
+
+	// This doesn't work in multiuser mode because dmInstallDir is
+	// owned by the device manager user and unreadable by the user
+	// running this test.
+	if !withSuid {
+		fi, err = ioutil.ReadDir(dmInstallDir)
+		if err != nil {
+			i.Fatalf("failed to readdir for %q: %v", dmInstallDir, err)
+		}
+	}
+
+	deviceScript.Run("uninstall")
+
+	fi, err = ioutil.ReadDir(dmInstallDir)
+	if err == nil || len(fi) > 0 {
+		i.Fatalf("managed to read %d entries from %q", len(fi), dmInstallDir)
+	}
+	if err != nil && !strings.Contains(err.Error(), "no such file or directory") {
+		i.Fatalf("wrong error: %v", err)
+	}
+}
+
+func buildAndCopyBinaries(i *v23tests.T, destinationDir string, packages ...string) {
+	var args []string
+	for _, pkg := range packages {
+		args = append(args, i.BuildGoPkg(pkg).Path())
+	}
+	args = append(args, destinationDir)
+	i.BinaryFromPath("/bin/cp").Start(args...).WaitOrDie(os.Stdout, os.Stderr)
+}
+
+func mkSubdir(i *v23tests.T, parent, child string) string {
+	dir := filepath.Join(parent, child)
+	if err := os.Mkdir(dir, 0755); err != nil {
+		i.Fatalf("failed to create %q: %v", dir, err)
+	}
+	return dir
+}
+
+var re = regexp.MustCompile("[ \t]+")
+
+// getUserForPid determines the username running process pid.
+func getUserForPid(i *v23tests.T, pid string) (string, error) {
+	pidString := i.BinaryFromPath("/bin/ps").Start(psFlags).Output()
+	for _, line := range strings.Split(pidString, "\n") {
+		fields := re.Split(line, -1)
+		if len(fields) > 1 && pid == fields[1] {
+			return fields[0], nil
+		}
+	}
+	return "", fmt.Errorf("Couldn't determine the user for pid %s", pid)
+}
diff --git a/services/device/suidhelper/main.go b/services/device/suidhelper/main.go
new file mode 100644
index 0000000..bde6625
--- /dev/null
+++ b/services/device/suidhelper/main.go
@@ -0,0 +1,29 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Command suidhelper runs the provided command as the specified user identity.
+// It should be installed setuid root.
+package main
+
+// suidhelper deliberately attempts to be as simple as possible to
+// simplify reviewing it for security concerns.
+
+import (
+	"flag"
+	"fmt"
+	"os"
+
+	"v.io/x/ref/services/device/internal/suid"
+)
+
+func main() {
+	flag.Parse()
+	fmt.Fprintln(os.Stderr, os.Args)
+	if err := suid.Run(os.Environ()); err != nil {
+		fmt.Fprintln(os.Stderr, "Failed with:", err)
+		// TODO(rjkroege): We should really only print the usage message
+		// if the error is related to interpreting flags.
+		flag.Usage()
+	}
+}
diff --git a/services/device/util_darwin_test.go b/services/device/util_darwin_test.go
new file mode 100644
index 0000000..1fc1891
--- /dev/null
+++ b/services/device/util_darwin_test.go
@@ -0,0 +1,88 @@
+// 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 device_test
+
+import (
+	"fmt"
+	"os/user"
+	"strconv"
+	"strings"
+
+	"v.io/x/ref/test/v23tests"
+)
+
+const runTestOnThisPlatform = true
+const psFlags = "-ej"
+
+type uidMap map[int]struct{}
+
+func (uids uidMap) findAvailable() (int, error) {
+	// Accounts starting at 501 are available. Don't use the largest
+	// UID because on a corporate imaged Mac, this will overlap with
+	// another employee's UID. Instead, use the first available UID >= 501.
+	for newuid := 501; newuid < 1e6; newuid++ {
+		if _, ok := uids[newuid]; !ok {
+			uids[newuid] = struct{}{}
+			return newuid, nil
+		}
+	}
+	return 0, fmt.Errorf("Couldn't find an available UID")
+}
+
+func newUidMap(i *v23tests.T) uidMap {
+	dsclCmd := i.BinaryFromPath("dscl")
+
+	// `dscl . -list /Users UniqueID` into a datastructure.
+	userstring := dsclCmd.Run(".", "-list", "/Users", "UniqueID")
+	users := strings.Split(userstring, "\n")
+
+	uids := make(map[int]struct{}, len(users))
+	for _, line := range users {
+		fields := re.Split(line, -1)
+		if len(fields) > 1 {
+			if uid, err := strconv.Atoi(fields[1]); err == nil {
+				uids[uid] = struct{}{}
+			}
+		}
+	}
+	return uids
+}
+
+func makeAccount(i *v23tests.T, uid int, uname, fullname string) {
+	sudoCmd := i.BinaryFromPath("/usr/bin/sudo")
+	dsclCmd := sudoCmd.WithPrefixArgs("dscl", ".", "-create", "/Users/"+uname)
+
+	dsclCmd.Run()
+	dsclCmd.Run("UserShell", "/bin/bash")
+	dsclCmd.Run("RealName", fullname)
+	dsclCmd.Run("UniqueID", strconv.FormatInt(int64(uid), 10))
+	dsclCmd.Run("PrimaryGroupID", "20")
+
+}
+
+func makeTestAccounts(i *v23tests.T) {
+	_, needVanaErr := user.Lookup("vana")
+	_, needDevErr := user.Lookup("devicemanager")
+
+	if needVanaErr == nil && needDevErr == nil {
+		return
+	}
+
+	uids := newUidMap(i)
+	if needVanaErr != nil {
+		vanauid, err := uids.findAvailable()
+		if err != nil {
+			i.Fatalf("Can't make test accounts: %v", err)
+		}
+		makeAccount(i, vanauid, "vana", "Vanadium White")
+	}
+	if needDevErr != nil {
+		devmgruid, err := uids.findAvailable()
+		if err != nil {
+			i.Fatalf("Can't make test accounts: %v", err)
+		}
+		makeAccount(i, devmgruid, "devicemanager", "Devicemanager")
+	}
+}
diff --git a/services/device/util_linux_test.go b/services/device/util_linux_test.go
new file mode 100644
index 0000000..f56e64a
--- /dev/null
+++ b/services/device/util_linux_test.go
@@ -0,0 +1,28 @@
+// 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 device_test
+
+import (
+	"os"
+	"os/user"
+
+	"v.io/x/ref/test/v23tests"
+)
+
+const psFlags = "-eouser:20,pid"
+
+func makeTestAccounts(i *v23tests.T) {
+	userAddCmd := i.BinaryFromPath("/usr/bin/sudo")
+
+	if _, err := user.Lookup("vana"); err != nil {
+		userAddCmd.Start("/usr/sbin/adduser", "--no-create-home", "vana").WaitOrDie(os.Stdout, os.Stderr)
+	}
+
+	if _, err := user.Lookup("devicemanager"); err != nil {
+		userAddCmd.Start("/usr/sbin/adduser", "--no-create-home", "devicemanager").Wait(os.Stdout, os.Stderr)
+	}
+}
+
+const runTestOnThisPlatform = true
diff --git a/services/device/v23_test.go b/services/device/v23_test.go
new file mode 100644
index 0000000..0bb141e
--- /dev/null
+++ b/services/device/v23_test.go
@@ -0,0 +1,34 @@
+// 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 device_test
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
+	"v.io/x/ref/test/v23tests"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	modules.DispatchAndExitIfChild()
+	cleanup := v23tests.UseSharedBinDir()
+	r := m.Run()
+	cleanup()
+	os.Exit(r)
+}
+
+func TestV23DeviceManager(t *testing.T) {
+	v23tests.RunTest(t, V23TestDeviceManager)
+}
+
+func TestV23DeviceManagerMultiUser(t *testing.T) {
+	v23tests.RunTest(t, V23TestDeviceManagerMultiUser)
+}
diff --git a/services/discharger/discharger.vdl b/services/discharger/discharger.vdl
new file mode 100644
index 0000000..c04ae3a
--- /dev/null
+++ b/services/discharger/discharger.vdl
@@ -0,0 +1,22 @@
+// 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 discharger defines an interface for obtaining discharges for
+// third-party caveats.
+package discharger
+
+import "v.io/v23/security"
+
+// Discharger is the interface for obtaining discharges for ThirdPartyCaveats.
+type Discharger interface {
+  // Discharge is called by a principal that holds a blessing with a third
+  // party caveat and seeks to get a discharge that proves the fulfillment of
+  // this caveat.
+  Discharge(Caveat security.Caveat, Impetus security.DischargeImpetus) (Discharge security.WireDischarge | error)
+}
+
+error (
+	// Indicates that the Caveat does not require a discharge
+	NotAThirdPartyCaveat(c security.Caveat) { "en": "discharges are not required for non-third-party caveats (id: {c.id})" }
+)
diff --git a/services/discharger/discharger.vdl.go b/services/discharger/discharger.vdl.go
new file mode 100644
index 0000000..ce2745f
--- /dev/null
+++ b/services/discharger/discharger.vdl.go
@@ -0,0 +1,148 @@
+// 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 by the vanadium vdl tool.
+// Source: discharger.vdl
+
+// Package discharger defines an interface for obtaining discharges for
+// third-party caveats.
+package discharger
+
+import (
+	// VDL system imports
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/i18n"
+	"v.io/v23/rpc"
+	"v.io/v23/verror"
+
+	// VDL user imports
+	"v.io/v23/security"
+)
+
+var (
+	// Indicates that the Caveat does not require a discharge
+	ErrNotAThirdPartyCaveat = verror.Register("v.io/x/ref/services/discharger.NotAThirdPartyCaveat", verror.NoRetry, "{1:}{2:} discharges are not required for non-third-party caveats (id: {c.id})")
+)
+
+func init() {
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrNotAThirdPartyCaveat.ID), "{1:}{2:} discharges are not required for non-third-party caveats (id: {c.id})")
+}
+
+// NewErrNotAThirdPartyCaveat returns an error with the ErrNotAThirdPartyCaveat ID.
+func NewErrNotAThirdPartyCaveat(ctx *context.T, c security.Caveat) error {
+	return verror.New(ErrNotAThirdPartyCaveat, ctx, c)
+}
+
+// DischargerClientMethods is the client interface
+// containing Discharger methods.
+//
+// Discharger is the interface for obtaining discharges for ThirdPartyCaveats.
+type DischargerClientMethods interface {
+	// Discharge is called by a principal that holds a blessing with a third
+	// party caveat and seeks to get a discharge that proves the fulfillment of
+	// this caveat.
+	Discharge(ctx *context.T, Caveat security.Caveat, Impetus security.DischargeImpetus, opts ...rpc.CallOpt) (Discharge security.Discharge, err error)
+}
+
+// DischargerClientStub adds universal methods to DischargerClientMethods.
+type DischargerClientStub interface {
+	DischargerClientMethods
+	rpc.UniversalServiceMethods
+}
+
+// DischargerClient returns a client stub for Discharger.
+func DischargerClient(name string) DischargerClientStub {
+	return implDischargerClientStub{name}
+}
+
+type implDischargerClientStub struct {
+	name string
+}
+
+func (c implDischargerClientStub) Discharge(ctx *context.T, i0 security.Caveat, i1 security.DischargeImpetus, opts ...rpc.CallOpt) (o0 security.Discharge, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "Discharge", []interface{}{i0, i1}, []interface{}{&o0}, opts...)
+	return
+}
+
+// DischargerServerMethods is the interface a server writer
+// implements for Discharger.
+//
+// Discharger is the interface for obtaining discharges for ThirdPartyCaveats.
+type DischargerServerMethods interface {
+	// Discharge is called by a principal that holds a blessing with a third
+	// party caveat and seeks to get a discharge that proves the fulfillment of
+	// this caveat.
+	Discharge(ctx *context.T, call rpc.ServerCall, Caveat security.Caveat, Impetus security.DischargeImpetus) (Discharge security.Discharge, err error)
+}
+
+// DischargerServerStubMethods is the server interface containing
+// Discharger methods, as expected by rpc.Server.
+// There is no difference between this interface and DischargerServerMethods
+// since there are no streaming methods.
+type DischargerServerStubMethods DischargerServerMethods
+
+// DischargerServerStub adds universal methods to DischargerServerStubMethods.
+type DischargerServerStub interface {
+	DischargerServerStubMethods
+	// Describe the Discharger interfaces.
+	Describe__() []rpc.InterfaceDesc
+}
+
+// DischargerServer returns a server stub for Discharger.
+// It converts an implementation of DischargerServerMethods into
+// an object that may be used by rpc.Server.
+func DischargerServer(impl DischargerServerMethods) DischargerServerStub {
+	stub := implDischargerServerStub{
+		impl: impl,
+	}
+	// Initialize GlobState; always check the stub itself first, to handle the
+	// case where the user has the Glob method defined in their VDL source.
+	if gs := rpc.NewGlobState(stub); gs != nil {
+		stub.gs = gs
+	} else if gs := rpc.NewGlobState(impl); gs != nil {
+		stub.gs = gs
+	}
+	return stub
+}
+
+type implDischargerServerStub struct {
+	impl DischargerServerMethods
+	gs   *rpc.GlobState
+}
+
+func (s implDischargerServerStub) Discharge(ctx *context.T, call rpc.ServerCall, i0 security.Caveat, i1 security.DischargeImpetus) (security.Discharge, error) {
+	return s.impl.Discharge(ctx, call, i0, i1)
+}
+
+func (s implDischargerServerStub) Globber() *rpc.GlobState {
+	return s.gs
+}
+
+func (s implDischargerServerStub) Describe__() []rpc.InterfaceDesc {
+	return []rpc.InterfaceDesc{DischargerDesc}
+}
+
+// DischargerDesc describes the Discharger interface.
+var DischargerDesc rpc.InterfaceDesc = descDischarger
+
+// descDischarger hides the desc to keep godoc clean.
+var descDischarger = rpc.InterfaceDesc{
+	Name:    "Discharger",
+	PkgPath: "v.io/x/ref/services/discharger",
+	Doc:     "// Discharger is the interface for obtaining discharges for ThirdPartyCaveats.",
+	Methods: []rpc.MethodDesc{
+		{
+			Name: "Discharge",
+			Doc:  "// Discharge is called by a principal that holds a blessing with a third\n// party caveat and seeks to get a discharge that proves the fulfillment of\n// this caveat.",
+			InArgs: []rpc.ArgDesc{
+				{"Caveat", ``},  // security.Caveat
+				{"Impetus", ``}, // security.DischargeImpetus
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"Discharge", ``}, // security.Discharge
+			},
+		},
+	},
+}
diff --git a/services/groups/README b/services/groups/README
new file mode 100644
index 0000000..fa22167
--- /dev/null
+++ b/services/groups/README
@@ -0,0 +1,6 @@
+WORK IN PROGRESS. DO NOT DEPEND ON ANYTHING IN THIS DIRECTORY.
+
+This directory provides an implementation of groups for managing access control.
+
+Group support is under development. Code and interfaces in this directory may
+change at any time.
diff --git a/services/groups/groups/doc.go b/services/groups/groups/doc.go
new file mode 100644
index 0000000..a925394
--- /dev/null
+++ b/services/groups/groups/doc.go
@@ -0,0 +1,181 @@
+// 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
+
+/*
+Command groups creates and manages Vanadium groups of blessing patterns.
+
+Usage:
+   groups <command>
+
+The groups commands are:
+   create      Creates a blessing pattern group
+   delete      Delete a blessing group
+   add         Adds a blessing pattern to a group
+   remove      Removes a blessing pattern from a group
+   relate      Relate a set of blessing to a group
+   get         Returns entries of a group
+   help        Display help for commands or topics
+
+The global flags are:
+ -alsologtostderr=true
+   log to standard error as well as files
+ -log_backtrace_at=:0
+   when logging hits line file:N, emit a stack trace
+ -log_dir=
+   if non-empty, write log files to this directory
+ -logtostderr=false
+   log to standard error instead of files
+ -max_stack_buf_size=4292608
+   max size in bytes of the buffer to use for logging stack traces
+ -metadata=<just specify -metadata to activate>
+   Displays metadata for the program and exits.
+ -stderrthreshold=2
+   logs at or above this threshold go to stderr
+ -v=0
+   log level for V logs
+ -v23.credentials=
+   directory to use for storing security credentials
+ -v23.i18n-catalogue=
+   18n catalogue files to load, comma separated
+ -v23.namespace.root=[/(dev.v.io/role/vprod/service/mounttabled)@ns.dev.v.io:8101]
+   local namespace root; can be repeated to provided multiple roots
+ -v23.proxy=
+   object name of proxy service to use to export services across network
+   boundaries
+ -v23.tcp.address=
+   address to listen on
+ -v23.tcp.protocol=wsh
+   protocol to listen with
+ -v23.vtrace.cache-size=1024
+   The number of vtrace traces to store in memory.
+ -v23.vtrace.collect-regexp=
+   Spans and annotations that match this regular expression will trigger trace
+   collection.
+ -v23.vtrace.dump-on-shutdown=true
+   If true, dump all stored traces on runtime shutdown.
+ -v23.vtrace.sample-rate=0
+   Rate (from 0.0 to 1.0) to sample vtrace traces.
+ -vmodule=
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
+
+Groups create - Creates a blessing pattern group
+
+Creates a blessing pattern group.
+
+Usage:
+   groups create [flags] <von> <patterns...>
+
+<von> is the vanadium object name of the group to create
+
+<patterns...> is a list of blessing pattern chunks
+
+The groups create flags are:
+ -permissions=
+   Path to a permissions file
+
+Groups delete - Delete a blessing group
+
+Delete a blessing group.
+
+Usage:
+   groups delete [flags] <von>
+
+<von> is the vanadium object name of the group
+
+The groups delete flags are:
+ -version=
+   Identifies group version
+
+Groups add - Adds a blessing pattern to a group
+
+Adds a blessing pattern to a group.
+
+Usage:
+   groups add [flags] <von> <pattern>
+
+<von> is the vanadium object name of the group
+
+<pattern> is the blessing pattern chunk to add
+
+The groups add flags are:
+ -version=
+   Identifies group version
+
+Groups remove - Removes a blessing pattern from a group
+
+Removes a blessing pattern from a group.
+
+Usage:
+   groups remove [flags] <von> <pattern>
+
+<von> is the vanadium object name of the group
+
+<pattern> is the blessing pattern chunk to add
+
+The groups remove flags are:
+ -version=
+   Identifies group version
+
+Groups relate - Relate a set of blessing to a group
+
+Relate a set of blessing to a group. The result is returned as a JSON-encoded
+output.
+
+NOTE: This command exists primarily for debugging purposes. In particular,
+invocations of the Relate RPC are expected to be mainly issued by the
+authorization logic.
+
+Usage:
+   groups relate [flags] <von> <blessings>
+
+<von> is the vanadium object name of the group
+
+<blessings...> is a list of blessings
+
+The groups relate flags are:
+ -approximation=under
+   Identifies the type of approximation to use; supported values = (under, over)
+ -version=
+   Identifies group version
+
+Groups get - Returns entries of a group
+
+Returns entries of a group.
+
+Usage:
+   groups get <von>
+
+<von> is the vanadium object name of the group
+
+Groups help - Display help for commands or topics
+
+Help with no args displays the usage of the parent command.
+
+Help with args displays the usage of the specified sub-command or help topic.
+
+"help ..." recursively displays help for all commands and topics.
+
+Usage:
+   groups help [flags] [command/topic ...]
+
+[command/topic ...] optionally identifies a specific sub-command or help topic.
+
+The groups help flags are:
+ -style=compact
+   The formatting style for help output:
+      compact - Good for compact cmdline output.
+      full    - Good for cmdline output, shows all global flags.
+      godoc   - Good for godoc processing.
+   Override the default by setting the CMDLINE_STYLE environment variable.
+ -width=<terminal width>
+   Format output to this target width in runes, or unlimited if width < 0.
+   Defaults to the terminal width if available.  Override the default by setting
+   the CMDLINE_WIDTH environment variable.
+*/
+package main
diff --git a/services/groups/groups/main.go b/services/groups/groups/main.go
new file mode 100644
index 0000000..3200264
--- /dev/null
+++ b/services/groups/groups/main.go
@@ -0,0 +1,222 @@
+// 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.
+
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go .
+
+package main
+
+import (
+	"encoding/json"
+	"fmt"
+	"os"
+
+	"v.io/v23/context"
+	"v.io/v23/security/access"
+	"v.io/v23/services/groups"
+	"v.io/x/lib/cmdline"
+	"v.io/x/lib/set"
+	"v.io/x/ref/lib/v23cmd"
+	_ "v.io/x/ref/runtime/factories/generic"
+)
+
+type relateResult struct {
+	Remainder      map[string]struct{}
+	Approximations []groups.Approximation
+	Version        string
+}
+
+var (
+	flagPermFile      string
+	flagVersion       string
+	flagApproximation string
+
+	cmdCreate = &cmdline.Command{
+		Name:     "create",
+		Short:    "Creates a blessing pattern group",
+		Long:     "Creates a blessing pattern group.",
+		ArgsName: "<von> <patterns...>",
+		ArgsLong: `
+<von> is the vanadium object name of the group to create
+
+<patterns...> is a list of blessing pattern chunks
+`,
+		Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline.Env, args []string) error {
+			// Process command-line arguments.
+			if want, got := 1, len(args); want > got {
+				return env.UsageErrorf("create: unexpected number of arguments, want at least %d, got %d", want, got)
+			}
+			von := args[0]
+			var patterns []groups.BlessingPatternChunk
+			for _, pattern := range args[1:] {
+				patterns = append(patterns, groups.BlessingPatternChunk(pattern))
+			}
+			// Process command-line flags.
+			var permissions access.Permissions
+			if flagPermFile != "" {
+				file, err := os.Open(flagPermFile)
+				if err != nil {
+					return fmt.Errorf("Open(%v) failed: %v", flagPermFile, err)
+				}
+				permissions, err = access.ReadPermissions(file)
+				if err != nil {
+					return err
+				}
+			}
+			// Invoke the "create" RPC.
+			client := groups.GroupClient(von)
+			return client.Create(ctx, permissions, patterns)
+		}),
+	}
+
+	cmdDelete = &cmdline.Command{
+		Name:     "delete",
+		Short:    "Delete a blessing group",
+		Long:     "Delete a blessing group.",
+		ArgsName: "<von>",
+		ArgsLong: "<von> is the vanadium object name of the group",
+		Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline.Env, args []string) error {
+			// Process command-line arguments.
+			if want, got := 1, len(args); want != got {
+				return env.UsageErrorf("delete: unexpected number of arguments, want %d, got %d", want, got)
+			}
+			von := args[0]
+			// Invoke the "delete" RPC.
+			client := groups.GroupClient(von)
+			return client.Delete(ctx, flagVersion)
+		}),
+	}
+
+	cmdAdd = &cmdline.Command{
+		Name:     "add",
+		Short:    "Adds a blessing pattern to a group",
+		Long:     "Adds a blessing pattern to a group.",
+		ArgsName: "<von> <pattern>",
+		ArgsLong: `
+<von> is the vanadium object name of the group
+
+<pattern> is the blessing pattern chunk to add
+`,
+		Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline.Env, args []string) error {
+			// Process command-line arguments.
+			if want, got := 2, len(args); want != got {
+				return env.UsageErrorf("add: unexpected number of arguments, want %d, got %d", want, got)
+			}
+			von, pattern := args[0], args[1]
+			// Invoke the "add" RPC.
+			client := groups.GroupClient(von)
+			return client.Add(ctx, groups.BlessingPatternChunk(pattern), flagVersion)
+		}),
+	}
+
+	cmdRemove = &cmdline.Command{
+		Name:     "remove",
+		Short:    "Removes a blessing pattern from a group",
+		Long:     "Removes a blessing pattern from a group.",
+		ArgsName: "<von> <pattern>",
+		ArgsLong: `
+<von> is the vanadium object name of the group
+
+<pattern> is the blessing pattern chunk to add
+`,
+		Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline.Env, args []string) error {
+			// Process command-line arguments.
+			if want, got := 2, len(args); want != got {
+				return env.UsageErrorf("remove: unexpected number of arguments, want %d, got %d", want, got)
+			}
+			von, pattern := args[0], args[1]
+			// Invoke the "remove" RPC.
+			client := groups.GroupClient(von)
+			return client.Remove(ctx, groups.BlessingPatternChunk(pattern), flagVersion)
+		}),
+	}
+
+	cmdRelate = &cmdline.Command{
+		Name:  "relate",
+		Short: "Relate a set of blessing to a group",
+		Long: `
+Relate a set of blessing to a group. The result is returned as a
+JSON-encoded output.
+
+NOTE: This command exists primarily for debugging purposes. In
+particular, invocations of the Relate RPC are expected to be mainly
+issued by the authorization logic.
+`,
+		ArgsName: "<von> <blessings>",
+		ArgsLong: `
+<von> is the vanadium object name of the group
+
+<blessings...> is a list of blessings
+`,
+		Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline.Env, args []string) error {
+			// Process command-line arguments.
+			if want, got := 1, len(args); want > got {
+				return env.UsageErrorf("relate: unexpected number of arguments, want at least %d, got %d", want, got)
+			}
+			von := args[0]
+			blessings := set.String.FromSlice(args[1:])
+			// Process command-line flags.
+			var hint groups.ApproximationType
+			switch flagApproximation {
+			case "under":
+				hint = groups.ApproximationTypeUnder
+			case "over":
+				hint = groups.ApproximationTypeOver
+			}
+			// Invoke the "relate" RPC.
+			client := groups.GroupClient(von)
+			remainder, approximations, version, err := client.Relate(ctx, blessings, hint, flagVersion, nil)
+			if err != nil {
+				return err
+			}
+			result := relateResult{
+				Remainder:      remainder,
+				Approximations: approximations,
+				Version:        version,
+			}
+			bytes, err := json.MarshalIndent(result, "", "  ")
+			if err != nil {
+				return fmt.Errorf("MarshalIndent(%v) failed: %v", result, err)
+			}
+			fmt.Fprintf(env.Stdout, "%v\n", string(bytes))
+			return nil
+		}),
+	}
+
+	cmdGet = &cmdline.Command{
+		Name:     "get",
+		Short:    "Returns entries of a group",
+		Long:     "Returns entries of a group.",
+		ArgsName: "<von>",
+		ArgsLong: "<von> is the vanadium object name of the group",
+		Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline.Env, args []string) error {
+			// TODO(jsimsa): Implement once the corresponding server-side
+			// API is designed.
+			return fmt.Errorf(`the "groups get ..." sub-command is not implemented`)
+		}),
+	}
+
+	cmdRoot = &cmdline.Command{
+		Name:     "groups",
+		Short:    "creates and manages Vanadium groups of blessing patterns",
+		Long:     "Command groups creates and manages Vanadium groups of blessing patterns.",
+		Children: []*cmdline.Command{cmdCreate, cmdDelete, cmdAdd, cmdRemove, cmdRelate, cmdGet},
+	}
+)
+
+func init() {
+	cmdCreate.Flags.StringVar(&flagPermFile, "permissions", "", "Path to a permissions file")
+	cmdDelete.Flags.StringVar(&flagVersion, "version", "", "Identifies group version")
+	cmdAdd.Flags.StringVar(&flagVersion, "version", "", "Identifies group version")
+	cmdRemove.Flags.StringVar(&flagVersion, "version", "", "Identifies group version")
+	cmdRelate.Flags.StringVar(&flagVersion, "version", "", "Identifies group version")
+	cmdRelate.Flags.StringVar(&flagApproximation, "approximation", "under",
+		"Identifies the type of approximation to use; supported values = (under, over)",
+	)
+}
+
+func main() {
+	cmdline.HideGlobalFlagsExcept()
+	cmdline.Main(cmdRoot)
+}
diff --git a/services/groups/groups/main_test.go b/services/groups/groups/main_test.go
new file mode 100644
index 0000000..a4c6fed
--- /dev/null
+++ b/services/groups/groups/main_test.go
@@ -0,0 +1,218 @@
+// 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 (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"reflect"
+	"strings"
+	"testing"
+	"unicode"
+	"unicode/utf8"
+
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+	"v.io/v23/security/access"
+	"v.io/v23/services/groups"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/v23cmd"
+	"v.io/x/ref/lib/xrpc"
+	"v.io/x/ref/test"
+)
+
+var group map[string]struct{}
+var buffer bytes.Buffer
+
+type mock struct{}
+
+func (mock) Create(ctx *context.T, call rpc.ServerCall, perms access.Permissions, entries []groups.BlessingPatternChunk) error {
+	fmt.Fprintf(&buffer, "Create(%v, %v) was called", perms, entries)
+	group = make(map[string]struct{}, len(entries))
+	for _, entry := range entries {
+		group[string(entry)] = struct{}{}
+	}
+	return nil
+}
+
+func (mock) Delete(ctx *context.T, call rpc.ServerCall, version string) error {
+	fmt.Fprintf(&buffer, "Delete(%v) was called", version)
+	group = nil
+	return nil
+}
+
+func (mock) Add(ctx *context.T, call rpc.ServerCall, entry groups.BlessingPatternChunk, version string) error {
+	fmt.Fprintf(&buffer, "Add(%v, %v) was called", entry, version)
+	group[string(entry)] = struct{}{}
+	return nil
+}
+
+func (mock) Remove(ctx *context.T, call rpc.ServerCall, entry groups.BlessingPatternChunk, version string) error {
+	fmt.Fprintf(&buffer, "Remove(%v, %v) was called", entry, version)
+	delete(group, string(entry))
+	return nil
+}
+
+func (mock) Relate(ctx *context.T, call rpc.ServerCall, blessings map[string]struct{}, hint groups.ApproximationType, version string, visitedGroups map[string]struct{}) (map[string]struct{}, []groups.Approximation, string, error) {
+	fmt.Fprintf(&buffer, "Relate(%v, %v, %v, %v) was called", blessings, hint, version, visitedGroups)
+	return nil, nil, "123", nil
+}
+
+func (mock) Get(ctx *context.T, call rpc.ServerCall, req groups.GetRequest, version string) (groups.GetResponse, string, error) {
+	return groups.GetResponse{}, "", nil
+}
+
+func (mock) SetPermissions(ctx *context.T, call rpc.ServerCall, perms access.Permissions, version string) error {
+	return nil
+}
+
+func (mock) GetPermissions(ctx *context.T, call rpc.ServerCall) (access.Permissions, string, error) {
+	return nil, "", nil
+}
+
+func capitalize(s string) string {
+	if s == "" {
+		return ""
+	}
+	rune, size := utf8.DecodeRuneInString(s)
+	return string(unicode.ToUpper(rune)) + s[size:]
+}
+
+func startServer(ctx *context.T, t *testing.T) (rpc.XServer, naming.Endpoint) {
+	unpublished := ""
+	s, err := xrpc.NewServer(ctx, unpublished, groups.GroupServer(&mock{}), nil)
+	if err != nil {
+		t.Fatalf("NewServer(%v) failed: %v", unpublished, err)
+	}
+	return s, s.Status().Endpoints[0]
+}
+
+func stopServer(t *testing.T, server rpc.XServer) {
+	if err := server.Stop(); err != nil {
+		t.Errorf("Stop() failed: %v", err)
+	}
+}
+
+func TestGroupClient(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	server, endpoint := startServer(ctx, t)
+	defer stopServer(t, server)
+
+	// Test the "create" command.
+	{
+		var stdout, stderr bytes.Buffer
+		env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
+		patterns := []string{"alice", "bob"}
+		args := append([]string{"create", naming.JoinAddressName(endpoint.String(), "")}, patterns...)
+		if err := v23cmd.ParseAndRunForTest(cmdRoot, ctx, env, args); err != nil {
+			t.Fatalf("run failed: %v\n%v", err, stderr.String())
+		}
+		if got, want := buffer.String(), fmt.Sprintf("Create(%v, %v) was called", access.Permissions{}, patterns); got != want {
+			t.Errorf("got %v, want %v", got, want)
+		}
+		if got, want := strings.TrimSpace(stdout.String()), ""; got != want {
+			t.Errorf("got %q, want %q", got, want)
+		}
+		if got, want := len(group), 2; got != want {
+			t.Errorf("got %v, want %v", got, want)
+		}
+		buffer.Reset()
+	}
+
+	// Test the "add" command.
+	{
+		var stdout, stderr bytes.Buffer
+		env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
+		pattern, version := "charlie", "123"
+		args := []string{"add", "-version=" + version, naming.JoinAddressName(endpoint.String(), ""), pattern}
+		if err := v23cmd.ParseAndRunForTest(cmdRoot, ctx, env, args); err != nil {
+			t.Fatalf("run failed: %v\n%v", err, stderr.String())
+		}
+		if got, want := buffer.String(), fmt.Sprintf("Add(%v, %v) was called", pattern, version); got != want {
+			t.Errorf("got %v, want %v", got, want)
+		}
+		if got, want := strings.TrimSpace(stdout.String()), ""; got != want {
+			t.Errorf("got %q, want %q", got, want)
+		}
+		if got, want := len(group), 3; got != want {
+			t.Errorf("got %v, want %v", got, want)
+		}
+		buffer.Reset()
+	}
+
+	// Test the "remove" command.
+	{
+		var stdout, stderr bytes.Buffer
+		env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
+		pattern, version := "bob", "123"
+		args := []string{"remove", "-version=" + version, naming.JoinAddressName(endpoint.String(), ""), "bob"}
+		if err := v23cmd.ParseAndRunForTest(cmdRoot, ctx, env, args); err != nil {
+			t.Fatalf("run failed: %v\n%v", err, stderr.String())
+		}
+		if got, want := buffer.String(), fmt.Sprintf("Remove(%v, %v) was called", pattern, version); got != want {
+			t.Errorf("got %v, want %v", got, want)
+		}
+		if got, want := strings.TrimSpace(stdout.String()), ""; got != want {
+			t.Errorf("got %q, want %q", got, want)
+		}
+		if got, want := len(group), 2; got != want {
+			t.Errorf("got %v, want %v", got, want)
+		}
+		buffer.Reset()
+	}
+
+	// Test the "delete" command.
+	{
+		var stdout, stderr bytes.Buffer
+		env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
+		version := "123"
+		args := []string{"delete", "-version=" + version, naming.JoinAddressName(endpoint.String(), "")}
+		if err := v23cmd.ParseAndRunForTest(cmdRoot, ctx, env, args); err != nil {
+			t.Fatalf("run failed: %v\n%v", err, stderr.String())
+		}
+		if got, want := buffer.String(), fmt.Sprintf("Delete(%v) was called", version); got != want {
+			t.Errorf("got %v, want %v", got, want)
+		}
+		if got, want := strings.TrimSpace(stdout.String()), ""; got != want {
+			t.Errorf("got %q, want %q", got, want)
+		}
+		if got, want := len(group), 0; got != want {
+			t.Errorf("got %v, want %v", got, want)
+		}
+		buffer.Reset()
+	}
+
+	// Test the "relate" command.
+	{
+		var stdout, stderr bytes.Buffer
+		env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
+		hint, version := "over", "123"
+		args := []string{"relate", "-approximation=" + hint, "-version=" + version, naming.JoinAddressName(endpoint.String(), "")}
+		if err := v23cmd.ParseAndRunForTest(cmdRoot, ctx, env, args); err != nil {
+			t.Fatalf("run failed: %v\n%v", err, stderr.String())
+		}
+		empty := map[string]struct{}{}
+		if got, want := buffer.String(), fmt.Sprintf("Relate(%v, %v, %v, %v) was called", empty, capitalize(hint), version, empty); got != want {
+			t.Errorf("got %v, want %v", got, want)
+		}
+		var got relateResult
+		if err := json.Unmarshal(stdout.Bytes(), &got); err != nil {
+			t.Fatalf("Unmarshal(%v) failed: %v", stdout.Bytes(), err)
+		}
+		want := relateResult{
+			Remainder:      nil,
+			Approximations: nil,
+			Version:        "123",
+		}
+		if !reflect.DeepEqual(got, want) {
+			t.Errorf("got %#v, want %#v", got, want)
+		}
+		buffer.Reset()
+	}
+}
diff --git a/services/groups/groupsd/groupsd_v23_test.go b/services/groups/groupsd/groupsd_v23_test.go
new file mode 100644
index 0000000..fb27e4c
--- /dev/null
+++ b/services/groups/groupsd/groupsd_v23_test.go
@@ -0,0 +1,282 @@
+// 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_test
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"os"
+	"reflect"
+	"syscall"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/security/access"
+	"v.io/v23/services/groups"
+	"v.io/v23/verror"
+	"v.io/x/lib/set"
+	"v.io/x/ref/lib/xrpc"
+	"v.io/x/ref/services/groups/groupsd/testdata/kvstore"
+	"v.io/x/ref/test/modules"
+	"v.io/x/ref/test/v23tests"
+)
+
+//go:generate v23 test generate
+
+type relateResult struct {
+	Remainder      map[string]struct{}
+	Approximations []groups.Approximation
+	Version        string
+}
+
+// V23TestGroupServerIntegration tests the integration between the
+// "groups" command-line client and the "groupsd" server.
+func V23TestGroupServerIntegration(t *v23tests.T) {
+	v23tests.RunRootMT(t, "--v23.tcp.address=127.0.0.1:0")
+
+	// Build binaries for the client and server.
+	var (
+		clientBin  = t.BuildV23Pkg("v.io/x/ref/services/groups/groups")
+		serverBin  = t.BuildV23Pkg("v.io/x/ref/services/groups/groupsd", "-tags=leveldb")
+		serverName = "groups-server"
+		groupA     = naming.Join(serverName, "groupA")
+		groupB     = naming.Join(serverName, "groupB")
+	)
+
+	// Start the groups server.
+	serverBin.Start("-name="+serverName, "-v23.tcp.address=127.0.0.1:0")
+
+	// Create a couple of groups.
+	clientBin.Start("create", groupA).WaitOrDie(os.Stdout, os.Stderr)
+	clientBin.Start("create", groupB, "a", "a/b").WaitOrDie(os.Stdout, os.Stderr)
+
+	// Add a couple of blessing patterns.
+	clientBin.Start("add", groupA, "<grp:groups-server/groupB>").WaitOrDie(os.Stdout, os.Stderr)
+	clientBin.Start("add", groupA, "a").WaitOrDie(os.Stdout, os.Stderr)
+	clientBin.Start("add", groupB, "a/b/c").WaitOrDie(os.Stdout, os.Stderr)
+
+	// Remove a blessing pattern.
+	clientBin.Start("remove", groupB, "a").WaitOrDie(os.Stdout, os.Stderr)
+
+	// Test simple group resolution.
+	{
+		var buffer, stderrBuf bytes.Buffer
+		clientBin.Start("relate", groupB, "a/b/c/d").WaitOrDie(&buffer, &stderrBuf)
+		var got relateResult
+		if err := json.Unmarshal(buffer.Bytes(), &got); err != nil {
+			t.Fatalf("Unmarshal(%v) failed: %v", buffer.String(), err)
+		}
+		want := relateResult{
+			Remainder:      set.String.FromSlice([]string{"c/d", "d"}),
+			Approximations: nil,
+			Version:        "2",
+		}
+		if !reflect.DeepEqual(got, want) {
+			t.Errorf("got %v, want %v", got, want)
+		}
+		if got, want := stderrBuf.Len(), 0; got != want {
+			t.Errorf("got %v, want %v", got, want)
+		}
+	}
+
+	// Test recursive group resolution.
+	{
+		var buffer, stderrBuf bytes.Buffer
+		clientBin.Start("relate", groupA, "a/b/c/d").WaitOrDie(&buffer, &stderrBuf)
+		var got relateResult
+		if err := json.Unmarshal(buffer.Bytes(), &got); err != nil {
+			t.Fatalf("Unmarshal(%v) failed: %v", buffer.String(), err)
+		}
+		want := relateResult{
+			Remainder:      set.String.FromSlice([]string{"b/c/d", "c/d", "d"}),
+			Approximations: nil,
+			Version:        "2",
+		}
+		if !reflect.DeepEqual(got, want) {
+			t.Errorf("got %v, want %v", got, want)
+		}
+		if got, want := stderrBuf.Len(), 0; got != want {
+			t.Errorf("got %v, want %v", got, want)
+		}
+	}
+
+	// Test group resolution failure. Note that under-approximation is
+	// used as the default to handle resolution failures.
+	{
+		clientBin.Start("add", groupB, "<grp:groups-server/groupC>").WaitOrDie(os.Stdout, os.Stderr)
+		var buffer, stderrBuf bytes.Buffer
+		clientBin.Start("relate", groupB, "a/b/c/d").WaitOrDie(&buffer, &stderrBuf)
+		var got relateResult
+		if err := json.Unmarshal(buffer.Bytes(), &got); err != nil {
+			t.Fatalf("Unmarshal(%v) failed: %v", buffer.String(), err)
+		}
+		want := relateResult{
+			Remainder: set.String.FromSlice([]string{"c/d", "d"}),
+			Approximations: []groups.Approximation{
+				groups.Approximation{
+					Reason:  "v.io/v23/verror.NoExist",
+					Details: `groupsd:"groupC".Relate: Does not exist: groupC`,
+				},
+			},
+			Version: "3",
+		}
+		if !reflect.DeepEqual(got, want) {
+			t.Errorf("got %v, want %v", got, want)
+		}
+		if got, want := stderrBuf.Len(), 0; got != want {
+			t.Errorf("got %v, want %v", got, want)
+		}
+	}
+
+	// Delete the groups.
+	clientBin.Start("delete", groupA).WaitOrDie(os.Stdout, os.Stderr)
+	clientBin.Start("delete", groupB).WaitOrDie(os.Stdout, os.Stderr)
+}
+
+// store implements the kvstore.Store interface.
+type store map[string]string
+
+func (s store) Get(ctx *context.T, call rpc.ServerCall, key string) (string, error) {
+	return s[key], nil
+}
+
+func (s store) Set(ctx *context.T, call rpc.ServerCall, key string, value string) error {
+	s[key] = value
+	return nil
+}
+
+const (
+	kvServerName = "key-value-store"
+	getFailed    = "GET FAILED"
+	getOK        = "GET OK"
+	setFailed    = "SET FAILED"
+	setOK        = "SET OK"
+)
+
+var runServer = modules.Register(func(env *modules.Env, args ...string) error {
+	ctx, shutdown := v23.Init()
+	defer shutdown()
+	// Use a shorter timeout to reduce the test overall runtime as the
+	// permission authorizer will attempt to connect to a non-existing
+	// groups server at some point in the test.
+	ctx, _ = context.WithTimeout(ctx, 2*time.Second)
+	authorizer, err := groups.PermissionsAuthorizer(access.Permissions{
+		"Read":  access.AccessList{In: []security.BlessingPattern{"<grp:groups-server/readers>"}},
+		"Write": access.AccessList{In: []security.BlessingPattern{"<grp:groups-server/writers>"}},
+	}, access.TypicalTagType())
+	if err != nil {
+		return err
+	}
+	if _, err := xrpc.NewServer(ctx, kvServerName, kvstore.StoreServer(&store{}), authorizer); err != nil {
+		return err
+	}
+	modules.WaitForEOF(env.Stdin)
+	return nil
+}, "server")
+
+var runClient = modules.Register(func(env *modules.Env, args ...string) error {
+	ctx, shutdown := v23.Init()
+	defer shutdown()
+
+	if got, want := len(args), 1; got < want {
+		return fmt.Errorf("unexpected number of arguments: got %v, want at least %v", got, want)
+	}
+	command := args[0]
+	client := kvstore.StoreClient("key-value-store")
+	switch command {
+	case "get":
+		if got, want := len(args), 2; got != want {
+			return fmt.Errorf("unexpected number of arguments: got %v, want %v", got, want)
+		}
+		key := args[1]
+		value, err := client.Get(ctx, key)
+		if err != nil {
+			fmt.Fprintf(env.Stdout, "%v %v\n", getFailed, verror.ErrorID(err))
+		} else {
+			fmt.Fprintf(env.Stdout, "%v %v\n", getOK, value)
+		}
+	case "set":
+		if got, want := len(args), 3; got != want {
+			return fmt.Errorf("unexpected number of arguments: got %v, want %v", got, want)
+		}
+		key, value := args[1], args[2]
+		if err := client.Set(ctx, key, value); err != nil {
+			fmt.Fprintf(env.Stdout, "%v %v\n", setFailed, verror.ErrorID(err))
+		} else {
+			fmt.Fprintf(env.Stdout, "%v\n", setOK)
+		}
+	}
+	return nil
+}, "client")
+
+func startClient(t *v23tests.T, name string, args ...string) modules.Handle {
+	creds, err := t.Shell().NewChildCredentials(name)
+	if err != nil {
+		t.Fatalf("NewChildCredentials(%v) failed: %v", name, err)
+	}
+	handle, err := t.Shell().StartWithOpts(
+		t.Shell().DefaultStartOpts().WithCustomCredentials(creds).WithSessions(t, time.Minute),
+		nil,
+		runClient,
+		args...,
+	)
+	if err != nil {
+		t.Fatalf("StartWithOpts() failed: %v", err)
+	}
+	return handle
+}
+
+// V23TestGroupServerAuthorization uses an instance of the
+// KeyValueStore server with an groups-based authorizer to test the
+// group server implementation.
+func V23TestGroupServerAuthorization(t *v23tests.T) {
+	v23tests.RunRootMT(t, "--v23.tcp.address=127.0.0.1:0")
+
+	// Build binaries for the groups client and server.
+	var (
+		clientBin  = t.BuildV23Pkg("v.io/x/ref/services/groups/groups")
+		serverBin  = t.BuildV23Pkg("v.io/x/ref/services/groups/groupsd")
+		serverName = "groups-server"
+		readers    = naming.Join(serverName, "readers")
+		writers    = naming.Join(serverName, "writers")
+	)
+
+	// Start the groups server.
+	server := serverBin.Start("-name="+serverName, "-v23.tcp.address=127.0.0.1:0")
+
+	// Create a couple of groups. The <readers> and <writers> groups
+	// identify blessings that can be used to read from and write to the
+	// key value store server respectively.
+	clientBin.Start("create", readers, "root/alice", "root/bob").WaitOrDie(os.Stdout, os.Stderr)
+	clientBin.Start("create", writers, "root/alice").WaitOrDie(os.Stdout, os.Stderr)
+
+	// Start an instance of the key value store server.
+	if _, err := t.Shell().Start(nil, runServer); err != nil {
+		t.Fatalf("Start() failed: %v", err)
+	}
+
+	// Test that alice can write.
+	startClient(t, "alice", "set", "foo", "bar").Expect(setOK)
+	// Test that alice can read.
+	startClient(t, "alice", "get", "foo").Expectf("%v %v", getOK, "bar")
+	// Test that bob can read.
+	startClient(t, "bob", "get", "foo").Expectf("%v %v", getOK, "bar")
+	// Test that bob cannot write.
+	startClient(t, "bob", "set", "foo", "bar").Expectf("%v %v", setFailed, verror.ErrNoAccess.ID)
+	// Stop the groups server and check that as a consequence "alice"
+	// cannot read from the key value store server anymore.
+	if err := server.Kill(syscall.SIGTERM); err != nil {
+		t.Fatalf("Kill() failed: %v", err)
+	}
+	if err := server.Wait(os.Stdout, os.Stderr); err != nil {
+		t.Fatalf("Wait() failed: %v", err)
+	}
+	startClient(t, "alice", "get", "foo").Expectf("%v %v", getFailed, verror.ErrNoAccess.ID)
+}
diff --git a/services/groups/groupsd/main.go b/services/groups/groupsd/main.go
new file mode 100644
index 0000000..c785272
--- /dev/null
+++ b/services/groups/groupsd/main.go
@@ -0,0 +1,111 @@
+// 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.
+
+// Daemon groupsd implements the v.io/v23/services/groups interfaces for
+// managing access control groups.
+package main
+
+// Example invocation:
+// groupsd --v23.tcp.address="127.0.0.1:0" --name=groupsd
+
+import (
+	"fmt"
+	"path/filepath"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/security/access"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/security/securityflag"
+	"v.io/x/ref/lib/signals"
+	"v.io/x/ref/lib/v23cmd"
+	"v.io/x/ref/lib/xrpc"
+	_ "v.io/x/ref/runtime/factories/roaming"
+	"v.io/x/ref/services/groups/internal/server"
+	"v.io/x/ref/services/groups/internal/store/gkv"
+	"v.io/x/ref/services/groups/internal/store/leveldb"
+	"v.io/x/ref/services/groups/internal/store/mem"
+)
+
+var (
+	flagName    string
+	flagEngine  string
+	flagRootDir string
+	flagPersist string
+)
+
+func main() {
+	cmdGroupsD.Flags.StringVar(&flagName, "name", "", "Name to mount the groups server as.")
+	cmdGroupsD.Flags.StringVar(&flagEngine, "engine", "memstore", "Storage engine to use. Currently supported: gkv, leveldb, and memstore.")
+	cmdGroupsD.Flags.StringVar(&flagRootDir, "root-dir", "/var/lib/groupsd", "Root dir for storage engines and other data.")
+
+	cmdline.HideGlobalFlagsExcept()
+	cmdline.Main(cmdGroupsD)
+}
+
+// defaultPerms returns a permissions object that grants all permissions to the
+// provided blessing patterns.
+func defaultPerms(blessingPatterns []security.BlessingPattern) access.Permissions {
+	perms := access.Permissions{}
+	for _, tag := range access.AllTypicalTags() {
+		for _, bp := range blessingPatterns {
+			perms.Add(bp, string(tag))
+		}
+	}
+	return perms
+}
+
+var cmdGroupsD = &cmdline.Command{
+	Runner: v23cmd.RunnerFunc(runGroupsD),
+	Name:   "groupsd",
+	Short:  "Runs the groups daemon.",
+	Long: `
+Command groupsd runs the groups daemon, which implements the
+v.io/v23/services/groups.Group interface.
+`,
+}
+
+func runGroupsD(ctx *context.T, env *cmdline.Env, args []string) error {
+	perms, err := securityflag.PermissionsFromFlag()
+	if err != nil {
+		return fmt.Errorf("PermissionsFromFlag() failed: %v", err)
+	}
+	if perms != nil {
+		ctx.Infof("Using permissions from command line flag.")
+	} else {
+		ctx.Infof("No permissions flag provided. Giving local principal all permissions.")
+		perms = defaultPerms(security.DefaultBlessingPatterns(v23.GetPrincipal(ctx)))
+	}
+	var dispatcher rpc.Dispatcher
+	switch flagEngine {
+	case "gkv":
+		file := filepath.Join(flagRootDir, ".gkvstore")
+		store, err := gkv.New(file)
+		if err != nil {
+			ctx.Fatalf("gkv.New(%v) failed: %v", file, err)
+		}
+		dispatcher = server.NewManager(store, perms)
+	case "leveldb":
+		store, err := leveldb.Open(flagRootDir)
+		if err != nil {
+			ctx.Fatalf("Open(%v) failed: %v", flagRootDir, err)
+		}
+		dispatcher = server.NewManager(store, perms)
+	case "memstore":
+		dispatcher = server.NewManager(mem.New(), perms)
+	default:
+		return fmt.Errorf("unknown storage engine %v", flagEngine)
+	}
+	server, err := xrpc.NewDispatchingServer(ctx, flagName, dispatcher)
+	if err != nil {
+		return fmt.Errorf("NewDispatchingServer(%v) failed: %v", flagName, err)
+	}
+	ctx.Infof("Groups server running at endpoint=%q", server.Status().Endpoints[0].Name())
+
+	// Wait until shutdown.
+	<-signals.ShutdownOnSignals(ctx)
+	return nil
+}
diff --git a/services/groups/groupsd/testdata/kvstore/kvstore.vdl b/services/groups/groupsd/testdata/kvstore/kvstore.vdl
new file mode 100644
index 0000000..ae8dd66
--- /dev/null
+++ b/services/groups/groupsd/testdata/kvstore/kvstore.vdl
@@ -0,0 +1,16 @@
+// 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 kvstore implements a simple key-value store used for
+// testing the groups-based authorization.
+package kvstore
+
+import (
+  "v.io/v23/security/access"
+)
+
+type Store interface {
+  Get(key string) (string | error) {access.Read}
+  Set(key string, value string) error {access.Write}
+}
diff --git a/services/groups/groupsd/testdata/kvstore/kvstore.vdl.go b/services/groups/groupsd/testdata/kvstore/kvstore.vdl.go
new file mode 100644
index 0000000..40fdc50
--- /dev/null
+++ b/services/groups/groupsd/testdata/kvstore/kvstore.vdl.go
@@ -0,0 +1,140 @@
+// 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 by the vanadium vdl tool.
+// Source: kvstore.vdl
+
+// Package kvstore implements a simple key-value store used for
+// testing the groups-based authorization.
+package kvstore
+
+import (
+	// VDL system imports
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/vdl"
+
+	// VDL user imports
+	"v.io/v23/security/access"
+)
+
+// StoreClientMethods is the client interface
+// containing Store methods.
+type StoreClientMethods interface {
+	Get(ctx *context.T, key string, opts ...rpc.CallOpt) (string, error)
+	Set(ctx *context.T, key string, value string, opts ...rpc.CallOpt) error
+}
+
+// StoreClientStub adds universal methods to StoreClientMethods.
+type StoreClientStub interface {
+	StoreClientMethods
+	rpc.UniversalServiceMethods
+}
+
+// StoreClient returns a client stub for Store.
+func StoreClient(name string) StoreClientStub {
+	return implStoreClientStub{name}
+}
+
+type implStoreClientStub struct {
+	name string
+}
+
+func (c implStoreClientStub) Get(ctx *context.T, i0 string, opts ...rpc.CallOpt) (o0 string, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "Get", []interface{}{i0}, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implStoreClientStub) Set(ctx *context.T, i0 string, i1 string, opts ...rpc.CallOpt) (err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "Set", []interface{}{i0, i1}, nil, opts...)
+	return
+}
+
+// StoreServerMethods is the interface a server writer
+// implements for Store.
+type StoreServerMethods interface {
+	Get(ctx *context.T, call rpc.ServerCall, key string) (string, error)
+	Set(ctx *context.T, call rpc.ServerCall, key string, value string) error
+}
+
+// StoreServerStubMethods is the server interface containing
+// Store methods, as expected by rpc.Server.
+// There is no difference between this interface and StoreServerMethods
+// since there are no streaming methods.
+type StoreServerStubMethods StoreServerMethods
+
+// StoreServerStub adds universal methods to StoreServerStubMethods.
+type StoreServerStub interface {
+	StoreServerStubMethods
+	// Describe the Store interfaces.
+	Describe__() []rpc.InterfaceDesc
+}
+
+// StoreServer returns a server stub for Store.
+// It converts an implementation of StoreServerMethods into
+// an object that may be used by rpc.Server.
+func StoreServer(impl StoreServerMethods) StoreServerStub {
+	stub := implStoreServerStub{
+		impl: impl,
+	}
+	// Initialize GlobState; always check the stub itself first, to handle the
+	// case where the user has the Glob method defined in their VDL source.
+	if gs := rpc.NewGlobState(stub); gs != nil {
+		stub.gs = gs
+	} else if gs := rpc.NewGlobState(impl); gs != nil {
+		stub.gs = gs
+	}
+	return stub
+}
+
+type implStoreServerStub struct {
+	impl StoreServerMethods
+	gs   *rpc.GlobState
+}
+
+func (s implStoreServerStub) Get(ctx *context.T, call rpc.ServerCall, i0 string) (string, error) {
+	return s.impl.Get(ctx, call, i0)
+}
+
+func (s implStoreServerStub) Set(ctx *context.T, call rpc.ServerCall, i0 string, i1 string) error {
+	return s.impl.Set(ctx, call, i0, i1)
+}
+
+func (s implStoreServerStub) Globber() *rpc.GlobState {
+	return s.gs
+}
+
+func (s implStoreServerStub) Describe__() []rpc.InterfaceDesc {
+	return []rpc.InterfaceDesc{StoreDesc}
+}
+
+// StoreDesc describes the Store interface.
+var StoreDesc rpc.InterfaceDesc = descStore
+
+// descStore hides the desc to keep godoc clean.
+var descStore = rpc.InterfaceDesc{
+	Name:    "Store",
+	PkgPath: "v.io/x/ref/services/groups/groupsd/testdata/kvstore",
+	Methods: []rpc.MethodDesc{
+		{
+			Name: "Get",
+			InArgs: []rpc.ArgDesc{
+				{"key", ``}, // string
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"", ``}, // string
+			},
+			Tags: []*vdl.Value{vdl.ValueOf(access.Tag("Read"))},
+		},
+		{
+			Name: "Set",
+			InArgs: []rpc.ArgDesc{
+				{"key", ``},   // string
+				{"value", ``}, // string
+			},
+			Tags: []*vdl.Value{vdl.ValueOf(access.Tag("Write"))},
+		},
+	},
+}
diff --git a/services/groups/groupsd/v23_test.go b/services/groups/groupsd/v23_test.go
new file mode 100644
index 0000000..d1b7b0c
--- /dev/null
+++ b/services/groups/groupsd/v23_test.go
@@ -0,0 +1,34 @@
+// 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 main_test
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
+	"v.io/x/ref/test/v23tests"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	modules.DispatchAndExitIfChild()
+	cleanup := v23tests.UseSharedBinDir()
+	r := m.Run()
+	cleanup()
+	os.Exit(r)
+}
+
+func TestV23GroupServerIntegration(t *testing.T) {
+	v23tests.RunTest(t, V23TestGroupServerIntegration)
+}
+
+func TestV23GroupServerAuthorization(t *testing.T) {
+	v23tests.RunTest(t, V23TestGroupServerAuthorization)
+}
diff --git a/services/groups/internal/server/doc.go b/services/groups/internal/server/doc.go
new file mode 100644
index 0000000..25f2d73
--- /dev/null
+++ b/services/groups/internal/server/doc.go
@@ -0,0 +1,6 @@
+// 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 server provides an implementation of the groups.Group RPC interface.
+package server
diff --git a/services/groups/internal/server/group.go b/services/groups/internal/server/group.go
new file mode 100644
index 0000000..715ba07
--- /dev/null
+++ b/services/groups/internal/server/group.go
@@ -0,0 +1,212 @@
+// 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 server
+
+// TODO(sadovsky): Check Resolve access on parent where applicable. Relatedly,
+// convert ErrNoExist and ErrNoAccess to ErrNoExistOrNoAccess where needed to
+// preserve privacy.
+
+import (
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/security/access"
+	"v.io/v23/services/groups"
+	"v.io/v23/verror"
+	"v.io/x/lib/set"
+	"v.io/x/ref/services/groups/internal/store"
+)
+
+type group struct {
+	name string
+	m    *manager
+}
+
+var _ groups.GroupServerMethods = (*group)(nil)
+
+// TODO(sadovsky): Reserve buckets for users.
+func (g *group) Create(ctx *context.T, call rpc.ServerCall, perms access.Permissions, entries []groups.BlessingPatternChunk) error {
+	// Perform Permissions check.
+	// TODO(sadovsky): Enable this Permissions check and acquire a lock on the
+	// group server Permissions.
+	if false {
+		if err := g.authorize(ctx, call.Security(), g.m.perms); err != nil {
+			return err
+		}
+	}
+	if perms == nil {
+		perms = access.Permissions{}
+		blessings, _ := security.RemoteBlessingNames(ctx, call.Security())
+		if len(blessings) == 0 {
+			// The blessings presented by the caller do not give it a name for this
+			// operation. We could create a world-accessible group, but it seems safer
+			// to return an error.
+			return groups.NewErrNoBlessings(ctx)
+		}
+		for _, tag := range access.AllTypicalTags() {
+			for _, b := range blessings {
+				perms.Add(security.BlessingPattern(b), string(tag))
+			}
+		}
+	}
+	entrySet := map[groups.BlessingPatternChunk]struct{}{}
+	for _, v := range entries {
+		entrySet[v] = struct{}{}
+	}
+	gd := groupData{Perms: perms, Entries: entrySet}
+	if err := g.m.st.Insert(g.name, gd); err != nil {
+		// TODO(sadovsky): We are leaking the fact that this group exists. If the
+		// client doesn't have access to this group, we should probably return an
+		// opaque error. (Reserving buckets for users will help.)
+		if verror.ErrorID(err) == store.ErrKeyExists.ID {
+			return verror.New(verror.ErrExist, ctx, g.name)
+		}
+		return verror.New(verror.ErrInternal, ctx, err)
+	}
+	return nil
+}
+
+func (g *group) Delete(ctx *context.T, call rpc.ServerCall, version string) error {
+	if err := g.readModifyWrite(ctx, call.Security(), version, func(gd *groupData, versionSt string) error {
+		return g.m.st.Delete(g.name, versionSt)
+	}); err != nil && verror.ErrorID(err) != verror.ErrNoExist.ID {
+		return err
+	}
+	return nil
+}
+
+func (g *group) Add(ctx *context.T, call rpc.ServerCall, entry groups.BlessingPatternChunk, version string) error {
+	return g.update(ctx, call.Security(), version, func(gd *groupData) {
+		if gd.Entries == nil {
+			gd.Entries = map[groups.BlessingPatternChunk]struct{}{}
+		}
+		gd.Entries[entry] = struct{}{}
+	})
+}
+
+func (g *group) Remove(ctx *context.T, call rpc.ServerCall, entry groups.BlessingPatternChunk, version string) error {
+	return g.update(ctx, call.Security(), version, func(gd *groupData) {
+		delete(gd.Entries, entry)
+	})
+}
+
+func (g *group) Get(ctx *context.T, call rpc.ServerCall, req groups.GetRequest, reqVersion string) (groups.GetResponse, string, error) {
+	gd, resVersion, err := g.getInternal(ctx, call.Security())
+	if err != nil {
+		return groups.GetResponse{}, "", err
+	}
+
+	// If version is set and matches the Group's current version,
+	// send an empty response (the equivalent of "HTTP 304 Not Modified").
+	if reqVersion == resVersion {
+		return groups.GetResponse{}, resVersion, nil
+	}
+
+	return groups.GetResponse{Entries: gd.Entries}, resVersion, nil
+}
+
+func (g *group) Relate(ctx *context.T, call rpc.ServerCall, blessings map[string]struct{}, hint groups.ApproximationType, reqVersion string, visitedGroups map[string]struct{}) (map[string]struct{}, []groups.Approximation, string, error) {
+	gd, resVersion, err := g.getInternal(ctx, call.Security())
+	if err != nil {
+		return nil, nil, "", err
+	}
+
+	// If version is set and matches the Group's current version,
+	// send an empty response (the equivalent of "HTTP 304 Not Modified").
+	if reqVersion == resVersion {
+		return nil, nil, resVersion, nil
+	}
+
+	remainder := make(map[string]struct{})
+	var approximations []groups.Approximation
+	for p := range gd.Entries {
+		rem, apprxs := groups.Match(ctx, security.BlessingPattern(p), hint, visitedGroups, blessings)
+		set.String.Union(remainder, rem)
+		approximations = append(approximations, apprxs...)
+	}
+
+	return remainder, approximations, resVersion, nil
+}
+
+func (g *group) SetPermissions(ctx *context.T, call rpc.ServerCall, perms access.Permissions, version string) error {
+	return g.update(ctx, call.Security(), version, func(gd *groupData) {
+		gd.Perms = perms
+	})
+}
+
+func (g *group) GetPermissions(ctx *context.T, call rpc.ServerCall) (perms access.Permissions, version string, err error) {
+	gd, version, err := g.getInternal(ctx, call.Security())
+	if err != nil {
+		return nil, "", err
+	}
+	return gd.Perms, version, nil
+}
+
+////////////////////////////////////////
+// Internal helpers
+
+// Returns a VDL-compatible error.
+func (g *group) authorize(ctx *context.T, call security.Call, perms access.Permissions) error {
+	//auth, _ := access.TypicalTagTypePermissionsAuthorizer(perms)
+	auth := access.TypicalTagTypePermissionsAuthorizer(perms)
+	if err := auth.Authorize(ctx, call); err != nil {
+		return verror.New(verror.ErrNoAccess, ctx, err)
+	}
+	return nil
+}
+
+// Returns a VDL-compatible error. Performs access check.
+func (g *group) getInternal(ctx *context.T, call security.Call) (gd groupData, version string, err error) {
+	version, err = g.m.st.Get(g.name, &gd)
+	if err != nil {
+		if verror.ErrorID(err) == store.ErrUnknownKey.ID {
+			return groupData{}, "", verror.New(verror.ErrNoExist, ctx, g.name)
+		}
+		return groupData{}, "", verror.New(verror.ErrInternal, ctx, err)
+	}
+	if err := g.authorize(ctx, call, gd.Perms); err != nil {
+		return groupData{}, "", err
+	}
+	return gd, version, nil
+}
+
+// Returns a VDL-compatible error. Performs access check.
+func (g *group) update(ctx *context.T, call security.Call, version string, fn func(gd *groupData)) error {
+	return g.readModifyWrite(ctx, call, version, func(gd *groupData, versionSt string) error {
+		fn(gd)
+		return g.m.st.Update(g.name, *gd, versionSt)
+	})
+}
+
+// Returns a VDL-compatible error. Performs access check.
+// fn should perform the "modify, write" part of "read, modify, write", and
+// should return a Store error.
+func (g *group) readModifyWrite(ctx *context.T, call security.Call, version string, fn func(gd *groupData, versionSt string) error) error {
+	// Transaction retry loop.
+	for i := 0; i < 3; i++ {
+		gd, versionSt, err := g.getInternal(ctx, call)
+		if err != nil {
+			return err
+		}
+		// Fail early if possible.
+		if version != "" && version != versionSt {
+			return verror.NewErrBadVersion(ctx)
+		}
+		if err := fn(&gd, versionSt); err != nil {
+			if verror.ErrorID(err) == verror.ErrBadVersion.ID {
+				// Retry on version error if the original version was empty.
+				if version != "" {
+					return err
+				}
+			} else {
+				// Abort on non-version error.
+				return verror.New(verror.ErrInternal, ctx, err)
+			}
+		} else {
+			return nil
+		}
+	}
+	return groups.NewErrExcessiveContention(ctx)
+}
diff --git a/services/groups/internal/server/manager.go b/services/groups/internal/server/manager.go
new file mode 100644
index 0000000..aa8595f
--- /dev/null
+++ b/services/groups/internal/server/manager.go
@@ -0,0 +1,36 @@
+// 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 server
+
+import (
+	"strings"
+
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/security/access"
+	"v.io/v23/services/groups"
+	"v.io/x/ref/services/groups/internal/store"
+)
+
+type manager struct {
+	st    store.Store
+	perms access.Permissions
+}
+
+var _ rpc.Dispatcher = (*manager)(nil)
+
+func NewManager(st store.Store, perms access.Permissions) *manager {
+	return &manager{st: st, perms: perms}
+}
+
+func (m *manager) Lookup(_ *context.T, suffix string) (interface{}, security.Authorizer, error) {
+	suffix = strings.TrimPrefix(suffix, "/")
+	// TODO(sadovsky): Check that suffix is a valid group name.
+	// TODO(sadovsky): Use a real authorizer. Note, this authorizer will be
+	// relatively permissive. Stricter access control happens in the individual
+	// RPC methods. See syncgroupserver/main.go for example.
+	return groups.GroupServer(&group{name: suffix, m: m}), nil, nil
+}
diff --git a/services/groups/internal/server/server_leveldb_test.go b/services/groups/internal/server/server_leveldb_test.go
new file mode 100644
index 0000000..650c1fd
--- /dev/null
+++ b/services/groups/internal/server/server_leveldb_test.go
@@ -0,0 +1,31 @@
+// 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 leveldb
+
+package server_test
+
+import (
+	"testing"
+)
+
+func TestCreateLevelDBStore(t *testing.T) {
+	testCreateHelper(t, leveldbstore)
+}
+
+func TestDeleteLevelDBStore(t *testing.T) {
+	testDeleteHelper(t, leveldbstore)
+}
+
+func TestPermsLevelDBStore(t *testing.T) {
+	testPermsHelper(t, leveldbstore)
+}
+
+func TestAddLevelDBStore(t *testing.T) {
+	testAddHelper(t, leveldbstore)
+}
+
+func TestRemoveLevelDBStore(t *testing.T) {
+	testRemoveHelper(t, leveldbstore)
+}
diff --git a/services/groups/internal/server/server_test.go b/services/groups/internal/server/server_test.go
new file mode 100644
index 0000000..d5b0e6e
--- /dev/null
+++ b/services/groups/internal/server/server_test.go
@@ -0,0 +1,622 @@
+// 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 server_test
+
+import (
+	"io/ioutil"
+	"os"
+	"reflect"
+	"runtime/debug"
+	"testing"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/security"
+	"v.io/v23/security/access"
+	"v.io/v23/services/groups"
+	"v.io/v23/verror"
+	"v.io/x/ref/lib/xrpc"
+	_ "v.io/x/ref/runtime/factories/generic"
+	"v.io/x/ref/services/groups/internal/server"
+	"v.io/x/ref/services/groups/internal/store"
+	"v.io/x/ref/services/groups/internal/store/gkv"
+	"v.io/x/ref/services/groups/internal/store/leveldb"
+	"v.io/x/ref/services/groups/internal/store/mem"
+	"v.io/x/ref/test/testutil"
+)
+
+type backend int
+
+const (
+	gkvstore backend = iota
+	leveldbstore
+	memstore
+)
+
+func Fatal(t *testing.T, args ...interface{}) {
+	debug.PrintStack()
+	t.Fatal(args...)
+}
+
+func Fatalf(t *testing.T, format string, args ...interface{}) {
+	debug.PrintStack()
+	t.Fatalf(format, args...)
+}
+
+func getEntriesOrDie(t *testing.T, ctx *context.T, g groups.GroupClientStub) map[groups.BlessingPatternChunk]struct{} {
+	res, _, err := g.Get(ctx, groups.GetRequest{}, "")
+	if err != nil {
+		Fatalf(t, "Get failed: %v", err)
+	}
+	return res.Entries
+}
+
+func getPermsOrDie(t *testing.T, ctx *context.T, g groups.GroupClientStub) access.Permissions {
+	res, _, err := g.GetPermissions(ctx)
+	if err != nil {
+		Fatalf(t, "GetPermissions failed: %v", err)
+	}
+	return res
+}
+
+func getVersionOrDie(t *testing.T, ctx *context.T, g groups.GroupClientStub) string {
+	_, version, err := g.Get(ctx, groups.GetRequest{}, "")
+	if err != nil {
+		Fatalf(t, "Get failed: %v", err)
+	}
+	return version
+}
+
+func bpc(chunk string) groups.BlessingPatternChunk {
+	return groups.BlessingPatternChunk(chunk)
+}
+
+func bpcSet(chunks ...string) map[groups.BlessingPatternChunk]struct{} {
+	res := map[groups.BlessingPatternChunk]struct{}{}
+	for _, chunk := range chunks {
+		res[bpc(chunk)] = struct{}{}
+	}
+	return res
+}
+
+func bpcSlice(chunks ...string) []groups.BlessingPatternChunk {
+	res := []groups.BlessingPatternChunk{}
+	for _, chunk := range chunks {
+		res = append(res, bpc(chunk))
+	}
+	return res
+}
+
+func entriesEqual(a, b map[groups.BlessingPatternChunk]struct{}) bool {
+	// Unlike DeepEqual, we treat nil and empty maps as equivalent.
+	if len(a) == 0 && len(b) == 0 {
+		return true
+	}
+	return reflect.DeepEqual(a, b)
+}
+
+func newServer(ctx *context.T, be backend) (string, func()) {
+	// TODO(sadovsky): Pass in perms and test perms-checking in Group.Create().
+	perms := access.Permissions{}
+	var st store.Store
+	var path string
+	var err error
+
+	switch be {
+	case memstore:
+		st = mem.New()
+	case gkvstore:
+		file, err := ioutil.TempFile("", "")
+		if err != nil {
+			ctx.Fatal("ioutil.TempFile() failed: ", err)
+		}
+		st, err = gkv.New(file.Name())
+		if err != nil {
+			ctx.Fatal("gkv.New() failed: ", err)
+		}
+		path = file.Name()
+	case leveldbstore:
+		path, err = ioutil.TempDir("", "")
+		if err != nil {
+			ctx.Fatal("ioutil.TempDir() failed: ", err)
+		}
+		st, err = leveldb.Open(path)
+		if err != nil {
+			ctx.Fatal("leveldb.Open() failed: ", err)
+		}
+	default:
+		ctx.Fatal("unknown backend: ", be)
+	}
+
+	m := server.NewManager(st, perms)
+
+	server, err := xrpc.NewDispatchingServer(ctx, "", m)
+	if err != nil {
+		ctx.Fatal("NewDispatchingServer() failed: ", err)
+	}
+
+	name := server.Status().Endpoints[0].Name()
+	return name, func() {
+		server.Stop()
+		if path != "" {
+			os.RemoveAll(path)
+		}
+	}
+}
+
+func setupOrDie(be backend) (clientCtx *context.T, serverName string, cleanup func()) {
+	ctx, shutdown := v23.Init()
+	cp, sp := testutil.NewPrincipal("client"), testutil.NewPrincipal("server")
+
+	// Have the server principal bless the client principal as "client".
+	blessings, err := sp.Bless(cp.PublicKey(), sp.BlessingStore().Default(), "client", security.UnconstrainedUse())
+	if err != nil {
+		clientCtx.Fatal("sp.Bless() failed: ", err)
+	}
+	// Have the client present its "client" blessing when talking to the server.
+	if _, err := cp.BlessingStore().Set(blessings, "server"); err != nil {
+		clientCtx.Fatal("cp.BlessingStore().Set() failed: ", err)
+	}
+	// Have the client treat the server's public key as an authority on all
+	// blessings that match the pattern "server".
+	if err := cp.AddToRoots(blessings); err != nil {
+		clientCtx.Fatal("cp.AddToRoots() failed: ", err)
+	}
+
+	clientCtx, err = v23.WithPrincipal(ctx, cp)
+	if err != nil {
+		clientCtx.Fatal("v23.WithPrincipal() failed: ", err)
+	}
+	serverCtx, err := v23.WithPrincipal(ctx, sp)
+	if err != nil {
+		clientCtx.Fatal("v23.WithPrincipal() failed: ", err)
+	}
+
+	serverName, stopServer := newServer(serverCtx, be)
+	cleanup = func() {
+		stopServer()
+		shutdown()
+	}
+	return
+}
+
+////////////////////////////////////////
+// Test cases
+
+func TestCreateGkvStore(t *testing.T) {
+	testCreateHelper(t, gkvstore)
+}
+
+func TestCreateMemStore(t *testing.T) {
+	testCreateHelper(t, memstore)
+}
+
+func testCreateHelper(t *testing.T, be backend) {
+	ctx, serverName, cleanup := setupOrDie(be)
+	defer cleanup()
+
+	// Create a group with a default perms and no entries.
+	g := groups.GroupClient(naming.JoinAddressName(serverName, "grpA"))
+	if err := g.Create(ctx, nil, nil); err != nil {
+		t.Fatalf("Create failed: %v", err)
+	}
+	// Verify perms of created group.
+	perms := access.Permissions{}
+	for _, tag := range access.AllTypicalTags() {
+		perms.Add(security.BlessingPattern("server/client"), string(tag))
+	}
+	gotPermissions, wantPermissions := getPermsOrDie(t, ctx, g), perms
+	if !reflect.DeepEqual(gotPermissions, wantPermissions) {
+		t.Errorf("Permissions do not match: got %v, want %v", gotPermissions, wantPermissions)
+	}
+	// Verify entries of created group.
+	got, want := getEntriesOrDie(t, ctx, g), bpcSet()
+	if !entriesEqual(got, want) {
+		t.Errorf("Entries do not match: got %v, want %v", got, want)
+	}
+
+	// Creating same group again should fail, since the group already exists.
+	g = groups.GroupClient(naming.JoinAddressName(serverName, "grpA"))
+	if err := g.Create(ctx, nil, nil); verror.ErrorID(err) != verror.ErrExist.ID {
+		t.Fatalf("Create should have failed: %v", err)
+	}
+
+	// Create a group with perms and a few entries, including some redundant ones.
+	g = groups.GroupClient(naming.JoinAddressName(serverName, "grpB"))
+	perms = access.Permissions{}
+	// Allow Admin and Read so that we can call GetPermissions and Get.
+	for _, tag := range []access.Tag{access.Admin, access.Read} {
+		perms.Add(security.BlessingPattern("server/client"), string(tag))
+	}
+	if err := g.Create(ctx, perms, bpcSlice("foo", "bar", "foo")); err != nil {
+		t.Fatalf("Create failed: %v", err)
+	}
+	// Verify perms of created group.
+	gotPermissions, wantPermissions = getPermsOrDie(t, ctx, g), perms
+	if !reflect.DeepEqual(gotPermissions, wantPermissions) {
+		t.Errorf("Permissions do not match: got %v, want %v", gotPermissions, wantPermissions)
+	}
+	// Verify entries of created group.
+	got, want = getEntriesOrDie(t, ctx, g), bpcSet("foo", "bar")
+	if !entriesEqual(got, want) {
+		t.Errorf("Entries do not match: got %v, want %v", got, want)
+	}
+}
+
+func TestDeleteGkvStore(t *testing.T) {
+	testDeleteHelper(t, gkvstore)
+}
+
+func TestDeleteMemStore(t *testing.T) {
+	testDeleteHelper(t, memstore)
+}
+
+func testDeleteHelper(t *testing.T, be backend) {
+	ctx, serverName, cleanup := setupOrDie(be)
+	defer cleanup()
+
+	// Create a group with a default perms and no entries, check that we can
+	// delete it.
+	g := groups.GroupClient(naming.JoinAddressName(serverName, "grpA"))
+	if err := g.Create(ctx, nil, nil); err != nil {
+		t.Fatalf("Create failed: %v", err)
+	}
+	// Delete with bad version should fail.
+	if err := g.Delete(ctx, "20"); verror.ErrorID(err) != verror.ErrBadVersion.ID {
+		t.Fatalf("Delete should have failed with version error: %v", err)
+	}
+	// Delete with correct version should succeed.
+	version := getVersionOrDie(t, ctx, g)
+	if err := g.Delete(ctx, version); err != nil {
+		t.Fatalf("Delete failed: %v", err)
+	}
+	// Check that the group was actually deleted.
+	if _, _, err := g.Get(ctx, groups.GetRequest{}, ""); verror.ErrorID(err) != verror.ErrNoExist.ID {
+		t.Fatal("Group was not deleted")
+	}
+
+	// Create a group with several entries, check that we can delete it.
+	g = groups.GroupClient(naming.JoinAddressName(serverName, "grpB"))
+	if err := g.Create(ctx, nil, bpcSlice("foo", "bar", "foo")); err != nil {
+		t.Fatalf("Create failed: %v", err)
+	}
+	// Delete with empty version should succeed.
+	if err := g.Delete(ctx, ""); err != nil {
+		t.Fatalf("Delete failed: %v", err)
+	}
+	// Check that the group was actually deleted.
+	if _, _, err := g.Get(ctx, groups.GetRequest{}, ""); verror.ErrorID(err) != verror.ErrNoExist.ID {
+		t.Fatal("Group was not deleted")
+	}
+	// Check that Delete is idempotent.
+	if err := g.Delete(ctx, ""); err != nil {
+		t.Fatalf("Delete failed: %v", err)
+	}
+	// Check that we can recreate a group that was deleted.
+	if err := g.Create(ctx, nil, nil); err != nil {
+		t.Fatalf("Create failed: %v", err)
+	}
+
+	// Create a group with perms that disallow Delete(), check that Delete()
+	// fails.
+	g = groups.GroupClient(naming.JoinAddressName(serverName, "grpC"))
+	perms := access.Permissions{}
+	perms.Add(security.BlessingPattern("server/client"), string(access.Admin))
+	if err := g.Create(ctx, perms, nil); err != nil {
+		t.Fatalf("Create failed: %v", err)
+	}
+	// Delete should fail (no access).
+	if err := g.Delete(ctx, ""); verror.ErrorID(err) != verror.ErrNoAccess.ID {
+		t.Fatalf("Delete should have failed with access error: %v", err)
+	}
+}
+
+func TestPermsGkvStore(t *testing.T) {
+	testPermsHelper(t, gkvstore)
+}
+
+func TestPermsMemStore(t *testing.T) {
+	testPermsHelper(t, memstore)
+}
+
+func testPermsHelper(t *testing.T, be backend) {
+	ctx, serverName, cleanup := setupOrDie(be)
+	defer cleanup()
+
+	// Create a group with a default perms.
+	g := groups.GroupClient(naming.JoinAddressName(serverName, "grpA"))
+	if err := g.Create(ctx, nil, nil); err != nil {
+		t.Fatalf("Create failed: %v", err)
+	}
+
+	// Use "ac" so the code below can exactly match code in syncbase.
+	// TODO(sadovsky): All Vanadium {Set,Get}Permissions tests ought to share this
+	// test implementation.
+	ac := g
+
+	// Mirrors syncbase/v23/syncbase/testutil/layer.go.
+	myperms := access.Permissions{}
+	myperms.Add(security.BlessingPattern("server/client"), string(access.Admin))
+	// Demonstrate that myperms differs from the current perms.
+	if reflect.DeepEqual(myperms, getPermsOrDie(t, ctx, ac)) {
+		t.Fatalf("Permissions should not match: %v", myperms)
+	}
+
+	var permsBefore, permsAfter access.Permissions
+	var versionBefore, versionAfter string
+
+	getPermsAndVersionOrDie := func() (access.Permissions, string) {
+		perms, version, err := ac.GetPermissions(ctx)
+		if err != nil {
+			// Use Fatalf rather than t.Fatalf so we get a stack trace.
+			Fatalf(t, "GetPermissions failed: %v", err)
+		}
+		return perms, version
+	}
+
+	// SetPermissions with bad version should fail.
+	permsBefore, versionBefore = getPermsAndVersionOrDie()
+	if err := ac.SetPermissions(ctx, myperms, "20"); verror.ErrorID(err) != verror.ErrBadVersion.ID {
+		t.Fatalf("SetPermissions should have failed with version error: %v", err)
+	}
+	// Since SetPermissions failed, perms and version should not have changed.
+	permsAfter, versionAfter = getPermsAndVersionOrDie()
+	if !reflect.DeepEqual(permsAfter, permsBefore) {
+		t.Errorf("Perms do not match: got %v, want %v", permsAfter, permsBefore)
+	}
+	if versionAfter != versionBefore {
+		t.Errorf("Versions do not match: got %v, want %v", versionAfter, versionBefore)
+	}
+
+	// SetPermissions with correct version should succeed.
+	permsBefore, versionBefore = permsAfter, versionAfter
+	if err := ac.SetPermissions(ctx, myperms, versionBefore); err != nil {
+		t.Fatalf("SetPermissions failed: %v", err)
+	}
+	// Check that perms and version actually changed.
+	permsAfter, versionAfter = getPermsAndVersionOrDie()
+	if !reflect.DeepEqual(permsAfter, myperms) {
+		t.Errorf("Perms do not match: got %v, want %v", permsAfter, myperms)
+	}
+	if versionBefore == versionAfter {
+		t.Errorf("Versions should not match: %v", versionBefore)
+	}
+
+	// SetPermissions with empty version should succeed.
+	permsBefore, versionBefore = permsAfter, versionAfter
+	myperms.Add(security.BlessingPattern("server/client"), string(access.Read))
+	if err := ac.SetPermissions(ctx, myperms, ""); err != nil {
+		t.Fatalf("SetPermissions failed: %v", err)
+	}
+	// Check that perms and version actually changed.
+	permsAfter, versionAfter = getPermsAndVersionOrDie()
+	if !reflect.DeepEqual(permsAfter, myperms) {
+		t.Errorf("Perms do not match: got %v, want %v", permsAfter, myperms)
+	}
+	if versionBefore == versionAfter {
+		t.Errorf("Versions should not match: %v", versionBefore)
+	}
+
+	// SetPermissions with unchanged perms should succeed, and version should
+	// still change.
+	permsBefore, versionBefore = permsAfter, versionAfter
+	if err := ac.SetPermissions(ctx, myperms, ""); err != nil {
+		t.Fatalf("SetPermissions failed: %v", err)
+	}
+	// Check that perms did not change and version did change.
+	permsAfter, versionAfter = getPermsAndVersionOrDie()
+	if !reflect.DeepEqual(permsAfter, permsBefore) {
+		t.Errorf("Perms do not match: got %v, want %v", permsAfter, permsBefore)
+	}
+	if versionBefore == versionAfter {
+		t.Errorf("Versions should not match: %v", versionBefore)
+	}
+
+	// Take away our access. SetPermissions and GetPermissions should fail.
+	if err := ac.SetPermissions(ctx, access.Permissions{}, ""); err != nil {
+		t.Fatalf("SetPermissions failed: %v", err)
+	}
+	if _, _, err := ac.GetPermissions(ctx); verror.ErrorID(err) != verror.ErrNoAccess.ID {
+		t.Fatalf("GetPermissions should have failed with access error: %v", err)
+	}
+	if err := ac.SetPermissions(ctx, myperms, ""); verror.ErrorID(err) != verror.ErrNoAccess.ID {
+		t.Fatalf("SetPermissions should have failed with access error: %v", err)
+	}
+}
+
+func TestAddGkvStore(t *testing.T) {
+	testAddHelper(t, gkvstore)
+}
+
+func TestAddMemStore(t *testing.T) {
+	testAddHelper(t, memstore)
+}
+
+// testAddHelper tests mirror testRemoveHelper tests.
+func testAddHelper(t *testing.T, be backend) {
+	ctx, serverName, cleanup := setupOrDie(be)
+	defer cleanup()
+
+	// Create a group with a default perms and no entries.
+	g := groups.GroupClient(naming.JoinAddressName(serverName, "grpA"))
+	if err := g.Create(ctx, nil, nil); err != nil {
+		t.Fatalf("Create failed: %v", err)
+	}
+	// Verify entries of created group.
+	got, want := getEntriesOrDie(t, ctx, g), bpcSet()
+	if !entriesEqual(got, want) {
+		t.Errorf("Entries do not match: got %v, want %v", got, want)
+	}
+
+	var versionBefore, versionAfter string
+	versionBefore = getVersionOrDie(t, ctx, g)
+	// Add with bad version should fail.
+	if err := g.Add(ctx, bpc("foo"), "20"); verror.ErrorID(err) != verror.ErrBadVersion.ID {
+		t.Fatalf("Add should have failed with version error: %v", err)
+	}
+	// Version should not have changed.
+	versionAfter = getVersionOrDie(t, ctx, g)
+	if versionAfter != versionBefore {
+		t.Errorf("Versions do not match: got %v, want %v", versionAfter, versionBefore)
+	}
+
+	// Add an entry, verify it was added and the version changed.
+	versionBefore = versionAfter
+	if err := g.Add(ctx, bpc("foo"), versionBefore); err != nil {
+		t.Fatalf("Add failed: %v", err)
+	}
+	got, want = getEntriesOrDie(t, ctx, g), bpcSet("foo")
+	if !entriesEqual(got, want) {
+		t.Errorf("Entries do not match: got %v, want %v", got, want)
+	}
+	versionAfter = getVersionOrDie(t, ctx, g)
+	if versionBefore == versionAfter {
+		t.Errorf("Versions should not match: %v", versionBefore)
+	}
+
+	// Add another entry, verify it was added and the version changed.
+	versionBefore = versionAfter
+	// Add with empty version should succeed.
+	if err := g.Add(ctx, bpc("bar"), ""); err != nil {
+		t.Fatalf("Add failed: %v", err)
+	}
+	got, want = getEntriesOrDie(t, ctx, g), bpcSet("foo", "bar")
+	if !entriesEqual(got, want) {
+		t.Errorf("Entries do not match: got %v, want %v", got, want)
+	}
+	versionAfter = getVersionOrDie(t, ctx, g)
+	if versionBefore == versionAfter {
+		t.Errorf("Versions should not match: %v", versionBefore)
+	}
+
+	// Add "bar" again, verify entries are still ["foo", "bar"] and the version
+	// changed.
+	versionBefore = versionAfter
+	if err := g.Add(ctx, bpc("bar"), versionBefore); err != nil {
+		t.Fatalf("Add failed: %v", err)
+	}
+	got, want = getEntriesOrDie(t, ctx, g), bpcSet("foo", "bar")
+	if !entriesEqual(got, want) {
+		t.Errorf("Entries do not match: got %v, want %v", got, want)
+	}
+	versionAfter = getVersionOrDie(t, ctx, g)
+	if versionBefore == versionAfter {
+		t.Errorf("Versions should not match: %v", versionBefore)
+	}
+
+	// Create a group with perms that disallow Add(), check that Add() fails.
+	g = groups.GroupClient(naming.JoinAddressName(serverName, "grpB"))
+	perms := access.Permissions{}
+	perms.Add(security.BlessingPattern("server/client"), string(access.Admin))
+	if err := g.Create(ctx, perms, nil); err != nil {
+		t.Fatalf("Create failed: %v", err)
+	}
+	// Add should fail (no access).
+	if err := g.Add(ctx, bpc("foo"), ""); verror.ErrorID(err) != verror.ErrNoAccess.ID {
+		t.Fatalf("Add should have failed with access error: %v", err)
+	}
+}
+
+func TestRemoveGkvStore(t *testing.T) {
+	testRemoveHelper(t, gkvstore)
+}
+
+func TestRemoveMemStore(t *testing.T) {
+	testRemoveHelper(t, memstore)
+}
+
+// testRemoveHelper tests mirror testAddHelper tests.
+func testRemoveHelper(t *testing.T, be backend) {
+	ctx, serverName, cleanup := setupOrDie(be)
+	defer cleanup()
+
+	// Create a group with a default perms and two entries.
+	g := groups.GroupClient(naming.JoinAddressName(serverName, "grpA"))
+	if err := g.Create(ctx, nil, bpcSlice("foo", "bar")); err != nil {
+		t.Fatalf("Create failed: %v", err)
+	}
+	// Verify entries of created group.
+	got, want := getEntriesOrDie(t, ctx, g), bpcSet("foo", "bar")
+	if !entriesEqual(got, want) {
+		t.Errorf("Entries do not match: got %v, want %v", got, want)
+	}
+
+	var versionBefore, versionAfter string
+	versionBefore = getVersionOrDie(t, ctx, g)
+	// Remove with bad version should fail.
+	if err := g.Remove(ctx, bpc("foo"), "20"); verror.ErrorID(err) != verror.ErrBadVersion.ID {
+		t.Fatalf("Remove should have failed with version error: %v", err)
+	}
+	// Version should not have changed.
+	versionAfter = getVersionOrDie(t, ctx, g)
+	if versionAfter != versionBefore {
+		t.Errorf("Versions do not match: got %v, want %v", versionAfter, versionBefore)
+	}
+
+	// Remove an entry, verify it was removed and the version changed.
+	versionBefore = versionAfter
+	if err := g.Remove(ctx, bpc("foo"), versionBefore); err != nil {
+		t.Fatalf("Remove failed: %v", err)
+	}
+	got, want = getEntriesOrDie(t, ctx, g), bpcSet("bar")
+	if !entriesEqual(got, want) {
+		t.Errorf("Entries do not match: got %v, want %v", got, want)
+	}
+	versionAfter = getVersionOrDie(t, ctx, g)
+	if versionBefore == versionAfter {
+		t.Errorf("Versions should not match: %v", versionBefore)
+	}
+
+	// Remove another entry, verify it was removed and the version changed.
+	versionBefore = versionAfter
+	// Remove with empty version should succeed.
+	if err := g.Remove(ctx, bpc("bar"), ""); err != nil {
+		t.Fatalf("Remove failed: %v", err)
+	}
+	got, want = getEntriesOrDie(t, ctx, g), bpcSet()
+	if !entriesEqual(got, want) {
+		t.Errorf("Entries do not match: got %v, want %v", got, want)
+	}
+	versionAfter = getVersionOrDie(t, ctx, g)
+	if versionBefore == versionAfter {
+		t.Errorf("Versions should not match: %v", versionBefore)
+	}
+
+	// Remove "bar" again, verify entries are still [] and the version changed.
+	versionBefore = versionAfter
+	if err := g.Remove(ctx, bpc("bar"), versionBefore); err != nil {
+		t.Fatalf("Remove failed: %v", err)
+	}
+	got, want = getEntriesOrDie(t, ctx, g), bpcSet()
+	if !entriesEqual(got, want) {
+		t.Errorf("Entries do not match: got %v, want %v", got, want)
+	}
+	versionAfter = getVersionOrDie(t, ctx, g)
+	if versionBefore == versionAfter {
+		t.Errorf("Versions should not match: %v", versionBefore)
+	}
+
+	// Create a group with perms that disallow Remove(), check that Remove()
+	// fails.
+	g = groups.GroupClient(naming.JoinAddressName(serverName, "grpB"))
+	perms := access.Permissions{}
+	perms.Add(security.BlessingPattern("server/client"), string(access.Admin))
+	if err := g.Create(ctx, perms, bpcSlice("foo", "bar")); err != nil {
+		t.Fatalf("Create failed: %v", err)
+	}
+	// Remove should fail (no access).
+	if err := g.Remove(ctx, bpc("foo"), ""); verror.ErrorID(err) != verror.ErrNoAccess.ID {
+		t.Fatalf("Remove should have failed with access error: %v", err)
+	}
+}
+
+func TestGet(t *testing.T) {
+	// TODO(sadovsky): Implement.
+}
+
+func TestRest(t *testing.T) {
+	// TODO(sadovsky): Implement.
+}
diff --git a/services/groups/internal/server/types.vdl b/services/groups/internal/server/types.vdl
new file mode 100644
index 0000000..49e18b3
--- /dev/null
+++ b/services/groups/internal/server/types.vdl
@@ -0,0 +1,17 @@
+// 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 server
+
+import (
+	"v.io/v23/services/groups"
+	"v.io/v23/security/access"
+)
+
+// groupData represents the persistent state of a group. (The group name is
+// persisted as the store entry key.)
+type groupData struct {
+	Perms   access.Permissions
+	Entries set[groups.BlessingPatternChunk]
+}
diff --git a/services/groups/internal/server/types.vdl.go b/services/groups/internal/server/types.vdl.go
new file mode 100644
index 0000000..0cbc820
--- /dev/null
+++ b/services/groups/internal/server/types.vdl.go
@@ -0,0 +1,33 @@
+// 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 by the vanadium vdl tool.
+// Source: types.vdl
+
+package server
+
+import (
+	// VDL system imports
+	"v.io/v23/vdl"
+
+	// VDL user imports
+	"v.io/v23/security/access"
+	"v.io/v23/services/groups"
+)
+
+// groupData represents the persistent state of a group. (The group name is
+// persisted as the store entry key.)
+type groupData struct {
+	Perms   access.Permissions
+	Entries map[groups.BlessingPatternChunk]struct{}
+}
+
+func (groupData) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/services/groups/internal/server.groupData"`
+}) {
+}
+
+func init() {
+	vdl.Register((*groupData)(nil))
+}
diff --git a/services/groups/internal/store/gkv/store.go b/services/groups/internal/store/gkv/store.go
new file mode 100644
index 0000000..107aa33
--- /dev/null
+++ b/services/groups/internal/store/gkv/store.go
@@ -0,0 +1,196 @@
+// 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 gkv provides a simple implementation of server.Store that uses
+// gkvlite for persistence. It's meant as a stopgap solution until the Syncbase
+// storage engine is ready for consumption by other Vanadium modules. Since it's
+// a stopgap, it doesn't bother with entry-level locking.
+package gkv
+
+import (
+	"os"
+	"strconv"
+	"sync"
+
+	"github.com/steveyen/gkvlite"
+
+	"v.io/v23/vdl"
+	"v.io/v23/verror"
+	"v.io/v23/vom"
+	"v.io/x/ref/services/groups/internal/store"
+)
+
+const collectionName string = "c"
+
+type entry struct {
+	Value   interface{}
+	Version uint64
+}
+
+// TODO(sadovsky): Compaction.
+type gkv struct {
+	mu   sync.Mutex
+	err  error
+	file *os.File
+	kvst *gkvlite.Store
+	coll *gkvlite.Collection
+}
+
+var _ store.Store = (*gkv)(nil)
+
+func New(filename string) (store.Store, error) {
+	file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0600)
+	if err != nil {
+		return nil, convertError(err)
+	}
+	kvst, err := gkvlite.NewStore(file)
+	if err != nil {
+		file.Close()
+		return nil, convertError(err)
+	}
+	res := &gkv{file: file, kvst: kvst}
+	coll := kvst.GetCollection(collectionName)
+	// Create collection if needed.
+	if coll == nil {
+		coll = kvst.SetCollection(collectionName, nil)
+		if err := res.flush(); err != nil {
+			res.Close()
+			return nil, convertError(err)
+		}
+	}
+	res.coll = coll
+	return res, nil
+}
+
+func (st *gkv) Get(k string, v interface{}) (version string, err error) {
+	st.mu.Lock()
+	defer st.mu.Unlock()
+	if st.err != nil {
+		return "", convertError(st.err)
+	}
+	e, err := st.get(k)
+	if err != nil {
+		return "", err
+	}
+	if err := vdl.Convert(v, e.Value); err != nil {
+		return "", convertError(err)
+	}
+	return strconv.FormatUint(e.Version, 10), nil
+}
+
+func (st *gkv) Insert(k string, v interface{}) error {
+	st.mu.Lock()
+	defer st.mu.Unlock()
+	if st.err != nil {
+		return convertError(st.err)
+	}
+	if _, err := st.get(k); verror.ErrorID(err) != store.ErrUnknownKey.ID {
+		if err != nil {
+			return err
+		}
+		return verror.New(store.ErrKeyExists, nil, k)
+	}
+	return st.put(k, &entry{Value: v})
+}
+
+func (st *gkv) Update(k string, v interface{}, version string) error {
+	st.mu.Lock()
+	defer st.mu.Unlock()
+	if st.err != nil {
+		return convertError(st.err)
+	}
+	e, err := st.get(k)
+	if err != nil {
+		return err
+	}
+	if err := e.checkVersion(version); err != nil {
+		return err
+	}
+	return st.put(k, &entry{Value: v, Version: e.Version + 1})
+}
+
+func (st *gkv) Delete(k string, version string) error {
+	st.mu.Lock()
+	defer st.mu.Unlock()
+	if st.err != nil {
+		return convertError(st.err)
+	}
+	e, err := st.get(k)
+	if err != nil {
+		return err
+	}
+	if err := e.checkVersion(version); err != nil {
+		return err
+	}
+	return st.delete(k)
+}
+
+func (st *gkv) Close() error {
+	st.mu.Lock()
+	defer st.mu.Unlock()
+	if st.err != nil {
+		return convertError(st.err)
+	}
+	st.err = verror.New(verror.ErrCanceled, nil, "closed store")
+	st.kvst.Close()
+	return convertError(st.file.Close())
+}
+
+////////////////////////////////////////
+// Internal helpers
+
+// get, put, delete, and flush all assume st.mu is held.
+func (st *gkv) get(k string) (*entry, error) {
+	bytes, err := st.coll.Get([]byte(k))
+	if err != nil {
+		return nil, convertError(err)
+	}
+	if bytes == nil {
+		return nil, verror.New(store.ErrUnknownKey, nil, k)
+	}
+	e := &entry{}
+	if err := vom.Decode(bytes, e); err != nil {
+		return nil, convertError(err)
+	}
+	return e, nil
+}
+
+func (st *gkv) put(k string, e *entry) error {
+	bytes, err := vom.Encode(e)
+	if err != nil {
+		return convertError(err)
+	}
+	if err := st.coll.Set([]byte(k), bytes); err != nil {
+		return convertError(err)
+	}
+	return convertError(st.flush())
+}
+
+func (st *gkv) delete(k string) error {
+	if _, err := st.coll.Delete([]byte(k)); err != nil {
+		return convertError(err)
+	}
+	return convertError(st.flush())
+}
+
+func (st *gkv) flush() error {
+	if err := st.kvst.Flush(); err != nil {
+		return convertError(err)
+	}
+	// TODO(sadovsky): Better handling for the case where kvst.Flush() succeeds
+	// but file.Sync() fails. See discussion in v.io/c/11829.
+	return convertError(st.file.Sync())
+}
+
+func (e *entry) checkVersion(version string) error {
+	newVersion := strconv.FormatUint(e.Version, 10)
+	if version != newVersion {
+		return verror.NewErrBadVersion(nil)
+	}
+	return nil
+}
+
+func convertError(err error) error {
+	return verror.Convert(verror.IDAction{}, nil, err)
+}
diff --git a/services/groups/internal/store/leveldb/doc.go b/services/groups/internal/store/leveldb/doc.go
new file mode 100644
index 0000000..ebe6a3e
--- /dev/null
+++ b/services/groups/internal/store/leveldb/doc.go
@@ -0,0 +1,7 @@
+// 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 leveldb provides an implementation of the groups server
+// Store interface that uses the levelDB-based syncbase storage layer.
+package leveldb
diff --git a/services/groups/internal/store/leveldb/store.go b/services/groups/internal/store/leveldb/store.go
new file mode 100644
index 0000000..964883c
--- /dev/null
+++ b/services/groups/internal/store/leveldb/store.go
@@ -0,0 +1,136 @@
+// 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 leveldb
+
+package leveldb
+
+import (
+	"strconv"
+
+	istore "v.io/syncbase/x/ref/services/syncbase/store"
+	"v.io/syncbase/x/ref/services/syncbase/store/leveldb"
+	"v.io/v23/context"
+	"v.io/v23/vdl"
+	"v.io/v23/verror"
+	"v.io/v23/vom"
+	"v.io/x/ref/services/groups/internal/store"
+)
+
+type entry struct {
+	Value   interface{}
+	Version uint64
+}
+
+type T struct {
+	db istore.Store
+}
+
+var _ store.Store = (*T)(nil)
+
+// Open opens a groups server store located at the given path,
+// creating it if it doesn't exist.
+func Open(path string) (store.Store, error) {
+	var _ context.T
+	db, err := leveldb.Open(path, leveldb.OpenOptions{CreateIfMissing: true, ErrorIfExists: false})
+	if err != nil {
+		return nil, convertError(err)
+	}
+	return &T{db: db}, nil
+}
+
+func (st *T) Get(key string, valbuf interface{}) (version string, err error) {
+	e, err := get(st.db, key)
+	if err != nil {
+		return "", err
+	}
+	if err := vdl.Convert(valbuf, e.Value); err != nil {
+		return "", convertError(err)
+	}
+	return strconv.FormatUint(e.Version, 10), nil
+}
+
+func (st *T) Insert(key string, value interface{}) error {
+	return istore.RunInTransaction(st.db, func(tx istore.Transaction) error {
+		if _, err := get(tx, key); verror.ErrorID(err) != store.ErrUnknownKey.ID {
+			if err != nil {
+				return err
+			}
+			return verror.New(store.ErrKeyExists, nil, key)
+		}
+		return put(tx, key, &entry{Value: value})
+	})
+}
+
+func (st *T) Update(key string, value interface{}, version string) error {
+	return istore.RunInTransaction(st.db, func(tx istore.Transaction) error {
+		e, err := get(tx, key)
+		if err != nil {
+			return err
+		}
+		if err := e.checkVersion(version); err != nil {
+			return err
+		}
+		return put(tx, key, &entry{Value: value, Version: e.Version + 1})
+	})
+}
+
+func (st *T) Delete(key string, version string) error {
+	return istore.RunInTransaction(st.db, func(tx istore.Transaction) error {
+		e, err := get(tx, key)
+		if err != nil {
+			return err
+		}
+		if err := e.checkVersion(version); err != nil {
+			return err
+		}
+		return delete(tx, key)
+	})
+}
+
+func (st *T) Close() error {
+	return convertError(st.db.Close())
+}
+
+func get(st istore.StoreReader, key string) (*entry, error) {
+	bytes, _ := st.Get([]byte(key), nil)
+	if bytes == nil {
+		return nil, verror.New(store.ErrUnknownKey, nil, key)
+	}
+	e := &entry{}
+	if err := vom.Decode(bytes, e); err != nil {
+		return nil, convertError(err)
+	}
+	return e, nil
+}
+
+func put(stw istore.StoreWriter, key string, e *entry) error {
+	bytes, err := vom.Encode(e)
+	if err != nil {
+		return convertError(err)
+	}
+	if err := stw.Put([]byte(key), bytes); err != nil {
+		return convertError(err)
+	}
+	return nil
+}
+
+func delete(stw istore.StoreWriter, key string) error {
+	if err := stw.Delete([]byte(key)); err != nil {
+		return convertError(err)
+	}
+	return nil
+}
+
+func (e *entry) checkVersion(version string) error {
+	newVersion := strconv.FormatUint(e.Version, 10)
+	if version != newVersion {
+		return verror.NewErrBadVersion(nil)
+	}
+	return nil
+}
+
+func convertError(err error) error {
+	return verror.Convert(verror.IDAction{}, nil, err)
+}
diff --git a/services/groups/internal/store/leveldb/store_stub.go b/services/groups/internal/store/leveldb/store_stub.go
new file mode 100644
index 0000000..47815d3
--- /dev/null
+++ b/services/groups/internal/store/leveldb/store_stub.go
@@ -0,0 +1,17 @@
+// 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 !leveldb
+
+package leveldb
+
+import (
+	"fmt"
+
+	"v.io/x/ref/services/groups/internal/store"
+)
+
+func Open(path string) (store.Store, error) {
+	return nil, fmt.Errorf("use the 'leveldb' build tag to build groupsd with leveldb storage engine")
+}
diff --git a/services/groups/internal/store/mem/store.go b/services/groups/internal/store/mem/store.go
new file mode 100644
index 0000000..f8ddd49
--- /dev/null
+++ b/services/groups/internal/store/mem/store.go
@@ -0,0 +1,122 @@
+// 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 mem provides a simple, in-memory implementation of
+// server.Store. Since it's a prototype implementation, it doesn't
+// bother with entry-level locking.
+package mem
+
+import (
+	"strconv"
+	"sync"
+
+	"v.io/v23/vdl"
+	"v.io/v23/verror"
+	"v.io/x/ref/services/groups/internal/store"
+)
+
+type entry struct {
+	Value   interface{}
+	Version uint64
+}
+
+type memstore struct {
+	mu   sync.Mutex
+	err  error
+	data map[string]*entry
+}
+
+var _ store.Store = (*memstore)(nil)
+
+func New() store.Store {
+	return &memstore{data: map[string]*entry{}}
+}
+
+func (st *memstore) Get(k string, v interface{}) (version string, err error) {
+	st.mu.Lock()
+	defer st.mu.Unlock()
+	if st.err != nil {
+		return "", convertError(st.err)
+	}
+	e, ok := st.data[k]
+	if !ok {
+		return "", verror.New(store.ErrUnknownKey, nil, k)
+	}
+	if err := vdl.Convert(v, e.Value); err != nil {
+		return "", convertError(err)
+	}
+	return strconv.FormatUint(e.Version, 10), nil
+}
+
+func (st *memstore) Insert(k string, v interface{}) error {
+	st.mu.Lock()
+	defer st.mu.Unlock()
+	if st.err != nil {
+		return convertError(st.err)
+	}
+	if _, ok := st.data[k]; ok {
+		return verror.New(store.ErrKeyExists, nil, k)
+	}
+	st.data[k] = &entry{Value: v}
+	return nil
+}
+
+func (st *memstore) Update(k string, v interface{}, version string) error {
+	st.mu.Lock()
+	defer st.mu.Unlock()
+	if st.err != nil {
+		return convertError(st.err)
+	}
+	e, ok := st.data[k]
+	if !ok {
+		return verror.New(store.ErrUnknownKey, nil, k)
+	}
+	if err := e.checkVersion(version); err != nil {
+		return err
+	}
+	st.data[k] = &entry{Value: v, Version: e.Version + 1}
+	return nil
+}
+
+func (st *memstore) Delete(k string, version string) error {
+	st.mu.Lock()
+	defer st.mu.Unlock()
+	if st.err != nil {
+		return convertError(st.err)
+	}
+	e, ok := st.data[k]
+	if !ok {
+		return verror.New(store.ErrUnknownKey, nil, k)
+	}
+	if err := e.checkVersion(version); err != nil {
+		return err
+	}
+	delete(st.data, k)
+	return nil
+}
+
+func (st *memstore) Close() error {
+	st.mu.Lock()
+	defer st.mu.Unlock()
+	if st.err != nil {
+		return convertError(st.err)
+	}
+	st.err = verror.New(verror.ErrCanceled, nil, "closed store")
+	return nil
+}
+
+////////////////////////////////////////
+// Internal helpers
+
+func (e *entry) checkVersion(version string) error {
+	newVersion := strconv.FormatUint(e.Version, 10)
+	if version != newVersion {
+		return verror.NewErrBadVersion(nil)
+	}
+	return nil
+}
+
+func convertError(err error) error {
+	return verror.Convert(verror.IDAction{}, nil, err)
+}
diff --git a/services/groups/internal/store/model.go b/services/groups/internal/store/model.go
new file mode 100644
index 0000000..6f51152
--- /dev/null
+++ b/services/groups/internal/store/model.go
@@ -0,0 +1,35 @@
+// 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 store
+
+// Store is a key-value store that uses versions for optimistic concurrency
+// control. The versions passed to Update and Delete must come from Get. If in
+// the meantime some client has called Update or Delete on the same key, the
+// version will be stale and the method call will fail.
+//
+// Note, this API disallows empty versions to simplify implementation. The group
+// server is the only client of this API and always specifies versions.
+type Store interface {
+	// Get returns the value for the given key (decoding into v).
+	// Fails if the given key is unknown (ErrUnknownKey).
+	Get(k string, v interface{}) (version string, err error)
+
+	// Insert writes the given value for the given key.
+	// Fails if an entry already exists for the given key (ErrKeyExists).
+	Insert(k string, v interface{}) error
+
+	// Update writes the given value for the given key.
+	// Fails if the given key is unknown (ErrUnknownKey).
+	// Fails if version doesn't match (ErrBadVersion).
+	Update(k string, v interface{}, version string) error
+
+	// Delete deletes the entry for the given key.
+	// Fails if the given key is unknown (ErrUnknownKey).
+	// Fails if version doesn't match (ErrBadVersion).
+	Delete(k string, version string) error
+
+	// Close closes the store. All subsequent method calls will fail.
+	Close() error
+}
diff --git a/services/groups/internal/store/model.vdl b/services/groups/internal/store/model.vdl
new file mode 100644
index 0000000..a6acab0
--- /dev/null
+++ b/services/groups/internal/store/model.vdl
@@ -0,0 +1,13 @@
+// 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 store
+
+error (
+	// KeyExists means the given key already exists in the store.
+	KeyExists() {"en":"Key exists{:_}"}
+
+	// UnknownKey means the given key does not exist in the store.
+	UnknownKey() {"en":"Unknown key{:_}"}
+)
diff --git a/services/groups/internal/store/model.vdl.go b/services/groups/internal/store/model.vdl.go
new file mode 100644
index 0000000..aa66235
--- /dev/null
+++ b/services/groups/internal/store/model.vdl.go
@@ -0,0 +1,37 @@
+// 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 by the vanadium vdl tool.
+// Source: model.vdl
+
+package store
+
+import (
+	// VDL system imports
+	"v.io/v23/context"
+	"v.io/v23/i18n"
+	"v.io/v23/verror"
+)
+
+var (
+	// KeyExists means the given key already exists in the store.
+	ErrKeyExists = verror.Register("v.io/x/ref/services/groups/internal/store.KeyExists", verror.NoRetry, "{1:}{2:} Key exists{:_}")
+	// UnknownKey means the given key does not exist in the store.
+	ErrUnknownKey = verror.Register("v.io/x/ref/services/groups/internal/store.UnknownKey", verror.NoRetry, "{1:}{2:} Unknown key{:_}")
+)
+
+func init() {
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrKeyExists.ID), "{1:}{2:} Key exists{:_}")
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrUnknownKey.ID), "{1:}{2:} Unknown key{:_}")
+}
+
+// NewErrKeyExists returns an error with the ErrKeyExists ID.
+func NewErrKeyExists(ctx *context.T) error {
+	return verror.New(ErrKeyExists, ctx)
+}
+
+// NewErrUnknownKey returns an error with the ErrUnknownKey ID.
+func NewErrUnknownKey(ctx *context.T) error {
+	return verror.New(ErrUnknownKey, ctx)
+}
diff --git a/services/identity/README.md b/services/identity/README.md
new file mode 100644
index 0000000..37f5e2c
--- /dev/null
+++ b/services/identity/README.md
@@ -0,0 +1,6 @@
+# Vanadium Identity Service
+
+This package and its sub-packages implement the identity service
+at https://dev.v.io/auth.
+
+The design is described in https://v.io/designdocs/identity-service.html
diff --git a/services/identity/const.go b/services/identity/const.go
new file mode 100644
index 0000000..01776c3
--- /dev/null
+++ b/services/identity/const.go
@@ -0,0 +1,13 @@
+// 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 identity
+
+import (
+	"v.io/x/ref/services/identity/internal/oauth"
+)
+
+// This file contains constants that need to be exported to users of the identity service.
+
+const SeekBlessingsRoute = oauth.SeekBlessingsRoute
diff --git a/services/identity/identity.vdl b/services/identity/identity.vdl
new file mode 100644
index 0000000..978fbac
--- /dev/null
+++ b/services/identity/identity.vdl
@@ -0,0 +1,47 @@
+// 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 identity defines interfaces for Vanadium identity providers.
+package identity
+
+import "v.io/v23/security"
+
+// OAuthBlesser exchanges OAuth access tokens for
+// an email address from an OAuth-based identity provider and uses the email
+// address obtained to bless the client.
+//
+// OAuth is described in RFC 6749 (http://tools.ietf.org/html/rfc6749),
+// though the Google implementation also has informative documentation at
+// https://developers.google.com/accounts/docs/OAuth2
+//
+// WARNING: There is no binding between the channel over which the access
+// token was obtained (typically https) and the channel used to make the RPC
+// (a vanadium virtual circuit). Thus, if Mallory possesses the access token
+// associated with Alice's account she may be able to obtain a blessing with
+// Alice's name on it.
+//
+// TODO(ataly): Get rid of this service once all clients have been
+// switched to use the HTTP OAuthBlessingHandler service.
+type OAuthBlesser interface {
+  // BlessUsingAccessToken uses the provided access token to obtain the email
+  // address and returns a blessing along with the email address.
+  BlessUsingAccessToken(token string) (blessing security.WireBlessings, email string | error)
+  BlessUsingAccessTokenWithCaveats(token string, caveats []security.Caveat) (blessing security.WireBlessings, email string | error)
+}
+
+// MacaroonBlesser returns a blessing given the provided macaroon string.
+type MacaroonBlesser interface {
+  // Bless uses the provided macaroon (which contains email and caveats)
+  // to return a blessing for the client.
+  Bless(macaroon string) (blessing security.WireBlessings | error)
+}
+
+// BlessingRootResponse is the struct representing the JSON response provided
+// by the "blessing-root" route of the identity service.
+type BlessingRootResponse struct {
+  // Names of the blessings.
+  Names []string
+  // Base64 der-encoded public key.
+  PublicKey string
+}
diff --git a/services/identity/identity.vdl.go b/services/identity/identity.vdl.go
new file mode 100644
index 0000000..8aa9da2
--- /dev/null
+++ b/services/identity/identity.vdl.go
@@ -0,0 +1,309 @@
+// 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 by the vanadium vdl tool.
+// Source: identity.vdl
+
+// Package identity defines interfaces for Vanadium identity providers.
+package identity
+
+import (
+	// VDL system imports
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/vdl"
+
+	// VDL user imports
+	"v.io/v23/security"
+)
+
+// BlessingRootResponse is the struct representing the JSON response provided
+// by the "blessing-root" route of the identity service.
+type BlessingRootResponse struct {
+	// Names of the blessings.
+	Names []string
+	// Base64 der-encoded public key.
+	PublicKey string
+}
+
+func (BlessingRootResponse) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/services/identity.BlessingRootResponse"`
+}) {
+}
+
+func init() {
+	vdl.Register((*BlessingRootResponse)(nil))
+}
+
+// OAuthBlesserClientMethods is the client interface
+// containing OAuthBlesser methods.
+//
+// OAuthBlesser exchanges OAuth access tokens for
+// an email address from an OAuth-based identity provider and uses the email
+// address obtained to bless the client.
+//
+// OAuth is described in RFC 6749 (http://tools.ietf.org/html/rfc6749),
+// though the Google implementation also has informative documentation at
+// https://developers.google.com/accounts/docs/OAuth2
+//
+// WARNING: There is no binding between the channel over which the access
+// token was obtained (typically https) and the channel used to make the RPC
+// (a vanadium virtual circuit). Thus, if Mallory possesses the access token
+// associated with Alice's account she may be able to obtain a blessing with
+// Alice's name on it.
+//
+// TODO(ataly): Get rid of this service once all clients have been
+// switched to use the HTTP OAuthBlessingHandler service.
+type OAuthBlesserClientMethods interface {
+	// BlessUsingAccessToken uses the provided access token to obtain the email
+	// address and returns a blessing along with the email address.
+	BlessUsingAccessToken(ctx *context.T, token string, opts ...rpc.CallOpt) (blessing security.Blessings, email string, err error)
+	BlessUsingAccessTokenWithCaveats(ctx *context.T, token string, caveats []security.Caveat, opts ...rpc.CallOpt) (blessing security.Blessings, email string, err error)
+}
+
+// OAuthBlesserClientStub adds universal methods to OAuthBlesserClientMethods.
+type OAuthBlesserClientStub interface {
+	OAuthBlesserClientMethods
+	rpc.UniversalServiceMethods
+}
+
+// OAuthBlesserClient returns a client stub for OAuthBlesser.
+func OAuthBlesserClient(name string) OAuthBlesserClientStub {
+	return implOAuthBlesserClientStub{name}
+}
+
+type implOAuthBlesserClientStub struct {
+	name string
+}
+
+func (c implOAuthBlesserClientStub) BlessUsingAccessToken(ctx *context.T, i0 string, opts ...rpc.CallOpt) (o0 security.Blessings, o1 string, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "BlessUsingAccessToken", []interface{}{i0}, []interface{}{&o0, &o1}, opts...)
+	return
+}
+
+func (c implOAuthBlesserClientStub) BlessUsingAccessTokenWithCaveats(ctx *context.T, i0 string, i1 []security.Caveat, opts ...rpc.CallOpt) (o0 security.Blessings, o1 string, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "BlessUsingAccessTokenWithCaveats", []interface{}{i0, i1}, []interface{}{&o0, &o1}, opts...)
+	return
+}
+
+// OAuthBlesserServerMethods is the interface a server writer
+// implements for OAuthBlesser.
+//
+// OAuthBlesser exchanges OAuth access tokens for
+// an email address from an OAuth-based identity provider and uses the email
+// address obtained to bless the client.
+//
+// OAuth is described in RFC 6749 (http://tools.ietf.org/html/rfc6749),
+// though the Google implementation also has informative documentation at
+// https://developers.google.com/accounts/docs/OAuth2
+//
+// WARNING: There is no binding between the channel over which the access
+// token was obtained (typically https) and the channel used to make the RPC
+// (a vanadium virtual circuit). Thus, if Mallory possesses the access token
+// associated with Alice's account she may be able to obtain a blessing with
+// Alice's name on it.
+//
+// TODO(ataly): Get rid of this service once all clients have been
+// switched to use the HTTP OAuthBlessingHandler service.
+type OAuthBlesserServerMethods interface {
+	// BlessUsingAccessToken uses the provided access token to obtain the email
+	// address and returns a blessing along with the email address.
+	BlessUsingAccessToken(ctx *context.T, call rpc.ServerCall, token string) (blessing security.Blessings, email string, err error)
+	BlessUsingAccessTokenWithCaveats(ctx *context.T, call rpc.ServerCall, token string, caveats []security.Caveat) (blessing security.Blessings, email string, err error)
+}
+
+// OAuthBlesserServerStubMethods is the server interface containing
+// OAuthBlesser methods, as expected by rpc.Server.
+// There is no difference between this interface and OAuthBlesserServerMethods
+// since there are no streaming methods.
+type OAuthBlesserServerStubMethods OAuthBlesserServerMethods
+
+// OAuthBlesserServerStub adds universal methods to OAuthBlesserServerStubMethods.
+type OAuthBlesserServerStub interface {
+	OAuthBlesserServerStubMethods
+	// Describe the OAuthBlesser interfaces.
+	Describe__() []rpc.InterfaceDesc
+}
+
+// OAuthBlesserServer returns a server stub for OAuthBlesser.
+// It converts an implementation of OAuthBlesserServerMethods into
+// an object that may be used by rpc.Server.
+func OAuthBlesserServer(impl OAuthBlesserServerMethods) OAuthBlesserServerStub {
+	stub := implOAuthBlesserServerStub{
+		impl: impl,
+	}
+	// Initialize GlobState; always check the stub itself first, to handle the
+	// case where the user has the Glob method defined in their VDL source.
+	if gs := rpc.NewGlobState(stub); gs != nil {
+		stub.gs = gs
+	} else if gs := rpc.NewGlobState(impl); gs != nil {
+		stub.gs = gs
+	}
+	return stub
+}
+
+type implOAuthBlesserServerStub struct {
+	impl OAuthBlesserServerMethods
+	gs   *rpc.GlobState
+}
+
+func (s implOAuthBlesserServerStub) BlessUsingAccessToken(ctx *context.T, call rpc.ServerCall, i0 string) (security.Blessings, string, error) {
+	return s.impl.BlessUsingAccessToken(ctx, call, i0)
+}
+
+func (s implOAuthBlesserServerStub) BlessUsingAccessTokenWithCaveats(ctx *context.T, call rpc.ServerCall, i0 string, i1 []security.Caveat) (security.Blessings, string, error) {
+	return s.impl.BlessUsingAccessTokenWithCaveats(ctx, call, i0, i1)
+}
+
+func (s implOAuthBlesserServerStub) Globber() *rpc.GlobState {
+	return s.gs
+}
+
+func (s implOAuthBlesserServerStub) Describe__() []rpc.InterfaceDesc {
+	return []rpc.InterfaceDesc{OAuthBlesserDesc}
+}
+
+// OAuthBlesserDesc describes the OAuthBlesser interface.
+var OAuthBlesserDesc rpc.InterfaceDesc = descOAuthBlesser
+
+// descOAuthBlesser hides the desc to keep godoc clean.
+var descOAuthBlesser = rpc.InterfaceDesc{
+	Name:    "OAuthBlesser",
+	PkgPath: "v.io/x/ref/services/identity",
+	Doc:     "// OAuthBlesser exchanges OAuth access tokens for\n// an email address from an OAuth-based identity provider and uses the email\n// address obtained to bless the client.\n//\n// OAuth is described in RFC 6749 (http://tools.ietf.org/html/rfc6749),\n// though the Google implementation also has informative documentation at\n// https://developers.google.com/accounts/docs/OAuth2\n//\n// WARNING: There is no binding between the channel over which the access\n// token was obtained (typically https) and the channel used to make the RPC\n// (a vanadium virtual circuit). Thus, if Mallory possesses the access token\n// associated with Alice's account she may be able to obtain a blessing with\n// Alice's name on it.\n//\n// TODO(ataly): Get rid of this service once all clients have been\n// switched to use the HTTP OAuthBlessingHandler service.",
+	Methods: []rpc.MethodDesc{
+		{
+			Name: "BlessUsingAccessToken",
+			Doc:  "// BlessUsingAccessToken uses the provided access token to obtain the email\n// address and returns a blessing along with the email address.",
+			InArgs: []rpc.ArgDesc{
+				{"token", ``}, // string
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"blessing", ``}, // security.Blessings
+				{"email", ``},    // string
+			},
+		},
+		{
+			Name: "BlessUsingAccessTokenWithCaveats",
+			InArgs: []rpc.ArgDesc{
+				{"token", ``},   // string
+				{"caveats", ``}, // []security.Caveat
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"blessing", ``}, // security.Blessings
+				{"email", ``},    // string
+			},
+		},
+	},
+}
+
+// MacaroonBlesserClientMethods is the client interface
+// containing MacaroonBlesser methods.
+//
+// MacaroonBlesser returns a blessing given the provided macaroon string.
+type MacaroonBlesserClientMethods interface {
+	// Bless uses the provided macaroon (which contains email and caveats)
+	// to return a blessing for the client.
+	Bless(ctx *context.T, macaroon string, opts ...rpc.CallOpt) (blessing security.Blessings, err error)
+}
+
+// MacaroonBlesserClientStub adds universal methods to MacaroonBlesserClientMethods.
+type MacaroonBlesserClientStub interface {
+	MacaroonBlesserClientMethods
+	rpc.UniversalServiceMethods
+}
+
+// MacaroonBlesserClient returns a client stub for MacaroonBlesser.
+func MacaroonBlesserClient(name string) MacaroonBlesserClientStub {
+	return implMacaroonBlesserClientStub{name}
+}
+
+type implMacaroonBlesserClientStub struct {
+	name string
+}
+
+func (c implMacaroonBlesserClientStub) Bless(ctx *context.T, i0 string, opts ...rpc.CallOpt) (o0 security.Blessings, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "Bless", []interface{}{i0}, []interface{}{&o0}, opts...)
+	return
+}
+
+// MacaroonBlesserServerMethods is the interface a server writer
+// implements for MacaroonBlesser.
+//
+// MacaroonBlesser returns a blessing given the provided macaroon string.
+type MacaroonBlesserServerMethods interface {
+	// Bless uses the provided macaroon (which contains email and caveats)
+	// to return a blessing for the client.
+	Bless(ctx *context.T, call rpc.ServerCall, macaroon string) (blessing security.Blessings, err error)
+}
+
+// MacaroonBlesserServerStubMethods is the server interface containing
+// MacaroonBlesser methods, as expected by rpc.Server.
+// There is no difference between this interface and MacaroonBlesserServerMethods
+// since there are no streaming methods.
+type MacaroonBlesserServerStubMethods MacaroonBlesserServerMethods
+
+// MacaroonBlesserServerStub adds universal methods to MacaroonBlesserServerStubMethods.
+type MacaroonBlesserServerStub interface {
+	MacaroonBlesserServerStubMethods
+	// Describe the MacaroonBlesser interfaces.
+	Describe__() []rpc.InterfaceDesc
+}
+
+// MacaroonBlesserServer returns a server stub for MacaroonBlesser.
+// It converts an implementation of MacaroonBlesserServerMethods into
+// an object that may be used by rpc.Server.
+func MacaroonBlesserServer(impl MacaroonBlesserServerMethods) MacaroonBlesserServerStub {
+	stub := implMacaroonBlesserServerStub{
+		impl: impl,
+	}
+	// Initialize GlobState; always check the stub itself first, to handle the
+	// case where the user has the Glob method defined in their VDL source.
+	if gs := rpc.NewGlobState(stub); gs != nil {
+		stub.gs = gs
+	} else if gs := rpc.NewGlobState(impl); gs != nil {
+		stub.gs = gs
+	}
+	return stub
+}
+
+type implMacaroonBlesserServerStub struct {
+	impl MacaroonBlesserServerMethods
+	gs   *rpc.GlobState
+}
+
+func (s implMacaroonBlesserServerStub) Bless(ctx *context.T, call rpc.ServerCall, i0 string) (security.Blessings, error) {
+	return s.impl.Bless(ctx, call, i0)
+}
+
+func (s implMacaroonBlesserServerStub) Globber() *rpc.GlobState {
+	return s.gs
+}
+
+func (s implMacaroonBlesserServerStub) Describe__() []rpc.InterfaceDesc {
+	return []rpc.InterfaceDesc{MacaroonBlesserDesc}
+}
+
+// MacaroonBlesserDesc describes the MacaroonBlesser interface.
+var MacaroonBlesserDesc rpc.InterfaceDesc = descMacaroonBlesser
+
+// descMacaroonBlesser hides the desc to keep godoc clean.
+var descMacaroonBlesser = rpc.InterfaceDesc{
+	Name:    "MacaroonBlesser",
+	PkgPath: "v.io/x/ref/services/identity",
+	Doc:     "// MacaroonBlesser returns a blessing given the provided macaroon string.",
+	Methods: []rpc.MethodDesc{
+		{
+			Name: "Bless",
+			Doc:  "// Bless uses the provided macaroon (which contains email and caveats)\n// to return a blessing for the client.",
+			InArgs: []rpc.ArgDesc{
+				{"macaroon", ``}, // string
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"blessing", ``}, // security.Blessings
+			},
+		},
+	},
+}
diff --git a/services/identity/identityd/doc.go b/services/identity/identityd/doc.go
new file mode 100644
index 0000000..2ecc2a2
--- /dev/null
+++ b/services/identity/identityd/doc.go
@@ -0,0 +1,116 @@
+// 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
+
+/*
+Command identityd runs a daemon HTTP server that uses OAuth to create
+security.Blessings objects.
+
+Starts an HTTP server that brokers blessings after authenticating through OAuth.
+
+To generate TLS certificates so the HTTP server can use SSL:
+  go run $(go list -f {{.Dir}} "crypto/tls")/generate_cert.go --host <IP address>
+
+To use Google as an OAuth provider the -google-config-* flags must be set to
+point to the a JSON file obtained after registering the application with the
+Google Developer Console at https://cloud.google.com/console
+
+More details on Google OAuth at:
+  https://developers.google.com/accounts/docs/OAuth2Login
+
+More details on the design of identityd at:
+  https://v.io/designdocs/identity-service.html
+
+Usage:
+   identityd [flags]
+
+The identityd flags are:
+ -assets-prefix=
+   Host serving the web assets for the identity server.
+ -external-http-addr=
+   External address on which the HTTP server listens on.  If none is provided
+   the server will only listen on -http-addr.
+ -google-config-android=
+   Path to the JSON-encoded OAuth client configuration for Android applications
+   that obtain blessings from this server (via the
+   OAuthBlesser.BlessUsingAccessToken RPC) from this server.
+ -google-config-chrome=
+   Path to the JSON-encoded OAuth client configuration for Chrome browser
+   applications that obtain blessings from this server (via the
+   OAuthBlesser.BlessUsingAccessToken RPC) from this server.
+ -google-config-web=
+   Path to JSON-encoded OAuth client configuration for the web application that
+   renders the audit log for blessings provided by this provider.
+ -http-addr=localhost:8125
+   Address on which the HTTP server listens on.
+ -mount-prefix=identity
+   Mount name prefix to use.  May be rooted.
+ -remote-signer-blessing-dir=
+   Path to the blessings to use with the remote signer. Use the empty string to
+   disable the remote signer.
+ -sql-config=
+   Path to file containing a json object of the following form:
+      {
+       "dataSourceName": "[username[:password]@][protocol[(address)]]/dbname", (the connection string required by go-sql-driver)
+       "tlsServerName": "serverName", (the domain name of the sql server for ssl)
+       "rootCertPath": "/path/server-ca.pem", (the root certificate of the sql server for ssl)
+       "clientCertPath": "/path/client-cert.pem", (the client certificate for ssl)
+       "clientKeyPath": "/path/client-key.pem" (the client private key for ssl)
+      }
+ -tls-config=
+   Comma-separated list of TLS certificate and private key files, in that order.
+   This must be provided.
+
+The global flags are:
+ -alsologtostderr=true
+   log to standard error as well as files
+ -log_backtrace_at=:0
+   when logging hits line file:N, emit a stack trace
+ -log_dir=
+   if non-empty, write log files to this directory
+ -logtostderr=false
+   log to standard error instead of files
+ -max_stack_buf_size=4292608
+   max size in bytes of the buffer to use for logging stack traces
+ -metadata=<just specify -metadata to activate>
+   Displays metadata for the program and exits.
+ -stderrthreshold=2
+   logs at or above this threshold go to stderr
+ -v=0
+   log level for V logs
+ -v23.credentials=
+   directory to use for storing security credentials
+ -v23.i18n-catalogue=
+   18n catalogue files to load, comma separated
+ -v23.namespace.root=[/(dev.v.io/role/vprod/service/mounttabled)@ns.dev.v.io:8101]
+   local namespace root; can be repeated to provided multiple roots
+ -v23.permissions.file=map[]
+   specify a perms file as <name>:<permsfile>
+ -v23.permissions.literal=
+   explicitly specify the runtime perms as a JSON-encoded access.Permissions.
+   Overrides all --v23.permissions.file flags.
+ -v23.proxy=
+   object name of proxy service to use to export services across network
+   boundaries
+ -v23.tcp.address=
+   address to listen on
+ -v23.tcp.protocol=wsh
+   protocol to listen with
+ -v23.vtrace.cache-size=1024
+   The number of vtrace traces to store in memory.
+ -v23.vtrace.collect-regexp=
+   Spans and annotations that match this regular expression will trigger trace
+   collection.
+ -v23.vtrace.dump-on-shutdown=true
+   If true, dump all stored traces on runtime shutdown.
+ -v23.vtrace.sample-rate=0
+   Rate (from 0.0 to 1.0) to sample vtrace traces.
+ -vmodule=
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
+*/
+package main
diff --git a/services/identity/identityd/identityd_v23_test.go b/services/identity/identityd/identityd_v23_test.go
new file mode 100644
index 0000000..474c7b2
--- /dev/null
+++ b/services/identity/identityd/identityd_v23_test.go
@@ -0,0 +1,93 @@
+// 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_test
+
+import (
+	"crypto/tls"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"net/http/cookiejar"
+	"strings"
+	"time"
+
+	"v.io/x/ref/test/v23tests"
+)
+
+//go:generate v23 test generate .
+
+const urlRE = "^(https://.*)$"
+
+func seekBlessings(i *v23tests.T, principal *v23tests.Binary, httpAddr string) {
+	args := []string{
+		"seekblessings",
+		"--browser=false",
+		fmt.Sprintf("--from=%s/auth/google", httpAddr),
+		"-v=3",
+	}
+	inv := principal.Start(args...)
+	// Reproduce the sleep that was present in the shell test to see if
+	// this allows the test to pass on macjenkins.
+	// TODO(sjr): I suspect the failure is caused by race conditions
+	// exacerbated by our new binary caching.
+	time.Sleep(10 * time.Second)
+	line := inv.ExpectSetEventuallyRE(urlRE)[0][1]
+	// Scan the output of "principal seekblessings", looking for the
+	// URL that can be used to retrieve the blessings.
+	transport := &http.Transport{
+		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
+	}
+	jar, err := cookiejar.New(&cookiejar.Options{})
+	if err != nil {
+		i.Fatalf("failed to create a cookie jar: %v", err)
+	}
+	client := &http.Client{
+		Jar:       jar,
+		Transport: transport,
+	}
+	resp, err := client.Get(line)
+	if err != nil {
+		i.Fatalf("Get(%q) failed: %v", line, err)
+	}
+	output, err := ioutil.ReadAll(resp.Body)
+	resp.Body.Close()
+	if err != nil {
+		i.Fatalf("ReadAll() failed: %v", err)
+	}
+	if want := "Received blessings"; !strings.Contains(string(output), want) {
+		i.Fatalf("failed to seek blessings: %v", string(output))
+	}
+}
+
+func V23TestIdentityServer(i *v23tests.T) {
+	v23tests.RunRootMT(i, "--v23.tcp.address=127.0.0.1:0")
+	// Start identityd:
+	//
+	// identityd must have credentials that recognize the root mounttable.
+	// In production, the two share a common root certificate and thus
+	// recognize each other. The same is done here, i.Principal()
+	// wields the root key.
+	identityd := i.BuildV23Pkg("v.io/x/ref/services/identity/internal/identityd_test")
+	creds, err := i.Shell().NewChildCredentials("identityd")
+	if err != nil {
+		i.Fatal(err)
+	}
+	identityd = identityd.WithStartOpts(identityd.StartOpts().WithCustomCredentials(creds))
+	httpAddr := identityd.Start(
+		"-v23.tcp.address=127.0.0.1:0",
+		"-http-addr=127.0.0.1:0").ExpectVar("HTTP_ADDR")
+
+	// Use the principal tool to seekblessings.
+	// This tool will not run with any credentials: Its whole purpose is to "seek" them!
+	principal := i.BuildGoPkg("v.io/x/ref/cmd/principal")
+	// Test an initial seekblessings call.
+	seekBlessings(i, principal, httpAddr)
+	// Test that a subsequent call succeeds with the same
+	// credentials. This means that the blessings and principal from the
+	// first call works correctly.
+	// TODO(ashankar): Does anyone recall what was the intent here? Running
+	// the tool twice doesn't seem to help?
+	seekBlessings(i, principal, httpAddr)
+}
diff --git a/services/identity/identityd/main.go b/services/identity/identityd/main.go
new file mode 100644
index 0000000..480573a
--- /dev/null
+++ b/services/identity/identityd/main.go
@@ -0,0 +1,169 @@
+// 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.
+
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go . -help
+
+package main
+
+import (
+	"database/sql"
+	"fmt"
+	"os"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/security"
+	"v.io/x/ref/lib/v23cmd"
+	_ "v.io/x/ref/runtime/factories/static"
+	"v.io/x/ref/services/identity/internal/auditor"
+	"v.io/x/ref/services/identity/internal/blesser"
+	"v.io/x/ref/services/identity/internal/caveats"
+	"v.io/x/ref/services/identity/internal/oauth"
+	"v.io/x/ref/services/identity/internal/revocation"
+	"v.io/x/ref/services/identity/internal/server"
+)
+
+var (
+	googleConfigWeb, googleConfigChrome, googleConfigAndroid         string
+	externalHttpAddr, httpAddr, tlsConfig, assetsPrefix, mountPrefix string
+	remoteSignerBlessings                                            string
+)
+
+func init() {
+	// Configuration for various Google OAuth-based clients.
+	cmdIdentityD.Flags.StringVar(&googleConfigWeb, "google-config-web", "", "Path to JSON-encoded OAuth client configuration for the web application that renders the audit log for blessings provided by this provider.")
+	cmdIdentityD.Flags.StringVar(&googleConfigChrome, "google-config-chrome", "", "Path to the JSON-encoded OAuth client configuration for Chrome browser applications that obtain blessings from this server (via the OAuthBlesser.BlessUsingAccessToken RPC) from this server.")
+	cmdIdentityD.Flags.StringVar(&googleConfigAndroid, "google-config-android", "", "Path to the JSON-encoded OAuth client configuration for Android applications that obtain blessings from this server (via the OAuthBlesser.BlessUsingAccessToken RPC) from this server.")
+
+	// Configuration using the remote signer
+	cmdIdentityD.Flags.StringVar(&remoteSignerBlessings, "remote-signer-blessing-dir", "", "Path to the blessings to use with the remote signer. Use the empty string to disable the remote signer.")
+
+	// Flags controlling the HTTP server
+	cmdIdentityD.Flags.StringVar(&externalHttpAddr, "external-http-addr", "", "External address on which the HTTP server listens on.  If none is provided the server will only listen on -http-addr.")
+	cmdIdentityD.Flags.StringVar(&httpAddr, "http-addr", "localhost:8125", "Address on which the HTTP server listens on.")
+	cmdIdentityD.Flags.StringVar(&tlsConfig, "tls-config", "", "Comma-separated list of TLS certificate and private key files, in that order.  This must be provided.")
+	cmdIdentityD.Flags.StringVar(&assetsPrefix, "assets-prefix", "", "Host serving the web assets for the identity server.")
+	cmdIdentityD.Flags.StringVar(&mountPrefix, "mount-prefix", "identity", "Mount name prefix to use.  May be rooted.")
+}
+
+func main() {
+	cmdline.HideGlobalFlagsExcept()
+	cmdline.Main(cmdIdentityD)
+}
+
+var cmdIdentityD = &cmdline.Command{
+	Runner: v23cmd.RunnerFunc(runIdentityD),
+	Name:   "identityd",
+	Short:  "Runs HTTP server that creates security.Blessings objects",
+	Long: `
+Command identityd runs a daemon HTTP server that uses OAuth to create
+security.Blessings objects.
+
+Starts an HTTP server that brokers blessings after authenticating through OAuth.
+
+To generate TLS certificates so the HTTP server can use SSL:
+  go run $(go list -f {{.Dir}} "crypto/tls")/generate_cert.go --host <IP address>
+
+To use Google as an OAuth provider the -google-config-* flags must be set to
+point to the a JSON file obtained after registering the application with the
+Google Developer Console at https://cloud.google.com/console
+
+More details on Google OAuth at:
+  https://developers.google.com/accounts/docs/OAuth2Login
+
+More details on the design of identityd at:
+  https://v.io/designdocs/identity-service.html
+`,
+}
+
+func runIdentityD(ctx *context.T, env *cmdline.Env, args []string) error {
+	var sqlDB *sql.DB
+	var err error
+	if sqlConf != "" {
+		if sqlDB, err = dbFromConfigFile(sqlConf); err != nil {
+			return env.UsageErrorf("Failed to create sqlDB: %v", err)
+		}
+	}
+
+	if remoteSignerBlessings != "" {
+		signer, err := server.NewRestSigner()
+		if err != nil {
+			return fmt.Errorf("Failed to create remote signer: %v", err)
+		}
+		state, err := security.NewPrincipalStateSerializer(remoteSignerBlessings)
+		if err != nil {
+			return fmt.Errorf("Failed to create blessing serializer: %v", err)
+		}
+		p, err := security.NewPrincipalFromSigner(signer, state)
+		if err != nil {
+			return fmt.Errorf("Failed to create principal: %v", err)
+		}
+		if ctx, err = v23.WithPrincipal(ctx, p); err != nil {
+			return fmt.Errorf("Failed to set principal: %v", err)
+		}
+	}
+
+	googleoauth, err := oauth.NewGoogleOAuth(ctx, googleConfigWeb)
+	if err != nil {
+		return env.UsageErrorf("Failed to setup GoogleOAuth: %v", err)
+	}
+
+	auditor, reader, err := auditor.NewSQLBlessingAuditor(ctx, sqlDB)
+	if err != nil {
+		return fmt.Errorf("Failed to create sql auditor from config: %v", err)
+	}
+
+	revocationManager, err := revocation.NewRevocationManager(ctx, sqlDB)
+	if err != nil {
+		return fmt.Errorf("Failed to start RevocationManager: %v", err)
+	}
+
+	listenSpec := v23.GetListenSpec(ctx)
+	s := server.NewIdentityServer(
+		googleoauth,
+		auditor,
+		reader,
+		revocationManager,
+		googleOAuthBlesserParams(ctx, googleoauth, revocationManager),
+		caveats.NewBrowserCaveatSelector(assetsPrefix),
+		assetsPrefix,
+		mountPrefix)
+	s.Serve(ctx, &listenSpec, externalHttpAddr, httpAddr, tlsConfig)
+	return nil
+}
+
+func googleOAuthBlesserParams(ctx *context.T, oauthProvider oauth.OAuthProvider, revocationManager revocation.RevocationManager) blesser.OAuthBlesserParams {
+	params := blesser.OAuthBlesserParams{
+		OAuthProvider:     oauthProvider,
+		BlessingDuration:  365 * 24 * time.Hour,
+		RevocationManager: revocationManager,
+	}
+	if clientID, err := getOAuthClientID(googleConfigChrome); err != nil {
+		ctx.Info(err)
+	} else {
+		params.AccessTokenClients = append(params.AccessTokenClients, oauth.AccessTokenClient{Name: "chrome", ClientID: clientID})
+	}
+	if clientID, err := getOAuthClientID(googleConfigAndroid); err != nil {
+		ctx.Info(err)
+	} else {
+		params.AccessTokenClients = append(params.AccessTokenClients, oauth.AccessTokenClient{Name: "android", ClientID: clientID})
+	}
+	return params
+}
+
+func getOAuthClientID(configFile string) (clientID string, err error) {
+	f, err := os.Open(configFile)
+	if err != nil {
+		return "", fmt.Errorf("failed to open %q: %v", configFile, err)
+	}
+	defer f.Close()
+	clientID, err = oauth.ClientIDFromJSON(f)
+	if err != nil {
+		return "", fmt.Errorf("failed to decode JSON in %q: %v", configFile, err)
+	}
+	return clientID, nil
+}
diff --git a/services/identity/identityd/sql.go b/services/identity/identityd/sql.go
new file mode 100644
index 0000000..7b8b09e
--- /dev/null
+++ b/services/identity/identityd/sql.go
@@ -0,0 +1,100 @@
+// 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 (
+	"crypto/tls"
+	"crypto/x509"
+	"database/sql"
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+
+	"github.com/go-sql-driver/mysql"
+)
+
+// Flag controlling auditing and revocation of Blessing operations.
+var sqlConf string
+
+func init() {
+	cmdIdentityD.Flags.StringVar(&sqlConf, "sql-config", "", `Path to file containing a json object of the following form:
+   {
+    "dataSourceName": "[username[:password]@][protocol[(address)]]/dbname", (the connection string required by go-sql-driver)
+    "tlsServerName": "serverName", (the domain name of the sql server for ssl)
+    "rootCertPath": "/path/server-ca.pem", (the root certificate of the sql server for ssl)
+    "clientCertPath": "/path/client-cert.pem", (the client certificate for ssl)
+    "clientKeyPath": "/path/client-key.pem" (the client private key for ssl)
+   }`)
+}
+
+// The key used by both go-sql-driver and tls for ssl.
+const tlsRegisteredKey = "identitydTLS"
+
+// sqlConfig holds the fields needed to connected to a sql instance and the fields
+// needed to encrypt the information sent over the wire.
+type sqlConfig struct {
+	// DataSourceName is the connection string required by go-sql-driver: "[username[:password]@][protocol[(address)]]/dbname".
+	DataSourceName string
+	// RootCertPath is the root certificate of the sql server for ssl.
+	RootCertPath string
+	// TLSServerName is the domain name of the sql server for ssl.
+	TLSServerName string
+	// ClientCertPath is the client certificate for ssl.
+	ClientCertPath string
+	// ClientKeyPath is the client private key for ssl.
+	ClientKeyPath string
+}
+
+func dbFromConfigFile(file string) (*sql.DB, error) {
+	config, err := readConfigFromFile(file)
+	if err != nil {
+		return nil, fmt.Errorf("failed to read sql config from %v: %v", file, err)
+	}
+	if err := registerTLSConfig(config); err != nil {
+		return nil, fmt.Errorf("failed to register sql tls config %#v: %v", config, err)
+	}
+	db, err := sql.Open("mysql", config.DataSourceName+"?parseTime=true&tls="+tlsRegisteredKey)
+	if err != nil {
+		return nil, fmt.Errorf("failed to create database with database(%v): %v", config.DataSourceName, err)
+	}
+	if err := db.Ping(); err != nil {
+		return nil, fmt.Errorf("db initial ping failed: %v", err)
+	}
+	return db, nil
+}
+
+func readConfigFromFile(file string) (*sqlConfig, error) {
+	configJSON, err := ioutil.ReadFile(file)
+	if err != nil {
+		return nil, err
+	}
+	var config sqlConfig
+	err = json.Unmarshal(configJSON, &config)
+	return &config, err
+}
+
+// registerTLSConfig sets up the connection to the sql instance to require ssl encryption.
+// For more information see https://cloud.google.com/sql/docs/instances#ssl.
+func registerTLSConfig(config *sqlConfig) error {
+	rootCertPool := x509.NewCertPool()
+	pem, err := ioutil.ReadFile(config.RootCertPath)
+	if err != nil {
+		return err
+	}
+	if ok := rootCertPool.AppendCertsFromPEM(pem); !ok {
+		return fmt.Errorf("failed to append PEM to cert pool")
+	}
+	certs, err := tls.LoadX509KeyPair(config.ClientCertPath, config.ClientKeyPath)
+	if err != nil {
+		return err
+	}
+	clientCert := []tls.Certificate{certs}
+	return mysql.RegisterTLSConfig(tlsRegisteredKey, &tls.Config{
+		RootCAs:      rootCertPool,
+		Certificates: clientCert,
+		ServerName:   config.TLSServerName,
+		ClientAuth:   tls.RequireAndVerifyClientCert,
+	})
+}
diff --git a/services/identity/identityd/v23_test.go b/services/identity/identityd/v23_test.go
new file mode 100644
index 0000000..17cc37d
--- /dev/null
+++ b/services/identity/identityd/v23_test.go
@@ -0,0 +1,30 @@
+// 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 main_test
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
+	"v.io/x/ref/test/v23tests"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	modules.DispatchAndExitIfChild()
+	cleanup := v23tests.UseSharedBinDir()
+	r := m.Run()
+	cleanup()
+	os.Exit(r)
+}
+
+func TestV23IdentityServer(t *testing.T) {
+	v23tests.RunTest(t, V23TestIdentityServer)
+}
diff --git a/services/identity/identitylib/test_identityd.go b/services/identity/identitylib/test_identityd.go
new file mode 100644
index 0000000..1038239
--- /dev/null
+++ b/services/identity/identitylib/test_identityd.go
@@ -0,0 +1,115 @@
+// 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 identitylib implements a test identityd service under the
+// v.io/x/ref/test/modules framework.
+package identitylib
+
+import (
+	"flag"
+	"fmt"
+	"net"
+	"strconv"
+	"time"
+
+	"v.io/v23"
+
+	"v.io/x/ref/services/identity/internal/auditor"
+	"v.io/x/ref/services/identity/internal/blesser"
+	"v.io/x/ref/services/identity/internal/caveats"
+	"v.io/x/ref/services/identity/internal/oauth"
+	"v.io/x/ref/services/identity/internal/revocation"
+	"v.io/x/ref/services/identity/internal/server"
+	"v.io/x/ref/services/identity/internal/util"
+	"v.io/x/ref/test/modules"
+)
+
+var (
+	externalHttpAddr = flag.String("external-http-addr", "", "External address on which the HTTP server listens on. If none is provided the server will only listen on -http-addr.")
+	httpAddr         = flag.CommandLine.String("http-addr", "localhost:0", "Address on which the HTTP server listens on.")
+	tlsConfig        = flag.CommandLine.String("tls-config", "", "Comma-separated list of TLS certificate and private key files. This must be provided.")
+)
+
+var TestIdentityd = modules.Register(func(env *modules.Env, args ...string) error {
+	// Duration to use for tls cert and blessing duration.
+	duration := 365 * 24 * time.Hour
+
+	ctx, shutdown := v23.Init()
+	defer shutdown()
+
+	// If no tls-config has been provided, generate new cert and key and use them.
+	if flag.CommandLine.Lookup("tls-config").Value.String() == "" {
+		addr := *externalHttpAddr
+		if *externalHttpAddr == "" {
+			addr = *httpAddr
+		}
+		host, _, err := net.SplitHostPort(addr)
+		if err != nil {
+			return fmt.Errorf("Failed to parse %q: %v", addr, err)
+		}
+		certFile, keyFile, err := util.WriteCertAndKey(host, duration)
+		if err != nil {
+			return fmt.Errorf("Could not write cert and key: %v", err)
+		}
+		if err := flag.CommandLine.Set("tls-config", certFile+","+keyFile); err != nil {
+			return fmt.Errorf("Could not set tls-config: %v", err)
+		}
+	}
+
+	// Pick a free port if http-addr flag is not set.
+	// We can't use :0 here, because the identity server calls
+	// http.ListenAndServeTLS, which blocks, leaving us with no way to tell
+	// what port the server is running on.  Hence, we must pass in an
+	// actual port so we know where the server is running.
+	if flag.CommandLine.Lookup("http-addr").Value.String() == flag.CommandLine.Lookup("http-addr").DefValue {
+		if err := flag.CommandLine.Set("http-addr", "localhost:"+freePort()); err != nil {
+			return fmt.Errorf("Could not set http-addr: %v", err)
+		}
+	}
+
+	mockClientID := "test-client-id"
+	mockClientName := "test-client"
+
+	auditor, reader := auditor.NewMockBlessingAuditor()
+	revocationManager := revocation.NewMockRevocationManager(ctx)
+	oauthProvider := oauth.NewMockOAuth("testemail@example.com", mockClientID)
+
+	params := blesser.OAuthBlesserParams{
+		OAuthProvider:     oauthProvider,
+		BlessingDuration:  duration,
+		RevocationManager: revocationManager,
+		AccessTokenClients: []oauth.AccessTokenClient{
+			oauth.AccessTokenClient{
+				Name:     mockClientName,
+				ClientID: mockClientID,
+			},
+		},
+	}
+
+	s := server.NewIdentityServer(
+		oauthProvider,
+		auditor,
+		reader,
+		revocationManager,
+		params,
+		caveats.NewMockCaveatSelector(),
+		"",
+		"identity")
+
+	l := v23.GetListenSpec(ctx)
+
+	_, eps, externalHttpAddress := s.Listen(ctx, &l, *externalHttpAddr, *httpAddr, *tlsConfig)
+
+	fmt.Fprintf(env.Stdout, "TEST_IDENTITYD_NAME=%s\n", eps[0])
+	fmt.Fprintf(env.Stdout, "TEST_IDENTITYD_HTTP_ADDR=%s\n", externalHttpAddress)
+
+	modules.WaitForEOF(env.Stdin)
+	return nil
+}, "TestIdentityd")
+
+func freePort() string {
+	l, _ := net.Listen("tcp", ":0")
+	defer l.Close()
+	return strconv.Itoa(l.Addr().(*net.TCPAddr).Port)
+}
diff --git a/services/identity/internal/auditor/blessing_auditor.go b/services/identity/internal/auditor/blessing_auditor.go
new file mode 100644
index 0000000..62b140f
--- /dev/null
+++ b/services/identity/internal/auditor/blessing_auditor.go
@@ -0,0 +1,145 @@
+// 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 auditor
+
+import (
+	"database/sql"
+	"fmt"
+	"strings"
+	"time"
+
+	"v.io/v23/context"
+	"v.io/v23/security"
+	"v.io/v23/vom"
+	"v.io/x/ref/lib/security/audit"
+)
+
+// BlessingLogReader provides the Read method to read audit logs.
+// Read returns a channel of BlessingEntrys whose extension matches the provided email.
+type BlessingLogReader interface {
+	Read(ctx *context.T, email string) <-chan BlessingEntry
+}
+
+// BlessingEntry contains important logged information about a blessed principal.
+type BlessingEntry struct {
+	Email              string
+	Caveats            []security.Caveat
+	Timestamp          time.Time // Time when the blesings were created.
+	RevocationCaveatID string
+	Blessings          security.Blessings
+	DecodeError        error
+}
+
+// NewSQLBlessingAuditor returns an auditor for wrapping a principal with, and a BlessingLogReader
+// for reading the audits made by that auditor. The config is used to construct the connection
+// to the SQL database that the auditor and BlessingLogReader use.
+func NewSQLBlessingAuditor(ctx *context.T, sqlDB *sql.DB) (audit.Auditor, BlessingLogReader, error) {
+	db, err := newSQLDatabase(ctx, sqlDB, "BlessingAudit")
+	if err != nil {
+		return nil, nil, fmt.Errorf("failed to create sql db: %v", err)
+	}
+	auditor, reader := &blessingAuditor{db}, &blessingLogReader{db}
+	return auditor, reader, nil
+}
+
+type blessingAuditor struct {
+	db database
+}
+
+func (a *blessingAuditor) Audit(ctx *context.T, entry audit.Entry) error {
+	if entry.Method != "Bless" {
+		return nil
+	}
+	dbentry, err := newDatabaseEntry(entry)
+	if err != nil {
+		return err
+	}
+	return a.db.Insert(ctx, dbentry)
+}
+
+type blessingLogReader struct {
+	db database
+}
+
+func (r *blessingLogReader) Read(ctx *context.T, email string) <-chan BlessingEntry {
+	c := make(chan BlessingEntry)
+	go r.sendAuditEvents(ctx, c, email)
+	return c
+}
+
+func (r *blessingLogReader) sendAuditEvents(ctx *context.T, dst chan<- BlessingEntry, email string) {
+	defer close(dst)
+	dbch := r.db.Query(ctx, email)
+	for dbentry := range dbch {
+		dst <- newBlessingEntry(dbentry)
+	}
+}
+
+func newDatabaseEntry(entry audit.Entry) (databaseEntry, error) {
+	d := databaseEntry{timestamp: entry.Timestamp}
+	extension, ok := entry.Arguments[2].(string)
+	if !ok {
+		return d, fmt.Errorf("failed to extract extension")
+	}
+	// Find the first email component
+	for _, n := range strings.Split(extension, security.ChainSeparator) {
+		// HACK ALERT: An email is the first entry to end up with
+		// a single "@" in it
+		if strings.Count(n, "@") == 1 {
+			d.email = n
+			break
+		}
+	}
+	if len(d.email) == 0 {
+		return d, fmt.Errorf("failed to extract email address from extension %q", extension)
+	}
+	var caveats []security.Caveat
+	for _, arg := range entry.Arguments[3:] {
+		if cav, ok := arg.(security.Caveat); !ok {
+			return d, fmt.Errorf("failed to extract Caveat")
+		} else {
+			caveats = append(caveats, cav)
+		}
+	}
+	var blessings security.Blessings
+	if blessings, ok = entry.Results[0].(security.Blessings); !ok {
+		return d, fmt.Errorf("failed to extract result blessing")
+	}
+	var err error
+	if d.blessings, err = vom.Encode(blessings); err != nil {
+		return d, err
+	}
+	if d.caveats, err = vom.Encode(caveats); err != nil {
+		return d, err
+	}
+	return d, nil
+}
+
+func newBlessingEntry(dbentry databaseEntry) BlessingEntry {
+	if dbentry.decodeErr != nil {
+		return BlessingEntry{DecodeError: dbentry.decodeErr}
+	}
+	b := BlessingEntry{
+		Email:     dbentry.email,
+		Timestamp: dbentry.timestamp,
+	}
+	if err := vom.Decode(dbentry.blessings, &b.Blessings); err != nil {
+		return BlessingEntry{DecodeError: fmt.Errorf("failed to decode blessings: %s", err)}
+	}
+	if err := vom.Decode(dbentry.caveats, &b.Caveats); err != nil {
+		return BlessingEntry{DecodeError: fmt.Errorf("failed to decode caveats: %s", err)}
+	}
+	b.RevocationCaveatID = revocationCaveatID(b.Caveats)
+	return b
+}
+
+func revocationCaveatID(caveats []security.Caveat) string {
+	for _, cav := range caveats {
+		if tp := cav.ThirdPartyDetails(); tp != nil {
+			return tp.ID()
+		}
+	}
+	return ""
+}
diff --git a/services/identity/internal/auditor/blessing_auditor_test.go b/services/identity/internal/auditor/blessing_auditor_test.go
new file mode 100644
index 0000000..5ce9400
--- /dev/null
+++ b/services/identity/internal/auditor/blessing_auditor_test.go
@@ -0,0 +1,117 @@
+// 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 auditor
+
+import (
+	"reflect"
+	"testing"
+	"time"
+
+	"v.io/v23/security"
+	vsecurity "v.io/x/ref/lib/security"
+	"v.io/x/ref/lib/security/audit"
+	"v.io/x/ref/test"
+)
+
+func TestBlessingAuditor(t *testing.T) {
+	ctx, cancel := test.TestContext()
+	defer cancel()
+	auditor, reader := NewMockBlessingAuditor()
+
+	p, err := vsecurity.NewPrincipal()
+	if err != nil {
+		t.Fatalf("failed to create principal: %v", err)
+	}
+	expiryCaveat := newCaveat(security.NewExpiryCaveat(time.Now().Add(time.Hour)))
+	revocationCaveat := newThirdPartyCaveat(t, p)
+
+	tests := []struct {
+		Extension          string
+		Email              string
+		Caveats            []security.Caveat
+		RevocationCaveatID string
+		Blessings          security.Blessings
+	}{
+		{
+			Extension:          "foo@bar.com/nocaveats/bar@baz.com",
+			Email:              "foo@bar.com",
+			RevocationCaveatID: "",
+			Blessings:          newBlessing(t, p, "test/foo@bar.com/nocaveats/bar@baz.com"),
+		},
+		{
+			Extension:          "users/foo@bar.com/caveat",
+			Email:              "foo@bar.com",
+			Caveats:            []security.Caveat{expiryCaveat},
+			RevocationCaveatID: "",
+			Blessings:          newBlessing(t, p, "test/foo@bar.com/caveat"),
+		},
+		{
+			Extension:          "special/guests/foo@bar.com/caveatAndRevocation",
+			Email:              "foo@bar.com",
+			Caveats:            []security.Caveat{expiryCaveat, revocationCaveat},
+			RevocationCaveatID: revocationCaveat.ThirdPartyDetails().ID(),
+			Blessings:          newBlessing(t, p, "test/foo@bar.com/caveatAndRevocation"),
+		},
+	}
+
+	for _, test := range tests {
+		args := []interface{}{nil, nil, test.Extension}
+		for _, cav := range test.Caveats {
+			args = append(args, cav)
+		}
+		if err := auditor.Audit(ctx, audit.Entry{
+			Method:    "Bless",
+			Arguments: args,
+			Results:   []interface{}{test.Blessings},
+		}); err != nil {
+			t.Errorf("Failed to audit Blessing %v: %v", test.Blessings, err)
+		}
+		ch := reader.Read(ctx, "query")
+		got := <-ch
+		if got.Email != test.Email {
+			t.Errorf("got %v, want %v", got.Email, test.Email)
+		}
+		if !reflect.DeepEqual(got.Caveats, test.Caveats) {
+			t.Errorf("got %#v, want %#v", got.Caveats, test.Caveats)
+		}
+		if got.RevocationCaveatID != test.RevocationCaveatID {
+			t.Errorf("got %v, want %v", got.RevocationCaveatID, test.RevocationCaveatID)
+		}
+		if !reflect.DeepEqual(got.Blessings, test.Blessings) {
+			t.Errorf("got %v, want %v", got.Blessings, test.Blessings)
+		}
+		var extra bool
+		for _ = range ch {
+			// Drain the channel to prevent the producer goroutines from being leaked.
+			extra = true
+		}
+		if extra {
+			t.Errorf("Got more entries that expected for test %+v", test)
+		}
+	}
+}
+
+func newThirdPartyCaveat(t *testing.T, p security.Principal) security.Caveat {
+	tp, err := security.NewPublicKeyCaveat(p.PublicKey(), "location", security.ThirdPartyRequirements{}, newCaveat(security.NewMethodCaveat("method")))
+	if err != nil {
+		t.Fatal(err)
+	}
+	return tp
+}
+
+func newBlessing(t *testing.T, p security.Principal, name string) security.Blessings {
+	b, err := p.BlessSelf(name)
+	if err != nil {
+		t.Fatal(err)
+	}
+	return b
+}
+
+func newCaveat(caveat security.Caveat, err error) security.Caveat {
+	if err != nil {
+		panic(err)
+	}
+	return caveat
+}
diff --git a/services/identity/internal/auditor/mock_auditor.go b/services/identity/internal/auditor/mock_auditor.go
new file mode 100644
index 0000000..65d179a
--- /dev/null
+++ b/services/identity/internal/auditor/mock_auditor.go
@@ -0,0 +1,38 @@
+// 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 auditor
+
+import (
+	"reflect"
+
+	"v.io/v23/context"
+	"v.io/x/ref/lib/security/audit"
+)
+
+func NewMockBlessingAuditor() (audit.Auditor, BlessingLogReader) {
+	db := &mockDatabase{}
+	return &blessingAuditor{db}, &blessingLogReader{db}
+}
+
+type mockDatabase struct {
+	NextEntry databaseEntry
+}
+
+func (db *mockDatabase) Insert(ctx *context.T, entry databaseEntry) error {
+	db.NextEntry = entry
+	return nil
+}
+
+func (db *mockDatabase) Query(ctx *context.T, email string) <-chan databaseEntry {
+	c := make(chan databaseEntry)
+	go func() {
+		var empty databaseEntry
+		if !reflect.DeepEqual(db.NextEntry, empty) {
+			c <- db.NextEntry
+		}
+		close(c)
+	}()
+	return c
+}
diff --git a/services/identity/internal/auditor/sql_database.go b/services/identity/internal/auditor/sql_database.go
new file mode 100644
index 0000000..9dac3a8
--- /dev/null
+++ b/services/identity/internal/auditor/sql_database.go
@@ -0,0 +1,86 @@
+// 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 auditor
+
+import (
+	"database/sql"
+	"fmt"
+	"time"
+
+	"v.io/v23/context"
+)
+
+type database interface {
+	Insert(ctx *context.T, entry databaseEntry) error
+	Query(ctx *context.T, email string) <-chan databaseEntry
+}
+
+type databaseEntry struct {
+	email              string
+	caveats, blessings []byte
+	timestamp          time.Time
+	decodeErr          error
+}
+
+// newSQLDatabase returns a SQL implementation of the database interface.
+// If the table does not exist it creates it.
+func newSQLDatabase(ctx *context.T, db *sql.DB, table string) (database, error) {
+	createStmt, err := db.Prepare(fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s ( Email NVARCHAR(256), Caveats BLOB, Timestamp DATETIME, Blessings BLOB );", table))
+	if err != nil {
+		return nil, err
+	}
+	if _, err = createStmt.Exec(); err != nil {
+		return nil, err
+	}
+	insertStmt, err := db.Prepare(fmt.Sprintf("INSERT INTO %s (Email, Caveats, Timestamp, Blessings) VALUES (?, ?, ?, ?)", table))
+	if err != nil {
+		return nil, err
+	}
+	queryStmt, err := db.Prepare(fmt.Sprintf("SELECT Email, Caveats, Timestamp, Blessings FROM %s WHERE Email=? ORDER BY Timestamp DESC", table))
+	return sqlDatabase{
+		insertStmt: insertStmt,
+		queryStmt:  queryStmt,
+	}, err
+}
+
+// Table with 4 columns:
+// (1) Email = string email of the Blessee.
+// (2) Caveats = vom encoded caveats
+// (3) Blessings = vom encoded resulting blessings.
+// (4) Timestamp = time that the blessing happened.
+type sqlDatabase struct {
+	insertStmt, queryStmt *sql.Stmt
+	ctx                   *context.T
+}
+
+func (s sqlDatabase) Insert(ctx *context.T, entry databaseEntry) error {
+	_, err := s.insertStmt.Exec(entry.email, entry.caveats, entry.timestamp, entry.blessings)
+	return err
+}
+
+func (s sqlDatabase) Query(ctx *context.T, email string) <-chan databaseEntry {
+	c := make(chan databaseEntry)
+	go s.sendDatabaseEntries(ctx, email, c)
+	return c
+}
+
+func (s sqlDatabase) sendDatabaseEntries(ctx *context.T, email string, dst chan<- databaseEntry) {
+	defer close(dst)
+	rows, err := s.queryStmt.Query(email)
+	if err != nil {
+		ctx.Errorf("query failed %v", err)
+		dst <- databaseEntry{decodeErr: fmt.Errorf("Failed to query for all audits: %v", err)}
+		return
+	}
+	defer rows.Close()
+	for rows.Next() {
+		var dbentry databaseEntry
+		if err = rows.Scan(&dbentry.email, &dbentry.caveats, &dbentry.timestamp, &dbentry.blessings); err != nil {
+			ctx.Errorf("scan of row failed %v", err)
+			dbentry.decodeErr = fmt.Errorf("failed to read sql row, %s", err)
+		}
+		dst <- dbentry
+	}
+}
diff --git a/services/identity/internal/auditor/sql_database_test.go b/services/identity/internal/auditor/sql_database_test.go
new file mode 100644
index 0000000..e85b744
--- /dev/null
+++ b/services/identity/internal/auditor/sql_database_test.go
@@ -0,0 +1,63 @@
+// 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 auditor
+
+import (
+	"reflect"
+	"testing"
+	"time"
+
+	sqlmock "github.com/DATA-DOG/go-sqlmock"
+
+	_ "v.io/x/ref/runtime/factories/fake"
+	"v.io/x/ref/test"
+)
+
+func TestSQLDatabaseQuery(t *testing.T) {
+	ctx, cancel := test.TestContext()
+	defer cancel()
+	db, err := sqlmock.New()
+	if err != nil {
+		t.Fatalf("failed to create new mock database stub: %v", err)
+	}
+	columns := []string{"Email", "Caveat", "Timestamp", "Blessings"}
+	sqlmock.ExpectExec("CREATE TABLE IF NOT EXISTS tableName (.+)").
+		WillReturnResult(sqlmock.NewResult(0, 1))
+	d, err := newSQLDatabase(ctx, db, "tableName")
+	if err != nil {
+		t.Fatalf("failed to create SQLDatabase: %v", err)
+	}
+
+	entry := databaseEntry{
+		email:     "email",
+		caveats:   []byte("caveats"),
+		timestamp: time.Now(),
+		blessings: []byte("blessings"),
+	}
+	sqlmock.ExpectExec("INSERT INTO tableName (.+) VALUES (.+)").
+		WithArgs(entry.email, entry.caveats, entry.timestamp, entry.blessings).
+		WillReturnResult(sqlmock.NewResult(0, 1)) // no insert id, 1 affected row
+	if err := d.Insert(ctx, entry); err != nil {
+		t.Errorf("failed to insert into SQLDatabase: %v", err)
+	}
+
+	// Test the querying.
+	sqlmock.ExpectQuery("SELECT Email, Caveats, Timestamp, Blessings FROM tableName").
+		WithArgs(entry.email).
+		WillReturnRows(sqlmock.NewRows(columns).AddRow(entry.email, entry.caveats, entry.timestamp, entry.blessings))
+	ch := d.Query(ctx, entry.email)
+	if res := <-ch; !reflect.DeepEqual(res, entry) {
+		t.Errorf("got %#v, expected %#v", res, entry)
+	}
+
+	var extra bool
+	for _ = range ch {
+		// Drain the channel to prevent the producer goroutines from being leaked.
+		extra = true
+	}
+	if extra {
+		t.Errorf("Got more entries that expected")
+	}
+}
diff --git a/services/identity/internal/blesser/macaroon.go b/services/identity/internal/blesser/macaroon.go
new file mode 100644
index 0000000..1cada67
--- /dev/null
+++ b/services/identity/internal/blesser/macaroon.go
@@ -0,0 +1,62 @@
+// 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 blesser
+
+import (
+	"errors"
+	"fmt"
+	"reflect"
+	"time"
+
+	"v.io/x/ref/services/identity"
+	"v.io/x/ref/services/identity/internal/oauth"
+	"v.io/x/ref/services/identity/internal/util"
+
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/vom"
+)
+
+type macaroonBlesser struct {
+	key []byte
+}
+
+// NewMacaroonBlesserServer provides an identity.MacaroonBlesser Service that generates blessings
+// after unpacking a BlessingMacaroon.
+func NewMacaroonBlesserServer(key []byte) identity.MacaroonBlesserServerStub {
+	return identity.MacaroonBlesserServer(&macaroonBlesser{key})
+}
+
+func (b *macaroonBlesser) Bless(ctx *context.T, call rpc.ServerCall, macaroon string) (security.Blessings, error) {
+	secCall := call.Security()
+	var empty security.Blessings
+	inputs, err := util.Macaroon(macaroon).Decode(b.key)
+	if err != nil {
+		return empty, err
+	}
+	var m oauth.BlessingMacaroon
+	if err := vom.Decode(inputs, &m); err != nil {
+		return empty, err
+	}
+	if time.Now().After(m.Creation.Add(time.Minute * 5)) {
+		return empty, fmt.Errorf("macaroon has expired")
+	}
+	if secCall.LocalPrincipal() == nil {
+		return empty, fmt.Errorf("server misconfiguration: no authentication happened")
+	}
+	macaroonPublicKey, err := security.UnmarshalPublicKey(m.PublicKey)
+	if err != nil {
+		return empty, fmt.Errorf("failed to unmarshal public key in macaroon: %v", err)
+	}
+	if !reflect.DeepEqual(secCall.RemoteBlessings().PublicKey(), macaroonPublicKey) {
+		return empty, errors.New("remote end's public key does not match public key in macaroon")
+	}
+	if len(m.Caveats) == 0 {
+		m.Caveats = []security.Caveat{security.UnconstrainedUse()}
+	}
+	return secCall.LocalPrincipal().Bless(secCall.RemoteBlessings().PublicKey(),
+		secCall.LocalBlessings(), m.Name, m.Caveats[0], m.Caveats[1:]...)
+}
diff --git a/services/identity/internal/blesser/macaroon_test.go b/services/identity/internal/blesser/macaroon_test.go
new file mode 100644
index 0000000..541f903
--- /dev/null
+++ b/services/identity/internal/blesser/macaroon_test.go
@@ -0,0 +1,81 @@
+// 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 blesser
+
+import (
+	"crypto/rand"
+	"reflect"
+	"testing"
+	"time"
+
+	"v.io/x/ref/services/identity/internal/oauth"
+	"v.io/x/ref/services/identity/internal/util"
+	"v.io/x/ref/test/testutil"
+
+	"v.io/v23/security"
+	"v.io/v23/vom"
+)
+
+func TestMacaroonBlesser(t *testing.T) {
+	var (
+		key            = make([]byte, 16)
+		provider, user = testutil.NewPrincipal(), testutil.NewPrincipal()
+		userKey, _     = user.PublicKey().MarshalBinary()
+		cOnlyMethodFoo = newCaveat(security.NewMethodCaveat("Foo"))
+		ctx, call      = fakeContextAndCall(provider, user)
+	)
+	if _, err := rand.Read(key); err != nil {
+		t.Fatal(err)
+	}
+	blesser := NewMacaroonBlesserServer(key)
+
+	m := oauth.BlessingMacaroon{Creation: time.Now().Add(-1 * time.Hour), Name: "foo", PublicKey: userKey}
+	wantErr := "macaroon has expired"
+	if _, err := blesser.Bless(ctx, call, newMacaroon(t, key, m)); err == nil || err.Error() != wantErr {
+		t.Errorf("Bless(...) failed with error: %v, want: %v", err, wantErr)
+	}
+
+	otherKey, _ := testutil.NewPrincipal().PublicKey().MarshalBinary()
+	m = oauth.BlessingMacaroon{Creation: time.Now(), Name: "foo", PublicKey: otherKey}
+	wantErr = "remote end's public key does not match public key in macaroon"
+	if _, err := blesser.Bless(ctx, call, newMacaroon(t, key, m)); err == nil || err.Error() != wantErr {
+		t.Errorf("Bless(...) failed with error: %v, want: %v", err, wantErr)
+	}
+
+	m = oauth.BlessingMacaroon{Creation: time.Now(), PublicKey: userKey, Name: "bugsbunny", Caveats: []security.Caveat{cOnlyMethodFoo}}
+	b, err := blesser.Bless(ctx, call, newMacaroon(t, key, m))
+	if err != nil {
+		t.Errorf("Bless failed: %v", err)
+	}
+
+	if !reflect.DeepEqual(b.PublicKey(), user.PublicKey()) {
+		t.Errorf("Received blessing for public key %v. Client:%v, Blesser:%v", b.PublicKey(), user.PublicKey(), provider.PublicKey())
+	}
+
+	// When the user does not recognize the provider, it should not see any strings for
+	// the client's blessings.
+	if got := user.BlessingsInfo(b); got != nil {
+		t.Errorf("Got blessing with info %v, want nil", got)
+	}
+	// But once it recognizes the provider, it should see exactly the name
+	// "provider/bugsbunny" for the caveat cOnlyMethodFoo.
+	user.AddToRoots(b)
+	binfo := user.BlessingsInfo(b)
+	if num := len(binfo); num != 1 {
+		t.Errorf("Got blessings with %d names, want exactly one name", num)
+	}
+	wantName := "provider/bugsbunny"
+	if got, want := binfo[wantName], []security.Caveat{cOnlyMethodFoo}; !reflect.DeepEqual(got, want) {
+		t.Errorf("binfo[%q]: Got %v, want %v", wantName, got, want)
+	}
+}
+
+func newMacaroon(t *testing.T, key []byte, m oauth.BlessingMacaroon) string {
+	encMac, err := vom.Encode(m)
+	if err != nil {
+		t.Fatal(err)
+	}
+	return string(util.NewMacaroon(key, encMac))
+}
diff --git a/services/identity/internal/blesser/oauth.go b/services/identity/internal/blesser/oauth.go
new file mode 100644
index 0000000..980829e
--- /dev/null
+++ b/services/identity/internal/blesser/oauth.go
@@ -0,0 +1,126 @@
+// 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 blesser
+
+import (
+	"fmt"
+	"strings"
+	"time"
+
+	"v.io/x/ref/services/identity"
+	"v.io/x/ref/services/identity/internal/oauth"
+	"v.io/x/ref/services/identity/internal/revocation"
+
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+)
+
+// OAuthBlesserParams represents all the parameters provided to NewOAuthBlessingServer.
+type OAuthBlesserParams struct {
+	// The OAuth provider that must have issued the access tokens accepted by ths service.
+	OAuthProvider oauth.OAuthProvider
+	// The OAuth client IDs and names for the clients of this service.
+	AccessTokenClients []oauth.AccessTokenClient
+	// The object name of the discharger service. If this is empty then revocation caveats will not be granted.
+	DischargerLocation string
+	// The revocation manager that generates caveats and manages revocation.
+	RevocationManager revocation.RevocationManager
+	// The duration for which blessings will be valid. (Used iff RevocationManager is nil).
+	BlessingDuration time.Duration
+}
+
+type oauthBlesser struct {
+	oauthProvider      oauth.OAuthProvider
+	authcodeClient     struct{ ID, Secret string }
+	accessTokenClients []oauth.AccessTokenClient
+	duration           time.Duration
+	dischargerLocation string
+	revocationManager  revocation.RevocationManager
+}
+
+// NewOAuthBlesserServer provides an identity.OAuthBlesserService that uses OAuth2
+// access tokens to obtain the username of a client and provide blessings with that
+// name.
+//
+// Blessings generated by this service carry a third-party revocation caveat if a
+// RevocationManager is specified by the params or they carry an ExpiryCaveat that
+// expires after the duration specified by the params.
+// TODO(ataly): Get rid of this service once all clients have been
+// switched to use the HTTP OAuthBlessingHandler service.
+func NewOAuthBlesserServer(p OAuthBlesserParams) identity.OAuthBlesserServerStub {
+	return identity.OAuthBlesserServer(&oauthBlesser{
+		oauthProvider:      p.OAuthProvider,
+		duration:           p.BlessingDuration,
+		dischargerLocation: p.DischargerLocation,
+		revocationManager:  p.RevocationManager,
+		accessTokenClients: p.AccessTokenClients,
+	})
+}
+
+func (b *oauthBlesser) BlessUsingAccessToken(ctx *context.T, call rpc.ServerCall, accessToken string) (security.Blessings, string, error) {
+	var noblessings security.Blessings
+	if len(b.accessTokenClients) == 0 {
+		return noblessings, "", fmt.Errorf("no expected AccessTokenClients specified")
+	}
+	email, clientID, err := b.oauthProvider.GetEmailAndClientID(accessToken)
+	if err != nil {
+		return noblessings, "", err
+	}
+	clientName, err := oauth.ClientName(clientID, b.accessTokenClients)
+	if err != nil {
+		return noblessings, "", err
+	}
+	return b.bless(ctx, call.Security(), email, clientName, nil)
+}
+
+func (b *oauthBlesser) BlessUsingAccessTokenWithCaveats(ctx *context.T, call rpc.ServerCall, accessToken string, caveats []security.Caveat) (security.Blessings, string, error) {
+	var noblessings security.Blessings
+	if len(b.accessTokenClients) == 0 {
+		return noblessings, "", fmt.Errorf("no expected AccessTokenClients specified")
+	}
+	email, clientID, err := b.oauthProvider.GetEmailAndClientID(accessToken)
+	if err != nil {
+		return noblessings, "", err
+	}
+	clientName, err := oauth.ClientName(clientID, b.accessTokenClients)
+	if err != nil {
+		return noblessings, "", err
+	}
+	return b.bless(ctx, call.Security(), email, clientName, caveats)
+}
+
+func (b *oauthBlesser) bless(ctx *context.T, call security.Call, email, clientName string, caveats []security.Caveat) (security.Blessings, string, error) {
+	var noblessings security.Blessings
+	self := call.LocalPrincipal()
+	if self == nil {
+		return noblessings, "", fmt.Errorf("server error: no authentication happened")
+	}
+	// TODO(suharshs, ataly): Should we ensure that we have at least a revocation or expiry caveat?
+	if len(caveats) == 0 {
+		var caveat security.Caveat
+		var err error
+		if b.revocationManager != nil {
+			caveat, err = b.revocationManager.NewCaveat(self.PublicKey(), b.dischargerLocation)
+		} else {
+			caveat, err = security.NewExpiryCaveat(time.Now().Add(b.duration))
+		}
+		if err != nil {
+			return noblessings, "", err
+		}
+		caveats = append(caveats, caveat)
+	}
+	// Append clientName (e.g., "android", "chrome") to the email and then bless under that.
+	// Since blessings issued by this process do not have many caveats on them and typically
+	// have a large expiry duration, we include the clientName in the extension so that
+	// servers can explicitly distinguish these clients while specifying authorization policies
+	// (say, via AccessLists).
+	extension := strings.Join([]string{email, clientName}, security.ChainSeparator)
+	blessing, err := self.Bless(call.RemoteBlessings().PublicKey(), call.LocalBlessings(), extension, caveats[0], caveats[1:]...)
+	if err != nil {
+		return noblessings, "", err
+	}
+	return blessing, extension, nil
+}
diff --git a/services/identity/internal/blesser/oauth_test.go b/services/identity/internal/blesser/oauth_test.go
new file mode 100644
index 0000000..6b56a72
--- /dev/null
+++ b/services/identity/internal/blesser/oauth_test.go
@@ -0,0 +1,151 @@
+// 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 blesser
+
+import (
+	"reflect"
+	"sort"
+	"strings"
+	"testing"
+	"time"
+
+	"v.io/x/ref/services/identity/internal/oauth"
+	"v.io/x/ref/test/testutil"
+
+	"v.io/v23/security"
+)
+
+func join(elements ...string) string {
+	return strings.Join(elements, security.ChainSeparator)
+}
+
+func TestOAuthBlesser(t *testing.T) {
+	var (
+		provider, user = testutil.NewPrincipal(), testutil.NewPrincipal()
+		ctx, call      = fakeContextAndCall(provider, user)
+	)
+	mockEmail := "testemail@example.com"
+	mockClientID := "test-client-id"
+	mockClientName := "test-client"
+	blesser := NewOAuthBlesserServer(OAuthBlesserParams{
+		OAuthProvider:    oauth.NewMockOAuth(mockEmail, mockClientID),
+		BlessingDuration: time.Hour,
+		AccessTokenClients: []oauth.AccessTokenClient{
+			oauth.AccessTokenClient{
+				Name:     mockClientName,
+				ClientID: mockClientID,
+			},
+		},
+	})
+
+	b, extension, err := blesser.BlessUsingAccessToken(ctx, call, "test-access-token")
+	if err != nil {
+		t.Errorf("BlessUsingAccessToken failed: %v", err)
+	}
+
+	wantExtension := join(mockEmail, mockClientName)
+	if extension != wantExtension {
+		t.Errorf("got extension: %s, want: %s", extension, wantExtension)
+	}
+
+	if !reflect.DeepEqual(b.PublicKey(), user.PublicKey()) {
+		t.Errorf("Received blessing for public key %v. Client:%v, Blesser:%v", b.PublicKey(), user.PublicKey(), provider.PublicKey())
+	}
+
+	// When the user does not recognize the provider, it should not see any strings for
+	// the client's blessings.
+	if got := user.BlessingsInfo(b); got != nil {
+		t.Errorf("Got blessing with info %v, want nil", got)
+	}
+	// But once it recognizes the provider, it should see exactly the name
+	// "provider/testemail@example.com/test-client".
+	user.AddToRoots(b)
+	binfo := user.BlessingsInfo(b)
+	if num := len(binfo); num != 1 {
+		t.Errorf("Got blessings with %d names, want exactly one name", num)
+	}
+	if _, ok := binfo[join("provider", wantExtension)]; !ok {
+		t.Errorf("BlessingsInfo %v does not have name %s", binfo, wantExtension)
+	}
+}
+
+func TestOAuthBlesserWithCaveats(t *testing.T) {
+	var (
+		provider, user = testutil.NewPrincipal(), testutil.NewPrincipal()
+		ctx, call      = fakeContextAndCall(provider, user)
+	)
+	mockEmail := "testemail@example.com"
+	mockClientID := "test-client-id"
+	mockClientName := "test-client"
+	blesser := NewOAuthBlesserServer(OAuthBlesserParams{
+		OAuthProvider:    oauth.NewMockOAuth(mockEmail, mockClientID),
+		BlessingDuration: time.Hour,
+		AccessTokenClients: []oauth.AccessTokenClient{
+			oauth.AccessTokenClient{
+				Name:     mockClientName,
+				ClientID: mockClientID,
+			},
+		},
+	})
+
+	expiryCav, err := security.NewExpiryCaveat(time.Now().Add(time.Minute))
+	if err != nil {
+		t.Fatal(err)
+	}
+	methodCav, err := security.NewMethodCaveat("foo", "bar")
+	if err != nil {
+		t.Fatal(err)
+	}
+	caveats := []security.Caveat{expiryCav, methodCav}
+
+	b, extension, err := blesser.BlessUsingAccessTokenWithCaveats(ctx, call, "test-access-token", caveats)
+	if err != nil {
+		t.Errorf("BlessUsingAccessToken failed: %v", err)
+	}
+
+	wantExtension := join(mockEmail, mockClientName)
+	if extension != wantExtension {
+		t.Errorf("got extension: %s, want: %s", extension, wantExtension)
+	}
+
+	if !reflect.DeepEqual(b.PublicKey(), user.PublicKey()) {
+		t.Errorf("Received blessing for public key %v. Client:%v, Blesser:%v", b.PublicKey(), user.PublicKey(), provider.PublicKey())
+	}
+
+	// When the user does not recognize the provider, it should not see any strings for
+	// the client's blessings.
+	if got := user.BlessingsInfo(b); got != nil {
+		t.Errorf("Got blessing with info %v, want nil", got)
+	}
+	// But once it recognizes the provider, it should see exactly the name
+	// "provider/testemail@example.com/test-client".
+	user.AddToRoots(b)
+	binfo := user.BlessingsInfo(b)
+	if num := len(binfo); num != 1 {
+		t.Errorf("Got blessings with %d names, want exactly one name", num)
+	}
+	cavs, ok := binfo[join("provider", wantExtension)]
+	if !ok {
+		t.Errorf("BlessingsInfo %v does not have name %s", binfo, wantExtension)
+	}
+	if !caveatsMatch(cavs, caveats) {
+		t.Errorf("got %v, want %v", cavs, caveats)
+	}
+}
+
+func caveatsMatch(got, want []security.Caveat) bool {
+	if len(got) != len(want) {
+		return false
+	}
+	gotStrings := make([]string, len(got))
+	wantStrings := make([]string, len(want))
+	for i := 0; i < len(got); i++ {
+		gotStrings[i] = got[i].String()
+		wantStrings[i] = want[i].String()
+	}
+	sort.Strings(gotStrings)
+	sort.Strings(wantStrings)
+	return reflect.DeepEqual(gotStrings, wantStrings)
+}
diff --git a/services/identity/internal/blesser/util_test.go b/services/identity/internal/blesser/util_test.go
new file mode 100644
index 0000000..08f05fe
--- /dev/null
+++ b/services/identity/internal/blesser/util_test.go
@@ -0,0 +1,45 @@
+// 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 blesser
+
+import (
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+)
+
+type rpcCall struct {
+	rpc.ServerCall
+	secCall security.Call
+}
+
+func (c rpcCall) Security() security.Call         { return c.secCall }
+func (c rpcCall) LocalEndpoint() naming.Endpoint  { return nil }
+func (c rpcCall) RemoteEndpoint() naming.Endpoint { return nil }
+
+func fakeContextAndCall(provider, user security.Principal) (*context.T, rpc.ServerCall) {
+	ctx, _ := context.RootContext()
+	return ctx, rpcCall{secCall: security.NewCall(&security.CallParams{
+		LocalPrincipal:  provider,
+		LocalBlessings:  blessSelf(provider, "provider"),
+		RemoteBlessings: blessSelf(user, "self-signed-user"),
+	})}
+}
+
+func blessSelf(p security.Principal, name string) security.Blessings {
+	b, err := p.BlessSelf(name)
+	if err != nil {
+		panic(err)
+	}
+	return b
+}
+
+func newCaveat(c security.Caveat, err error) security.Caveat {
+	if err != nil {
+		panic(err)
+	}
+	return c
+}
diff --git a/services/identity/internal/caveats/browser_caveat_selector.go b/services/identity/internal/caveats/browser_caveat_selector.go
new file mode 100644
index 0000000..09746a1
--- /dev/null
+++ b/services/identity/internal/caveats/browser_caveat_selector.go
@@ -0,0 +1,125 @@
+// 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 caveats
+
+import (
+	"fmt"
+	"net/http"
+	"strings"
+	"time"
+
+	"v.io/x/ref/services/identity/internal/templates"
+
+	"v.io/v23/security"
+)
+
+var ErrSeekblessingsCancelled = fmt.Errorf("seekblessings has been cancelled")
+
+type browserCaveatSelector struct {
+	assetsPrefix string
+}
+
+// NewBrowserCaveatSelector returns a caveat selector that renders a form in the
+// to accept user caveat selections.
+func NewBrowserCaveatSelector(assetsPrefix string) CaveatSelector {
+	return &browserCaveatSelector{assetsPrefix}
+}
+
+func (s *browserCaveatSelector) Render(blessingName, state, redirectURL string, w http.ResponseWriter, r *http.Request) error {
+	tmplargs := struct {
+		BlessingName, Macaroon, MacaroonURL, AssetsPrefix string
+	}{blessingName, state, redirectURL, s.assetsPrefix}
+	w.Header().Set("Context-Type", "text/html")
+	if err := templates.SelectCaveats.Execute(w, tmplargs); err != nil {
+		return err
+	}
+	return nil
+}
+
+func (s *browserCaveatSelector) ParseSelections(r *http.Request) (caveats []CaveatInfo, state string, additionalExtension string, err error) {
+	state = r.FormValue("macaroon")
+	if r.FormValue("cancelled") == "true" {
+		err = ErrSeekblessingsCancelled
+		return
+	}
+	additionalExtension = r.FormValue("blessingExtension")
+	if caveats, err = s.caveats(r); err != nil {
+		return
+	}
+	return
+}
+
+func (s *browserCaveatSelector) caveats(r *http.Request) ([]CaveatInfo, error) {
+	if err := r.ParseForm(); err != nil {
+		return nil, err
+	}
+	var caveats []CaveatInfo
+	for i, cavName := range r.Form["caveat"] {
+		var err error
+		var caveat CaveatInfo
+		switch cavName {
+		case "ExpiryCaveat":
+			caveat, err = newExpiryCaveatInfo(r.Form[cavName][i], r.FormValue("timezoneOffset"))
+		case "MethodCaveat":
+			caveat, err = newMethodCaveatInfo(r.Form[cavName][i])
+		case "PeerBlessingsCaveat":
+			caveat, err = newPeerBlessingsCaveatInfo(r.Form[cavName][i])
+		case "RevocationCaveat":
+			caveat = newRevocationCaveatInfo()
+		default:
+			continue
+		}
+		if err != nil {
+			return nil, fmt.Errorf("unable to create caveat %s: %v", cavName, err)
+		}
+		caveats = append(caveats, caveat)
+	}
+	if len(caveats) == 0 {
+		return nil, fmt.Errorf("server does not allow unconstrained blessings")
+	}
+	return caveats, nil
+}
+
+func newExpiryCaveatInfo(timestamp, utcOffset string) (CaveatInfo, error) {
+	var empty CaveatInfo
+	t, err := time.Parse("2006-01-02T15:04", timestamp)
+	if err != nil {
+		return empty, fmt.Errorf("parseTime failed: %v", err)
+	}
+	// utcOffset is returned as minutes from JS, so we need to parse it to a duration.
+	offset, err := time.ParseDuration(utcOffset + "m")
+	if err != nil {
+		return empty, fmt.Errorf("failed to parse duration: %v", err)
+	}
+	return CaveatInfo{"Expiry", []interface{}{t.Add(offset)}}, nil
+}
+
+func newMethodCaveatInfo(methodsCSV string) (CaveatInfo, error) {
+	methods := strings.Split(methodsCSV, ",")
+	if len(methods) < 1 {
+		return CaveatInfo{}, fmt.Errorf("must pass at least one method")
+	}
+	var ifaces []interface{}
+	for _, m := range methods {
+		ifaces = append(ifaces, m)
+	}
+	return CaveatInfo{"Method", ifaces}, nil
+}
+
+func newPeerBlessingsCaveatInfo(patternsCSV string) (CaveatInfo, error) {
+	patterns := strings.Split(patternsCSV, ",")
+	if len(patterns) < 1 {
+		return CaveatInfo{}, fmt.Errorf("must pass at least one peer blessing pattern")
+	}
+	var ifaces []interface{}
+	for _, p := range patterns {
+		ifaces = append(ifaces, security.BlessingPattern(p))
+	}
+	return CaveatInfo{"PeerBlessings", ifaces}, nil
+}
+
+func newRevocationCaveatInfo() CaveatInfo {
+	return CaveatInfo{Type: "Revocation"}
+}
diff --git a/services/identity/internal/caveats/caveat_factory.go b/services/identity/internal/caveats/caveat_factory.go
new file mode 100644
index 0000000..d11382e
--- /dev/null
+++ b/services/identity/internal/caveats/caveat_factory.go
@@ -0,0 +1,120 @@
+// 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 caveats
+
+import (
+	"fmt"
+	"time"
+
+	"v.io/x/ref/services/identity/internal/revocation"
+
+	"v.io/v23/security"
+)
+
+type CaveatFactory interface {
+	New(caveatInfo CaveatInfo) (security.Caveat, error)
+}
+
+type CaveatInfo struct {
+	Type string
+	Args []interface{}
+}
+
+type caveatFactory map[string]func(args ...interface{}) (security.Caveat, error)
+
+func NewCaveatFactory() CaveatFactory {
+	return caveatFactory{
+		"Expiry":        expiryCaveat,
+		"Method":        methodCaveat,
+		"PeerBlessings": peerBlessingsCaveat,
+		"Revocation":    revocationCaveat,
+	}
+}
+
+func (c caveatFactory) New(caveatInfo CaveatInfo) (security.Caveat, error) {
+	fact, exists := c[caveatInfo.Type]
+	if !exists {
+		return security.Caveat{}, fmt.Errorf("caveat %s does not exist in CaveatFactory", caveatInfo.Type)
+	}
+	return fact(caveatInfo.Args...)
+}
+
+func expiryCaveat(args ...interface{}) (security.Caveat, error) {
+	var empty security.Caveat
+	if len(args) != 1 {
+		return empty, fmt.Errorf("expiry caveat: must input exactly one time argument")
+	}
+	t, ok := args[0].(time.Time)
+	if !ok {
+		return empty, fmt.Errorf("expiry caveat: received arg of type %T, expected time.Time", args[0])
+	}
+	return security.NewExpiryCaveat(t)
+}
+
+func methodCaveat(args ...interface{}) (security.Caveat, error) {
+	if len(args) < 1 {
+		return security.Caveat{}, fmt.Errorf("method caveat requires at least one argument")
+	}
+	methods, err := interfacesToStrings(args)
+	if err != nil {
+		return security.Caveat{}, fmt.Errorf("method caveat: %v", err)
+	}
+	return security.NewMethodCaveat(methods[0], methods[1:]...)
+}
+
+func peerBlessingsCaveat(args ...interface{}) (security.Caveat, error) {
+	if len(args) < 1 {
+		return security.Caveat{}, fmt.Errorf("peer-blessings caveat requires at least one argument")
+	}
+	patterns, err := interfacesToBlessingPatterns(args)
+	if err != nil {
+		return security.Caveat{}, fmt.Errorf("peer-blessings caveat: %v", err)
+	}
+	return security.NewCaveat(security.PeerBlessingsCaveat, patterns)
+}
+
+func interfacesToStrings(args []interface{}) ([]string, error) {
+	var s []string
+	for _, arg := range args {
+		a, ok := arg.(string)
+		if !ok {
+			return nil, fmt.Errorf("received arg of type %T, expected string", arg)
+		}
+		s = append(s, a)
+	}
+	return s, nil
+}
+
+func interfacesToBlessingPatterns(args []interface{}) ([]security.BlessingPattern, error) {
+	var bps []security.BlessingPattern
+	for _, arg := range args {
+		a, ok := arg.(security.BlessingPattern)
+		if !ok {
+			return nil, fmt.Errorf("received arg of type %T, expected security.BlessingPattern", arg)
+		}
+		bps = append(bps, a)
+	}
+	return bps, nil
+}
+
+func revocationCaveat(args ...interface{}) (security.Caveat, error) {
+	var empty security.Caveat
+	if len(args) != 3 {
+		return empty, fmt.Errorf("revocation caveat: must input a revocation manager, publickey, and discharge location")
+	}
+	revocationManager, ok := args[0].(revocation.RevocationManager)
+	if !ok {
+		return empty, fmt.Errorf("revocation caveat: received args of type %T, expected revocation.RevocationManager", args[0])
+	}
+	publicKey, ok := args[1].(security.PublicKey)
+	if !ok {
+		return empty, fmt.Errorf("revocation caveat: received args of type %T, expected security.PublicKey", args[1])
+	}
+	dischargerLocation, ok := args[2].(string)
+	if !ok {
+		return empty, fmt.Errorf("revocation caveat: received args of type %T, expected string", args[2])
+	}
+	return revocationManager.NewCaveat(publicKey, dischargerLocation)
+}
diff --git a/services/identity/internal/caveats/caveat_selector.go b/services/identity/internal/caveats/caveat_selector.go
new file mode 100644
index 0000000..487c5a6
--- /dev/null
+++ b/services/identity/internal/caveats/caveat_selector.go
@@ -0,0 +1,23 @@
+// 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 caveats
+
+import (
+	"net/http"
+)
+
+// CaveatSelector is used to render a web page where the user can select caveats
+// to be added to a blessing being granted
+type CaveatSelector interface {
+	// Render renders the caveat input form. When the user has completed inputing caveats,
+	// Render should redirect to the specified redirect route.
+	// blessingName is the name used for the blessings that is being caveated.
+	// state is any state passed by the caller (e.g., for CSRF mitigation) and is returned by ParseSelections.
+	// redirectRoute is the route to be returned to.
+	Render(blessingName, state, redirectURL string, w http.ResponseWriter, r *http.Request) error
+	// ParseSelections parse the users choices of Caveats, and returns the information needed to create them,
+	// the state passed to Render, and any additionalExtension selected by the user to further extend the blessing.
+	ParseSelections(r *http.Request) (caveats []CaveatInfo, state string, additionalExtension string, err error)
+}
diff --git a/services/identity/internal/caveats/mock_caveat_selector.go b/services/identity/internal/caveats/mock_caveat_selector.go
new file mode 100644
index 0000000..f0209fd
--- /dev/null
+++ b/services/identity/internal/caveats/mock_caveat_selector.go
@@ -0,0 +1,42 @@
+// 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 caveats
+
+import (
+	"net/http"
+	"time"
+
+	"v.io/v23/security"
+)
+
+type mockCaveatSelector struct {
+	state string
+}
+
+// NewMockCaveatSelector returns a CaveatSelector that always returns a default set
+// of caveats: [exprity caveat with a 1h expiry, revocation caveat, and a method caveat
+// for methods "methodA" and "methodB"] and the additional extension: "test-extension"
+// This selector is only meant to be used during testing.
+func NewMockCaveatSelector() CaveatSelector {
+	return &mockCaveatSelector{}
+}
+
+func (s *mockCaveatSelector) Render(_, state, redirectURL string, w http.ResponseWriter, r *http.Request) error {
+	s.state = state
+	http.Redirect(w, r, redirectURL, http.StatusFound)
+	return nil
+}
+
+func (s *mockCaveatSelector) ParseSelections(r *http.Request) (caveats []CaveatInfo, state string, additionalExtension string, err error) {
+	caveats = []CaveatInfo{
+		CaveatInfo{"Revocation", []interface{}{}},
+		CaveatInfo{"Expiry", []interface{}{time.Now().Add(time.Hour)}},
+		CaveatInfo{"Method", []interface{}{"methodA", "methodB"}},
+		CaveatInfo{"PeerBlessings", []interface{}{security.BlessingPattern("peerA"), security.BlessingPattern("peerB")}},
+	}
+	state = s.state
+	additionalExtension = "test-extension"
+	return
+}
diff --git a/services/identity/internal/dischargerlib/discharger.go b/services/identity/internal/dischargerlib/discharger.go
new file mode 100644
index 0000000..d1d401e
--- /dev/null
+++ b/services/identity/internal/dischargerlib/discharger.go
@@ -0,0 +1,42 @@
+// 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 dischargerlib
+
+import (
+	"fmt"
+	"time"
+
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/x/ref/services/discharger"
+)
+
+// dischargerd issues discharges for all caveats present in the current
+// namespace with no additional caveats iff the caveat is valid.
+type dischargerd struct{}
+
+func (dischargerd) Discharge(ctx *context.T, call rpc.ServerCall, caveat security.Caveat, _ security.DischargeImpetus) (security.Discharge, error) {
+	tp := caveat.ThirdPartyDetails()
+	if tp == nil {
+		return security.Discharge{}, discharger.NewErrNotAThirdPartyCaveat(ctx, caveat)
+	}
+	if err := tp.Dischargeable(ctx, call.Security()); err != nil {
+		return security.Discharge{}, fmt.Errorf("third-party caveat %v cannot be discharged for this context: %v", tp, err)
+	}
+	expiry, err := security.NewExpiryCaveat(time.Now().Add(15 * time.Minute))
+	if err != nil {
+		return security.Discharge{}, fmt.Errorf("unable to create expiration caveat on the discharge: %v", err)
+	}
+	return call.Security().LocalPrincipal().MintDischarge(caveat, expiry)
+}
+
+// NewDischarger returns a discharger service implementation that grants
+// discharges using the MintDischarge on the principal receiving the RPC.
+//
+// Discharges are valid for 15 minutes.
+func NewDischarger() discharger.DischargerServerMethods {
+	return dischargerd{}
+}
diff --git a/services/identity/internal/handlers/bless.go b/services/identity/internal/handlers/bless.go
new file mode 100644
index 0000000..45142ac
--- /dev/null
+++ b/services/identity/internal/handlers/bless.go
@@ -0,0 +1,202 @@
+// 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 handlers
+
+import (
+	"encoding/base64"
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"strings"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/security"
+	"v.io/v23/vom"
+	"v.io/x/ref/services/identity/internal/blesser"
+	"v.io/x/ref/services/identity/internal/util"
+)
+
+const (
+	publicKeyFormKey    = "public_key"
+	tokenFormKey        = "token"
+	caveatsFormKey      = "caveats"
+	outputFormatFormKey = "output_format"
+
+	jsonFormat      = "json"
+	base64VomFormat = "base64vom"
+)
+
+type accessTokenBlesser struct {
+	ctx    *context.T
+	params blesser.OAuthBlesserParams
+}
+
+// NewOAuthBlessingHandler returns an http.Handler that uses Google OAuth2 Access tokens
+// to obtain the username of the requestor and reponds with blessings for that username.
+//
+// The blessings are namespaced under the ClientID for the access token. In particular,
+// the name of the granted blessing is of the form <idp>/<clientID>/<email> where <idp>
+// is the name of the default blessings used by the identity provider.
+//
+// Blessings generated by this service carry a third-party revocation caveat if a
+// RevocationManager is specified by the params or they carry an ExpiryCaveat that
+// expires after the duration specified by the params.
+//
+// The handler expects the following request parameters:
+// - "public_key": Base64 DER encoded PKIX representation of the client's public key
+// - "caveats": Base64 VOM encoded list of caveats [OPTIONAL]
+// - "token": Google OAuth2 Access token
+// - "output_format": The encoding format for the returned blessings. The following
+//   formats are supported:
+//     - "json": JSON-encoding of the wire format of Blessings.
+//     - "base64vom": Base64 encoding of VOM-encoded Blessings [DEFAULT]
+//
+// The response consists of blessings encoded in the requested output format.
+//
+// WARNINGS:
+//   - There is no binding between the channel over which the access token
+//     was obtained and the channel used to make this request.
+//   - There is no "proof of possession of private key" required by the server.
+// Thus, if Mallory (attacker) possesses the access token associated with Alice's
+// account (victim), she may be able to obtain a blessing with Alice's name on it
+// for any public key of her choice.
+func NewOAuthBlessingHandler(ctx *context.T, params blesser.OAuthBlesserParams) http.Handler {
+	return &accessTokenBlesser{ctx, params}
+}
+
+func (a *accessTokenBlesser) blessingCaveats(r *http.Request, p security.Principal) ([]security.Caveat, error) {
+	var caveats []security.Caveat
+	if base64VomCaveats := r.FormValue(caveatsFormKey); len(base64VomCaveats) != 0 {
+		vomCaveats, err := base64.URLEncoding.DecodeString(base64VomCaveats)
+		if err != nil {
+			return nil, fmt.Errorf("base64.URLEncoding.DecodeString failed: %v", err)
+		}
+
+		if err := vom.Decode(vomCaveats, &caveats); err != nil {
+			return nil, fmt.Errorf("vom.Decode failed: %v", err)
+		}
+	}
+	// TODO(suharshs, ataly): Should we ensure that we have at least a
+	// revocation or expiry caveat?
+	if len(caveats) == 0 {
+		var (
+			cav security.Caveat
+			err error
+		)
+		if a.params.RevocationManager != nil {
+			cav, err = a.params.RevocationManager.NewCaveat(p.PublicKey(), a.params.DischargerLocation)
+		} else {
+			cav, err = security.NewExpiryCaveat(time.Now().Add(a.params.BlessingDuration))
+		}
+		if err != nil {
+			return nil, fmt.Errorf("failed to construct caveats: %v", err)
+		}
+		caveats = append(caveats, cav)
+	}
+	return caveats, nil
+
+}
+
+func (a *accessTokenBlesser) remotePublicKey(r *http.Request) (security.PublicKey, error) {
+	publicKeyVom, err := base64.URLEncoding.DecodeString(r.FormValue(publicKeyFormKey))
+	if err != nil {
+		return nil, fmt.Errorf("base64.URLEncoding.DecodeString failed: %v", err)
+	}
+	return security.UnmarshalPublicKey(publicKeyVom)
+}
+
+func (a *accessTokenBlesser) blessingExtension(r *http.Request) (string, error) {
+	email, clientID, err := a.params.OAuthProvider.GetEmailAndClientID(r.FormValue(tokenFormKey))
+	if err != nil {
+		return "", err
+	}
+	// We use <clientID>/<email> as the extension in order to namespace the blessing under
+	// the <clientID>. This has the downside that the blessing cannot be used to act on
+	// behalf of the user, i.e., services access controlled to blessings matching"<idp>/<email>"
+	// would not authorize this blessing.
+	//
+	// The alternative is to use the extension <email>/<clientID> however this is risky as it
+	// may provide too much authority to the app, especially since we don't have a set of default
+	// caveats to apply to the blessing.
+	//
+	// TODO(ataly, ashankar): Think about changing to the extension <email>/<clientID>.
+	return strings.Join([]string{clientID, email}, security.ChainSeparator), nil
+}
+
+func (a *accessTokenBlesser) encodeBlessingsJson(b security.Blessings) ([]byte, error) {
+	return json.Marshal(security.MarshalBlessings(b))
+}
+
+func (a *accessTokenBlesser) encodeBlessingsVom(b security.Blessings) (string, error) {
+	bVom, err := vom.Encode(b)
+	if err != nil {
+		return "", fmt.Errorf("vom.Encode(%v) failed: %v", b, err)
+	}
+	return base64.URLEncoding.EncodeToString(bVom), nil
+}
+
+func (a *accessTokenBlesser) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	remoteKey, err := a.remotePublicKey(r)
+	if err != nil {
+		a.ctx.Info("Failed to decode public key [%v] for request %#v", err, r)
+		util.HTTPServerError(w, fmt.Errorf("failed to decode public key: %v", err))
+		return
+	}
+
+	p := v23.GetPrincipal(a.ctx)
+	with := p.BlessingStore().Default()
+
+	caveats, err := a.blessingCaveats(r, p)
+	if err != nil {
+		a.ctx.Info("Failed to constuct caveats for blessing [%v] for request %#v", err, r)
+		util.HTTPServerError(w, fmt.Errorf("failed to construct caveats for blessing: %v", err))
+		return
+	}
+
+	extension, err := a.blessingExtension(r)
+	if err != nil {
+		a.ctx.Info("Failed to process access token [%v] for request %#v", err, r)
+		util.HTTPServerError(w, fmt.Errorf("failed to process access token: %v", err))
+		return
+	}
+
+	blessings, err := p.Bless(remoteKey, with, extension, caveats[0], caveats[1:]...)
+	if err != nil {
+		a.ctx.Info("Failed to Bless [%v] for request %#v", err, r)
+		util.HTTPServerError(w, fmt.Errorf("failed to Bless: %v", err))
+		return
+	}
+
+	outputFormat := r.FormValue(outputFormatFormKey)
+	if len(outputFormat) == 0 {
+		outputFormat = base64VomFormat
+	}
+	switch outputFormat {
+	case jsonFormat:
+		encodedBlessings, err := a.encodeBlessingsJson(blessings)
+		if err != nil {
+			a.ctx.Info("Failed to encode blessings [%v] for request %#v", err, r)
+			util.HTTPServerError(w, fmt.Errorf("failed to encode blessings in format %v: %v", outputFormat, err))
+			return
+		}
+		w.Header().Set("Content-Type", "application/json")
+		w.Write(encodedBlessings)
+	case base64VomFormat:
+		encodedBlessings, err := a.encodeBlessingsVom(blessings)
+		if err != nil {
+			a.ctx.Info("Failed to encode blessings [%v] for request %#v", err, r)
+			util.HTTPServerError(w, fmt.Errorf("failed to encode blessings in format %v: %v", outputFormat, err))
+			return
+		}
+		w.Header().Set("Content-Type", "application/text")
+		w.Write([]byte(encodedBlessings))
+	default:
+		a.ctx.Info("Unrecognized output format [%v] in request %#v", outputFormat, r)
+		util.HTTPServerError(w, fmt.Errorf("unrecognized output format [%v] in request. Allowed formats are [%v, %v]", outputFormat, base64VomFormat, jsonFormat))
+		return
+	}
+}
diff --git a/services/identity/internal/handlers/blessing_root.go b/services/identity/internal/handlers/blessing_root.go
new file mode 100644
index 0000000..0a043cb
--- /dev/null
+++ b/services/identity/internal/handlers/blessing_root.go
@@ -0,0 +1,69 @@
+// 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 handlers
+
+import (
+	"encoding/base64"
+	"encoding/json"
+	"net/http"
+
+	"v.io/v23/security"
+	"v.io/x/ref/services/identity/internal/util"
+)
+
+// BlessingRoot is an http.Handler implementation that renders the server's
+// blessing names and public key in a json string.
+type BlessingRoot struct {
+	P security.Principal
+}
+
+// Cached response so we don't have to bless and encode every time somebody
+// hits this route.
+var cachedResponseJson []byte
+
+func (b BlessingRoot) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	if cachedResponseJson != nil {
+		respondJson(w, cachedResponseJson)
+		return
+	}
+
+	// The identity service itself is blessed by a more protected key.
+	// Use the root certificate as the identity provider.
+	//
+	// TODO(ashankar): This is making the assumption that the identity
+	// service has a single blessing, which may not be true in general.
+	// Revisit this.
+	name, der, err := util.RootCertificateDetails(b.P.BlessingStore().Default())
+	if err != nil {
+		util.HTTPServerError(w, err)
+		return
+	}
+	str := base64.URLEncoding.EncodeToString(der)
+
+	// TODO(suharshs): Ideally this struct would be BlessingRootResponse but vdl does
+	// not currently allow field annotations. Once those are allowed, then use that
+	// here.
+	rootInfo := struct {
+		Names     []string `json:"names"`
+		PublicKey string   `json:"publicKey"`
+	}{
+		Names:     []string{name},
+		PublicKey: str,
+	}
+
+	res, err := json.Marshal(rootInfo)
+	if err != nil {
+		util.HTTPServerError(w, err)
+		return
+	}
+
+	cachedResponseJson = res
+	respondJson(w, res)
+}
+
+func respondJson(w http.ResponseWriter, res []byte) {
+	w.Header().Set("Content-Type", "application/json")
+	w.Write(res)
+}
diff --git a/services/identity/internal/handlers/handlers_test.go b/services/identity/internal/handlers/handlers_test.go
new file mode 100644
index 0000000..5046b7f
--- /dev/null
+++ b/services/identity/internal/handlers/handlers_test.go
@@ -0,0 +1,256 @@
+// 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 handlers
+
+import (
+	"bytes"
+	"encoding/base64"
+	"encoding/json"
+	"io/ioutil"
+	"net/http"
+	"net/http/httptest"
+	"net/url"
+	"reflect"
+	"sort"
+	"testing"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/security"
+	"v.io/v23/vom"
+
+	_ "v.io/x/ref/runtime/factories/generic"
+	"v.io/x/ref/services/identity"
+	"v.io/x/ref/services/identity/internal/blesser"
+	"v.io/x/ref/services/identity/internal/oauth"
+	"v.io/x/ref/services/identity/internal/revocation"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/testutil"
+)
+
+func TestBlessingRoot(t *testing.T) {
+	// TODO(ashankar,ataly): Handle multiple root names?
+	blessingNames := []string{"test-root"}
+	p := testutil.NewPrincipal(blessingNames...)
+
+	ts := httptest.NewServer(BlessingRoot{p})
+	defer ts.Close()
+	response, err := http.Get(ts.URL)
+	if err != nil {
+		t.Fatal(err)
+	}
+	dec := json.NewDecoder(response.Body)
+	var res identity.BlessingRootResponse
+	if err := dec.Decode(&res); err != nil {
+		t.Fatal(err)
+	}
+
+	// Check that the names are correct.
+	sort.Strings(blessingNames)
+	sort.Strings(res.Names)
+	if !reflect.DeepEqual(res.Names, blessingNames) {
+		t.Errorf("Response has incorrect name. Got %v, want %v", res.Names, blessingNames)
+	}
+
+	// Check that the public key is correct.
+	gotMarshalled, err := base64.URLEncoding.DecodeString(res.PublicKey)
+	if err != nil {
+		t.Fatal(err)
+	}
+	got, err := security.UnmarshalPublicKey(gotMarshalled)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if want := p.PublicKey(); !reflect.DeepEqual(got, want) {
+		t.Errorf("Response has incorrect public key.  Got %v, want %v", got, want)
+	}
+}
+
+func TestBless(t *testing.T) {
+	var (
+		blesserPrin = testutil.NewPrincipal("blesser")
+		blesseePrin = testutil.NewPrincipal("blessee")
+
+		methodCav, _ = security.NewMethodCaveat("foo")
+		expiryCav, _ = security.NewExpiryCaveat(time.Now().Add(time.Hour))
+
+		mkReqURL = func(baseURLStr string, caveats []security.Caveat, outputFormat string) string {
+			baseURL, err := url.Parse(baseURLStr)
+			if err != nil {
+				t.Fatal(err)
+			}
+			params := url.Values{}
+
+			if len(caveats) != 0 {
+				caveatsVom, err := vom.Encode(caveats)
+				if err != nil {
+					t.Fatal(err)
+				}
+				params.Add(caveatsFormKey, base64.URLEncoding.EncodeToString(caveatsVom))
+			}
+			keyBytes, err := blesseePrin.PublicKey().MarshalBinary()
+			if err != nil {
+				t.Fatal(err)
+			}
+			params.Add(publicKeyFormKey, base64.URLEncoding.EncodeToString(keyBytes))
+			params.Add(tokenFormKey, "mocktoken")
+			params.Add(outputFormatFormKey, outputFormat)
+
+			baseURL.RawQuery = params.Encode()
+			return baseURL.String()
+		}
+
+		vomRoundTrip = func(in, out interface{}) error {
+			data, err := vom.Encode(in)
+			if err != nil {
+				return err
+			}
+			return vom.Decode(data, out)
+		}
+
+		decodeBlessings = func(b []byte, outputFormat string) security.Blessings {
+			if len(outputFormat) == 0 {
+				outputFormat = base64VomFormat
+			}
+			var res security.Blessings
+			switch outputFormat {
+			case base64VomFormat:
+				if raw, err := base64.URLEncoding.DecodeString(string(b)); err != nil {
+					t.Fatal(err)
+				} else if err = vom.Decode(raw, &res); err != nil {
+					t.Fatal(err)
+				}
+			case jsonFormat:
+				var wb security.WireBlessings
+				if err := json.Unmarshal(b, &wb); err != nil {
+					t.Fatal(err)
+				} else if err = vomRoundTrip(wb, &res); err != nil {
+					t.Fatal(err)
+				}
+			}
+			return res
+		}
+	)
+
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	var err error
+	if ctx, err = v23.WithPrincipal(ctx, blesserPrin); err != nil {
+		t.Fatal(err)
+	}
+
+	// Make the blessee trust the blesser's roots
+	if err := blesseePrin.AddToRoots(blesserPrin.BlessingStore().Default()); err != nil {
+		t.Fatal(err)
+	}
+
+	testEmail := "foo@bar.com"
+	testClientID := "test-client-id"
+	revocationManager := revocation.NewMockRevocationManager(ctx)
+	oauthProvider := oauth.NewMockOAuth(testEmail, testClientID)
+
+	testcases := []struct {
+		params  blesser.OAuthBlesserParams
+		caveats []security.Caveat
+	}{
+		{
+			blesser.OAuthBlesserParams{
+				OAuthProvider:    oauthProvider,
+				BlessingDuration: 24 * time.Hour,
+			},
+			nil,
+		},
+		{
+			blesser.OAuthBlesserParams{
+				OAuthProvider:     oauthProvider,
+				RevocationManager: revocationManager,
+			},
+			nil,
+		},
+		{
+			blesser.OAuthBlesserParams{
+				OAuthProvider:     oauthProvider,
+				RevocationManager: revocationManager,
+			},
+			[]security.Caveat{expiryCav, methodCav},
+		},
+	}
+	for _, testcase := range testcases {
+		for _, outputFormat := range []string{jsonFormat, base64VomFormat, ""} {
+			ts := httptest.NewServer(NewOAuthBlessingHandler(ctx, testcase.params))
+			defer ts.Close()
+
+			response, err := http.Get(mkReqURL(ts.URL, testcase.caveats, outputFormat))
+			if err != nil {
+				t.Fatal(err)
+			}
+			b, err := ioutil.ReadAll(response.Body)
+			if err != nil {
+				t.Fatal(err)
+			}
+			blessings := decodeBlessings(b, outputFormat)
+
+			// Blessing should be bound to the blessee.
+			if got, want := blessings.PublicKey(), blesseePrin.PublicKey(); !reflect.DeepEqual(got, want) {
+				t.Errorf("got blessings for public key %v, want blessings for public key %v", got, want)
+			}
+
+			// Verify the name and caveats on the blessings.
+			binfo := blesseePrin.BlessingsInfo(blessings)
+			if len(binfo) != 1 {
+				t.Errorf("got blessings with %d names, want blessings with 1 name", len(binfo))
+			}
+			wantName := "blesser" + security.ChainSeparator + testClientID + security.ChainSeparator + testEmail
+			caveats, ok := binfo[wantName]
+			if !ok {
+				t.Errorf("expected blessing with name %v, got none", wantName)
+			}
+
+			if len(testcase.caveats) > 0 {
+				// The blessing must have exactly those caveats that were provided in the request.
+				if !caveatsMatch(t, caveats, testcase.caveats) {
+					t.Errorf("got blessings with caveats %v, want blessings with caveats %v", caveats, testcase.caveats)
+				}
+			} else if len(caveats) != 1 {
+				t.Errorf("got blessings with %d caveats, want blessings with 1 caveats", len(caveats))
+			} else if testcase.params.RevocationManager != nil && caveats[0].Id != security.PublicKeyThirdPartyCaveat.Id {
+				// The blessing must have a third-party revocation caveat.
+				t.Errorf("got blessings with caveat (%v), want blessings with a PublicKeyThirdPartyCaveat", caveats[0].Id)
+			} else if testcase.params.RevocationManager == nil && caveats[0].Id != security.ExpiryCaveat.Id {
+				// The blessing must have an expiry caveat.
+				t.Errorf("got blessings with caveat (%v), want blessings with an ExpiryCaveat", caveats[0].Id)
+			}
+		}
+	}
+}
+
+type caveatsSorter struct {
+	caveats []security.Caveat
+	t       *testing.T
+}
+
+func (c caveatsSorter) Len() int      { return len(c.caveats) }
+func (c caveatsSorter) Swap(i, j int) { c.caveats[i], c.caveats[j] = c.caveats[j], c.caveats[i] }
+func (c caveatsSorter) Less(i, j int) bool {
+	b_i, err := vom.Encode(c.caveats[i])
+	if err != nil {
+		c.t.Fatal(err)
+	}
+	b_j, err := vom.Encode(c.caveats[j])
+	if err != nil {
+		c.t.Fatal(err)
+	}
+	return bytes.Compare(b_i, b_j) == -1
+}
+
+func caveatsMatch(t *testing.T, got, want []security.Caveat) bool {
+	if len(got) != len(want) {
+		return false
+	}
+	g, w := caveatsSorter{got, t}, caveatsSorter{want, t}
+	sort.Sort(g)
+	sort.Sort(w)
+	return reflect.DeepEqual(g, w)
+}
diff --git a/services/identity/internal/identityd_test/doc.go b/services/identity/internal/identityd_test/doc.go
new file mode 100644
index 0000000..1ab2911
--- /dev/null
+++ b/services/identity/internal/identityd_test/doc.go
@@ -0,0 +1,88 @@
+// 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
+
+/*
+Command identityd_test runs a daemon HTTP server that uses OAuth to create
+security.Blessings objects.
+
+Starts a test version of the identityd server that mocks out oauth, auditing,
+and revocation.
+
+To generate TLS certificates so the HTTP server can use SSL:
+  go run $(go list -f {{.Dir}} "crypto/tls")/generate_cert.go --host <IP address>
+
+Usage:
+   identityd_test [flags]
+
+The identityd_test flags are:
+ -assets-prefix=
+   Host serving the web assets for the identity server.
+ -browser=false
+   Whether to open a browser caveat selector.
+ -external-http-addr=
+   External address on which the HTTP server listens on.  If none is provided
+   the server will only listen on -http-addr.
+ -http-addr=localhost:0
+   Address on which the HTTP server listens on.
+ -mount-prefix=identity
+   Mount name prefix to use.  May be rooted.
+ -oauth-email=testemail@example.com
+   Username for the mock oauth to put in the returned blessings.
+ -tls-config=
+   Comma-separated list of TLS certificate and private key files, in that order.
+   This must be provided.
+
+The global flags are:
+ -alsologtostderr=true
+   log to standard error as well as files
+ -log_backtrace_at=:0
+   when logging hits line file:N, emit a stack trace
+ -log_dir=
+   if non-empty, write log files to this directory
+ -logtostderr=false
+   log to standard error instead of files
+ -max_stack_buf_size=4292608
+   max size in bytes of the buffer to use for logging stack traces
+ -metadata=<just specify -metadata to activate>
+   Displays metadata for the program and exits.
+ -stderrthreshold=2
+   logs at or above this threshold go to stderr
+ -v=0
+   log level for V logs
+ -v23.credentials=
+   directory to use for storing security credentials
+ -v23.i18n-catalogue=
+   18n catalogue files to load, comma separated
+ -v23.namespace.root=[/(dev.v.io/role/vprod/service/mounttabled)@ns.dev.v.io:8101]
+   local namespace root; can be repeated to provided multiple roots
+ -v23.permissions.file=map[]
+   specify a perms file as <name>:<permsfile>
+ -v23.permissions.literal=
+   explicitly specify the runtime perms as a JSON-encoded access.Permissions.
+   Overrides all --v23.permissions.file flags.
+ -v23.proxy=
+   object name of proxy service to use to export services across network
+   boundaries
+ -v23.tcp.address=
+   address to listen on
+ -v23.tcp.protocol=wsh
+   protocol to listen with
+ -v23.vtrace.cache-size=1024
+   The number of vtrace traces to store in memory.
+ -v23.vtrace.collect-regexp=
+   Spans and annotations that match this regular expression will trigger trace
+   collection.
+ -v23.vtrace.dump-on-shutdown=true
+   If true, dump all stored traces on runtime shutdown.
+ -v23.vtrace.sample-rate=0
+   Rate (from 0.0 to 1.0) to sample vtrace traces.
+ -vmodule=
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
+*/
+package main
diff --git a/services/identity/internal/identityd_test/main.go b/services/identity/internal/identityd_test/main.go
new file mode 100644
index 0000000..3b3b152
--- /dev/null
+++ b/services/identity/internal/identityd_test/main.go
@@ -0,0 +1,129 @@
+// 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.
+
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go . -help
+
+package main
+
+import (
+	"flag"
+	"net"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/v23cmd"
+	_ "v.io/x/ref/runtime/factories/static"
+	"v.io/x/ref/services/identity/internal/auditor"
+	"v.io/x/ref/services/identity/internal/blesser"
+	"v.io/x/ref/services/identity/internal/caveats"
+	"v.io/x/ref/services/identity/internal/oauth"
+	"v.io/x/ref/services/identity/internal/revocation"
+	"v.io/x/ref/services/identity/internal/server"
+	"v.io/x/ref/services/identity/internal/util"
+)
+
+var (
+	externalHttpAddr, httpAddr, tlsConfig, assetsPrefix, mountPrefix string
+	browser                                                          bool
+	oauthEmail                                                       string
+)
+
+func init() {
+	// Flags controlling the HTTP server
+	cmdTest.Flags.StringVar(&externalHttpAddr, "external-http-addr", "", "External address on which the HTTP server listens on.  If none is provided the server will only listen on -http-addr.")
+	cmdTest.Flags.StringVar(&httpAddr, "http-addr", "localhost:0", "Address on which the HTTP server listens on.")
+	cmdTest.Flags.StringVar(&tlsConfig, "tls-config", "", "Comma-separated list of TLS certificate and private key files, in that order.  This must be provided.")
+	cmdTest.Flags.StringVar(&assetsPrefix, "assets-prefix", "", "Host serving the web assets for the identity server.")
+	cmdTest.Flags.StringVar(&mountPrefix, "mount-prefix", "identity", "Mount name prefix to use.  May be rooted.")
+	cmdTest.Flags.BoolVar(&browser, "browser", false, "Whether to open a browser caveat selector.")
+	cmdTest.Flags.StringVar(&oauthEmail, "oauth-email", "testemail@example.com", "Username for the mock oauth to put in the returned blessings.")
+}
+
+func main() {
+	cmdline.HideGlobalFlagsExcept()
+	cmdline.Main(cmdTest)
+}
+
+var cmdTest = &cmdline.Command{
+	Runner: v23cmd.RunnerFunc(runIdentityDTest),
+	Name:   "identityd_test",
+	Short:  "Runs HTTP server that creates security.Blessings objects",
+	Long: `
+Command identityd_test runs a daemon HTTP server that uses OAuth to create
+security.Blessings objects.
+
+Starts a test version of the identityd server that mocks out oauth, auditing,
+and revocation.
+
+To generate TLS certificates so the HTTP server can use SSL:
+  go run $(go list -f {{.Dir}} "crypto/tls")/generate_cert.go --host <IP address>
+`,
+}
+
+func runIdentityDTest(ctx *context.T, env *cmdline.Env, args []string) error {
+	// Duration to use for tls cert and blessing duration.
+	duration := 365 * 24 * time.Hour
+
+	// If no tlsConfig has been provided, write and use our own.
+	if flag.Lookup("tls-config").Value.String() == "" {
+		addr := externalHttpAddr
+		if externalHttpAddr == "" {
+			addr = httpAddr
+		}
+		host, _, err := net.SplitHostPort(addr)
+		if err != nil {
+			// NOTE(caprita): The (non-test) identityd binary
+			// accepts an address with no port.  Should this test
+			// binary do the same instead?
+			return env.UsageErrorf("Failed to parse http address %q: %v", addr, err)
+		}
+		certFile, keyFile, err := util.WriteCertAndKey(host, duration)
+		if err != nil {
+			return err
+		}
+		if err := flag.Set("tls-config", certFile+","+keyFile); err != nil {
+			return err
+		}
+	}
+
+	mockClientID := "test-client-id"
+	mockClientName := "test-client"
+
+	auditor, reader := auditor.NewMockBlessingAuditor()
+	revocationManager := revocation.NewMockRevocationManager(ctx)
+	oauthProvider := oauth.NewMockOAuth(oauthEmail, mockClientID)
+
+	params := blesser.OAuthBlesserParams{
+		OAuthProvider:     oauthProvider,
+		BlessingDuration:  duration,
+		RevocationManager: revocationManager,
+		AccessTokenClients: []oauth.AccessTokenClient{
+			oauth.AccessTokenClient{
+				Name:     mockClientName,
+				ClientID: mockClientID,
+			},
+		},
+	}
+
+	caveatSelector := caveats.NewMockCaveatSelector()
+	if browser {
+		caveatSelector = caveats.NewBrowserCaveatSelector(assetsPrefix)
+	}
+
+	listenSpec := v23.GetListenSpec(ctx)
+	s := server.NewIdentityServer(
+		oauthProvider,
+		auditor,
+		reader,
+		revocationManager,
+		params,
+		caveatSelector,
+		assetsPrefix,
+		mountPrefix)
+	s.Serve(ctx, &listenSpec, externalHttpAddr, httpAddr, tlsConfig)
+	return nil
+}
diff --git a/services/identity/internal/oauth/googleoauth.go b/services/identity/internal/oauth/googleoauth.go
new file mode 100644
index 0000000..4f09b09
--- /dev/null
+++ b/services/identity/internal/oauth/googleoauth.go
@@ -0,0 +1,188 @@
+// 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 oauth
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"os"
+
+	"golang.org/x/oauth2"
+
+	"v.io/v23/context"
+)
+
+// googleOAuth implements the OAuthProvider interface with google oauth 2.0.
+type googleOAuth struct {
+	// client_id and client_secret registered with the Google Developer
+	// Console for API access.
+	clientID, clientSecret   string
+	scope, authURL, tokenURL string
+	// URL used to verify google tokens.
+	// (From https://developers.google.com/accounts/docs/OAuth2Login#validatinganidtoken
+	// and https://developers.google.com/accounts/docs/OAuth2UserAgent#validatetoken)
+	verifyURL string
+
+	ctx *context.T
+}
+
+func NewGoogleOAuth(ctx *context.T, configFile string) (OAuthProvider, error) {
+	clientID, clientSecret, err := getOAuthClientIDAndSecret(configFile)
+	if err != nil {
+		return nil, err
+	}
+	return &googleOAuth{
+		clientID:     clientID,
+		clientSecret: clientSecret,
+		scope:        "email",
+		authURL:      "https://accounts.google.com/o/oauth2/auth",
+		tokenURL:     "https://accounts.google.com/o/oauth2/token",
+		verifyURL:    "https://www.googleapis.com/oauth2/v1/tokeninfo?",
+		ctx:          ctx,
+	}, nil
+}
+
+func (g *googleOAuth) AuthURL(redirectUrl, state string, approval AuthURLApproval) string {
+	var opts []oauth2.AuthCodeOption
+	if approval == ExplicitApproval {
+		opts = append(opts, oauth2.ApprovalForce)
+	}
+	return g.oauthConfig(redirectUrl).AuthCodeURL(state, opts...)
+}
+
+// ExchangeAuthCodeForEmail exchanges the authorization code (which must
+// have been obtained with scope=email) for an OAuth token and then uses Google's
+// tokeninfo API to extract the email address from that token.
+func (g *googleOAuth) ExchangeAuthCodeForEmail(authcode string, url string) (string, error) {
+	config := g.oauthConfig(url)
+	t, err := config.Exchange(oauth2.NoContext, authcode)
+	if err != nil {
+		return "", fmt.Errorf("failed to exchange authorization code for token: %v", err)
+	}
+
+	if !t.Valid() {
+		return "", fmt.Errorf("oauth2 token invalid")
+	}
+	// Ideally, would validate the token ourselves without an HTTP roundtrip.
+	// However, for now, as per:
+	// https://developers.google.com/accounts/docs/OAuth2Login#validatinganidtoken
+	// pay an HTTP round-trip to have Google do this.
+	idToken, ok := t.Extra("id_token").(string)
+	if !ok {
+		return "", fmt.Errorf("no GoogleIDToken found in OAuth token")
+	}
+	// The GoogleIDToken is currently validated by sending an HTTP request to
+	// googleapis.com.  This adds a round-trip and service may be denied by
+	// googleapis.com if this handler becomes a breakout success and receives tons
+	// of traffic.  If either is a concern, the GoogleIDToken can be validated
+	// without an additional HTTP request.
+	// See: https://developers.google.com/accounts/docs/OAuth2Login#validatinganidtoken
+	tinfo, err := http.Get(g.verifyURL + "id_token=" + idToken)
+	if err != nil {
+		return "", fmt.Errorf("failed to talk to GoogleIDToken verifier (%q): %v", g.verifyURL, err)
+	}
+	if tinfo.StatusCode != http.StatusOK {
+		return "", fmt.Errorf("failed to verify GoogleIDToken: %s", tinfo.Status)
+	}
+	var gtoken token
+	if err := json.NewDecoder(tinfo.Body).Decode(&gtoken); err != nil {
+		return "", fmt.Errorf("invalid JSON response from Google's tokeninfo API: %v", err)
+	}
+	// We check both "verified_email" and "email_verified" here because the token response sometimes
+	// contains one and sometimes contains the other.
+	if !gtoken.VerifiedEmail && !gtoken.EmailVerified {
+		return "", fmt.Errorf("email not verified: %#v", gtoken)
+	}
+	if gtoken.Issuer != "accounts.google.com" {
+		return "", fmt.Errorf("invalid issuer: %v", gtoken.Issuer)
+	}
+	if gtoken.Audience != config.ClientID {
+		return "", fmt.Errorf("unexpected audience(%v) in GoogleIDToken", gtoken.Audience)
+	}
+	return gtoken.Email, nil
+}
+
+// GetEmailAndClientID uses Google's tokeninfo API to determine the email and clientID
+// associated with the token.
+func (g *googleOAuth) GetEmailAndClientID(accessToken string) (string, string, error) {
+	// As per https://developers.google.com/accounts/docs/OAuth2UserAgent#validatetoken
+	// we obtain the 'info' for the token via an HTTP roundtrip to Google.
+	tokeninfo, err := http.Get(g.verifyURL + "access_token=" + accessToken)
+	if err != nil {
+		return "", "", fmt.Errorf("unable to use token: %v", err)
+	}
+	if tokeninfo.StatusCode != http.StatusOK {
+		return "", "", fmt.Errorf("unable to verify access token, OAuth2 TokenInfo endpoint responded with StatusCode: %v", tokeninfo.StatusCode)
+	}
+	// tokeninfo contains a JSON-encoded struct
+	var token struct {
+		IssuedTo      string `json:"issued_to"`
+		Audience      string `json:"audience"`
+		UserID        string `json:"user_id"`
+		Scope         string `json:"scope"`
+		ExpiresIn     int64  `json:"expires_in"`
+		Email         string `json:"email"`
+		VerifiedEmail bool   `json:"verified_email"`
+		EmailVerified bool   `json:"email_verified"`
+		AccessType    string `json:"access_type"`
+	}
+	if err := json.NewDecoder(tokeninfo.Body).Decode(&token); err != nil {
+		return "", "", fmt.Errorf("invalid JSON response from Google's tokeninfo API: %v", err)
+	}
+	// We check both "verified_email" and "email_verified" here because the token response sometimes
+	// contains one and sometimes contains the other.
+	if !token.VerifiedEmail && !token.EmailVerified {
+		return "", "", fmt.Errorf("email not verified")
+	}
+	return token.Email, token.Audience, nil
+}
+
+func (g *googleOAuth) oauthConfig(redirectUrl string) *oauth2.Config {
+	return &oauth2.Config{
+		ClientID:     g.clientID,
+		ClientSecret: g.clientSecret,
+		RedirectURL:  redirectUrl,
+		Scopes:       []string{g.scope},
+		Endpoint: oauth2.Endpoint{
+			AuthURL:  g.authURL,
+			TokenURL: g.tokenURL,
+		},
+	}
+}
+
+func getOAuthClientIDAndSecret(configFile string) (clientID, clientSecret string, err error) {
+	f, err := os.Open(configFile)
+	if err != nil {
+		return "", "", fmt.Errorf("failed to open %q: %v", configFile, err)
+	}
+	defer f.Close()
+	clientID, clientSecret, err = ClientIDAndSecretFromJSON(f)
+	if err != nil {
+		return "", "", fmt.Errorf("failed to decode JSON in %q: %v", configFile, err)
+	}
+	return clientID, clientSecret, nil
+}
+
+// IDToken JSON message returned by Google's verification endpoint.
+//
+// This differs from the description in:
+// https://developers.google.com/accounts/docs/OAuth2Login#obtainuserinfo
+// because the Google tokeninfo endpoint
+// (https://www.googleapis.com/oauth2/v1/tokeninfo?id_token=XYZ123)
+// mentioned in:
+// https://developers.google.com/accounts/docs/OAuth2Login#validatinganidtoken
+// seems to return the following JSON message.
+type token struct {
+	Issuer        string `json:"issuer"`
+	IssuedTo      string `json:"issued_to"`
+	Audience      string `json:"audience"`
+	UserID        string `json:"user_id"`
+	ExpiresIn     int64  `json:"expires_in"`
+	IssuedAt      int64  `json:"issued_at"`
+	Email         string `json:"email"`
+	VerifiedEmail bool   `json:"verified_email"`
+	EmailVerified bool   `json:"email_verified"`
+}
diff --git a/services/identity/internal/oauth/handler.go b/services/identity/internal/oauth/handler.go
new file mode 100644
index 0000000..deb7bf5
--- /dev/null
+++ b/services/identity/internal/oauth/handler.go
@@ -0,0 +1,489 @@
+// 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 oauth implements an http.Handler that has two main purposes
+// listed below:
+//
+// (1) Uses OAuth to authenticate and then renders a page that
+//     displays all the blessings that were provided for that Google user.
+//     The client calls the /listblessings route which redirects to listblessingscallback which
+//     renders the list.
+// (2) Performs the oauth flow for seeking a blessing using the principal tool
+//     located at v.io/x/ref/cmd/principal.
+//     The seek blessing flow works as follows:
+//     (a) Client (principal tool) hits the /seekblessings route.
+//     (b) /seekblessings performs oauth with a redirect to /seekblessingscallback.
+//     (c) Client specifies desired caveats in the form that /seekblessingscallback displays.
+//     (d) Submission of the form sends caveat information to /sendmacaroon.
+//     (e) /sendmacaroon sends a macaroon with blessing information to client
+//         (via a redirect to an HTTP server run by the tool).
+//     (f) Client invokes bless rpc with macaroon.
+
+package oauth
+
+import (
+	"encoding/base64"
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"net"
+	"net/http"
+	"net/url"
+	"path"
+	"strings"
+	"time"
+
+	"v.io/v23/context"
+	"v.io/v23/security"
+	"v.io/v23/vom"
+	"v.io/x/ref/services/identity/internal/auditor"
+	"v.io/x/ref/services/identity/internal/caveats"
+	"v.io/x/ref/services/identity/internal/revocation"
+	"v.io/x/ref/services/identity/internal/templates"
+	"v.io/x/ref/services/identity/internal/util"
+)
+
+const (
+	clientIDCookie = "VeyronHTTPIdentityClientID"
+
+	ListBlessingsRoute         = "listblessings"
+	listBlessingsCallbackRoute = "listblessingscallback"
+	revokeRoute                = "revoke"
+	SeekBlessingsRoute         = "seekblessings"
+	addCaveatsRoute            = "addcaveats"
+	sendMacaroonRoute          = "sendmacaroon"
+)
+
+type HandlerArgs struct {
+	// The principal to use.
+	Principal security.Principal
+	// The Key that is used for creating and verifying macaroons.
+	// This needs to be common between the handler and the MacaroonBlesser service.
+	MacaroonKey []byte
+	// URL at which the hander is installed.
+	// e.g. http://host:port/google/
+	Addr string
+	// BlessingLogReder is needed for reading audit logs.
+	BlessingLogReader auditor.BlessingLogReader
+	// The RevocationManager is used to revoke blessings granted with a revocation caveat.
+	// If nil, then revocation caveats cannot be added to blessings and an expiration caveat
+	// will be used instead.
+	RevocationManager revocation.RevocationManager
+	// The object name of the discharger service.
+	DischargerLocation string
+	// MacaroonBlessingService is the object name to which macaroons create by this HTTP
+	// handler can be exchanged for a blessing.
+	MacaroonBlessingService string
+	// OAuthProvider is used to authenticate and get a blessee email.
+	OAuthProvider OAuthProvider
+	// CaveatSelector is used to obtain caveats from the user when seeking a blessing.
+	CaveatSelector caveats.CaveatSelector
+	// AssetsPrefix is the host where web assets for rendering the list blessings template are stored.
+	AssetsPrefix string
+	// GoogleServers is the list of published Google blessings services.
+	GoogleServers []string
+	// DischargeServers is the list of published disharges services.
+	DischargeServers []string
+}
+
+// BlessingMacaroon contains the data that is encoded into the macaroon for creating blessings.
+type BlessingMacaroon struct {
+	Creation  time.Time
+	Caveats   []security.Caveat
+	Name      string
+	PublicKey []byte // Marshaled public key of the principal tool.
+}
+
+func redirectURL(baseURL, suffix string) string {
+	if !strings.HasSuffix(baseURL, "/") {
+		baseURL += "/"
+	}
+	return baseURL + suffix
+}
+
+// NewHandler returns an http.Handler that expects to be rooted at args.Addr
+// and can be used to authenticate with args.OAuthProvider, mint a new
+// identity and bless it with the OAuthProvider email address.
+func NewHandler(ctx *context.T, args HandlerArgs) (http.Handler, error) {
+	csrfCop, err := util.NewCSRFCop(ctx)
+	if err != nil {
+		return nil, fmt.Errorf("NewHandler failed to create csrfCop: %v", err)
+	}
+	return &handler{
+		args:    args,
+		csrfCop: csrfCop,
+		ctx:     ctx,
+	}, nil
+}
+
+type handler struct {
+	args    HandlerArgs
+	csrfCop *util.CSRFCop
+	ctx     *context.T
+}
+
+func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	switch path.Base(r.URL.Path) {
+	case ListBlessingsRoute:
+		h.listBlessings(h.ctx, w, r)
+	case listBlessingsCallbackRoute:
+		h.listBlessingsCallback(h.ctx, w, r)
+	case revokeRoute:
+		h.revoke(h.ctx, w, r)
+	case SeekBlessingsRoute:
+		h.seekBlessings(h.ctx, w, r)
+	case addCaveatsRoute:
+		h.addCaveats(h.ctx, w, r)
+	case sendMacaroonRoute:
+		h.sendMacaroon(h.ctx, w, r)
+	default:
+		util.HTTPBadRequest(w, r, nil)
+	}
+}
+
+func (h *handler) listBlessings(ctx *context.T, w http.ResponseWriter, r *http.Request) {
+	csrf, err := h.csrfCop.NewToken(w, r, clientIDCookie, nil)
+	if err != nil {
+		ctx.Infof("Failed to create CSRF token[%v] for request %#v", err, r)
+		util.HTTPServerError(w, fmt.Errorf("failed to create new token: %v", err))
+		return
+	}
+	http.Redirect(w, r, h.args.OAuthProvider.AuthURL(redirectURL(h.args.Addr, listBlessingsCallbackRoute), csrf, ReuseApproval), http.StatusFound)
+}
+
+func (h *handler) listBlessingsCallback(ctx *context.T, w http.ResponseWriter, r *http.Request) {
+	if err := h.csrfCop.ValidateToken(r.FormValue("state"), r, clientIDCookie, nil); err != nil {
+		ctx.Infof("Invalid CSRF token: %v in request: %#v", err, r)
+		util.HTTPBadRequest(w, r, fmt.Errorf("Suspected request forgery: %v", err))
+		return
+	}
+	email, err := h.args.OAuthProvider.ExchangeAuthCodeForEmail(r.FormValue("code"), redirectURL(h.args.Addr, listBlessingsCallbackRoute))
+	if err != nil {
+		util.HTTPBadRequest(w, r, err)
+		return
+	}
+
+	type tmplentry struct {
+		Timestamp      time.Time
+		Caveats        []string
+		RevocationTime time.Time
+		Blessed        security.Blessings
+		Token          string
+		Error          error
+	}
+	tmplargs := struct {
+		Log                              chan tmplentry
+		Email, RevokeRoute, AssetsPrefix string
+		Self                             security.Blessings
+		GoogleServers, DischargeServers  []string
+	}{
+		Log:              make(chan tmplentry),
+		Email:            email,
+		RevokeRoute:      revokeRoute,
+		AssetsPrefix:     h.args.AssetsPrefix,
+		Self:             h.args.Principal.BlessingStore().Default(),
+		GoogleServers:    h.args.GoogleServers,
+		DischargeServers: h.args.DischargeServers,
+	}
+	entrych := h.args.BlessingLogReader.Read(ctx, email)
+
+	w.Header().Set("Context-Type", "text/html")
+	// This MaybeSetCookie call is needed to ensure that a cookie is created. Since the
+	// header cannot be changed once the body is written to, this needs to be called first.
+	if _, err = h.csrfCop.MaybeSetCookie(w, r, clientIDCookie); err != nil {
+		ctx.Infof("Failed to set CSRF cookie[%v] for request %#v", err, r)
+		util.HTTPServerError(w, err)
+		return
+	}
+	go func(ch chan tmplentry) {
+		defer close(ch)
+		for entry := range entrych {
+			tmplEntry := tmplentry{
+				Error:     entry.DecodeError,
+				Timestamp: entry.Timestamp,
+				Blessed:   entry.Blessings,
+			}
+			if len(entry.Caveats) > 0 {
+				if tmplEntry.Caveats, err = prettyPrintCaveats(entry.Caveats); err != nil {
+					ctx.Errorf("Failed to pretty print caveats: %v", err)
+					tmplEntry.Error = fmt.Errorf("failed to pretty print caveats: %v", err)
+				}
+			}
+			if len(entry.RevocationCaveatID) > 0 && h.args.RevocationManager != nil {
+				if revocationTime := h.args.RevocationManager.GetRevocationTime(entry.RevocationCaveatID); revocationTime != nil {
+					tmplEntry.RevocationTime = *revocationTime
+				} else {
+					caveatID := base64.URLEncoding.EncodeToString([]byte(entry.RevocationCaveatID))
+					if tmplEntry.Token, err = h.csrfCop.NewToken(w, r, clientIDCookie, caveatID); err != nil {
+						ctx.Errorf("Failed to create CSRF token[%v] for request %#v", err, r)
+						tmplEntry.Error = fmt.Errorf("server error: unable to create revocation token")
+					}
+				}
+			}
+			ch <- tmplEntry
+		}
+	}(tmplargs.Log)
+	if err := templates.ListBlessings.Execute(w, tmplargs); err != nil {
+		ctx.Errorf("Unable to execute audit page template: %v", err)
+		util.HTTPServerError(w, err)
+	}
+}
+
+// prettyPrintCaveats returns a user friendly string for vanadium standard caveat.
+// Unrecognized caveats will fall back to the Caveat's String() method.
+func prettyPrintCaveats(cavs []security.Caveat) ([]string, error) {
+	s := make([]string, len(cavs))
+	for i, cav := range cavs {
+		if cav.Id == security.PublicKeyThirdPartyCaveat.Id {
+			c := cav.ThirdPartyDetails()
+			s[i] = fmt.Sprintf("ThirdPartyCaveat: Requires discharge from %v (ID=%q)", c.Location(), c.ID())
+			continue
+		}
+
+		var param interface{}
+		if err := vom.Decode(cav.ParamVom, &param); err != nil {
+			return nil, err
+		}
+		switch cav.Id {
+		case security.ExpiryCaveat.Id:
+			s[i] = fmt.Sprintf("Expires at %v", param)
+		case security.MethodCaveat.Id:
+			s[i] = fmt.Sprintf("Restricted to methods %v", param)
+		case security.PeerBlessingsCaveat.Id:
+			s[i] = fmt.Sprintf("Restricted to peers with blessings %v", param)
+		default:
+			s[i] = cav.String()
+		}
+	}
+	return s, nil
+}
+
+func (h *handler) revoke(ctx *context.T, w http.ResponseWriter, r *http.Request) {
+	w.Header().Set("Content-Type", "application/json")
+	const (
+		success = `{"success": "true"}`
+		failure = `{"success": "false"}`
+	)
+	if h.args.RevocationManager == nil {
+		ctx.Infof("no provided revocation manager")
+		w.Write([]byte(failure))
+		return
+	}
+
+	content, err := ioutil.ReadAll(r.Body)
+	if err != nil {
+		ctx.Infof("Failed to parse request: %s", err)
+		w.Write([]byte(failure))
+		return
+	}
+	var requestParams struct {
+		Token string
+	}
+	if err := json.Unmarshal(content, &requestParams); err != nil {
+		ctx.Infof("json.Unmarshal failed : %s", err)
+		w.Write([]byte(failure))
+		return
+	}
+
+	var caveatID string
+	if caveatID, err = h.validateRevocationToken(ctx, requestParams.Token, r); err != nil {
+		ctx.Infof("failed to validate token for caveat: %s", err)
+		w.Write([]byte(failure))
+		return
+	}
+	if err := h.args.RevocationManager.Revoke(caveatID); err != nil {
+		ctx.Infof("Revocation failed: %s", err)
+		w.Write([]byte(failure))
+		return
+	}
+
+	w.Write([]byte(success))
+	return
+}
+
+func (h *handler) validateRevocationToken(ctx *context.T, Token string, r *http.Request) (string, error) {
+	var encCaveatID string
+	if err := h.csrfCop.ValidateToken(Token, r, clientIDCookie, &encCaveatID); err != nil {
+		return "", fmt.Errorf("invalid CSRF token: %v in request: %#v", err, r)
+	}
+	caveatID, err := base64.URLEncoding.DecodeString(encCaveatID)
+	if err != nil {
+		return "", fmt.Errorf("decode caveatID failed: %v", err)
+	}
+	return string(caveatID), nil
+}
+
+type seekBlessingsMacaroon struct {
+	RedirectURL, State string
+	PublicKey          []byte // Marshaled public key of the principal tool.
+}
+
+func validLoopbackURL(u string) (*url.URL, error) {
+	netURL, err := url.Parse(u)
+	if err != nil {
+		return nil, fmt.Errorf("invalid url: %v", err)
+	}
+	// Remove the port from the netURL.Host.
+	host, _, err := net.SplitHostPort(netURL.Host)
+	// Check if its localhost or loopback ip
+	if host == "localhost" {
+		return netURL, nil
+	}
+	urlIP := net.ParseIP(host)
+	if urlIP.IsLoopback() {
+		return netURL, nil
+	}
+	return nil, fmt.Errorf("invalid loopback url")
+}
+
+func (h *handler) seekBlessings(ctx *context.T, w http.ResponseWriter, r *http.Request) {
+	redirect := r.FormValue("redirect_url")
+	if _, err := validLoopbackURL(redirect); err != nil {
+		ctx.Infof("seekBlessings failed: invalid redirect_url: %v", err)
+		util.HTTPBadRequest(w, r, fmt.Errorf("invalid redirect_url: %v", err))
+		return
+	}
+	pubKeyBytes, err := base64.URLEncoding.DecodeString(r.FormValue("public_key"))
+	if err != nil {
+		ctx.Infof("seekBlessings failed: invalid public_key: %v", err)
+		util.HTTPBadRequest(w, r, fmt.Errorf("invalid public_key: %v", err))
+		return
+	}
+	outputMacaroon, err := h.csrfCop.NewToken(w, r, clientIDCookie, seekBlessingsMacaroon{
+		RedirectURL: redirect,
+		State:       r.FormValue("state"),
+		PublicKey:   pubKeyBytes,
+	})
+	if err != nil {
+		ctx.Infof("Failed to create CSRF token[%v] for request %#v", err, r)
+		util.HTTPServerError(w, fmt.Errorf("failed to create new token: %v", err))
+		return
+	}
+	http.Redirect(w, r, h.args.OAuthProvider.AuthURL(redirectURL(h.args.Addr, addCaveatsRoute), outputMacaroon, ExplicitApproval), http.StatusFound)
+}
+
+type addCaveatsMacaroon struct {
+	ToolRedirectURL, ToolState, Email string
+	ToolPublicKey                     []byte // Marshaled public key of the principal tool.
+}
+
+func (h *handler) addCaveats(ctx *context.T, w http.ResponseWriter, r *http.Request) {
+	var inputMacaroon seekBlessingsMacaroon
+	if err := h.csrfCop.ValidateToken(r.FormValue("state"), r, clientIDCookie, &inputMacaroon); err != nil {
+		util.HTTPBadRequest(w, r, fmt.Errorf("Suspected request forgery: %v", err))
+		return
+	}
+	email, err := h.args.OAuthProvider.ExchangeAuthCodeForEmail(r.FormValue("code"), redirectURL(h.args.Addr, addCaveatsRoute))
+	if err != nil {
+		util.HTTPBadRequest(w, r, err)
+		return
+	}
+	outputMacaroon, err := h.csrfCop.NewToken(w, r, clientIDCookie, addCaveatsMacaroon{
+		ToolRedirectURL: inputMacaroon.RedirectURL,
+		ToolState:       inputMacaroon.State,
+		ToolPublicKey:   inputMacaroon.PublicKey,
+		Email:           email,
+	})
+	if err != nil {
+		ctx.Infof("Failed to create caveatForm token[%v] for request %#v", err, r)
+		util.HTTPServerError(w, fmt.Errorf("failed to create new token: %v", err))
+		return
+	}
+	localBlessings := security.DefaultBlessingPatterns(h.args.Principal)
+	if len(localBlessings) == 0 {
+		ctx.Infof("server principal has no blessings: %v", h.args.Principal)
+		util.HTTPServerError(w, fmt.Errorf("failed to get server blessings"))
+		return
+	}
+	fullBlessingName := strings.Join([]string{string(localBlessings[0]), email}, security.ChainSeparator)
+	if err := h.args.CaveatSelector.Render(fullBlessingName, outputMacaroon, redirectURL(h.args.Addr, sendMacaroonRoute), w, r); err != nil {
+		ctx.Errorf("Unable to invoke render caveat selector: %v", err)
+		util.HTTPServerError(w, err)
+	}
+}
+
+func (h *handler) sendMacaroon(ctx *context.T, w http.ResponseWriter, r *http.Request) {
+	var inputMacaroon addCaveatsMacaroon
+	caveatInfos, macaroonString, blessingExtension, err := h.args.CaveatSelector.ParseSelections(r)
+	cancelled := err == caveats.ErrSeekblessingsCancelled
+	if !cancelled && err != nil {
+		util.HTTPBadRequest(w, r, fmt.Errorf("failed to parse blessing information: %v", err))
+		return
+	}
+	if err := h.csrfCop.ValidateToken(macaroonString, r, clientIDCookie, &inputMacaroon); err != nil {
+		util.HTTPBadRequest(w, r, fmt.Errorf("suspected request forgery: %v", err))
+		return
+	}
+	// Construct the url to send back to the tool.
+	baseURL, err := validLoopbackURL(inputMacaroon.ToolRedirectURL)
+	if err != nil {
+		util.HTTPBadRequest(w, r, fmt.Errorf("invalid ToolRedirectURL: %v", err))
+		return
+	}
+	// Now that we have a valid tool redirect url, we can send the errors to the tool.
+	if cancelled {
+		h.sendErrorToTool(ctx, w, r, inputMacaroon.ToolState, baseURL, caveats.ErrSeekblessingsCancelled)
+	}
+	caveats, err := h.caveats(ctx, caveatInfos)
+	if err != nil {
+		h.sendErrorToTool(ctx, w, r, inputMacaroon.ToolState, baseURL, fmt.Errorf("failed to create caveats: %v", err))
+		return
+	}
+	parts := []string{inputMacaroon.Email}
+	if len(blessingExtension) > 0 {
+		parts = append(parts, blessingExtension)
+	}
+	if len(caveats) == 0 {
+		h.sendErrorToTool(ctx, w, r, inputMacaroon.ToolState, baseURL, fmt.Errorf("server disallows attempts to bless with no caveats"))
+		return
+	}
+	m := BlessingMacaroon{
+		Creation:  time.Now(),
+		Caveats:   caveats,
+		Name:      strings.Join(parts, security.ChainSeparator),
+		PublicKey: inputMacaroon.ToolPublicKey,
+	}
+	macBytes, err := vom.Encode(m)
+	if err != nil {
+		h.sendErrorToTool(ctx, w, r, inputMacaroon.ToolState, baseURL, fmt.Errorf("failed to encode BlessingsMacaroon: %v", err))
+		return
+	}
+	marshalKey, err := h.args.Principal.PublicKey().MarshalBinary()
+	if err != nil {
+		h.sendErrorToTool(ctx, w, r, inputMacaroon.ToolState, baseURL, fmt.Errorf("failed to marshal public key: %v", err))
+		return
+	}
+	encKey := base64.URLEncoding.EncodeToString(marshalKey)
+	params := url.Values{}
+	params.Add("macaroon", string(util.NewMacaroon(h.args.MacaroonKey, macBytes)))
+	params.Add("state", inputMacaroon.ToolState)
+	params.Add("object_name", h.args.MacaroonBlessingService)
+	params.Add("root_key", encKey)
+	baseURL.RawQuery = params.Encode()
+	http.Redirect(w, r, baseURL.String(), http.StatusFound)
+}
+
+func (h *handler) sendErrorToTool(ctx *context.T, w http.ResponseWriter, r *http.Request, toolState string, baseURL *url.URL, err error) {
+	errEnc := base64.URLEncoding.EncodeToString([]byte(err.Error()))
+	params := url.Values{}
+	params.Add("error", errEnc)
+	params.Add("state", toolState)
+	baseURL.RawQuery = params.Encode()
+	http.Redirect(w, r, baseURL.String(), http.StatusFound)
+}
+
+func (h *handler) caveats(ctx *context.T, caveatInfos []caveats.CaveatInfo) (cavs []security.Caveat, err error) {
+	caveatFactories := caveats.NewCaveatFactory()
+	for _, caveatInfo := range caveatInfos {
+		if caveatInfo.Type == "Revocation" {
+			caveatInfo.Args = []interface{}{h.args.RevocationManager, h.args.Principal.PublicKey(), h.args.DischargerLocation}
+		}
+		cav, err := caveatFactories.New(caveatInfo)
+		if err != nil {
+			return nil, err
+		}
+		cavs = append(cavs, cav)
+	}
+	return
+}
diff --git a/services/identity/internal/oauth/mockoauth.go b/services/identity/internal/oauth/mockoauth.go
new file mode 100644
index 0000000..897fc69
--- /dev/null
+++ b/services/identity/internal/oauth/mockoauth.go
@@ -0,0 +1,27 @@
+// 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 oauth
+
+// mockOAuth is a mock OAuthProvider for use in tests.
+type mockOAuth struct {
+	email    string
+	clientID string
+}
+
+func NewMockOAuth(mockEmail, mockClientID string) OAuthProvider {
+	return &mockOAuth{email: mockEmail, clientID: mockClientID}
+}
+
+func (m *mockOAuth) AuthURL(redirectUrl string, state string, _ AuthURLApproval) string {
+	return redirectUrl + "?state=" + state
+}
+
+func (m *mockOAuth) ExchangeAuthCodeForEmail(string, string) (string, error) {
+	return m.email, nil
+}
+
+func (m *mockOAuth) GetEmailAndClientID(string) (string, string, error) {
+	return m.email, m.clientID, nil
+}
diff --git a/services/identity/internal/oauth/oauth_provider.go b/services/identity/internal/oauth/oauth_provider.go
new file mode 100644
index 0000000..1928a54
--- /dev/null
+++ b/services/identity/internal/oauth/oauth_provider.go
@@ -0,0 +1,25 @@
+// 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 oauth
+
+// Option to OAuthProvider.AuthURL controlling whether previously provided user consent can be re-used.
+type AuthURLApproval bool
+
+const (
+	ExplicitApproval AuthURLApproval = false // Require explicit user consent.
+	ReuseApproval    AuthURLApproval = true  // Reuse a previous user consent if possible.
+)
+
+// OAuthProvider authenticates users to the identity server via the OAuth2 Web Server flow.
+type OAuthProvider interface {
+	// AuthURL is the URL the user must visit in order to authenticate with the OAuthProvider.
+	// After authentication, the user will be re-directed to redirectURL with the provided state.
+	AuthURL(redirectUrl string, state string, approval AuthURLApproval) (url string)
+	// ExchangeAuthCodeForEmail exchanges the provided authCode for the email of the
+	// authenticated user on behalf of the token has been issued.
+	ExchangeAuthCodeForEmail(authCode string, url string) (email string, err error)
+	// GetEmailAndClientID returns the email and clientID associated with the token.
+	GetEmailAndClientID(accessToken string) (email string, clientID string, err error)
+}
diff --git a/services/identity/internal/oauth/utils.go b/services/identity/internal/oauth/utils.go
new file mode 100644
index 0000000..cf4e341
--- /dev/null
+++ b/services/identity/internal/oauth/utils.go
@@ -0,0 +1,89 @@
+// 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 oauth
+
+import (
+	"encoding/json"
+	"fmt"
+	"io"
+)
+
+// AccessTokenClient represents a client of an OAuthProvider.
+type AccessTokenClient struct {
+	// Descriptive name of the client.
+	Name string
+	// OAuth Client ID.
+	ClientID string
+}
+
+// ClientIDFromJSON parses JSON-encoded API access information in 'r' and returns
+// the extracted ClientID.
+// This JSON-encoded data is typically available as a download from the Google
+// API Access console for your application
+// (https://code.google.com/apis/console).
+func ClientIDFromJSON(r io.Reader) (id string, err error) {
+	var data map[string]interface{}
+	var typ string
+	if data, typ, err = decodeAccessMapFromJSON(r); err != nil {
+		return
+	}
+	var ok bool
+	if id, ok = data["client_id"].(string); !ok {
+		err = fmt.Errorf("%s.client_id not found", typ)
+		return
+	}
+	return
+}
+
+// ClientIDAndSecretFromJSON parses JSON-encoded API access information in 'r'
+// and returns the extracted ClientID and ClientSecret.
+// This JSON-encoded data is typically available as a download from the Google
+// API Access console for your application
+// (https://code.google.com/apis/console).
+func ClientIDAndSecretFromJSON(r io.Reader) (id, secret string, err error) {
+	var data map[string]interface{}
+	var typ string
+	if data, typ, err = decodeAccessMapFromJSON(r); err != nil {
+		return
+	}
+	var ok bool
+	if id, ok = data["client_id"].(string); !ok {
+		err = fmt.Errorf("%s.client_id not found", typ)
+		return
+	}
+	if secret, ok = data["client_secret"].(string); !ok {
+		err = fmt.Errorf("%s.client_secret not found", typ)
+		return
+	}
+	return
+}
+
+// ClientName checks if the provided clientID is present in one of the provided
+// 'clients' and if so returns the corresponding client name. It returns an error
+// otherwise.
+func ClientName(clientID string, clients []AccessTokenClient) (string, error) {
+	for _, c := range clients {
+		if clientID == c.ClientID {
+			return c.Name, nil
+		}
+	}
+	return "", fmt.Errorf("unrecognized client ID, confused deputy? https://developers.google.com/accounts/docs/OAuth2UserAgent#validatetoken")
+}
+
+func decodeAccessMapFromJSON(r io.Reader) (data map[string]interface{}, typ string, err error) {
+	var full map[string]interface{}
+	if err = json.NewDecoder(r).Decode(&full); err != nil {
+		return
+	}
+	var ok bool
+	typ = "web"
+	if data, ok = full[typ].(map[string]interface{}); !ok {
+		typ = "installed"
+		if data, ok = full[typ].(map[string]interface{}); !ok {
+			err = fmt.Errorf("web or installed configuration not found")
+		}
+	}
+	return
+}
diff --git a/services/identity/internal/oauth/utils_test.go b/services/identity/internal/oauth/utils_test.go
new file mode 100644
index 0000000..075fba7
--- /dev/null
+++ b/services/identity/internal/oauth/utils_test.go
@@ -0,0 +1,24 @@
+// 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 oauth
+
+import (
+	"strings"
+	"testing"
+)
+
+func TestClientIDAndSecretFromJSON(t *testing.T) {
+	json := `{"web":{"auth_uri":"https://accounts.google.com/o/oauth2/auth","client_secret":"SECRET","token_uri":"https://accounts.google.com/o/oauth2/token","client_email":"EMAIL","redirect_uris":["http://redirecturl"],"client_id":"ID","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","javascript_origins":["http://javascriptorigins"]}}`
+	id, secret, err := ClientIDAndSecretFromJSON(strings.NewReader(json))
+	if err != nil {
+		t.Error(err)
+	}
+	if id != "ID" {
+		t.Errorf("Got %q want %q", id, "ID")
+	}
+	if secret != "SECRET" {
+		t.Errorf("Got %q want %q", secret, "SECRET")
+	}
+}
diff --git a/services/identity/internal/rest_signer_test/main.go b/services/identity/internal/rest_signer_test/main.go
new file mode 100644
index 0000000..15e55ff
--- /dev/null
+++ b/services/identity/internal/rest_signer_test/main.go
@@ -0,0 +1,35 @@
+// 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 (
+	"encoding/base64"
+	"fmt"
+	"math/big"
+
+	"v.io/x/ref/services/identity/internal/server"
+)
+
+func main() {
+	signer, err := server.NewRestSigner()
+	if err != nil {
+		fmt.Printf("NewRestSigner error: %v\n", err)
+		return
+	}
+	der, err := signer.PublicKey().MarshalBinary()
+	if err != nil {
+		fmt.Printf("Failed to marshal public key: %v\n", err)
+		return
+	}
+	sig, err := signer.Sign([]byte("purpose"), []byte("message"))
+	if err != nil {
+		fmt.Printf("Sign error: %v\n", err)
+		return
+	}
+	ok := sig.Verify(signer.PublicKey(), []byte("message"))
+	fmt.Printf("PublicKey: %v\n", base64.URLEncoding.EncodeToString(der))
+	fmt.Printf("R: %v\n", big.NewInt(0).SetBytes(sig.R))
+	fmt.Printf("S: %v\n", big.NewInt(0).SetBytes(sig.S))
+	fmt.Printf("Verified: %v\n", ok)
+}
diff --git a/services/identity/internal/revocation/caveat.vdl b/services/identity/internal/revocation/caveat.vdl
new file mode 100644
index 0000000..fb579c9
--- /dev/null
+++ b/services/identity/internal/revocation/caveat.vdl
@@ -0,0 +1,21 @@
+// 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 revocation
+
+import (
+	"v.io/v23/uniqueid"
+	"v.io/v23/security"
+)
+
+// NotRevokedCaveat is used to implement revocation.
+// It validates iff the parameter is not included in a list of blacklisted
+// values.
+//
+// The third-party discharging service checks this revocation caveat against a
+// database of blacklisted (revoked) keys before issuing a discharge.
+const NotRevokedCaveat = security.CaveatDescriptor{
+    Id:        uniqueid.Id{0x4b, 0x46, 0x5c, 0x56, 0x37, 0x79, 0xd1, 0x3b, 0x7b, 0xa3, 0xa7, 0xd6, 0xa5, 0x34, 0x80, 0x0},
+    ParamType: typeobject([]byte),
+}
diff --git a/services/identity/internal/revocation/caveat.vdl.go b/services/identity/internal/revocation/caveat.vdl.go
new file mode 100644
index 0000000..713a6d1
--- /dev/null
+++ b/services/identity/internal/revocation/caveat.vdl.go
@@ -0,0 +1,45 @@
+// 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 by the vanadium vdl tool.
+// Source: caveat.vdl
+
+package revocation
+
+import (
+	// VDL system imports
+	"v.io/v23/vdl"
+
+	// VDL user imports
+	"v.io/v23/security"
+	"v.io/v23/uniqueid"
+)
+
+// NotRevokedCaveat is used to implement revocation.
+// It validates iff the parameter is not included in a list of blacklisted
+// values.
+//
+// The third-party discharging service checks this revocation caveat against a
+// database of blacklisted (revoked) keys before issuing a discharge.
+var NotRevokedCaveat = security.CaveatDescriptor{
+	Id: uniqueid.Id{
+		75,
+		70,
+		92,
+		86,
+		55,
+		121,
+		209,
+		59,
+		123,
+		163,
+		167,
+		214,
+		165,
+		52,
+		128,
+		0,
+	},
+	ParamType: vdl.TypeOf([]byte(nil)),
+}
diff --git a/services/identity/internal/revocation/mock_revocation_manager.go b/services/identity/internal/revocation/mock_revocation_manager.go
new file mode 100644
index 0000000..959443d
--- /dev/null
+++ b/services/identity/internal/revocation/mock_revocation_manager.go
@@ -0,0 +1,41 @@
+// 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 revocation
+
+import (
+	"time"
+
+	"v.io/v23/context"
+)
+
+func NewMockRevocationManager(ctx *context.T) RevocationManager {
+	revocationDB = &mockDatabase{make(map[string][]byte), make(map[string]*time.Time)}
+	return &revocationManager{ctx}
+}
+
+type mockDatabase struct {
+	tpCavIDToRevCavID   map[string][]byte
+	revCavIDToTimestamp map[string]*time.Time
+}
+
+func (m *mockDatabase) InsertCaveat(thirdPartyCaveatID string, revocationCaveatID []byte) error {
+	m.tpCavIDToRevCavID[thirdPartyCaveatID] = revocationCaveatID
+	return nil
+}
+
+func (m *mockDatabase) Revoke(thirdPartyCaveatID string) error {
+	timestamp := time.Now()
+	m.revCavIDToTimestamp[string(m.tpCavIDToRevCavID[thirdPartyCaveatID])] = &timestamp
+	return nil
+}
+
+func (m *mockDatabase) IsRevoked(revocationCaveatID []byte) (bool, error) {
+	_, exists := m.revCavIDToTimestamp[string(revocationCaveatID)]
+	return exists, nil
+}
+
+func (m *mockDatabase) RevocationTime(thirdPartyCaveatID string) (*time.Time, error) {
+	return m.revCavIDToTimestamp[string(m.tpCavIDToRevCavID[thirdPartyCaveatID])], nil
+}
diff --git a/services/identity/internal/revocation/revocation_manager.go b/services/identity/internal/revocation/revocation_manager.go
new file mode 100644
index 0000000..9a274ee
--- /dev/null
+++ b/services/identity/internal/revocation/revocation_manager.go
@@ -0,0 +1,104 @@
+// 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 revocation provides tools to create and manage revocation caveats.
+package revocation
+
+import (
+	"crypto/rand"
+	"database/sql"
+	"fmt"
+	"sync"
+	"time"
+
+	"v.io/v23/context"
+	"v.io/v23/security"
+)
+
+// RevocationManager persists information for revocation caveats to provided discharges and allow for future revocations.
+type RevocationManager interface {
+	NewCaveat(discharger security.PublicKey, dischargerLocation string) (security.Caveat, error)
+	Revoke(caveatID string) error
+	GetRevocationTime(caveatID string) *time.Time
+}
+
+// revocationManager persists information for revocation caveats to provided discharges and allow for future revocations.
+type revocationManager struct{ ctx *context.T }
+
+// NewRevocationManager returns a RevocationManager that persists information about
+// revocationCaveats in a SQL database and allows for revocation and caveat creation.
+// This function can only be called once because of the use of global variables.
+func NewRevocationManager(ctx *context.T, sqlDB *sql.DB) (RevocationManager, error) {
+	revocationLock.Lock()
+	defer revocationLock.Unlock()
+	if revocationDB != nil {
+		return nil, fmt.Errorf("NewRevocationManager can only be called once")
+	}
+	var err error
+	revocationDB, err = newSQLDatabase(sqlDB, "RevocationCaveatInfo")
+	if err != nil {
+		return nil, err
+	}
+	return &revocationManager{ctx: ctx}, nil
+}
+
+var revocationDB database
+var revocationLock sync.RWMutex
+
+// NewCaveat returns a security.Caveat constructed with a ThirdPartyCaveat for which discharges will be
+// issued iff Revoke has not been called for the returned caveat.
+func (r *revocationManager) NewCaveat(discharger security.PublicKey, dischargerLocation string) (security.Caveat, error) {
+	var empty security.Caveat
+	var revocation [16]byte
+	if _, err := rand.Read(revocation[:]); err != nil {
+		return empty, err
+	}
+	notRevoked, err := security.NewCaveat(NotRevokedCaveat, revocation[:])
+	if err != nil {
+		return empty, err
+	}
+	cav, err := security.NewPublicKeyCaveat(discharger, dischargerLocation, security.ThirdPartyRequirements{}, notRevoked)
+	if err != nil {
+		return empty, err
+	}
+	if err = revocationDB.InsertCaveat(cav.ThirdPartyDetails().ID(), revocation[:]); err != nil {
+		return empty, err
+	}
+	return cav, nil
+}
+
+// Revoke disables discharges from being issued for the provided third-party caveat.
+func (r *revocationManager) Revoke(caveatID string) error {
+	return revocationDB.Revoke(caveatID)
+}
+
+// GetRevocationTimestamp returns the timestamp at which a caveat was revoked.
+// If the caveat wasn't revoked returns nil
+func (r *revocationManager) GetRevocationTime(caveatID string) *time.Time {
+	timestamp, err := revocationDB.RevocationTime(caveatID)
+	if err != nil {
+		return nil
+	}
+	return timestamp
+}
+
+func isRevoked(ctx *context.T, call security.Call, key []byte) error {
+	revocationLock.RLock()
+	if revocationDB == nil {
+		revocationLock.RUnlock()
+		return fmt.Errorf("missing call to NewRevocationManager")
+	}
+	revocationLock.RUnlock()
+	revoked, err := revocationDB.IsRevoked(key)
+	if err != nil {
+	}
+	if revoked {
+		return fmt.Errorf("revoked")
+	}
+	return err
+}
+
+func init() {
+	security.RegisterCaveatValidator(NotRevokedCaveat, isRevoked)
+}
diff --git a/services/identity/internal/revocation/revocation_test.go b/services/identity/internal/revocation/revocation_test.go
new file mode 100644
index 0000000..34ad488
--- /dev/null
+++ b/services/identity/internal/revocation/revocation_test.go
@@ -0,0 +1,65 @@
+// 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 revocation
+
+import (
+	"testing"
+
+	"v.io/x/ref/lib/xrpc"
+	_ "v.io/x/ref/runtime/factories/generic"
+	"v.io/x/ref/services/discharger"
+	"v.io/x/ref/services/identity/internal/dischargerlib"
+	"v.io/x/ref/test"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/security"
+)
+
+//go:generate v23 test generate
+
+func revokerSetup(t *testing.T, ctx *context.T) (dischargerKey security.PublicKey, dischargerEndpoint string, revoker RevocationManager) {
+	dischargerServiceStub := discharger.DischargerServer(dischargerlib.NewDischarger())
+	dischargerServer, err := xrpc.NewServer(ctx, "", dischargerServiceStub, nil)
+	if err != nil {
+		t.Fatalf("r.NewServer: %s", err)
+	}
+	name := dischargerServer.Status().Endpoints[0].Name()
+	return v23.GetPrincipal(ctx).PublicKey(), name, NewMockRevocationManager(ctx)
+}
+
+func TestDischargeRevokeDischargeRevokeDischarge(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	dcKey, dc, revoker := revokerSetup(t, ctx)
+
+	discharger := discharger.DischargerClient(dc)
+	caveat, err := revoker.NewCaveat(dcKey, dc)
+	if err != nil {
+		t.Fatalf("failed to create revocation caveat: %s", err)
+	}
+	tp := caveat.ThirdPartyDetails()
+	if tp == nil {
+		t.Fatalf("failed to extract third party details from caveat %v", caveat)
+	}
+
+	var impetus security.DischargeImpetus
+	if _, err := discharger.Discharge(ctx, caveat, impetus); err != nil {
+		t.Fatalf("failed to get discharge: %s", err)
+	}
+	if err := revoker.Revoke(tp.ID()); err != nil {
+		t.Fatalf("failed to revoke: %s", err)
+	}
+	if _, err := discharger.Discharge(ctx, caveat, impetus); err == nil {
+		t.Fatalf("got a discharge for a revoked caveat: %s", err)
+	}
+	if err := revoker.Revoke(tp.ID()); err != nil {
+		t.Fatalf("failed to revoke again: %s", err)
+	}
+	if _, err := discharger.Discharge(ctx, caveat, impetus); err == nil {
+		t.Fatalf("got a discharge for a doubly revoked caveat: %s", err)
+	}
+}
diff --git a/services/identity/internal/revocation/sql_database.go b/services/identity/internal/revocation/sql_database.go
new file mode 100644
index 0000000..e5f57aa
--- /dev/null
+++ b/services/identity/internal/revocation/sql_database.go
@@ -0,0 +1,86 @@
+// 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 revocation
+
+import (
+	"database/sql"
+	"encoding/hex"
+	"fmt"
+	"time"
+)
+
+type database interface {
+	InsertCaveat(thirdPartyCaveatID string, revocationCaveatID []byte) error
+	Revoke(thirdPartyCaveatID string) error
+	IsRevoked(revocationCaveatID []byte) (bool, error)
+	RevocationTime(thirdPartyCaveatID string) (*time.Time, error)
+}
+
+// Table with 3 columns:
+// (1) ThirdPartyCaveatID= string thirdPartyCaveatID.
+// (2) RevocationCaveatID= hex encoded revcationCaveatID.
+// (3) RevocationTime= time (if any) that the Caveat was revoked.
+type sqlDatabase struct {
+	insertCaveatStmt, revokeStmt, isRevokedStmt, revocationTimeStmt *sql.Stmt
+}
+
+func (s *sqlDatabase) InsertCaveat(thirdPartyCaveatID string, revocationCaveatID []byte) error {
+	_, err := s.insertCaveatStmt.Exec(thirdPartyCaveatID, hex.EncodeToString(revocationCaveatID))
+	return err
+}
+
+func (s *sqlDatabase) Revoke(thirdPartyCaveatID string) error {
+	_, err := s.revokeStmt.Exec(time.Now(), thirdPartyCaveatID)
+	return err
+}
+
+func (s *sqlDatabase) IsRevoked(revocationCaveatID []byte) (bool, error) {
+	rows, err := s.isRevokedStmt.Query(hex.EncodeToString(revocationCaveatID))
+	if err != nil {
+		return false, err
+	}
+	defer rows.Close()
+	return rows.Next(), err
+}
+
+func (s *sqlDatabase) RevocationTime(thirdPartyCaveatID string) (*time.Time, error) {
+	rows, err := s.revocationTimeStmt.Query(thirdPartyCaveatID)
+	if err != nil {
+		return nil, err
+	}
+	defer rows.Close()
+	if rows.Next() {
+		var timestamp time.Time
+		if err := rows.Scan(&timestamp); err != nil {
+			return nil, err
+		}
+		return &timestamp, nil
+	}
+	return nil, fmt.Errorf("the caveat (%v) was not revoked", thirdPartyCaveatID)
+}
+
+func newSQLDatabase(db *sql.DB, table string) (database, error) {
+	createStmt, err := db.Prepare(fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s ( ThirdPartyCaveatID NVARCHAR(255), RevocationCaveatID NVARCHAR(255), RevocationTime DATETIME, PRIMARY KEY (ThirdPartyCaveatID), KEY (RevocationCaveatID) );", table))
+	if err != nil {
+		return nil, err
+	}
+	if _, err = createStmt.Exec(); err != nil {
+		return nil, err
+	}
+	insertCaveatStmt, err := db.Prepare(fmt.Sprintf("INSERT INTO %s (ThirdPartyCaveatID, RevocationCaveatID, RevocationTime) VALUES (?, ?, NULL)", table))
+	if err != nil {
+		return nil, err
+	}
+	revokeStmt, err := db.Prepare(fmt.Sprintf("UPDATE %s SET RevocationTime=? WHERE ThirdPartyCaveatID=?", table))
+	if err != nil {
+		return nil, err
+	}
+	isRevokedStmt, err := db.Prepare(fmt.Sprintf("SELECT 1 FROM %s WHERE RevocationCaveatID=? AND RevocationTime IS NOT NULL", table))
+	if err != nil {
+		return nil, err
+	}
+	revocationTimeStmt, err := db.Prepare(fmt.Sprintf("SELECT RevocationTime FROM %s WHERE ThirdPartyCaveatID=?", table))
+	return &sqlDatabase{insertCaveatStmt, revokeStmt, isRevokedStmt, revocationTimeStmt}, err
+}
diff --git a/services/identity/internal/revocation/sql_database_test.go b/services/identity/internal/revocation/sql_database_test.go
new file mode 100644
index 0000000..bc4dfd4
--- /dev/null
+++ b/services/identity/internal/revocation/sql_database_test.go
@@ -0,0 +1,77 @@
+// 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 revocation
+
+import (
+	"encoding/hex"
+	"github.com/DATA-DOG/go-sqlmock"
+	"reflect"
+	"testing"
+	"time"
+)
+
+func TestSQLDatabase(t *testing.T) {
+	db, err := sqlmock.New()
+	if err != nil {
+		t.Fatalf("failed to create new mock database stub: %v", err)
+	}
+	columns := []string{"ThirdPartyCaveatID", "RevocationCaveatID", "RevocationTime"}
+	sqlmock.ExpectExec("CREATE TABLE IF NOT EXISTS tableName (.+)").
+		WillReturnResult(sqlmock.NewResult(0, 1))
+	d, err := newSQLDatabase(db, "tableName")
+	if err != nil {
+		t.Fatalf("failed to create SQLDatabase: %v", err)
+	}
+
+	tpCavID, revCavID := "tpCavID", []byte("revCavID")
+	tpCavID2, revCavID2 := "tpCavID2", []byte("revCavID2")
+	encRevCavID := hex.EncodeToString(revCavID)
+	encRevCavID2 := hex.EncodeToString(revCavID2)
+	sqlmock.ExpectExec("INSERT INTO tableName (.+) VALUES (.+)").
+		WithArgs(tpCavID, encRevCavID).
+		WillReturnResult(sqlmock.NewResult(0, 1)) // no insert id, 1 affected row
+	if err := d.InsertCaveat(tpCavID, revCavID); err != nil {
+		t.Errorf("failed to InsertCaveat into SQLDatabase: %v", err)
+	}
+
+	sqlmock.ExpectExec("INSERT INTO tableName (.+) VALUES (.+)").
+		WithArgs(tpCavID2, encRevCavID2).
+		WillReturnResult(sqlmock.NewResult(0, 1)) // no insert id, 1 affected row
+	if err := d.InsertCaveat(tpCavID2, revCavID2); err != nil {
+		t.Errorf("second InsertCaveat into SQLDatabase failed: %v", err)
+	}
+
+	// Test Revocation
+	sqlmock.ExpectExec("UPDATE tableName SET RevocationTime=.+").
+		WillReturnResult(sqlmock.NewResult(0, 1))
+	if err := d.Revoke(tpCavID); err != nil {
+		t.Errorf("failed to Revoke Caveat: %v", err)
+	}
+
+	// Test IsRevoked returns true.
+	sqlmock.ExpectQuery("SELECT 1 FROM tableName").
+		WithArgs(encRevCavID).
+		WillReturnRows(sqlmock.NewRows(columns).AddRow(1, 1, 1))
+	if revoked, err := d.IsRevoked(revCavID); err != nil || !revoked {
+		t.Errorf("expected revCavID to be revoked: err: (%v)", err)
+	}
+
+	// Test IsRevoked returns false.
+	sqlmock.ExpectQuery("SELECT 1 FROM tableName").
+		WithArgs(encRevCavID2).
+		WillReturnRows(sqlmock.NewRows(columns))
+	if revoked, err := d.IsRevoked(revCavID2); err != nil || revoked {
+		t.Errorf("expected revCavID to not be revoked: err: (%v)", err)
+	}
+
+	// Test RevocationTime.
+	revocationTime := time.Now()
+	sqlmock.ExpectQuery("SELECT RevocationTime FROM tableName").
+		WithArgs(tpCavID).
+		WillReturnRows(sqlmock.NewRows([]string{"RevocationTime"}).AddRow(revocationTime))
+	if got, err := d.RevocationTime(tpCavID); err != nil || !reflect.DeepEqual(*got, revocationTime) {
+		t.Errorf("got %v, expected %v: err : %v", got, revocationTime, err)
+	}
+}
diff --git a/services/identity/internal/revocation/v23_internal_test.go b/services/identity/internal/revocation/v23_internal_test.go
new file mode 100644
index 0000000..d53fea3
--- /dev/null
+++ b/services/identity/internal/revocation/v23_internal_test.go
@@ -0,0 +1,20 @@
+// 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 revocation
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	os.Exit(m.Run())
+}
diff --git a/services/identity/internal/server/identityd.go b/services/identity/internal/server/identityd.go
new file mode 100644
index 0000000..a14a6ba
--- /dev/null
+++ b/services/identity/internal/server/identityd.go
@@ -0,0 +1,297 @@
+// 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.
+
+// HTTP server that uses OAuth to create security.Blessings objects.
+package server
+
+import (
+	"crypto/rand"
+	"fmt"
+	mrand "math/rand"
+	"net"
+	"net/http"
+	"reflect"
+	"strconv"
+	"strings"
+	"syscall"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/verror"
+	"v.io/x/ref/lib/security/audit"
+	"v.io/x/ref/lib/signals"
+	"v.io/x/ref/services/discharger"
+	"v.io/x/ref/services/identity/internal/auditor"
+	"v.io/x/ref/services/identity/internal/blesser"
+	"v.io/x/ref/services/identity/internal/caveats"
+	"v.io/x/ref/services/identity/internal/dischargerlib"
+	"v.io/x/ref/services/identity/internal/handlers"
+	"v.io/x/ref/services/identity/internal/oauth"
+	"v.io/x/ref/services/identity/internal/revocation"
+	"v.io/x/ref/services/identity/internal/templates"
+)
+
+const (
+	// TODO(ataly, ashankar, suharshs): The name "google" for the oauthBlesserService does
+	// not seem appropriate given our modular construction of the identity server. The
+	// oauthBlesserService can use any oauthProvider of its choosing, i.e., it does not
+	// always have to be "google". One option would be change the value to "oauth". This
+	// would also make the name analogous to that of macaroonService. Note that this option
+	// also requires changing the extension.
+	oauthBlesserService = "google"
+	macaroonService     = "macaroon"
+	dischargerService   = "discharger"
+)
+
+type IdentityServer struct {
+	oauthProvider      oauth.OAuthProvider
+	auditor            audit.Auditor
+	blessingLogReader  auditor.BlessingLogReader
+	revocationManager  revocation.RevocationManager
+	oauthBlesserParams blesser.OAuthBlesserParams
+	caveatSelector     caveats.CaveatSelector
+	rootedObjectAddrs  []naming.Endpoint
+	assetsPrefix       string
+	mountNamePrefix    string
+}
+
+// NewIdentityServer returns a IdentityServer that:
+// - uses oauthProvider to authenticate users
+// - auditor and blessingLogReader to audit the root principal and read audit logs
+// - revocationManager to store revocation data and grant discharges
+// - oauthBlesserParams to configure the identity.OAuthBlesser service
+func NewIdentityServer(oauthProvider oauth.OAuthProvider, auditor audit.Auditor, blessingLogReader auditor.BlessingLogReader, revocationManager revocation.RevocationManager, oauthBlesserParams blesser.OAuthBlesserParams, caveatSelector caveats.CaveatSelector, assetsPrefix, mountNamePrefix string) *IdentityServer {
+	return &IdentityServer{
+		oauthProvider:      oauthProvider,
+		auditor:            auditor,
+		blessingLogReader:  blessingLogReader,
+		revocationManager:  revocationManager,
+		oauthBlesserParams: oauthBlesserParams,
+		caveatSelector:     caveatSelector,
+		assetsPrefix:       assetsPrefix,
+		mountNamePrefix:    mountNamePrefix,
+	}
+}
+
+// findUnusedPort finds an unused port and returns it. Of course, no guarantees
+// are made that the port will actually be available by the time the caller
+// gets around to binding to it. If no port can be found, (0, nil) is returned.
+// If an error occurs while creating a socket, that error is returned and the
+// other return value is 0.
+func findUnusedPort() (int, error) {
+	random := mrand.New(mrand.NewSource(time.Now().UnixNano()))
+	for i := 0; i < 1000; i++ {
+		fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_TCP)
+		if err != nil {
+			return 0, err
+		}
+
+		port := int(1024 + random.Int31n(64512))
+		sa := &syscall.SockaddrInet4{Port: port}
+		err = syscall.Bind(fd, sa)
+		syscall.Close(fd)
+		if err == nil {
+			return port, nil
+		}
+	}
+
+	return 0, nil
+}
+
+func (s *IdentityServer) Serve(ctx *context.T, listenSpec *rpc.ListenSpec, externalHttpAddr, httpAddr, tlsConfig string) {
+	ctx, err := v23.WithPrincipal(ctx, audit.NewPrincipal(ctx, s.auditor))
+	if err != nil {
+		ctx.Panic(err)
+	}
+	httphost, httpport, err := net.SplitHostPort(httpAddr)
+	if err != nil || httpport == "0" {
+		httpportNum, err := findUnusedPort()
+		if err != nil {
+			ctx.Panic(err)
+		}
+		httpAddr = net.JoinHostPort(httphost, strconv.Itoa(httpportNum))
+	}
+	rpcServer, _, externalAddr := s.Listen(ctx, listenSpec, externalHttpAddr, httpAddr, tlsConfig)
+	fmt.Printf("HTTP_ADDR=%s\n", externalAddr)
+	if len(s.rootedObjectAddrs) > 0 {
+		fmt.Printf("NAME=%s\n", s.rootedObjectAddrs[0].Name())
+	}
+	<-signals.ShutdownOnSignals(ctx)
+	if err := rpcServer.Stop(); err != nil {
+		ctx.Errorf("Failed to stop rpc server: %v", err)
+	}
+}
+
+func (s *IdentityServer) Listen(ctx *context.T, listenSpec *rpc.ListenSpec, externalHttpAddr, httpAddr, tlsConfig string) (rpc.Server, []string, string) {
+	// Setup handlers
+
+	// json-encoded public key and blessing names of this server
+	principal := v23.GetPrincipal(ctx)
+	http.Handle("/auth/blessing-root", handlers.BlessingRoot{principal})
+
+	macaroonKey := make([]byte, 32)
+	if _, err := rand.Read(macaroonKey); err != nil {
+		ctx.Fatalf("macaroonKey generation failed: %v", err)
+	}
+
+	rpcServer, published, err := s.setupBlessingServices(ctx, listenSpec, macaroonKey)
+	if err != nil {
+		ctx.Fatalf("Failed to setup vanadium services for blessing: %v", err)
+	}
+
+	externalHttpAddr = httpAddress(externalHttpAddr, httpAddr)
+
+	http.HandleFunc("/health", func(w http.ResponseWriter, _ *http.Request) {
+		w.WriteHeader(http.StatusNoContent)
+	})
+
+	n := "/auth/google/"
+	args := oauth.HandlerArgs{
+		Principal:               principal,
+		MacaroonKey:             macaroonKey,
+		Addr:                    fmt.Sprintf("%s%s", externalHttpAddr, n),
+		BlessingLogReader:       s.blessingLogReader,
+		RevocationManager:       s.revocationManager,
+		DischargerLocation:      naming.JoinAddressName(published[0], dischargerService),
+		MacaroonBlessingService: naming.JoinAddressName(published[0], macaroonService),
+		OAuthProvider:           s.oauthProvider,
+		CaveatSelector:          s.caveatSelector,
+		AssetsPrefix:            s.assetsPrefix,
+	}
+	if s.revocationManager != nil {
+		args.DischargeServers = appendSuffixTo(published, dischargerService)
+	}
+	var emptyParams blesser.OAuthBlesserParams
+	if !reflect.DeepEqual(s.oauthBlesserParams, emptyParams) {
+		args.GoogleServers = appendSuffixTo(published, oauthBlesserService)
+	}
+	h, err := oauth.NewHandler(ctx, args)
+	if err != nil {
+		ctx.Fatalf("Failed to create HTTP handler for oauth authentication: %v", err)
+	}
+	http.Handle(n, h)
+
+	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+		tmplArgs := struct {
+			Self                            security.Blessings
+			GoogleServers, DischargeServers []string
+			ListBlessingsRoute              string
+			AssetsPrefix                    string
+			Email                           string
+		}{
+			Self:               principal.BlessingStore().Default(),
+			GoogleServers:      args.GoogleServers,
+			DischargeServers:   args.DischargeServers,
+			ListBlessingsRoute: oauth.ListBlessingsRoute,
+			AssetsPrefix:       s.assetsPrefix,
+		}
+		if err := templates.Home.Execute(w, tmplArgs); err != nil {
+			ctx.Info("Failed to render template:", err)
+		}
+	})
+	ctx.Infof("Running HTTP server at: %v", externalHttpAddr)
+	go runHTTPSServer(ctx, httpAddr, tlsConfig)
+	return rpcServer, published, externalHttpAddr
+}
+
+func appendSuffixTo(objectname []string, suffix string) []string {
+	names := make([]string, len(objectname))
+	for i, o := range objectname {
+		names[i] = naming.JoinAddressName(o, suffix)
+	}
+	return names
+}
+
+// Starts the Vanadium and HTTP services for blessing, and the Vanadium service for discharging.
+// All Vanadium services are started on the same port.
+func (s *IdentityServer) setupBlessingServices(ctx *context.T, listenSpec *rpc.ListenSpec, macaroonKey []byte) (rpc.Server, []string, error) {
+	server, err := v23.NewServer(ctx)
+	if err != nil {
+		return nil, nil, fmt.Errorf("failed to create new rpc.Server: %v", err)
+	}
+
+	principal := v23.GetPrincipal(ctx)
+	objectAddr := naming.Join(s.mountNamePrefix, fmt.Sprintf("%v", principal.BlessingStore().Default()))
+	if s.rootedObjectAddrs, err = server.Listen(*listenSpec); err != nil {
+		defer server.Stop()
+		return nil, nil, fmt.Errorf("server.Listen(%v) failed: %v", *listenSpec, err)
+	}
+	var rootedObjectAddr string
+	if naming.Rooted(objectAddr) {
+		rootedObjectAddr = objectAddr
+	} else if nsroots := v23.GetNamespace(ctx).Roots(); len(nsroots) >= 1 {
+		rootedObjectAddr = naming.Join(nsroots[0], objectAddr)
+	} else {
+		rootedObjectAddr = s.rootedObjectAddrs[0].Name()
+	}
+
+	params := oauthBlesserParams(s.oauthBlesserParams, rootedObjectAddr)
+	dispatcher := newDispatcher(macaroonKey, params)
+	if err := server.ServeDispatcher(objectAddr, dispatcher); err != nil {
+		return nil, nil, fmt.Errorf("failed to start Vanadium services: %v", err)
+	}
+	ctx.Infof("Vanadium Blessing and discharger services will be published at %v", rootedObjectAddr)
+
+	// Start the HTTP Handler for the OAuth2 access token based blesser.
+	http.Handle("/auth/google/bless", handlers.NewOAuthBlessingHandler(ctx, params))
+
+	return server, []string{rootedObjectAddr}, nil
+}
+
+// newDispatcher returns a dispatcher for both the blessing and the
+// discharging service.
+func newDispatcher(macaroonKey []byte, blesserParams blesser.OAuthBlesserParams) rpc.Dispatcher {
+	d := dispatcher(map[string]interface{}{
+		macaroonService:     blesser.NewMacaroonBlesserServer(macaroonKey),
+		dischargerService:   discharger.DischargerServer(dischargerlib.NewDischarger()),
+		oauthBlesserService: blesser.NewOAuthBlesserServer(blesserParams),
+	})
+	// Set up the glob invoker.
+	var children []string
+	for k, _ := range d {
+		children = append(children, k)
+	}
+	d[""] = rpc.ChildrenGlobberInvoker(children...)
+	return d
+}
+
+type dispatcher map[string]interface{}
+
+func (d dispatcher) Lookup(_ *context.T, suffix string) (interface{}, security.Authorizer, error) {
+	if invoker := d[suffix]; invoker != nil {
+		return invoker, security.AllowEveryone(), nil
+	}
+	return nil, nil, verror.New(verror.ErrNoExist, nil, suffix)
+}
+
+func oauthBlesserParams(inputParams blesser.OAuthBlesserParams, servername string) blesser.OAuthBlesserParams {
+	inputParams.DischargerLocation = naming.Join(servername, dischargerService)
+	return inputParams
+}
+
+func runHTTPSServer(ctx *context.T, addr, tlsConfig string) {
+	if len(tlsConfig) == 0 {
+		ctx.Fatal("Please set the --tls-config flag")
+	}
+	paths := strings.Split(tlsConfig, ",")
+	if len(paths) != 2 {
+		ctx.Fatalf("Could not parse --tls-config. Must have exactly two components, separated by a comma")
+	}
+	ctx.Infof("Starting HTTP server with TLS using certificate [%s] and private key [%s] at https://%s", paths[0], paths[1], addr)
+	if err := http.ListenAndServeTLS(addr, paths[0], paths[1], nil); err != nil {
+		ctx.Fatalf("http.ListenAndServeTLS failed: %v", err)
+	}
+}
+
+func httpAddress(externalHttpAddr, httpAddr string) string {
+	// If an externalHttpAddr is provided use that.
+	if externalHttpAddr != "" {
+		httpAddr = externalHttpAddr
+	}
+	return fmt.Sprintf("https://%v", httpAddr)
+}
diff --git a/services/identity/internal/server/rest_signer.go b/services/identity/internal/server/rest_signer.go
new file mode 100644
index 0000000..5a8efbf
--- /dev/null
+++ b/services/identity/internal/server/rest_signer.go
@@ -0,0 +1,69 @@
+// 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 server
+
+import (
+	"crypto/ecdsa"
+	"crypto/x509"
+	"encoding/base64"
+	"fmt"
+	"math/big"
+
+	"golang.org/x/oauth2"
+	"golang.org/x/oauth2/google"
+	"v.io/v23/security"
+	"v.io/x/ref/services/identity/internal/signer/v1"
+)
+
+func DecodePublicKey(k *signer.PublicKey) (*ecdsa.PublicKey, error) {
+	bytes, err := base64.URLEncoding.DecodeString(k.Base64)
+	if err != nil {
+		return nil, err
+	}
+	key, err := x509.ParsePKIXPublicKey(bytes)
+	if err != nil {
+		return nil, err
+	}
+	pub, ok := key.(*ecdsa.PublicKey)
+	if !ok {
+		return nil, fmt.Errorf("Not an ECDSA public key")
+	}
+	return pub, nil
+}
+
+func DecodeSignature(sig *signer.VSignature) (r, s *big.Int, err error) {
+	r, s = new(big.Int), new(big.Int)
+	if _, ok := r.SetString(sig.R, 0); !ok {
+		return nil, nil, fmt.Errorf("unable to parse big.Int %s", sig.R)
+	}
+	if _, ok := s.SetString(sig.S, 0); !ok {
+		return nil, nil, fmt.Errorf("unable to parse big.Int %s", sig.S)
+	}
+	return
+}
+
+func NewRestSigner() (security.Signer, error) {
+	client, err := signer.New(oauth2.NewClient(oauth2.NoContext, google.ComputeTokenSource("")))
+	if err != nil {
+		return nil, err
+	}
+	jkey, err := client.PublicKey().Do()
+	if err != nil {
+		return nil, err
+	}
+	key, err := DecodePublicKey(jkey)
+	if err != nil {
+		return nil, err
+	}
+	sign := func(message []byte) (r, s *big.Int, err error) {
+		msgBase64 := base64.URLEncoding.EncodeToString(message)
+		jsig, err := client.Sign(msgBase64).Do()
+		if err != nil {
+			return nil, nil, err
+		}
+		return DecodeSignature(jsig)
+	}
+	return security.NewECDSASigner(key, sign), nil
+}
diff --git a/services/identity/internal/server/rest_signer_test.go b/services/identity/internal/server/rest_signer_test.go
new file mode 100644
index 0000000..20252a8
--- /dev/null
+++ b/services/identity/internal/server/rest_signer_test.go
@@ -0,0 +1,36 @@
+// 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 server_test
+
+import (
+	"math/big"
+	"testing"
+
+	"v.io/v23/security"
+	"v.io/x/ref/services/identity/internal/server"
+	"v.io/x/ref/services/identity/internal/signer/v1"
+)
+
+func TestDecode(t *testing.T) {
+	// To generate encodedKey and encodedSig run the binary in v.io/x/ref/services/identity/internal/rest_signer_test
+	encodedKey := &signer.PublicKey{Base64: "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEx8xywmgl2_UmDUoJxrh2N9pAij7jg1kIqruKpnT6SNtcNubCG_PgdpWqiVLp3zBWlw1T3F2ecy4iGpi5N4Yj-A=="}
+	encodedSig := &signer.VSignature{R: "90128808689861327833210881969781001621382090117447023854233028840694123302875", S: "102696248968928040866906648206566376772954871370602978407028885005693672370943"}
+
+	key, err := server.DecodePublicKey(encodedKey)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	s := security.NewECDSASigner(key, func(message []byte) (r, s *big.Int, err error) {
+		return server.DecodeSignature(encodedSig)
+	})
+	sig, err := s.Sign([]byte("purpose"), []byte("message"))
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !sig.Verify(s.PublicKey(), []byte("message")) {
+		t.Fatal("Signature does not verify")
+	}
+}
diff --git a/services/identity/internal/signer/v1/README b/services/identity/internal/signer/v1/README
new file mode 100644
index 0000000..23fa070
--- /dev/null
+++ b/services/identity/internal/signer/v1/README
@@ -0,0 +1,12 @@
+Auto generated client for the signer service.
+
+The service is implemented using Cloud Endpoints
+https://cloud.google.com/endpoints/
+
+The signer-api.json file is auto generated from the server implementation.
+
+The signer-gen.go file is generated using
+google.golang.org/api/google-api-go-generator:
+
+google-api-go-generator --api_json_file=signer-api.json -output=signer-gen.go
+
diff --git a/services/identity/internal/signer/v1/signer-api.json b/services/identity/internal/signer/v1/signer-api.json
new file mode 100644
index 0000000..3a4e012
--- /dev/null
+++ b/services/identity/internal/signer/v1/signer-api.json
@@ -0,0 +1,130 @@
+{
+ "kind": "discovery#restDescription",
+ "etag": "\"u_zXkMELIlX4ktyNbM2XKD4vK8E/fv51HUFpIp2SZ4FxWW-y5odvVuE\"",
+ "discoveryVersion": "v1",
+ "id": "signer:v1",
+ "name": "signer",
+ "version": "v1",
+ "description": "Vanadium remote signer",
+ "icons": {
+  "x16": "http://www.google.com/images/icons/product/search-16.gif",
+  "x32": "http://www.google.com/images/icons/product/search-32.gif"
+ },
+ "protocol": "rest",
+ "baseUrl": "https://vanadium-production.appspot.com/_ah/api/signer/v1/",
+ "basePath": "/_ah/api/signer/v1/",
+ "rootUrl": "https://vanadium-production.appspot.com/_ah/api/",
+ "servicePath": "signer/v1/",
+ "batchPath": "batch",
+ "parameters": {
+  "alt": {
+   "type": "string",
+   "description": "Data format for the response.",
+   "default": "json",
+   "enum": [
+    "json"
+   ],
+   "enumDescriptions": [
+    "Responses with Content-Type of application/json"
+   ],
+   "location": "query"
+  },
+  "fields": {
+   "type": "string",
+   "description": "Selector specifying which fields to include in a partial response.",
+   "location": "query"
+  },
+  "key": {
+   "type": "string",
+   "description": "API key. Your API key identifies your project and provides you with API access, quota, and reports. Required unless you provide an OAuth 2.0 token.",
+   "location": "query"
+  },
+  "oauth_token": {
+   "type": "string",
+   "description": "OAuth 2.0 token for the current user.",
+   "location": "query"
+  },
+  "prettyPrint": {
+   "type": "boolean",
+   "description": "Returns response with indentations and line breaks.",
+   "default": "true",
+   "location": "query"
+  },
+  "quotaUser": {
+   "type": "string",
+   "description": "Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. Overrides userIp if both are provided.",
+   "location": "query"
+  },
+  "userIp": {
+   "type": "string",
+   "description": "IP address of the site where the request originates. Use this if you want to enforce per-user limits.",
+   "location": "query"
+  }
+ },
+ "auth": {
+  "oauth2": {
+   "scopes": {
+    "https://www.googleapis.com/auth/userinfo.email": {
+     "description": "View your email address"
+    }
+   }
+  }
+ },
+ "schemas": {
+  "PublicKey": {
+   "id": "PublicKey",
+   "type": "object",
+   "properties": {
+    "base64": {
+     "type": "string"
+    }
+   }
+  },
+  "VSignature": {
+   "id": "VSignature",
+   "type": "object",
+   "properties": {
+    "r": {
+     "type": "string"
+    },
+    "s": {
+     "type": "string"
+    }
+   }
+  }
+ },
+ "methods": {
+  "publicKey": {
+   "id": "signer.publicKey",
+   "path": "publicKey",
+   "httpMethod": "POST",
+   "response": {
+    "$ref": "PublicKey"
+   },
+   "scopes": [
+    "https://www.googleapis.com/auth/userinfo.email"
+   ]
+  },
+  "sign": {
+   "id": "signer.sign",
+   "path": "sign/{base64}",
+   "httpMethod": "POST",
+   "parameters": {
+    "base64": {
+     "type": "string",
+     "required": true,
+     "location": "path"
+    }
+   },
+   "parameterOrder": [
+    "base64"
+   ],
+   "response": {
+    "$ref": "VSignature"
+   },
+   "scopes": [
+    "https://www.googleapis.com/auth/userinfo.email"
+   ]
+  }
+ }
+}
diff --git a/services/identity/internal/signer/v1/signer-gen.go b/services/identity/internal/signer/v1/signer-gen.go
new file mode 100644
index 0000000..98308ed
--- /dev/null
+++ b/services/identity/internal/signer/v1/signer-gen.go
@@ -0,0 +1,207 @@
+// 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 signer provides access to the .
+//
+// Usage example:
+//
+//   import "google.golang.org/api/signer/v1"
+//   ...
+//   signerService, err := signer.New(oauthHttpClient)
+package signer
+
+import (
+	"bytes"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"golang.org/x/net/context"
+	"google.golang.org/api/googleapi"
+	"io"
+	"net/http"
+	"net/url"
+	"strconv"
+	"strings"
+)
+
+// Always reference these packages, just in case the auto-generated code
+// below doesn't.
+var _ = bytes.NewBuffer
+var _ = strconv.Itoa
+var _ = fmt.Sprintf
+var _ = json.NewDecoder
+var _ = io.Copy
+var _ = url.Parse
+var _ = googleapi.Version
+var _ = errors.New
+var _ = strings.Replace
+var _ = context.Background
+
+const apiId = "signer:v1"
+const apiName = "signer"
+const apiVersion = "v1"
+const basePath = "https://vanadium-production.appspot.com/_ah/api/signer/v1/"
+
+// OAuth2 scopes used by this API.
+const (
+	// View your email address
+	UserinfoEmailScope = "https://www.googleapis.com/auth/userinfo.email"
+)
+
+func New(client *http.Client) (*Service, error) {
+	if client == nil {
+		return nil, errors.New("client is nil")
+	}
+	s := &Service{client: client, BasePath: basePath}
+	return s, nil
+}
+
+type Service struct {
+	client   *http.Client
+	BasePath string // API endpoint base URL
+}
+
+type PublicKey struct {
+	Base64 string `json:"base64,omitempty"`
+}
+
+type VSignature struct {
+	R string `json:"r,omitempty"`
+
+	S string `json:"s,omitempty"`
+}
+
+// method id "signer.publicKey":
+
+type PublicKeyCall struct {
+	s    *Service
+	opt_ map[string]interface{}
+}
+
+// PublicKey:
+func (s *Service) PublicKey() *PublicKeyCall {
+	c := &PublicKeyCall{s: s, opt_: make(map[string]interface{})}
+	return c
+}
+
+// Fields allows partial responses to be retrieved.
+// See https://developers.google.com/gdata/docs/2.0/basics#PartialResponse
+// for more information.
+func (c *PublicKeyCall) Fields(s ...googleapi.Field) *PublicKeyCall {
+	c.opt_["fields"] = googleapi.CombineFields(s)
+	return c
+}
+
+func (c *PublicKeyCall) Do() (*PublicKey, error) {
+	var body io.Reader = nil
+	params := make(url.Values)
+	params.Set("alt", "json")
+	if v, ok := c.opt_["fields"]; ok {
+		params.Set("fields", fmt.Sprintf("%v", v))
+	}
+	urls := googleapi.ResolveRelative(c.s.BasePath, "publicKey")
+	urls += "?" + params.Encode()
+	req, _ := http.NewRequest("POST", urls, body)
+	googleapi.SetOpaque(req.URL)
+	req.Header.Set("User-Agent", "google-api-go-client/0.5")
+	res, err := c.s.client.Do(req)
+	if err != nil {
+		return nil, err
+	}
+	defer googleapi.CloseBody(res)
+	if err := googleapi.CheckResponse(res); err != nil {
+		return nil, err
+	}
+	var ret *PublicKey
+	if err := json.NewDecoder(res.Body).Decode(&ret); err != nil {
+		return nil, err
+	}
+	return ret, nil
+	// {
+	//   "httpMethod": "POST",
+	//   "id": "signer.publicKey",
+	//   "path": "publicKey",
+	//   "response": {
+	//     "$ref": "PublicKey"
+	//   },
+	//   "scopes": [
+	//     "https://www.googleapis.com/auth/userinfo.email"
+	//   ]
+	// }
+
+}
+
+// method id "signer.sign":
+
+type SignCall struct {
+	s      *Service
+	base64 string
+	opt_   map[string]interface{}
+}
+
+// Sign:
+func (s *Service) Sign(base64 string) *SignCall {
+	c := &SignCall{s: s, opt_: make(map[string]interface{})}
+	c.base64 = base64
+	return c
+}
+
+// Fields allows partial responses to be retrieved.
+// See https://developers.google.com/gdata/docs/2.0/basics#PartialResponse
+// for more information.
+func (c *SignCall) Fields(s ...googleapi.Field) *SignCall {
+	c.opt_["fields"] = googleapi.CombineFields(s)
+	return c
+}
+
+func (c *SignCall) Do() (*VSignature, error) {
+	var body io.Reader = nil
+	params := make(url.Values)
+	params.Set("alt", "json")
+	if v, ok := c.opt_["fields"]; ok {
+		params.Set("fields", fmt.Sprintf("%v", v))
+	}
+	urls := googleapi.ResolveRelative(c.s.BasePath, "sign/{base64}")
+	urls += "?" + params.Encode()
+	req, _ := http.NewRequest("POST", urls, body)
+	googleapi.Expand(req.URL, map[string]string{
+		"base64": c.base64,
+	})
+	req.Header.Set("User-Agent", "google-api-go-client/0.5")
+	res, err := c.s.client.Do(req)
+	if err != nil {
+		return nil, err
+	}
+	defer googleapi.CloseBody(res)
+	if err := googleapi.CheckResponse(res); err != nil {
+		return nil, err
+	}
+	var ret *VSignature
+	if err := json.NewDecoder(res.Body).Decode(&ret); err != nil {
+		return nil, err
+	}
+	return ret, nil
+	// {
+	//   "httpMethod": "POST",
+	//   "id": "signer.sign",
+	//   "parameterOrder": [
+	//     "base64"
+	//   ],
+	//   "parameters": {
+	//     "base64": {
+	//       "location": "path",
+	//       "required": true,
+	//       "type": "string"
+	//     }
+	//   },
+	//   "path": "sign/{base64}",
+	//   "response": {
+	//     "$ref": "VSignature"
+	//   },
+	//   "scopes": [
+	//     "https://www.googleapis.com/auth/userinfo.email"
+	//   ]
+	// }
+
+}
diff --git a/services/identity/internal/templates/caveats.go b/services/identity/internal/templates/caveats.go
new file mode 100644
index 0000000..79baa0c
--- /dev/null
+++ b/services/identity/internal/templates/caveats.go
@@ -0,0 +1,241 @@
+// 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 templates
+
+import "html/template"
+
+var SelectCaveats = template.Must(selectCaveats.Parse(headPartial))
+
+var selectCaveats = template.Must(template.New("bless").Parse(`<!doctype html>
+<html>
+<head>
+  <title>Add Blessing - Vanadium Identity Provider</title>
+
+  {{template "head" .}}
+
+</head>
+
+<body class="identityprovider-layout">
+
+  <header>
+    <nav class="left">
+      <a href="#" class="logo">Vanadium</a>
+      <span class="service-name">Identity Provider</span>
+    </nav>
+    <nav class="right">
+      <a href="#">{{.BlessingName}}</a>
+    </nav>
+  </header>
+
+  <main class="add-blessing">
+
+    <form method="POST" id="caveats-form" name="input"
+    action="{{.MacaroonURL}}" role="form" novalidate>
+      <input type="text" class="hidden" name="macaroon" value="{{.Macaroon}}">
+
+      <h1 class="page-head">Add blessing</h1>
+      <p>
+        This blessing allows the Vanadium Identity Provider to authorize your
+        application's credentials and provides your application access to the
+        data associated with your Google Account. Blessing names contain the
+        email address associated with your Google Account, and will be visible
+        to peers you connect to.
+      </p>
+
+      <div class="note">
+        <p>
+          <strong>
+            Using Vanadium in production applications is discouraged at this
+          time.</strong><br>
+          During this preview, the
+          <a href="https://v.io/glossary.html#blessing-root" target="_">
+            blessing root
+          </a>
+          may change without notice.
+        </p>
+      </div>
+
+      <label for="blessingExtension">Blessing name</label>
+      <div class="value">
+        {{.BlessingName}}/
+        <input name="blessingExtension" type="text" placeholder="extension">
+        <input type="hidden" id="timezoneOffset" name="timezoneOffset">
+      </div>
+
+      <label>Caveats</label>
+      <div class="caveatRow">
+        <div class="define-caveat">
+          <span class="selected value RevocationCaveatSelected">
+            Active until revoked
+          </span>
+          <span class="selected value ExpiryCaveatSelected hidden">
+            Expires on
+          </span>
+          <span class="selected value MethodCaveatSelected hidden">
+            Allowed methods are
+          </span>
+          <span class="selected value PeerBlessingsCaveatSelected hidden">
+            Allowed peers are
+          </span>
+
+          <select name="caveat" class="caveats hidden">
+            <option name="RevocationCaveat" value="RevocationCaveat"
+            class="cavOption">Active until revoked</option>
+            <option name="ExpiryCaveat" value="ExpiryCaveat"
+            class="cavOption">Expires on</option>
+            <option name="MethodCaveat" value="MethodCaveat"
+            class="cavOption">Allowed methods are</option>
+            <option name="PeerBlessingsCaveat" value="PeerBlessingsCaveat"
+            class="cavOption">Allowed peers are</option>
+          </select>
+
+          <input type="text" class="caveatInput hidden"
+            id="RevocationCaveat" name="RevocationCaveat">
+          <input type="datetime-local" class="caveatInput expiry hidden"
+            id="ExpiryCaveat" name="ExpiryCaveat">
+          <input type="text" class="caveatInput hidden"
+           id="MethodCaveat" name="MethodCaveat"
+           placeholder="comma-separated method list">
+          <input type="text" class="caveatInput hidden"
+            id="PeerBlessingsCaveat" name="PeerBlessingsCaveat"
+            placeholder="comma-separated blessing list">
+        </div>
+        <div class="add-caveat">
+          <a href="#" class="addMore">Add more caveats</a>
+        </div>
+      </div>
+
+      <div class="action-buttons">
+        <input type="text" class="hidden" name="cancelled" id="cancelled" value="false">
+        <button class="button-tertiary" id="cancel" type="button">Cancel</button>
+        <button class="button-primary" type="submit" id="bless">Bless</button>
+      </div>
+
+      <p class="disclaimer-text">
+        By clicking "Bless", you agree to the Google
+        <a href="https://www.google.com/intl/en/policies/terms/">General Terms of Service</a>,
+        <a href="https://developers.google.com/terms/">APIs Terms of Service</a>,
+        and <a href="https://www.google.com/intl/en/policies/privacy/">Privacy Policy</a>
+      </p>
+    </form>
+  </main>
+
+  <script src="{{.AssetsPrefix}}/identity/moment.js"></script>
+  <script src="{{.AssetsPrefix}}/identity/jquery.js"></script>
+
+  <script>
+  $(document).ready(function() {
+    var numCaveats = 1;
+    // When a caveat selector changes show the corresponding input box.
+    $('body').on('change', '.caveats', function (){
+      var caveatSelector = $(this).parents('.caveatRow');
+
+      // Hide the visible inputs and show the selected one.
+      caveatSelector.find('.caveatInput').hide();
+      var caveatName = $(this).val();
+      if (caveatName !== 'RevocationCaveat') {
+        caveatSelector.find('#'+caveatName).show();
+      }
+    });
+
+    var updateNewSelector = function(newSelector, caveatName) {
+      // disable the option from being selected again and make the next caveat the
+      // default for the next selector.
+      var selectedOption = newSelector.find('option[name="' + caveatName + '"]');
+      selectedOption.prop('disabled', true);
+      var newCaveat = newSelector.find('option:enabled').first();
+      newCaveat.prop('selected', true);
+      newSelector.find('.caveatInput').hide();
+      newSelector.find('#'+newCaveat.attr('name')).show();
+    }
+
+    // Upon clicking the 'Add Caveat' button a new caveat selector should appear.
+    $('body').on('click', '.addCaveat', function() {
+      var selector = $(this).parents('.caveatRow');
+      var newSelector = selector.clone();
+      var caveatName = selector.find('.caveats').val();
+
+      updateNewSelector(newSelector, caveatName);
+
+      // Change the selector's select to a fixed label and fix the inputs.
+      selector.find('.caveats').hide();
+      selector.find('.'+caveatName+'Selected').show();
+      selector.find('.caveatInput').prop('readonly', true);
+
+      selector.after(newSelector);
+      $(this).replaceWith('<button type="button" class="button-passive right removeCaveat hidden">Remove</button>');
+
+      numCaveats += 1;
+      if (numCaveats > 1) {
+        $('.removeCaveat').show();
+      }
+      if (numCaveats >= 4) {
+        $('.addCaveat').hide();
+        $('.caveats').hide();
+      }
+    });
+
+    // If add more is selected, remove the button and show the caveats selector.
+    $('body').on('click', '.addMore', function() {
+      var selector = $(this).parents('.caveatRow');
+      var newSelector = selector.clone();
+      var caveatName = selector.find('.caveats').val();
+
+      updateNewSelector(newSelector, caveatName);
+
+      newSelector.find('.caveats').show();
+      // Change the 'Add more' button in the copied selector to an 'Add caveats' button.
+      newSelector.find('.addMore').replaceWith('<button type="button" class="button-primary right addCaveat">Add</button>');
+      // Hide the default selected caveat for the copied selector.
+      newSelector.find('.selected').hide();
+
+      selector.after(newSelector);
+      $(this).replaceWith('<button type="button" class="button-passive right removeCaveat hidden">Remove</button>');
+    });
+
+    // Upon clicking bless, caveats that have not been added yet should be removed,
+    // before they are sent to the server.
+    $('#bless').click(function(){
+      $('.addCaveat').parents('.caveatRow').remove();
+      $("#caveats-form").submit();
+    });
+
+    // Upon clicking the 'Remove Caveat' button, the caveat row should be removed.
+    $('body').on('click', '.removeCaveat', function() {
+      var selector = $(this).parents('.caveatRow')
+      var caveatName = selector.find('.caveats').val();
+
+      // Enable choosing this caveat again.
+      $('option[name="' + caveatName + '"]').last().prop('disabled', false);
+
+      selector.remove();
+
+      numCaveats -= 1;
+      if (numCaveats == 1) {
+        $('.removeCaveat').hide();
+      }
+      $('.addCaveat').show();
+      $('.caveats').last().show();
+    });
+
+    // Get the timezoneOffset for the server to create a correct expiry caveat.
+    // The offset is the minutes between UTC and local time.
+    var d = new Date();
+    $('#timezoneOffset').val(d.getTimezoneOffset());
+
+    // Set the datetime picker to have a default value of one day from now.
+    $('.expiry').val(moment().add(1, 'd').format('YYYY-MM-DDTHH:mm'));
+    // Remove the clear button from the date input.
+    $('.expiry').attr('required', 'required');
+
+    // Activate the cancel button.
+    $('#cancel').click(function(){
+      $("#cancelled").val("true");
+      $("#caveats-form").submit();
+    });
+  });
+  </script>
+</body>
+</html>`))
diff --git a/services/identity/internal/templates/home.go b/services/identity/internal/templates/home.go
new file mode 100644
index 0000000..03d8d67
--- /dev/null
+++ b/services/identity/internal/templates/home.go
@@ -0,0 +1,36 @@
+// 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 templates
+
+import "html/template"
+
+var Home = template.Must(homeWithHeader.Parse(sidebarPartial))
+var homeWithHead = template.Must(home.Parse(headPartial))
+var homeWithHeader = template.Must(homeWithHead.Parse(headerPartial))
+
+var home = template.Must(template.New("main").Parse(`<!doctype html>
+<html>
+<head>
+  <title>Vanadium Identity Provider</title>
+  {{template "head" .}}
+</head>
+
+<body class="identityprovider-layout">
+  {{template "header" .}}
+  <main>
+    <h1 class="page-head">Authorize Vanadium apps with Google</h1>
+    <p>
+      The Vanadium Identity Provider authorizes Vanadium blessings based on your Google Account.<br>
+      <a href="http://v.io/glossary.html#identity-provider">Learn more</a>
+    </p>
+    <p>
+      <a href="/auth/google/{{.ListBlessingsRoute}}" class="button-passive">
+        Show blessings
+      </a>
+    </p>
+    {{template "sidebar" .}}
+  </main>
+</body>
+</html>`))
diff --git a/services/identity/internal/templates/list_blessings.go b/services/identity/internal/templates/list_blessings.go
new file mode 100644
index 0000000..481f0a6
--- /dev/null
+++ b/services/identity/internal/templates/list_blessings.go
@@ -0,0 +1,135 @@
+// 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 templates
+
+import "html/template"
+
+var ListBlessings = template.Must(listWithHeader.Parse(sidebarPartial))
+var listWithHead = template.Must(listBlessings.Parse(headPartial))
+var listWithHeader = template.Must(listWithHead.Parse(headerPartial))
+
+var listBlessings = template.Must(template.New("auditor").Parse(`<!doctype html>
+<html>
+<head>
+  <title>Blessings for {{.Email}} - Vanadium Identity Provider</title>
+  {{template "head" .}}
+  <link rel="stylesheet" href="{{.AssetsPrefix}}/identity/toastr.css">
+</head>
+
+<body class="identityprovider-layout">
+  {{template "header" .}}
+  <main>
+    <h1 class="page-head">Authorize Vanadium apps with Google</h1>
+    <p>
+      The Vanadium Identity Provider authorizes Vanadium blessings based on your Google Account.<br>
+      <a href="http://v.io/glossary.html#identity-provider">Learn more</a>
+    </p>
+
+    <div class="blessings-list">
+      <div class="blessings-header">
+          <h1>Your blessings</h1>
+          <h5>Issued</h5>
+          <h5>Revoked</h5>
+        </div>
+    {{range .Log}}
+      {{if .Error}}
+        <h1>Error</h1>
+        <p>
+          Failed to read audit log.<br>
+          {{.Error}}
+        </p>
+      {{else}} {{/* if .Error */}}
+        <div class="blessings-item">
+          <div class="blessing-details">
+            <h3>{{.Blessed}}</h3>
+            <p>
+              <b>Public Key</b><br>
+              {{.Blessed.PublicKey}}
+            </p>
+            <p class="blessing-caveats">
+              <b>Caveats</b><br>
+              {{range .Caveats}}
+                {{.}}<br>
+              {{end}}
+            </p>
+          </div>
+
+          <div class="blessing-issued unixtime" data-unixtime={{.Timestamp.Unix}}>{{.Timestamp.String}}</div>
+
+          <div class="blessing-revoked">
+            {{if .Token}}
+              <button class="revoke button-passive" value="{{.Token}}">Revoke</button>
+            {{else if not .RevocationTime.IsZero}}
+              <p class="unixtime" data-unixtime={{.RevocationTime.Unix}}>{{.RevocationTime.String}}</p>
+            {{end}}
+          </div>
+        </div>
+      {{end}} {{/* if .Error */}}
+    {{else}} {{/* range .Log */}}
+      <p>
+        <a href="http://v.io/installation">Install Vanadium</a> to set up your first blessing.
+      </p>
+    {{end}} {{/* range .Log */}}
+    </div>
+  {{template "sidebar" .}}
+  </main>
+
+  <script src="{{.AssetsPrefix}}/identity/toastr.js"></script>
+  <script src="{{.AssetsPrefix}}/identity/moment.js"></script>
+  <script src="{{.AssetsPrefix}}/identity/jquery.js"></script>
+  <script>
+  function setTimeText(elem) {
+    var timestamp = elem.data("unixtime");
+    var m = moment(timestamp*1000.0);
+    var style = elem.data("style");
+    if (style === "absolute") {
+      elem.html("<a href='#'>" + m.format("MMM DD, YYYY h:mm:ss a") + "</a>");
+      elem.data("style", "fromNow");
+    } else {
+      elem.html("<a href='#'>" + m.fromNow() + "</a>");
+      elem.data("style", "absolute");
+    }
+  }
+
+  $(document).ready(function() {
+    $(".unixtime").each(function() {
+      // clicking the timestamp should toggle the display format.
+      $(this).click(function() { setTimeText($(this)); });
+      setTimeText($(this));
+    });
+
+    // Setup the revoke buttons click events.
+    $(".revoke").click(function() {
+      var revokeButton = $(this);
+      $.ajax({
+        url: "/auth/google/{{.RevokeRoute}}",
+        type: "POST",
+        data: JSON.stringify({
+          "Token": revokeButton.val()
+        })
+      }).done(function(data) {
+        if (data.success == "false") {
+          failMessage(revokeButton);
+          return;
+        }
+        revokeButton.replaceWith("<div>Revoked just now</div>");
+      }).fail(function(xhr, textStatus){
+        failMessage(revokeButton);
+        console.error('Bad request: %s', status, xhr)
+      });
+    });
+  });
+
+  function failMessage(revokeButton) {
+    revokeButton.parent().parent().fadeIn(function(){
+      $(this).addClass("bg-danger");
+    });
+    toastr.options.closeButton = true;
+    toastr.error('Unable to revoke identity', 'Error')
+  }
+  </script>
+
+</body>
+</html>`))
diff --git a/services/identity/internal/templates/partials.go b/services/identity/internal/templates/partials.go
new file mode 100644
index 0000000..d2a8d11
--- /dev/null
+++ b/services/identity/internal/templates/partials.go
@@ -0,0 +1,116 @@
+// 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 templates
+
+var headPartial = `{{define "head"}}
+  <meta
+     name="viewport"
+     content="width=device-width,
+              initial-scale=1,
+              maximum-scale=1,
+              user-scalable=no,
+              minimal-ui">
+  <meta
+     name="apple-mobile-web-app-capable"
+     content="yes">
+
+  <meta
+     name="apple-mobile-web-app-status-bar-style"
+     content="black">
+
+
+  <link href='//fonts.googleapis.com/css?family=Source+Code+Pro:400,500|Roboto:500,400italic,300,500italic,300italic,400'
+    rel='stylesheet'
+    type='text/css'>
+
+  <link rel="stylesheet" href="{{.AssetsPrefix}}/identity.css">
+
+  <link rel="apple-touch-icon" sizes="57x57" href="{{.AssetsPrefix}}/favicons/apple-touch-icon-57x57.png">
+  <link rel="apple-touch-icon" sizes="114x114" href="{{.AssetsPrefix}}/favicons/apple-touch-icon-114x114.png">
+  <link rel="apple-touch-icon" sizes="72x72" href="{{.AssetsPrefix}}/favicons/apple-touch-icon-72x72.png">
+  <link rel="apple-touch-icon" sizes="144x144" href="{{.AssetsPrefix}}/favicons/apple-touch-icon-144x144.png">
+  <link rel="apple-touch-icon" sizes="60x60" href="{{.AssetsPrefix}}/favicons/apple-touch-icon-60x60.png">
+  <link rel="apple-touch-icon" sizes="120x120" href="{{.AssetsPrefix}}/favicons/apple-touch-icon-120x120.png">
+  <link rel="apple-touch-icon" sizes="76x76" href="{{.AssetsPrefix}}/favicons/apple-touch-icon-76x76.png">
+  <link rel="apple-touch-icon" sizes="152x152" href="{{.AssetsPrefix}}/favicons/apple-touch-icon-152x152.png">
+  <link rel="apple-touch-icon" sizes="180x180" href="{{.AssetsPrefix}}/favicons/apple-touch-icon-180x180.png">
+  <link rel="icon" type="image/png" href="{{.AssetsPrefix}}/favicons/favicon-192x192.png" sizes="192x192">
+  <link rel="icon" type="image/png" href="{{.AssetsPrefix}}/favicons/favicon-160x160.png" sizes="160x160">
+  <link rel="icon" type="image/png" href="{{.AssetsPrefix}}/favicons/favicon-96x96.png" sizes="96x96">
+  <link rel="icon" type="image/png" href="{{.AssetsPrefix}}/favicons/favicon-16x16.png" sizes="16x16">
+  <link rel="icon" type="image/png" href="{{.AssetsPrefix}}/favicons/favicon-32x32.png" sizes="32x32">
+  <meta name="msapplication-TileColor" content="#da532c">
+  <meta name="msapplication-TileImage" content="{{.AssetsPrefix}}/favicons/mstile-144x144.png">
+{{end}}`
+
+var headerPartial = `{{define "header"}}
+  <header>
+    <nav class="left">
+      <a href="#" class="logo">Vanadium</a>
+      <span class="service-name">Identity Provider</span>
+    </nav>
+    <nav class="right">
+      {{if .Email}}
+        <a href="#">{{.Email}}</a>
+      {{end}}
+    </nav>
+  </header>
+{{end}}`
+
+var sidebarPartial = `{{define "sidebar"}}
+<section class="provider-info">
+  <div class="provider-info-section">
+    <h5>Root name</h5>
+    <span class="provider-address">
+      {{.Self}}
+    </span>
+
+    <h5>Public key</h5>
+    <span class="provider-address">
+      {{.Self.PublicKey}}
+    </span>
+
+    <p>
+      Get this provider’s root name and public key as a <a
+      href="http://en.wikipedia.org/wiki/X.690#DER_encoding" target="_blank">
+      DER</a>-encoded <a href="/auth/blessing-root" target="_blank">
+      JSON object</a>.
+    </p>
+  </div>
+
+  {{if .GoogleServers}}
+    <div class="provider-info-section">
+      <h5>Blessings</h5>
+      <p>
+        Provided via Vanadium RPC to:
+        <span class="provider-address">
+          {{range .GoogleServers}}{{.}}{{end}}
+        </span>
+      </p>
+    </div>
+  {{end}}
+
+  {{if .DischargeServers}}
+    <div class="provider-info-section">
+      <h5>Discharges</h5>
+      <p>
+        Provided via Vanadium RPC to:
+        <span class="provider-address">
+          {{range .DischargeServers}}{{.}}{{end}}
+        </span>
+      </p>
+    </div>
+  {{end}}
+
+  <div class="provider-info-section">
+    <h5>Learn more</h5>
+    <p>
+    Vanadium Concepts: <a href="https://v.io/concepts/security.html">Security</a><br>
+    <a href="https://v.io/tutorials/security">Tutorials</a><br>
+    <a href="https://v.io/tools/identity-service-faq.html">FAQ</a><br>
+    </p>
+  </div>
+</section>
+{{end}}`
diff --git a/services/identity/internal/util/blessings_info.go b/services/identity/internal/util/blessings_info.go
new file mode 100644
index 0000000..3c6cb47
--- /dev/null
+++ b/services/identity/internal/util/blessings_info.go
@@ -0,0 +1,26 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package util
+
+import (
+	"fmt"
+	"v.io/v23/security"
+	"v.io/v23/vom"
+)
+
+// Circuitious route to obtain the certificate chain because the use
+// of security.MarshalBlessings is discouraged.
+func RootCertificateDetails(b security.Blessings) (string, []byte, error) {
+	data, err := vom.Encode(b)
+	if err != nil {
+		return "", nil, fmt.Errorf("malformed Blessings: %v", err)
+	}
+	var wire security.WireBlessings
+	if err := vom.Decode(data, &wire); err != nil {
+		return "", nil, fmt.Errorf("malformed WireBlessings: %v", err)
+	}
+	cert := wire.CertificateChains[0][0]
+	return cert.Extension, cert.PublicKey, nil
+}
diff --git a/services/identity/internal/util/certs.go b/services/identity/internal/util/certs.go
new file mode 100644
index 0000000..c6aeba6
--- /dev/null
+++ b/services/identity/internal/util/certs.go
@@ -0,0 +1,34 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package util
+
+import (
+	"fmt"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"strings"
+	"time"
+)
+
+// WriteCertAndKey creates a certificate and private key for a given host and
+// duration and writes them to cert.pem and key.pem in tmpdir.  It returns the
+// locations of the files, or an error if one is encountered.
+func WriteCertAndKey(host string, duration time.Duration) (string, string, error) {
+	listCmd := exec.Command("go", "list", "-f", "{{.Dir}}", "crypto/tls")
+	output, err := listCmd.Output()
+	if err != nil {
+		return "", "", fmt.Errorf("%s failed: %v", strings.Join(listCmd.Args, " "), err)
+	}
+	tmpDir := os.TempDir()
+	generateCertFile := filepath.Join(strings.TrimSpace(string(output)), "generate_cert.go")
+	generateCertCmd := exec.Command("go", "run", generateCertFile, "--host", host, "--duration", duration.String())
+	generateCertCmd.Dir = tmpDir
+	if output, err := generateCertCmd.CombinedOutput(); err != nil {
+		fmt.Fprintf(os.Stderr, "%v failed:\n%s\n", generateCertCmd.Args, output)
+		return "", "", fmt.Errorf("Could not generate key and cert: %v", err)
+	}
+	return filepath.Join(tmpDir, "cert.pem"), filepath.Join(tmpDir, "key.pem"), nil
+}
diff --git a/services/identity/internal/util/common_test.go b/services/identity/internal/util/common_test.go
new file mode 100644
index 0000000..5b52b30
--- /dev/null
+++ b/services/identity/internal/util/common_test.go
@@ -0,0 +1,24 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package util
+
+import "net/http"
+
+type iface interface {
+	Method()
+}
+type impl struct{ Content string }
+
+func (i *impl) Method() {}
+
+var _ iface = (*impl)(nil)
+
+func newRequest() *http.Request {
+	r, err := http.NewRequest("GET", "http://does-not-matter", nil)
+	if err != nil {
+		panic(err)
+	}
+	return r
+}
diff --git a/services/identity/internal/util/csrf.go b/services/identity/internal/util/csrf.go
new file mode 100644
index 0000000..6409ba8
--- /dev/null
+++ b/services/identity/internal/util/csrf.go
@@ -0,0 +1,141 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package util
+
+import (
+	"crypto/hmac"
+	"crypto/rand"
+	"crypto/sha256"
+	"encoding/base64"
+	"fmt"
+	"net/http"
+	"time"
+
+	"v.io/v23/context"
+	"v.io/v23/vom"
+)
+
+const (
+	cookieLen = 16
+	keyLength = 16
+)
+
+// CSRFCop implements utilities for generating and validating tokens for
+// cross-site-request-forgery prevention (also called XSRF).
+type CSRFCop struct {
+	key []byte
+	ctx *context.T
+}
+
+func (c *CSRFCop) keyForCookie(cookie []byte) []byte {
+	hm := hmac.New(sha256.New, c.key)
+	hm.Write(cookie)
+	return hm.Sum(nil)
+}
+
+func NewCSRFCop(ctx *context.T) (*CSRFCop, error) {
+	key := make([]byte, keyLength)
+	if _, err := rand.Read(key); err != nil {
+		return nil, fmt.Errorf("newCSRFCop failed: %v", err)
+	}
+	return &CSRFCop{key: key, ctx: ctx}, nil
+}
+
+// NewToken creates an anti-cross-site-request-forgery, aka CSRF aka XSRF token
+// with some data bound to it that can be obtained by ValidateToken.
+// It returns an error if the token could not be created.
+func (c *CSRFCop) NewToken(w http.ResponseWriter, r *http.Request, cookieName string, data interface{}) (string, error) {
+	cookieValue, err := c.MaybeSetCookie(w, r, cookieName)
+	if err != nil {
+		return "", fmt.Errorf("bad cookie: %v", err)
+	}
+	var encData []byte
+	if data != nil {
+		if encData, err = vom.Encode(data); err != nil {
+			return "", err
+		}
+	}
+	return string(NewMacaroon(c.keyForCookie(cookieValue), encData)), nil
+}
+
+// ValidateToken checks the validity of the provided CSRF token for the
+// provided request, and extracts the data encoded in the token into 'decoded'.
+// If the token is invalid, return an error. This error should not be shown to end users,
+// it is meant for the consumption by the server process only.
+func (c *CSRFCop) ValidateToken(token string, req *http.Request, cookieName string, decoded interface{}) error {
+	cookie, err := req.Cookie(cookieName)
+	if err != nil {
+		return err
+	}
+	cookieValue, err := decodeCookieValue(cookie.Value)
+	if err != nil {
+		return fmt.Errorf("invalid cookie")
+	}
+	encodedInput, err := Macaroon(token).Decode(c.keyForCookie(cookieValue))
+	if err != nil {
+		return err
+	}
+	if decoded != nil {
+		if err := vom.Decode(encodedInput, decoded); err != nil {
+			return fmt.Errorf("invalid token data: %v", err)
+		}
+	}
+	return nil
+}
+
+func (c *CSRFCop) MaybeSetCookie(w http.ResponseWriter, req *http.Request, cookieName string) ([]byte, error) {
+	cookie, err := req.Cookie(cookieName)
+	switch err {
+	case nil:
+		if v, err := decodeCookieValue(cookie.Value); err == nil {
+			return v, nil
+		}
+		c.ctx.Infof("Invalid cookie: %#v, err: %v. Regenerating one.", cookie, err)
+	case http.ErrNoCookie:
+		// Intentionally blank: Cookie will be generated below.
+	default:
+		c.ctx.Infof("Error decoding cookie %q in request: %v. Regenerating one.", cookieName, err)
+	}
+	cookie, v := newCookie(c.ctx, cookieName)
+	if cookie == nil || v == nil {
+		return nil, fmt.Errorf("failed to create cookie")
+	}
+	http.SetCookie(w, cookie)
+	// We need to add the cookie to the request also to prevent repeatedly resetting cookies on multiple
+	// calls from the same request.
+	req.AddCookie(cookie)
+	return v, nil
+}
+
+func newCookie(ctx *context.T, cookieName string) (*http.Cookie, []byte) {
+	b := make([]byte, cookieLen)
+	if _, err := rand.Read(b); err != nil {
+		ctx.Errorf("newCookie failed: %v", err)
+		return nil, nil
+	}
+	return &http.Cookie{
+		Name:     cookieName,
+		Value:    b64encode(b),
+		Expires:  time.Now().Add(time.Hour * 24),
+		HttpOnly: true,
+		Secure:   true,
+		Path:     "/",
+	}, b
+}
+
+func decodeCookieValue(v string) ([]byte, error) {
+	b, err := b64decode(v)
+	if err != nil {
+		return nil, err
+	}
+	if len(b) != cookieLen {
+		return nil, fmt.Errorf("invalid cookie length[%d]", len(b))
+	}
+	return b, nil
+}
+
+// Shorthands.
+func b64encode(b []byte) string          { return base64.URLEncoding.EncodeToString(b) }
+func b64decode(s string) ([]byte, error) { return base64.URLEncoding.DecodeString(s) }
diff --git a/services/identity/internal/util/csrf_test.go b/services/identity/internal/util/csrf_test.go
new file mode 100644
index 0000000..8e4eefa
--- /dev/null
+++ b/services/identity/internal/util/csrf_test.go
@@ -0,0 +1,158 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package util
+
+import (
+	"fmt"
+	"net/http"
+	"net/http/httptest"
+	"strings"
+	"testing"
+
+	"v.io/v23/context"
+	"v.io/x/ref/internal/logger"
+)
+
+const (
+	cookieName     = "VeyronCSRFTestCookie"
+	failCookieName = "FailCookieName"
+)
+
+func TestCSRFTokenWithoutCookie(t *testing.T) {
+	ctx, _ := context.RootContext()
+	ctx = context.WithLogger(ctx, logger.Global())
+	r := newRequest()
+	c, err := NewCSRFCop(ctx)
+	if err != nil {
+		t.Fatalf("NewCSRFCop failed: %v", err)
+	}
+	w := httptest.NewRecorder()
+	tok, err := c.NewToken(w, r, cookieName, nil)
+	if err != nil {
+		t.Errorf("NewToken failed: %v", err)
+	}
+	cookie, err := cookieVal(w, cookieName)
+	if err != nil {
+		t.Error(err)
+	}
+	if len(cookie) == 0 {
+		t.Errorf("Cookie should have been set. Request: [%v], Response: [%v]", r, w)
+	}
+	// Cookie needs to be present for validation
+	r.AddCookie(&http.Cookie{Name: cookieName, Value: cookie})
+	if err := c.ValidateToken(tok, r, cookieName, nil); err != nil {
+		t.Error("CSRF token failed validation:", err)
+	}
+
+	w = httptest.NewRecorder()
+	if _, err = c.MaybeSetCookie(w, r, failCookieName); err != nil {
+		t.Error("failed to create cookie: ", err)
+	}
+	cookie, err = cookieVal(w, failCookieName)
+	if err != nil {
+		t.Error(err)
+	}
+	if len(cookie) == 0 {
+		t.Errorf("Cookie should have been set. Request: [%v], Response: [%v]", r, w)
+	}
+
+	if err := c.ValidateToken(tok, r, failCookieName, nil); err == nil {
+		t.Error("CSRF token should have failed validation")
+	}
+}
+
+func TestCSRFTokenWithCookie(t *testing.T) {
+	ctx, _ := context.RootContext()
+	ctx = context.WithLogger(ctx, logger.Global())
+	r := newRequest()
+	c, err := NewCSRFCop(ctx)
+	if err != nil {
+		t.Fatalf("NewCSRFCop failed: %v", err)
+	}
+	w := httptest.NewRecorder()
+	r.AddCookie(&http.Cookie{Name: cookieName, Value: "u776AC7hf794pTtGVlO50w=="})
+	tok, err := c.NewToken(w, r, cookieName, nil)
+	if err != nil {
+		t.Errorf("NewToken failed: %v", err)
+	}
+	cookie, err := cookieVal(w, cookieName)
+	if err != nil {
+		t.Error(err)
+	}
+	if len(cookie) > 0 {
+		t.Errorf("Cookie should not be set when it is already present. Request: [%v], Response: [%v]", r, w)
+	}
+	if err := c.ValidateToken(tok, r, cookieName, nil); err != nil {
+		t.Error("CSRF token failed validation:", err)
+	}
+
+	r.AddCookie(&http.Cookie{Name: failCookieName, Value: "u864AC7gf794pTtCAlO40w=="})
+	if err := c.ValidateToken(tok, r, failCookieName, nil); err == nil {
+		t.Error("CSRF token should have failed validation")
+	}
+}
+
+func TestCSRFTokenWithData(t *testing.T) {
+	ctx, _ := context.RootContext()
+	ctx = context.WithLogger(ctx, logger.Global())
+	r := newRequest()
+	c, err := NewCSRFCop(ctx)
+	if err != nil {
+		t.Fatalf("NewCSRFCop failed: %v", err)
+	}
+	w := httptest.NewRecorder()
+	r.AddCookie(&http.Cookie{Name: cookieName, Value: "u776AC7hf794pTtGVlO50w=="})
+	tok, err := c.NewToken(w, r, cookieName, 1)
+	if err != nil {
+		t.Errorf("NewToken failed: %v", err)
+	}
+	cookie, err := cookieVal(w, cookieName)
+	if err != nil {
+		t.Error(err)
+	}
+	if len(cookie) > 0 {
+		t.Errorf("Cookie should not be set when it is already present. Request: [%v], Response: [%v]", r, w)
+	}
+	var got int
+	if err := c.ValidateToken(tok, r, cookieName, &got); err != nil {
+		t.Error("CSRF token failed validation:", err)
+	}
+	if want := 1; got != want {
+		t.Errorf("Got %v, want %v", got, want)
+	}
+
+	r.AddCookie(&http.Cookie{Name: failCookieName, Value: "u864AC7gf794pTtCAlO40w=="})
+	if err := c.ValidateToken(tok, r, failCookieName, &got); err == nil {
+		t.Error("CSRF token should have failed validation")
+	}
+}
+
+func cookieVal(w *httptest.ResponseRecorder, cookieName string) (string, error) {
+	cookie := w.Header().Get("Set-Cookie")
+	if len(cookie) == 0 {
+		return "", nil
+	}
+	var (
+		val              string
+		httpOnly, secure bool
+	)
+	for _, part := range strings.Split(cookie, "; ") {
+		switch {
+		case strings.HasPrefix(part, cookieName):
+			val = strings.TrimPrefix(part, cookieName+"=")
+		case part == "HttpOnly":
+			httpOnly = true
+		case part == "Secure":
+			secure = true
+		}
+	}
+	if !httpOnly {
+		return "", fmt.Errorf("cookie for name %v is not HttpOnly", cookieName)
+	}
+	if !secure {
+		return "", fmt.Errorf("cookie for name %v is not Secure", cookieName)
+	}
+	return val, nil
+}
diff --git a/services/identity/internal/util/doc.go b/services/identity/internal/util/doc.go
new file mode 100644
index 0000000..8e33c19
--- /dev/null
+++ b/services/identity/internal/util/doc.go
@@ -0,0 +1,6 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package util implements miscellaneous utility functions needed by the identity HTTP server.
+package util
diff --git a/services/identity/internal/util/macaroon.go b/services/identity/internal/util/macaroon.go
new file mode 100644
index 0000000..eb9d5f7
--- /dev/null
+++ b/services/identity/internal/util/macaroon.go
@@ -0,0 +1,46 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package util
+
+import (
+	"bytes"
+	"crypto/hmac"
+	"crypto/sha256"
+	"fmt"
+)
+
+// Macaroon encapsulates an arbitrary slice of data with an HMAC for integrity protection.
+// Term borrowed from http://research.google.com/pubs/pub41892.html.
+type Macaroon string
+
+// NewMacaroon creates an opaque token that encodes "data".
+//
+// Input can be extracted from the returned token only if the key provided to NewMacaroon is known.
+func NewMacaroon(key, data []byte) Macaroon {
+	return Macaroon(b64encode(append(data, computeHMAC(key, data)...)))
+}
+
+// Decode returns the input if the macaroon is decodable with the provided key.
+func (m Macaroon) Decode(key []byte) (input []byte, err error) {
+	decoded, err := b64decode(string(m))
+	if err != nil {
+		return nil, err
+	}
+	if len(decoded) < sha256.Size {
+		return nil, fmt.Errorf("invalid macaroon, too small")
+	}
+	data := decoded[:len(decoded)-sha256.Size]
+	decodedHMAC := decoded[len(decoded)-sha256.Size:]
+	if !bytes.Equal(decodedHMAC, computeHMAC(key, data)) {
+		return nil, fmt.Errorf("invalid macaroon, HMAC does not match")
+	}
+	return data, nil
+}
+
+func computeHMAC(key, data []byte) []byte {
+	hm := hmac.New(sha256.New, key)
+	hm.Write(data)
+	return hm.Sum(nil)
+}
diff --git a/services/identity/internal/util/macaroon_test.go b/services/identity/internal/util/macaroon_test.go
new file mode 100644
index 0000000..bc52009
--- /dev/null
+++ b/services/identity/internal/util/macaroon_test.go
@@ -0,0 +1,77 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package util
+
+import (
+	"bytes"
+	"crypto/rand"
+	"testing"
+)
+
+func TestMacaroon(t *testing.T) {
+	var (
+		key          = randBytes(t)
+		incorrectKey = randBytes(t)
+		input        = randBytes(t)
+		m            = NewMacaroon(key, input)
+	)
+
+	// Test incorrect key.
+	decoded, err := m.Decode(incorrectKey)
+	if err == nil {
+		t.Errorf("m.Decode should have failed")
+	}
+	if decoded != nil {
+		t.Errorf("decoded value should be nil when decode fails")
+	}
+
+	// Test correct key.
+	if decoded, err = m.Decode(key); err != nil {
+		t.Errorf("m.Decode should have succeeded")
+	}
+	if !bytes.Equal(decoded, input) {
+		t.Errorf("decoded value should equal input")
+	}
+}
+
+func TestBadMacaroon(t *testing.T) {
+	var (
+		key   = []byte{0xd4, 0x4f, 0x6b, 0x5c, 0xf2, 0x5f, 0xc4, 0x3, 0x68, 0x34, 0x15, 0xc6, 0x26, 0xc5, 0x1, 0x8a}
+		tests = []Macaroon{
+			"Hkd_PqB1ekct6eH1rTntfJYFgYpGFQpM7z0Ur8cuAcVQscWa-FNV4_kTfC_2m4r-",   // valid
+			"Hkd_PqB1ekct6eH1rTntfJYFgYpGFQpM7z0Ur8cuAcVQscWa-FNV4_kTfC_2m4r",    // truncated
+			"Hkd_PqB1ekct6eH1rTntfJYFgYpGFQpM7z0Ur8cuAcVQscWa-FNV4_kTfC_2m4r=",   // truncated content but valid base64
+			"Hkd_PqB1ekct6eH1rTntfJYFgYpGFQpM7z0Ur8cuAcVQscWa-FNV4_kTfC_2m4r--",  // extended
+			"Hkd_PqB1ekct6eH1rTntfJYFgYpGFQpM7z0Ur8cuAcVQscWa-FNV4_kTfC_2m4r-A=", // extended content but valid base64
+			"AAA_PqB1ekct6eH1rTntfJYFgYpGFQpM7z0Ur8cuAcVQscWa-FNV4_kTfC_2m4r-",   // modified data
+			"Hkd_PqB1ekct6eH1rTntfJYFgYpGFQpM7z0Ur8cuAcVQscWa-FNV4_kTfC_XXXX-",   // modified HMAC
+			"", // zero value
+		}
+	)
+	// Test data above was generated by:
+	//{
+	//	key := randBytes(t)
+	//	t.Logf("key=%#v\ntests = []Macaroon{\n\t%q, // valid\n}", key, NewMacaroon(key, randBytes(t)))
+	//}
+
+	// Make sure that "valid" is indeed valid!
+	if data, err := tests[0].Decode(key); err != nil || data == nil {
+		t.Fatalf("Bad test data: Got (%v, %v), want (<non-empty>, nil)", data, err)
+	}
+	// And all others are not:
+	for idx, test := range tests[1:] {
+		if _, err := test.Decode(key); err == nil {
+			t.Errorf("Should have failed to decode invalid macaroon #%d", idx)
+		}
+	}
+}
+
+func randBytes(t *testing.T) []byte {
+	b := make([]byte, 16)
+	if _, err := rand.Read(b); err != nil {
+		t.Fatalf("bytes creation failed: %v", err)
+	}
+	return b
+}
diff --git a/services/identity/internal/util/write.go b/services/identity/internal/util/write.go
new file mode 100644
index 0000000..5eefb0c
--- /dev/null
+++ b/services/identity/internal/util/write.go
@@ -0,0 +1,75 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package util
+
+import (
+	"bytes"
+	"html/template"
+	"net/http"
+
+	"v.io/x/ref/internal/logger"
+)
+
+// HTTPBadRequest sends an HTTP 400 error on 'w' and renders a pretty page.
+// If err is not nil, it also renders the string representation of err in the response page.
+func HTTPBadRequest(w http.ResponseWriter, req *http.Request, err error) {
+	w.WriteHeader(http.StatusBadRequest)
+	if e := tmplBadRequest.Execute(w, badRequestData{Request: requestString(req), Error: err}); e != nil {
+		logger.Global().Errorf("Failed to execute Bad Request Template: %v", e)
+	}
+}
+
+// ServerError sends an HTTP 500 error on 'w' and renders a pretty page that
+// also has the string representation of err.
+func HTTPServerError(w http.ResponseWriter, err error) {
+	w.WriteHeader(http.StatusInternalServerError)
+	if e := tmplServerError.Execute(w, err); e != nil {
+		logger.Global().Errorf("Failed to execute Server Error template: %v", e)
+	}
+}
+
+var (
+	tmplBadRequest = template.Must(template.New("Bad Request").Parse(`<!doctype html>
+<html>
+<head>
+<meta charset="UTF8">
+<title>Bad Request</title>
+</head>
+<body>
+<h1>Bad Request</h1>
+{{with $data := .}}
+{{if $data.Error}}Error: {{$data.Error}}{{end}}
+<pre>
+{{$data.Request}}
+</pre>
+{{end}}
+</body>
+</html>`))
+
+	tmplServerError = template.Must(template.New("Server Error").Parse(`<!doctype html>
+<html>
+<head>
+<meta charset="UTF8">
+<title>Server Error</title>
+</head>
+<body>
+<h1>Oops! Error at the server</h1>
+Error: {{.}}
+<br/>
+Ask the server administrator to check the server logs
+</body>
+</html>`))
+)
+
+func requestString(r *http.Request) string {
+	var buf bytes.Buffer
+	r.Write(&buf)
+	return buf.String()
+}
+
+type badRequestData struct {
+	Request string
+	Error   error
+}
diff --git a/services/identity/internal/util/write_test.go b/services/identity/internal/util/write_test.go
new file mode 100644
index 0000000..a980dfc
--- /dev/null
+++ b/services/identity/internal/util/write_test.go
@@ -0,0 +1,27 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package util
+
+import (
+	"net/http"
+	"net/http/httptest"
+	"testing"
+)
+
+func TestBadRequest(t *testing.T) {
+	w := httptest.NewRecorder()
+	HTTPBadRequest(w, newRequest(), nil)
+	if got, want := w.Code, http.StatusBadRequest; got != want {
+		t.Errorf("Got %d, want %d", got, want)
+	}
+}
+
+func TestServerError(t *testing.T) {
+	w := httptest.NewRecorder()
+	HTTPServerError(w, nil)
+	if got, want := w.Code, http.StatusInternalServerError; got != want {
+		t.Errorf("Got %d, want %d", got, want)
+	}
+}
diff --git a/services/internal/binarylib/client.go b/services/internal/binarylib/client.go
new file mode 100644
index 0000000..2e0424e
--- /dev/null
+++ b/services/internal/binarylib/client.go
@@ -0,0 +1,364 @@
+// 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 binarylib
+
+// TODO(jsimsa): Implement parallel download and upload.
+
+import (
+	"bytes"
+	"crypto/md5"
+	"crypto/sha256"
+	"encoding/hex"
+	"fmt"
+	"hash"
+	"io"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/security"
+	"v.io/v23/services/binary"
+	"v.io/v23/services/repository"
+	"v.io/v23/verror"
+
+	"v.io/x/ref/services/internal/packages"
+)
+
+var (
+	errOperationFailed = verror.Register(pkgPath+".errOperationFailed", verror.NoRetry, "{1:}{2:} operation failed{:_}")
+)
+
+const (
+	nAttempts   = 2
+	partSize    = 1 << 22
+	subpartSize = 1 << 12
+)
+
+func Delete(ctx *context.T, name string) error {
+	if err := repository.BinaryClient(name).Delete(ctx); err != nil {
+		ctx.Errorf("Delete() failed: %v", err)
+		return err
+	}
+	return nil
+}
+
+type indexedPart struct {
+	part   binary.PartInfo
+	index  int
+	offset int64
+}
+
+func downloadPartAttempt(ctx *context.T, w io.WriteSeeker, client repository.BinaryClientStub, ip *indexedPart) bool {
+	ctx, cancel := context.WithCancel(ctx)
+	defer cancel()
+
+	if _, err := w.Seek(ip.offset, 0); err != nil {
+		ctx.Errorf("Seek(%v, 0) failed: %v", ip.offset, err)
+		return false
+	}
+	stream, err := client.Download(ctx, int32(ip.index))
+	if err != nil {
+		ctx.Errorf("Download(%v) failed: %v", ip.index, err)
+		return false
+	}
+	h, nreceived := md5.New(), 0
+	rStream := stream.RecvStream()
+	for rStream.Advance() {
+		bytes := rStream.Value()
+		if _, err := w.Write(bytes); err != nil {
+			ctx.Errorf("Write() failed: %v", err)
+			return false
+		}
+		h.Write(bytes)
+		nreceived += len(bytes)
+	}
+
+	if err := rStream.Err(); err != nil {
+		ctx.Errorf("Advance() failed: %v", err)
+		return false
+	}
+	if err := stream.Finish(); err != nil {
+		ctx.Errorf("Finish() failed: %v", err)
+		return false
+	}
+	if expected, got := ip.part.Checksum, hex.EncodeToString(h.Sum(nil)); expected != got {
+		ctx.Errorf("Unexpected checksum: expected %v, got %v", expected, got)
+		return false
+	}
+	if expected, got := ip.part.Size, int64(nreceived); expected != got {
+		ctx.Errorf("Unexpected size: expected %v, got %v", expected, got)
+		return false
+	}
+	return true
+}
+
+func downloadPart(ctx *context.T, w io.WriteSeeker, client repository.BinaryClientStub, ip *indexedPart) bool {
+	for i := 0; i < nAttempts; i++ {
+		if downloadPartAttempt(ctx, w, client, ip) {
+			return true
+		}
+	}
+	return false
+}
+
+func Stat(ctx *context.T, name string) (repository.MediaInfo, error) {
+	client := repository.BinaryClient(name)
+	_, mediaInfo, err := client.Stat(ctx)
+	if err != nil {
+		return repository.MediaInfo{}, err
+	}
+	return mediaInfo, nil
+}
+
+func download(ctx *context.T, w io.WriteSeeker, von string) (repository.MediaInfo, error) {
+	client := repository.BinaryClient(von)
+	parts, mediaInfo, err := client.Stat(ctx)
+	if err != nil {
+		ctx.Errorf("Stat() failed: %v", err)
+		return repository.MediaInfo{}, err
+	}
+	for _, part := range parts {
+		if part.Checksum == binary.MissingChecksum {
+			return repository.MediaInfo{}, verror.New(verror.ErrNoExist, ctx)
+		}
+	}
+	offset := int64(0)
+	for i, part := range parts {
+		ip := &indexedPart{part, i, offset}
+		if !downloadPart(ctx, w, client, ip) {
+			return repository.MediaInfo{}, verror.New(errOperationFailed, ctx)
+		}
+		offset += part.Size
+	}
+	return mediaInfo, nil
+}
+
+func Download(ctx *context.T, von string) ([]byte, repository.MediaInfo, error) {
+	dir, prefix := "", ""
+	file, err := ioutil.TempFile(dir, prefix)
+	if err != nil {
+		ctx.Errorf("TempFile(%v, %v) failed: %v", dir, prefix, err)
+		return nil, repository.MediaInfo{}, verror.New(errOperationFailed, ctx)
+	}
+	defer os.Remove(file.Name())
+	defer file.Close()
+	mediaInfo, err := download(ctx, file, von)
+	if err != nil {
+		return nil, repository.MediaInfo{}, verror.New(errOperationFailed, ctx)
+	}
+	bytes, err := ioutil.ReadFile(file.Name())
+	if err != nil {
+		ctx.Errorf("ReadFile(%v) failed: %v", file.Name(), err)
+		return nil, repository.MediaInfo{}, verror.New(errOperationFailed, ctx)
+	}
+	return bytes, mediaInfo, nil
+}
+
+func DownloadToFile(ctx *context.T, von, path string) error {
+	dir := filepath.Dir(path)
+	prefix := fmt.Sprintf(".download.%s.", filepath.Base(path))
+	file, err := ioutil.TempFile(dir, prefix)
+	if err != nil {
+		ctx.Errorf("TempFile(%v, %v) failed: %v", dir, prefix, err)
+		return verror.New(errOperationFailed, ctx)
+	}
+	defer file.Close()
+	mediaInfo, err := download(ctx, file, von)
+	if err != nil {
+		if err := os.Remove(file.Name()); err != nil {
+			ctx.Errorf("Remove(%v) failed: %v", file.Name(), err)
+		}
+		return verror.New(errOperationFailed, ctx)
+	}
+	perm := os.FileMode(0600)
+	if err := file.Chmod(perm); err != nil {
+		ctx.Errorf("Chmod(%v) failed: %v", perm, err)
+		if err := os.Remove(file.Name()); err != nil {
+			ctx.Errorf("Remove(%v) failed: %v", file.Name(), err)
+		}
+		return verror.New(errOperationFailed, ctx)
+	}
+	if err := os.Rename(file.Name(), path); err != nil {
+		ctx.Errorf("Rename(%v, %v) failed: %v", file.Name(), path, err)
+		if err := os.Remove(file.Name()); err != nil {
+			ctx.Errorf("Remove(%v) failed: %v", file.Name(), err)
+		}
+		return verror.New(errOperationFailed, ctx)
+	}
+	if err := packages.SaveMediaInfo(path, mediaInfo); err != nil {
+		ctx.Errorf("packages.SaveMediaInfo(%v, %v) failed: %v", path, mediaInfo, err)
+		if err := os.Remove(path); err != nil {
+			ctx.Errorf("Remove(%v) failed: %v", path, err)
+		}
+		return verror.New(errOperationFailed, ctx)
+	}
+	return nil
+}
+
+func DownloadUrl(ctx *context.T, von string) (string, int64, error) {
+	url, ttl, err := repository.BinaryClient(von).DownloadUrl(ctx)
+	if err != nil {
+		ctx.Errorf("DownloadUrl() failed: %v", err)
+		return "", 0, err
+	}
+	return url, ttl, nil
+}
+
+func uploadPartAttempt(ctx *context.T, h hash.Hash, r io.ReadSeeker, client repository.BinaryClientStub, part int, size int64) (bool, error) {
+	ctx, cancel := context.WithCancel(ctx)
+	defer cancel()
+
+	offset := int64(part * partSize)
+	if _, err := r.Seek(offset, 0); err != nil {
+		ctx.Errorf("Seek(%v, 0) failed: %v", offset, err)
+		return false, nil
+	}
+	stream, err := client.Upload(ctx, int32(part))
+	if err != nil {
+		ctx.Errorf("Upload(%v) failed: %v", part, err)
+		return false, nil
+	}
+	bufferSize := partSize
+	if remaining := size - offset; remaining < int64(bufferSize) {
+		bufferSize = int(remaining)
+	}
+	buffer := make([]byte, bufferSize)
+
+	nread := 0
+	for nread < len(buffer) {
+		n, err := r.Read(buffer[nread:])
+		nread += n
+		if err != nil && (err != io.EOF || nread < len(buffer)) {
+			ctx.Errorf("Read() failed: %v", err)
+			return false, nil
+		}
+	}
+	sender := stream.SendStream()
+	for from := 0; from < len(buffer); from += subpartSize {
+		to := from + subpartSize
+		if to > len(buffer) {
+			to = len(buffer)
+		}
+		if err := sender.Send(buffer[from:to]); err != nil {
+			ctx.Errorf("Send() failed: %v", err)
+			return false, nil
+		}
+	}
+	// TODO(gauthamt): To detect corruption, the upload checksum needs
+	// to be computed here rather than on the binary server.
+	if err := sender.Close(); err != nil {
+		ctx.Errorf("Close() failed: %v", err)
+		parts, _, statErr := client.Stat(ctx)
+		if statErr != nil {
+			ctx.Errorf("Stat() failed: %v", statErr)
+			if deleteErr := client.Delete(ctx); err != nil {
+				ctx.Errorf("Delete() failed: %v", deleteErr)
+			}
+			return false, err
+		}
+		if parts[part].Checksum == binary.MissingChecksum {
+			return false, nil
+		}
+	}
+	if err := stream.Finish(); err != nil {
+		ctx.Errorf("Finish() failed: %v", err)
+		parts, _, statErr := client.Stat(ctx)
+		if statErr != nil {
+			ctx.Errorf("Stat() failed: %v", statErr)
+			if deleteErr := client.Delete(ctx); err != nil {
+				ctx.Errorf("Delete() failed: %v", deleteErr)
+			}
+			return false, err
+		}
+		if parts[part].Checksum == binary.MissingChecksum {
+			return false, nil
+		}
+	}
+	h.Write(buffer)
+	return true, nil
+}
+
+func uploadPart(ctx *context.T, h hash.Hash, r io.ReadSeeker, client repository.BinaryClientStub, part int, size int64) error {
+	for i := 0; i < nAttempts; i++ {
+		if success, err := uploadPartAttempt(ctx, h, r, client, part, size); success || err != nil {
+			return err
+		}
+	}
+	return verror.New(errOperationFailed, ctx)
+}
+
+func upload(ctx *context.T, r io.ReadSeeker, mediaInfo repository.MediaInfo, von string) (*security.Signature, error) {
+	client := repository.BinaryClient(von)
+	offset, whence := int64(0), 2
+	size, err := r.Seek(offset, whence)
+	if err != nil {
+		ctx.Errorf("Seek(%v, %v) failed: %v", offset, whence, err)
+		return nil, verror.New(errOperationFailed, ctx)
+	}
+	nparts := (size-1)/partSize + 1
+	if err := client.Create(ctx, int32(nparts), mediaInfo); err != nil {
+		ctx.Errorf("Create() failed: %v", err)
+		return nil, err
+	}
+	h := sha256.New()
+	for i := 0; int64(i) < nparts; i++ {
+		if err := uploadPart(ctx, h, r, client, i, size); err != nil {
+			return nil, err
+		}
+	}
+	return signHash(ctx, h)
+}
+
+func signHash(ctx *context.T, h hash.Hash) (*security.Signature, error) {
+	hash := h.Sum(nil)
+	sig, err := v23.GetPrincipal(ctx).Sign(hash[:])
+	if err != nil {
+		ctx.Errorf("Sign() of hash failed:%v", err)
+		return nil, err
+	}
+	return &sig, nil
+}
+
+func Upload(ctx *context.T, von string, data []byte, mediaInfo repository.MediaInfo) (*security.Signature, error) {
+	buffer := bytes.NewReader(data)
+	return upload(ctx, buffer, mediaInfo, von)
+}
+
+func Sign(ctx *context.T, in io.Reader) (*security.Signature, error) {
+	out := sha256.New()
+	if _, err := io.Copy(out, in); err != nil {
+		return nil, err
+	}
+	return signHash(ctx, out)
+}
+
+func UploadFromFile(ctx *context.T, von, path string) (*security.Signature, error) {
+	file, err := os.Open(path)
+	if err != nil {
+		ctx.Errorf("Open(%v) failed: %v", path, err)
+		return nil, verror.New(errOperationFailed, ctx)
+	}
+	defer file.Close()
+	mediaInfo, err := packages.LoadMediaInfo(path)
+	if err != nil {
+		mediaInfo = packages.MediaInfoForFileName(path)
+	}
+	return upload(ctx, file, mediaInfo, von)
+}
+
+func UploadFromDir(ctx *context.T, von, sourceDir string) (*security.Signature, error) {
+	dir, err := ioutil.TempDir("", "create-package-")
+	if err != nil {
+		return nil, err
+	}
+	defer os.RemoveAll(dir)
+	zipfile := filepath.Join(dir, "file.zip")
+	if err := packages.CreateZip(zipfile, sourceDir); err != nil {
+		return nil, err
+	}
+	return UploadFromFile(ctx, von, zipfile)
+}
diff --git a/services/internal/binarylib/client_test.go b/services/internal/binarylib/client_test.go
new file mode 100644
index 0000000..7c83ca2
--- /dev/null
+++ b/services/internal/binarylib/client_test.go
@@ -0,0 +1,196 @@
+// 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 binarylib
+
+import (
+	"bytes"
+	"crypto/sha256"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"reflect"
+	"testing"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/services/repository"
+
+	"v.io/x/ref/lib/xrpc"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/testutil"
+)
+
+//go:generate v23 test generate
+
+const (
+	v23Prefix = "vanadium_binary_repository"
+)
+
+func setupRepository(t *testing.T, ctx *context.T) (string, func()) {
+	// Setup the root of the binary repository.
+	rootDir, err := ioutil.TempDir("", v23Prefix)
+	if err != nil {
+		t.Fatalf("TempDir() failed: %v", err)
+	}
+	path, perm := filepath.Join(rootDir, VersionFile), os.FileMode(0600)
+	if err := ioutil.WriteFile(path, []byte(Version), perm); err != nil {
+		ctx.Fatalf("WriteFile(%v, %v, %v) failed: %v", path, Version, perm, err)
+	}
+	// Setup and start the binary repository server.
+	depth := 2
+	state, err := NewState(rootDir, "http://test-root-url", depth)
+	if err != nil {
+		t.Fatalf("NewState(%v, %v) failed: %v", rootDir, depth, err)
+	}
+	dispatcher, err := NewDispatcher(ctx, state)
+	if err != nil {
+		t.Fatalf("NewDispatcher() failed: %v\n", err)
+	}
+	server, err := xrpc.NewDispatchingServer(ctx, "", dispatcher)
+	if err != nil {
+		t.Fatalf("NewServer() failed: %v", err)
+	}
+	von := naming.JoinAddressName(server.Status().Endpoints[0].String(), "test")
+	return von, func() {
+		if err := os.Remove(path); err != nil {
+			t.Fatalf("Remove(%v) failed: %v", path, err)
+		}
+		// Check that any directories and files that were created to
+		// represent the binary objects have been garbage collected.
+		if err := os.RemoveAll(rootDir); err != nil {
+			t.Fatalf("Remove(%v) failed: %v", rootDir, err)
+		}
+		// Shutdown the binary repository server.
+		if err := server.Stop(); err != nil {
+			t.Fatalf("Stop() failed: %v", err)
+		}
+	}
+}
+
+// TestBufferAPI tests the binary repository client-side library
+// interface using buffers.
+func TestBufferAPI(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	rg := testutil.NewRandGenerator(t.Logf)
+
+	von, cleanup := setupRepository(t, ctx)
+	defer cleanup()
+	data := rg.RandomBytes(rg.RandomIntn(10 << 20))
+	mediaInfo := repository.MediaInfo{Type: "application/octet-stream"}
+	sig, err := Upload(ctx, von, data, mediaInfo)
+	if err != nil {
+		t.Fatalf("Upload(%v) failed: %v", von, err)
+	}
+	p := v23.GetPrincipal(ctx)
+	if sig != nil {
+		// verify the principal signature
+		h := sha256.Sum256(data)
+		if !sig.Verify(p.PublicKey(), h[:]) {
+			t.Fatalf("Failed to verify upload signature(%v)", sig)
+		}
+		// verify that Sign called directly also produces a working signature
+		reader := bytes.NewReader(data)
+		sig2, err := Sign(ctx, reader)
+		if err != nil {
+			t.Fatalf("Sign failed: %v", err)
+		}
+		if !sig2.Verify(p.PublicKey(), h[:]) {
+			t.Fatalf("Failed to verify signature from Sign(%v, %v)", sig, sig2)
+		}
+	} else {
+		t.Fatalf("Upload(%v) failed to generate principal(%v) signature", von, p)
+	}
+	output, outInfo, err := Download(ctx, von)
+	if err != nil {
+		t.Fatalf("Download(%v) failed: %v", von, err)
+	}
+	if bytes.Compare(data, output) != 0 {
+		t.Errorf("Data mismatch:\nexpected %v %v\ngot %v %v", len(data), data[:100], len(output), output[:100])
+	}
+	if err := Delete(ctx, von); err != nil {
+		t.Errorf("Delete(%v) failed: %v", von, err)
+	}
+	if _, _, err := Download(ctx, von); err == nil {
+		t.Errorf("Download(%v) did not fail", von)
+	}
+	if !reflect.DeepEqual(mediaInfo, outInfo) {
+		t.Errorf("unexpected media info: expected %v, got %v", mediaInfo, outInfo)
+	}
+}
+
+// TestFileAPI tests the binary repository client-side library
+// interface using files.
+func TestFileAPI(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	rg := testutil.NewRandGenerator(t.Logf)
+
+	von, cleanup := setupRepository(t, ctx)
+	defer cleanup()
+	// Create up to 10MB of random bytes.
+	data := rg.RandomBytes(rg.RandomIntn(10 << 20))
+	dir, prefix := "", ""
+	src, err := ioutil.TempFile(dir, prefix)
+	if err != nil {
+		t.Fatalf("TempFile(%v, %v) failed: %v", dir, prefix, err)
+	}
+	defer os.Remove(src.Name())
+	defer src.Close()
+	dstdir, err := ioutil.TempDir(dir, prefix)
+	if err != nil {
+		t.Fatalf("TempDir(%v, %v) failed: %v", dir, prefix, err)
+	}
+	defer os.RemoveAll(dstdir)
+	dst, err := ioutil.TempFile(dstdir, prefix)
+	if err != nil {
+		t.Fatalf("TempFile(%v, %v) failed: %v", dstdir, prefix, err)
+	}
+	defer dst.Close()
+	if _, err := src.Write(data); err != nil {
+		t.Fatalf("Write() failed: %v", err)
+	}
+	if _, err := UploadFromFile(ctx, von, src.Name()); err != nil {
+		t.Fatalf("UploadFromFile(%v, %v) failed: %v", von, src.Name(), err)
+	}
+	if err := DownloadToFile(ctx, von, dst.Name()); err != nil {
+		t.Fatalf("DownloadToFile(%v, %v) failed: %v", von, dst.Name(), err)
+	}
+	output, err := ioutil.ReadFile(dst.Name())
+	if err != nil {
+		t.Errorf("ReadFile(%v) failed: %v", dst.Name(), err)
+	}
+	if bytes.Compare(data, output) != 0 {
+		t.Errorf("Data mismatch:\nexpected %v %v\ngot %v %v", len(data), data[:100], len(output), output[:100])
+	}
+	jMediaInfo, err := ioutil.ReadFile(dst.Name() + ".__info")
+	if err != nil {
+		t.Errorf("ReadFile(%v) failed: %v", dst.Name()+".__info", err)
+	}
+	if expected := `{"Type":"application/octet-stream","Encoding":""}`; string(jMediaInfo) != expected {
+		t.Errorf("unexpected media info: expected %q, got %q", expected, string(jMediaInfo))
+	}
+	if err := Delete(ctx, von); err != nil {
+		t.Errorf("Delete(%v) failed: %v", von, err)
+	}
+}
+
+// TestDownloadUrl tests the binary repository client-side library
+// DownloadUrl method.
+func TestDownloadUrl(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	von, cleanup := setupRepository(t, ctx)
+	defer cleanup()
+	url, _, err := DownloadUrl(ctx, von)
+	if err != nil {
+		t.Fatalf("DownloadUrl(%v) failed: %v", von, err)
+	}
+	if got, want := url, "http://test-root-url/test"; got != want {
+		t.Fatalf("unexpect output: got %v, want %v", got, want)
+	}
+}
diff --git a/services/internal/binarylib/dispatcher.go b/services/internal/binarylib/dispatcher.go
new file mode 100644
index 0000000..fbe0439
--- /dev/null
+++ b/services/internal/binarylib/dispatcher.go
@@ -0,0 +1,64 @@
+// 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 binarylib
+
+import (
+	"path/filepath"
+
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/services/repository"
+	"v.io/x/ref/services/internal/pathperms"
+)
+
+const (
+	VersionFile = "VERSION"
+	Version     = "1.0"
+)
+
+// dispatcher holds the state of the binary repository dispatcher.
+type dispatcher struct {
+	state      *state
+	permsStore *pathperms.PathStore
+}
+
+// NewDispatcher is the dispatcher factory.
+func NewDispatcher(ctx *context.T, state *state) (rpc.Dispatcher, error) {
+	return &dispatcher{
+		state:      state,
+		permsStore: pathperms.NewPathStore(ctx),
+	}, nil
+}
+
+// DISPATCHER INTERFACE IMPLEMENTATION
+
+func permsPath(rootDir, suffix string) string {
+	var dir string
+	if suffix == "" {
+		// Directory is in namespace overlapped with Vanadium namespace
+		// so hide it.
+		dir = filepath.Join(rootDir, "__acls")
+	} else {
+		dir = filepath.Join(rootDir, suffix, "acls")
+	}
+	return dir
+}
+
+func newAuthorizer(rootDir, suffix string, permsStore *pathperms.PathStore) (security.Authorizer, error) {
+	return pathperms.NewHierarchicalAuthorizer(
+		permsPath(rootDir, ""),
+		permsPath(rootDir, suffix),
+		permsStore,
+		[]string{"Create", "__Glob"})
+}
+
+func (d *dispatcher) Lookup(_ *context.T, suffix string) (interface{}, security.Authorizer, error) {
+	auth, err := newAuthorizer(d.state.rootDir, suffix, d.permsStore)
+	if err != nil {
+		return nil, nil, err
+	}
+	return repository.BinaryServer(newBinaryService(d.state, suffix, d.permsStore)), auth, nil
+}
diff --git a/services/internal/binarylib/fs_utils.go b/services/internal/binarylib/fs_utils.go
new file mode 100644
index 0000000..6545a6b
--- /dev/null
+++ b/services/internal/binarylib/fs_utils.go
@@ -0,0 +1,150 @@
+// 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 binarylib
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strconv"
+	"strings"
+
+	"v.io/v23/context"
+	"v.io/v23/verror"
+)
+
+const (
+	checksumFileName  = "checksum"
+	dataFileName      = "data"
+	lockFileName      = "lock"
+	nameFileName      = "name"
+	mediaInfoFileName = "mediainfo"
+)
+
+// checksumExists checks whether the given part path is valid and
+// contains a checksum. The implementation uses the existence of
+// the path dir to determine whether the part is valid, and the
+// existence of checksum to determine whether the binary part
+// exists.
+func checksumExists(ctx *context.T, path string) error {
+	switch _, err := os.Stat(path); {
+	case os.IsNotExist(err):
+		return verror.New(ErrInvalidPart, nil, path)
+	case err != nil:
+		ctx.Errorf("Stat(%v) failed: %v", path, err)
+		return verror.New(ErrOperationFailed, nil, path)
+	}
+	checksumFile := filepath.Join(path, checksumFileName)
+	_, err := os.Stat(checksumFile)
+	switch {
+	case os.IsNotExist(err):
+		return verror.New(verror.ErrNoExist, nil, path)
+	case err != nil:
+		ctx.Errorf("Stat(%v) failed: %v", checksumFile, err)
+		return verror.New(ErrOperationFailed, nil, path)
+	default:
+		return nil
+	}
+}
+
+// generatePartPath generates a path for the given binary part.
+func (i *binaryService) generatePartPath(part int) string {
+	return generatePartPath(i.path, part)
+}
+
+func generatePartPath(dir string, part int) string {
+	return filepath.Join(dir, fmt.Sprintf("%d", part))
+}
+
+// getParts returns a collection of paths to the parts of the binary.
+func getParts(ctx *context.T, path string) ([]string, error) {
+	infos, err := ioutil.ReadDir(path)
+	if err != nil {
+		ctx.Errorf("ReadDir(%v) failed: %v", path, err)
+		return []string{}, verror.New(ErrOperationFailed, nil, path)
+	}
+	nDirs := 0
+	for _, info := range infos {
+		if info.IsDir() {
+			nDirs++
+		}
+	}
+	result := make([]string, nDirs)
+	for _, info := range infos {
+		if info.IsDir() {
+			partName := info.Name()
+			idx, err := strconv.Atoi(partName)
+			if err != nil {
+				ctx.Errorf("Atoi(%v) failed: %v", partName, err)
+				return []string{}, verror.New(ErrOperationFailed, nil, path)
+			}
+			if idx < 0 || idx >= len(infos) || result[idx] != "" {
+				return []string{}, verror.New(ErrOperationFailed, nil, path)
+			}
+			result[idx] = filepath.Join(path, partName)
+		} else {
+			if info.Name() == nameFileName || info.Name() == mediaInfoFileName {
+				continue
+			}
+			// The only entries should correspond to the part dirs.
+			return []string{}, verror.New(ErrOperationFailed, nil, path)
+		}
+	}
+	return result, nil
+}
+
+// createObjectNameTree returns a tree of all the valid object names in the
+// repository.
+func (i *binaryService) createObjectNameTree() *treeNode {
+	pattern := i.state.rootDir
+	for d := 0; d < i.state.depth; d++ {
+		pattern = filepath.Join(pattern, "*")
+	}
+	pattern = filepath.Join(pattern, "*", nameFileName)
+	matches, err := filepath.Glob(pattern)
+	if err != nil {
+		return nil
+	}
+	tree := newTreeNode()
+	for _, m := range matches {
+		name, err := ioutil.ReadFile(m)
+		if err != nil {
+			continue
+		}
+		elems := strings.Split(string(name), string(filepath.Separator))
+		tree.find(elems, true)
+	}
+	return tree
+}
+
+type treeNode struct {
+	children map[string]*treeNode
+}
+
+func newTreeNode() *treeNode {
+	return &treeNode{children: make(map[string]*treeNode)}
+}
+
+func (n *treeNode) find(names []string, create bool) *treeNode {
+	for {
+		if len(names) == 0 {
+			return n
+		}
+		if next, ok := n.children[names[0]]; ok {
+			n = next
+			names = names[1:]
+			continue
+		}
+		if create {
+			nn := newTreeNode()
+			n.children[names[0]] = nn
+			n = nn
+			names = names[1:]
+			continue
+		}
+		return nil
+	}
+}
diff --git a/services/internal/binarylib/http.go b/services/internal/binarylib/http.go
new file mode 100644
index 0000000..a7e10d8
--- /dev/null
+++ b/services/internal/binarylib/http.go
@@ -0,0 +1,54 @@
+// 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 binarylib
+
+import (
+	"net/http"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"v.io/v23/context"
+	"v.io/v23/verror"
+	"v.io/x/ref/services/internal/multipart"
+)
+
+// NewHTTPRoot returns an implementation of http.FileSystem that can be used
+// to serve the content in the binary service.
+func NewHTTPRoot(ctx *context.T, state *state) http.FileSystem {
+	return &httpRoot{ctx: ctx, state: state}
+}
+
+type httpRoot struct {
+	ctx   *context.T
+	state *state
+}
+
+// TODO(caprita): Tie this in with DownloadUrl, to control which binaries
+// are downloadable via url.
+
+// Open implements http.FileSystem.  It uses the multipart file implementation
+// to wrap the content parts into one logical file.
+func (r httpRoot) Open(name string) (http.File, error) {
+	name = strings.TrimPrefix(name, "/")
+	r.ctx.Infof("HTTP handler opening %s", name)
+	parts, err := getParts(r.ctx, r.state.dir(name))
+	if err != nil {
+		return nil, err
+	}
+	partFiles := make([]*os.File, len(parts))
+	for i, part := range parts {
+		if err := checksumExists(r.ctx, part); err != nil {
+			return nil, err
+		}
+		dataPath := filepath.Join(part, dataFileName)
+		var err error
+		if partFiles[i], err = os.Open(dataPath); err != nil {
+			r.ctx.Errorf("Open(%v) failed: %v", dataPath, err)
+			return nil, verror.New(ErrOperationFailed, nil, dataPath)
+		}
+	}
+	return multipart.NewFile(name, partFiles)
+}
diff --git a/services/internal/binarylib/http_test.go b/services/internal/binarylib/http_test.go
new file mode 100644
index 0000000..198cb75
--- /dev/null
+++ b/services/internal/binarylib/http_test.go
@@ -0,0 +1,86 @@
+// 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 binarylib_test
+
+import (
+	"bytes"
+	"crypto/md5"
+	"encoding/hex"
+	"io/ioutil"
+	"net/http"
+	"testing"
+
+	"v.io/v23/services/repository"
+
+	"v.io/x/ref/services/internal/binarylib"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/testutil"
+)
+
+// TestHTTP checks that HTTP download works.
+func TestHTTP(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	rg := testutil.NewRandGenerator(t.Logf)
+
+	// TODO(caprita): This is based on TestMultiPart (impl_test.go).  Share
+	// the code where possible.
+	for length := 2; length < 5; length++ {
+		binary, _, url, cleanup := startServer(t, ctx, 2)
+		defer cleanup()
+		// Create <length> chunks of up to 4MB of random bytes.
+		data := make([][]byte, length)
+		for i := 0; i < length; i++ {
+			// Random size, but at least 1 (avoid empty parts).
+			size := rg.RandomIntn(1000*binarylib.BufferLength) + 1
+			data[i] = rg.RandomBytes(size)
+		}
+		mediaInfo := repository.MediaInfo{Type: "application/octet-stream"}
+		if err := binary.Create(ctx, int32(length), mediaInfo); err != nil {
+			t.Fatalf("Create() failed: %v", err)
+		}
+		for i := 0; i < length; i++ {
+			if streamErr, err := invokeUpload(t, ctx, binary, data[i], int32(i)); streamErr != nil || err != nil {
+				t.FailNow()
+			}
+		}
+		parts, _, err := binary.Stat(ctx)
+		if err != nil {
+			t.Fatalf("Stat() failed: %v", err)
+		}
+		response, err := http.Get(url)
+		if err != nil {
+			t.Fatal(err)
+		}
+		downloaded, err := ioutil.ReadAll(response.Body)
+		if err != nil {
+			t.Fatal(err)
+		}
+		from, to := 0, 0
+		for i := 0; i < length; i++ {
+			hpart := md5.New()
+			to += len(data[i])
+			if ld := len(downloaded); to > ld {
+				t.Fatalf("Download falls short: len(downloaded):%d, need:%d (i:%d, length:%d)", ld, to, i, length)
+			}
+			output := downloaded[from:to]
+			from = to
+			if bytes.Compare(output, data[i]) != 0 {
+				t.Fatalf("Unexpected output: expected %v, got %v", data[i], output)
+			}
+			hpart.Write(data[i])
+			checksum := hex.EncodeToString(hpart.Sum(nil))
+			if expected, got := checksum, parts[i].Checksum; expected != got {
+				t.Fatalf("Unexpected checksum: expected %v, got %v", expected, got)
+			}
+			if expected, got := len(data[i]), int(parts[i].Size); expected != got {
+				t.Fatalf("Unexpected size: expected %v, got %v", expected, got)
+			}
+		}
+		if err := binary.Delete(ctx); err != nil {
+			t.Fatalf("Delete() failed: %v", err)
+		}
+	}
+}
diff --git a/services/internal/binarylib/impl_test.go b/services/internal/binarylib/impl_test.go
new file mode 100644
index 0000000..1ffd9a6
--- /dev/null
+++ b/services/internal/binarylib/impl_test.go
@@ -0,0 +1,326 @@
+// 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 binarylib_test
+
+import (
+	"bytes"
+	"crypto/md5"
+	"encoding/hex"
+	"fmt"
+	"net"
+	"net/http"
+	"reflect"
+	"testing"
+
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/services/repository"
+	"v.io/v23/verror"
+
+	"v.io/x/ref/lib/xrpc"
+	_ "v.io/x/ref/runtime/factories/static"
+	"v.io/x/ref/services/internal/binarylib"
+	"v.io/x/ref/services/internal/servicetest"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/testutil"
+)
+
+const (
+	v23Prefix = "vanadium_binary_repository"
+)
+
+// startServer starts the binary repository server.
+func startServer(t *testing.T, ctx *context.T, depth int) (repository.BinaryClientMethods, string, string, func()) {
+	// Setup the root of the binary repository.
+	rootDir, cleanup := servicetest.SetupRootDir(t, "bindir")
+	prepDirectory(t, rootDir)
+
+	listener, err := net.Listen("tcp", "127.0.0.1:0")
+	if err != nil {
+		t.Fatal(err)
+	}
+	state, err := binarylib.NewState(rootDir, listener.Addr().String(), depth)
+	if err != nil {
+		t.Fatalf("NewState(%v, %v, %v) failed: %v", rootDir, listener.Addr().String(), depth, err)
+	}
+	go func() {
+		if err := http.Serve(listener, http.FileServer(binarylib.NewHTTPRoot(ctx, state))); err != nil {
+			ctx.Fatalf("Serve() failed: %v", err)
+		}
+	}()
+
+	// Setup and start the binary repository server.
+	dispatcher, err := binarylib.NewDispatcher(ctx, state)
+	if err != nil {
+		t.Fatalf("NewDispatcher failed: %v", err)
+	}
+	dontPublishName := ""
+	server, err := xrpc.NewDispatchingServer(ctx, dontPublishName, dispatcher)
+	if err != nil {
+		t.Fatalf("NewServer(%q) failed: %v", dontPublishName, err)
+	}
+	endpoint := server.Status().Endpoints[0].String()
+	name := naming.JoinAddressName(endpoint, "test")
+	binary := repository.BinaryClient(name)
+	return binary, endpoint, fmt.Sprintf("http://%s/test", listener.Addr()), func() {
+		// Shutdown the binary repository server.
+		if err := server.Stop(); err != nil {
+			t.Fatalf("Stop() failed: %v", err)
+		}
+		cleanup()
+	}
+}
+
+// TestHierarchy checks that the binary repository works correctly for
+// all possible valid values of the depth used for the directory
+// hierarchy that stores binary objects in the local file system.
+func TestHierarchy(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	rg := testutil.NewRandGenerator(t.Logf)
+
+	for i := 0; i < md5.Size; i++ {
+		binary, ep, _, cleanup := startServer(t, ctx, i)
+		defer cleanup()
+		data := testData(rg)
+
+		// Test the binary repository interface.
+		if err := binary.Create(ctx, 1, repository.MediaInfo{Type: "application/octet-stream"}); err != nil {
+			t.Fatalf("Create() failed: %v", err)
+		}
+		if streamErr, err := invokeUpload(t, ctx, binary, data, 0); streamErr != nil || err != nil {
+			t.FailNow()
+		}
+		parts, _, err := binary.Stat(ctx)
+		if err != nil {
+			t.Fatalf("Stat() failed: %v", err)
+		}
+		h := md5.New()
+		h.Write(data)
+		checksum := hex.EncodeToString(h.Sum(nil))
+		if expected, got := checksum, parts[0].Checksum; expected != got {
+			t.Fatalf("Unexpected checksum: expected %v, got %v", expected, got)
+		}
+		if expected, got := len(data), int(parts[0].Size); expected != got {
+			t.Fatalf("Unexpected size: expected %v, got %v", expected, got)
+		}
+		output, streamErr, err := invokeDownload(t, ctx, binary, 0)
+		if streamErr != nil || err != nil {
+			t.FailNow()
+		}
+		if bytes.Compare(output, data) != 0 {
+			t.Fatalf("Unexpected output: expected %v, got %v", data, output)
+		}
+		results, _, err := testutil.GlobName(ctx, naming.JoinAddressName(ep, ""), "...")
+		if err != nil {
+			t.Fatalf("GlobName failed: %v", err)
+		}
+		if expected := []string{"", "test"}; !reflect.DeepEqual(results, expected) {
+			t.Errorf("Unexpected results: expected %q, got %q", expected, results)
+		}
+		if err := binary.Delete(ctx); err != nil {
+			t.Fatalf("Delete() failed: %v", err)
+		}
+	}
+}
+
+// TestMultiPart checks that the binary repository supports multi-part
+// uploads and downloads ranging the number of parts the test binary
+// consists of.
+func TestMultiPart(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	rg := testutil.NewRandGenerator(t.Logf)
+
+	for length := 2; length < 5; length++ {
+		binary, _, _, cleanup := startServer(t, ctx, 2)
+		defer cleanup()
+		// Create <length> chunks of up to 4MB of random bytes.
+		data := make([][]byte, length)
+		for i := 0; i < length; i++ {
+			data[i] = testData(rg)
+		}
+		// Test the binary repository interface.
+		if err := binary.Create(ctx, int32(length), repository.MediaInfo{Type: "application/octet-stream"}); err != nil {
+			t.Fatalf("Create() failed: %v", err)
+		}
+		for i := 0; i < length; i++ {
+			if streamErr, err := invokeUpload(t, ctx, binary, data[i], int32(i)); streamErr != nil || err != nil {
+				t.FailNow()
+			}
+		}
+		parts, _, err := binary.Stat(ctx)
+		if err != nil {
+			t.Fatalf("Stat() failed: %v", err)
+		}
+		for i := 0; i < length; i++ {
+			hpart := md5.New()
+			output, streamErr, err := invokeDownload(t, ctx, binary, int32(i))
+			if streamErr != nil || err != nil {
+				t.FailNow()
+			}
+			if bytes.Compare(output, data[i]) != 0 {
+				t.Fatalf("Unexpected output: expected %v, got %v", data[i], output)
+			}
+			hpart.Write(data[i])
+			checksum := hex.EncodeToString(hpart.Sum(nil))
+			if expected, got := checksum, parts[i].Checksum; expected != got {
+				t.Fatalf("Unexpected checksum: expected %v, got %v", expected, got)
+			}
+			if expected, got := len(data[i]), int(parts[i].Size); expected != got {
+				t.Fatalf("Unexpected size: expected %v, got %v", expected, got)
+			}
+		}
+		if err := binary.Delete(ctx); err != nil {
+			t.Fatalf("Delete() failed: %v", err)
+		}
+	}
+}
+
+// TestResumption checks that the binary interface supports upload
+// resumption ranging the number of parts the uploaded binary consists
+// of.
+func TestResumption(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	rg := testutil.NewRandGenerator(t.Logf)
+
+	for length := 2; length < 5; length++ {
+		binary, _, _, cleanup := startServer(t, ctx, 2)
+		defer cleanup()
+		// Create <length> chunks of up to 4MB of random bytes.
+		data := make([][]byte, length)
+		for i := 0; i < length; i++ {
+			data[i] = testData(rg)
+		}
+		if err := binary.Create(ctx, int32(length), repository.MediaInfo{Type: "application/octet-stream"}); err != nil {
+			t.Fatalf("Create() failed: %v", err)
+		}
+		// Simulate a flaky upload client that keeps uploading parts until
+		// finished.
+		for {
+			parts, _, err := binary.Stat(ctx)
+			if err != nil {
+				t.Fatalf("Stat() failed: %v", err)
+			}
+			finished := true
+			for _, part := range parts {
+				finished = finished && (part != binarylib.MissingPart)
+			}
+			if finished {
+				break
+			}
+			for i := 0; i < length; i++ {
+				fail := rg.RandomIntn(2)
+				if parts[i] == binarylib.MissingPart && fail != 0 {
+					if streamErr, err := invokeUpload(t, ctx, binary, data[i], int32(i)); streamErr != nil || err != nil {
+						t.FailNow()
+					}
+				}
+			}
+		}
+		if err := binary.Delete(ctx); err != nil {
+			t.Fatalf("Delete() failed: %v", err)
+		}
+	}
+}
+
+// TestErrors checks that the binary interface correctly reports errors.
+func TestErrors(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	rg := testutil.NewRandGenerator(t.Logf)
+
+	binary, _, _, cleanup := startServer(t, ctx, 2)
+	defer cleanup()
+	const length = 2
+	data := make([][]byte, length)
+	for i := 0; i < length; i++ {
+		data[i] = testData(rg)
+		for j := 0; j < len(data[i]); j++ {
+			data[i][j] = byte(rg.RandomInt())
+		}
+	}
+	if err := binary.Create(ctx, int32(length), repository.MediaInfo{Type: "application/octet-stream"}); err != nil {
+		t.Fatalf("Create() failed: %v", err)
+	}
+	if err := binary.Create(ctx, int32(length), repository.MediaInfo{Type: "application/octet-stream"}); err == nil {
+		t.Fatalf("Create() did not fail when it should have")
+	} else if want := verror.ErrExist.ID; verror.ErrorID(err) != want {
+		t.Fatalf("Unexpected error: %v, expected error id %v", err, want)
+	}
+	if streamErr, err := invokeUpload(t, ctx, binary, data[0], 0); streamErr != nil || err != nil {
+		t.Fatalf("Upload() failed: %v", err)
+	}
+	if _, err := invokeUpload(t, ctx, binary, data[0], 0); err == nil {
+		t.Fatalf("Upload() did not fail when it should have")
+	} else if want := verror.ErrExist.ID; verror.ErrorID(err) != want {
+		t.Fatalf("Unexpected error: %v, expected error id %v", err, want)
+	}
+	if _, _, err := invokeDownload(t, ctx, binary, 1); err == nil {
+		t.Fatalf("Download() did not fail when it should have")
+	} else if want := verror.ErrNoExist.ID; verror.ErrorID(err) != want {
+		t.Fatalf("Unexpected error: %v, expected error id %v", err, want)
+	}
+	if streamErr, err := invokeUpload(t, ctx, binary, data[1], 1); streamErr != nil || err != nil {
+		t.Fatalf("Upload() failed: %v", err)
+	}
+	if _, streamErr, err := invokeDownload(t, ctx, binary, 0); streamErr != nil || err != nil {
+		t.Fatalf("Download() failed: %v", err)
+	}
+	// Upload/Download on a part number that's outside the range set forth in
+	// Create should fail.
+	for _, part := range []int32{-1, length} {
+		if _, err := invokeUpload(t, ctx, binary, []byte("dummy"), part); err == nil {
+			t.Fatalf("Upload() did not fail when it should have")
+		} else if want := binarylib.ErrInvalidPart.ID; verror.ErrorID(err) != want {
+			t.Fatalf("Unexpected error: %v, expected error id %v", err, want)
+		}
+		if _, _, err := invokeDownload(t, ctx, binary, part); err == nil {
+			t.Fatalf("Download() did not fail when it should have")
+		} else if want := binarylib.ErrInvalidPart.ID; verror.ErrorID(err) != want {
+			t.Fatalf("Unexpected error: %v, expected error id %v", err, want)
+		}
+	}
+	if err := binary.Delete(ctx); err != nil {
+		t.Fatalf("Delete() failed: %v", err)
+	}
+	if err := binary.Delete(ctx); err == nil {
+		t.Fatalf("Delete() did not fail when it should have")
+	} else if want := verror.ErrNoExist.ID; verror.ErrorID(err) != want {
+		t.Fatalf("Unexpected error: %v, expected error id %v", err, want)
+	}
+}
+
+func TestGlob(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	rg := testutil.NewRandGenerator(t.Logf)
+
+	_, ep, _, cleanup := startServer(t, ctx, 2)
+	defer cleanup()
+	data := testData(rg)
+
+	objects := []string{"foo", "bar", "hello world", "a/b/c"}
+	for _, obj := range objects {
+		name := naming.JoinAddressName(ep, obj)
+		binary := repository.BinaryClient(name)
+
+		if err := binary.Create(ctx, 1, repository.MediaInfo{Type: "application/octet-stream"}); err != nil {
+			t.Fatalf("Create() failed: %v", err)
+		}
+		if streamErr, err := invokeUpload(t, ctx, binary, data, 0); streamErr != nil || err != nil {
+			t.FailNow()
+		}
+	}
+	results, _, err := testutil.GlobName(ctx, naming.JoinAddressName(ep, ""), "...")
+	if err != nil {
+		t.Fatalf("GlobName failed: %v", err)
+	}
+	expected := []string{"", "a", "a/b", "a/b/c", "bar", "foo", "hello world"}
+	if !reflect.DeepEqual(results, expected) {
+		t.Errorf("Unexpected results: expected %q, got %q", expected, results)
+	}
+}
diff --git a/services/internal/binarylib/perms_test.go b/services/internal/binarylib/perms_test.go
new file mode 100644
index 0000000..0729b69
--- /dev/null
+++ b/services/internal/binarylib/perms_test.go
@@ -0,0 +1,477 @@
+// 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 binarylib_test
+
+import (
+	"fmt"
+	"os"
+	"reflect"
+	"syscall"
+	"testing"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/security"
+	"v.io/v23/security/access"
+	"v.io/v23/services/repository"
+	"v.io/v23/verror"
+	"v.io/x/ref/lib/signals"
+	"v.io/x/ref/lib/xrpc"
+	"v.io/x/ref/services/internal/binarylib"
+	"v.io/x/ref/services/internal/servicetest"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
+	"v.io/x/ref/test/testutil"
+)
+
+//go:generate v23 test generate
+
+var binaryd = modules.Register(func(env *modules.Env, args ...string) error {
+	ctx, shutdown := test.V23Init()
+	if len(args) < 2 {
+		ctx.Fatalf("binaryd expected at least name and store arguments and optionally AccessList flags per PermissionsFromFlag")
+	}
+	publishName := args[0]
+	storedir := args[1]
+
+	defer fmt.Fprintf(env.Stdout, "%v terminating\n", publishName)
+	defer ctx.VI(1).Infof("%v terminating", publishName)
+	defer shutdown()
+
+	depth := 2
+	state, err := binarylib.NewState(storedir, "", depth)
+	if err != nil {
+		ctx.Fatalf("NewState(%v, %v, %v) failed: %v", storedir, "", depth, err)
+	}
+	dispatcher, err := binarylib.NewDispatcher(ctx, state)
+	if err != nil {
+		ctx.Fatalf("Failed to create binaryd dispatcher: %v", err)
+	}
+	server, err := xrpc.NewDispatchingServer(ctx, publishName, dispatcher)
+	if err != nil {
+		ctx.Fatalf("NewDispatchingServer(%v) failed: %v", publishName, err)
+	}
+	ctx.VI(1).Infof("binaryd name: %v", server.Status().Endpoints[0].Name())
+
+	fmt.Fprintf(env.Stdout, "ready:%d\n", os.Getpid())
+	<-signals.ShutdownOnSignals(ctx)
+
+	return nil
+}, "binaryd")
+
+func b(name string) repository.BinaryClientStub {
+	return repository.BinaryClient(name)
+}
+
+func ctxWithBlessedPrincipal(ctx *context.T, childExtension string) (*context.T, error) {
+	child := testutil.NewPrincipal()
+	if err := testutil.IDProviderFromPrincipal(v23.GetPrincipal(ctx)).Bless(child, childExtension); err != nil {
+		return nil, err
+	}
+	return v23.WithPrincipal(ctx, child)
+}
+
+func TestBinaryCreateAccessList(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	rg := testutil.NewRandGenerator(t.Logf)
+
+	selfCtx, err := v23.WithPrincipal(ctx, testutil.NewPrincipal("self"))
+	if err != nil {
+		t.Fatalf("WithPrincipal failed: %v", err)
+	}
+	childCtx, err := ctxWithBlessedPrincipal(selfCtx, "child")
+	if err != nil {
+		t.Fatalf("WithPrincipal failed: %v", err)
+	}
+
+	sh, deferFn := servicetest.CreateShellAndMountTable(t, childCtx, v23.GetPrincipal(childCtx))
+	defer deferFn()
+	// make selfCtx and childCtx have the same Namespace Roots as set by
+	// CreateShellAndMountTable
+	v23.GetNamespace(selfCtx).SetRoots(v23.GetNamespace(childCtx).Roots()...)
+
+	// setup mock up directory to put state in
+	storedir, cleanup := servicetest.SetupRootDir(t, "bindir")
+	defer cleanup()
+	prepDirectory(t, storedir)
+
+	nmh := servicetest.RunCommand(t, sh, nil, binaryd, "bini", storedir)
+	pid := servicetest.ReadPID(t, nmh)
+	defer syscall.Kill(pid, syscall.SIGINT)
+
+	ctx.VI(2).Infof("Self uploads a shared and private binary.")
+	if err := b("bini/private").Create(childCtx, 1, repository.MediaInfo{Type: "application/octet-stream"}); err != nil {
+		t.Fatalf("Create() failed %v", err)
+	}
+	fakeDataPrivate := testData(rg)
+	if streamErr, err := invokeUpload(t, childCtx, b("bini/private"), fakeDataPrivate, 0); streamErr != nil || err != nil {
+		t.Fatalf("invokeUpload() failed %v, %v", err, streamErr)
+	}
+
+	ctx.VI(2).Infof("Validate that the AccessList also allows Self")
+	perms, _, err := b("bini/private").GetPermissions(selfCtx)
+	if err != nil {
+		t.Fatalf("GetPermissions failed: %v", err)
+	}
+	expected := access.Permissions{
+		"Admin":   access.AccessList{In: []security.BlessingPattern{"self/$", "self/child"}},
+		"Read":    access.AccessList{In: []security.BlessingPattern{"self/$", "self/child"}},
+		"Write":   access.AccessList{In: []security.BlessingPattern{"self/$", "self/child"}},
+		"Debug":   access.AccessList{In: []security.BlessingPattern{"self/$", "self/child"}},
+		"Resolve": access.AccessList{In: []security.BlessingPattern{"self/$", "self/child"}},
+	}
+	if got, want := perms.Normalize(), expected.Normalize(); !reflect.DeepEqual(got, want) {
+		t.Errorf("got %#v, expected %#v ", got, want)
+	}
+}
+
+func TestBinaryRootAccessList(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	rg := testutil.NewRandGenerator(t.Logf)
+
+	selfPrincipal := testutil.NewPrincipal("self")
+	selfCtx, err := v23.WithPrincipal(ctx, selfPrincipal)
+	if err != nil {
+		t.Fatalf("WithPrincipal failed: %v", err)
+	}
+	sh, deferFn := servicetest.CreateShellAndMountTable(t, selfCtx, v23.GetPrincipal(selfCtx))
+	defer deferFn()
+
+	// setup mock up directory to put state in
+	storedir, cleanup := servicetest.SetupRootDir(t, "bindir")
+	defer cleanup()
+	prepDirectory(t, storedir)
+
+	otherPrincipal := testutil.NewPrincipal("other")
+	if err := otherPrincipal.AddToRoots(selfPrincipal.BlessingStore().Default()); err != nil {
+		t.Fatalf("otherPrincipal.AddToRoots() failed: %v", err)
+	}
+	otherCtx, err := v23.WithPrincipal(selfCtx, otherPrincipal)
+	if err != nil {
+		t.Fatalf("WithPrincipal() failed: %v", err)
+	}
+
+	nmh := servicetest.RunCommand(t, sh, nil, binaryd, "bini", storedir)
+	pid := servicetest.ReadPID(t, nmh)
+	defer syscall.Kill(pid, syscall.SIGINT)
+
+	ctx.VI(2).Infof("Self uploads a shared and private binary.")
+	if err := b("bini/private").Create(selfCtx, 1, repository.MediaInfo{Type: "application/octet-stream"}); err != nil {
+		t.Fatalf("Create() failed %v", err)
+	}
+	fakeDataPrivate := testData(rg)
+	if streamErr, err := invokeUpload(t, selfCtx, b("bini/private"), fakeDataPrivate, 0); streamErr != nil || err != nil {
+		t.Fatalf("invokeUpload() failed %v, %v", err, streamErr)
+	}
+
+	if err := b("bini/shared").Create(selfCtx, 1, repository.MediaInfo{Type: "application/octet-stream"}); err != nil {
+		t.Fatalf("Create() failed %v", err)
+	}
+	fakeDataShared := testData(rg)
+	if streamErr, err := invokeUpload(t, selfCtx, b("bini/shared"), fakeDataShared, 0); streamErr != nil || err != nil {
+		t.Fatalf("invokeUpload() failed %v, %v", err, streamErr)
+	}
+
+	ctx.VI(2).Infof("Verify that in the beginning other can't access bini/private or bini/shared")
+	if _, _, err := b("bini/private").Stat(otherCtx); verror.ErrorID(err) != verror.ErrNoAccess.ID {
+		t.Fatalf("Stat() should have failed but didn't: %v", err)
+	}
+	if _, _, err := b("bini/shared").Stat(otherCtx); verror.ErrorID(err) != verror.ErrNoAccess.ID {
+		t.Fatalf("Stat() should have failed but didn't: %v", err)
+	}
+
+	ctx.VI(2).Infof("Validate the AccessList file on bini/private.")
+	perms, _, err := b("bini/private").GetPermissions(selfCtx)
+	if err != nil {
+		t.Fatalf("GetPermissions failed: %v", err)
+	}
+	expected := access.Permissions{
+		"Admin":   access.AccessList{In: []security.BlessingPattern{"self"}},
+		"Read":    access.AccessList{In: []security.BlessingPattern{"self"}},
+		"Write":   access.AccessList{In: []security.BlessingPattern{"self"}},
+		"Debug":   access.AccessList{In: []security.BlessingPattern{"self"}},
+		"Resolve": access.AccessList{In: []security.BlessingPattern{"self"}},
+	}
+	if got, want := perms.Normalize(), expected.Normalize(); !reflect.DeepEqual(got, want) {
+		t.Errorf("got %#v, expected %#v ", got, want)
+	}
+
+	ctx.VI(2).Infof("Validate the AccessList file on bini/private.")
+	perms, version, err := b("bini/private").GetPermissions(selfCtx)
+	if err != nil {
+		t.Fatalf("GetPermissions failed: %v", err)
+	}
+	if got, want := perms.Normalize(), expected.Normalize(); !reflect.DeepEqual(got, want) {
+		t.Errorf("got %#v, expected %#v ", got, want)
+	}
+
+	ctx.VI(2).Infof("self blesses other as self/other and locks the bini/private binary to itself.")
+	selfBlessing := selfPrincipal.BlessingStore().Default()
+	otherBlessing, err := selfPrincipal.Bless(otherPrincipal.PublicKey(), selfBlessing, "other", security.UnconstrainedUse())
+	if err != nil {
+		t.Fatalf("selfPrincipal.Bless() failed: %v", err)
+	}
+	if _, err := otherPrincipal.BlessingStore().Set(otherBlessing, security.AllPrincipals); err != nil {
+		t.Fatalf("otherPrincipal.BlessingStore() failed: %v", err)
+	}
+
+	ctx.VI(2).Infof("Self modifies the AccessList file on bini/private.")
+	for _, tag := range access.AllTypicalTags() {
+		perms.Clear("self", string(tag))
+		perms.Add("self/$", string(tag))
+	}
+	if err := b("bini/private").SetPermissions(selfCtx, perms, version); err != nil {
+		t.Fatalf("SetPermissions failed: %v", err)
+	}
+
+	ctx.VI(2).Infof(" Verify that bini/private's perms are updated.")
+	updated := access.Permissions{
+		"Admin":   access.AccessList{In: []security.BlessingPattern{"self/$"}},
+		"Read":    access.AccessList{In: []security.BlessingPattern{"self/$"}},
+		"Write":   access.AccessList{In: []security.BlessingPattern{"self/$"}},
+		"Debug":   access.AccessList{In: []security.BlessingPattern{"self/$"}},
+		"Resolve": access.AccessList{In: []security.BlessingPattern{"self/$"}},
+	}
+	perms, _, err = b("bini/private").GetPermissions(selfCtx)
+	if err != nil {
+		t.Fatalf("GetPermissions failed: %v", err)
+	}
+	if got, want := perms.Normalize(), updated.Normalize(); !reflect.DeepEqual(got, want) {
+		t.Errorf("got %#v, expected %#v ", got, want)
+	}
+
+	// Other still can't access bini/shared because there's no AccessList file at the
+	// root level. Self has to set one explicitly to enable sharing. This way, self
+	// can't accidentally expose the server without setting a root AccessList.
+	ctx.VI(2).Infof(" Verify that other still can't access bini/shared.")
+	if _, _, err := b("bini/shared").Stat(otherCtx); verror.ErrorID(err) != verror.ErrNoAccess.ID {
+		t.Fatalf("Stat() should have failed but didn't: %v", err)
+	}
+
+	ctx.VI(2).Infof("Self sets a root AccessList.")
+	newRootAccessList := make(access.Permissions)
+	for _, tag := range access.AllTypicalTags() {
+		newRootAccessList.Add("self/$", string(tag))
+	}
+	if err := b("bini").SetPermissions(selfCtx, newRootAccessList, ""); err != nil {
+		t.Fatalf("SetPermissions failed: %v", err)
+	}
+
+	ctx.VI(2).Infof("Verify that other can access bini/shared now but not access bini/private.")
+	if _, _, err := b("bini/shared").Stat(otherCtx); err != nil {
+		t.Fatalf("Stat() shouldn't have failed: %v", err)
+	}
+	if _, _, err := b("bini/private").Stat(otherCtx); verror.ErrorID(err) != verror.ErrNoAccess.ID {
+		t.Fatalf("Stat() should have failed but didn't: %v", err)
+	}
+
+	ctx.VI(2).Infof("Other still can't create so Self gives Other right to Create.")
+	perms, tag, err := b("bini").GetPermissions(selfCtx)
+	if err != nil {
+		t.Fatalf("GetPermissions() failed: %v", err)
+	}
+
+	// More than one AccessList change will result in the same functional result in
+	// this test: that self/other acquires the right to invoke Create at the
+	// root. In particular:
+	//
+	// a. perms.Add("self", "Write ")
+	// b. perms.Add("self/other", "Write")
+	// c. perms.Add("self/other/$", "Write")
+	//
+	// will all give self/other the right to invoke Create but in the case of
+	// (a) it will also extend this right to self's delegates (because of the
+	// absence of the $) including other and in (b) will also extend the
+	// Create right to all of other's delegates. Since (c) is the minimum
+	// case, use that.
+	perms.Add("self/other/$", string("Write"))
+	err = b("bini").SetPermissions(selfCtx, perms, tag)
+	if err != nil {
+		t.Fatalf("SetPermissions() failed: %v", err)
+	}
+
+	ctx.VI(2).Infof("Other creates bini/otherbinary")
+	if err := b("bini/otherbinary").Create(otherCtx, 1, repository.MediaInfo{Type: "application/octet-stream"}); err != nil {
+		t.Fatalf("Create() failed %v", err)
+	}
+	fakeDataOther := testData(rg)
+	if streamErr, err := invokeUpload(t, otherCtx, b("bini/otherbinary"), fakeDataOther, 0); streamErr != nil || err != nil {
+		t.FailNow()
+	}
+
+	ctx.VI(2).Infof("Other can read perms for bini/otherbinary.")
+	updated = access.Permissions{
+		"Admin":   access.AccessList{In: []security.BlessingPattern{"self/$", "self/other"}},
+		"Read":    access.AccessList{In: []security.BlessingPattern{"self/$", "self/other"}},
+		"Write":   access.AccessList{In: []security.BlessingPattern{"self/$", "self/other"}},
+		"Debug":   access.AccessList{In: []security.BlessingPattern{"self/$", "self/other"}},
+		"Resolve": access.AccessList{In: []security.BlessingPattern{"self/$", "self/other"}},
+	}
+	perms, _, err = b("bini/otherbinary").GetPermissions(otherCtx)
+	if err != nil {
+		t.Fatalf("GetPermissions failed: %v", err)
+	}
+	if got, want := perms.Normalize(), updated.Normalize(); !reflect.DeepEqual(want, got) {
+		t.Errorf("got %#v, expected %#v ", got, want)
+	}
+
+	ctx.VI(2).Infof("Other tries to exclude self by removing self from the AccessList set")
+	perms, tag, err = b("bini/otherbinary").GetPermissions(otherCtx)
+	if err != nil {
+		t.Fatalf("GetPermissions() failed: %v", err)
+	}
+	perms.Clear("self/$")
+	err = b("bini/otherbinary").SetPermissions(otherCtx, perms, tag)
+	if err != nil {
+		t.Fatalf("SetPermissions() failed: %v", err)
+	}
+
+	ctx.VI(2).Infof("Verify that other can make this change.")
+	updated = access.Permissions{
+		"Admin":   access.AccessList{In: []security.BlessingPattern{"self/other"}},
+		"Read":    access.AccessList{In: []security.BlessingPattern{"self/other"}},
+		"Write":   access.AccessList{In: []security.BlessingPattern{"self/other"}},
+		"Debug":   access.AccessList{In: []security.BlessingPattern{"self/other"}},
+		"Resolve": access.AccessList{In: []security.BlessingPattern{"self/other"}},
+	}
+	perms, _, err = b("bini/otherbinary").GetPermissions(otherCtx)
+	if err != nil {
+		t.Fatalf("GetPermissions failed: %v", err)
+	}
+	if got, want := perms.Normalize(), updated.Normalize(); !reflect.DeepEqual(want, got) {
+		t.Errorf("got %#v, expected %#v ", got, want)
+	}
+
+	ctx.VI(2).Infof("But self's rights are inherited from root so self can still access despite this.")
+	if _, _, err := b("bini/otherbinary").Stat(selfCtx); err != nil {
+		t.Fatalf("Stat() shouldn't have failed: %v", err)
+	}
+
+	ctx.VI(2).Infof("Self petulantly blacklists other back.")
+	perms, tag, err = b("bini").GetPermissions(selfCtx)
+	if err != nil {
+		t.Fatalf("GetPermissions() failed: %v", err)
+	}
+	for _, tag := range access.AllTypicalTags() {
+		perms.Blacklist("self/other", string(tag))
+	}
+	err = b("bini").SetPermissions(selfCtx, perms, tag)
+	if err != nil {
+		t.Fatalf("SetPermissions() failed: %v", err)
+	}
+
+	ctx.VI(2).Infof("And now other can do nothing at affecting the root. Other should be penitent.")
+	if err := b("bini/nototherbinary").Create(otherCtx, 1, repository.MediaInfo{Type: "application/octet-stream"}); verror.ErrorID(err) != verror.ErrNoAccess.ID {
+		t.Fatalf("Create() should have failed %v", err)
+	}
+
+	ctx.VI(2).Infof("But other can still access shared.")
+	if _, _, err := b("bini/shared").Stat(otherCtx); err != nil {
+		t.Fatalf("Stat() should not have failed but did: %v", err)
+	}
+
+	ctx.VI(2).Infof("Self petulantly blacklists other's binary too.")
+	perms, tag, err = b("bini/shared").GetPermissions(selfCtx)
+	if err != nil {
+		t.Fatalf("GetPermissions() failed: %v", err)
+	}
+	for _, tag := range access.AllTypicalTags() {
+		perms.Blacklist("self/other", string(tag))
+	}
+	err = b("bini/shared").SetPermissions(selfCtx, perms, tag)
+	if err != nil {
+		t.Fatalf("SetPermissions() failed: %v", err)
+	}
+	ctx.VI(2).Infof("And now other can't access shared either.")
+	if _, _, err := b("bini/shared").Stat(otherCtx); verror.ErrorID(err) != verror.ErrNoAccess.ID {
+		t.Fatalf("Stat() should have failed but didn't: %v", err)
+	}
+	// TODO(rjkroege): Extend the test with a third principal and verify that
+	// new principals can be given Admin perimission at the root.
+
+	ctx.VI(2).Infof("Self feels guilty for petulance and disempowers itself")
+	// TODO(rjkroege,caprita): This is a one-way transition for self. Perhaps it
+	// should not be. Consider adding a factory-reset facility.
+	perms, tag, err = b("bini").GetPermissions(selfCtx)
+	if err != nil {
+		t.Fatalf("GetPermissions() failed: %v", err)
+	}
+	perms.Clear("self/$", "Admin")
+	err = b("bini").SetPermissions(selfCtx, perms, tag)
+	if err != nil {
+		t.Fatalf("SetPermissions() failed: %v", err)
+	}
+
+	ctx.VI(2).Info("Self can't access other's binary now")
+	if _, _, err := b("bini/otherbinary").Stat(selfCtx); err == nil {
+		t.Fatalf("Stat() should have failed but didn't")
+	}
+}
+
+func TestBinaryRationalStartingValueForGetPermissions(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	selfPrincipal := testutil.NewPrincipal("self")
+	selfCtx, err := v23.WithPrincipal(ctx, selfPrincipal)
+	if err != nil {
+		t.Fatalf("WithPrincipal failed: %v", err)
+	}
+	sh, deferFn := servicetest.CreateShellAndMountTable(t, selfCtx, v23.GetPrincipal(selfCtx))
+	defer deferFn()
+
+	// setup mock up directory to put state in
+	storedir, cleanup := servicetest.SetupRootDir(t, "bindir")
+	defer cleanup()
+	prepDirectory(t, storedir)
+
+	otherPrincipal := testutil.NewPrincipal("other")
+	if err := otherPrincipal.AddToRoots(selfPrincipal.BlessingStore().Default()); err != nil {
+		t.Fatalf("otherPrincipal.AddToRoots() failed: %v", err)
+	}
+
+	nmh := servicetest.RunCommand(t, sh, nil, binaryd, "bini", storedir)
+	pid := servicetest.ReadPID(t, nmh)
+	defer syscall.Kill(pid, syscall.SIGINT)
+
+	perms, tag, err := b("bini").GetPermissions(selfCtx)
+	if err != nil {
+		t.Fatalf("GetPermissions failed: %#v", err)
+	}
+	expected := access.Permissions{
+		"Admin":   access.AccessList{In: []security.BlessingPattern{"self/$", "self/child"}, NotIn: []string{}},
+		"Read":    access.AccessList{In: []security.BlessingPattern{"self/$", "self/child"}, NotIn: []string{}},
+		"Write":   access.AccessList{In: []security.BlessingPattern{"self/$", "self/child"}, NotIn: []string{}},
+		"Debug":   access.AccessList{In: []security.BlessingPattern{"self/$", "self/child"}, NotIn: []string{}},
+		"Resolve": access.AccessList{In: []security.BlessingPattern{"self/$", "self/child"}, NotIn: []string{}},
+	}
+	if got, want := perms.Normalize(), expected.Normalize(); !reflect.DeepEqual(got, want) {
+		t.Errorf("got %#v, expected %#v ", got, want)
+	}
+
+	perms.Blacklist("self", string("Read"))
+	err = b("bini").SetPermissions(selfCtx, perms, tag)
+	if err != nil {
+		t.Fatalf("SetPermissions() failed: %v", err)
+	}
+
+	perms, tag, err = b("bini").GetPermissions(selfCtx)
+	if err != nil {
+		t.Fatalf("GetPermissions failed: %#v", err)
+	}
+	expected = access.Permissions{
+		"Admin":   access.AccessList{In: []security.BlessingPattern{"self/$", "self/child"}, NotIn: []string{}},
+		"Read":    access.AccessList{In: []security.BlessingPattern{"self/$", "self/child"}, NotIn: []string{"self"}},
+		"Write":   access.AccessList{In: []security.BlessingPattern{"self/$", "self/child"}, NotIn: []string{}},
+		"Debug":   access.AccessList{In: []security.BlessingPattern{"self/$", "self/child"}, NotIn: []string{}},
+		"Resolve": access.AccessList{In: []security.BlessingPattern{"self/$", "self/child"}, NotIn: []string{}},
+	}
+	if got, want := perms.Normalize(), expected.Normalize(); !reflect.DeepEqual(got, want) {
+		t.Errorf("got %#v, expected %#v ", got, want)
+	}
+
+}
diff --git a/services/internal/binarylib/service.go b/services/internal/binarylib/service.go
new file mode 100644
index 0000000..3a43e42
--- /dev/null
+++ b/services/internal/binarylib/service.go
@@ -0,0 +1,412 @@
+// 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.
+
+// The implementation of the binary repository interface stores objects
+// identified by object name suffixes using the local file system. Given an
+// object name suffix, the implementation computes an MD5 hash of the suffix and
+// generates the following path in the local filesystem:
+// /<root-dir>/<dir_1>/.../<dir_n>/<hash>. The root directory and the directory
+// depth are parameters of the implementation. <root-dir> also contains
+// __acls/data and __acls/sig files storing the Permissions for the root level.
+// The contents of the directory include the checksum and data for each of the
+// individual parts of the binary, the name of the object and a directory
+// containing the perms for this particular object:
+//
+// name
+// acls/data
+// acls/sig
+// mediainfo
+// name
+// <part_1>/checksum
+// <part_1>/data
+// ...
+// <part_n>/checksum
+// <part_n>/data
+//
+// TODO(jsimsa): Add an "fsck" method that cleans up existing on-disk
+// repository and provide a command-line flag that identifies whether
+// fsck should run when new repository server process starts up.
+package binarylib
+
+import (
+	"crypto/md5"
+	"encoding/hex"
+	"encoding/json"
+	"io"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strings"
+	"syscall"
+
+	"v.io/v23/context"
+	"v.io/v23/glob"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/security/access"
+	"v.io/v23/services/binary"
+	"v.io/v23/services/repository"
+	"v.io/v23/verror"
+	"v.io/x/ref/services/internal/pathperms"
+)
+
+// binaryService implements the Binary server interface.
+type binaryService struct {
+	// path is the local filesystem path to the object identified by the
+	// object name suffix.
+	path string
+	// state holds the state shared across different binary repository
+	// invocations.
+	state *state
+	// suffix is the name of the binary object.
+	suffix     string
+	permsStore *pathperms.PathStore
+}
+
+const pkgPath = "v.io/x/ref/services/internal/binarylib"
+
+var (
+	ErrInProgress      = verror.Register(pkgPath+".errInProgress", verror.NoRetry, "{1:}{2:} identical upload already in progress{:_}")
+	ErrInvalidParts    = verror.Register(pkgPath+".errInvalidParts", verror.NoRetry, "{1:}{2:} invalid number of binary parts{:_}")
+	ErrInvalidPart     = verror.Register(pkgPath+".errInvalidPart", verror.NoRetry, "{1:}{2:} invalid binary part number{:_}")
+	ErrOperationFailed = verror.Register(pkgPath+".errOperationFailed", verror.NoRetry, "{1:}{2:} operation failed{:_}")
+	ErrNotAuthorized   = verror.Register(pkgPath+".errNotAuthorized", verror.NoRetry, "{1:}{2:} none of the client's blessings are valid {:_}")
+)
+
+// TODO(jsimsa): When VDL supports composite literal constants, remove
+// this definition.
+var MissingPart = binary.PartInfo{
+	Checksum: binary.MissingChecksum,
+	Size:     binary.MissingSize,
+}
+
+// newBinaryService returns a new Binary service implementation.
+func newBinaryService(state *state, suffix string, permsStore *pathperms.PathStore) *binaryService {
+	return &binaryService{
+		path:       state.dir(suffix),
+		state:      state,
+		suffix:     suffix,
+		permsStore: permsStore,
+	}
+}
+
+const BufferLength = 4096
+
+func (i *binaryService) createFileTree(ctx *context.T, nparts int32, mediaInfo repository.MediaInfo) (string, error) {
+	parent, dirPerm := filepath.Dir(i.path), os.FileMode(0700)
+	if err := os.MkdirAll(parent, dirPerm); err != nil {
+		ctx.Errorf("MkdirAll(%v, %v) failed: %v", parent, dirPerm, err)
+		return "", verror.New(ErrOperationFailed, ctx)
+	}
+	prefix := "creating-"
+	tmpDir, err := ioutil.TempDir(parent, prefix)
+	if err != nil {
+		ctx.Errorf("TempDir(%v, %v) failed: %v", parent, prefix, err)
+		return "", verror.New(ErrOperationFailed, ctx)
+	}
+	nameFile, filePerm := filepath.Join(tmpDir, nameFileName), os.FileMode(0600)
+	if err := ioutil.WriteFile(nameFile, []byte(i.suffix), filePerm); err != nil {
+		ctx.Errorf("WriteFile(%q) failed: %v", nameFile, err)
+		return "", verror.New(ErrOperationFailed, ctx)
+	}
+	infoFile := filepath.Join(tmpDir, mediaInfoFileName)
+	jInfo, err := json.Marshal(mediaInfo)
+	if err != nil {
+		ctx.Errorf("json.Marshal(%v) failed: %v", mediaInfo, err)
+		return "", verror.New(ErrOperationFailed, ctx)
+	}
+	if err := ioutil.WriteFile(infoFile, jInfo, filePerm); err != nil {
+		ctx.Errorf("WriteFile(%q) failed: %v", infoFile, err)
+		return "", verror.New(ErrOperationFailed, ctx)
+	}
+	for j := 0; j < int(nparts); j++ {
+		partPath := generatePartPath(tmpDir, j)
+		if err := os.MkdirAll(partPath, dirPerm); err != nil {
+			ctx.Errorf("MkdirAll(%v, %v) failed: %v", partPath, dirPerm, err)
+			if err := os.RemoveAll(tmpDir); err != nil {
+				ctx.Errorf("RemoveAll(%v) failed: %v", tmpDir, err)
+			}
+			return "", verror.New(ErrOperationFailed, ctx)
+		}
+	}
+	return tmpDir, nil
+}
+
+func (i *binaryService) setInitialPermissions(ctx *context.T, call rpc.ServerCall) error {
+	rb, _ := security.RemoteBlessingNames(ctx, call.Security())
+	if len(rb) == 0 {
+		// None of the client's blessings are valid.
+		return verror.New(ErrNotAuthorized, ctx)
+	}
+	if err := i.permsStore.Set(permsPath(i.state.rootDir, i.suffix), pathperms.PermissionsForBlessings(rb), ""); err != nil {
+		ctx.Errorf("insertPermissions(%v) failed: %v", rb, err)
+		return verror.New(ErrOperationFailed, ctx)
+	}
+	return nil
+}
+
+func (i *binaryService) Create(ctx *context.T, call rpc.ServerCall, nparts int32, mediaInfo repository.MediaInfo) error {
+	ctx.Infof("%v.Create(%v, %v)", i.suffix, nparts, mediaInfo)
+	if nparts < 1 {
+		return verror.New(ErrInvalidParts, ctx)
+	}
+	tmpDir, err := i.createFileTree(ctx, nparts, mediaInfo)
+	if err != nil {
+		return err
+	}
+	// Use os.Rename() to atomically create the binary directory
+	// structure.
+	if err := os.Rename(tmpDir, i.path); err != nil {
+		defer func() {
+			if err := os.RemoveAll(tmpDir); err != nil {
+				ctx.Errorf("RemoveAll(%v) failed: %v", tmpDir, err)
+			}
+		}()
+		if linkErr, ok := err.(*os.LinkError); ok && linkErr.Err == syscall.ENOTEMPTY {
+			return verror.New(verror.ErrExist, ctx, i.path)
+		}
+		ctx.Errorf("Rename(%v, %v) failed: %v", tmpDir, i.path, err)
+		return verror.New(ErrOperationFailed, ctx, i.path)
+	}
+	// We only set the permissions for the binary after we ensure that it
+	// did not already exist.  This allows for brief time period during
+	// which the new binary's directory has been created but no permissions
+	// have been set yet.  To prevent unauthorized access during this
+	// interval, the authorizer is configured to reject RPCs other than
+	// Create when there are no permissions set on a binary (we also allow
+	// Glob in order to permit globbing inner nodes that don't have
+	// permissions set).
+	//
+	// TODO(caprita): consider making the setting of permissions atomic
+	// w.r.t. creating the binary.
+	if err := i.setInitialPermissions(ctx, call); err != nil {
+		if err := os.RemoveAll(i.path); err != nil {
+			ctx.Errorf("RemoveAll(%v) failed: %v", i.path, err)
+		}
+		return err
+	}
+	return nil
+}
+
+func (i *binaryService) Delete(ctx *context.T, _ rpc.ServerCall) error {
+	ctx.Infof("%v.Delete()", i.suffix)
+	if _, err := os.Stat(i.path); err != nil {
+		if os.IsNotExist(err) {
+			return verror.New(verror.ErrNoExist, ctx, i.path)
+		}
+		ctx.Errorf("Stat(%v) failed: %v", i.path, err)
+		return verror.New(ErrOperationFailed, ctx)
+	}
+	// Use os.Rename() to atomically remove the binary directory
+	// structure.
+	path := filepath.Join(filepath.Dir(i.path), "removing-"+filepath.Base(i.path))
+	if err := os.Rename(i.path, path); err != nil {
+		ctx.Errorf("Rename(%v, %v) failed: %v", i.path, path, err)
+		return verror.New(ErrOperationFailed, ctx, i.path)
+	}
+	if err := os.RemoveAll(path); err != nil {
+		ctx.Errorf("Remove(%v) failed: %v", path, err)
+		return verror.New(ErrOperationFailed, ctx)
+	}
+	for {
+		// Remove the binary and all directories on the path back to the
+		// root directory that are left empty after the binary is removed.
+		path = filepath.Dir(path)
+		if i.state.rootDir == path {
+			break
+		}
+		if err := os.Remove(path); err != nil {
+			if err.(*os.PathError).Err.Error() == syscall.ENOTEMPTY.Error() {
+				break
+			}
+			ctx.Errorf("Remove(%v) failed: %v", path, err)
+			return verror.New(ErrOperationFailed, ctx)
+		}
+	}
+	return nil
+}
+
+func (i *binaryService) Download(ctx *context.T, call repository.BinaryDownloadServerCall, part int32) error {
+	ctx.Infof("%v.Download(%v)", i.suffix, part)
+	path := i.generatePartPath(int(part))
+	if err := checksumExists(ctx, path); err != nil {
+		return err
+	}
+	dataPath := filepath.Join(path, dataFileName)
+	file, err := os.Open(dataPath)
+	if err != nil {
+		ctx.Errorf("Open(%v) failed: %v", dataPath, err)
+		return verror.New(ErrOperationFailed, ctx)
+	}
+	defer file.Close()
+	buffer := make([]byte, BufferLength)
+	sender := call.SendStream()
+	for {
+		n, err := file.Read(buffer)
+		if err != nil && err != io.EOF {
+			ctx.Errorf("Read() failed: %v", err)
+			return verror.New(ErrOperationFailed, ctx)
+		}
+		if n == 0 {
+			break
+		}
+		if err := sender.Send(buffer[:n]); err != nil {
+			ctx.Errorf("Send() failed: %v", err)
+			return verror.New(ErrOperationFailed, ctx)
+		}
+	}
+	return nil
+}
+
+// TODO(jsimsa): Design and implement an access control mechanism for
+// the URL-based downloads.
+func (i *binaryService) DownloadUrl(ctx *context.T, _ rpc.ServerCall) (string, int64, error) {
+	ctx.Infof("%v.DownloadUrl()", i.suffix)
+	return i.state.rootURL + "/" + i.suffix, 0, nil
+}
+
+func (i *binaryService) Stat(ctx *context.T, _ rpc.ServerCall) ([]binary.PartInfo, repository.MediaInfo, error) {
+	ctx.Infof("%v.Stat()", i.suffix)
+	result := make([]binary.PartInfo, 0)
+	parts, err := getParts(ctx, i.path)
+	if err != nil {
+		return []binary.PartInfo{}, repository.MediaInfo{}, err
+	}
+	for _, part := range parts {
+		checksumFile := filepath.Join(part, checksumFileName)
+		bytes, err := ioutil.ReadFile(checksumFile)
+		if err != nil {
+			if os.IsNotExist(err) {
+				result = append(result, MissingPart)
+				continue
+			}
+			ctx.Errorf("ReadFile(%v) failed: %v", checksumFile, err)
+			return []binary.PartInfo{}, repository.MediaInfo{}, verror.New(ErrOperationFailed, ctx)
+		}
+		dataFile := filepath.Join(part, dataFileName)
+		fi, err := os.Stat(dataFile)
+		if err != nil {
+			if os.IsNotExist(err) {
+				result = append(result, MissingPart)
+				continue
+			}
+			ctx.Errorf("Stat(%v) failed: %v", dataFile, err)
+			return []binary.PartInfo{}, repository.MediaInfo{}, verror.New(ErrOperationFailed, ctx)
+		}
+		result = append(result, binary.PartInfo{Checksum: string(bytes), Size: fi.Size()})
+	}
+	infoFile := filepath.Join(i.path, mediaInfoFileName)
+	jInfo, err := ioutil.ReadFile(infoFile)
+	if err != nil {
+		ctx.Errorf("ReadFile(%q) failed: %v", infoFile, err)
+		return []binary.PartInfo{}, repository.MediaInfo{}, verror.New(ErrOperationFailed, ctx)
+	}
+	var mediaInfo repository.MediaInfo
+	if err := json.Unmarshal(jInfo, &mediaInfo); err != nil {
+		ctx.Errorf("json.Unmarshal(%v) failed: %v", jInfo, err)
+		return []binary.PartInfo{}, repository.MediaInfo{}, verror.New(ErrOperationFailed, ctx)
+	}
+	return result, mediaInfo, nil
+}
+
+func (i *binaryService) Upload(ctx *context.T, call repository.BinaryUploadServerCall, part int32) error {
+	ctx.Infof("%v.Upload(%v)", i.suffix, part)
+	path, suffix := i.generatePartPath(int(part)), ""
+	err := checksumExists(ctx, path)
+	if err == nil {
+		return verror.New(verror.ErrExist, ctx, path)
+	} else if verror.ErrorID(err) != verror.ErrNoExist.ID {
+		return err
+	}
+	// Use os.OpenFile() to resolve races.
+	lockPath, flags, perm := filepath.Join(path, lockFileName), os.O_CREATE|os.O_WRONLY|os.O_EXCL, os.FileMode(0600)
+	lockFile, err := os.OpenFile(lockPath, flags, perm)
+	if err != nil {
+		if os.IsExist(err) {
+			return verror.New(ErrInProgress, ctx, path)
+		}
+		ctx.Errorf("OpenFile(%v, %v, %v) failed: %v", lockPath, flags, suffix, err)
+		return verror.New(ErrOperationFailed, ctx)
+	}
+	defer os.Remove(lockFile.Name())
+	defer lockFile.Close()
+	file, err := ioutil.TempFile(path, suffix)
+	if err != nil {
+		ctx.Errorf("TempFile(%v, %v) failed: %v", path, suffix, err)
+		return verror.New(ErrOperationFailed, ctx)
+	}
+	defer file.Close()
+	h := md5.New()
+	rStream := call.RecvStream()
+	for rStream.Advance() {
+		bytes := rStream.Value()
+		if _, err := file.Write(bytes); err != nil {
+			ctx.Errorf("Write() failed: %v", err)
+			if err := os.Remove(file.Name()); err != nil {
+				ctx.Errorf("Remove(%v) failed: %v", file.Name(), err)
+			}
+			return verror.New(ErrOperationFailed, ctx)
+		}
+		h.Write(bytes)
+	}
+
+	if err := rStream.Err(); err != nil {
+		ctx.Errorf("Advance() failed: %v", err)
+		if err := os.Remove(file.Name()); err != nil {
+			ctx.Errorf("Remove(%v) failed: %v", file.Name(), err)
+		}
+		return verror.New(ErrOperationFailed, ctx)
+	}
+
+	hash := hex.EncodeToString(h.Sum(nil))
+	checksumFile, perm := filepath.Join(path, checksumFileName), os.FileMode(0600)
+	if err := ioutil.WriteFile(checksumFile, []byte(hash), perm); err != nil {
+		ctx.Errorf("WriteFile(%v, %v, %v) failed: %v", checksumFile, hash, perm, err)
+		if err := os.Remove(file.Name()); err != nil {
+			ctx.Errorf("Remove(%v) failed: %v", file.Name(), err)
+		}
+		return verror.New(ErrOperationFailed, ctx)
+	}
+	dataFile := filepath.Join(path, dataFileName)
+	if err := os.Rename(file.Name(), dataFile); err != nil {
+		ctx.Errorf("Rename(%v, %v) failed: %v", file.Name(), dataFile, err)
+		if err := os.Remove(file.Name()); err != nil {
+			ctx.Errorf("Remove(%v) failed: %v", file.Name(), err)
+		}
+		return verror.New(ErrOperationFailed, ctx)
+	}
+	return nil
+}
+
+func (i *binaryService) GlobChildren__(ctx *context.T, call rpc.GlobChildrenServerCall, m *glob.Element) error {
+	elems := strings.Split(i.suffix, "/")
+	if len(elems) == 1 && elems[0] == "" {
+		elems = nil
+	}
+	n := i.createObjectNameTree().find(elems, false)
+	if n == nil {
+		return verror.New(ErrOperationFailed, ctx)
+	}
+	for k, _ := range n.children {
+		if m.Match(k) {
+			call.SendStream().Send(naming.GlobChildrenReplyName{Value: k})
+		}
+	}
+	return nil
+}
+
+func (i *binaryService) GetPermissions(ctx *context.T, call rpc.ServerCall) (perms access.Permissions, version string, err error) {
+	perms, version, err = i.permsStore.Get(permsPath(i.state.rootDir, i.suffix))
+	if os.IsNotExist(err) {
+		// No Permissions file found which implies a nil authorizer. This results in
+		// default authorization.
+		return pathperms.NilAuthPermissions(ctx, call.Security()), "", nil
+	}
+	return perms, version, err
+}
+
+func (i *binaryService) SetPermissions(_ *context.T, _ rpc.ServerCall, perms access.Permissions, version string) error {
+	return i.permsStore.Set(permsPath(i.state.rootDir, i.suffix), perms, version)
+}
diff --git a/services/internal/binarylib/setup.go b/services/internal/binarylib/setup.go
new file mode 100644
index 0000000..e01ab2b
--- /dev/null
+++ b/services/internal/binarylib/setup.go
@@ -0,0 +1,53 @@
+// 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 binarylib
+
+import (
+	"io/ioutil"
+	"os"
+	"path/filepath"
+
+	"v.io/x/ref/internal/logger"
+)
+
+const defaultRootPrefix = "veyron_binary_repository"
+
+// SetupRootDir sets up the root directory if it doesn't already exist. If an
+// empty string is used as root, create a new temporary directory.
+func SetupRootDir(root string) (string, error) {
+	if root == "" {
+		var err error
+		if root, err = ioutil.TempDir("", defaultRootPrefix); err != nil {
+			logger.Global().Errorf("TempDir() failed: %v\n", err)
+			return "", err
+		}
+		path, perm := filepath.Join(root, VersionFile), os.FileMode(0600)
+		if err := ioutil.WriteFile(path, []byte(Version), perm); err != nil {
+			logger.Global().Errorf("WriteFile(%v, %v, %v) failed: %v", path, Version, perm, err)
+			return "", err
+		}
+		return root, nil
+	}
+
+	_, err := os.Stat(root)
+	switch {
+	case err == nil:
+	case os.IsNotExist(err):
+		perm := os.FileMode(0700)
+		if err := os.MkdirAll(root, perm); err != nil {
+			logger.Global().Errorf("MkdirAll(%v, %v) failed: %v", root, perm, err)
+			return "", err
+		}
+		path, perm := filepath.Join(root, VersionFile), os.FileMode(0600)
+		if err := ioutil.WriteFile(path, []byte(Version), perm); err != nil {
+			logger.Global().Errorf("WriteFile(%v, %v, %v) failed: %v", path, Version, perm, err)
+			return "", err
+		}
+	default:
+		logger.Global().Errorf("Stat(%v) failed: %v", root, err)
+		return "", err
+	}
+	return root, nil
+}
diff --git a/services/internal/binarylib/state.go b/services/internal/binarylib/state.go
new file mode 100644
index 0000000..391bda2
--- /dev/null
+++ b/services/internal/binarylib/state.go
@@ -0,0 +1,86 @@
+// 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 binarylib
+
+import (
+	"crypto/md5"
+	"encoding/hex"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"v.io/v23/verror"
+)
+
+var (
+	errUnexpectedDepth   = verror.Register(pkgPath+".errUnexpectedDepth", verror.NoRetry, "{1:}{2:} Unexpected depth, expected a value between {3} and {4}, got {5}{:_}")
+	errStatFailed        = verror.Register(pkgPath+".errStatFailed", verror.NoRetry, "{1:}{2:} Stat({3}) failed{:_}")
+	errReadFileFailed    = verror.Register(pkgPath+".errReadFileFailed", verror.NoRetry, "{1:}{2:} ReadFile({3}) failed{:_}")
+	errUnexpectedVersion = verror.Register(pkgPath+".errUnexpectedVersion", verror.NoRetry, "{1:}{2:} Unexpected version: expected {3}, got {4}{:_}")
+)
+
+// state holds the state shared across different binary repository
+// invocations.
+type state struct {
+	// depth determines the depth of the directory hierarchy that the
+	// binary repository uses to organize binaries in the local file
+	// system. There is a trade-off here: smaller values lead to faster
+	// access, while higher values allow the performance to scale to
+	// larger collections of binaries. The number should be a value
+	// between 0 and (md5.Size - 1).
+	//
+	// Note that the cardinality of each level (except the leaf level)
+	// is at most 256. If you expect to have X total binary items, and
+	// you want to limit directories to at most Y entries (because of
+	// filesystem limitations), then you should set depth to at least
+	// log_256(X/Y). For example, using hierarchyDepth = 3 with a local
+	// filesystem that can handle up to 1,000 entries per directory
+	// before its performance degrades allows the binary repository to
+	// store 16B objects.
+	depth int
+	// rootDir identifies the local filesystem directory in which the
+	// binary repository stores its objects.
+	rootDir string
+	// rootURL identifies the root URL of the HTTP server serving
+	// the download URLs.
+	rootURL string
+}
+
+// NewState creates a new state object for the binary service.  This
+// should be passed into both NewDispatcher and NewHTTPRoot.
+func NewState(rootDir, rootURL string, depth int) (*state, error) {
+	if min, max := 0, md5.Size-1; min > depth || depth > max {
+		return nil, verror.New(errUnexpectedDepth, nil, min, max, depth)
+	}
+	if _, err := os.Stat(rootDir); err != nil {
+		return nil, verror.New(errStatFailed, nil, rootDir, err)
+	}
+	path := filepath.Join(rootDir, VersionFile)
+	output, err := ioutil.ReadFile(path)
+	if err != nil {
+		return nil, verror.New(errReadFileFailed, nil, path, err)
+	}
+	if expected, got := Version, strings.TrimSpace(string(output)); expected != got {
+		return nil, verror.New(errUnexpectedVersion, nil, expected, got)
+	}
+	return &state{
+		depth:   depth,
+		rootDir: rootDir,
+		rootURL: rootURL,
+	}, nil
+}
+
+// dir generates the local filesystem path for the binary identified by suffix.
+func (s *state) dir(suffix string) string {
+	h := md5.New()
+	h.Write([]byte(suffix))
+	hash := hex.EncodeToString(h.Sum(nil))
+	dir := ""
+	for j := 0; j < s.depth; j++ {
+		dir = filepath.Join(dir, hash[j*2:(j+1)*2])
+	}
+	return filepath.Join(s.rootDir, dir, hash)
+}
diff --git a/services/internal/binarylib/util_test.go b/services/internal/binarylib/util_test.go
new file mode 100644
index 0000000..cd3d785
--- /dev/null
+++ b/services/internal/binarylib/util_test.go
@@ -0,0 +1,95 @@
+// 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 binarylib_test
+
+import (
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"testing"
+
+	"v.io/v23/context"
+	"v.io/v23/services/repository"
+
+	"v.io/x/ref/services/internal/binarylib"
+	"v.io/x/ref/test/testutil"
+)
+
+// invokeUpload invokes the Upload RPC using the given client binary
+// <binary> and streams the given binary <binary> to it.
+func invokeUpload(t *testing.T, ctx *context.T, binary repository.BinaryClientMethods, data []byte, part int32) (error, error) {
+	stream, err := binary.Upload(ctx, part)
+	if err != nil {
+		t.Errorf("Upload() failed: %v", err)
+		return nil, err
+	}
+	sender := stream.SendStream()
+	if streamErr := sender.Send(data); streamErr != nil {
+		err := stream.Finish()
+		if err != nil {
+			t.Logf("Finish() failed: %v", err)
+		}
+		t.Logf("Send() failed: %v", streamErr)
+		return streamErr, err
+	}
+	if streamErr := sender.Close(); streamErr != nil {
+		err := stream.Finish()
+		if err != nil {
+			t.Logf("Finish() failed: %v", err)
+		}
+		t.Logf("Close() failed: %v", streamErr)
+		return streamErr, err
+	}
+	if err := stream.Finish(); err != nil {
+		t.Logf("Finish() failed: %v", err)
+		return nil, err
+	}
+	return nil, nil
+}
+
+// invokeDownload invokes the Download RPC using the given client binary
+// <binary> and streams binary from to it.
+func invokeDownload(t *testing.T, ctx *context.T, binary repository.BinaryClientMethods, part int32) ([]byte, error, error) {
+	stream, err := binary.Download(ctx, part)
+	if err != nil {
+		t.Errorf("Download() failed: %v", err)
+		return nil, nil, err
+	}
+	output := make([]byte, 0)
+	rStream := stream.RecvStream()
+	for rStream.Advance() {
+		bytes := rStream.Value()
+		output = append(output, bytes...)
+	}
+
+	if streamErr := rStream.Err(); streamErr != nil {
+		err := stream.Finish()
+		if err != nil {
+			t.Logf("Finish() failed: %v", err)
+		}
+		t.Logf("Advance() failed with: %v", streamErr)
+		return nil, streamErr, err
+	}
+
+	if err := stream.Finish(); err != nil {
+		t.Logf("Finish() failed: %v", err)
+		return nil, nil, err
+	}
+	return output, nil, nil
+}
+
+func prepDirectory(t *testing.T, rootDir string) {
+	path, perm := filepath.Join(rootDir, binarylib.VersionFile), os.FileMode(0600)
+	if err := ioutil.WriteFile(path, []byte(binarylib.Version), perm); err != nil {
+		t.Fatalf(testutil.FormatLogLine(2, "WriteFile(%v, %v, %v) failed: %v", path, binarylib.Version, perm, err))
+	}
+}
+
+// testData creates up to 4MB of random bytes.
+func testData(rg *testutil.Random) []byte {
+	size := rg.RandomIntn(1000 * binarylib.BufferLength)
+	data := rg.RandomBytes(size)
+	return data
+}
diff --git a/services/internal/binarylib/v23_test.go b/services/internal/binarylib/v23_test.go
new file mode 100644
index 0000000..9e2e792
--- /dev/null
+++ b/services/internal/binarylib/v23_test.go
@@ -0,0 +1,22 @@
+// 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 binarylib_test
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	modules.DispatchAndExitIfChild()
+	os.Exit(m.Run())
+}
diff --git a/services/internal/fs/only_for_test.go b/services/internal/fs/only_for_test.go
new file mode 100644
index 0000000..eeb2cb3
--- /dev/null
+++ b/services/internal/fs/only_for_test.go
@@ -0,0 +1,47 @@
+// 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 fs
+
+import (
+	"v.io/v23/naming"
+	"v.io/v23/security"
+	"v.io/v23/services/application"
+)
+
+// TP is a convenience function. It prepends the transactionNamePrefix
+// to the given path.
+func TP(path string) string {
+	return naming.Join(transactionNamePrefix, path)
+}
+
+func (ms *Memstore) PersistedFile() string {
+	return ms.persistedFile
+}
+
+func translateToGobEncodeable(in interface{}) interface{} {
+	env, ok := in.(application.Envelope)
+	if !ok {
+		return in
+	}
+	return applicationEnvelope{
+		Title:     env.Title,
+		Args:      env.Args,
+		Binary:    env.Binary,
+		Publisher: security.MarshalBlessings(env.Publisher),
+		Env:       env.Env,
+		Packages:  env.Packages,
+	}
+}
+
+func (ms *Memstore) GetGOBConvertedMemstore() map[string]interface{} {
+	convertedMap := make(map[string]interface{})
+	for k, v := range ms.data {
+		switch tv := v.(type) {
+		case application.Envelope:
+			convertedMap[k] = translateToGobEncodeable(tv)
+		}
+	}
+	return convertedMap
+}
diff --git a/services/internal/fs/simplestore.go b/services/internal/fs/simplestore.go
new file mode 100644
index 0000000..44c37e7
--- /dev/null
+++ b/services/internal/fs/simplestore.go
@@ -0,0 +1,573 @@
+// 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.
+
+// Implements a map-based store substitute that implements the legacy store API.
+package fs
+
+import (
+	"encoding/gob"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"reflect"
+	"sort"
+	"strings"
+	"sync"
+	"time"
+
+	"v.io/v23/security"
+	"v.io/v23/security/access"
+	"v.io/v23/services/application"
+	"v.io/v23/verror"
+	"v.io/v23/vom"
+	"v.io/x/lib/set"
+	"v.io/x/ref/services/profile"
+)
+
+// TODO(rjkroege@google.com) Switch Memstore to the mid-August 2014
+// style store API.
+
+const pkgPath = "v.io/x/ref/services/internal/fs"
+
+// Errors
+var (
+	ErrNoRecursiveCreateTransaction = verror.Register(pkgPath+".ErrNoRecursiveCreateTransaction", verror.NoRetry, "{1:}{2:} recursive CreateTransaction() not permitted{:_}")
+	ErrDoubleCommit                 = verror.Register(pkgPath+".ErrDoubleCommit", verror.NoRetry, "{1:}{2:} illegal attempt to commit previously committed or abandonned transaction{:_}")
+	ErrAbortWithoutTransaction      = verror.Register(pkgPath+".ErrAbortWithoutTransaction", verror.NoRetry, "{1:}{2:} illegal attempt to abort non-existent transaction{:_}")
+	ErrWithoutTransaction           = verror.Register(pkgPath+".ErrRemoveWithoutTransaction", verror.NoRetry, "{1:}{2:} call without a transaction{:_}")
+	ErrNotInMemStore                = verror.Register(pkgPath+".ErrNotInMemStore", verror.NoRetry, "{1:}{2:} not in Memstore{:_}")
+	ErrUnsupportedType              = verror.Register(pkgPath+".ErrUnsupportedType", verror.NoRetry, "{1:}{2:} attempted Put to Memstore of unsupported type{:_}")
+	ErrChildrenWithoutLock          = verror.Register(pkgPath+".ErrChildrenWithoutLock", verror.NoRetry, "{1:}{2:} Children() without a lock{:_}")
+
+	errTempFileFailed      = verror.Register(pkgPath+".errTempFileFailed", verror.NoRetry, "{1:}{2:} TempFile({3}, {4}) failed{:_}")
+	errCantCreate          = verror.Register(pkgPath+".errCantCreate", verror.NoRetry, "{1:}{2:} File ({3}) could not be created ({4}){:_}")
+	errCantOpen            = verror.Register(pkgPath+".errCantOpen", verror.NoRetry, "{1:}{2:} File ({3}) could not be opened ({4}){:_}")
+	errDecodeFailedBadData = verror.Register(pkgPath+".errDecodeFailedBadData", verror.NoRetry, "{1:}{2:} Decode() failed: data format mismatch or backing file truncated{:_}")
+	errCreateFailed        = verror.Register(pkgPath+".errCreateFailed", verror.NoRetry, "{1:}{2:} Create({3}) failed{:_}")
+	errEncodeFailed        = verror.Register(pkgPath+".errEncodeFailed", verror.NoRetry, "{1:}{2:} Encode() failed{:_}")
+	errFileSystemError     = verror.Register(pkgPath+".errFileSystemError", verror.NoRetry, "{1:}{2:} File system operation failed{:_}")
+	errFormatUpgradeError  = verror.Register(pkgPath+".errFormatUpgradeError", verror.NoRetry, "{1:}{2:} File format upgrading failed{:_}")
+)
+
+// Memstore contains the state of the memstore. It supports a single
+// transaction at a time. The current state of a Memstore under a
+// transactional name binding is the contents of puts then the contents
+// of (data - removes). puts and removes will be empty at the beginning
+// of a transaction and after an Unlock operation.
+type Memstore struct {
+	sync.Mutex
+	persistedFile              string
+	haveTransactionNameBinding bool
+	locked                     bool
+	data                       map[string]interface{}
+	puts                       map[string]interface{}
+	removes                    map[string]struct{}
+}
+
+const (
+	startingMemstoreSize  = 10
+	transactionNamePrefix = "memstore-transaction"
+
+	// A memstore is a serialized Go map. A GOB-encoded map using Go 1.4
+	// cannot begin with this byte. Consequently, simplestore writes this
+	// magic byte to the start of a vom file. Its absence at the
+	// beginning of the file indicates that the memstore file is using
+	// the GOB legacy encoding.
+	vomGobMagicByte = 0xF0
+)
+
+var keyExists = struct{}{}
+
+// TODO(rjkroege): Simplestore used GOB for its persistence
+// layer. However, now, it uses VOM to store persisted values and
+// automatically converts GOB format files to VOM-compatible on load.
+// At some point in the future, it may be possible to simplify the
+// implementation by removing support for loading files in the
+// now legacy GOB format.
+type applicationEnvelope struct {
+	Title             string
+	Args              []string
+	Binary            application.SignedFile
+	Publisher         security.WireBlessings
+	Env               []string
+	Packages          application.Packages
+	Restarts          int32
+	RestartTimeWindow time.Duration
+}
+
+// This function is needed only to support existing serialized data and
+// can be removed in a future release.
+func translateFromGobEncodeable(in interface{}) (interface{}, error) {
+	env, ok := in.(applicationEnvelope)
+	if !ok {
+		return in, nil
+	}
+	// Have to roundtrip through vom to convert from WireBlessings to Blessings.
+	// This may seem silly, but this whole translation business is silly too :)
+	// and will go away once we switch this package to using 'vom' instead of 'gob'.
+	// So for now, live with the funkiness.
+	bytes, err := vom.Encode(env.Publisher)
+	if err != nil {
+		return nil, err
+	}
+	var publisher security.Blessings
+	if err := vom.Decode(bytes, &publisher); err != nil {
+		return nil, err
+	}
+	return application.Envelope{
+		Title:             env.Title,
+		Args:              env.Args,
+		Binary:            env.Binary,
+		Publisher:         publisher,
+		Env:               env.Env,
+		Packages:          env.Packages,
+		Restarts:          env.Restarts,
+		RestartTimeWindow: env.RestartTimeWindow,
+	}, nil
+}
+
+// The implementation of set requires gob instead of json.
+func init() {
+	gob.Register(profile.Specification{})
+	gob.Register(applicationEnvelope{})
+	gob.Register(access.Permissions{})
+	// Ensure that no fields have been added to application.Envelope,
+	// because if so, then applicationEnvelope defined in this package
+	// needs to change
+	if n := reflect.TypeOf(application.Envelope{}).NumField(); n != 8 {
+		panic(fmt.Sprintf("It appears that fields have been added to or removed from application.Envelope before the hack in this file around gob-encodeability was removed. Please also update applicationEnvelope, translateToGobEncodeable and translateToGobDecodeable in this file"))
+	}
+}
+
+// isVOM returns true if the file is a VOM-format file.
+func isVOM(file io.ReadSeeker) (bool, error) {
+	oneByte := make([]byte, 1)
+	c, err := file.Read(oneByte)
+	for c == 0 && err == nil {
+		c, err = file.Read(oneByte)
+	}
+
+	if c > 0 {
+		if oneByte[0] == vomGobMagicByte {
+			return true, nil
+		} else {
+			if _, err := file.Seek(0, 0); err != nil {
+				return false, verror.New(errFileSystemError, nil, err)
+			}
+			return false, nil
+		}
+	}
+	return false, err
+}
+
+func nonEmptyExists(fileName string) (bool, error) {
+	fi, serr := os.Stat(fileName)
+	if os.IsNotExist(serr) {
+		return false, nil
+	} else if serr != nil {
+		return false, serr
+	}
+	if fi.Size() > 0 {
+		return true, nil
+	}
+	return false, nil
+}
+
+func convertFileToVomIfNecessary(fileName string) error {
+	// Open the legacy file.
+	file, err := os.Open(fileName)
+	if err != nil {
+		return verror.New(errCantOpen, nil, fileName, err)
+	}
+	defer file.Close()
+
+	// VOM files don't need conversion.
+	if is, err := isVOM(file); is || err != nil {
+		return err
+	}
+
+	// Decode the legacy GOB file
+	decoder := gob.NewDecoder(file)
+	data := make(map[string]interface{}, startingMemstoreSize)
+	if err := decoder.Decode(&data); err != nil {
+		return verror.New(errDecodeFailedBadData, nil, err)
+	}
+
+	// Update GOB file to VOM format in memory.
+	for k, v := range data {
+		tv, err := translateFromGobEncodeable(v)
+		if err != nil {
+			return verror.New(errFormatUpgradeError, nil, err)
+		}
+		data[k] = tv
+	}
+
+	ms := &Memstore{
+		data:          data,
+		persistedFile: fileName,
+	}
+	if err := ms.persist(); err != nil {
+		return err
+	}
+	return nil
+}
+
+// NewMemstore persists the Memstore to os.TempDir() if no file is
+// configured.
+func NewMemstore(configuredPersistentFile string) (*Memstore, error) {
+	data := make(map[string]interface{}, startingMemstoreSize)
+	if configuredPersistentFile == "" {
+		f, err := ioutil.TempFile(os.TempDir(), "memstore-vom")
+		if err != nil {
+			return nil, verror.New(errTempFileFailed, nil, os.TempDir(), "memstore-vom", err)
+		}
+		f.Close()
+		configuredPersistentFile = f.Name()
+	}
+
+	// Empty or non-existent files.
+	nee, err := nonEmptyExists(configuredPersistentFile)
+	if err != nil {
+		return nil, verror.New(errCantCreate, nil, configuredPersistentFile, err)
+	}
+	if !nee {
+		// Ensure that we can create a file.
+		file, cerr := os.Create(configuredPersistentFile)
+		if cerr != nil {
+			return nil, verror.New(errCantCreate, nil, configuredPersistentFile, err, cerr)
+		}
+		file.Close()
+		return &Memstore{
+			data:          data,
+			persistedFile: configuredPersistentFile,
+		}, nil
+	}
+
+	// Convert a non-empty GOB file into VOM format.
+	if err := convertFileToVomIfNecessary(configuredPersistentFile); err != nil {
+		return nil, err
+	}
+
+	file, err := os.Open(configuredPersistentFile)
+	if err != nil {
+		return nil, verror.New(errCantOpen, nil, configuredPersistentFile, err)
+	}
+	// Skip past the magic byte that identifies this as a VOM format file.
+	if _, err := file.Seek(1, 0); err != nil {
+		return nil, verror.New(errFileSystemError, nil, err)
+	}
+
+	decoder := vom.NewDecoder(file)
+	if err := decoder.Decode(&data); err != nil {
+		return nil, verror.New(errDecodeFailedBadData, nil, err)
+	}
+
+	return &Memstore{
+		data:          data,
+		persistedFile: configuredPersistentFile,
+	}, nil
+}
+
+type MemstoreObject interface {
+	Remove(_ interface{}) error
+	Exists(_ interface{}) (bool, error)
+}
+
+type boundObject struct {
+	path  string
+	ms    *Memstore
+	Value interface{}
+}
+
+// BindObject sets the path string for subsequent operations.
+func (ms *Memstore) BindObject(path string) *boundObject {
+	pathParts := strings.SplitN(path, "/", 2)
+	if pathParts[0] == transactionNamePrefix {
+		ms.haveTransactionNameBinding = true
+	} else {
+		ms.haveTransactionNameBinding = false
+	}
+	return &boundObject{path: pathParts[1], ms: ms}
+}
+
+func (ms *Memstore) removeChildren(path string) bool {
+	deleted := false
+	for k, _ := range ms.data {
+		if strings.HasPrefix(k, path) {
+			deleted = true
+			ms.removes[k] = keyExists
+		}
+	}
+	for k, _ := range ms.puts {
+		if strings.HasPrefix(k, path) {
+			deleted = true
+			delete(ms.puts, k)
+		}
+	}
+	return deleted
+}
+
+type Transaction interface {
+	CreateTransaction(_ interface{}) (string, error)
+	Commit(_ interface{}) error
+}
+
+// BindTransactionRoot on a Memstore always operates over the
+// entire Memstore. As a result, the root parameter is ignored.
+func (ms *Memstore) BindTransactionRoot(_ string) Transaction {
+	return ms
+}
+
+// BindTransaction on a Memstore can only use the single Memstore
+// transaction.
+func (ms *Memstore) BindTransaction(_ string) Transaction {
+	return ms
+}
+
+func (ms *Memstore) newTransactionState() {
+	ms.puts = make(map[string]interface{}, startingMemstoreSize)
+	ms.removes = make(map[string]struct{}, startingMemstoreSize)
+}
+
+func (ms *Memstore) clearTransactionState() {
+	ms.puts = nil
+	ms.removes = nil
+}
+
+// Unlock abandons an in-progress transaction before releasing the lock.
+func (ms *Memstore) Unlock() {
+	ms.locked = false
+	ms.clearTransactionState()
+	ms.Mutex.Unlock()
+}
+
+// Lock acquires a lock and caches the state of the lock.
+func (ms *Memstore) Lock() {
+	ms.Mutex.Lock()
+	ms.clearTransactionState()
+	ms.locked = true
+}
+
+// CreateTransaction requires the caller to acquire a lock on the Memstore.
+func (ms *Memstore) CreateTransaction(_ interface{}) (string, error) {
+	if ms.puts != nil || ms.removes != nil {
+		return "", verror.New(ErrNoRecursiveCreateTransaction, nil)
+	}
+	ms.newTransactionState()
+	return transactionNamePrefix, nil
+}
+
+// Commit updates the store and persists the result.
+func (ms *Memstore) Commit(_ interface{}) error {
+	if !ms.locked || ms.puts == nil || ms.removes == nil {
+		return verror.New(ErrDoubleCommit, nil)
+	}
+	for k, v := range ms.puts {
+		ms.data[k] = v
+	}
+	for k, _ := range ms.removes {
+		delete(ms.data, k)
+	}
+	return ms.persist()
+}
+
+func (ms *Memstore) Abort(_ interface{}) error {
+	if !ms.locked {
+		return verror.New(ErrAbortWithoutTransaction, nil)
+	}
+	return nil
+}
+
+func (o *boundObject) Remove(_ interface{}) error {
+	if !o.ms.locked {
+		return verror.New(ErrWithoutTransaction, nil, "Remove()")
+	}
+
+	if _, pendingRemoval := o.ms.removes[o.path]; pendingRemoval {
+		return verror.New(ErrNotInMemStore, nil, o.path)
+	}
+
+	_, found := o.ms.data[o.path]
+	if !found && !o.ms.removeChildren(o.path) {
+		return verror.New(ErrNotInMemStore, nil, o.path)
+	}
+	delete(o.ms.puts, o.path)
+	o.ms.removes[o.path] = keyExists
+	return nil
+}
+
+// transactionExists implements Exists() for bound names that have the
+// transaction prefix.
+func (o *boundObject) transactionExists() bool {
+	// Determine if the bound name point to a real object.
+	_, inBase := o.ms.data[o.path]
+	_, inPuts := o.ms.puts[o.path]
+	_, inRemoves := o.ms.removes[o.path]
+
+	// not yet committed.
+	if inPuts || (inBase && !inRemoves) {
+		return true
+	}
+
+	// The bound names might be a prefix of the path for a real object. For
+	// example, BindObject("/test/a"). Put(o) creates a real object o at path
+	/// test/a so the code above will cause BindObject("/test/a").Exists() to
+	// return true. Testing this is however not sufficient because
+	// BindObject(any prefix of on object path).Exists() needs to also be
+	// true. For example, here BindObject("/test").Exists() is true.
+	//
+	// Consequently, transactionExists scans all object names in the Memstore
+	// to determine if any of their names have the bound name as a prefix.
+	// Puts take precedence over removes so we scan it first.
+
+	for k, _ := range o.ms.puts {
+		if strings.HasPrefix(k, o.path) {
+			return true
+		}
+	}
+
+	// Then we scan data for matches and verify that at least one of the
+	// object names with the bound prefix have not been removed.
+
+	for k, _ := range o.ms.data {
+		if _, inRemoves := o.ms.removes[k]; strings.HasPrefix(k, o.path) && !inRemoves {
+			return true
+		}
+	}
+	return false
+}
+
+func (o *boundObject) Exists(_ interface{}) (bool, error) {
+	if o.ms.haveTransactionNameBinding {
+		return o.transactionExists(), nil
+	} else {
+		_, inBase := o.ms.data[o.path]
+		if inBase {
+			return true, nil
+		}
+		for k, _ := range o.ms.data {
+			if strings.HasPrefix(k, o.path) {
+				return true, nil
+			}
+		}
+	}
+	return false, nil
+}
+
+// transactionBoundGet implements Get while the bound name has the
+// transaction prefix.
+func (o *boundObject) transactionBoundGet() (*boundObject, error) {
+	bv, inBase := o.ms.data[o.path]
+	_, inRemoves := o.ms.removes[o.path]
+	pv, inPuts := o.ms.puts[o.path]
+
+	found := inPuts || (inBase && !inRemoves)
+	if !found {
+		return nil, verror.New(ErrNotInMemStore, nil, o.path)
+	}
+
+	if inPuts {
+		o.Value = pv
+	} else {
+		o.Value = bv
+	}
+	var err error
+	if o.Value, err = translateFromGobEncodeable(o.Value); err != nil {
+		return nil, err
+	}
+	return o, nil
+}
+
+func (o *boundObject) bareGet() (*boundObject, error) {
+	bv, inBase := o.ms.data[o.path]
+
+	if !inBase {
+		return nil, verror.New(ErrNotInMemStore, nil, o.path)
+	}
+	o.Value = bv
+	return o, nil
+}
+
+func (o *boundObject) Get(_ interface{}) (*boundObject, error) {
+	if o.ms.haveTransactionNameBinding {
+		return o.transactionBoundGet()
+	} else {
+		return o.bareGet()
+	}
+}
+
+func (o *boundObject) Put(_ interface{}, envelope interface{}) (*boundObject, error) {
+	if !o.ms.locked {
+		return nil, verror.New(ErrWithoutTransaction, nil, "Put()")
+	}
+	switch v := envelope.(type) {
+	case application.Envelope, profile.Specification, access.Permissions:
+		o.ms.puts[o.path] = v
+		delete(o.ms.removes, o.path)
+		o.Value = o.path
+		return o, nil
+	default:
+		return o, verror.New(ErrUnsupportedType, nil)
+	}
+}
+
+func (o *boundObject) Children() ([]string, error) {
+	if !o.ms.locked {
+		return nil, verror.New(ErrChildrenWithoutLock, nil)
+	}
+	found := false
+	childrenSet := make(map[string]struct{})
+	for k, _ := range o.ms.data {
+		if strings.HasPrefix(k, o.path) || o.path == "" {
+			name := strings.TrimPrefix(k, o.path)
+			// Found the object itself.
+			if len(name) == 0 {
+				found = true
+				continue
+			}
+			// This was only a prefix match, not what we're looking for.
+			if name[0] != '/' && o.path != "" {
+				continue
+			}
+			found = true
+			name = strings.TrimLeft(name, "/")
+			if idx := strings.Index(name, "/"); idx != -1 {
+				name = name[:idx]
+			}
+			childrenSet[name] = keyExists
+		}
+	}
+	if !found {
+		return nil, verror.New(verror.ErrNoExist, nil, o.path)
+	}
+	children := set.String.ToSlice(childrenSet)
+	sort.Strings(children)
+	return children, nil
+}
+
+// persist() writes the state of the Memstore to persistent storage.
+func (ms *Memstore) persist() error {
+	file, err := ioutil.TempFile(os.TempDir(), "memstore-persisting")
+	if err != nil {
+		return verror.New(errTempFileFailed, nil, os.TempDir(), "memstore-persisting", err)
+	}
+	defer file.Close()
+	defer os.Remove(file.Name())
+
+	// Mark this VOM file with the VOM file format magic byte.
+	if _, err := file.Write([]byte{byte(vomGobMagicByte)}); err != nil {
+		return err
+	}
+	enc := vom.NewEncoder(file)
+	if err := enc.Encode(ms.data); err != nil {
+		return verror.New(errEncodeFailed, nil, err)
+	}
+	ms.clearTransactionState()
+
+	if err := os.Rename(file.Name(), ms.persistedFile); err != nil {
+		return verror.New(errEncodeFailed, nil, err)
+	}
+	return nil
+}
diff --git a/services/internal/fs/simplestore_test.go b/services/internal/fs/simplestore_test.go
new file mode 100644
index 0000000..969905f
--- /dev/null
+++ b/services/internal/fs/simplestore_test.go
@@ -0,0 +1,595 @@
+// 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 fs_test
+
+import (
+	"encoding/gob"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"reflect"
+	"testing"
+
+	"v.io/v23/naming"
+	"v.io/v23/services/application"
+	"v.io/v23/verror"
+	"v.io/x/ref/services/internal/fs"
+	_ "v.io/x/ref/services/profile"
+)
+
+func tempFile(t *testing.T) string {
+	tmpfile, err := ioutil.TempFile("", "simplestore-test-")
+	if err != nil {
+		t.Fatalf("ioutil.TempFile() failed: %v", err)
+	}
+	defer tmpfile.Close()
+	return tmpfile.Name()
+}
+
+func TestNewMemstore(t *testing.T) {
+	memstore, err := fs.NewMemstore("")
+
+	if err != nil {
+		t.Fatalf("fs.NewMemstore() failed: %v", err)
+	}
+
+	if _, err = os.Stat(memstore.PersistedFile()); err != nil {
+		t.Fatalf("Stat(%v) failed: %v", memstore.PersistedFile(), err)
+	}
+	os.Remove(memstore.PersistedFile())
+}
+
+func TestNewNamedMemstore(t *testing.T) {
+	path := tempFile(t)
+	defer os.Remove(path)
+	memstore, err := fs.NewMemstore(path)
+	if err != nil {
+		t.Fatalf("fs.NewMemstore() failed: %v", err)
+	}
+
+	if _, err = os.Stat(memstore.PersistedFile()); err != nil {
+		t.Fatalf("Stat(%v) failed: %v", path, err)
+	}
+}
+
+// Verify that all of the listed paths Exists().
+// Caller is responsible for setting up any transaction state necessary.
+func allPathsExist(ts *fs.Memstore, paths []string) error {
+	for _, p := range paths {
+		exists, err := ts.BindObject(p).Exists(nil)
+		if err != nil {
+			return fmt.Errorf("Exists(%s) expected to succeed but failed: %v", p, err)
+		}
+		if !exists {
+			return fmt.Errorf("Exists(%s) expected to be true but is false", p)
+		}
+	}
+	return nil
+}
+
+// Verify that all of the listed paths !Exists().
+// Caller is responsible for setting up any transaction state necessary.
+func allPathsDontExist(ts *fs.Memstore, paths []string) error {
+	for _, p := range paths {
+		exists, err := ts.BindObject(p).Exists(nil)
+		if err != nil {
+			return fmt.Errorf("Exists(%s) expected to succeed but failed: %v", p, err)
+		}
+		if exists {
+			return fmt.Errorf("Exists(%s) expected to be false but is true", p)
+		}
+	}
+	return nil
+}
+
+type PathValue struct {
+	Path     string
+	Expected interface{}
+}
+
+// getEquals tests that every provided path is equal to the specified value.
+func allPathsEqual(ts *fs.Memstore, pvs []PathValue) error {
+	for _, p := range pvs {
+		v, err := ts.BindObject(p.Path).Get(nil)
+		if err != nil {
+			return fmt.Errorf("Get(%s) expected to succeed but failed: %v", p, err)
+		}
+		if !reflect.DeepEqual(p.Expected, v.Value) {
+			return fmt.Errorf("Unexpected non-equality for %s: got %v, expected %v", p.Path, v.Value, p.Expected)
+		}
+	}
+	return nil
+}
+
+func TestSerializeDeserialize(t *testing.T) {
+	path := tempFile(t)
+	defer os.Remove(path)
+	memstoreOriginal, err := fs.NewMemstore(path)
+	if err != nil {
+		t.Fatalf("fs.NewMemstore() failed: %v", err)
+	}
+
+	// Create example data.
+	envelope := application.Envelope{
+		Args:   []string{"--help"},
+		Env:    []string{"DEBUG=1"},
+		Binary: application.SignedFile{File: "/v23/name/of/binary"},
+	}
+	secondEnvelope := application.Envelope{
+		Args:   []string{"--save"},
+		Env:    []string{"VEYRON=42"},
+		Binary: application.SignedFile{File: "/v23/name/of/binary/is/memstored"},
+	}
+
+	// TRANSACTION BEGIN
+	// Insert a value into the fs.Memstore at /test/a
+	memstoreOriginal.Lock()
+	tname, err := memstoreOriginal.BindTransactionRoot("ignored").CreateTransaction(nil)
+	if err != nil {
+		t.Fatalf("CreateTransaction() failed: %v", err)
+	}
+	if _, err := memstoreOriginal.BindObject(fs.TP("/test/a")).Put(nil, envelope); err != nil {
+		t.Fatalf("Put() failed: %v", err)
+	}
+
+	if err := allPathsExist(memstoreOriginal, []string{fs.TP("/test/a"), fs.TP("/test")}); err != nil {
+		t.Fatalf("%v", err)
+	}
+	if err := allPathsEqual(memstoreOriginal, []PathValue{{fs.TP("/test/a"), envelope}}); err != nil {
+		t.Fatalf("%v", err)
+	}
+
+	if err := memstoreOriginal.BindTransaction(tname).Commit(nil); err != nil {
+		t.Fatalf("Commit() failed: %v", err)
+	}
+	memstoreOriginal.Unlock()
+	// TRANSACTION END
+
+	// Validate persisted state.
+	if err := allPathsExist(memstoreOriginal, []string{"/test/a", "/test"}); err != nil {
+		t.Fatalf("%v", err)
+	}
+	if err := allPathsEqual(memstoreOriginal, []PathValue{{"/test/a", envelope}}); err != nil {
+		t.Fatalf("%v", err)
+	}
+
+	// TRANSACTION BEGIN Write a value to /test/b as well.
+	memstoreOriginal.Lock()
+	tname, err = memstoreOriginal.BindTransactionRoot("also ignored").CreateTransaction(nil)
+	bindingTnameTestB := memstoreOriginal.BindObject(fs.TP("/test/b"))
+	if _, err := bindingTnameTestB.Put(nil, envelope); err != nil {
+		t.Fatalf("Put() failed: %v", err)
+	}
+
+	// Validate persisted state during transaction
+	if err := allPathsExist(memstoreOriginal, []string{"/test/a", "/test"}); err != nil {
+		t.Fatalf("%v", err)
+	}
+	if err := allPathsEqual(memstoreOriginal, []PathValue{{"/test/a", envelope}}); err != nil {
+		t.Fatalf("%v", err)
+	}
+	// Validate pending state during transaction
+	if err := allPathsExist(memstoreOriginal, []string{fs.TP("/test/a"), fs.TP("/test"), fs.TP("/test/b")}); err != nil {
+		t.Fatalf("%v", err)
+	}
+	if err := allPathsEqual(memstoreOriginal, []PathValue{
+		{fs.TP("/test/a"), envelope},
+		{fs.TP("/test/b"), envelope}}); err != nil {
+		t.Fatalf("%v", err)
+	}
+
+	// Commit the <tname>/test/b to /test/b
+	if err := memstoreOriginal.Commit(nil); err != nil {
+		t.Fatalf("Commit() failed: %v", err)
+	}
+	memstoreOriginal.Unlock()
+	// TODO(rjkroege): Consider ensuring that Get() on  <tname>/test/b should now fail.
+	// TRANSACTION END
+
+	// Validate persisted state after transaction
+	if err := allPathsExist(memstoreOriginal, []string{"/test/a", "/test", "/test/b"}); err != nil {
+		t.Fatalf("%v", err)
+	}
+	if err := allPathsEqual(memstoreOriginal, []PathValue{
+		{"/test/a", envelope},
+		{"/test/b", envelope}}); err != nil {
+		t.Fatalf("%v", err)
+	}
+
+	// TRANSACTION BEGIN (to be abandonned)
+	memstoreOriginal.Lock()
+	tname, err = memstoreOriginal.BindTransactionRoot("").CreateTransaction(nil)
+
+	// Exists is true before doing anything.
+	if err := allPathsExist(memstoreOriginal, []string{fs.TP("/test")}); err != nil {
+		t.Fatalf("%v", err)
+	}
+
+	if _, err := memstoreOriginal.BindObject(fs.TP("/test/b")).Put(nil, secondEnvelope); err != nil {
+		t.Fatalf("Put() failed: %v", err)
+	}
+
+	// Validate persisted state during transaction
+	if err := allPathsExist(memstoreOriginal, []string{
+		"/test/a",
+		"/test/b",
+		"/test",
+		fs.TP("/test"),
+		fs.TP("/test/a"),
+		fs.TP("/test/b"),
+	}); err != nil {
+		t.Fatalf("%v", err)
+	}
+	if err := allPathsEqual(memstoreOriginal, []PathValue{
+		{"/test/a", envelope},
+		{"/test/b", envelope},
+		{fs.TP("/test/b"), secondEnvelope},
+		{fs.TP("/test/a"), envelope},
+	}); err != nil {
+		t.Fatalf("%v", err)
+	}
+
+	// Pending Remove() of /test
+	if err := memstoreOriginal.BindObject(fs.TP("/test")).Remove(nil); err != nil {
+		t.Fatalf("Remove() failed: %v", err)
+	}
+
+	// Verify that all paths are successfully removed from the in-progress transaction.
+	if err := allPathsDontExist(memstoreOriginal, []string{fs.TP("/test/a"), fs.TP("/test"), fs.TP("/test/b")}); err != nil {
+		t.Fatalf("%v", err)
+	}
+	// But all paths remain in the persisted version.
+	if err := allPathsExist(memstoreOriginal, []string{"/test/a", "/test", "/test/b"}); err != nil {
+		t.Fatalf("%v", err)
+	}
+	if err := allPathsEqual(memstoreOriginal, []PathValue{
+		{"/test/a", envelope},
+		{"/test/b", envelope},
+	}); err != nil {
+		t.Fatalf("%v", err)
+	}
+
+	// At which point, Get() on the transaction won't find anything.
+	if _, err := memstoreOriginal.BindObject(fs.TP("/test/a")).Get(nil); verror.ErrorID(err) != fs.ErrNotInMemStore.ID {
+		t.Fatalf("Get() should have failed: got %v, expected %v", err, verror.New(fs.ErrNotInMemStore, nil, tname+"/test/a"))
+	}
+
+	// Attempting to Remove() it over again will fail.
+	if err := memstoreOriginal.BindObject(fs.TP("/test/a")).Remove(nil); verror.ErrorID(err) != fs.ErrNotInMemStore.ID {
+		t.Fatalf("Remove() should have failed: got %v, expected %v", err, verror.New(fs.ErrNotInMemStore, nil, tname+"/test/a"))
+	}
+
+	// Attempting to Remove() a non-existing path will fail.
+	if err := memstoreOriginal.BindObject(fs.TP("/foo")).Remove(nil); verror.ErrorID(err) != fs.ErrNotInMemStore.ID {
+		t.Fatalf("Remove() should have failed: got %v, expected %v", err, verror.New(fs.ErrNotInMemStore, nil, tname+"/foo"))
+	}
+
+	// Exists() a non-existing path will fail.
+	if present, _ := memstoreOriginal.BindObject(fs.TP("/foo")).Exists(nil); present {
+		t.Fatalf("Exists() should have failed for non-existing path %s", tname+"/foo")
+	}
+
+	// Abort the transaction without committing it.
+	memstoreOriginal.Abort(nil)
+	memstoreOriginal.Unlock()
+	// TRANSACTION END (ABORTED)
+
+	// Validate that persisted state after abandonned transaction has not changed.
+	if err := allPathsExist(memstoreOriginal, []string{"/test/a", "/test", "/test/b"}); err != nil {
+		t.Fatalf("%v", err)
+	}
+	if err := allPathsEqual(memstoreOriginal, []PathValue{
+		{"/test/a", envelope},
+		{"/test/b", envelope}}); err != nil {
+		t.Fatalf("%v", err)
+	}
+
+	// Validate that Get will fail on a non-existent path.
+	if _, err := memstoreOriginal.BindObject("/test/c").Get(nil); verror.ErrorID(err) != fs.ErrNotInMemStore.ID {
+		t.Fatalf("Get() should have failed: got %v, expected %v", err, verror.New(fs.ErrNotInMemStore, nil, tname+"/test/c"))
+	}
+
+	// Verify that the previous Commit() operations have persisted to
+	// disk by creating a new Memstore from the contents on disk.
+	memstoreCopy, err := fs.NewMemstore(path)
+	if err != nil {
+		t.Fatalf("fs.NewMemstore() failed: %v", err)
+	}
+	// Verify that memstoreCopy is an exact copy of memstoreOriginal.
+	if err := allPathsExist(memstoreCopy, []string{"/test/a", "/test", "/test/b"}); err != nil {
+		t.Fatalf("%v", err)
+	}
+	if err := allPathsEqual(memstoreCopy, []PathValue{
+		{"/test/a", envelope},
+		{"/test/b", envelope}}); err != nil {
+		t.Fatalf("%v", err)
+	}
+
+	// TRANSACTION BEGIN
+	memstoreCopy.Lock()
+	tname, err = memstoreCopy.BindTransactionRoot("also ignored").CreateTransaction(nil)
+
+	// Add a pending object c to test that pending objects are deleted.
+	if _, err := memstoreCopy.BindObject(fs.TP("/test/c")).Put(nil, secondEnvelope); err != nil {
+		t.Fatalf("Put() failed: %v", err)
+	}
+	if err := allPathsExist(memstoreCopy, []string{
+		fs.TP("/test/a"),
+		"/test/a",
+		fs.TP("/test"),
+		"/test",
+		fs.TP("/test/b"),
+		"/test/b",
+		fs.TP("/test/c"),
+	}); err != nil {
+		t.Fatalf("%v", err)
+	}
+	if err := allPathsEqual(memstoreCopy, []PathValue{
+		{fs.TP("/test/a"), envelope},
+		{fs.TP("/test/b"), envelope},
+		{fs.TP("/test/c"), secondEnvelope},
+		{"/test/a", envelope},
+		{"/test/b", envelope},
+	}); err != nil {
+		t.Fatalf("%v", err)
+	}
+
+	// Remove /test/a /test/b /test/c /test
+	if err := memstoreCopy.BindObject(fs.TP("/test")).Remove(nil); err != nil {
+		t.Fatalf("Remove() failed: %v", err)
+	}
+	// Verify that all paths are successfully removed from the in-progress transaction.
+	if err := allPathsDontExist(memstoreCopy, []string{
+		fs.TP("/test/a"),
+		fs.TP("/test"),
+		fs.TP("/test/b"),
+		fs.TP("/test/c"),
+	}); err != nil {
+		t.Fatalf("%v", err)
+	}
+	if err := allPathsExist(memstoreCopy, []string{
+		"/test/a",
+		"/test",
+		"/test/b",
+	}); err != nil {
+		t.Fatalf("%v", err)
+	}
+	// Commit the change.
+	if err = memstoreCopy.Commit(nil); err != nil {
+		t.Fatalf("Commit() failed: %v", err)
+	}
+	memstoreCopy.Unlock()
+	// TRANSACTION END
+
+	// Create a new Memstore from file to see if Remove operates are
+	// persisted.
+	memstoreRemovedCopy, err := fs.NewMemstore(path)
+	if err != nil {
+		t.Fatalf("fs.NewMemstore() failed for removed copy: %v", err)
+	}
+	if err := allPathsDontExist(memstoreRemovedCopy, []string{
+		"/test/a",
+		"/test",
+		"/test/b",
+		"/test/c",
+	}); err != nil {
+		t.Fatalf("%v", err)
+	}
+}
+
+func TestOperationsNeedValidBinding(t *testing.T) {
+	path := tempFile(t)
+	defer os.Remove(path)
+	memstoreOriginal, err := fs.NewMemstore(path)
+	if err != nil {
+		t.Fatalf("fs.NewMemstore() failed: %v", err)
+	}
+
+	// Create example data.
+	envelope := application.Envelope{
+		Args:   []string{"--help"},
+		Env:    []string{"DEBUG=1"},
+		Binary: application.SignedFile{File: "/v23/name/of/binary"},
+	}
+
+	// TRANSACTION BEGIN
+	// Attempt inserting a value at /test/a.
+	memstoreOriginal.Lock()
+	tname, err := memstoreOriginal.BindTransactionRoot("").CreateTransaction(nil)
+	if err != nil {
+		t.Fatalf("CreateTransaction() failed: %v", err)
+	}
+
+	if err := memstoreOriginal.BindTransaction(tname).Commit(nil); err != nil {
+		t.Fatalf("Commit() failed: %v", err)
+	}
+	memstoreOriginal.Unlock()
+	// TRANSACTION END
+
+	// Put outside ot a transaction should fail.
+	bindingTnameTestA := memstoreOriginal.BindObject(naming.Join("fooey", "/test/a"))
+	if _, err := bindingTnameTestA.Put(nil, envelope); verror.ErrorID(err) != fs.ErrWithoutTransaction.ID {
+		t.Fatalf("Put() failed: got %v, expected %v", err, verror.New(fs.ErrWithoutTransaction, nil, "Put()"))
+	}
+
+	// Remove outside of a transaction should fail
+	if err := bindingTnameTestA.Remove(nil); verror.ErrorID(err) != fs.ErrWithoutTransaction.ID {
+		t.Fatalf("Put() failed: got %v, expected %v", err, verror.New(fs.ErrWithoutTransaction, nil, "Remove()"))
+	}
+
+	// Commit outside of a transaction should fail
+	if err := memstoreOriginal.BindTransaction(tname).Commit(nil); verror.ErrorID(err) != fs.ErrDoubleCommit.ID {
+		t.Fatalf("Commit() failed: got %v, expected %v", err, verror.New(fs.ErrDoubleCommit, nil))
+	}
+
+	// Attempt inserting a value at /test/b
+	memstoreOriginal.Lock()
+	tname, err = memstoreOriginal.BindTransactionRoot("").CreateTransaction(nil)
+	if err != nil {
+		t.Fatalf("CreateTransaction() failed: %v", err)
+	}
+
+	bindingTnameTestB := memstoreOriginal.BindObject(fs.TP("/test/b"))
+	if _, err := bindingTnameTestB.Put(nil, envelope); err != nil {
+		t.Fatalf("Put() failed: %v", err)
+	}
+	// Abandon transaction.
+	memstoreOriginal.Unlock()
+
+	// Remove should definitely fail on an abndonned transaction.
+	if err := bindingTnameTestB.Remove(nil); verror.ErrorID(err) != fs.ErrWithoutTransaction.ID {
+		t.Fatalf("Remove() failed: got %v, expected %v", err, verror.New(fs.ErrWithoutTransaction, nil, "Remove()"))
+	}
+}
+
+func TestOpenEmptyMemstore(t *testing.T) {
+	path := tempFile(t)
+	defer os.Remove(path)
+
+	// Create a brand new memstore persisted to namedms. This will
+	// have the side-effect of creating an empty backing file.
+	if _, err := fs.NewMemstore(path); err != nil {
+		t.Fatalf("fs.NewMemstore() failed: %v", err)
+	}
+
+	// Create another memstore that will attempt to deserialize the empty
+	// backing file.
+	if _, err := fs.NewMemstore(path); err != nil {
+		t.Fatalf("fs.NewMemstore() failed: %v", err)
+	}
+}
+
+func TestChildren(t *testing.T) {
+	memstore, err := fs.NewMemstore("")
+	if err != nil {
+		t.Fatalf("fs.NewMemstore() failed: %v", err)
+	}
+	defer os.Remove(memstore.PersistedFile())
+
+	// Create example data.
+	envelope := application.Envelope{
+		Args:   []string{"--help"},
+		Env:    []string{"DEBUG=1"},
+		Binary: application.SignedFile{File: "/v23/name/of/binary"},
+	}
+
+	// TRANSACTION BEGIN
+	memstore.Lock()
+	tname, err := memstore.BindTransactionRoot("ignored").CreateTransaction(nil)
+	if err != nil {
+		t.Fatalf("CreateTransaction() failed: %v", err)
+	}
+	// Insert a few values
+	names := []string{"/test/a", "/test/b", "/test/a/x", "/test/a/y", "/test/b/fooooo/bar"}
+	for _, n := range names {
+		if _, err := memstore.BindObject(fs.TP(n)).Put(nil, envelope); err != nil {
+			t.Fatalf("Put() failed: %v", err)
+		}
+	}
+	if err := memstore.BindTransaction(tname).Commit(nil); err != nil {
+		t.Fatalf("Commit() failed: %v", err)
+	}
+	memstore.Unlock()
+	// TRANSACTION END
+
+	memstore.Lock()
+	testcases := []struct {
+		name     string
+		children []string
+	}{
+		{"/", []string{"test"}},
+		{"/test", []string{"a", "b"}},
+		{"/test/a", []string{"x", "y"}},
+		{"/test/b", []string{"fooooo"}},
+		{"/test/b/fooooo", []string{"bar"}},
+		{"/test/a/x", nil},
+		{"/test/a/y", nil},
+	}
+	for _, tc := range testcases {
+		children, err := memstore.BindObject(tc.name).Children()
+		if err != nil {
+			t.Errorf("unexpected error for %q: %v", tc.name, err)
+			continue
+		}
+		if !reflect.DeepEqual(children, tc.children) {
+			t.Errorf("unexpected result for %q: got %q, expected %q", tc.name, children, tc.children)
+		}
+	}
+
+	for _, notthere := range []string{"/doesnt-exist", "/tes"} {
+		if _, err := memstore.BindObject(notthere).Children(); err == nil {
+			t.Errorf("unexpected success for: %q", notthere)
+		}
+	}
+	memstore.Unlock()
+}
+
+func TestFormatConversion(t *testing.T) {
+	path := tempFile(t)
+	defer os.Remove(path)
+	originalMemstore, err := fs.NewMemstore(path)
+	if err != nil {
+		t.Fatalf("fs.NewMemstore() failed: %v", err)
+	}
+
+	// Create example data.
+	envelope := application.Envelope{
+		Args:   []string{"--help"},
+		Env:    []string{"DEBUG=1"},
+		Binary: application.SignedFile{File: "/v23/name/of/binary"},
+	}
+
+	// TRANSACTION BEGIN
+	// Insert a value into the legacy Memstore at /test/a
+	originalMemstore.Lock()
+	tname, err := originalMemstore.BindTransactionRoot("ignored").CreateTransaction(nil)
+	if err != nil {
+		t.Fatalf("CreateTransaction() failed: %v", err)
+	}
+	if _, err := originalMemstore.BindObject(fs.TP("/test/a")).Put(nil, envelope); err != nil {
+		t.Fatalf("Put() failed: %v", err)
+	}
+	if err := originalMemstore.BindTransaction(tname).Commit(nil); err != nil {
+		t.Fatalf("Commit() failed: %v", err)
+	}
+	originalMemstore.Unlock()
+
+	// Write the original memstore to a GOB file.
+	if err := gobPersist(t, originalMemstore); err != nil {
+		t.Fatalf("gobPersist() failed: %v", err)
+	}
+
+	// Open the GOB format file.
+	memstore, err := fs.NewMemstore(path)
+	if err != nil {
+		t.Fatalf("fs.NewMemstore() failed: %v", err)
+	}
+
+	// Verify the state.
+	if err := allPathsEqual(memstore, []PathValue{{"/test/a", envelope}}); err != nil {
+		t.Fatalf("%v", err)
+	}
+}
+
+// gobPersist writes Memstore ms to its backing file.
+func gobPersist(t *testing.T, ms *fs.Memstore) error {
+	// Convert this memstore to the legacy GOM format.
+	data := ms.GetGOBConvertedMemstore()
+
+	// Persist this file to a GOB format file.
+	fname := ms.PersistedFile()
+	file, err := os.Create(fname)
+	if err != nil {
+		t.Fatalf("os.Create(%s) failed: %v", fname, err)
+	}
+	defer file.Close()
+
+	enc := gob.NewEncoder(file)
+	err = enc.Encode(data)
+	if err := enc.Encode(data); err != nil {
+		t.Fatalf("enc.Encode() failed: %v", err)
+	}
+	return nil
+}
diff --git a/services/internal/logreaderlib/logfile.go b/services/internal/logreaderlib/logfile.go
new file mode 100644
index 0000000..5654301
--- /dev/null
+++ b/services/internal/logreaderlib/logfile.go
@@ -0,0 +1,157 @@
+// 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 logreaderlib implements the LogFile interface from
+// v.io/v23/services/logreader, which can be used to allow remote access to log
+// files, and the Globbable interface from v.io/v23/services/mounttable to find
+// the files in a logs directory.
+package logreaderlib
+
+import (
+	"io"
+	"math"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"v.io/v23/context"
+	"v.io/v23/glob"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+	"v.io/v23/services/logreader"
+	"v.io/v23/verror"
+)
+
+const pkgPath = "v.io/x/ref/services/internal/logreaderlib"
+
+var (
+	errOperationFailed = verror.Register(pkgPath+".errOperationFailed", verror.NoRetry, "{1:}{2:} operation failed{:_}")
+)
+
+// NewLogFileService returns a new log file server.
+func NewLogFileService(root, suffix string) interface{} {
+	return logreader.LogFileServer(&logfileService{filepath.Clean(root), suffix})
+}
+
+// translateNameToFilename returns the file name that corresponds to the object
+// name.
+func translateNameToFilename(root, name string) (string, error) {
+	name = filepath.Join(strings.Split(name, "/")...)
+	p := filepath.Join(root, name)
+	// Make sure we're not asked to read a file outside of the root
+	// directory. This could happen if suffix contains "../", which get
+	// collapsed by filepath.Join().
+	if !strings.HasPrefix(p, root) {
+		return "", verror.New(errOperationFailed, nil, name)
+	}
+	return p, nil
+}
+
+// logfileService holds the state of a logfile invocation.
+type logfileService struct {
+	// root is the root directory from which the object names are based.
+	root string
+	// suffix is the suffix of the current invocation that is assumed to
+	// be used as a relative object name to identify a log file.
+	suffix string
+}
+
+// Size returns the size of the log file, in bytes.
+func (i *logfileService) Size(ctx *context.T, _ rpc.ServerCall) (int64, error) {
+	ctx.VI(1).Infof("%v.Size()", i.suffix)
+	fname, err := translateNameToFilename(i.root, i.suffix)
+	if err != nil {
+		return 0, err
+	}
+	fi, err := os.Stat(fname)
+	if err != nil {
+		if os.IsNotExist(err) {
+			return 0, verror.New(verror.ErrNoExist, ctx, fname)
+		}
+		ctx.Errorf("Stat(%v) failed: %v", fname, err)
+		return 0, verror.New(errOperationFailed, ctx, fname)
+	}
+	if fi.IsDir() {
+		return 0, verror.New(errOperationFailed, ctx, fname)
+	}
+	return fi.Size(), nil
+}
+
+// ReadLog returns log entries from the log file.
+func (i *logfileService) ReadLog(ctx *context.T, call logreader.LogFileReadLogServerCall, startpos int64, numEntries int32, follow bool) (int64, error) {
+	ctx.VI(1).Infof("%v.ReadLog(%v, %v, %v)", i.suffix, startpos, numEntries, follow)
+	fname, err := translateNameToFilename(i.root, i.suffix)
+	if err != nil {
+		return 0, err
+	}
+	f, err := os.Open(fname)
+	if err != nil {
+		if os.IsNotExist(err) {
+			return 0, verror.New(verror.ErrNoExist, ctx, fname)
+		}
+		return 0, verror.New(errOperationFailed, ctx, fname)
+	}
+	reader := newFollowReader(ctx, f, startpos, follow)
+	if numEntries == logreader.AllEntries {
+		numEntries = int32(math.MaxInt32)
+	}
+	for n := int32(0); n < numEntries; n++ {
+		line, offset, err := reader.readLine()
+		if err == io.EOF && n > 0 {
+			return reader.tell(), nil
+		}
+		if err == io.EOF {
+			return reader.tell(), verror.NewErrEndOfFile(ctx)
+		}
+		if err != nil {
+			return reader.tell(), verror.New(errOperationFailed, ctx, fname)
+		}
+		if err := call.SendStream().Send(logreader.LogEntry{Position: offset, Line: line}); err != nil {
+			return reader.tell(), err
+		}
+	}
+	return reader.tell(), nil
+}
+
+// GlobChildren__ returns the list of files in a directory on a stream.
+// The list is empty if the object is a file.
+func (i *logfileService) GlobChildren__(ctx *context.T, call rpc.GlobChildrenServerCall, m *glob.Element) error {
+	ctx.VI(1).Infof("%v.GlobChildren__()", i.suffix)
+	dirName, err := translateNameToFilename(i.root, i.suffix)
+	if err != nil {
+		return err
+	}
+	stat, err := os.Stat(dirName)
+	if err != nil {
+		if os.IsNotExist(err) {
+			return verror.New(verror.ErrNoExist, ctx, dirName)
+		}
+		return verror.New(errOperationFailed, ctx, dirName)
+	}
+	if !stat.IsDir() {
+		return nil
+	}
+
+	f, err := os.Open(dirName)
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+	for {
+		fi, err := f.Readdir(100)
+		if err == io.EOF {
+			break
+		}
+		if err != nil {
+			return err
+		}
+		for _, file := range fi {
+			name := file.Name()
+			if m.Match(name) {
+				call.SendStream().Send(naming.GlobChildrenReplyName{Value: name})
+			}
+		}
+	}
+	return nil
+}
diff --git a/services/internal/logreaderlib/logfile_test.go b/services/internal/logreaderlib/logfile_test.go
new file mode 100644
index 0000000..aeda437
--- /dev/null
+++ b/services/internal/logreaderlib/logfile_test.go
@@ -0,0 +1,194 @@
+// 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 logreaderlib_test
+
+import (
+	"io/ioutil"
+	"os"
+	"path"
+	"testing"
+
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/security"
+	"v.io/v23/services/logreader"
+	"v.io/v23/verror"
+	"v.io/x/ref/lib/xrpc"
+	"v.io/x/ref/services/internal/logreaderlib"
+	"v.io/x/ref/test"
+
+	_ "v.io/x/ref/runtime/factories/generic"
+)
+
+//go:generate v23 test generate
+
+type logFileDispatcher struct {
+	root string
+}
+
+func (d *logFileDispatcher) Lookup(_ *context.T, suffix string) (interface{}, security.Authorizer, error) {
+	return logreaderlib.NewLogFileService(d.root, suffix), nil, nil
+}
+
+func writeAndSync(t *testing.T, w *os.File, s string) {
+	if _, err := w.WriteString(s); err != nil {
+		t.Fatalf("w.WriteString failed: %v", err)
+	}
+	if err := w.Sync(); err != nil {
+		t.Fatalf("w.Sync failed: %v", err)
+	}
+}
+
+func TestReadLogImplNoFollow(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	workdir, err := ioutil.TempDir("", "logreadertest")
+	if err != nil {
+		t.Fatalf("ioutil.TempDir: %v", err)
+	}
+	defer os.RemoveAll(workdir)
+	server, err := xrpc.NewDispatchingServer(ctx, "", &logFileDispatcher{workdir})
+	if err != nil {
+		t.Fatalf("NewDispatchingServer failed: %v", err)
+	}
+	endpoint := server.Status().Endpoints[0].String()
+	const testFile = "mylogfile.INFO"
+	writer, err := os.Create(path.Join(workdir, testFile))
+	if err != nil {
+		t.Fatalf("Create failed: %v", err)
+	}
+
+	tests := []string{
+		"Hello World!",
+		"Life is too short",
+		"Have fun",
+		"Play hard",
+		"Break something",
+		"Fix it later",
+	}
+	for _, s := range tests {
+		writeAndSync(t, writer, s+"\n")
+	}
+
+	// Try to access a file that doesn't exist.
+	lf := logreader.LogFileClient(naming.JoinAddressName(endpoint, "doesntexist"))
+	_, err = lf.Size(ctx)
+	if expected := verror.ErrNoExist.ID; verror.ErrorID(err) != expected {
+		t.Errorf("unexpected error value, got %v, want: %v", err, expected)
+	}
+
+	// Try to access a file that does exist.
+	lf = logreader.LogFileClient(naming.JoinAddressName(endpoint, testFile))
+	_, err = lf.Size(ctx)
+	if err != nil {
+		t.Errorf("Size failed: %v", err)
+	}
+
+	// Read without follow.
+	stream, err := lf.ReadLog(ctx, 0, logreader.AllEntries, false)
+	if err != nil {
+		t.Errorf("ReadLog failed: %v", err)
+	}
+	rStream := stream.RecvStream()
+	expectedPosition := int64(0)
+	for count := 0; rStream.Advance(); count++ {
+		entry := rStream.Value()
+		if entry.Position != expectedPosition {
+			t.Errorf("unexpected position. Got %v, want %v", entry.Position, expectedPosition)
+		}
+		if expected := tests[count]; entry.Line != expected {
+			t.Errorf("unexpected content. Got %q, want %q", entry.Line, expected)
+		}
+		expectedPosition += int64(len(entry.Line)) + 1
+	}
+
+	if err := rStream.Err(); err != nil {
+		t.Errorf("unexpected stream error: %v", rStream.Err())
+	}
+	offset, err := stream.Finish()
+	if err != nil {
+		t.Errorf("Finish failed: %v", err)
+	}
+	if offset != expectedPosition {
+		t.Errorf("unexpected offset. Got %q, want %q", offset, expectedPosition)
+	}
+
+	// Read with follow from EOF (where the previous read ended).
+	stream, err = lf.ReadLog(ctx, offset, logreader.AllEntries, false)
+	if err != nil {
+		t.Errorf("ReadLog failed: %v", err)
+	}
+	_, err = stream.Finish()
+	if verror.ErrorID(err) != verror.ErrEndOfFile.ID {
+		t.Errorf("unexpected error, got %#v, want EOF", err)
+	}
+}
+
+func TestReadLogImplWithFollow(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	workdir, err := ioutil.TempDir("", "logreadertest")
+	if err != nil {
+		t.Fatalf("ioutil.TempDir: %v", err)
+	}
+	defer os.RemoveAll(workdir)
+	server, err := xrpc.NewDispatchingServer(ctx, "", &logFileDispatcher{workdir})
+	if err != nil {
+		t.Fatalf("NewDispatchingServer failed: %v", err)
+	}
+	endpoint := server.Status().Endpoints[0].String()
+
+	const testFile = "mylogfile.INFO"
+	writer, err := os.Create(path.Join(workdir, testFile))
+	if err != nil {
+		t.Fatalf("Create failed: %v", err)
+	}
+
+	tests := []string{
+		"Hello World!",
+		"Life is too short",
+		"Have fun",
+		"Play hard",
+		"Break something",
+		"Fix it later",
+	}
+
+	lf := logreader.LogFileClient(naming.JoinAddressName(endpoint, testFile))
+	_, err = lf.Size(ctx)
+	if err != nil {
+		t.Errorf("Size failed: %v", err)
+	}
+
+	// Read with follow.
+	stream, err := lf.ReadLog(ctx, 0, int32(len(tests)), true)
+	if err != nil {
+		t.Errorf("ReadLog failed: %v", err)
+	}
+	rStream := stream.RecvStream()
+	writeAndSync(t, writer, tests[0]+"\n")
+	for count, pos := 0, int64(0); rStream.Advance(); count++ {
+		entry := rStream.Value()
+		if entry.Position != pos {
+			t.Errorf("unexpected position. Got %v, want %v", entry.Position, pos)
+		}
+		if expected := tests[count]; entry.Line != expected {
+			t.Errorf("unexpected content. Got %q, want %q", entry.Line, expected)
+		}
+		pos += int64(len(entry.Line)) + 1
+		if count+1 < len(tests) {
+			writeAndSync(t, writer, tests[count+1]+"\n")
+		}
+	}
+
+	if err := rStream.Err(); err != nil {
+		t.Errorf("unexpected stream error: %v", rStream.Err())
+	}
+	_, err = stream.Finish()
+	if err != nil {
+		t.Errorf("Finish failed: %v", err)
+	}
+}
diff --git a/services/internal/logreaderlib/reader.go b/services/internal/logreaderlib/reader.go
new file mode 100644
index 0000000..724409b
--- /dev/null
+++ b/services/internal/logreaderlib/reader.go
@@ -0,0 +1,98 @@
+// 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 logreaderlib
+
+import (
+	"bytes"
+	"io"
+	"strings"
+	"time"
+
+	"v.io/v23/context"
+	"v.io/v23/verror"
+)
+
+// followReader implements the functionality of io.Reader, plus:
+// - it can block for new input when the end of the file is reached, and
+// - it aborts when the parent RPC is canceled.
+type followReader struct {
+	reader io.ReadSeeker
+	ctx    *context.T
+	offset int64
+	follow bool
+	err    error
+	buf    []byte
+}
+
+// newFollowReader is the factory for followReader.
+func newFollowReader(ctx *context.T, reader io.ReadSeeker, startpos int64, follow bool) *followReader {
+	_, err := reader.Seek(startpos, 0)
+	return &followReader{
+		reader: reader,
+		ctx:    ctx,
+		offset: startpos,
+		follow: follow,
+		err:    err,
+	}
+}
+
+// tell returns the offset where the next read will start.
+func (f *followReader) tell() int64 {
+	return f.offset
+}
+
+func (f *followReader) read(b []byte) (int, error) {
+	if f.err != nil {
+		return 0, f.err
+	}
+	for {
+		if f.ctx != nil {
+			select {
+			case <-f.ctx.Done():
+				return 0, verror.New(verror.ErrCanceled, f.ctx)
+			default:
+			}
+		}
+		n, err := f.reader.Read(b)
+		if n == 0 && err == nil {
+			// According to http://golang.org/pkg/io/#Reader, this
+			// weird case should be treated as a no-op.
+			continue
+		}
+		if n > 0 && err == io.EOF {
+			err = nil
+		}
+		if err == io.EOF && f.follow {
+			time.Sleep(500 * time.Millisecond)
+			continue
+		}
+		return n, err
+	}
+}
+
+// readLine returns a whole line as a string, and the offset where it starts in
+// the file. White spaces are removed from the beginning and the end of the line.
+// If readLine returns an error, the other two return values should be discarded.
+func (f *followReader) readLine() (string, int64, error) {
+	startOff := f.offset
+	var off int
+	for {
+		off = bytes.IndexByte(f.buf, '\n') + 1
+		if off != 0 {
+			break
+		}
+		b := make([]byte, 2048)
+		n, err := f.read(b)
+		if n > 0 {
+			f.buf = append(f.buf, b[:n]...)
+			continue
+		}
+		return "", 0, err
+	}
+	line := f.buf[:off-1] // -1 to remove the trailing \n
+	f.buf = f.buf[off:]
+	f.offset += int64(off)
+	return strings.TrimSpace(string(line)), startOff, nil
+}
diff --git a/services/internal/logreaderlib/reader_test.go b/services/internal/logreaderlib/reader_test.go
new file mode 100644
index 0000000..fec4faa
--- /dev/null
+++ b/services/internal/logreaderlib/reader_test.go
@@ -0,0 +1,140 @@
+// 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 logreaderlib
+
+import (
+	"io"
+	"io/ioutil"
+	"os"
+	"testing"
+	"time"
+)
+
+func writeAndSync(t *testing.T, w *os.File, s string) {
+	if _, err := w.WriteString(s); err != nil {
+		t.Fatalf("w.WriteString failed: %v", err)
+	}
+	if err := w.Sync(); err != nil {
+		t.Fatalf("w.Sync failed: %v", err)
+	}
+}
+
+func TestFollowReaderNoFollow(t *testing.T) {
+	w, err := ioutil.TempFile("", "reader-test-")
+	if err != nil {
+		t.Fatalf("ioutil.TempFile: unexpected error: %v", err)
+	}
+	defer w.Close()
+	defer os.Remove(w.Name())
+
+	tests := []string{
+		"Hello world",
+		"Hello world Two",
+		"Hello world Three",
+	}
+	for _, s := range tests {
+		writeAndSync(t, w, s+"\n")
+	}
+	writeAndSync(t, w, "Partial line with no newline")
+
+	r, err := os.Open(w.Name())
+	if err != nil {
+		t.Fatalf("os.Open: unexpected error: %v", err)
+	}
+	defer r.Close()
+
+	f := newFollowReader(nil, r, 0, false)
+	if f == nil {
+		t.Fatalf("newFollowReader return nil")
+	}
+
+	var expectedOffset int64
+	for _, s := range tests {
+		line, offset, err := f.readLine()
+		if err != nil {
+			t.Errorf("readLine, unexpected error: %v", err)
+		}
+		if line != s {
+			t.Errorf("unexpected result. Got %v, want %v", line, s)
+		}
+		if offset != expectedOffset {
+			t.Errorf("unexpected result. Got %v, want %v", offset, expectedOffset)
+		}
+		expectedOffset += int64(len(s)) + 1
+	}
+
+	// Attempt to read the partial line.
+	if line, _, err := f.readLine(); line != "" || err != io.EOF {
+		t.Errorf("unexpected result. Got %v:%v, want \"\":EOF", line, err)
+	}
+}
+
+func sleep() {
+	time.Sleep(500 * time.Millisecond)
+}
+
+func TestFollowReaderWithFollow(t *testing.T) {
+	w, err := ioutil.TempFile("", "reader-test-")
+	if err != nil {
+		t.Fatalf("ioutil.TempFile: unexpected error: %v", err)
+	}
+	defer os.Remove(w.Name())
+
+	tests := []string{
+		"Hello world",
+		"Hello world Two",
+		"Hello world Three",
+	}
+	go func() {
+		for _, s := range tests {
+			sleep()
+			writeAndSync(t, w, s+"\n")
+		}
+		sleep()
+		writeAndSync(t, w, "Hello ")
+		sleep()
+		writeAndSync(t, w, "world ")
+		sleep()
+		writeAndSync(t, w, "Four\n")
+		w.Close()
+	}()
+
+	r, err := os.Open(w.Name())
+	if err != nil {
+		t.Fatalf("os.Open: unexpected error: %v", err)
+	}
+	defer r.Close()
+
+	f := newFollowReader(nil, r, 0, true)
+	if f == nil {
+		t.Fatalf("newFollowReader return nil")
+	}
+
+	var expectedOffset int64
+	for _, s := range tests {
+		line, offset, err := f.readLine()
+		if err != nil {
+			t.Errorf("readLine, unexpected error: %v", err)
+		}
+		if line != s {
+			t.Errorf("unexpected result. Got %v, want %v", line, s)
+		}
+		if offset != expectedOffset {
+			t.Errorf("unexpected result. Got %v, want %v", offset, expectedOffset)
+		}
+		expectedOffset += int64(len(s)) + 1
+	}
+
+	line, offset, err := f.readLine()
+	if err != nil {
+		t.Errorf("readLine, unexpected error: %v", err)
+	}
+	if expected := "Hello world Four"; line != expected {
+		t.Errorf("unexpected result. Got %q, want %q", line, expected)
+	}
+	if offset != expectedOffset {
+		t.Errorf("unexpected result. Got %v, want %v", offset, expectedOffset)
+	}
+}
diff --git a/services/internal/logreaderlib/v23_internal_test.go b/services/internal/logreaderlib/v23_internal_test.go
new file mode 100644
index 0000000..c0b54bf
--- /dev/null
+++ b/services/internal/logreaderlib/v23_internal_test.go
@@ -0,0 +1,20 @@
+// 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 logreaderlib
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	os.Exit(m.Run())
+}
diff --git a/services/internal/multipart/multipart.go b/services/internal/multipart/multipart.go
new file mode 100644
index 0000000..af7f986
--- /dev/null
+++ b/services/internal/multipart/multipart.go
@@ -0,0 +1,185 @@
+// 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 multipart implements an http.File that acts as one logical file
+// backed by several physical files (the 'parts').
+package multipart
+
+import (
+	"fmt"
+	"io"
+	"net/http"
+	"os"
+	"time"
+)
+
+var internalErr = fmt.Errorf("internal error")
+
+// NewFile creates the multipart file out of the provided parts.
+// The sizes of the parts are captured at the outset and not updated
+// for the lifetime of the multipart file (any subsequent modifications
+// in the parts will cause Read and Seek to work incorrectly).
+func NewFile(name string, parts []*os.File) (http.File, error) {
+	fileParts := make([]filePart, len(parts))
+	for i, p := range parts {
+		stat, err := p.Stat()
+		if err != nil {
+			return nil, err
+		}
+		size := stat.Size()
+		// TODO(caprita): we can relax this restriction later.
+		if size == 0 {
+			return nil, fmt.Errorf("Part is empty")
+		}
+		fileParts[i] = filePart{file: p, size: size}
+	}
+	return &multipartFile{name: name, parts: fileParts}, nil
+}
+
+type filePart struct {
+	file *os.File
+	size int64
+}
+
+type multipartFile struct {
+	name       string
+	parts      []filePart
+	activePart int
+	partOffset int64
+}
+
+func (m *multipartFile) currPos() (res int64) {
+	for i := 0; i < m.activePart; i++ {
+		res += m.parts[i].size
+	}
+	res += m.partOffset
+	return
+}
+
+func (m *multipartFile) totalSize() (res int64) {
+	for _, p := range m.parts {
+		res += p.size
+	}
+	return
+}
+
+// Readdir is not implemented.
+func (*multipartFile) Readdir(int) ([]os.FileInfo, error) {
+	return nil, fmt.Errorf("Not implemented")
+}
+
+type fileInfo struct {
+	name    string
+	size    int64
+	mode    os.FileMode
+	modTime time.Time
+}
+
+// Name returns the name of the multipart file.
+func (f *fileInfo) Name() string {
+	return f.name
+}
+
+// Size returns the size of the multipart file (the sum of all parts).
+func (f *fileInfo) Size() int64 {
+	return f.size
+}
+
+// Mode is currently hardcoded to 0700.
+func (f *fileInfo) Mode() os.FileMode {
+	return f.mode
+}
+
+// ModTime is set to the current time.
+func (f *fileInfo) ModTime() time.Time {
+	return f.modTime
+}
+
+// IsDir always returns false.
+func (f *fileInfo) IsDir() bool {
+	return false
+}
+
+// Sys always returns nil.
+func (f *fileInfo) Sys() interface{} {
+	return nil
+}
+
+// Stat describes the multipart file.
+func (m *multipartFile) Stat() (os.FileInfo, error) {
+	return &fileInfo{
+		name:    m.name,
+		size:    m.totalSize(),
+		mode:    0700,
+		modTime: time.Now(),
+	}, nil
+}
+
+// Close closes all the parts.
+func (m *multipartFile) Close() error {
+	var lastErr error
+	for _, p := range m.parts {
+		if err := p.file.Close(); err != nil {
+			lastErr = err
+		}
+	}
+	return lastErr
+}
+
+// Read reads from the parts in sequence.
+func (m *multipartFile) Read(buf []byte) (int, error) {
+	if m.activePart >= len(m.parts) {
+		return 0, io.EOF
+	}
+	p := m.parts[m.activePart]
+	n, err := p.file.Read(buf)
+	m.partOffset += int64(n)
+	if m.partOffset > p.size {
+		// Likely, the file has changed.
+		return 0, internalErr
+	}
+	if m.partOffset == p.size {
+		m.activePart++
+		if m.activePart < len(m.parts) {
+			if _, err := m.parts[m.activePart].file.Seek(0, 0); err != nil {
+				return 0, err
+			}
+			m.partOffset = 0
+		}
+	}
+	return n, err
+}
+
+// Seek seeks into the part corresponding to the global offset.
+func (m *multipartFile) Seek(offset int64, whence int) (int64, error) {
+	var target int64
+	switch whence {
+	case 0:
+		target = offset
+	case 1:
+		target = m.currPos() + offset
+	case 2:
+		target = m.totalSize() - offset
+	default:
+		return 0, fmt.Errorf("invalid whence: %d", whence)
+	}
+	if target < 0 || target > m.totalSize() {
+		return 0, fmt.Errorf("invalid offset")
+	}
+	var c int64
+	for i, p := range m.parts {
+		if pSize := p.size; c+pSize <= target {
+			c += pSize
+			continue
+		}
+		m.activePart = i
+		if _, err := p.file.Seek(target-c, 0); err != nil {
+			return 0, err
+		}
+		m.partOffset = target - c
+		return target, nil
+	}
+	// target <= m.totalSize() should ensure this is never reached.
+	return 0, internalErr // Should not be reached.
+}
diff --git a/services/internal/multipart/multipart_test.go b/services/internal/multipart/multipart_test.go
new file mode 100644
index 0000000..d0995d2
--- /dev/null
+++ b/services/internal/multipart/multipart_test.go
@@ -0,0 +1,151 @@
+// 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 multipart_test
+
+import (
+	"io"
+	"io/ioutil"
+	"net/http"
+	"os"
+	"path/filepath"
+	"strconv"
+	"strings"
+	"testing"
+
+	"v.io/x/ref/services/internal/multipart"
+)
+
+func read(t *testing.T, m http.File, thisMuch int) string {
+	buf := make([]byte, thisMuch)
+	bytesRead := 0
+	for {
+		n, err := m.Read(buf[bytesRead:])
+		bytesRead += n
+		if bytesRead == thisMuch {
+			return string(buf)
+		}
+		switch err {
+		case nil:
+		case io.EOF:
+			return string(buf[:bytesRead])
+		default:
+			t.Fatalf("Read failed: %v", err)
+		}
+	}
+}
+
+// TestFile verifies the http.File operations on the multipart file.
+func TestFile(t *testing.T) {
+	contents := []string{"v", "is", "for", "vanadium"}
+	files := make([]*os.File, len(contents))
+	d, err := ioutil.TempDir("", "multiparts")
+	if err != nil {
+		t.Fatalf("TempDir() failed: %v", err)
+	}
+	defer os.RemoveAll(d)
+	contentsSize := 0
+	for i, c := range contents {
+		contentsSize += len(c)
+		fPath := filepath.Join(d, strconv.Itoa(i))
+		if err := ioutil.WriteFile(fPath, []byte(c), 0600); err != nil {
+			t.Fatalf("WriteFile(%v) failed: %v", fPath, err)
+		}
+		var err error
+		if files[i], err = os.Open(fPath); err != nil {
+			t.Fatalf("Open(%v) failed: %v", fPath, err)
+		}
+	}
+	m, err := multipart.NewFile("bunnies", files)
+	if err != nil {
+		t.Fatalf("NewFile failed: %v", err)
+	}
+	defer func() {
+		if err := m.Close(); err != nil {
+			t.Fatalf("Close failed: %v", err)
+		}
+	}()
+	stat, err := m.Stat()
+	if err != nil {
+		t.Fatalf("Stat failed: %v", err)
+	}
+	if want, got := "bunnies", stat.Name(); want != got {
+		t.Fatalf("Name returned %s, expected %s", got, want)
+	}
+	if want, got := int64(contentsSize), stat.Size(); want != got {
+		t.Fatalf("Size returned %d, expected %d", got, want)
+	}
+	if want, got := strings.Join(contents, ""), read(t, m, 1024); want != got {
+		t.Fatalf("Read %v, wanted %v instead", got, want)
+	}
+	if want, got := "", read(t, m, 1024); want != got {
+		t.Fatalf("Read %v, wanted %v instead", got, want)
+	}
+	if pos, err := m.Seek(0, 0); err != nil {
+		t.Fatalf("Seek failed: %v", err)
+	} else if want, got := int64(0), pos; want != got {
+		t.Fatalf("Pos is %d, wanted %d", got, want)
+	}
+	if want, got := strings.Join(contents, ""), read(t, m, 1024); want != got {
+		t.Fatalf("Read %v, wanted %v instead", got, want)
+	}
+	if pos, err := m.Seek(0, 0); err != nil {
+		t.Fatalf("Seek failed: %v", err)
+	} else if want, got := int64(0), pos; want != got {
+		t.Fatalf("Pos is %d, wanted %d", got, want)
+	}
+	for _, c := range contents {
+		if want, got := c, read(t, m, len(c)); want != got {
+			t.Fatalf("Read %v, wanted %v instead", got, want)
+		}
+	}
+	if want, got := "", read(t, m, 1024); want != got {
+		t.Fatalf("Read %v, wanted %v instead", got, want)
+	}
+	if pos, err := m.Seek(1, 0); err != nil {
+		t.Fatalf("Seek failed: %v", err)
+	} else if want, got := int64(1), pos; want != got {
+		t.Fatalf("Pos is %d, wanted %d", got, want)
+	}
+	if want, got := "isfo", read(t, m, 4); want != got {
+		t.Fatalf("Read %v, wanted %v instead", got, want)
+	}
+	if pos, err := m.Seek(2, 1); err != nil {
+		t.Fatalf("Seek failed: %v", err)
+	} else if want, got := int64(7), pos; want != got {
+		t.Fatalf("Pos is %d, wanted %d", got, want)
+	}
+	if want, got := "anadi", read(t, m, 5); want != got {
+		t.Fatalf("Read %v, wanted %v instead", got, want)
+	}
+	if _, err := m.Seek(100, 1); err == nil {
+		t.Fatalf("Seek expected to fail")
+	}
+	if want, got := "u", read(t, m, 1); want != got {
+		t.Fatalf("Read %v, wanted %v instead", got, want)
+	}
+	if pos, err := m.Seek(8, 2); err != nil {
+		t.Fatalf("Seek failed: %v", err)
+	} else if want, got := int64(6), pos; want != got {
+		t.Fatalf("Pos is %d, wanted %d", got, want)
+	}
+	if want, got := "vanad", read(t, m, 5); want != got {
+		t.Fatalf("Read %v, wanted %v instead", got, want)
+	}
+	if _, err := m.Seek(100, 2); err == nil {
+		t.Fatalf("Seek expected to fail")
+	}
+	if pos, err := m.Seek(9, 2); err != nil {
+		t.Fatalf("Seek failed: %v", err)
+	} else if want, got := int64(5), pos; want != got {
+		t.Fatalf("Pos is %d, wanted %d", got, want)
+	}
+	if want, got := "rvana", read(t, m, 5); want != got {
+		t.Fatalf("Read %v, wanted %v instead", got, want)
+	}
+
+	// TODO(caprita): Add some auto-generated test cases where we seek/read
+	// using various combinations of indices.  These can be exhaustive or
+	// randomized, the idea is to get better coverage.
+}
diff --git a/services/internal/packages/packages.go b/services/internal/packages/packages.go
new file mode 100644
index 0000000..1656401
--- /dev/null
+++ b/services/internal/packages/packages.go
@@ -0,0 +1,284 @@
+// 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 packages provides functionality to install ZIP and TAR packages.
+package packages
+
+import (
+	"archive/tar"
+	"archive/zip"
+	"compress/bzip2"
+	"compress/gzip"
+	"encoding/json"
+	"io"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"v.io/v23/services/repository"
+	"v.io/v23/verror"
+)
+
+const (
+	defaultType    = "application/octet-stream"
+	createDirMode  = 0755
+	createFileMode = 0644
+)
+
+var typemap = map[string]repository.MediaInfo{
+	".zip":     repository.MediaInfo{Type: "application/zip"},
+	".tar":     repository.MediaInfo{Type: "application/x-tar"},
+	".tgz":     repository.MediaInfo{Type: "application/x-tar", Encoding: "gzip"},
+	".tar.gz":  repository.MediaInfo{Type: "application/x-tar", Encoding: "gzip"},
+	".tbz2":    repository.MediaInfo{Type: "application/x-tar", Encoding: "bzip2"},
+	".tb2":     repository.MediaInfo{Type: "application/x-tar", Encoding: "bzip2"},
+	".tbz":     repository.MediaInfo{Type: "application/x-tar", Encoding: "bzip2"},
+	".tar.bz2": repository.MediaInfo{Type: "application/x-tar", Encoding: "bzip2"},
+}
+
+const pkgPath = "v.io/x/ref/services/internal/packages"
+
+var (
+	errBadMediaType    = verror.Register(pkgPath+".errBadMediaType", verror.NoRetry, "{1:}{2:} unsupported media type{:_}")
+	errMkDirFailed     = verror.Register(pkgPath+".errMkDirFailed", verror.NoRetry, "{1:}{2:} os.Mkdir({3}) failed{:_}")
+	errFailedToExtract = verror.Register(pkgPath+".errFailedToExtract", verror.NoRetry, "{1:}{2:} failed to extract file {3} outside of install directory{:_}")
+	errBadFileSize     = verror.Register(pkgPath+".errBadFileSize", verror.NoRetry, "{1:}{2:} file size doesn't match for {3}: {4} != {5}{:_}")
+	errBadEncoding     = verror.Register(pkgPath+".errBadEncoding", verror.NoRetry, "{1:}{2:} unsupported encoding{:_}")
+)
+
+// MediaInfoForFileName returns the MediaInfo based on the file's extension.
+func MediaInfoForFileName(fileName string) repository.MediaInfo {
+	fileName = strings.ToLower(fileName)
+	for k, v := range typemap {
+		if strings.HasSuffix(fileName, k) {
+			return v
+		}
+	}
+	return repository.MediaInfo{Type: defaultType}
+}
+
+func copyFile(src, dst string) error {
+	s, err := os.Open(src)
+	if err != nil {
+		return err
+	}
+	defer s.Close()
+	d, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY, createFileMode)
+	if err != nil {
+		return err
+	}
+	defer d.Close()
+	if _, err = io.Copy(d, s); err != nil {
+		return err
+	}
+	return d.Sync()
+}
+
+// Install installs a package in the given destination. If the package is a TAR
+// or ZIP archive, the destination becomes a directory where the archive content
+// is extracted.  Otherwise, the package file itself is copied to the
+// destination.
+func Install(pkgFile, destination string) error {
+	mediaInfo, err := LoadMediaInfo(pkgFile)
+	if err != nil {
+		return err
+	}
+	switch mediaInfo.Type {
+	case "application/x-tar":
+		return extractTar(pkgFile, mediaInfo.Encoding, destination)
+	case "application/zip":
+		return extractZip(pkgFile, destination)
+	case defaultType:
+		return copyFile(pkgFile, destination)
+	default:
+		return verror.New(errBadMediaType, nil, mediaInfo.Type)
+	}
+}
+
+// LoadMediaInfo returns the MediaInfo for the given package file.
+func LoadMediaInfo(pkgFile string) (repository.MediaInfo, error) {
+	jInfo, err := ioutil.ReadFile(pkgFile + ".__info")
+	if err != nil {
+		return repository.MediaInfo{}, err
+	}
+	var info repository.MediaInfo
+	if err := json.Unmarshal(jInfo, &info); err != nil {
+		return repository.MediaInfo{}, err
+	}
+	return info, nil
+}
+
+// SaveMediaInfo saves the media info for a package.
+func SaveMediaInfo(pkgFile string, mediaInfo repository.MediaInfo) error {
+	jInfo, err := json.Marshal(mediaInfo)
+	if err != nil {
+		return err
+	}
+	infoFile := pkgFile + ".__info"
+	if err := ioutil.WriteFile(infoFile, jInfo, os.FileMode(0600)); err != nil {
+		return err
+	}
+	return nil
+}
+
+// CreateZip creates a package from the files in the source directory. The
+// created package is a Zip file.
+func CreateZip(zipFile, sourceDir string) error {
+	z, err := os.OpenFile(zipFile, os.O_CREATE|os.O_WRONLY, os.FileMode(0644))
+	if err != nil {
+		return err
+	}
+	defer z.Close()
+	w := zip.NewWriter(z)
+	if err := filepath.Walk(sourceDir, func(path string, info os.FileInfo, err error) error {
+		if err != nil {
+			return err
+		}
+		if sourceDir == path {
+			return nil
+		}
+		fh, err := zip.FileInfoHeader(info)
+		if err != nil {
+			return err
+		}
+		fh.Method = zip.Deflate
+		fh.Name, _ = filepath.Rel(sourceDir, path)
+		hdr, err := w.CreateHeader(fh)
+		if err != nil {
+			return err
+		}
+		if !info.IsDir() {
+			content, err := ioutil.ReadFile(path)
+			if err != nil {
+				return err
+			}
+			if _, err = hdr.Write(content); err != nil {
+				return err
+			}
+		}
+		return nil
+	}); err != nil {
+		return err
+	}
+	if err := w.Close(); err != nil {
+		return err
+	}
+	if err := SaveMediaInfo(zipFile, repository.MediaInfo{Type: "application/zip"}); err != nil {
+		return err
+	}
+	return nil
+}
+
+func extractZip(zipFile, installDir string) error {
+	if err := os.Mkdir(installDir, os.FileMode(createDirMode)); err != nil {
+		return verror.New(errMkDirFailed, nil, installDir, err)
+	}
+	zr, err := zip.OpenReader(zipFile)
+	if err != nil {
+		return err
+	}
+	for _, file := range zr.File {
+		fi := file.FileInfo()
+		name := filepath.Join(installDir, file.Name)
+		if !strings.HasPrefix(name, installDir) {
+			return verror.New(errFailedToExtract, nil, file.Name)
+		}
+		if fi.IsDir() {
+			if err := os.MkdirAll(name, os.FileMode(createDirMode)); err != nil && !os.IsExist(err) {
+				return err
+			}
+			continue
+		}
+		in, err := file.Open()
+		if err != nil {
+			return err
+		}
+		parentName := filepath.Dir(name)
+		if err := os.MkdirAll(parentName, os.FileMode(createDirMode)); err != nil {
+			return err
+		}
+		out, err := os.OpenFile(name, os.O_CREATE|os.O_WRONLY, os.FileMode(createFileMode))
+		if err != nil {
+			in.Close()
+			return err
+		}
+		nbytes, err := io.Copy(out, in)
+		in.Close()
+		out.Close()
+		if err != nil {
+			return err
+		}
+		if nbytes != fi.Size() {
+			return verror.New(errBadFileSize, nil, fi.Name(), nbytes, fi.Size())
+		}
+	}
+	return nil
+}
+
+func extractTar(pkgFile string, encoding string, installDir string) error {
+	if err := os.Mkdir(installDir, os.FileMode(createDirMode)); err != nil {
+		return verror.New(errMkDirFailed, nil, installDir, err)
+	}
+	f, err := os.Open(pkgFile)
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+
+	var reader io.Reader
+	switch encoding {
+	case "":
+		reader = f
+	case "gzip":
+		var err error
+		if reader, err = gzip.NewReader(f); err != nil {
+			return err
+		}
+	case "bzip2":
+		reader = bzip2.NewReader(f)
+	default:
+		return verror.New(errBadEncoding, nil, encoding)
+	}
+
+	tr := tar.NewReader(reader)
+	for {
+		hdr, err := tr.Next()
+		if err == io.EOF {
+			return nil
+		}
+		if err != nil {
+			return err
+		}
+		name := filepath.Join(installDir, hdr.Name)
+		if !strings.HasPrefix(name, installDir) {
+			return verror.New(errFailedToExtract, nil, hdr.Name)
+		}
+		// Regular file
+		if hdr.Typeflag == tar.TypeReg {
+			out, err := os.OpenFile(name, os.O_CREATE|os.O_WRONLY, os.FileMode(createFileMode))
+			if err != nil {
+				return err
+			}
+			nbytes, err := io.Copy(out, tr)
+			out.Close()
+			if err != nil {
+				return err
+			}
+			if nbytes != hdr.Size {
+				return verror.New(errBadFileSize, nil, hdr.Name, nbytes, hdr.Size)
+			}
+			continue
+		}
+		// Directory
+		if hdr.Typeflag == tar.TypeDir {
+			if err := os.Mkdir(name, os.FileMode(createDirMode)); err != nil && !os.IsExist(err) {
+				return err
+			}
+			continue
+		}
+		// Skip unsupported types
+		// TODO(rthellend): Consider adding support for Symlink.
+	}
+}
diff --git a/services/internal/packages/packages_test.go b/services/internal/packages/packages_test.go
new file mode 100644
index 0000000..a6b130c
--- /dev/null
+++ b/services/internal/packages/packages_test.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 packages_test
+
+import (
+	"archive/tar"
+	"compress/gzip"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"reflect"
+	"sort"
+	"testing"
+
+	"v.io/v23/services/repository"
+
+	"v.io/x/ref/services/internal/packages"
+)
+
+func TestInstall(t *testing.T) {
+	workdir, err := ioutil.TempDir("", "packages-test-")
+	if err != nil {
+		t.Fatalf("ioutil.TempDir failed: %v", err)
+	}
+	defer os.RemoveAll(workdir)
+	srcdir := filepath.Join(workdir, "src")
+	dstdir := filepath.Join(workdir, "dst")
+	createFiles(t, srcdir)
+
+	zipfile := filepath.Join(workdir, "archivezip")
+	tarfile := filepath.Join(workdir, "archivetar")
+	tgzfile := filepath.Join(workdir, "archivetgz")
+
+	makeZip(t, zipfile, srcdir)
+	makeTar(t, tarfile, srcdir)
+	doGzip(t, tarfile, tgzfile)
+
+	binfile := filepath.Join(workdir, "binfile")
+	ioutil.WriteFile(binfile, []byte("This is a binary file"), os.FileMode(0644))
+	ioutil.WriteFile(binfile+".__info", []byte(`{"type":"application/octet-stream"}`), os.FileMode(0644))
+
+	expected := []string{
+		"a perm:700",
+		"a/b perm:700",
+		"a/b/xyzzy.txt perm:600",
+		"a/bar.txt perm:600",
+		"a/foo.txt perm:600",
+	}
+	for _, file := range []string{zipfile, tarfile, tgzfile} {
+		if err := packages.Install(file, dstdir); err != nil {
+			t.Errorf("packages.Install failed for %q: %v", file, err)
+		}
+		files := scanDir(dstdir)
+		if !reflect.DeepEqual(files, expected) {
+			t.Errorf("unexpected result for %q: got %q, want %q", file, files, expected)
+		}
+		if err := os.RemoveAll(dstdir); err != nil {
+			t.Fatalf("os.RemoveAll(%q) failed: %v", dstdir, err)
+		}
+	}
+	dstfile := filepath.Join(workdir, "dstfile")
+	if err := packages.Install(binfile, dstfile); err != nil {
+		t.Errorf("packages.Install failed for %q: %v", binfile, err)
+	}
+	contents, err := ioutil.ReadFile(dstfile)
+	if err != nil {
+		t.Errorf("ReadFile(%q) failed: %v", dstfile, err)
+	}
+	if want, got := "This is a binary file", string(contents); want != got {
+		t.Errorf("unexpected result for %q: got %q, want %q", binfile, got, want)
+	}
+}
+
+func TestMediaInfo(t *testing.T) {
+	testcases := []struct {
+		filename string
+		expected repository.MediaInfo
+	}{
+		{"foo.zip", repository.MediaInfo{Type: "application/zip"}},
+		{"foo.ZIP", repository.MediaInfo{Type: "application/zip"}},
+		{"foo.tar", repository.MediaInfo{Type: "application/x-tar"}},
+		{"foo.TAR", repository.MediaInfo{Type: "application/x-tar"}},
+		{"foo.tgz", repository.MediaInfo{Type: "application/x-tar", Encoding: "gzip"}},
+		{"FOO.TAR.GZ", repository.MediaInfo{Type: "application/x-tar", Encoding: "gzip"}},
+		{"foo.tbz2", repository.MediaInfo{Type: "application/x-tar", Encoding: "bzip2"}},
+		{"foo.tar.bz2", repository.MediaInfo{Type: "application/x-tar", Encoding: "bzip2"}},
+		{"foo", repository.MediaInfo{Type: "application/octet-stream"}},
+	}
+	for _, tc := range testcases {
+		if got := packages.MediaInfoForFileName(tc.filename); !reflect.DeepEqual(got, tc.expected) {
+			t.Errorf("unexpected result for %q: got %v, want %v", tc.filename, got, tc.expected)
+		}
+	}
+}
+
+func createFiles(t *testing.T, dir string) {
+	if err := os.Mkdir(dir, os.FileMode(0755)); err != nil {
+		t.Fatalf("os.Mkdir(%q) failed: %v", dir, err)
+	}
+	dirs := []string{"a", "a/b"}
+	for _, d := range dirs {
+		fullname := filepath.Join(dir, d)
+		if err := os.Mkdir(fullname, os.FileMode(0755)); err != nil {
+			t.Fatalf("os.Mkdir(%q) failed: %v", fullname, err)
+		}
+	}
+	files := []string{"a/foo.txt", "a/bar.txt", "a/b/xyzzy.txt"}
+	for _, f := range files {
+		fullname := filepath.Join(dir, f)
+		if err := ioutil.WriteFile(fullname, []byte(f), os.FileMode(0644)); err != nil {
+			t.Fatalf("ioutil.WriteFile(%q) failed: %v", fullname, err)
+		}
+	}
+}
+
+func makeZip(t *testing.T, zipfile, dir string) {
+	if err := packages.CreateZip(zipfile, dir); err != nil {
+		t.Fatalf("packages.CreateZip failed: %v", err)
+	}
+}
+
+func makeTar(t *testing.T, tarfile, dir string) {
+	tf, err := os.OpenFile(tarfile, os.O_CREATE|os.O_WRONLY, os.FileMode(0644))
+	if err != nil {
+		t.Fatalf("os.OpenFile(%q) failed: %v", tarfile, err)
+	}
+	defer tf.Close()
+
+	tw := tar.NewWriter(tf)
+	filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
+		if err != nil {
+			t.Fatalf("Walk(%q) error: %v", dir, err)
+		}
+		if dir == path {
+			return nil
+		}
+		hdr, err := tar.FileInfoHeader(info, "")
+		if err != nil {
+			t.Fatalf("tar.FileInfoHeader failed: %v", err)
+		}
+		hdr.Name, _ = filepath.Rel(dir, path)
+		if err := tw.WriteHeader(hdr); err != nil {
+			t.Fatalf("tw.WriteHeader failed: %v", err)
+		}
+		if !info.IsDir() {
+			content, err := ioutil.ReadFile(path)
+			if err != nil {
+				t.Fatalf("ioutil.ReadFile(%q) failed: %v", path, err)
+			}
+			if _, err := tw.Write(content); err != nil {
+				t.Fatalf("tw.Write failed: %v", err)
+			}
+		}
+		return nil
+	})
+	if err := tw.Close(); err != nil {
+		t.Fatalf("tw.Close failed: %v", err)
+	}
+	if err := ioutil.WriteFile(tarfile+".__info", []byte(`{"type":"application/x-tar"}`), os.FileMode(0644)); err != nil {
+		t.Fatalf("ioutil.WriteFile() failed: %v", err)
+	}
+}
+
+func doGzip(t *testing.T, infile, outfile string) {
+	in, err := os.Open(infile)
+	if err != nil {
+		t.Fatalf("os.Open(%q) failed: %v", infile, err)
+	}
+	defer in.Close()
+	out, err := os.OpenFile(outfile, os.O_CREATE|os.O_WRONLY, os.FileMode(0644))
+	if err != nil {
+		t.Fatalf("os.OpenFile(%q) failed: %v", outfile, err)
+	}
+	defer out.Close()
+	writer := gzip.NewWriter(out)
+	defer writer.Close()
+	if _, err := io.Copy(writer, in); err != nil {
+		t.Fatalf("io.Copy() failed: %v", err)
+	}
+
+	info, err := packages.LoadMediaInfo(infile)
+	if err != nil {
+		t.Fatalf("LoadMediaInfo(%q) failed: %v", infile, err)
+	}
+	info.Encoding = "gzip"
+	if err := packages.SaveMediaInfo(outfile, info); err != nil {
+		t.Fatalf("SaveMediaInfo(%v) failed: %v", outfile, err)
+	}
+}
+
+func scanDir(root string) []string {
+	files := []string{}
+	filepath.Walk(root, func(path string, info os.FileInfo, _ error) error {
+		if root == path {
+			return nil
+		}
+		rel, _ := filepath.Rel(root, path)
+		perm := info.Mode() & 0700
+		files = append(files, fmt.Sprintf("%s perm:%o", rel, perm))
+		return nil
+	})
+	sort.Strings(files)
+	return files
+}
diff --git a/services/internal/pathperms/hierarchical_authorizer.go b/services/internal/pathperms/hierarchical_authorizer.go
new file mode 100644
index 0000000..4fd7c60
--- /dev/null
+++ b/services/internal/pathperms/hierarchical_authorizer.go
@@ -0,0 +1,102 @@
+// 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 pathperms
+
+import (
+	"fmt"
+
+	"v.io/v23/context"
+	"v.io/v23/security"
+	"v.io/v23/security/access"
+)
+
+// hierarchicalAuthorizer contains the state needed to implement
+// hierarchical authorization in the Authorize method.
+type hierarchicalAuthorizer struct {
+	rootDir, childDir      string
+	get                    PermsGetter
+	emptyChildPermsMethods map[string]bool
+}
+
+// PermsGetter defines an abstract interface that a customer of
+// NewHierarchicalAuthorizer can use to obtain the PermissionsAuthorizer
+// instances that it needs to construct a hierarchicalAuthorizer.
+type PermsGetter interface {
+	// PermsForPath has two successful outcomes: either returning a valid
+	// Permissions object or a boolean status true indicating that the
+	// Permissions object is intentionally not present. Finally, it returns an
+	// error if anything has gone wrong.
+	PermsForPath(ctx *context.T, path string) (access.Permissions, bool, error)
+}
+
+// NewHierarchicalAuthorizer creates a new hierarchicalAuthorizer: one
+// that implements a "root" like concept: admin rights at the root of
+// a server can invoke RPCs regardless of permissions set on child objects.
+//
+// If the root permissions are not set, the authorizer behaves like the
+// DefaultAuthorizer.
+//
+// If the child permissions are not set, the authorizer uses the permissions set
+// on the root to restrict access to the child (including the admin override
+// described above), provided that the method being invoked is among the subset
+// specified (empty set means all methods).  If the method is not among the
+// subset and the child permissions are not set, the request is rejected.
+func NewHierarchicalAuthorizer(rootDir, childDir string, get PermsGetter, emptyChildPermissionsMethods []string) (security.Authorizer, error) {
+	emptyChildPermsMethods := make(map[string]bool)
+	for _, m := range emptyChildPermissionsMethods {
+		emptyChildPermsMethods[m] = true
+	}
+	return &hierarchicalAuthorizer{
+		rootDir:  rootDir,
+		childDir: childDir,
+		get:      get,
+		emptyChildPermsMethods: emptyChildPermsMethods,
+	}, nil
+}
+
+func (ha *hierarchicalAuthorizer) Authorize(ctx *context.T, call security.Call) error {
+	rootPerms, intentionallyEmpty, err := ha.get.PermsForPath(ctx, ha.rootDir)
+	if err != nil {
+		return err
+	} else if intentionallyEmpty {
+		ctx.VI(2).Infof("PermsForPath(%s) is intentionally empty", ha.rootDir)
+		return security.DefaultAuthorizer().Authorize(ctx, call)
+	}
+
+	// We are at the root so exit early.
+	if ha.rootDir == ha.childDir {
+		return adminCheckAuth(ctx, call, access.TypicalTagTypePermissionsAuthorizer(rootPerms), rootPerms)
+	}
+
+	// This is not fatal: the childDir may not exist if we are invoking
+	// a method creating the object, so we only use the root Permissions.
+	childPerms, intentionallyEmpty, err := ha.get.PermsForPath(ctx, ha.childDir)
+	if err != nil {
+		return err
+	} else if intentionallyEmpty {
+		if len(ha.emptyChildPermsMethods) == 0 || ha.emptyChildPermsMethods[call.Method()] {
+			return adminCheckAuth(ctx, call, access.TypicalTagTypePermissionsAuthorizer(rootPerms), rootPerms)
+		}
+		return fmt.Errorf("access disallowed for method %v: no permissions specified on object", call.Method())
+	}
+
+	return adminCheckAuth(ctx, call, access.TypicalTagTypePermissionsAuthorizer(childPerms), rootPerms)
+}
+
+func adminCheckAuth(ctx *context.T, call security.Call, auth security.Authorizer, perms access.Permissions) error {
+	err := auth.Authorize(ctx, call)
+	if err == nil {
+		return nil
+	}
+
+	// Maybe the invoking principal can invoke this method because
+	// it has Admin permissions.
+	names, _ := security.RemoteBlessingNames(ctx, call)
+	if len(names) > 0 && perms[string(access.Admin)].Includes(names...) {
+		return nil
+	}
+
+	return err
+}
diff --git a/services/internal/pathperms/permsaccess.go b/services/internal/pathperms/permsaccess.go
new file mode 100644
index 0000000..5c5d785
--- /dev/null
+++ b/services/internal/pathperms/permsaccess.go
@@ -0,0 +1,254 @@
+// 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 pathperms provides a library to assist servers implementing
+// GetPermissions/SetPermissions functions and authorizers where there are
+// path-specific Permissions stored individually in files.
+// TODO(rjkroege): Add unit tests.
+package pathperms
+
+import (
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"sync"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/security"
+	"v.io/v23/security/access"
+	"v.io/v23/verror"
+	"v.io/x/ref/lib/security/serialization"
+)
+
+const (
+	pkgPath   = "v.io/x/ref/services/internal/pathperms"
+	sigName   = "signature"
+	permsName = "data"
+)
+
+var (
+	ErrOperationFailed = verror.Register(pkgPath+".OperationFailed", verror.NoRetry, "{1:}{2:} operation failed{:_}")
+)
+
+type pathEntry struct {
+	lk sync.Mutex
+	c  int
+}
+
+// PathStore manages storage of a set of Permissions in the filesystem where each
+// path identifies a specific Permissions in the set. PathStore synchronizes
+// access to its member Permissions.
+type PathStore struct {
+	pthlks    map[string]*pathEntry
+	lk        sync.Mutex
+	ctx       *context.T
+	principal security.Principal
+}
+
+// NewPathStore creates a new instance of the lock map that uses
+// principal to sign stored Permissions files.
+func NewPathStore(ctx *context.T) *PathStore {
+	return &PathStore{pthlks: make(map[string]*pathEntry), ctx: ctx, principal: v23.GetPrincipal(ctx)}
+}
+
+// Get returns the Permissions from the data file in dir.
+func (store *PathStore) Get(dir string) (access.Permissions, string, error) {
+	permspath := filepath.Join(dir, permsName)
+	sigpath := filepath.Join(dir, sigName)
+	defer store.lockPath(dir)()
+	return getCore(store.ctx, permspath, sigpath)
+}
+
+// TODO(rjkroege): Improve lock handling.
+func (store *PathStore) lockPath(dir string) func() {
+	store.lk.Lock()
+	pe, contains := store.pthlks[dir]
+	if !contains {
+		pe = &pathEntry{}
+		store.pthlks[dir] = pe
+	}
+	pe.c++
+	store.lk.Unlock()
+	pe.lk.Lock()
+
+	return func() {
+		pe.lk.Unlock()
+		store.lk.Lock()
+		pe.c--
+		if pe.c == 0 {
+			delete(store.pthlks, dir)
+		}
+		store.lk.Unlock()
+	}
+}
+
+func getCore(ctx *context.T, permspath, sigpath string) (access.Permissions, string, error) {
+	principal := v23.GetPrincipal(ctx)
+	f, err := os.Open(permspath)
+	if err != nil {
+		// This path is rarely a fatal error so log informationally only.
+		ctx.VI(2).Infof("os.Open(%s) failed: %v", permspath, err)
+		return nil, "", err
+	}
+	defer f.Close()
+
+	s, err := os.Open(sigpath)
+	if err != nil {
+		ctx.Errorf("Signatures for Permissions are required: %s unavailable: %v", permspath, err)
+		return nil, "", verror.New(ErrOperationFailed, nil)
+	}
+	defer s.Close()
+
+	// read and verify the signature of the perms file
+	vf, err := serialization.NewVerifyingReader(f, s, principal.PublicKey())
+	if err != nil {
+		ctx.Errorf("NewVerifyingReader() failed: %v (perms=%s, sig=%s)", err, permspath, sigpath)
+		return nil, "", verror.New(ErrOperationFailed, nil)
+	}
+
+	perms, err := access.ReadPermissions(vf)
+	if err != nil {
+		ctx.Errorf("ReadPermissions(%s) failed: %v", permspath, err)
+		return nil, "", err
+	}
+	version, err := ComputeVersion(perms)
+	if err != nil {
+		ctx.Errorf("pathperms.ComputeVersion failed: %v", err)
+		return nil, "", err
+	}
+	return perms, version, nil
+}
+
+// Set writes the specified Permissions to the provided directory with
+// enforcement of version synchronization mechanism and locking.
+func (store *PathStore) Set(dir string, perms access.Permissions, version string) error {
+	return store.SetShareable(dir, perms, version, false)
+}
+
+// SetShareable writes the specified Permissions to the provided
+// directory with enforcement of version synchronization mechanism and
+// locking with file modes that will give the application read-only
+// access to the permissions file.
+func (store *PathStore) SetShareable(dir string, perms access.Permissions, version string, shareable bool) error {
+	permspath := filepath.Join(dir, permsName)
+	sigpath := filepath.Join(dir, sigName)
+	defer store.lockPath(dir)()
+	_, oversion, err := getCore(store.ctx, permspath, sigpath)
+	if err != nil && !os.IsNotExist(err) {
+		return verror.New(ErrOperationFailed, nil)
+	}
+	if len(version) > 0 && version != oversion {
+		return verror.NewErrBadVersion(nil)
+	}
+	return write(store.ctx, permspath, sigpath, dir, perms, shareable)
+}
+
+// write writes the specified Permissions to the permsFile with a
+// signature in sigFile.
+func write(ctx *context.T, permsFile, sigFile, dir string, perms access.Permissions, shareable bool) error {
+	principal := v23.GetPrincipal(ctx)
+	filemode := os.FileMode(0600)
+	dirmode := os.FileMode(0700)
+	if shareable {
+		filemode = os.FileMode(0644)
+		dirmode = os.FileMode(0711)
+	}
+
+	// Create dir directory if it does not exist
+	os.MkdirAll(dir, dirmode)
+	// Save the object to temporary data and signature files, and then move
+	// those files to the actual data and signature file.
+	data, err := ioutil.TempFile(dir, permsName)
+	if err != nil {
+		ctx.Errorf("Failed to open tmpfile data:%v", err)
+		return verror.New(ErrOperationFailed, nil)
+	}
+	defer os.Remove(data.Name())
+	sig, err := ioutil.TempFile(dir, sigName)
+	if err != nil {
+		ctx.Errorf("Failed to open tmpfile sig:%v", err)
+		return verror.New(ErrOperationFailed, nil)
+	}
+	defer os.Remove(sig.Name())
+	writer, err := serialization.NewSigningWriteCloser(data, sig, principal, nil)
+	if err != nil {
+		ctx.Errorf("Failed to create NewSigningWriteCloser:%v", err)
+		return verror.New(ErrOperationFailed, nil)
+	}
+	if err = access.WritePermissions(writer, perms); err != nil {
+		ctx.Errorf("Failed to SavePermissions:%v", err)
+		return verror.New(ErrOperationFailed, nil)
+	}
+	if err = writer.Close(); err != nil {
+		ctx.Errorf("Failed to Close() SigningWriteCloser:%v", err)
+		return verror.New(ErrOperationFailed, nil)
+	}
+	if err := os.Rename(data.Name(), permsFile); err != nil {
+		ctx.Errorf("os.Rename() failed:%v", err)
+		return verror.New(ErrOperationFailed, nil)
+	}
+	if err := os.Chmod(permsFile, filemode); err != nil {
+		ctx.Errorf("os.Chmod() failed:%v", err)
+		return verror.New(ErrOperationFailed, nil)
+	}
+	if err := os.Rename(sig.Name(), sigFile); err != nil {
+		ctx.Errorf("os.Rename() failed:%v", err)
+		return verror.New(ErrOperationFailed, nil)
+	}
+	if err := os.Chmod(sigFile, filemode); err != nil {
+		ctx.Errorf("os.Chmod() failed:%v", err)
+		return verror.New(ErrOperationFailed, nil)
+	}
+	return nil
+}
+
+func (store *PathStore) PermsForPath(ctx *context.T, path string) (access.Permissions, bool, error) {
+	perms, _, err := store.Get(path)
+	if os.IsNotExist(err) {
+		return nil, true, nil
+	} else if err != nil {
+		return nil, false, err
+	}
+	return perms, false, nil
+}
+
+// PrefixPatterns creates a pattern containing all of the prefix patterns of the
+// provided blessings.
+func PrefixPatterns(blessings []string) []security.BlessingPattern {
+	var patterns []security.BlessingPattern
+	for _, b := range blessings {
+		patterns = append(patterns, security.BlessingPattern(b).PrefixPatterns()...)
+	}
+	return patterns
+}
+
+// PermissionsForBlessings creates the Permissions list that should be used with
+// a newly created object.
+func PermissionsForBlessings(blessings []string) access.Permissions {
+	perms := make(access.Permissions)
+
+	// Add the invoker's blessings and all its prefixes.
+	for _, p := range PrefixPatterns(blessings) {
+		for _, tag := range access.AllTypicalTags() {
+			perms.Add(p, string(tag))
+		}
+	}
+	return perms
+}
+
+// NilAuthPermissions creates Permissions that mimics the default authorization
+// policy (i.e., Permissions is matched by all blessings that are either
+// extensions of one of the local blessings or can be extended to form one of
+// the local blessings.)
+func NilAuthPermissions(ctx *context.T, call security.Call) access.Permissions {
+	perms := make(access.Permissions)
+	lb := security.LocalBlessingNames(ctx, call)
+	for _, p := range PrefixPatterns(lb) {
+		for _, tag := range access.AllTypicalTags() {
+			perms.Add(p, string(tag))
+		}
+	}
+	return perms
+}
diff --git a/services/internal/pathperms/version.go b/services/internal/pathperms/version.go
new file mode 100644
index 0000000..c278e39
--- /dev/null
+++ b/services/internal/pathperms/version.go
@@ -0,0 +1,27 @@
+// 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 pathperms
+
+import (
+	"bytes"
+	"crypto/md5"
+	"encoding/hex"
+
+	"v.io/v23/security/access"
+)
+
+// ComputeVersion produces the tag value returned by access.GetPermissions()
+// (per v23/services/permissions/service.vdl) that GetPermissions/SetPermissions
+// use to determine if the Permissions have been asynchronously modified.
+func ComputeVersion(perms access.Permissions) (string, error) {
+	b := new(bytes.Buffer)
+	if err := access.WritePermissions(b, perms); err != nil {
+		return "", err
+	}
+
+	md5hash := md5.Sum(b.Bytes())
+	version := hex.EncodeToString(md5hash[:])
+	return version, nil
+}
diff --git a/services/internal/pproflib/proxy.go b/services/internal/pproflib/proxy.go
new file mode 100644
index 0000000..4226395
--- /dev/null
+++ b/services/internal/pproflib/proxy.go
@@ -0,0 +1,224 @@
+// 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 pproflib defines a client-side proxy and server-side implementation
+// of the v.io/v23/services/pprof interface.
+//
+// It is functionally equivalent to http://golang.org/pkg/net/http/pprof/,
+// except that the data comes from a remote vanadium server, and the handlers
+// are not registered in DefaultServeMux.
+package pproflib
+
+import (
+	"bufio"
+	"fmt"
+	"html/template"
+	"io"
+	"net"
+	"net/http"
+	"strconv"
+	"strings"
+	"time"
+
+	"v.io/v23/context"
+	"v.io/v23/services/pprof"
+	"v.io/v23/vtrace"
+)
+
+// StartProxy starts the pprof proxy to a remote pprof object.
+func StartProxy(ctx *context.T, name string) (net.Listener, error) {
+	listener, err := net.Listen("tcp", "127.0.0.1:0")
+	if err != nil {
+		return nil, err
+	}
+	p := &proxy{ctx, name}
+	mux := http.NewServeMux()
+	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+		w.Header().Add("Location", "/pprof/")
+		w.WriteHeader(http.StatusFound)
+	})
+	mux.HandleFunc("/pprof/", p.index)
+	mux.HandleFunc("/pprof/cmdline", p.cmdLine)
+	mux.HandleFunc("/pprof/profile", p.profile)
+	mux.HandleFunc("/pprof/symbol", p.symbol)
+
+	server := &http.Server{
+		Handler:      mux,
+		ReadTimeout:  time.Hour,
+		WriteTimeout: time.Hour,
+	}
+	go server.Serve(listener)
+	return listener, nil
+}
+
+type proxy struct {
+	ctx  *context.T
+	name string
+}
+
+func replyUnavailable(w http.ResponseWriter, err error) {
+	w.WriteHeader(http.StatusServiceUnavailable)
+	fmt.Fprintf(w, "%v", err)
+}
+
+// index serves an html page for /pprof/ and, indirectly, raw data for /pprof/*
+func (p *proxy) index(w http.ResponseWriter, r *http.Request) {
+	if strings.HasPrefix(r.URL.Path, "/pprof/") {
+		name := strings.TrimPrefix(r.URL.Path, "/pprof/")
+		if name != "" {
+			p.sendProfile(name, w, r)
+			return
+		}
+	}
+	c := pprof.PProfClient(p.name)
+	ctx, _ := vtrace.WithNewTrace(p.ctx)
+	profiles, err := c.Profiles(ctx)
+	if err != nil {
+		replyUnavailable(w, err)
+		return
+	}
+	if err := indexTmpl.Execute(w, profiles); err != nil {
+		w.WriteHeader(http.StatusInternalServerError)
+		fmt.Fprintf(w, "%v", err)
+		return
+	}
+}
+
+// sendProfile sends the requested profile as a response.
+func (p *proxy) sendProfile(name string, w http.ResponseWriter, r *http.Request) {
+	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
+	debug, _ := strconv.Atoi(r.FormValue("debug"))
+	c := pprof.PProfClient(p.name)
+	ctx, _ := vtrace.WithNewTrace(p.ctx)
+	prof, err := c.Profile(ctx, name, int32(debug))
+	if err != nil {
+		replyUnavailable(w, err)
+		return
+	}
+	iterator := prof.RecvStream()
+	for iterator.Advance() {
+		_, err := w.Write(iterator.Value())
+		if err != nil {
+			return
+		}
+	}
+	if err := iterator.Err(); err != nil {
+		replyUnavailable(w, err)
+		return
+	}
+	if err := prof.Finish(); err != nil {
+		replyUnavailable(w, err)
+		return
+	}
+}
+
+// profile replies with a CPU profile.
+func (p *proxy) profile(w http.ResponseWriter, r *http.Request) {
+	sec, _ := strconv.ParseUint(r.FormValue("seconds"), 10, 64)
+	if sec == 0 {
+		sec = 30
+	}
+	w.Header().Set("Content-Type", "application/octet-stream")
+	c := pprof.PProfClient(p.name)
+	ctx, _ := vtrace.WithNewTrace(p.ctx)
+	prof, err := c.CpuProfile(ctx, int32(sec))
+	if err != nil {
+		replyUnavailable(w, err)
+		return
+	}
+	iterator := prof.RecvStream()
+	for iterator.Advance() {
+		_, err := w.Write(iterator.Value())
+		if err != nil {
+			return
+		}
+	}
+	if err := iterator.Err(); err != nil {
+		replyUnavailable(w, err)
+		return
+	}
+	if err := prof.Finish(); err != nil {
+		replyUnavailable(w, err)
+		return
+	}
+}
+
+// cmdLine replies with the command-line arguments of the process.
+func (p *proxy) cmdLine(w http.ResponseWriter, r *http.Request) {
+	c := pprof.PProfClient(p.name)
+	ctx, _ := vtrace.WithNewTrace(p.ctx)
+	cmdline, err := c.CmdLine(ctx)
+	if err != nil {
+		replyUnavailable(w, err)
+		return
+	}
+	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
+	fmt.Fprintf(w, strings.Join(cmdline, "\x00"))
+}
+
+// symbol replies with a map of program counters to object name.
+func (p *proxy) symbol(w http.ResponseWriter, r *http.Request) {
+	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
+	var b *bufio.Reader
+	if r.Method == "POST" {
+		b = bufio.NewReader(r.Body)
+	} else {
+		b = bufio.NewReader(strings.NewReader(r.URL.RawQuery))
+	}
+	pcList := []uint64{}
+	for {
+		word, err := b.ReadSlice('+')
+		if err == nil {
+			word = word[0 : len(word)-1] // trim +
+		}
+		pc, _ := strconv.ParseUint(string(word), 0, 64)
+		if pc != 0 {
+			pcList = append(pcList, pc)
+		}
+		if err != nil {
+			if err != io.EOF {
+				replyUnavailable(w, err)
+				return
+			}
+			break
+		}
+	}
+	c := pprof.PProfClient(p.name)
+	ctx, _ := vtrace.WithNewTrace(p.ctx)
+	pcMap, err := c.Symbol(ctx, pcList)
+	if err != nil {
+		replyUnavailable(w, err)
+		return
+	}
+	if len(pcMap) != len(pcList) {
+		replyUnavailable(w, fmt.Errorf("received the wrong number of results. Got %d, want %d", len(pcMap), len(pcList)))
+		return
+	}
+	// The pprof tool always wants num_symbols to be non-zero, even if no
+	// symbols are returned.. The actual value doesn't matter.
+	fmt.Fprintf(w, "num_symbols: 1\n")
+	for i, v := range pcMap {
+		if len(v) > 0 {
+			fmt.Fprintf(w, "%#x %s\n", pcList[i], v)
+		}
+	}
+}
+
+var indexTmpl = template.Must(template.New("index").Parse(`<html>
+<head>
+<title>/pprof/</title>
+</head>
+/pprof/<br>
+<br>
+<body>
+profiles:<br>
+<table>
+{{range .}}<tr><td align=right><td><a href="/pprof/{{.}}?debug=1">{{.}}</a>
+{{end}}
+</table>
+<br>
+<a href="/pprof/goroutine?debug=2">full goroutine stack dump</a><br>
+</body>
+</html>
+`))
diff --git a/services/internal/pproflib/proxy_test.go b/services/internal/pproflib/proxy_test.go
new file mode 100644
index 0000000..079993a
--- /dev/null
+++ b/services/internal/pproflib/proxy_test.go
@@ -0,0 +1,73 @@
+// 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 pproflib_test
+
+import (
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"testing"
+
+	"v.io/v23/context"
+	"v.io/v23/security"
+	"v.io/x/ref/lib/xrpc"
+	_ "v.io/x/ref/runtime/factories/generic"
+	"v.io/x/ref/services/internal/pproflib"
+	"v.io/x/ref/test"
+)
+
+//go:generate v23 test generate
+
+type dispatcher struct {
+	server interface{}
+}
+
+func (d *dispatcher) Lookup(_ *context.T, suffix string) (interface{}, security.Authorizer, error) {
+	return d.server, nil, nil
+}
+
+func TestPProfProxy(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	s, err := xrpc.NewDispatchingServer(ctx, "", &dispatcher{pproflib.NewPProfService()})
+	if err != nil {
+		t.Fatalf("failed to start server: %v", err)
+	}
+	endpoints := s.Status().Endpoints
+	l, err := pproflib.StartProxy(ctx, endpoints[0].Name())
+	if err != nil {
+		t.Fatalf("failed to start proxy: %v", err)
+	}
+	defer l.Close()
+
+	testcases := []string{
+		"/pprof/",
+		"/pprof/cmdline",
+		"/pprof/profile?seconds=1",
+		"/pprof/heap",
+		"/pprof/goroutine",
+		fmt.Sprintf("/pprof/symbol?%p", TestPProfProxy),
+	}
+	for _, c := range testcases {
+		url := "http://" + l.Addr().String() + c
+		t.Log(url)
+		resp, err := http.Get(url)
+		if err != nil {
+			t.Fatalf("http.Get failed: %v", err)
+		}
+		if resp.StatusCode != 200 {
+			t.Errorf("unexpected status code. Got %d, want 200", resp.StatusCode)
+		}
+		body, err := ioutil.ReadAll(resp.Body)
+		if err != nil {
+			t.Fatalf("ReadAll failed: %v", err)
+		}
+		resp.Body.Close()
+		if len(body) == 0 {
+			t.Errorf("unexpected empty body")
+		}
+	}
+}
diff --git a/services/internal/pproflib/server.go b/services/internal/pproflib/server.go
new file mode 100644
index 0000000..1866e6e
--- /dev/null
+++ b/services/internal/pproflib/server.go
@@ -0,0 +1,104 @@
+// 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 pproflib
+
+import (
+	"os"
+	"runtime"
+	"runtime/pprof"
+	"time"
+
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	s_pprof "v.io/v23/services/pprof"
+	"v.io/v23/verror"
+)
+
+const pkgPath = "v.io/x/ref/services/internal/pproflib"
+
+// Errors
+var (
+	errNoProfile      = verror.Register(pkgPath+".errNoProfile", verror.NoRetry, "{1:}{2:} profile does not exist{:_}")
+	errInvalidSeconds = verror.Register(pkgPath+".errInvalidSeconds", verror.NoRetry, "{1:}{2:} invalid number of seconds{:_}")
+)
+
+// NewPProfService returns a new pprof service implementation.
+func NewPProfService() interface{} {
+	return s_pprof.PProfServer(&pprofService{})
+}
+
+type pprofService struct {
+}
+
+// CmdLine returns the command-line argument of the server.
+func (pprofService) CmdLine(*context.T, rpc.ServerCall) ([]string, error) {
+	return os.Args, nil
+}
+
+// Profiles returns the list of available profiles.
+func (pprofService) Profiles(*context.T, rpc.ServerCall) ([]string, error) {
+	profiles := pprof.Profiles()
+	results := make([]string, len(profiles))
+	for i, v := range profiles {
+		results[i] = v.Name()
+	}
+	return results, nil
+}
+
+// Profile streams the requested profile. The debug parameter enables
+// additional output. Passing debug=0 includes only the hexadecimal
+// addresses that pprof needs. Passing debug=1 adds comments translating
+// addresses to function names and line numbers, so that a programmer
+// can read the profile without tools.
+func (pprofService) Profile(ctx *context.T, call s_pprof.PProfProfileServerCall, name string, debug int32) error {
+	profile := pprof.Lookup(name)
+	if profile == nil {
+		return verror.New(errNoProfile, ctx, name)
+	}
+	if err := profile.WriteTo(&streamWriter{call.SendStream()}, int(debug)); err != nil {
+		return verror.Convert(verror.ErrUnknown, ctx, err)
+	}
+	return nil
+}
+
+// CPUProfile enables CPU profiling for the requested duration and
+// streams the profile data.
+func (pprofService) CpuProfile(ctx *context.T, call s_pprof.PProfCpuProfileServerCall, seconds int32) error {
+	if seconds <= 0 || seconds > 3600 {
+		return verror.New(errInvalidSeconds, ctx, seconds)
+	}
+	if err := pprof.StartCPUProfile(&streamWriter{call.SendStream()}); err != nil {
+		return verror.Convert(verror.ErrUnknown, ctx, err)
+	}
+	time.Sleep(time.Duration(seconds) * time.Second)
+	pprof.StopCPUProfile()
+	return nil
+}
+
+// Symbol looks up the program counters and returns their respective
+// function names.
+func (pprofService) Symbol(_ *context.T, _ rpc.ServerCall, programCounters []uint64) ([]string, error) {
+	results := make([]string, len(programCounters))
+	for i, v := range programCounters {
+		f := runtime.FuncForPC(uintptr(v))
+		if f != nil {
+			results[i] = f.Name()
+		}
+	}
+	return results, nil
+}
+
+type streamWriter struct {
+	sender interface {
+		Send(item []byte) error
+	}
+}
+
+func (w *streamWriter) Write(p []byte) (int, error) {
+	if err := w.sender.Send(p); err != nil {
+		return 0, err
+	}
+	return len(p), nil
+}
diff --git a/services/internal/pproflib/v23_internal_test.go b/services/internal/pproflib/v23_internal_test.go
new file mode 100644
index 0000000..2b18e2d
--- /dev/null
+++ b/services/internal/pproflib/v23_internal_test.go
@@ -0,0 +1,20 @@
+// 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 pproflib
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	os.Exit(m.Run())
+}
diff --git a/services/internal/profiles/listprofiles.go b/services/internal/profiles/listprofiles.go
new file mode 100644
index 0000000..6542bbe
--- /dev/null
+++ b/services/internal/profiles/listprofiles.go
@@ -0,0 +1,85 @@
+// 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 profiles
+
+import (
+	//	"bytes"
+	//	"errors"
+	//	"os/exec"
+	//	"runtime"
+	//	"strings"
+
+	"v.io/v23/services/build"
+	//	"v.io/v23/services/device"
+	"v.io/x/ref/services/profile"
+)
+
+// GetKnownProfiles gets a list of description for all publicly known
+// profiles.
+//
+// TODO(jsimsa): Avoid retrieving the list of known profiles from a
+// remote server if a recent cached copy exists.
+func GetKnownProfiles() ([]*profile.Specification, error) {
+	return []*profile.Specification{
+		{
+			Label:       "linux-amd64",
+			Description: "",
+			Arch:        build.ArchitectureAmd64,
+			Os:          build.OperatingSystemLinux,
+			Format:      build.FormatElf,
+		},
+		{
+			// Note that linux-386 is used instead of linux-x86 for the
+			// label to facilitate generation of a matching label string
+			// using the runtime.GOARCH value. In VDL, the 386 architecture
+			// is represented using the value X86 because the VDL grammar
+			// does not allow identifiers starting with a number.
+			Label:       "linux-386",
+			Description: "",
+			Arch:        build.ArchitectureX86,
+			Os:          build.OperatingSystemLinux,
+			Format:      build.FormatElf,
+		},
+		{
+			Label:       "linux-arm",
+			Description: "",
+			Arch:        build.ArchitectureArm,
+			Os:          build.OperatingSystemLinux,
+			Format:      build.FormatElf,
+		},
+		{
+			Label:       "darwin-amd64",
+			Description: "",
+			Arch:        build.ArchitectureAmd64,
+			Os:          build.OperatingSystemDarwin,
+			Format:      build.FormatMach,
+		},
+	}, nil
+
+	// TODO(jsimsa): This function assumes the existence of a profile
+	// server from which a list of known profiles can be retrieved. The
+	// profile server is a work in progress. When it exists, the
+	// commented out code below should work.
+
+	/*
+		knownProfiles := make([]profile.Specification, 0)
+				client, err := r.NewClient()
+				if err != nil {
+					return nil,  verror.New(ErrOperationFailed, nil, fmt.Sprintf("NewClient() failed: %v\n", err))
+				}
+				defer client.Close()
+			  server := // TODO
+				method := "List"
+				inputs := make([]interface{}, 0)
+				call, err := client.StartCall(server, method, inputs)
+				if err != nil {
+					return nil, verror.New(ErrOperationFailed, nil, fmt.Sprintf("StartCall(%s, %q, %v) failed: %v\n", server, method, inputs, err))
+				}
+				if err := call.Finish(&knownProfiles); err != nil {
+					return nil, verror.New(ErrOperationFailed, nil, fmt.Sprintf("Finish(&knownProfile) failed: %v\n", err))
+				}
+		return knownProfiles, nil
+	*/
+}
diff --git a/services/internal/servicetest/doc.go b/services/internal/servicetest/doc.go
new file mode 100644
index 0000000..28a6564
--- /dev/null
+++ b/services/internal/servicetest/doc.go
@@ -0,0 +1,7 @@
+// 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 servicetest provides functionality to help write tests
+// for the Vanadium services.
+package servicetest
diff --git a/services/internal/servicetest/mock.go b/services/internal/servicetest/mock.go
new file mode 100644
index 0000000..8699eae
--- /dev/null
+++ b/services/internal/servicetest/mock.go
@@ -0,0 +1,111 @@
+// 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 servicetest
+
+import (
+	"fmt"
+	"sync"
+)
+
+// Tape holds a record of function call stimuli and each function call's
+// response. Use Tape to help build a mock framework by first creating a
+// new Tape, then SetResponses to define the mock responses and then
+// Record each function invocation. Play returns the function invocations
+// for verification in a test.
+type Tape struct {
+	sync.Mutex
+	stimuli   []interface{}
+	responses []interface{}
+}
+
+// Record stores a new function invocation in a Tape and returns the
+// response for that function interface.
+func (t *Tape) Record(call interface{}) interface{} {
+	t.Lock()
+	defer t.Unlock()
+	t.stimuli = append(t.stimuli, call)
+
+	if len(t.responses) < 1 {
+		// Returning an error at this point will likely cause the mock
+		// using the tape to panic when it tries to cast the response
+		// to the desired type.
+		// Panic'ing here at least makes the issue more
+		// apparent.
+		// TODO(caprita): Don't panic.
+		panic(fmt.Errorf("Record(%#v) had no response", call))
+	}
+	resp := t.responses[0]
+	t.responses = t.responses[1:]
+	return resp
+}
+
+// SetResponses updates the Tape's associated responses.
+func (t *Tape) SetResponses(responses ...interface{}) {
+	t.Lock()
+	defer t.Unlock()
+	t.responses = make([]interface{}, len(responses))
+	copy(t.responses, responses)
+}
+
+// Rewind resets the tape to the beginning so that it could be used again
+// for further tests.
+func (t *Tape) Rewind() {
+	t.Lock()
+	defer t.Unlock()
+	t.stimuli = make([]interface{}, 0)
+	t.responses = make([]interface{}, 0)
+}
+
+// Play returns the function call stimuli recorded to this Tape.
+func (t *Tape) Play() []interface{} {
+	t.Lock()
+	defer t.Unlock()
+	resp := make([]interface{}, len(t.stimuli))
+	copy(resp, t.stimuli)
+	return resp
+}
+
+// NewTape creates a new Tape.
+func NewTape() *Tape {
+	t := new(Tape)
+	t.Rewind()
+	return t
+}
+
+// TapeMap provides multiple tapes for different strings. Use TapeMap to
+// record separate Tapes for each suffix in a service.
+type TapeMap struct {
+	sync.Mutex
+	tapes map[string]*Tape
+}
+
+// NewTapeMap creates a new empty TapeMap.
+func NewTapeMap() *TapeMap {
+	tm := &TapeMap{
+		tapes: make(map[string]*Tape),
+	}
+	return tm
+}
+
+// ForSuffix returns the Tape for suffix s.
+func (tm *TapeMap) ForSuffix(s string) *Tape {
+	tm.Lock()
+	defer tm.Unlock()
+	t, ok := tm.tapes[s]
+	if !ok {
+		t = new(Tape)
+		tm.tapes[s] = t
+	}
+	return t
+}
+
+// Rewind rewinds all of the Tapes in the TapeMap.
+func (tm *TapeMap) Rewind() {
+	tm.Lock()
+	defer tm.Unlock()
+	for _, t := range tm.tapes {
+		t.Rewind()
+	}
+}
diff --git a/services/internal/servicetest/mock_test.go b/services/internal/servicetest/mock_test.go
new file mode 100644
index 0000000..dc9d4c2
--- /dev/null
+++ b/services/internal/servicetest/mock_test.go
@@ -0,0 +1,89 @@
+// 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 servicetest
+
+import (
+	"fmt"
+	"reflect"
+	"sort"
+	"testing"
+)
+
+func TestOneTape(t *testing.T) {
+	tm := NewTapeMap()
+	tm.ForSuffix("mytape").SetResponses("b", "c")
+	if want, got := "b", tm.ForSuffix("mytape").Record("bar"); want != got {
+		t.Errorf("Expected %v, got %v", want, got)
+	}
+	if want, got := "c", tm.ForSuffix("mytape").Record("baz"); want != got {
+		t.Errorf("Expected %v, got %v", want, got)
+	}
+	if want, got := []interface{}{"bar", "baz"}, tm.ForSuffix("mytape").Play(); !reflect.DeepEqual(want, got) {
+		t.Errorf("Expected %v, got %v", want, got)
+	}
+	tm.ForSuffix("mytape").Rewind()
+	if want, got := []interface{}{}, tm.ForSuffix("mytape").Play(); !reflect.DeepEqual(want, got) {
+		t.Errorf("Expected %v, got %v", want, got)
+	}
+}
+
+func TestManyTapes(t *testing.T) {
+	tm := NewTapeMap()
+	tapes := []string{"duct tape", "cassette tape", "watergate tape", "tape worm"}
+	for _, tp := range tapes {
+		tm.ForSuffix(tp).SetResponses(tp + "resp")
+	}
+	for _, tp := range tapes {
+		if want, got := tp+"resp", tm.ForSuffix(tp).Record(tp+"stimulus"); want != got {
+			t.Errorf("Expected %v, got %v", want, got)
+		}
+	}
+	for _, tp := range tapes {
+		if want, got := []interface{}{tp + "stimulus"}, tm.ForSuffix(tp).Play(); !reflect.DeepEqual(want, got) {
+			t.Errorf("Expected %v, got %v", want, got)
+		}
+	}
+}
+
+func TestTapeParallelism(t *testing.T) {
+	tm := NewTapeMap()
+	var resp []interface{}
+	const N = 100
+	for i := 0; i < N; i++ {
+		resp = append(resp, fmt.Sprintf("resp%010d", i))
+	}
+	tm.ForSuffix("mytape").SetResponses(resp...)
+	results := make(chan string, N)
+	for i := 0; i < N; i++ {
+		go func(index int) {
+			results <- tm.ForSuffix("mytape").Record(fmt.Sprintf("stimulus%010d", index)).(string)
+		}(i)
+	}
+	var res []string
+	for i := 0; i < N; i++ {
+		r := <-results
+		res = append(res, r)
+	}
+	sort.Strings(res)
+	var expectedRes []string
+	for i := 0; i < N; i++ {
+		expectedRes = append(expectedRes, fmt.Sprintf("resp%010d", i))
+	}
+	if want, got := expectedRes, res; !reflect.DeepEqual(want, got) {
+		t.Errorf("Expected %v, got %v", want, got)
+	}
+	var expectedStimuli []string
+	for i := 0; i < N; i++ {
+		expectedStimuli = append(expectedStimuli, fmt.Sprintf("stimulus%010d", i))
+	}
+	var gotStimuli []string
+	for _, s := range tm.ForSuffix("mytape").Play() {
+		gotStimuli = append(gotStimuli, s.(string))
+	}
+	sort.Strings(gotStimuli)
+	if want, got := expectedStimuli, gotStimuli; !reflect.DeepEqual(want, got) {
+		t.Errorf("Expected %v, got %v", want, got)
+	}
+}
diff --git a/services/internal/servicetest/modules.go b/services/internal/servicetest/modules.go
new file mode 100644
index 0000000..56b4264
--- /dev/null
+++ b/services/internal/servicetest/modules.go
@@ -0,0 +1,189 @@
+// 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 servicetest
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strconv"
+	"testing"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/options"
+	"v.io/v23/security"
+	"v.io/x/ref"
+	"v.io/x/ref/internal/logger"
+	"v.io/x/ref/lib/xrpc"
+	"v.io/x/ref/services/mounttable/mounttablelib"
+	"v.io/x/ref/test/modules"
+	"v.io/x/ref/test/testutil"
+)
+
+const (
+	// Setting this environment variable to any non-empty value avoids
+	// removing the generated workspace for successful test runs (for
+	// failed test runs, this is already the case).  This is useful when
+	// developing test cases.
+	preserveWorkspaceEnv = "V23_TEST_PRESERVE_WORKSPACE"
+)
+
+var rootMT = modules.Register(func(env *modules.Env, args ...string) error {
+	ctx, shutdown := v23.Init()
+	defer shutdown()
+
+	mt, err := mounttablelib.NewMountTableDispatcher(ctx, "", "", "mounttable")
+	if err != nil {
+		return fmt.Errorf("mounttablelib.NewMountTableDispatcher failed: %s", err)
+	}
+	server, err := xrpc.NewDispatchingServer(ctx, "", mt, options.ServesMountTable(true))
+	if err != nil {
+		return fmt.Errorf("root failed: %v", err)
+	}
+	fmt.Fprintf(env.Stdout, "PID=%d\n", os.Getpid())
+	for _, ep := range server.Status().Endpoints {
+		fmt.Fprintf(env.Stdout, "MT_NAME=%s\n", ep.Name())
+	}
+	modules.WaitForEOF(env.Stdin)
+	return nil
+}, "rootMT")
+
+// startRootMT sets up a root mount table for tests.
+func startRootMT(t *testing.T, sh *modules.Shell) (string, modules.Handle) {
+	h, err := sh.Start(nil, rootMT, "--v23.tcp.address=127.0.0.1:0")
+	if err != nil {
+		t.Fatalf("failed to start root mount table: %s", err)
+	}
+	h.ExpectVar("PID")
+	rootName := h.ExpectVar("MT_NAME")
+	if t.Failed() {
+		t.Fatalf("failed to read mt name: %s", h.Error())
+	}
+	return rootName, h
+}
+
+// setNSRoots sets the roots for the local runtime's namespace.
+func setNSRoots(t *testing.T, ctx *context.T, roots ...string) {
+	ns := v23.GetNamespace(ctx)
+	if err := ns.SetRoots(roots...); err != nil {
+		t.Fatalf(testutil.FormatLogLine(3, "SetRoots(%v) failed with %v", roots, err))
+	}
+}
+
+// CreateShellAndMountTable builds a new modules shell and its
+// associated mount table.
+func CreateShellAndMountTable(t *testing.T, ctx *context.T, p security.Principal) (*modules.Shell, func()) {
+	sh, err := modules.NewShell(ctx, p, testing.Verbose(), t)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	opts := sh.DefaultStartOpts()
+	opts.ExpectTimeout = ExpectTimeout
+	sh.SetDefaultStartOpts(opts)
+	// The shell, will, by default share credentials with its children.
+	sh.ClearVar(ref.EnvCredentials)
+
+	mtName, _ := startRootMT(t, sh)
+	ctx.VI(1).Infof("Started shell mounttable with name %v", mtName)
+
+	// TODO(caprita): Define a GetNamespaceRootsCommand in modules/core and
+	// use that?
+
+	oldNamespaceRoots := v23.GetNamespace(ctx).Roots()
+	fn := func() {
+		ctx.VI(1).Info("------------ CLEANUP ------------")
+		ctx.VI(1).Info("---------------------------------")
+		ctx.VI(1).Info("--(cleaning up shell)------------")
+		if err := sh.Cleanup(os.Stdout, os.Stderr); err != nil {
+			t.Errorf(testutil.FormatLogLine(2, "sh.Cleanup failed with %v", err))
+		}
+		ctx.VI(1).Info("--(done cleaning up shell)-------")
+		setNSRoots(t, ctx, oldNamespaceRoots...)
+	}
+	setNSRoots(t, ctx, mtName)
+	sh.SetVar(ref.EnvNamespacePrefix, mtName)
+	return sh, fn
+}
+
+// CreateShell builds a new modules shell.
+func CreateShell(t *testing.T, ctx *context.T, p security.Principal) (*modules.Shell, func()) {
+	sh, err := modules.NewShell(ctx, p, testing.Verbose(), t)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	opts := sh.DefaultStartOpts()
+	opts.ExpectTimeout = ExpectTimeout
+	sh.SetDefaultStartOpts(opts)
+	// The shell, will, by default share credentials with its children.
+	sh.ClearVar(ref.EnvCredentials)
+
+	fn := func() {
+		ctx.VI(1).Info("------------ CLEANUP ------------")
+		ctx.VI(1).Info("---------------------------------")
+		ctx.VI(1).Info("--(cleaning up shell)------------")
+		if err := sh.Cleanup(os.Stdout, os.Stderr); err != nil {
+			t.Errorf(testutil.FormatLogLine(2, "sh.Cleanup failed with %v", err))
+		}
+		ctx.VI(1).Info("--(done cleaning up shell)-------")
+	}
+	nsRoots := v23.GetNamespace(ctx).Roots()
+	if len(nsRoots) == 0 {
+		t.Fatalf("shell context has no namespace roots")
+	}
+	sh.SetVar(ref.EnvNamespacePrefix, nsRoots[0])
+	return sh, fn
+}
+
+// RunCommand runs a modules command.
+func RunCommand(t *testing.T, sh *modules.Shell, env []string, prog modules.Program, args ...string) modules.Handle {
+	h, err := sh.StartWithOpts(sh.DefaultStartOpts(), env, prog, args...)
+	if err != nil {
+		t.Fatalf(testutil.FormatLogLine(2, "failed to start %q: %s", prog, err))
+		return nil
+	}
+	h.SetVerbosity(testing.Verbose())
+	return h
+}
+
+// ReadPID waits for the "ready:<PID>" line from the child and parses out the
+// PID of the child.
+func ReadPID(t *testing.T, h modules.ExpectSession) int {
+	m := h.ExpectRE("ready:([0-9]+)", -1)
+	if len(m) == 1 && len(m[0]) == 2 {
+		pid, err := strconv.Atoi(m[0][1])
+		if err != nil {
+			t.Fatalf(testutil.FormatLogLine(2, "Atoi(%q) failed: %v", m[0][1], err))
+		}
+		return pid
+	}
+	t.Fatalf(testutil.FormatLogLine(2, "failed to extract pid: %v", m))
+	return 0
+}
+
+// SetupRootDir sets up and returns a directory for the root and returns
+// a cleanup function.
+func SetupRootDir(t *testing.T, prefix string) (string, func()) {
+	rootDir, err := ioutil.TempDir("", prefix)
+	if err != nil {
+		t.Fatalf("Failed to set up temporary dir for test: %v", err)
+	}
+	// On some operating systems (e.g. darwin) os.TempDir() can return a
+	// symlink. To avoid having to account for this eventuality later,
+	// evaluate the symlink.
+	rootDir, err = filepath.EvalSymlinks(rootDir)
+	if err != nil {
+		logger.Global().Fatalf("EvalSymlinks(%v) failed: %v", rootDir, err)
+	}
+
+	return rootDir, func() {
+		if t.Failed() || os.Getenv(preserveWorkspaceEnv) != "" {
+			t.Logf("You can examine the %s workspace at %v", prefix, rootDir)
+		} else {
+			os.RemoveAll(rootDir)
+		}
+	}
+}
diff --git a/services/internal/servicetest/timeouts.go b/services/internal/servicetest/timeouts.go
new file mode 100644
index 0000000..bae8ff7
--- /dev/null
+++ b/services/internal/servicetest/timeouts.go
@@ -0,0 +1,14 @@
+// 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 servicetest
+
+import (
+	"time"
+)
+
+const (
+	// TODO(caprita): Set the timeout in a more principled manner.
+	ExpectTimeout = 20 * time.Second
+)
diff --git a/services/internal/statslib/stats.go b/services/internal/statslib/stats.go
new file mode 100644
index 0000000..08f7157
--- /dev/null
+++ b/services/internal/statslib/stats.go
@@ -0,0 +1,114 @@
+// 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 statslib implements the Stats interface from
+// v.io/v23/services/stats.
+package statslib
+
+import (
+	"reflect"
+	"time"
+
+	"v.io/v23/context"
+	"v.io/v23/glob"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+	"v.io/v23/services/stats"
+	"v.io/v23/services/watch"
+	"v.io/v23/vdl"
+	"v.io/v23/verror"
+	libstats "v.io/x/ref/lib/stats"
+)
+
+type statsService struct {
+	suffix    string
+	watchFreq time.Duration
+}
+
+const pkgPath = "v.io/x/ref/services/internal/statslib"
+
+var (
+	errOperationFailed = verror.Register(pkgPath+".errOperationFailed", verror.NoRetry, "{1:}{2:} operation failed{:_}")
+)
+
+// NewStatsService returns a stats server implementation. The value of watchFreq
+// is used to specify the time between WatchGlob updates.
+func NewStatsService(suffix string, watchFreq time.Duration) interface{} {
+	return stats.StatsServer(&statsService{suffix, watchFreq})
+}
+
+// Glob__ returns the name of all objects that match pattern.
+func (i *statsService) Glob__(ctx *context.T, call rpc.GlobServerCall, g *glob.Glob) error {
+	ctx.VI(1).Infof("%v.Glob__(%q)", i.suffix, g.String())
+	sender := call.SendStream()
+	it := libstats.Glob(i.suffix, g.String(), time.Time{}, false)
+	for it.Advance() {
+		sender.Send(naming.GlobReplyEntry{Value: naming.MountEntry{Name: it.Value().Key}})
+	}
+	if err := it.Err(); err != nil {
+		ctx.VI(1).Infof("libstats.Glob(%q, %q) failed: %v", i.suffix, g.String(), err)
+	}
+	return nil
+}
+
+// WatchGlob returns the name and value of the objects that match the request,
+// followed by periodic updates when values change.
+func (i *statsService) WatchGlob(ctx *context.T, call watch.GlobWatcherWatchGlobServerCall, req watch.GlobRequest) error {
+	ctx.VI(1).Infof("%v.WatchGlob(%+v)", i.suffix, req)
+
+	var t time.Time
+Loop:
+	for {
+		prevTime := t
+		t = time.Now()
+		it := libstats.Glob(i.suffix, req.Pattern, prevTime, true)
+		changes := []watch.Change{}
+		for it.Advance() {
+			v := it.Value()
+			c := watch.Change{
+				Name:  v.Key,
+				State: watch.Exists,
+				Value: vdl.ValueOf(v.Value),
+			}
+			changes = append(changes, c)
+		}
+		if err := it.Err(); err != nil {
+			if verror.ErrorID(err) == verror.ErrNoExist.ID {
+				return verror.New(verror.ErrNoExist, ctx, i.suffix)
+			}
+			return verror.New(errOperationFailed, ctx, i.suffix)
+		}
+		for _, change := range changes {
+			if err := call.SendStream().Send(change); err != nil {
+				return err
+			}
+		}
+		select {
+		case <-ctx.Done():
+			break Loop
+		case <-time.After(i.watchFreq):
+		}
+	}
+	return nil
+}
+
+// Value returns the value of the receiver object.
+func (i *statsService) Value(ctx *context.T, _ rpc.ServerCall) (*vdl.Value, error) {
+	ctx.VI(1).Infof("%v.Value()", i.suffix)
+
+	rv, err := libstats.Value(i.suffix)
+	switch {
+	case verror.ErrorID(err) == verror.ErrNoExist.ID:
+		return nil, verror.New(verror.ErrNoExist, ctx, i.suffix)
+	case verror.ErrorID(err) == stats.ErrNoValue.ID:
+		return nil, stats.NewErrNoValue(ctx, i.suffix)
+	case err != nil:
+		return nil, verror.New(errOperationFailed, ctx, i.suffix)
+	}
+	vv, err := vdl.ValueFromReflect(reflect.ValueOf(rv))
+	if err != nil {
+		return nil, verror.New(verror.ErrInternal, ctx, i.suffix, err)
+	}
+	return vv, nil
+}
diff --git a/services/internal/statslib/stats_test.go b/services/internal/statslib/stats_test.go
new file mode 100644
index 0000000..eccd497
--- /dev/null
+++ b/services/internal/statslib/stats_test.go
@@ -0,0 +1,172 @@
+// 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 statslib_test
+
+import (
+	"reflect"
+	"sort"
+	"testing"
+	"time"
+
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/security"
+	"v.io/v23/services/stats"
+	"v.io/v23/services/watch"
+	"v.io/v23/vdl"
+
+	libstats "v.io/x/ref/lib/stats"
+	"v.io/x/ref/lib/stats/histogram"
+	"v.io/x/ref/lib/xrpc"
+	"v.io/x/ref/services/internal/statslib"
+	s_stats "v.io/x/ref/services/stats"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/testutil"
+
+	_ "v.io/x/ref/runtime/factories/generic"
+)
+
+//go:generate v23 test generate
+
+type statsDispatcher struct {
+}
+
+func (d *statsDispatcher) Lookup(_ *context.T, suffix string) (interface{}, security.Authorizer, error) {
+	return statslib.NewStatsService(suffix, 100*time.Millisecond), nil, nil
+}
+
+func TestStatsImpl(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	server, err := xrpc.NewDispatchingServer(ctx, "", &statsDispatcher{})
+	if err != nil {
+		t.Fatalf("NewServer failed: %v", err)
+		return
+	}
+	endpoint := server.Status().Endpoints[0].String()
+
+	counter := libstats.NewCounter("testing/foo/bar")
+	counter.Incr(10)
+
+	histogram := libstats.NewHistogram("testing/hist/foo", histogram.Options{
+		NumBuckets:         5,
+		GrowthFactor:       1,
+		SmallestBucketSize: 1,
+		MinValue:           0,
+	})
+	for i := 0; i < 10; i++ {
+		histogram.Add(int64(i))
+	}
+
+	name := naming.JoinAddressName(endpoint, "")
+	c := stats.StatsClient(name)
+
+	// Test Glob()
+	{
+		results, _, err := testutil.GlobName(ctx, name, "testing/foo/...")
+		if err != nil {
+			t.Fatalf("testutil.GlobName failed: %v", err)
+		}
+		expected := []string{
+			"testing/foo",
+			"testing/foo/bar",
+			"testing/foo/bar/delta10m",
+			"testing/foo/bar/delta1h",
+			"testing/foo/bar/delta1m",
+			"testing/foo/bar/rate10m",
+			"testing/foo/bar/rate1h",
+			"testing/foo/bar/rate1m",
+		}
+		sort.Strings(results)
+		sort.Strings(expected)
+		if !reflect.DeepEqual(results, expected) {
+			t.Errorf("unexpected result. Got %v, want %v", results, expected)
+		}
+	}
+
+	// Test WatchGlob()
+	{
+		var noRM watch.ResumeMarker
+		ctx, cancel := context.WithCancel(ctx)
+		stream, err := c.WatchGlob(ctx, watch.GlobRequest{Pattern: "testing/foo/bar"})
+		if err != nil {
+			t.Fatalf("c.WatchGlob failed: %v", err)
+		}
+		iterator := stream.RecvStream()
+		if !iterator.Advance() {
+			t.Fatalf("expected more stream values")
+		}
+		got := iterator.Value()
+		expected := watch.Change{Name: "testing/foo/bar", Value: vdl.Int64Value(10), ResumeMarker: noRM}
+		if !reflect.DeepEqual(got, expected) {
+			t.Errorf("unexpected result. Got %#v, want %#v", got, expected)
+		}
+
+		counter.Incr(5)
+
+		if !iterator.Advance() {
+			t.Fatalf("expected more stream values")
+		}
+		got = iterator.Value()
+		expected = watch.Change{Name: "testing/foo/bar", Value: vdl.Int64Value(15), ResumeMarker: noRM}
+		if !reflect.DeepEqual(got, expected) {
+			t.Errorf("unexpected result. Got %#v, want %#v", got, expected)
+		}
+
+		counter.Incr(2)
+
+		if !iterator.Advance() {
+			t.Fatalf("expected more stream values")
+		}
+		got = iterator.Value()
+		expected = watch.Change{Name: "testing/foo/bar", Value: vdl.Int64Value(17), ResumeMarker: noRM}
+		if !reflect.DeepEqual(got, expected) {
+			t.Errorf("unexpected result. Got %#v, want %#v", got, expected)
+		}
+		cancel()
+
+		if iterator.Advance() {
+			t.Errorf("expected no more stream values, got: %v", iterator.Value())
+		}
+	}
+
+	// Test Value()
+	{
+		c := stats.StatsClient(naming.JoinAddressName(endpoint, "testing/foo/bar"))
+		value, err := c.Value(ctx)
+		if err != nil {
+			t.Errorf("unexpected error: %v", err)
+		}
+		if want := vdl.Int64Value(17); !vdl.EqualValue(value, want) {
+			t.Errorf("unexpected result. Got %v, want %v", value, want)
+		}
+	}
+
+	// Test Value() with Histogram
+	{
+		c := stats.StatsClient(naming.JoinAddressName(endpoint, "testing/hist/foo"))
+		value, err := c.Value(ctx)
+		if err != nil {
+			t.Errorf("unexpected error: %v", err)
+		}
+		want := vdl.ValueOf(s_stats.HistogramValue{
+			Count: 10,
+			Sum:   45,
+			Min:   0,
+			Max:   9,
+			Buckets: []s_stats.HistogramBucket{
+				s_stats.HistogramBucket{LowBound: 0, Count: 1},
+				s_stats.HistogramBucket{LowBound: 1, Count: 2},
+				s_stats.HistogramBucket{LowBound: 3, Count: 4},
+				s_stats.HistogramBucket{LowBound: 7, Count: 3},
+				s_stats.HistogramBucket{LowBound: 15, Count: 0},
+			},
+		})
+		if !vdl.EqualValue(value, want) {
+			t.Errorf("unexpected result. Got %v, want %v", value, want)
+		}
+	}
+}
diff --git a/services/internal/statslib/v23_internal_test.go b/services/internal/statslib/v23_internal_test.go
new file mode 100644
index 0000000..b34a92f
--- /dev/null
+++ b/services/internal/statslib/v23_internal_test.go
@@ -0,0 +1,20 @@
+// 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 statslib
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	os.Exit(m.Run())
+}
diff --git a/services/internal/vtracelib/v23_internal_test.go b/services/internal/vtracelib/v23_internal_test.go
new file mode 100644
index 0000000..2229a8d
--- /dev/null
+++ b/services/internal/vtracelib/v23_internal_test.go
@@ -0,0 +1,20 @@
+// 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 vtracelib
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	os.Exit(m.Run())
+}
diff --git a/services/internal/vtracelib/vtrace.go b/services/internal/vtracelib/vtrace.go
new file mode 100644
index 0000000..e1427a8
--- /dev/null
+++ b/services/internal/vtracelib/vtrace.go
@@ -0,0 +1,42 @@
+// 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 vtracelib
+
+import (
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	s_vtrace "v.io/v23/services/vtrace"
+	"v.io/v23/uniqueid"
+	"v.io/v23/verror"
+	"v.io/v23/vtrace"
+)
+
+type vtraceService struct{}
+
+func (v *vtraceService) Trace(ctx *context.T, _ rpc.ServerCall, id uniqueid.Id) (vtrace.TraceRecord, error) {
+	store := vtrace.GetStore(ctx)
+	tr := store.TraceRecord(id)
+	if tr == nil {
+		return vtrace.TraceRecord{}, verror.New(verror.ErrNoExist, ctx, "No trace with id %x", id)
+	}
+	return *tr, nil
+}
+
+func (v *vtraceService) AllTraces(ctx *context.T, call s_vtrace.StoreAllTracesServerCall) error {
+	// TODO(mattr): Consider changing the store to allow us to iterate through traces
+	// when there are many.
+	store := vtrace.GetStore(ctx)
+	traces := store.TraceRecords()
+	for i := range traces {
+		if err := call.SendStream().Send(traces[i]); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func NewVtraceService() interface{} {
+	return s_vtrace.StoreServer(&vtraceService{})
+}
diff --git a/services/internal/vtracelib/vtrace_test.go b/services/internal/vtracelib/vtrace_test.go
new file mode 100644
index 0000000..3848ad2
--- /dev/null
+++ b/services/internal/vtracelib/vtrace_test.go
@@ -0,0 +1,81 @@
+// 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 vtracelib_test
+
+import (
+	"io"
+	"testing"
+
+	s_vtrace "v.io/v23/services/vtrace"
+	"v.io/v23/vtrace"
+	"v.io/x/ref/lib/xrpc"
+	"v.io/x/ref/services/internal/vtracelib"
+	"v.io/x/ref/test"
+
+	_ "v.io/x/ref/runtime/factories/generic"
+)
+
+//go:generate v23 test generate
+
+func TestVtraceServer(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	server, err := xrpc.NewServer(ctx, "", vtracelib.NewVtraceService(), nil)
+	if err != nil {
+		t.Fatalf("Could not create server: %s", err)
+	}
+	endpoints := server.Status().Endpoints
+
+	sctx, span := vtrace.WithNewSpan(ctx, "The Span")
+	vtrace.ForceCollect(sctx)
+	span.Finish()
+	id := span.Trace()
+
+	client := s_vtrace.StoreClient(endpoints[0].Name())
+
+	sctx, _ = vtrace.WithNewTrace(sctx)
+	trace, err := client.Trace(sctx, id)
+	if err != nil {
+		t.Fatalf("Unexpected error getting trace: %s", err)
+	}
+	if len(trace.Spans) != 1 {
+		t.Errorf("Returned trace should have 1 span, found %#v", trace)
+	}
+	if trace.Spans[0].Name != "The Span" {
+		t.Errorf("Returned span has wrong name: %#v", trace)
+	}
+
+	sctx, _ = vtrace.WithNewTrace(sctx)
+	call, err := client.AllTraces(sctx)
+	if err != nil {
+		t.Fatalf("Unexpected error getting traces: %s", err)
+	}
+	ntraces := 0
+	stream := call.RecvStream()
+	var tr *vtrace.TraceRecord
+	for stream.Advance() {
+		trace := stream.Value()
+		if trace.Id == id {
+			tr = &trace
+		}
+		ntraces++
+	}
+	if err = stream.Err(); err != nil && err != io.EOF {
+		t.Fatalf("Unexpected error reading trace stream: %s", err)
+	}
+	if ntraces != 1 {
+		t.Fatalf("Expected 1 trace, got %#v", ntraces)
+	}
+	if tr == nil {
+		t.Fatalf("Desired trace %x not found.", id)
+	}
+	if len(tr.Spans) != 1 {
+		t.Errorf("Returned trace should have 1 span, found %#v", tr)
+	}
+	if tr.Spans[0].Name != "The Span" {
+		t.Fatalf("Returned span has wrong name: %#v", tr)
+	}
+}
diff --git a/services/mounttable/mounttabled/doc.go b/services/mounttable/mounttabled/doc.go
new file mode 100644
index 0000000..4deba0d
--- /dev/null
+++ b/services/mounttable/mounttabled/doc.go
@@ -0,0 +1,79 @@
+// 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
+
+/*
+Command mounttabled runs the mounttable daemon, which implements the
+v.io/v23/services/mounttable interfaces.
+
+Usage:
+   mounttabled [flags]
+
+The mounttabled flags are:
+ -acls=
+   AccessList file.  Default is to allow all access.
+ -name=
+   If provided, causes the mount table to mount itself under this name.  The
+   name may be absolute for a remote mount table service (e.g. "/<remote mt
+   address>//some/suffix") or could be relative to this process' default mount
+   table (e.g. "some/suffix").
+ -neighborhood-name=
+   If provided, enables sharing with the local neighborhood with the provided
+   name.  The address of this mounttable will be published to the neighboorhood
+   and everything in the neighborhood will be visible on this mounttable.
+ -persist-dir=
+   Directory in which to persist permissions.
+
+The global flags are:
+ -alsologtostderr=true
+   log to standard error as well as files
+ -log_backtrace_at=:0
+   when logging hits line file:N, emit a stack trace
+ -log_dir=
+   if non-empty, write log files to this directory
+ -logtostderr=false
+   log to standard error instead of files
+ -max_stack_buf_size=4292608
+   max size in bytes of the buffer to use for logging stack traces
+ -metadata=<just specify -metadata to activate>
+   Displays metadata for the program and exits.
+ -stderrthreshold=2
+   logs at or above this threshold go to stderr
+ -v=0
+   log level for V logs
+ -v23.credentials=
+   directory to use for storing security credentials
+ -v23.i18n-catalogue=
+   18n catalogue files to load, comma separated
+ -v23.namespace.root=[/(dev.v.io/role/vprod/service/mounttabled)@ns.dev.v.io:8101]
+   local namespace root; can be repeated to provided multiple roots
+ -v23.permissions.file=map[]
+   specify a perms file as <name>:<permsfile>
+ -v23.permissions.literal=
+   explicitly specify the runtime perms as a JSON-encoded access.Permissions.
+   Overrides all --v23.permissions.file flags.
+ -v23.proxy=
+   object name of proxy service to use to export services across network
+   boundaries
+ -v23.tcp.address=
+   address to listen on
+ -v23.tcp.protocol=wsh
+   protocol to listen with
+ -v23.vtrace.cache-size=1024
+   The number of vtrace traces to store in memory.
+ -v23.vtrace.collect-regexp=
+   Spans and annotations that match this regular expression will trigger trace
+   collection.
+ -v23.vtrace.dump-on-shutdown=true
+   If true, dump all stored traces on runtime shutdown.
+ -v23.vtrace.sample-rate=0
+   Rate (from 0.0 to 1.0) to sample vtrace traces.
+ -vmodule=
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
+*/
+package main
diff --git a/services/mounttable/mounttabled/mounttable.go b/services/mounttable/mounttabled/mounttable.go
new file mode 100644
index 0000000..72b8cef
--- /dev/null
+++ b/services/mounttable/mounttabled/mounttable.go
@@ -0,0 +1,58 @@
+// 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.
+
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go . -help
+
+package main
+
+import (
+	"fmt"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/signals"
+	"v.io/x/ref/lib/v23cmd"
+	_ "v.io/x/ref/runtime/factories/roaming"
+	"v.io/x/ref/services/mounttable/mounttablelib"
+)
+
+var mountName, aclFile, nhName, persistDir string
+
+func main() {
+	cmdMTD.Flags.StringVar(&mountName, "name", "", `If provided, causes the mount table to mount itself under this name.  The name may be absolute for a remote mount table service (e.g. "/<remote mt address>//some/suffix") or could be relative to this process' default mount table (e.g. "some/suffix").`)
+	cmdMTD.Flags.StringVar(&aclFile, "acls", "", "AccessList file.  Default is to allow all access.")
+	cmdMTD.Flags.StringVar(&nhName, "neighborhood-name", "", `If provided, enables sharing with the local neighborhood with the provided name.  The address of this mounttable will be published to the neighboorhood and everything in the neighborhood will be visible on this mounttable.`)
+	cmdMTD.Flags.StringVar(&persistDir, "persist-dir", "", `Directory in which to persist permissions.`)
+
+	cmdline.HideGlobalFlagsExcept()
+	cmdline.Main(cmdMTD)
+}
+
+var cmdMTD = &cmdline.Command{
+	Runner: v23cmd.RunnerFunc(runMountTableD),
+	Name:   "mounttabled",
+	Short:  "Runs the mounttable interface daemon",
+	Long: `
+Command mounttabled runs the mounttable daemon, which implements the
+v.io/v23/services/mounttable interfaces.
+`,
+}
+
+func runMountTableD(ctx *context.T, env *cmdline.Env, args []string) error {
+	name, stop, err := mounttablelib.StartServers(ctx, v23.GetListenSpec(ctx), mountName, nhName, aclFile, persistDir, "mounttable")
+	if err != nil {
+		return fmt.Errorf("mounttablelib.StartServers failed: %v", err)
+	}
+	defer stop()
+
+	// Print out a directly accessible name of the mount table so that
+	// integration tests can reliably read it from stdout.
+	fmt.Printf("NAME=%s\n", name)
+
+	// Wait until signal is received.
+	ctx.Info("Received signal ", <-signals.ShutdownOnSignals(ctx))
+	return nil
+}
diff --git a/services/mounttable/mounttabled/mounttabled_v23_test.go b/services/mounttable/mounttabled/mounttabled_v23_test.go
new file mode 100644
index 0000000..9b86b10
--- /dev/null
+++ b/services/mounttable/mounttabled/mounttabled_v23_test.go
@@ -0,0 +1,72 @@
+// 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_test
+
+import (
+	"fmt"
+	"os"
+	"regexp"
+
+	"v.io/x/ref"
+	"v.io/x/ref/test/v23tests"
+)
+
+//go:generate v23 test generate
+
+func getHostname(i *v23tests.T) string {
+	if hostname, err := os.Hostname(); err != nil {
+		i.Fatalf("Hostname() failed: %v", err)
+		return ""
+	} else {
+		return hostname
+	}
+}
+
+func binaryWithCredentials(i *v23tests.T, extension, pkgpath string) *v23tests.Binary {
+	creds, err := i.Shell().NewChildCredentials(extension)
+	if err != nil {
+		i.Fatalf("NewCustomCredentials (for %q) failed: %v", pkgpath, err)
+	}
+	b := i.BuildV23Pkg(pkgpath)
+	return b.WithStartOpts(b.StartOpts().WithCustomCredentials(creds))
+}
+
+func V23TestMount(i *v23tests.T) {
+	neighborhood := fmt.Sprintf("test-%s-%d", getHostname(i), os.Getpid())
+	v23tests.RunRootMT(i, "--v23.tcp.address=127.0.0.1:0", "--neighborhood-name="+neighborhood)
+
+	name, _ := i.GetVar(ref.EnvNamespacePrefix)
+	clientBin := binaryWithCredentials(i, "cmd", "v.io/x/ref/cmd/mounttable")
+
+	// Get the neighborhood endpoint from the mounttable.
+	neighborhoodEndpoint := clientBin.Start("glob", name, "nh").ExpectSetEventuallyRE(`^nh (.*) \(Deadline .*\)$`)[0][1]
+
+	if err := clientBin.Start("mount", name+"/myself", name, "5m").Wait(os.Stdout, os.Stderr); err != nil {
+		i.Fatalf("failed to mount the mounttable on itself: %v", err)
+	}
+	if err := clientBin.Start("mount", name+"/google", "/www.google.com:80", "5m").Wait(os.Stdout, os.Stderr); err != nil {
+		i.Fatalf("failed to mount www.google.com: %v", err)
+	}
+
+	// Test glob output. We expect three entries (two we mounted plus the
+	// neighborhood). The 'myself' entry should be the IP:port we
+	// specified for the mounttable.
+	glob := clientBin.Start("glob", name, "*")
+	matches := glob.ExpectSetEventuallyRE(
+		`^google /www\.google\.com:80 \(Deadline .*\)$`,
+		`^myself (.*) \(Deadline .*\)$`,
+		`^nh `+regexp.QuoteMeta(neighborhoodEndpoint)+` \(Deadline .*\)$`)
+	if matches[1][1] != name {
+		i.Fatalf("expected 'myself' entry to be %q, but was %q", name, matches[1][1])
+	}
+
+	// Test globbing on the neighborhood name. Its endpoint should be the
+	// endpoint of the mount table.
+	glob = clientBin.Start("glob", "/"+neighborhoodEndpoint, neighborhood)
+	matches = glob.ExpectSetEventuallyRE("^" + regexp.QuoteMeta(neighborhood) + ` (.*) \(Deadline .*\)$`)
+	if matches[0][1] != name {
+		i.Fatalf("expected endpoint of mount table for name %s", neighborhood)
+	}
+}
diff --git a/services/mounttable/mounttabled/v23_test.go b/services/mounttable/mounttabled/v23_test.go
new file mode 100644
index 0000000..b88590f
--- /dev/null
+++ b/services/mounttable/mounttabled/v23_test.go
@@ -0,0 +1,30 @@
+// 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 main_test
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
+	"v.io/x/ref/test/v23tests"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	modules.DispatchAndExitIfChild()
+	cleanup := v23tests.UseSharedBinDir()
+	r := m.Run()
+	cleanup()
+	os.Exit(r)
+}
+
+func TestV23Mount(t *testing.T) {
+	v23tests.RunTest(t, V23TestMount)
+}
diff --git a/services/mounttable/mounttablelib/collection_test_interface.vdl b/services/mounttable/mounttablelib/collection_test_interface.vdl
new file mode 100644
index 0000000..2795079
--- /dev/null
+++ b/services/mounttable/mounttablelib/collection_test_interface.vdl
@@ -0,0 +1,17 @@
+// 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 mounttablelib
+
+type Collection interface {
+	// Export sets the value for a name.  Overwrite controls the behavior when
+	// an entry exists, if Overwrite is true, then the binding is replaced,
+	// otherwise the call fails with an error.  The Val must be no larger than
+	// MaxSize bytes.
+	Export(Val string, Overwrite bool) error
+
+	// Lookup retrieves the value associated with a name.  Returns an error if
+	// there is no such binding.
+	Lookup() ([]byte | error)
+}
diff --git a/services/mounttable/mounttablelib/collection_test_interface.vdl.go b/services/mounttable/mounttablelib/collection_test_interface.vdl.go
new file mode 100644
index 0000000..bf634db
--- /dev/null
+++ b/services/mounttable/mounttablelib/collection_test_interface.vdl.go
@@ -0,0 +1,143 @@
+// 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 by the vanadium vdl tool.
+// Source: collection_test_interface.vdl
+
+package mounttablelib
+
+import (
+	// VDL system imports
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+)
+
+// CollectionClientMethods is the client interface
+// containing Collection methods.
+type CollectionClientMethods interface {
+	// Export sets the value for a name.  Overwrite controls the behavior when
+	// an entry exists, if Overwrite is true, then the binding is replaced,
+	// otherwise the call fails with an error.  The Val must be no larger than
+	// MaxSize bytes.
+	Export(ctx *context.T, Val string, Overwrite bool, opts ...rpc.CallOpt) error
+	// Lookup retrieves the value associated with a name.  Returns an error if
+	// there is no such binding.
+	Lookup(*context.T, ...rpc.CallOpt) ([]byte, error)
+}
+
+// CollectionClientStub adds universal methods to CollectionClientMethods.
+type CollectionClientStub interface {
+	CollectionClientMethods
+	rpc.UniversalServiceMethods
+}
+
+// CollectionClient returns a client stub for Collection.
+func CollectionClient(name string) CollectionClientStub {
+	return implCollectionClientStub{name}
+}
+
+type implCollectionClientStub struct {
+	name string
+}
+
+func (c implCollectionClientStub) Export(ctx *context.T, i0 string, i1 bool, opts ...rpc.CallOpt) (err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "Export", []interface{}{i0, i1}, nil, opts...)
+	return
+}
+
+func (c implCollectionClientStub) Lookup(ctx *context.T, opts ...rpc.CallOpt) (o0 []byte, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "Lookup", nil, []interface{}{&o0}, opts...)
+	return
+}
+
+// CollectionServerMethods is the interface a server writer
+// implements for Collection.
+type CollectionServerMethods interface {
+	// Export sets the value for a name.  Overwrite controls the behavior when
+	// an entry exists, if Overwrite is true, then the binding is replaced,
+	// otherwise the call fails with an error.  The Val must be no larger than
+	// MaxSize bytes.
+	Export(ctx *context.T, call rpc.ServerCall, Val string, Overwrite bool) error
+	// Lookup retrieves the value associated with a name.  Returns an error if
+	// there is no such binding.
+	Lookup(*context.T, rpc.ServerCall) ([]byte, error)
+}
+
+// CollectionServerStubMethods is the server interface containing
+// Collection methods, as expected by rpc.Server.
+// There is no difference between this interface and CollectionServerMethods
+// since there are no streaming methods.
+type CollectionServerStubMethods CollectionServerMethods
+
+// CollectionServerStub adds universal methods to CollectionServerStubMethods.
+type CollectionServerStub interface {
+	CollectionServerStubMethods
+	// Describe the Collection interfaces.
+	Describe__() []rpc.InterfaceDesc
+}
+
+// CollectionServer returns a server stub for Collection.
+// It converts an implementation of CollectionServerMethods into
+// an object that may be used by rpc.Server.
+func CollectionServer(impl CollectionServerMethods) CollectionServerStub {
+	stub := implCollectionServerStub{
+		impl: impl,
+	}
+	// Initialize GlobState; always check the stub itself first, to handle the
+	// case where the user has the Glob method defined in their VDL source.
+	if gs := rpc.NewGlobState(stub); gs != nil {
+		stub.gs = gs
+	} else if gs := rpc.NewGlobState(impl); gs != nil {
+		stub.gs = gs
+	}
+	return stub
+}
+
+type implCollectionServerStub struct {
+	impl CollectionServerMethods
+	gs   *rpc.GlobState
+}
+
+func (s implCollectionServerStub) Export(ctx *context.T, call rpc.ServerCall, i0 string, i1 bool) error {
+	return s.impl.Export(ctx, call, i0, i1)
+}
+
+func (s implCollectionServerStub) Lookup(ctx *context.T, call rpc.ServerCall) ([]byte, error) {
+	return s.impl.Lookup(ctx, call)
+}
+
+func (s implCollectionServerStub) Globber() *rpc.GlobState {
+	return s.gs
+}
+
+func (s implCollectionServerStub) Describe__() []rpc.InterfaceDesc {
+	return []rpc.InterfaceDesc{CollectionDesc}
+}
+
+// CollectionDesc describes the Collection interface.
+var CollectionDesc rpc.InterfaceDesc = descCollection
+
+// descCollection hides the desc to keep godoc clean.
+var descCollection = rpc.InterfaceDesc{
+	Name:    "Collection",
+	PkgPath: "v.io/x/ref/services/mounttable/mounttablelib",
+	Methods: []rpc.MethodDesc{
+		{
+			Name: "Export",
+			Doc:  "// Export sets the value for a name.  Overwrite controls the behavior when\n// an entry exists, if Overwrite is true, then the binding is replaced,\n// otherwise the call fails with an error.  The Val must be no larger than\n// MaxSize bytes.",
+			InArgs: []rpc.ArgDesc{
+				{"Val", ``},       // string
+				{"Overwrite", ``}, // bool
+			},
+		},
+		{
+			Name: "Lookup",
+			Doc:  "// Lookup retrieves the value associated with a name.  Returns an error if\n// there is no such binding.",
+			OutArgs: []rpc.ArgDesc{
+				{"", ``}, // []byte
+			},
+		},
+	},
+}
diff --git a/services/mounttable/mounttablelib/collectionserver_test.go b/services/mounttable/mounttablelib/collectionserver_test.go
new file mode 100644
index 0000000..1d725ff
--- /dev/null
+++ b/services/mounttable/mounttablelib/collectionserver_test.go
@@ -0,0 +1,66 @@
+// 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 mounttablelib_test
+
+import (
+	"sync"
+
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/verror"
+)
+
+// collectionServer is a very simple collection server implementation for testing, with sufficient debugging to help
+// when there are problems.
+type collectionServer struct {
+	sync.Mutex
+	contents map[string][]byte
+}
+type collectionDispatcher struct {
+	*collectionServer
+}
+type rpcContext struct {
+	name string
+	*collectionServer
+}
+
+var instance collectionServer
+
+func newCollectionServer() *collectionDispatcher {
+	return &collectionDispatcher{collectionServer: &collectionServer{contents: make(map[string][]byte)}}
+}
+
+// Lookup implements rpc.Dispatcher.Lookup.
+func (d *collectionDispatcher) Lookup(_ *context.T, name string) (interface{}, security.Authorizer, error) {
+	rpcc := &rpcContext{name: name, collectionServer: d.collectionServer}
+	return rpcc, d, nil
+}
+
+func (collectionDispatcher) Authorize(*context.T, security.Call) error {
+	return nil
+}
+
+// Export implements CollectionServerMethods.Export.
+func (c *rpcContext) Export(ctx *context.T, _ rpc.ServerCall, val []byte, overwrite bool) error {
+	c.Lock()
+	defer c.Unlock()
+	if b := c.contents[c.name]; overwrite || b == nil {
+		c.contents[c.name] = val
+		return nil
+	}
+	return verror.New(naming.ErrNameExists, ctx, c.name)
+}
+
+// Lookup implements CollectionServerMethods.Lookup.
+func (c *rpcContext) Lookup(ctx *context.T, _ rpc.ServerCall) ([]byte, error) {
+	c.Lock()
+	defer c.Unlock()
+	if val := c.contents[c.name]; val != nil {
+		return val, nil
+	}
+	return nil, verror.New(naming.ErrNoSuchName, ctx, c.name)
+}
diff --git a/services/mounttable/mounttablelib/mounttable.go b/services/mounttable/mounttablelib/mounttable.go
new file mode 100644
index 0000000..58cb1f2
--- /dev/null
+++ b/services/mounttable/mounttablelib/mounttable.go
@@ -0,0 +1,940 @@
+// 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 mounttablelib implements utilities for mounttable implementations.
+package mounttablelib
+
+import (
+	"os"
+	"reflect"
+	"runtime"
+	"strings"
+	"sync"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/glob"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/security/access"
+	"v.io/v23/services/mounttable"
+	"v.io/v23/verror"
+	"v.io/x/ref/lib/stats"
+)
+
+const pkgPath = "v.io/x/ref/services/mounttable/mounttablelib"
+
+const defaultMaxNodesPerUser = 1000
+const maxNameElementLen = 512
+
+var (
+	errMalformedAddress   = verror.Register(pkgPath+".errMalformedAddress", verror.NoRetry, "{1:}{2:} malformed address {3} for mounted server {4}{:_}")
+	errMTDoesntMatch      = verror.Register(pkgPath+".errMTDoesntMatch", verror.NoRetry, "{1:}{2:} MT doesn't match{:_}")
+	errLeafDoesntMatch    = verror.Register(pkgPath+".errLeafDoesntMatch", verror.NoRetry, "{1:}{2:} Leaf doesn't match{:_}")
+	errCantDeleteRoot     = verror.Register(pkgPath+".errCantDeleteRoot", verror.NoRetry, "{1:}{2:} cannot delete root node{:_}")
+	errNotEmpty           = verror.Register(pkgPath+".errNotEmpty", verror.NoRetry, "{1:}{2:} cannot delete {3}: has children{:_}")
+	errNamingLoop         = verror.Register(pkgPath+".errNamingLoop", verror.NoRetry, "{1:}{2:} Loop in namespace{:_}")
+	errTooManyNodes       = verror.Register(pkgPath+".errTooManyNodes", verror.NoRetry, "{1:}{2:} User has exceeded his node limit {:_}")
+	errNoSharedRoot       = verror.Register(pkgPath+".errNoSharedRoot", verror.NoRetry, "{1:}{2:} Server and User share no blessing root {:_}")
+	errNameElementTooLong = verror.Register(pkgPath+".errNameElementTooLong", verror.NoRetry, "{1:}{2:} path element {3}: too long {:_}")
+)
+
+var (
+	traverseTags = []mounttable.Tag{mounttable.Read, mounttable.Resolve, mounttable.Create, mounttable.Admin}
+	createTags   = []mounttable.Tag{mounttable.Create, mounttable.Admin}
+	removeTags   = []mounttable.Tag{mounttable.Admin}
+	mountTags    = []mounttable.Tag{mounttable.Mount, mounttable.Admin}
+	resolveTags  = []mounttable.Tag{mounttable.Read, mounttable.Resolve, mounttable.Admin}
+	globTags     = []mounttable.Tag{mounttable.Read, mounttable.Admin}
+	setTags      = []mounttable.Tag{mounttable.Admin}
+	getTags      = []mounttable.Tag{mounttable.Admin, mounttable.Read}
+	allTags      = []mounttable.Tag{mounttable.Read, mounttable.Resolve, mounttable.Admin, mounttable.Mount, mounttable.Create}
+)
+
+type persistence interface {
+	persistPerms(name, creator string, perm *VersionedPermissions) error
+	persistDelete(name string) error
+	close()
+}
+
+// mountTable represents a namespace.  One exists per server instance.
+type mountTable struct {
+	sync.Mutex
+	root               *node
+	superUsers         access.AccessList
+	persisting         bool
+	persist            persistence
+	nodeCounter        *stats.Integer
+	serverCounter      *stats.Integer
+	perUserNodeCounter *stats.Map
+	maxNodesPerUser    int64
+}
+
+var _ rpc.Dispatcher = (*mountTable)(nil)
+
+// mountContext represents a client bind.  The name is the name that was bound to.
+type mountContext struct {
+	name  string
+	elems []string // parsed elements of name
+	mt    *mountTable
+}
+
+// mount represents a single mount point.  It contains the rooted names of all servers mounted
+// here.  The servers are considered equivalent, i.e., RPCs to a name below this
+// point can be sent to any of these servers.
+type mount struct {
+	servers *serverList
+	mt      bool
+	leaf    bool
+}
+
+// node is a single point in the tree representing the mount table.
+type node struct {
+	sync.RWMutex
+	parent              *node
+	mount               *mount
+	children            map[string]*node
+	vPerms              *VersionedPermissions
+	permsTemplate       access.Permissions
+	explicitPermissions bool
+	creator             string
+}
+
+type callContext struct {
+	ctx          *context.T
+	call         security.Call
+	self         bool                        // true if client and server are the same.
+	rbn          []string                    // remote blessing names to avoid authenticating on every check.
+	rejected     []security.RejectedBlessing // rejected remote blessing names.
+	create       bool                        // true if we are to create traversed nodes.
+	creatorSet   bool                        // true if the creator string is already set.
+	creator      string
+	ignorePerms  bool
+	ignoreLimits bool
+}
+
+const createMissingNodes = true
+
+const templateVar = "%%"
+
+// NewMountTableDispatcher creates a new server that uses the AccessLists specified in
+// permissions file for authorization.
+//
+// permsFile is a JSON-encoded mapping from paths in the mounttable to the
+// access.Permissions for that path. The tags used in the map are the typical
+// access tags (the Tag type defined in v.io/v23/security/access).
+//
+// persistDir is the directory for persisting Permissions.
+//
+// statsPrefix is the prefix for for exported statistics objects.
+func NewMountTableDispatcher(ctx *context.T, permsFile, persistDir, statsPrefix string) (rpc.Dispatcher, error) {
+	mt := &mountTable{
+		root:               new(node),
+		nodeCounter:        stats.NewInteger(naming.Join(statsPrefix, "num-nodes")),
+		serverCounter:      stats.NewInteger(naming.Join(statsPrefix, "num-mounted-servers")),
+		perUserNodeCounter: stats.NewMap(naming.Join(statsPrefix, "num-nodes-per-user")),
+		maxNodesPerUser:    defaultMaxNodesPerUser,
+	}
+	mt.root.parent = mt.newNode() // just for its lock
+	if persistDir != "" {
+		mt.persist = newPersistentStore(ctx, mt, persistDir)
+		mt.persisting = mt.persist != nil
+	}
+	if err := mt.parsePermFile(ctx, permsFile); err != nil && !os.IsNotExist(err) {
+		return nil, err
+	}
+	return mt, nil
+}
+
+// newNode creates a new node, and updates the number of nodes.
+func (mt *mountTable) newNode() *node {
+	mt.nodeCounter.Incr(1)
+	return new(node)
+}
+
+// deleteNode deletes a node and all its children, and updates the number of
+// nodes.
+func (mt *mountTable) deleteNode(parent *node, child string) {
+	// Assumes that parent and parent[child] are locked.
+
+	// Walk the tree and count the number of nodes deleted.
+	first := parent.children[child]
+	if first == nil {
+		return
+	}
+	delete(parent.children, child)
+	mt.credit(first)
+	nodeCount := int64(0)
+	serverCount := int64(0)
+	queue := []*node{first}
+	for len(queue) > 0 {
+		n := queue[0]
+		queue = queue[1:]
+		nodeCount++
+		serverCount += numServers(n)
+		if n != first {
+			n.Lock()
+		}
+		for k, ch := range n.children {
+			queue = append(queue, ch)
+			delete(n.children, k)
+			mt.credit(ch)
+		}
+		if n != first {
+			n.Unlock()
+		}
+	}
+
+	mt.nodeCounter.Incr(-nodeCount)
+	mt.serverCounter.Incr(-serverCount)
+}
+
+// Lookup implements rpc.Dispatcher.Lookup.
+func (mt *mountTable) Lookup(ctx *context.T, name string) (interface{}, security.Authorizer, error) {
+	ctx.VI(2).Infof("*********************Lookup %s", name)
+	ms := &mountContext{
+		name: name,
+		mt:   mt,
+	}
+	if len(name) > 0 {
+		ms.elems = strings.Split(name, "/")
+	}
+	return mounttable.MountTableServer(ms), ms, nil
+}
+
+// isActive returns true if a mount has unexpired servers attached.
+func (m *mount) isActive(mt *mountTable) bool {
+	if m == nil {
+		return false
+	}
+	numLeft, numRemoved := m.servers.removeExpired()
+	if numRemoved > 0 {
+		mt.serverCounter.Incr(int64(-numRemoved))
+	}
+	return numLeft > 0
+}
+
+// satisfies returns no error if the ctx + n.vPerms satisfies the associated one of the required Tags.
+func (n *node) satisfies(mt *mountTable, cc *callContext, tags []mounttable.Tag) error {
+	// Nothing to check.
+	if cc.ignorePerms || tags == nil || n.vPerms == nil {
+		return nil
+	}
+	// "Self-RPCs" are always authorized.
+	if cc.self {
+		return nil
+	}
+	// Match client's blessings against the AccessLists.
+	for _, tag := range tags {
+		if al, exists := n.vPerms.AccessListForTag(string(tag)); exists && al.Includes(cc.rbn...) {
+			return nil
+		}
+	}
+	if mt.superUsers.Includes(cc.rbn...) {
+		return nil
+	}
+	if len(cc.rejected) > 0 {
+		return verror.New(verror.ErrNoAccess, cc.ctx, cc.rbn, cc.rejected)
+	}
+	return verror.New(verror.ErrNoAccess, cc.ctx, cc.rbn)
+}
+
+func expand(al *access.AccessList, name string) *access.AccessList {
+	newAccessList := new(access.AccessList)
+	for _, bp := range al.In {
+		newAccessList.In = append(newAccessList.In, security.BlessingPattern(strings.Replace(string(bp), templateVar, name, -1)))
+	}
+	for _, bp := range al.NotIn {
+		newAccessList.NotIn = append(newAccessList.NotIn, strings.Replace(bp, templateVar, name, -1))
+	}
+	return newAccessList
+}
+
+// satisfiesTemplate returns no error if the ctx + n.permsTemplate satisfies the associated one of
+// the required Tags.
+func (n *node) satisfiesTemplate(cc *callContext, tags []mounttable.Tag, name string) error {
+	// If no template, ignore.
+	if cc.ignorePerms || n.permsTemplate == nil {
+		return nil
+	}
+	// Match client's blessings against the AccessLists.
+	for _, tag := range tags {
+		if al, exists := n.permsTemplate[string(tag)]; exists && expand(&al, name).Includes(cc.rbn...) {
+			return nil
+		}
+	}
+	return verror.New(verror.ErrNoAccess, cc.ctx, cc.rbn, cc.rejected)
+}
+
+// CopyPermissions copies one node's permissions to another and adds the clients blessings as
+// patterns to the Admin tag.
+func CopyPermissions(cc *callContext, cur *node) *VersionedPermissions {
+	if cur.vPerms == nil {
+		return nil
+	}
+	vPerms := cur.vPerms.Copy()
+	if cc.rbn == nil {
+		return vPerms
+	}
+	for _, b := range cc.rbn {
+		vPerms.Add(security.BlessingPattern(b), string(mounttable.Admin))
+	}
+	vPerms.P.Normalize()
+	return vPerms
+}
+
+// createVersionedPermissionsFromTemplate creates a new VersionedPermissions from the template subsituting name for %% everywhere.
+func createVersionedPermissionsFromTemplate(perms access.Permissions, name string) *VersionedPermissions {
+	vPerms := NewVersionedPermissions()
+	for tag, al := range perms {
+		vPerms.P[tag] = *expand(&al, name)
+	}
+	return vPerms
+}
+
+// traverse returns the node for the path represented by elems.  If none exists and create is false, return nil.
+// Otherwise create the path and return a pointer to the terminal node.  If a mount point is encountered
+// while following the path, return that node and any remaining elems.
+//
+// If it returns a node, both the node and its parent are locked.
+func (mt *mountTable) traverse(cc *callContext, elems []string) (*node, []string, error) {
+	// Invariant is that the current node and its parent are both locked.
+	cur := mt.root
+	cur.parent.Lock()
+	cur.Lock()
+	for i, e := range elems {
+		cc.ctx.VI(2).Infof("satisfying %v %v", elems[0:i], *cur)
+		if err := cur.satisfies(mt, cc, traverseTags); err != nil {
+			cur.parent.Unlock()
+			cur.Unlock()
+			return nil, nil, err
+		}
+		// If we hit another mount table, we're done.
+		if cur.mount.isActive(mt) {
+			return cur, elems[i:], nil
+		}
+		// Walk the children looking for a match.
+		c, ok := cur.children[e]
+		if ok {
+			cur.parent.Unlock()
+			cur = c
+			cur.Lock()
+			continue
+		}
+		if !cc.create {
+			cur.parent.Unlock()
+			cur.Unlock()
+			return nil, nil, nil
+		}
+		// Create a new node and keep recursing.
+		cur.parent.Unlock()
+		if err := cur.satisfies(mt, cc, createTags); err != nil {
+			cur.Unlock()
+			return nil, nil, err
+		}
+		if err := cur.satisfiesTemplate(cc, createTags, e); err != nil {
+			cur.Unlock()
+			return nil, nil, err
+		}
+		// Obey account limits.
+		var err error
+		if err = mt.debit(cc); err != nil {
+			cur.Unlock()
+			return nil, nil, err
+		}
+		// At this point cur is still locked, OK to use and change it.
+		next := mt.newNode()
+		next.creator = cc.creator
+		next.parent = cur
+		if cur.permsTemplate != nil {
+			next.vPerms = createVersionedPermissionsFromTemplate(cur.permsTemplate, e)
+		} else {
+			next.vPerms = CopyPermissions(cc, cur)
+		}
+		if cur.children == nil {
+			cur.children = make(map[string]*node)
+		}
+		cur.children[e] = next
+		cur = next
+		cur.Lock()
+	}
+	// Only way out of the loop is via a return or exhausting all elements.  In
+	// the latter case both cur and cur.parent are locked.
+	return cur, nil, nil
+}
+
+// findNode finds a node in the table and optionally creates a path to it.
+//
+// If a node is found, on return it and its parent are locked.
+func (mt *mountTable) findNode(cc *callContext, elems []string, tags, ptags []mounttable.Tag) (*node, error) {
+	n, nelems, err := mt.traverse(cc, elems)
+	if err != nil {
+		return nil, err
+	}
+	if n == nil {
+		return nil, nil
+	}
+	if len(nelems) > 0 {
+		n.parent.Unlock()
+		n.Unlock()
+		return nil, nil
+	}
+	// Either the node has to satisfy tags or the parent has to satisfy ptags.
+	if err := n.satisfies(mt, cc, tags); err != nil {
+		if ptags == nil {
+			n.parent.Unlock()
+			n.Unlock()
+			return nil, err
+		}
+		if err := n.parent.satisfies(mt, cc, ptags); err != nil {
+			n.parent.Unlock()
+			n.Unlock()
+			return nil, err
+		}
+	}
+	return n, nil
+}
+
+// findMountPoint returns the first mount point encountered in the path and
+// any elements remaining of the path.
+//
+// If a mountpoint is found, on return it and its parent are locked.
+func (mt *mountTable) findMountPoint(cc *callContext, elems []string) (*node, []string, error) {
+	n, nelems, err := mt.traverse(cc, elems)
+	if err != nil {
+		return nil, nil, err
+	}
+	if n == nil {
+		return nil, nil, nil
+	}
+	// If we can't resolve it, we can't use it.
+	if err := n.satisfies(mt, cc, resolveTags); err != nil {
+		n.parent.Unlock()
+		n.Unlock()
+		return nil, nil, err
+	}
+	if !n.mount.isActive(mt) {
+		removed := n.removeUseless(mt)
+		n.parent.Unlock()
+		n.Unlock()
+		// If we removed the node, see if we can remove any of its
+		// ascendants.
+		if removed && len(elems) > 0 {
+			mt.removeUselessRecursive(cc, elems[:len(elems)-1])
+		}
+		return nil, nil, nil
+	}
+	return n, nelems, nil
+}
+
+// Authorize verifies that the client has access to the requested node.
+// Since we do the check at the time of access, we always return OK here.
+func (ms *mountContext) Authorize(*context.T, security.Call) error {
+	return nil
+}
+
+// ResolveStep returns the next server in a resolution in the form of a MountEntry.  The name
+// in the mount entry is the name relative to the server's root.
+func (ms *mountContext) ResolveStep(ctx *context.T, call rpc.ServerCall) (entry naming.MountEntry, err error) {
+	ctx.VI(2).Infof("ResolveStep %q", ms.name)
+	cc := newCallContext(ctx, call.Security(), !createMissingNodes)
+	mt := ms.mt
+	// Find the next mount point for the name.
+	n, elems, werr := mt.findMountPoint(cc, ms.elems)
+	if werr != nil {
+		err = werr
+		return
+	}
+	if n == nil {
+		entry.Name = ms.name
+		if len(ms.elems) == 0 {
+			err = verror.New(naming.ErrNoSuchNameRoot, ctx, ms.name)
+		} else {
+			err = verror.New(naming.ErrNoSuchName, ctx, ms.name)
+		}
+		return
+	}
+	n.parent.Unlock()
+	defer n.Unlock()
+	entry.Servers = n.mount.servers.copyToSlice()
+	entry.Name = strings.Join(elems, "/")
+	entry.ServesMountTable = n.mount.mt
+	entry.IsLeaf = n.mount.leaf
+	return
+}
+
+func hasMTFlag(flags naming.MountFlag) bool {
+	return (flags & naming.MT) == naming.MT
+}
+
+func hasLeafFlag(flags naming.MountFlag) bool {
+	return (flags & naming.Leaf) == naming.Leaf
+}
+
+func hasReplaceFlag(flags naming.MountFlag) bool {
+	return (flags & naming.Replace) == naming.Replace
+}
+
+func numServers(n *node) int64 {
+	if n == nil || n.mount == nil || n.mount.servers == nil {
+		return 0
+	}
+	return int64(n.mount.servers.len())
+}
+
+// This isn't a storage system.
+func checkElementLengths(ctx *context.T, elems []string) error {
+	for _, e := range elems {
+		if len(e) > maxNameElementLen {
+			return verror.New(errNameElementTooLong, ctx, e)
+		}
+	}
+	return nil
+}
+
+
+// Mount a server onto the name in the receiver.
+func (ms *mountContext) Mount(ctx *context.T, call rpc.ServerCall, server string, ttlsecs uint32, flags naming.MountFlag) error {
+	ctx.VI(2).Infof("*********************Mount %q -> %s", ms.name, server)
+	if err := checkElementLengths(ctx, ms.elems); err != nil {
+		return err
+	}
+	cc := newCallContext(ctx, call.Security(), createMissingNodes)
+	mt := ms.mt
+	if ttlsecs == 0 {
+		ttlsecs = 10 * 365 * 24 * 60 * 60 // a really long time
+	}
+
+	// Make sure the server address is reasonable.
+	epString := server
+	if naming.Rooted(server) {
+		epString, _ = naming.SplitAddressName(server)
+	}
+	_, err := v23.NewEndpoint(epString)
+	if err != nil {
+		return verror.New(errMalformedAddress, ctx, epString, server)
+	}
+
+	// Find/create node in namespace and add the mount.
+	n, werr := mt.findNode(cc, ms.elems, mountTags, nil)
+	if werr != nil {
+		return werr
+	}
+	if n == nil {
+		return verror.New(naming.ErrNoSuchNameRoot, ctx, ms.name)
+	}
+	// We don't need the parent lock
+	n.parent.Unlock()
+	defer n.Unlock()
+
+	wantMT := hasMTFlag(flags)
+	wantLeaf := hasLeafFlag(flags)
+	if n.mount != nil {
+		if wantMT != n.mount.mt {
+			return verror.New(errMTDoesntMatch, ctx)
+		}
+		if wantLeaf != n.mount.leaf {
+			return verror.New(errLeafDoesntMatch, ctx)
+		}
+	}
+	// Remove any existing children.
+	for child := range n.children {
+		mt.deleteNode(n, child)
+	}
+
+	nServersBefore := numServers(n)
+	if hasReplaceFlag(flags) {
+		n.mount = nil
+	}
+	if n.mount == nil {
+		n.mount = &mount{servers: newServerList(), mt: wantMT, leaf: wantLeaf}
+	}
+	n.mount.servers.add(server, time.Duration(ttlsecs)*time.Second)
+	mt.serverCounter.Incr(numServers(n) - nServersBefore)
+	return nil
+}
+
+// fullName is for debugging only and should not normally be called.
+func (n *node) fullName() string {
+	if n.parent == nil || n.parent.parent == nil {
+		return ""
+	}
+	for k, c := range n.parent.children {
+		if c == n {
+			return n.parent.fullName() + "/" + k
+		}
+	}
+	return n.parent.fullName() + "/" + "?"
+}
+
+// removeUseless removes a node and all of its ascendants that are not useful.
+//
+// We assume both n and n.parent are locked.
+func (n *node) removeUseless(mt *mountTable) bool {
+	if len(n.children) > 0 || n.mount.isActive(mt) || n.explicitPermissions {
+		return false
+	}
+	for k, c := range n.parent.children {
+		if c == n {
+			mt.deleteNode(n.parent, k)
+			break
+		}
+	}
+	return true
+}
+
+// removeUselessRecursive removes any useless nodes on the tail of the path.
+func (mt *mountTable) removeUselessRecursive(cc *callContext, elems []string) {
+	for i := len(elems); i > 0; i-- {
+		n, nelems, _ := mt.traverse(cc, elems[:i])
+		if n == nil {
+			break
+		}
+		if nelems != nil {
+			n.parent.Unlock()
+			n.Unlock()
+			break
+		}
+		removed := n.removeUseless(mt)
+		n.parent.Unlock()
+		n.Unlock()
+		if !removed {
+			break
+		}
+	}
+}
+
+// Unmount removes servers from the name in the receiver. If server is specified, only that
+// server is removed.
+func (ms *mountContext) Unmount(ctx *context.T, call rpc.ServerCall, server string) error {
+	ctx.VI(2).Infof("*********************Unmount %q, %s", ms.name, server)
+	cc := newCallContext(ctx, call.Security(), !createMissingNodes)
+	mt := ms.mt
+	n, err := mt.findNode(cc, ms.elems, mountTags, nil)
+	if err != nil {
+		return err
+	}
+	if n == nil {
+		return nil
+	}
+	nServersBefore := numServers(n)
+	if server == "" {
+		n.mount = nil
+	} else if n.mount != nil && n.mount.servers.remove(server) == 0 {
+		n.mount = nil
+	}
+	mt.serverCounter.Incr(numServers(n) - nServersBefore)
+	removed := n.removeUseless(mt)
+	n.parent.Unlock()
+	n.Unlock()
+	if removed {
+		// If we removed the node, see if we can also remove
+		// any of its ascendants.
+		mt.removeUselessRecursive(cc, ms.elems[:len(ms.elems)-1])
+	}
+	return nil
+}
+
+// Delete removes the receiver.  If all is true, any subtree is also removed.
+func (ms *mountContext) Delete(ctx *context.T, call rpc.ServerCall, deleteSubTree bool) error {
+	ctx.VI(2).Infof("*********************Delete %q, %v", ms.name, deleteSubTree)
+	cc := newCallContext(ctx, call.Security(), !createMissingNodes)
+	if len(ms.elems) == 0 {
+		// We can't delete the root.
+		return verror.New(errCantDeleteRoot, ctx)
+	}
+	mt := ms.mt
+	// Find and lock the parent node and parent node.  Either the node or its parent has
+	// to satisfy removeTags.
+	n, err := mt.findNode(cc, ms.elems, removeTags, removeTags)
+	if err != nil {
+		return err
+	}
+	if n == nil {
+		return nil
+	}
+	defer n.parent.Unlock()
+	defer n.Unlock()
+	if !deleteSubTree && len(n.children) > 0 {
+		return verror.New(errNotEmpty, ctx, ms.name)
+	}
+	mt.deleteNode(n.parent, ms.elems[len(ms.elems)-1])
+	if mt.persisting {
+		mt.persist.persistDelete(ms.name)
+	}
+	return nil
+}
+
+// A struct holding a partial result of Glob.
+type globEntry struct {
+	n    *node
+	name string
+}
+
+// globStep is called with n and n.parent locked.  Returns with both unlocked.
+func (mt *mountTable) globStep(cc *callContext, n *node, name string, pattern *glob.Glob, gCall rpc.GlobServerCall) {
+	if shouldAbort(cc) {
+		n.parent.Unlock()
+		n.Unlock()
+		return
+	}
+	cc.ctx.VI(2).Infof("globStep(%s, %s)", name, pattern)
+
+	// Globing is the lowest priority so we give up the cpu often.
+	runtime.Gosched()
+
+	// If this is a mount point, we're done.
+	if m := n.mount; m != nil {
+		removed := n.removeUseless(mt)
+		if removed {
+			n.parent.Unlock()
+			n.Unlock()
+			return
+		}
+		// Don't need the parent lock anymore.
+		n.parent.Unlock()
+		me := naming.MountEntry{
+			Name: name,
+		}
+		// Only fill in the mount info if we can resolve this name.
+		if err := n.satisfies(mt, cc, resolveTags); err == nil {
+			me.Servers = m.servers.copyToSlice()
+			me.ServesMountTable = n.mount.mt
+			me.IsLeaf = n.mount.leaf
+		} else {
+			me.Servers = []naming.MountedServer{}
+		}
+		// Hold no locks while we are sending on the channel to avoid livelock.
+		n.Unlock()
+		gCall.SendStream().Send(naming.GlobReplyEntry{Value: me})
+		return
+	}
+
+	if !pattern.Empty() {
+		// We can only list children to whom we have some access AND either
+		// - we have Read or Admin access to the directory or
+		// - we have Resolve or Create access to the directory and the
+		//    next element in the pattern is a fixed string.
+		if err := n.satisfies(mt, cc, globTags); err != nil {
+			if err := n.satisfies(mt, cc, traverseTags); err != nil {
+				goto out
+			}
+			fixed, _ := pattern.SplitFixedElements()
+			if len(fixed) == 0 {
+				goto out
+			}
+		}
+
+		// Since we will be unlocking the node,
+		// we need to grab the list of children before any unlocking.
+		children := make(map[string]*node, len(n.children))
+		for k, c := range n.children {
+			children[k] = c
+		}
+		n.parent.Unlock()
+
+		// Recurse through the children.
+		matcher, suffix := pattern.Head(), pattern.Tail()
+		for k, c := range children {
+			if shouldAbort(cc) {
+				n.Unlock()
+				return
+			}
+			// At this point, n lock is held.
+			if matcher.Match(k) {
+				c.Lock()
+				// If child allows any access show it.  Otherwise, skip.
+				if err := c.satisfies(mt, cc, allTags); err != nil {
+					c.Unlock()
+					continue
+				}
+				mt.globStep(cc, c, naming.Join(name, k), suffix, gCall)
+				n.Lock()
+			}
+		}
+		// Relock the node and its parent in the correct order to avoid deadlock.
+		// Safe to access n.parent when its unlocked because it never changes.
+		n.Unlock()
+		n.parent.Lock()
+		n.Lock()
+	}
+
+out:
+	// Remove if no longer useful.
+	if n.removeUseless(mt) || pattern.Len() != 0 {
+		n.parent.Unlock()
+		n.Unlock()
+		return
+	}
+
+	// To see anything, one has to have some access to the node.  Don't need the parent lock anymore.
+	n.parent.Unlock()
+	if err := n.satisfies(mt, cc, allTags); err != nil {
+		n.Unlock()
+		return
+	}
+	// Hold no locks while we are sending on the channel to avoid livelock.
+	n.Unlock()
+	// Intermediate nodes are marked as serving a mounttable since they answer the mounttable methods.
+	gCall.SendStream().Send(naming.GlobReplyEntry{Value: naming.MountEntry{Name: name, ServesMountTable: true}})
+}
+
+// Glob finds matches in the namespace.  If we reach a mount point before matching the
+// whole pattern, return that mount point.
+//
+// pattern is a glob pattern as defined by the v.io/x/ref/lib/glob package.
+//
+// To avoid livelocking an application, Glob grabs and releases locks as it descends the tree
+// and holds no locks while writing to the channel.  As such a glob can interleave with other
+// operations that add or remove nodes.  The result returned by glob may, therefore, represent
+// a state that never existed in the mounttable.  For example, if someone removes c/d and later
+// adds a/b while a Glob is in progress, the Glob may return a set of nodes that includes both
+// c/d and a/b.
+func (ms *mountContext) Glob__(ctx *context.T, call rpc.GlobServerCall, g *glob.Glob) error {
+	ctx.VI(2).Infof("mt.Glob %v", ms.elems)
+	cc := newCallContext(ctx, call.Security(), !createMissingNodes)
+
+	mt := ms.mt
+	// If there was an access error, just ignore the entry, i.e., make it invisible.
+	n, err := mt.findNode(cc, ms.elems, nil, nil)
+	if err != nil {
+		return nil
+	}
+	// If the current name is not fully resolvable on this nameserver we
+	// don't need to evaluate the glob expression. Send a partially resolved
+	// name back to the client.
+	if n == nil {
+		ms.linkToLeaf(cc, call)
+		return nil
+	}
+	mt.globStep(cc, n, "", g, call)
+	return nil
+}
+
+func (ms *mountContext) linkToLeaf(cc *callContext, gCall rpc.GlobServerCall) {
+	n, elems, err := ms.mt.findMountPoint(cc, ms.elems)
+	if err != nil || n == nil {
+		return
+	}
+	n.parent.Unlock()
+	servers := n.mount.servers.copyToSlice()
+	for i, s := range servers {
+		servers[i].Server = naming.Join(s.Server, strings.Join(elems, "/"))
+	}
+	n.Unlock()
+	gCall.SendStream().Send(naming.GlobReplyEntry{Value: naming.MountEntry{Name: "", Servers: servers}})
+}
+
+func (ms *mountContext) SetPermissions(ctx *context.T, call rpc.ServerCall, perms access.Permissions, version string) error {
+	ctx.VI(2).Infof("SetPermissions %q", ms.name)
+	if err := checkElementLengths(ctx, ms.elems); err != nil {
+		return err
+	}
+	cc := newCallContext(ctx, call.Security(), createMissingNodes)
+	mt := ms.mt
+
+	// Find/create node in namespace and add the mount.
+	n, err := mt.findNode(cc, ms.elems, setTags, nil)
+	if err != nil {
+		return err
+	}
+	if n == nil {
+		// TODO(p): can this even happen?
+		return verror.New(naming.ErrNoSuchName, ctx, ms.name)
+	}
+	n.parent.Unlock()
+	defer n.Unlock()
+
+	// If the caller is trying to add a Permission that they are no longer Admin in,
+	// retain the caller's blessings that were in Admin to prevent them from locking themselves out.
+	if al, ok := perms[string(mounttable.Admin)]; !ok || !al.Includes(cc.rbn...) {
+		_, oldPerms := n.vPerms.Get()
+		if oldPerms == nil {
+			for _, bname := range cc.rbn {
+				perms.Add(security.BlessingPattern(bname), string(mounttable.Admin))
+			}
+		} else {
+			oldAl := oldPerms[string(mounttable.Admin)]
+			for _, bname := range cc.rbn {
+				if oldAl.Includes(bname) {
+					perms.Add(security.BlessingPattern(bname), string(mounttable.Admin))
+				}
+			}
+		}
+	}
+	perms.Normalize()
+
+	n.vPerms, err = n.vPerms.Set(ctx, version, perms)
+	if err == nil {
+		if mt.persisting {
+			mt.persist.persistPerms(ms.name, n.creator, n.vPerms)
+		}
+		n.explicitPermissions = true
+	}
+	return err
+}
+
+func (ms *mountContext) GetPermissions(ctx *context.T, call rpc.ServerCall) (access.Permissions, string, error) {
+	ctx.VI(2).Infof("GetPermissions %q", ms.name)
+	cc := newCallContext(ctx, call.Security(), !createMissingNodes)
+	mt := ms.mt
+
+	// Find node in namespace and add the mount.
+	n, err := mt.findNode(cc, ms.elems, getTags, nil)
+	if err != nil {
+		return nil, "", err
+	}
+	if n == nil {
+		return nil, "", verror.New(naming.ErrNoSuchName, ctx, ms.name)
+	}
+	n.parent.Unlock()
+	defer n.Unlock()
+	version, perms := n.vPerms.Get()
+	return perms, version, nil
+}
+
+// credit user for node deletion.
+func (mt *mountTable) credit(n *node) {
+	mt.perUserNodeCounter.Incr(n.creator, -1)
+}
+
+// debit user for node creation.
+func (mt *mountTable) debit(cc *callContext) error {
+	// Cache any creator we pick so that we don't do it again.
+	if !cc.creatorSet {
+		cc.creator = mt.pickCreator(cc.ctx, cc.call)
+		cc.creatorSet = true
+	}
+	count, ok := mt.perUserNodeCounter.Incr(cc.creator, 1).(int64)
+	if !ok {
+		return verror.New(errTooManyNodes, cc.ctx)
+	}
+	if count > mt.maxNodesPerUser && !cc.ignoreLimits {
+		mt.perUserNodeCounter.Incr(cc.creator, -1)
+		return verror.New(errTooManyNodes, cc.ctx)
+	}
+	return nil
+}
+
+func shouldAbort(cc *callContext) bool {
+	select {
+	case <-cc.ctx.Done():
+		return true
+	default:
+		return false
+	}
+}
+
+func newCallContext(ctx *context.T, call security.Call, create bool) *callContext {
+	cc := &callContext{ctx: ctx, call: call, create: create}
+	if call != nil {
+		if l, r := cc.call.LocalBlessings().PublicKey(), cc.call.RemoteBlessings().PublicKey(); l != nil && reflect.DeepEqual(l, r) {
+			cc.self = true
+		}
+		cc.rbn, cc.rejected = security.RemoteBlessingNames(ctx, call)
+	}
+	return cc
+}
diff --git a/services/mounttable/mounttablelib/mounttable_test.go b/services/mounttable/mounttablelib/mounttable_test.go
new file mode 100644
index 0000000..d72fc39
--- /dev/null
+++ b/services/mounttable/mounttablelib/mounttable_test.go
@@ -0,0 +1,921 @@
+// 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 mounttablelib_test
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io"
+	"reflect"
+	"runtime/debug"
+	"sort"
+	"testing"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/conventions"
+	"v.io/v23/glob"
+	"v.io/v23/naming"
+	"v.io/v23/options"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/security/access"
+	"v.io/v23/services/mounttable"
+	"v.io/v23/services/stats"
+	"v.io/v23/vdl"
+
+	libstats "v.io/x/ref/lib/stats"
+	"v.io/x/ref/lib/xrpc"
+	"v.io/x/ref/services/debug/debuglib"
+	"v.io/x/ref/services/mounttable/mounttablelib"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/testutil"
+)
+
+func init() {
+	test.Init()
+}
+
+// Simulate different processes with different runtimes.
+// rootCtx is the one running the mounttable service.
+const ttlSecs = 60 * 60
+
+func boom(t *testing.T, f string, v ...interface{}) {
+	t.Logf(f, v...)
+	t.Fatal(string(debug.Stack()))
+}
+
+func doMount(t *testing.T, ctx *context.T, ep, suffix, service string, shouldSucceed bool) {
+	name := naming.JoinAddressName(ep, suffix)
+	client := v23.GetClient(ctx)
+	if err := client.Call(ctx, name, "Mount", []interface{}{service, uint32(ttlSecs), 0}, nil, options.NoResolve{}); err != nil {
+		if !shouldSucceed {
+			return
+		}
+		boom(t, "Failed to Mount %s onto %s: %s", service, name, err)
+	}
+}
+
+func doUnmount(t *testing.T, ctx *context.T, ep, suffix, service string, shouldSucceed bool) {
+	name := naming.JoinAddressName(ep, suffix)
+	client := v23.GetClient(ctx)
+	if err := client.Call(ctx, name, "Unmount", []interface{}{service}, nil, options.NoResolve{}); err != nil {
+		if !shouldSucceed {
+			return
+		}
+		boom(t, "Failed to Unmount %s off of %s: %s", service, name, err)
+	}
+}
+
+func doGetPermissions(t *testing.T, ctx *context.T, ep, suffix string, shouldSucceed bool) (perms access.Permissions, version string) {
+	name := naming.JoinAddressName(ep, suffix)
+	client := v23.GetClient(ctx)
+	if err := client.Call(ctx, name, "GetPermissions", nil, []interface{}{&perms, &version}, options.NoResolve{}); err != nil {
+		if !shouldSucceed {
+			return
+		}
+		boom(t, "Failed to GetPermissions %s: %s", name, err)
+	}
+	return
+}
+
+func doSetPermissions(t *testing.T, ctx *context.T, ep, suffix string, perms access.Permissions, version string, shouldSucceed bool) {
+	name := naming.JoinAddressName(ep, suffix)
+	client := v23.GetClient(ctx)
+	if err := client.Call(ctx, name, "SetPermissions", []interface{}{perms, version}, nil, options.NoResolve{}); err != nil {
+		if !shouldSucceed {
+			return
+		}
+		boom(t, "Failed to SetPermissions %s: %s", name, err)
+	}
+}
+
+func doDeleteNode(t *testing.T, ctx *context.T, ep, suffix string, shouldSucceed bool) {
+	name := naming.JoinAddressName(ep, suffix)
+	client := v23.GetClient(ctx)
+	if err := client.Call(ctx, name, "Delete", []interface{}{false}, nil, options.NoResolve{}); err != nil {
+		if !shouldSucceed {
+			return
+		}
+		boom(t, "Failed to Delete node %s: %s", name, err)
+	}
+}
+
+func doDeleteSubtree(t *testing.T, ctx *context.T, ep, suffix string, shouldSucceed bool) {
+	name := naming.JoinAddressName(ep, suffix)
+	client := v23.GetClient(ctx)
+	if err := client.Call(ctx, name, "Delete", []interface{}{true}, nil, options.NoResolve{}); err != nil {
+		if !shouldSucceed {
+			return
+		}
+		boom(t, "Failed to Delete subtree %s: %s", name, err)
+	}
+}
+
+func mountentry2names(e *naming.MountEntry) []string {
+	names := make([]string, len(e.Servers))
+	for idx, s := range e.Servers {
+		names[idx] = naming.JoinAddressName(s.Server, e.Name)
+	}
+	return names
+}
+
+func strslice(strs ...string) []string {
+	return strs
+}
+
+func resolve(ctx *context.T, name string) (*naming.MountEntry, error) {
+	// Resolve the name one level.
+	var entry naming.MountEntry
+	client := v23.GetClient(ctx)
+	if err := client.Call(ctx, name, "ResolveStep", nil, []interface{}{&entry}, options.NoResolve{}); err != nil {
+		return nil, err
+	}
+	if len(entry.Servers) < 1 {
+		return nil, errors.New("resolve returned no servers")
+	}
+	return &entry, nil
+}
+
+func export(t *testing.T, ctx *context.T, name, contents string) {
+	// Resolve the name.
+	resolved, err := resolve(ctx, name)
+	if err != nil {
+		boom(t, "Failed to Export.Resolve %s: %s", name, err)
+	}
+	// Export the value.
+	client := v23.GetClient(ctx)
+	if err := client.Call(ctx, mountentry2names(resolved)[0], "Export", []interface{}{contents, true}, nil, options.NoResolve{}); err != nil {
+		boom(t, "Failed to Export.Call %s to %s: %s", name, contents, err)
+	}
+}
+
+func checkContents(t *testing.T, ctx *context.T, name, expected string, shouldSucceed bool) {
+	// Resolve the name.
+	resolved, err := resolve(ctx, name)
+	if err != nil {
+		if !shouldSucceed {
+			return
+		}
+		boom(t, "Failed to Resolve %s: %s", name, err)
+	}
+	// Look up the value.
+	client := v23.GetClient(ctx)
+	call, err := client.StartCall(ctx, mountentry2names(resolved)[0], "Lookup", nil, options.NoResolve{})
+	if err != nil {
+		if shouldSucceed {
+			boom(t, "Failed Lookup.StartCall %s: %s", name, err)
+		}
+		return
+	}
+	var contents []byte
+	if err := call.Finish(&contents); err != nil {
+		if shouldSucceed {
+			boom(t, "Failed to Lookup %s: %s", name, err)
+		}
+		return
+	}
+	if string(contents) != expected {
+		boom(t, "Lookup %s, expected %q, got %q", name, expected, contents)
+	}
+	if !shouldSucceed {
+		boom(t, "Lookup %s, expected failure, got %q", name, contents)
+	}
+}
+
+func newMT(t *testing.T, permsFile, persistDir, statsDir string, rootCtx *context.T) (func() error, string) {
+	reservedDisp := debuglib.NewDispatcher(nil)
+	ctx := v23.WithReservedNameDispatcher(rootCtx, reservedDisp)
+
+	// Add mount table service.
+	mt, err := mounttablelib.NewMountTableDispatcher(ctx, permsFile, persistDir, statsDir)
+	if err != nil {
+		boom(t, "mounttablelib.NewMountTableDispatcher: %v", err)
+	}
+
+	// Start serving on a loopback address.
+	server, err := xrpc.NewDispatchingServer(ctx, "", mt, options.ServesMountTable(true))
+	if err != nil {
+		boom(t, "r.NewServer: %s", err)
+	}
+
+	estr := server.Status().Endpoints[0].String()
+	t.Logf("endpoint %s", estr)
+	return server.Stop, estr
+}
+
+func newCollection(t *testing.T, rootCtx *context.T) (func() error, string) {
+	// Start serving a collection service on a loopback address.  This
+	// is just a service we can mount and test against.
+	server, err := xrpc.NewDispatchingServer(rootCtx, "collection", newCollectionServer())
+	if err != nil {
+		boom(t, "r.NewServer: %s", err)
+	}
+	estr := server.Status().Endpoints[0].String()
+	t.Logf("endpoint %s", estr)
+	return server.Stop, estr
+}
+
+func TestMountTable(t *testing.T) {
+	rootCtx, aliceCtx, bobCtx, shutdown := initTest()
+	defer shutdown()
+
+	stop, mtAddr := newMT(t, "testdata/test.perms", "", "testMountTable", rootCtx)
+	defer stop()
+	stop, collectionAddr := newCollection(t, rootCtx)
+	defer stop()
+
+	collectionName := naming.JoinAddressName(collectionAddr, "collection")
+
+	// Mount the collection server into the mount table.
+	rootCtx.Infof("Mount the collection server into the mount table.")
+	doMount(t, rootCtx, mtAddr, "stuff", collectionName, true)
+
+	// Create a few objects and make sure we can read them.
+	rootCtx.Infof("Create a few objects.")
+	export(t, rootCtx, naming.JoinAddressName(mtAddr, "stuff/the/rain"), "the rain")
+	export(t, rootCtx, naming.JoinAddressName(mtAddr, "stuff/in/spain"), "in spain")
+	export(t, rootCtx, naming.JoinAddressName(mtAddr, "stuff/falls"), "falls mainly on the plain")
+	rootCtx.Infof("Make sure we can read them.")
+	checkContents(t, rootCtx, naming.JoinAddressName(mtAddr, "stuff/the/rain"), "the rain", true)
+	checkContents(t, rootCtx, naming.JoinAddressName(mtAddr, "stuff/in/spain"), "in spain", true)
+	checkContents(t, rootCtx, naming.JoinAddressName(mtAddr, "stuff/falls"), "falls mainly on the plain", true)
+	checkContents(t, rootCtx, naming.JoinAddressName(mtAddr, "/stuff/falls"), "falls mainly on the plain", true)
+	checkContents(t, rootCtx, naming.JoinAddressName(mtAddr, "stuff/nonexistant"), "falls mainly on the plain", false)
+	checkContents(t, bobCtx, naming.JoinAddressName(mtAddr, "stuff/the/rain"), "the rain", true)
+	checkContents(t, aliceCtx, naming.JoinAddressName(mtAddr, "stuff/the/rain"), "the rain", false)
+	
+	// Test name element too long.
+	rootCtx.Infof("Name element too long.")
+	doMount(t, rootCtx, mtAddr, "a/abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnop", collectionName, false)
+
+	// Test multiple mounts.
+	rootCtx.Infof("Multiple mounts.")
+	doMount(t, rootCtx, mtAddr, "a/b", collectionName, true)
+	doMount(t, rootCtx, mtAddr, "x/y", collectionName, true)
+	doMount(t, rootCtx, mtAddr, "alpha//beta", collectionName, true)
+	rootCtx.Infof("Make sure we can read them.")
+	checkContents(t, rootCtx, naming.JoinAddressName(mtAddr, "stuff/falls"), "falls mainly on the plain", true)
+	checkContents(t, rootCtx, naming.JoinAddressName(mtAddr, "a/b/falls"), "falls mainly on the plain", true)
+	checkContents(t, rootCtx, naming.JoinAddressName(mtAddr, "x/y/falls"), "falls mainly on the plain", true)
+	checkContents(t, rootCtx, naming.JoinAddressName(mtAddr, "alpha/beta/falls"), "falls mainly on the plain", true)
+	checkContents(t, aliceCtx, naming.JoinAddressName(mtAddr, "a/b/falls"), "falls mainly on the plain", true)
+	checkContents(t, bobCtx, naming.JoinAddressName(mtAddr, "a/b/falls"), "falls mainly on the plain", false)
+
+	// Test getting/setting AccessLists.
+	perms, version := doGetPermissions(t, rootCtx, mtAddr, "stuff", true)
+	doSetPermissions(t, rootCtx, mtAddr, "stuff", perms, "xyzzy", false) // bad version
+	doSetPermissions(t, rootCtx, mtAddr, "stuff", perms, version, true)  // correct version
+	_, nversion := doGetPermissions(t, rootCtx, mtAddr, "stuff", true)
+	if nversion == version {
+		boom(t, "version didn't change after SetPermissions: %s", nversion)
+	}
+	doSetPermissions(t, rootCtx, mtAddr, "stuff", perms, "", true) // no version
+
+	// Bob should be able to create nodes under the mounttable root but not alice.
+	doSetPermissions(t, aliceCtx, mtAddr, "onlybob", perms, "", false)
+	doSetPermissions(t, bobCtx, mtAddr, "onlybob", perms, "", true)
+
+	// Test that setting Permissions to permissions that don't include the the setter's
+	// blessings in Admin, automatically add their Blessings to Admin to prevent
+	// locking everyone out.
+	perms, _ = doGetPermissions(t, bobCtx, mtAddr, "onlybob", true)
+	noRootPerms := perms.Copy()
+	noRootPerms.Clear("bob", "Admin")
+	doSetPermissions(t, bobCtx, mtAddr, "onlybob", noRootPerms, "", true)
+	// This should succeed, because "bob" should automatically be added to "Admin"
+	// even though he cleared himself from "Admin".
+	doSetPermissions(t, bobCtx, mtAddr, "onlybob", perms, "", true)
+	// Test that adding a non-standard perms is normalized when retrieved.
+	admin := perms["Admin"]
+	admin.In = []security.BlessingPattern{"bob", "bob"}
+	perms["Admin"] = admin
+	doSetPermissions(t, bobCtx, mtAddr, "onlybob", perms, "", true)
+	perms, _ = doGetPermissions(t, bobCtx, mtAddr, "onlybob", true)
+	if got, want := perms["Admin"].In, []security.BlessingPattern{"bob"}; !reflect.DeepEqual(got, want) {
+		boom(t, "got %v, want %v", got, want)
+	}
+
+	// Test generic unmount.
+	rootCtx.Info("Test generic unmount.")
+	doUnmount(t, rootCtx, mtAddr, "a/b", "", true)
+	checkContents(t, rootCtx, naming.JoinAddressName(mtAddr, "a/b/falls"), "falls mainly on the plain", false)
+
+	// Test specific unmount.
+	rootCtx.Info("Test specific unmount.")
+	doMount(t, rootCtx, mtAddr, "a/b", collectionName, true)
+	doUnmount(t, rootCtx, mtAddr, "a/b", collectionName, true)
+	checkContents(t, rootCtx, naming.JoinAddressName(mtAddr, "a/b/falls"), "falls mainly on the plain", false)
+
+	// Try timing out a mount.
+	rootCtx.Info("Try timing out a mount.")
+	ft := mounttablelib.NewFakeTimeClock()
+	mounttablelib.SetServerListClock(ft)
+	doMount(t, rootCtx, mtAddr, "stuffWithTTL", collectionName, true)
+	checkContents(t, rootCtx, naming.JoinAddressName(mtAddr, "stuffWithTTL/the/rain"), "the rain", true)
+	ft.Advance(time.Duration(ttlSecs+4) * time.Second)
+	checkContents(t, rootCtx, naming.JoinAddressName(mtAddr, "stuffWithTTL/the/rain"), "the rain", false)
+
+	// Test unauthorized mount.
+	rootCtx.Info("Test unauthorized mount.")
+	doMount(t, bobCtx, mtAddr, "/a/b", collectionName, false)
+	doMount(t, aliceCtx, mtAddr, "/a/b", collectionName, false)
+
+	doUnmount(t, bobCtx, mtAddr, "x/y", collectionName, false)
+}
+
+func doGlobX(t *testing.T, ctx *context.T, ep, suffix, pattern string, joinServer bool) []string {
+	name := naming.JoinAddressName(ep, suffix)
+	client := v23.GetClient(ctx)
+	call, err := client.StartCall(ctx, name, rpc.GlobMethod, []interface{}{pattern}, options.NoResolve{})
+	if err != nil {
+		boom(t, "Glob.StartCall %s %s: %s", name, pattern, err)
+	}
+	var reply []string
+	for {
+		var gr naming.GlobReply
+		err := call.Recv(&gr)
+		if err == io.EOF {
+			break
+		}
+		if err != nil {
+			boom(t, "Glob.StartCall %s: %s", name, pattern, err)
+		}
+		switch v := gr.(type) {
+		case naming.GlobReplyEntry:
+			if joinServer && len(v.Value.Servers) > 0 {
+				reply = append(reply, naming.JoinAddressName(v.Value.Servers[0].Server, v.Value.Name))
+			} else {
+				reply = append(reply, v.Value.Name)
+			}
+		}
+	}
+	if err := call.Finish(); err != nil {
+		boom(t, "Glob.Finish %s: %s", name, pattern, err)
+	}
+	return reply
+}
+
+func doGlob(t *testing.T, ctx *context.T, ep, suffix, pattern string) []string {
+	return doGlobX(t, ctx, ep, suffix, pattern, false)
+}
+
+// checkMatch verified that the two slices contain the same string items, albeit
+// not necessarily in the same order.  Item repetitions are allowed, but their
+// numbers need to match as well.
+func checkMatch(t *testing.T, want []string, got []string) {
+	if len(want) == 0 && len(got) == 0 {
+		return
+	}
+	w := sort.StringSlice(want)
+	w.Sort()
+	g := sort.StringSlice(got)
+	g.Sort()
+	if !reflect.DeepEqual(w, g) {
+		boom(t, "Glob expected %v got %v", want, got)
+	}
+}
+
+// checkExists makes sure a name exists (or not).
+func checkExists(t *testing.T, ctx *context.T, ep, suffix string, shouldSucceed bool) {
+	x := doGlobX(t, ctx, ep, "", suffix, false)
+	if len(x) != 1 || x[0] != suffix {
+		if shouldSucceed {
+			boom(t, "Failed to find %s", suffix)
+		}
+		return
+	}
+	if !shouldSucceed {
+		boom(t, "%s exists but shouldn't", suffix)
+	}
+}
+
+func TestGlob(t *testing.T) {
+	rootCtx, shutdown := test.V23Init()
+	defer shutdown()
+
+	stop, estr := newMT(t, "", "", "testGlob", rootCtx)
+	defer stop()
+
+	// set up a mount space
+	fakeServer := naming.JoinAddressName(estr, "quux")
+	doMount(t, rootCtx, estr, "one/bright/day", fakeServer, true)
+	doMount(t, rootCtx, estr, "in/the/middle", fakeServer, true)
+	doMount(t, rootCtx, estr, "of/the/night", fakeServer, true)
+
+	// Try various globs.
+	tests := []struct {
+		in       string
+		expected []string
+	}{
+		{"*", []string{"one", "in", "of"}},
+		{"...", []string{"", "one", "in", "of", "one/bright", "in/the", "of/the", "one/bright/day", "in/the/middle", "of/the/night"}},
+		{"*/...", []string{"one", "in", "of", "one/bright", "in/the", "of/the", "one/bright/day", "in/the/middle", "of/the/night"}},
+		{"one/...", []string{"one", "one/bright", "one/bright/day"}},
+		{"of/the/night/two/dead/boys", []string{"of/the/night"}},
+		{"*/the", []string{"in/the", "of/the"}},
+		{"*/the/...", []string{"in/the", "of/the", "in/the/middle", "of/the/night"}},
+		{"o*", []string{"one", "of"}},
+		{"", []string{""}},
+	}
+	for _, test := range tests {
+		out := doGlob(t, rootCtx, estr, "", test.in)
+		checkMatch(t, test.expected, out)
+	}
+
+	// Test Glob on a name that is under a mounted server. The result should the
+	// the address the mounted server with the extra suffix.
+	{
+		results := doGlobX(t, rootCtx, estr, "of/the/night/two/dead/boys/got/up/to/fight", "*", true)
+		if len(results) != 1 {
+			boom(t, "Unexpected number of results. Got %v, want 1", len(results))
+		}
+		_, suffix := naming.SplitAddressName(results[0])
+		if expected := "quux/two/dead/boys/got/up/to/fight"; suffix != expected {
+			boom(t, "Unexpected suffix. Got %v, want %v", suffix, expected)
+		}
+	}
+}
+
+type fakeServerCall struct {
+	sendCount int
+}
+
+func (fakeServerCall) Security() security.Call              { return security.NewCall(&security.CallParams{}) }
+func (fakeServerCall) Suffix() string                       { return "" }
+func (fakeServerCall) LocalEndpoint() naming.Endpoint       { return nil }
+func (fakeServerCall) RemoteEndpoint() naming.Endpoint      { return nil }
+func (fakeServerCall) GrantedBlessings() security.Blessings { return security.Blessings{} }
+func (fakeServerCall) Server() rpc.Server                   { return nil }
+func (c *fakeServerCall) SendStream() interface {
+	Send(naming.GlobReply) error
+} {
+	return c
+}
+func (c *fakeServerCall) Send(reply naming.GlobReply) error {
+	c.sendCount++
+	return nil
+}
+
+func TestGlobAborts(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	mt, err := mounttablelib.NewMountTableDispatcher(ctx, "", "", "")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	mount := func(name string) error {
+		invoker, _, _ := mt.Lookup(ctx, name)
+		server := naming.FormatEndpoint("tcp", name)
+		return invoker.(mounttable.MountTableServerStub).Mount(ctx, fakeServerCall{}, server, 0, 0)
+	}
+	// Mount 125 entries: 5 "directories" with 25 entries each.
+	for i := 0; i < 5; i++ {
+		for j := 0; j < 25; j++ {
+			if err := mount(fmt.Sprintf("%d/%d", i, j)); err != nil {
+				t.Fatalf("%v (%d, %d)", err, i, j)
+			}
+		}
+	}
+
+	glob := func(ctx *context.T) (int, error) {
+		root, _, _ := mt.Lookup(ctx, "")
+		g, _ := glob.Parse("...")
+		fCall := &fakeServerCall{}
+		root.(rpc.Globber).Globber().AllGlobber.Glob__(ctx, fCall, g)
+		return fCall.sendCount, nil
+	}
+
+	got, err := glob(ctx)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if want := 5 + 125 + 1; got != want { // 5 "directories", 125 entries, 1 root entry
+		t.Errorf("Got %d want %d", got, want)
+	}
+	canceled, cancel := context.WithCancel(ctx)
+	cancel()
+	if got, err = glob(canceled); err != nil {
+		t.Fatal(err)
+	}
+	if got != 0 {
+		t.Errorf("Glob returned entries even though the context was cancelled first (returned %d)", got)
+	}
+}
+
+func TestAccessListTemplate(t *testing.T) {
+	rootCtx, aliceCtx, bobCtx, shutdown := initTest()
+	defer shutdown()
+
+	stop, estr := newMT(t, "testdata/test.perms", "", "testAccessListTemplate", rootCtx)
+	defer stop()
+	fakeServer := naming.JoinAddressName(estr, "quux")
+
+	// Noone should be able to mount on someone else's names.
+	doMount(t, aliceCtx, estr, "users/ted", fakeServer, false)
+	doMount(t, bobCtx, estr, "users/carol", fakeServer, false)
+	doMount(t, rootCtx, estr, "users/george", fakeServer, false)
+
+	// Anyone should be able to mount on their own names.
+	doMount(t, aliceCtx, estr, "users/alice", fakeServer, true)
+	doMount(t, bobCtx, estr, "users/bob", fakeServer, true)
+	doMount(t, rootCtx, estr, "users/root", fakeServer, true)
+
+	// Make sure the counter works.
+	doUnmount(t, aliceCtx, estr, "users/alice", "", true)
+	doUnmount(t, bobCtx, estr, "users/bob", "", true)
+	doUnmount(t, rootCtx, estr, "users/root", "", true)
+	perms := access.Permissions{"Admin": access.AccessList{In: []security.BlessingPattern{security.AllPrincipals}}}
+	doSetPermissions(t, aliceCtx, estr, "users/alice/a/b/c/d", perms, "", true)
+	doSetPermissions(t, aliceCtx, estr, "users/alice/a/b/c/d", perms, "", true)
+
+	// Do we obey limits?
+	for i := 0; i < mounttablelib.DefaultMaxNodesPerUser()-5; i++ {
+		node := fmt.Sprintf("users/alice/a/b/c/d/%d", i)
+		doSetPermissions(t, aliceCtx, estr, node, perms, "", true)
+	}
+	doSetPermissions(t, aliceCtx, estr, "users/alice/a/b/c/d/straw", perms, "", false)
+
+	// See if the stats numbers are correct.
+	testcases := []struct {
+		key      string
+		expected interface{}
+	}{
+		{"alice", int64(mounttablelib.DefaultMaxNodesPerUser())},
+		{"bob", int64(0)},
+		{"root", int64(0)},
+		{conventions.ServerUser, int64(3)},
+	}
+	for _, tc := range testcases {
+		name := "testAccessListTemplate/num-nodes-per-user/" + tc.key
+		got, err := libstats.Value(name)
+		if err != nil {
+			t.Errorf("unexpected error getting map entry for %s: %s", name, err)
+		}
+		if got != tc.expected {
+			t.Errorf("unexpected getting map entry for %s. Got %v, want %v", name, got, tc.expected)
+		}
+	}
+}
+
+func getUserNodeCounts(t *testing.T) (counts map[string]int32) {
+	s, err := libstats.Value("mounttable/num-nodes-per-user")
+	if err != nil {
+		boom(t, "Can't get mounttable statistics")
+	}
+	// This string is a json encoded map.  Decode.
+	switch v := s.(type) {
+	default:
+		boom(t, "Wrong type for mounttable statistics")
+	case string:
+		err = json.Unmarshal([]byte(v), &counts)
+		if err != nil {
+			boom(t, "Can't unmarshal mounttable statistics")
+		}
+	}
+	return
+}
+
+func TestGlobAccessLists(t *testing.T) {
+	rootCtx, aliceCtx, bobCtx, shutdown := initTest()
+	defer shutdown()
+
+	stop, estr := newMT(t, "testdata/test.perms", "", "testGlobAccessLists", rootCtx)
+	defer stop()
+
+	// set up a mount space
+	fakeServer := naming.JoinAddressName(estr, "quux")
+	doMount(t, aliceCtx, estr, "one/bright/day", fakeServer, false) // Fails because alice can't mount there.
+	doMount(t, bobCtx, estr, "one/bright/day", fakeServer, true)
+	doMount(t, rootCtx, estr, "a/b/c", fakeServer, true)
+
+	// Try various globs.
+	tests := []struct {
+		ctx      *context.T
+		in       string
+		expected []string
+	}{
+		{rootCtx, "*", []string{"one", "a", "stuff", "users"}},
+		{aliceCtx, "*", []string{"one", "a", "users"}},
+		{bobCtx, "*", []string{"one", "stuff", "users"}},
+		// bob, alice, and root have different visibility to the space.
+		{rootCtx, "*/...", []string{"one", "a", "one/bright", "a/b", "one/bright/day", "a/b/c", "stuff", "users"}},
+		{aliceCtx, "*/...", []string{"one", "a", "one/bright", "a/b", "one/bright/day", "a/b/c", "users"}},
+		{bobCtx, "*/...", []string{"one", "one/bright", "one/bright/day", "stuff", "users"}},
+	}
+	for _, test := range tests {
+		out := doGlob(t, test.ctx, estr, "", test.in)
+		checkMatch(t, test.expected, out)
+	}
+}
+
+func TestCleanup(t *testing.T) {
+	rootCtx, shutdown := test.V23Init()
+	defer shutdown()
+
+	stop, estr := newMT(t, "", "", "testCleanup", rootCtx)
+	defer stop()
+
+	// Set up one mount.
+	fakeServer := naming.JoinAddressName(estr, "quux")
+	doMount(t, rootCtx, estr, "one/bright/day", fakeServer, true)
+	checkMatch(t, []string{"one", "one/bright", "one/bright/day"}, doGlob(t, rootCtx, estr, "", "*/..."))
+
+	// After the unmount nothing should be left
+	doUnmount(t, rootCtx, estr, "one/bright/day", "", true)
+	checkMatch(t, nil, doGlob(t, rootCtx, estr, "", "one"))
+	checkMatch(t, nil, doGlob(t, rootCtx, estr, "", "*/..."))
+
+	// Set up a mount, then set the AccessList.
+	doMount(t, rootCtx, estr, "one/bright/day", fakeServer, true)
+	checkMatch(t, []string{"one", "one/bright", "one/bright/day"}, doGlob(t, rootCtx, estr, "", "*/..."))
+	perms := access.Permissions{"Read": access.AccessList{In: []security.BlessingPattern{security.AllPrincipals}}}
+	doSetPermissions(t, rootCtx, estr, "one/bright", perms, "", true)
+
+	// After the unmount we should still have everything above the AccessList.
+	doUnmount(t, rootCtx, estr, "one/bright/day", "", true)
+	checkMatch(t, []string{"one", "one/bright"}, doGlob(t, rootCtx, estr, "", "*/..."))
+}
+
+func TestDelete(t *testing.T) {
+	rootCtx, aliceCtx, bobCtx, shutdown := initTest()
+	defer shutdown()
+
+	stop, estr := newMT(t, "testdata/test.perms", "", "testDelete", rootCtx)
+	defer stop()
+
+	// set up a mount space
+	fakeServer := naming.JoinAddressName(estr, "quux")
+	doMount(t, bobCtx, estr, "one/bright/day", fakeServer, true)
+	doMount(t, rootCtx, estr, "a/b/c", fakeServer, true)
+
+	// It shouldn't be possible to delete anything with children unless explicitly requested.
+	doDeleteNode(t, rootCtx, estr, "a/b", false)
+	checkExists(t, rootCtx, estr, "a/b", true)
+	doDeleteSubtree(t, rootCtx, estr, "a/b", true)
+	checkExists(t, rootCtx, estr, "a/b", false)
+
+	// Alice shouldn't be able to delete what bob created but bob and root should.
+	doDeleteNode(t, aliceCtx, estr, "one/bright/day", false)
+	checkExists(t, rootCtx, estr, "one/bright/day", true)
+	doDeleteNode(t, rootCtx, estr, "one/bright/day", true)
+	checkExists(t, rootCtx, estr, "one/bright/day", false)
+	doDeleteNode(t, bobCtx, estr, "one/bright", true)
+	checkExists(t, rootCtx, estr, "one/bright", false)
+
+	// Make sure directory admin can delete directory children.
+	perms := access.Permissions{"Admin": access.AccessList{In: []security.BlessingPattern{"bob"}}}
+	doSetPermissions(t, bobCtx, estr, "hoohaa", perms, "", false)
+	doDeleteNode(t, rootCtx, estr, "hoohaa", true)
+	checkExists(t, rootCtx, estr, "hoohaa", false)
+}
+
+func TestServerFormat(t *testing.T) {
+	rootCtx, shutdown := test.V23Init()
+	defer shutdown()
+
+	stop, estr := newMT(t, "", "", "testerverFormat", rootCtx)
+	defer stop()
+
+	doMount(t, rootCtx, estr, "endpoint", naming.JoinAddressName(estr, "life/on/the/mississippi"), true)
+	doMount(t, rootCtx, estr, "hostport", "/atrampabroad:8000", true)
+	doMount(t, rootCtx, estr, "invalid/not/rooted", "atrampabroad:8000", false)
+	doMount(t, rootCtx, estr, "invalid/no/port", "/atrampabroad", false)
+	doMount(t, rootCtx, estr, "invalid/endpoint", "/@following the equator:8000@@@", false)
+}
+
+func TestExpiry(t *testing.T) {
+	rootCtx, shutdown := test.V23Init()
+	defer shutdown()
+
+	stop, estr := newMT(t, "", "", "testExpiry", rootCtx)
+	defer stop()
+	stop, collectionAddr := newCollection(t, rootCtx)
+	defer stop()
+
+	collectionName := naming.JoinAddressName(collectionAddr, "collection")
+
+	ft := mounttablelib.NewFakeTimeClock()
+	mounttablelib.SetServerListClock(ft)
+	doMount(t, rootCtx, estr, "a1/b1", collectionName, true)
+	doMount(t, rootCtx, estr, "a1/b2", collectionName, true)
+	doMount(t, rootCtx, estr, "a2/b1", collectionName, true)
+	doMount(t, rootCtx, estr, "a2/b2/c", collectionName, true)
+
+	checkMatch(t, []string{"a1/b1", "a2/b1"}, doGlob(t, rootCtx, estr, "", "*/b1/..."))
+	ft.Advance(time.Duration(ttlSecs/2) * time.Second)
+	checkMatch(t, []string{"a1/b1", "a2/b1"}, doGlob(t, rootCtx, estr, "", "*/b1/..."))
+	checkMatch(t, []string{"c"}, doGlob(t, rootCtx, estr, "a2/b2", "*"))
+	// Refresh only a1/b1.  All the other mounts will expire upon the next
+	// ft advance.
+	doMount(t, rootCtx, estr, "a1/b1", collectionName, true)
+	ft.Advance(time.Duration(ttlSecs/2+4) * time.Second)
+	checkMatch(t, []string{"a1", "a1/b1"}, doGlob(t, rootCtx, estr, "", "*/..."))
+	checkMatch(t, []string{"a1/b1"}, doGlob(t, rootCtx, estr, "", "*/b1/..."))
+}
+
+func TestBadAccessLists(t *testing.T) {
+	ctx, shutdown := test.TestContext()
+	defer shutdown()
+	_, err := mounttablelib.NewMountTableDispatcher(ctx, "testdata/invalid.perms", "", "mounttable")
+	if err == nil {
+		boom(t, "Expected json parse error in permissions file")
+	}
+	_, err = mounttablelib.NewMountTableDispatcher(ctx, "testdata/doesntexist.perms", "", "mounttable")
+	if err != nil {
+		boom(t, "Missing permissions file should not cause an error")
+	}
+}
+
+func getCounter(t *testing.T, ctx *context.T, name string) int64 {
+	st := stats.StatsClient(name)
+	v, err := st.Value(ctx)
+	if err != nil {
+		t.Fatalf("Failed to get %q: %v", name, err)
+		return -1
+	}
+	var value int64
+	if err := vdl.Convert(&value, v); err != nil {
+		t.Fatalf("Unexpected value type for %q: %v", name, err)
+	}
+	return value
+}
+
+func nodeCount(t *testing.T, ctx *context.T, addr string) int64 {
+	name := naming.JoinAddressName(addr, "__debug/stats/mounttable/num-nodes")
+	return getCounter(t, ctx, name)
+}
+
+func serverCount(t *testing.T, ctx *context.T, addr string) int64 {
+	name := naming.JoinAddressName(addr, "__debug/stats/mounttable/num-mounted-servers")
+	return getCounter(t, ctx, name)
+}
+
+func TestStatsCounters(t *testing.T) {
+	rootCtx, shutdown := test.V23Init()
+	defer shutdown()
+
+	ft := mounttablelib.NewFakeTimeClock()
+	mounttablelib.SetServerListClock(ft)
+
+	stop, estr := newMT(t, "", "", "mounttable", rootCtx)
+	defer stop()
+
+	// Test flat tree
+	for i := 1; i <= 10; i++ {
+		name := fmt.Sprintf("node%d", i)
+		addr := naming.JoinAddressName(estr, name)
+		doMount(t, rootCtx, estr, name, addr, true)
+		if expected, got := int64(i+1), nodeCount(t, rootCtx, estr); got != expected {
+			t.Errorf("Unexpected number of nodes. Got %d, expected %d", got, expected)
+		}
+		if expected, got := int64(i), serverCount(t, rootCtx, estr); got != expected {
+			t.Errorf("Unexpected number of servers. Got %d, expected %d", got, expected)
+		}
+	}
+	for i := 1; i <= 10; i++ {
+		name := fmt.Sprintf("node%d", i)
+		if i%2 == 0 {
+			doUnmount(t, rootCtx, estr, name, "", true)
+		} else {
+			doDeleteSubtree(t, rootCtx, estr, name, true)
+		}
+		if expected, got := int64(11-i), nodeCount(t, rootCtx, estr); got != expected {
+			t.Errorf("Unexpected number of nodes. Got %d, expected %d", got, expected)
+		}
+		if expected, got := int64(10-i), serverCount(t, rootCtx, estr); got != expected {
+			t.Errorf("Unexpected number of server. Got %d, expected %d", got, expected)
+		}
+	}
+
+	// Test deep tree
+	doMount(t, rootCtx, estr, "1/2/3/4/5/6/7/8/9a/10", naming.JoinAddressName(estr, ""), true)
+	doMount(t, rootCtx, estr, "1/2/3/4/5/6/7/8/9b/11", naming.JoinAddressName(estr, ""), true)
+	if expected, got := int64(13), nodeCount(t, rootCtx, estr); got != expected {
+		t.Errorf("Unexpected number of nodes. Got %d, expected %d", got, expected)
+	}
+	if expected, got := int64(2), serverCount(t, rootCtx, estr); got != expected {
+		t.Errorf("Unexpected number of servers. Got %d, expected %d", got, expected)
+	}
+	doDeleteSubtree(t, rootCtx, estr, "1/2/3/4/5", true)
+	if expected, got := int64(5), nodeCount(t, rootCtx, estr); got != expected {
+		t.Errorf("Unexpected number of nodes. Got %d, expected %d", got, expected)
+	}
+	if expected, got := int64(0), serverCount(t, rootCtx, estr); got != expected {
+		t.Errorf("Unexpected number of servers. Got %d, expected %d", got, expected)
+	}
+	doDeleteSubtree(t, rootCtx, estr, "1", true)
+	if expected, got := int64(1), nodeCount(t, rootCtx, estr); got != expected {
+		t.Errorf("Unexpected number of nodes. Got %d, expected %d", got, expected)
+	}
+
+	// Test multiple servers per node
+	for i := 1; i <= 5; i++ {
+		server := naming.JoinAddressName(estr, fmt.Sprintf("addr%d", i))
+		doMount(t, rootCtx, estr, "node1", server, true)
+		doMount(t, rootCtx, estr, "node2", server, true)
+		if expected, got := int64(3), nodeCount(t, rootCtx, estr); got != expected {
+			t.Errorf("Unexpected number of nodes. Got %d, expected %d", got, expected)
+		}
+		if expected, got := int64(2*i), serverCount(t, rootCtx, estr); got != expected {
+			t.Errorf("Unexpected number of servers. Got %d, expected %d", got, expected)
+		}
+	}
+	doUnmount(t, rootCtx, estr, "node1", "", true)
+	if expected, got := int64(2), nodeCount(t, rootCtx, estr); got != expected {
+		t.Errorf("Unexpected number of nodes. Got %d, expected %d", got, expected)
+	}
+	if expected, got := int64(5), serverCount(t, rootCtx, estr); got != expected {
+		t.Errorf("Unexpected number of servers. Got %d, expected %d", got, expected)
+	}
+	for i := 1; i <= 5; i++ {
+		server := naming.JoinAddressName(estr, fmt.Sprintf("addr%d", i))
+		doUnmount(t, rootCtx, estr, "node2", server, true)
+		expectedNodes := int64(2)
+		if i == 5 {
+			expectedNodes = 1
+		}
+		if expected, got := expectedNodes, nodeCount(t, rootCtx, estr); got != expected {
+			t.Errorf("Unexpected number of nodes. Got %d, expected %d", got, expected)
+		}
+		if expected, got := int64(5-i), serverCount(t, rootCtx, estr); got != expected {
+			t.Errorf("Unexpected number of servers. Got %d, expected %d", got, expected)
+		}
+	}
+
+	// Mount on an existing intermediate node.
+	doMount(t, rootCtx, estr, "1/2/3/4/5/6/7/8/9a/10", naming.JoinAddressName(estr, ""), true)
+	doMount(t, rootCtx, estr, "1/2/3/4/5", naming.JoinAddressName(estr, ""), true)
+	if expected, got := int64(6), nodeCount(t, rootCtx, estr); got != expected {
+		t.Errorf("Unexpected number of nodes. Got %d, expected %d", got, expected)
+	}
+	if expected, got := int64(1), serverCount(t, rootCtx, estr); got != expected {
+		t.Errorf("Unexpected number of servers. Got %d, expected %d", got, expected)
+	}
+
+	// Test expired mounts
+	// "1/2/3/4/5" is still mounted from earlier.
+	ft.Advance(time.Duration(ttlSecs+4) * time.Second)
+	if _, err := resolve(rootCtx, naming.JoinAddressName(estr, "1/2/3/4/5")); err == nil {
+		t.Errorf("Expected failure. Got success")
+	}
+	if expected, got := int64(0), serverCount(t, rootCtx, estr); got != expected {
+		t.Errorf("Unexpected number of servers. Got %d, expected %d", got, expected)
+	}
+}
+
+func TestIntermediateNodesCreatedFromConfig(t *testing.T) {
+	rootCtx, _, _, shutdown := initTest()
+	defer shutdown()
+
+	stop, estr := newMT(t, "testdata/intermediate.perms", "", "TestIntermediateNodesCreatedFromConfig", rootCtx)
+	defer stop()
+
+	// x and x/y should have the same permissions at the root.
+	rootPerms, _ := doGetPermissions(t, rootCtx, estr, "", true)
+	if perms, _ := doGetPermissions(t, rootCtx, estr, "x", true); !reflect.DeepEqual(rootPerms, perms) {
+		boom(t, "for x got %v, want %v", perms, rootPerms)
+	}
+	if perms, _ := doGetPermissions(t, rootCtx, estr, "x/y", true); !reflect.DeepEqual(rootPerms, perms) {
+		boom(t, "for x/y got %v, want %v", perms, rootPerms)
+	}
+	if perms, _ := doGetPermissions(t, rootCtx, estr, "x/y/z", true); reflect.DeepEqual(rootPerms, perms) {
+		boom(t, "for x/y/z got %v, don't want %v", perms, rootPerms)
+	}
+}
+
+func initTest() (rootCtx *context.T, aliceCtx *context.T, bobCtx *context.T, shutdown v23.Shutdown) {
+	test.Init()
+	ctx, shutdown := test.V23Init()
+	var err error
+	if rootCtx, err = v23.WithPrincipal(ctx, testutil.NewPrincipal("root")); err != nil {
+		panic("failed to set root principal")
+	}
+	if aliceCtx, err = v23.WithPrincipal(ctx, testutil.NewPrincipal("alice")); err != nil {
+		panic("failed to set alice principal")
+	}
+	if bobCtx, err = v23.WithPrincipal(ctx, testutil.NewPrincipal("bob")); err != nil {
+		panic("failed to set bob principal")
+	}
+	for _, r := range []*context.T{rootCtx, aliceCtx, bobCtx} {
+		// A hack to set the namespace roots to a value that won't work.
+		v23.GetNamespace(r).SetRoots()
+		// And have all principals recognize each others blessings.
+		p1 := v23.GetPrincipal(r)
+		for _, other := range []*context.T{rootCtx, aliceCtx, bobCtx} {
+			// testutil.NewPrincipal has already setup each
+			// principal to use the same blessing for both server
+			// and client activities.
+			if err := p1.AddToRoots(v23.GetPrincipal(other).BlessingStore().Default()); err != nil {
+				panic(err)
+			}
+		}
+	}
+	return rootCtx, aliceCtx, bobCtx, shutdown
+}
diff --git a/services/mounttable/mounttablelib/neighborhood.go b/services/mounttable/mounttablelib/neighborhood.go
new file mode 100644
index 0000000..c0cc83b
--- /dev/null
+++ b/services/mounttable/mounttablelib/neighborhood.go
@@ -0,0 +1,312 @@
+// 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 mounttablelib
+
+import (
+	"net"
+	"strconv"
+	"strings"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/glob"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/security/access"
+	"v.io/v23/services/mounttable"
+	vdltime "v.io/v23/vdlroot/time"
+	"v.io/v23/verror"
+	"v.io/x/lib/netconfig"
+
+	"v.io/x/ref/internal/logger"
+
+	mdns "github.com/presotto/go-mdns-sd"
+)
+
+var (
+	errNoUsefulAddresses             = verror.Register(pkgPath+".errNoUsefulAddresses", verror.NoRetry, "{1:}{2:} neighborhood passed no useful addresses{:_}")
+	errCantFindPort                  = verror.Register(pkgPath+".errCantFindPort", verror.NoRetry, "{1:}{2:} neighborhood couldn't determine a port to use{:_}")
+	errDoesntImplementMount          = verror.Register(pkgPath+".errDoesntImplementMount", verror.NoRetry, "{1:}{2:} this server does not implement Mount{:_}")
+	errDoesntImplementUnmount        = verror.Register(pkgPath+".errDoesntImplementUnmount", verror.NoRetry, "{1:}{2:} this server does not implement Unmount{:_}")
+	errDoesntImplementDelete         = verror.Register(pkgPath+".errDoesntImplementDelete", verror.NoRetry, "{1:}{2:} this server does not implement Delete{:_}")
+	errDoesntImplementSetPermissions = verror.Register(pkgPath+".errDoesntImplementSetPermissions", verror.NoRetry, "{1:}{2:} this server does not implement SetPermissions{:_}")
+	errSlashInHostName               = verror.Register(pkgPath+".errSlashInHostName", verror.NoRetry, "{1:}{2:} hostname may not contain '/'{:_}")
+)
+
+const addressPrefix = "address:"
+
+// neighborhood defines a set of machines on the same multicast media.
+type neighborhood struct {
+	mdns             *mdns.MDNS
+	nelems           int
+	nw               netconfig.NetConfigWatcher
+	lastSubscription time.Time
+}
+
+var _ rpc.Dispatcher = (*neighborhood)(nil)
+
+type neighborhoodService struct {
+	name  string
+	elems []string
+	nh    *neighborhood
+}
+
+func getPort(address string) uint16 {
+	epAddr, _ := naming.SplitAddressName(address)
+
+	ep, err := v23.NewEndpoint(epAddr)
+	if err != nil {
+		return 0
+	}
+	addr := ep.Addr()
+	if addr == nil {
+		return 0
+	}
+	switch addr.Network() {
+	case "tcp", "tcp4", "tcp6", "ws", "ws4", "ws6", "wsh", "wsh4", "wsh6":
+	default:
+		return 0
+	}
+	_, pstr, err := net.SplitHostPort(addr.String())
+	if err != nil {
+		return 0
+	}
+	port, err := strconv.ParseUint(pstr, 10, 16)
+	if err != nil || port == 0 {
+		return 0
+	}
+	return uint16(port)
+}
+
+func newNeighborhood(host string, addresses []string, loopback bool) (*neighborhood, error) {
+	if strings.Contains(host, "/") {
+		return nil, verror.New(errSlashInHostName, nil)
+	}
+
+	// Create the TXT contents with addresses to announce. Also pick up a port number.
+	var txt []string
+	var port uint16
+	for _, addr := range addresses {
+		txt = append(txt, addressPrefix+addr)
+		if port == 0 {
+			port = getPort(addr)
+		}
+	}
+	if txt == nil {
+		return nil, verror.New(errNoUsefulAddresses, nil)
+	}
+	if port == 0 {
+		return nil, verror.New(errCantFindPort, nil)
+	}
+
+	// Start up MDNS, subscribe to the vanadium service, and add us as a vanadium service provider.
+	m, err := mdns.NewMDNS(host, "", "", loopback, false)
+	if err != nil {
+		// The name may not have been unique.  Try one more time with a unique
+		// name.  NewMDNS will replace the "()" with "(hardware mac address)".
+		if len(host) > 0 {
+			m, err = mdns.NewMDNS(host+"()", "", "", loopback, false)
+		}
+		if err != nil {
+			logger.Global().Errorf("mdns startup failed: %s", err)
+			return nil, err
+		}
+	}
+	logger.Global().VI(2).Infof("listening for service vanadium on port %d", port)
+	m.SubscribeToService("vanadium")
+	if len(host) > 0 {
+		m.AddService("vanadium", "", port, txt...)
+	}
+
+	// A small sleep to allow the world to learn about us and vice versa.  Not
+	// necessary but helpful.
+	time.Sleep(50 * time.Millisecond)
+
+	nh := &neighborhood{
+		mdns: m,
+	}
+
+	// Watch the network configuration so that we can make MDNS reattach to
+	// interfaces when the network changes.
+	nh.nw, err = netconfig.NewNetConfigWatcher()
+	if err != nil {
+		logger.Global().Errorf("nighborhood can't watch network: %s", err)
+		return nh, nil
+	}
+	go func() {
+		if _, ok := <-nh.nw.Channel(); !ok {
+			return
+		}
+		if _, err := nh.mdns.ScanInterfaces(); err != nil {
+			logger.Global().Errorf("nighborhood can't scan interfaces: %s", err)
+		}
+	}()
+
+	return nh, nil
+}
+
+// NewLoopbackNeighborhoodDispatcher creates a new instance of a dispatcher for
+// a neighborhood service provider on loopback interfaces (meant for testing).
+func NewLoopbackNeighborhoodDispatcher(host string, addresses ...string) (rpc.Dispatcher, error) {
+	return newNeighborhood(host, addresses, true)
+}
+
+// NewNeighborhoodDispatcher creates a new instance of a dispatcher for a
+// neighborhood service provider.
+func NewNeighborhoodDispatcher(host string, addresses ...string) (rpc.Dispatcher, error) {
+	return newNeighborhood(host, addresses, false)
+}
+
+// Lookup implements rpc.Dispatcher.Lookup.
+func (nh *neighborhood) Lookup(ctx *context.T, name string) (interface{}, security.Authorizer, error) {
+	logger.Global().VI(1).Infof("*********************LookupServer '%s'\n", name)
+	elems := strings.Split(name, "/")[nh.nelems:]
+	if name == "" {
+		elems = nil
+	}
+	ns := &neighborhoodService{
+		name:  name,
+		elems: elems,
+		nh:    nh,
+	}
+	return mounttable.MountTableServer(ns), nh, nil
+}
+
+func (nh *neighborhood) Authorize(*context.T, security.Call) error {
+	// TODO(rthellend): Figure out whether it's OK to accept all requests
+	// unconditionally.
+	return nil
+}
+
+// Stop performs cleanup.
+func (nh *neighborhood) Stop() {
+	if nh.nw != nil {
+		nh.nw.Stop()
+	}
+	nh.mdns.Stop()
+}
+
+// neighbor returns the MountedServers for a particular neighbor.
+func (nh *neighborhood) neighbor(instance string) []naming.MountedServer {
+	now := time.Now()
+	var reply []naming.MountedServer
+	si := nh.mdns.ResolveInstance(instance, "vanadium")
+
+	// Use a map to dedup any addresses seen
+	addrMap := make(map[string]vdltime.Deadline)
+
+	// Look for any TXT records with addresses.
+	for _, rr := range si.TxtRRs {
+		for _, s := range rr.Txt {
+			if !strings.HasPrefix(s, addressPrefix) {
+				continue
+			}
+			addr := s[len(addressPrefix):]
+			ttl := time.Second * time.Duration(rr.Header().Ttl)
+			addrMap[addr] = vdltime.Deadline{Time: now.Add(ttl)}
+		}
+	}
+	for addr, deadline := range addrMap {
+		reply = append(reply, naming.MountedServer{
+			Server:   addr,
+			Deadline: deadline,
+		})
+	}
+	return reply
+}
+
+// neighbors returns all neighbors and their MountedServer structs.
+func (nh *neighborhood) neighbors() map[string][]naming.MountedServer {
+	// If we haven't refreshed in a while, do it now.
+	if time.Now().Sub(nh.lastSubscription) > time.Duration(30)*time.Second {
+		nh.mdns.SubscribeToService("vanadium")
+		time.Sleep(50 * time.Millisecond)
+		nh.lastSubscription = time.Now()
+	}
+	neighbors := make(map[string][]naming.MountedServer, 0)
+	members := nh.mdns.ServiceDiscovery("vanadium")
+	for _, m := range members {
+		if neighbor := nh.neighbor(m.Name); neighbor != nil {
+			neighbors[m.Name] = neighbor
+		}
+	}
+	logger.Global().VI(2).Infof("members %v neighbors %v", members, neighbors)
+	return neighbors
+}
+
+// ResolveStep implements ResolveStep
+func (ns *neighborhoodService) ResolveStep(ctx *context.T, _ rpc.ServerCall) (entry naming.MountEntry, err error) {
+	nh := ns.nh
+	ctx.VI(2).Infof("ResolveStep %v\n", ns.elems)
+	if len(ns.elems) == 0 {
+		//nothing can be mounted at the root
+		err = verror.New(naming.ErrNoSuchNameRoot, ctx, ns.elems)
+		return
+	}
+
+	// We can only resolve the first element and it always refers to a mount table (for now).
+	neighbor := nh.neighbor(ns.elems[0])
+	if neighbor == nil {
+		err = verror.New(naming.ErrNoSuchName, ctx, ns.elems)
+		entry.Name = ns.name
+		return
+	}
+	entry.ServesMountTable = true
+	entry.Name = naming.Join(ns.elems[1:]...)
+	entry.Servers = neighbor
+	return
+}
+
+// Mount not implemented.
+func (ns *neighborhoodService) Mount(ctx *context.T, _ rpc.ServerCall, _ string, _ uint32, _ naming.MountFlag) error {
+	return verror.New(errDoesntImplementMount, ctx)
+}
+
+// Unmount not implemented.
+func (*neighborhoodService) Unmount(ctx *context.T, _ rpc.ServerCall, _ string) error {
+	return verror.New(errDoesntImplementUnmount, ctx)
+}
+
+// Delete not implemented.
+func (*neighborhoodService) Delete(ctx *context.T, _ rpc.ServerCall, _ bool) error {
+	return verror.New(errDoesntImplementDelete, ctx)
+}
+
+// Glob__ implements rpc.AllGlobber
+func (ns *neighborhoodService) Glob__(ctx *context.T, call rpc.GlobServerCall, g *glob.Glob) error {
+	// return all neighbors that match the first element of the pattern.
+	nh := ns.nh
+
+	sender := call.SendStream()
+	switch len(ns.elems) {
+	case 0:
+		matcher := g.Head()
+		for k, n := range nh.neighbors() {
+			if matcher.Match(k) {
+				sender.Send(naming.GlobReplyEntry{Value: naming.MountEntry{Name: k, Servers: n, ServesMountTable: true}})
+			}
+		}
+		return nil
+	case 1:
+		neighbor := nh.neighbor(ns.elems[0])
+		if neighbor == nil {
+			return verror.New(naming.ErrNoSuchName, ctx, ns.elems[0])
+		}
+		sender.Send(naming.GlobReplyEntry{Value: naming.MountEntry{Name: "", Servers: neighbor, ServesMountTable: true}})
+		return nil
+	default:
+		return verror.New(naming.ErrNoSuchName, ctx, ns.elems)
+	}
+}
+
+func (*neighborhoodService) SetPermissions(ctx *context.T, _ rpc.ServerCall, _ access.Permissions, _ string) error {
+	return verror.New(errDoesntImplementSetPermissions, ctx)
+}
+
+func (*neighborhoodService) GetPermissions(*context.T, rpc.ServerCall) (access.Permissions, string, error) {
+	return nil, "", nil
+}
diff --git a/services/mounttable/mounttablelib/neighborhood_test.go b/services/mounttable/mounttablelib/neighborhood_test.go
new file mode 100644
index 0000000..b5c1ff1
--- /dev/null
+++ b/services/mounttable/mounttablelib/neighborhood_test.go
@@ -0,0 +1,129 @@
+// 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 mounttablelib_test
+
+import (
+	"fmt"
+	"os"
+	"reflect"
+	"testing"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/naming"
+	"v.io/v23/options"
+
+	_ "v.io/x/ref/runtime/factories/generic"
+	"v.io/x/ref/services/mounttable/mounttablelib"
+	"v.io/x/ref/test"
+)
+
+func protocolAndAddress(e naming.Endpoint) (string, string, error) {
+	addr := e.Addr()
+	if addr == nil {
+		return "", "", fmt.Errorf("failed to get address")
+	}
+	return addr.Network(), addr.String(), nil
+}
+
+type stopper interface {
+	Stop()
+}
+
+func TestNeighborhood(t *testing.T) {
+	rootCtx, shutdown := test.V23Init()
+	defer shutdown()
+
+	rootCtx.Infof("TestNeighborhood")
+	server, err := v23.NewServer(rootCtx)
+	if err != nil {
+		boom(t, "r.NewServer: %s", err)
+	}
+	defer server.Stop()
+
+	// Start serving on a loopback address.
+	eps, err := server.Listen(v23.GetListenSpec(rootCtx))
+	if err != nil {
+		boom(t, "Failed to Listen mount table: %s", err)
+	}
+	estr := eps[0].String()
+	addresses := []string{
+		naming.JoinAddressName(estr, ""),
+		naming.JoinAddressName(estr, "suffix1"),
+		naming.JoinAddressName(estr, "suffix2"),
+	}
+
+	// Create a name for the server.
+	serverName := fmt.Sprintf("nhtest%d", os.Getpid())
+
+	// Add neighborhood server.
+	nhd, err := mounttablelib.NewLoopbackNeighborhoodDispatcher(serverName, addresses...)
+	if err != nil {
+		boom(t, "Failed to create neighborhood server: %s\n", err)
+	}
+	defer nhd.(stopper).Stop()
+	if err := server.ServeDispatcher("", nhd); err != nil {
+		boom(t, "Failed to register neighborhood server: %s", err)
+	}
+
+	// Wait for the mounttable to appear in mdns
+L:
+	for tries := 1; tries < 2; tries++ {
+		names := doGlob(t, rootCtx, estr, "", "*")
+		t.Logf("names %v", names)
+		for _, n := range names {
+			if n == serverName {
+				break L
+			}
+		}
+		time.Sleep(1 * time.Second)
+	}
+
+	// Make sure we get back a root for the server.
+	want, got := []string{""}, doGlob(t, rootCtx, estr, serverName, "")
+	if !reflect.DeepEqual(want, got) {
+		t.Errorf("Unexpected Glob result want: %q, got: %q", want, got)
+	}
+
+	// Make sure we can resolve through the neighborhood.
+	expectedSuffix := "a/b"
+
+	client := v23.GetClient(rootCtx)
+	name := naming.JoinAddressName(estr, serverName+"/"+expectedSuffix)
+	call, cerr := client.StartCall(rootCtx, name, "ResolveStep", nil, options.NoResolve{})
+	if cerr != nil {
+		boom(t, "ResolveStep.StartCall: %s", cerr)
+	}
+	var entry naming.MountEntry
+	if err := call.Finish(&entry); err != nil {
+		boom(t, "ResolveStep: %s", err)
+	}
+
+	// Resolution returned something.  Make sure its correct.
+	if entry.Name != expectedSuffix {
+		boom(t, "resolveStep suffix: expected %s, got %s", expectedSuffix, entry.Name)
+	}
+	if len(entry.Servers) == 0 {
+		boom(t, "resolveStep returns no severs")
+	}
+L2:
+	for _, s := range entry.Servers {
+		for _, a := range addresses {
+			if a == s.Server {
+				continue L2
+			}
+		}
+		boom(t, "Unexpected address from resolveStep result: %v", s.Server)
+	}
+L3:
+	for _, a := range addresses {
+		for _, s := range entry.Servers {
+			if a == s.Server {
+				continue L3
+			}
+		}
+		boom(t, "Missing address from resolveStep result: %v", a)
+	}
+}
diff --git a/services/mounttable/mounttablelib/persist_test.go b/services/mounttable/mounttablelib/persist_test.go
new file mode 100644
index 0000000..74e6e1e
--- /dev/null
+++ b/services/mounttable/mounttablelib/persist_test.go
@@ -0,0 +1,78 @@
+// 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 mounttablelib_test
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"reflect"
+	"testing"
+
+	"v.io/v23/security"
+	"v.io/v23/security/access"
+)
+
+func TestPersistence(t *testing.T) {
+	rootCtx, _, _, shutdown := initTest()
+	defer shutdown()
+
+	td, err := ioutil.TempDir("", "upyournose")
+	if err != nil {
+		t.Fatalf("Failed to make temporary dir: %s", err)
+	}
+	defer os.RemoveAll(td)
+	fmt.Printf("temp persist dir %s\n", td)
+	stop, mtAddr := newMT(t, "", td, "testPersistence", rootCtx)
+
+	perms1 := access.Permissions{
+		"Read":    access.AccessList{In: []security.BlessingPattern{security.AllPrincipals}},
+		"Resolve": access.AccessList{In: []security.BlessingPattern{security.AllPrincipals}},
+		"Create":  access.AccessList{In: []security.BlessingPattern{security.AllPrincipals}},
+	}
+	perms2 := access.Permissions{
+		"Read":    access.AccessList{In: []security.BlessingPattern{security.AllPrincipals}},
+		"Resolve": access.AccessList{In: []security.BlessingPattern{security.AllPrincipals}},
+		"Create":  access.AccessList{In: []security.BlessingPattern{"root"}},
+	}
+	perms3 := access.Permissions{
+		"Read":    access.AccessList{In: []security.BlessingPattern{security.AllPrincipals}},
+		"Resolve": access.AccessList{In: []security.BlessingPattern{security.AllPrincipals}},
+		"Create":  access.AccessList{In: []security.BlessingPattern{"bob"}},
+	}
+
+	// Set some permissions.  It should be persisted.
+	doSetPermissions(t, rootCtx, mtAddr, "a", perms1, "", true)
+	doSetPermissions(t, rootCtx, mtAddr, "a/b", perms2, "", true)
+	doSetPermissions(t, rootCtx, mtAddr, "a/b/c/d", perms2, "", true)
+	doSetPermissions(t, rootCtx, mtAddr, "a/b/c/d/e", perms2, "", true)
+	doDeleteSubtree(t, rootCtx, mtAddr, "a/b/c", true)
+	doSetPermissions(t, rootCtx, mtAddr, "a/c/d", perms3, "", true)
+	stop()
+
+	// Restart with the persisted data.
+	stop, mtAddr = newMT(t, "", td, "testPersistence", rootCtx)
+
+	// Add root as Admin to each of the perms since the mounttable itself will.
+	perms1["Admin"] = access.AccessList{In: []security.BlessingPattern{"root"}}
+	perms2["Admin"] = access.AccessList{In: []security.BlessingPattern{"root"}}
+	perms3["Admin"] = access.AccessList{In: []security.BlessingPattern{"root"}}
+
+	// Check deletion.
+	checkExists(t, rootCtx, mtAddr, "a/b", true)
+	checkExists(t, rootCtx, mtAddr, "a/b/c", false)
+
+	// Check Persistance.
+	if perm, _ := doGetPermissions(t, rootCtx, mtAddr, "a", true); !reflect.DeepEqual(perm, perms1) {
+		t.Fatalf("a: got %v, want %v", perm, perms1)
+	}
+	if perm, _ := doGetPermissions(t, rootCtx, mtAddr, "a/b", true); !reflect.DeepEqual(perm, perms2) {
+		t.Fatalf("a/b: got %v, want %v", perm, perms2)
+	}
+	if perm, _ := doGetPermissions(t, rootCtx, mtAddr, "a/c/d", true); !reflect.DeepEqual(perm, perms3) {
+		t.Fatalf("a/c/d: got %v, want %v", perm, perms3)
+	}
+	stop()
+}
diff --git a/services/mounttable/mounttablelib/persistentstore.go b/services/mounttable/mounttablelib/persistentstore.go
new file mode 100644
index 0000000..18c9af5
--- /dev/null
+++ b/services/mounttable/mounttablelib/persistentstore.go
@@ -0,0 +1,193 @@
+// 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 mounttablelib
+
+import (
+	"encoding/json"
+	"io"
+	"os"
+	"path"
+	"strings"
+	"sync"
+
+	"v.io/v23/context"
+
+	"v.io/x/ref/internal/logger"
+)
+
+type store struct {
+	l   sync.Mutex
+	mt  *mountTable
+	dir string
+	enc *json.Encoder
+	f   *os.File
+}
+
+type storeElement struct {
+	N string // Name of affected node
+	V VersionedPermissions
+	D bool   // True if the subtree at N has been deleted
+	C string // Creator
+}
+
+// newPersistentStore will read the permissions log from the directory and apply them to the
+// in memory tree.  It will then write a new file from the in memory tree and any new permission
+// changes will be appened to this file.  By writing into a new file, we effectively compress
+// the permissions file since any set permissions that have been deleted or overwritten will be
+// lost.
+//
+// The code manages three files in the directory 'dir':
+//   persistent.permslog - the log of permissions.  A new log entry is added with each SetPermissions or
+//      Delete RPC.
+//   tmp.permslog - a temporary file created whenever we restart.  Once we write the current state into it,
+//      it will be renamed persistent.perms becoming the new log.
+//   old.permslog - the previous version of persistent.perms.  This is left around primarily for debugging
+//      and as an emergency backup.
+func newPersistentStore(ctx *context.T, mt *mountTable, dir string) persistence {
+	s := &store{mt: mt, dir: dir}
+	file := path.Join(dir, "persistent.permslog")
+	tmp := path.Join(dir, "tmp.permslog")
+	old := path.Join(dir, "old.permslog")
+
+	// If the permissions file doesn't exist, try renaming the temporary one.
+	f, err := os.Open(file)
+	if err != nil {
+		if !os.IsNotExist(err) {
+			logger.Global().Fatalf("cannot open %s: %s", file, err)
+		}
+		os.Rename(tmp, file)
+		if f, err = os.Open(file); err != nil && !os.IsNotExist(err) {
+			logger.Global().Fatalf("cannot open %s: %s", file, err)
+		}
+	} else {
+		os.Remove(tmp)
+	}
+
+	// Parse the permissions file and apply it to the in memory tree.
+	if f != nil {
+		if err := s.parseLogFile(ctx, f); err != nil {
+			f.Close()
+			// Log the error but keep going.  There's not much else we can do.
+			logger.Global().Infof("parsing old persistent permissions file %s: %s", file, err)
+		}
+		f.Close()
+	}
+
+	// Write the permissions to a temporary file.  This compresses
+	// the file since it writes out only the end state.
+	f, err = os.OpenFile(tmp, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600)
+	if err != nil {
+		// Log the error but keep going, don't compress, just append to the current file.
+		logger.Global().Infof("can't rewrite persistent permissions file %s: %s", file, err)
+		if f, err = os.OpenFile(file, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600); err != nil {
+			logger.Global().Fatalf("can't append to log %s: %s", file, err)
+		}
+		f.Seek(0, 2)
+		s.enc = json.NewEncoder(f)
+		return s
+	}
+	s.enc = json.NewEncoder(f)
+	s.depthFirstPersist(mt.root, "")
+	f.Close()
+
+	// Switch names and remove the old file.
+	if err := os.Remove(old); err != nil {
+		ctx.Infof("removing %s: %s", old, err)
+	}
+	if err := os.Rename(file, old); err != nil {
+		ctx.Infof("renaming %s to %s: %s", file, old, err)
+	}
+	if err := os.Rename(tmp, file); err != nil {
+		ctx.Fatalf("renaming %s to %s: %s", tmp, file, err)
+	}
+
+	// Reopen the new log file.  We could have just kept around the encoder used
+	// to create it but that assumes that, after the Rename above, the s.f still
+	// points to the same file.  Only true on Unix like file systems.
+	f, err = os.OpenFile(file, os.O_WRONLY|os.O_APPEND, 0600)
+	if err != nil {
+		ctx.Fatalf("can't open %s: %s", file, err)
+	}
+	f.Seek(0, 2)
+	s.enc = json.NewEncoder(f)
+	return s
+}
+
+// parseLogFile reads a file and parses the contained VersionedPermissions .
+func (s *store) parseLogFile(ctx *context.T, f *os.File) error {
+	if f == nil {
+		return nil
+	}
+	ctx.VI(2).Infof("parseLogFile(%s)", f.Name())
+	mt := s.mt
+	decoder := json.NewDecoder(f)
+	cc := &callContext{ctx: ctx,
+		creatorSet:   true,
+		create:       true,
+		ignorePerms:  true,
+		ignoreLimits: true,
+	}
+	for {
+		var e storeElement
+		if err := decoder.Decode(&e); err != nil {
+			if err == io.EOF {
+				break
+			}
+			return err
+		}
+
+		elems := strings.Split(e.N, "/")
+		cc.creator = e.C
+		n, err := mt.findNode(cc, elems, nil, nil)
+		if n == nil {
+			continue
+		}
+		if err == nil {
+			if e.D {
+				mt.deleteNode(n.parent, elems[len(elems)-1])
+				ctx.VI(2).Infof("deleted %s", e.N)
+			} else {
+				n.vPerms = &e.V
+				n.explicitPermissions = true
+				ctx.VI(2).Infof("added versions permissions %v to %s", e.V, e.N)
+			}
+		}
+		n.parent.Unlock()
+		n.Unlock()
+	}
+	return nil
+}
+
+// depthFirstPersist performs a recursive depth first traversal logging any explicit permissions.
+// Doing this immediately after reading in a log file effectively compresses the log file since
+// any duplicate or deleted entries disappear.
+func (s *store) depthFirstPersist(n *node, name string) {
+	if n.explicitPermissions {
+		s.persistPerms(name, n.creator, n.vPerms)
+	}
+	for nodeName, c := range n.children {
+		s.depthFirstPersist(c, path.Join(name, nodeName))
+	}
+}
+
+// persistPerms appends a changed permission to the log.
+func (s *store) persistPerms(name, creator string, vPerms *VersionedPermissions) error {
+	s.l.Lock()
+	defer s.l.Unlock()
+	e := storeElement{N: name, V: *vPerms, C: creator}
+	return s.enc.Encode(&e)
+}
+
+// persistDelete appends a single deletion to the log.
+func (s *store) persistDelete(name string) error {
+	s.l.Lock()
+	defer s.l.Unlock()
+	e := storeElement{N: name, D: true}
+	return s.enc.Encode(&e)
+}
+
+func (s *store) close() {
+	s.f.Close()
+}
diff --git a/services/mounttable/mounttablelib/serverlist.go b/services/mounttable/mounttablelib/serverlist.go
new file mode 100644
index 0000000..d5b8084
--- /dev/null
+++ b/services/mounttable/mounttablelib/serverlist.go
@@ -0,0 +1,128 @@
+// 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 mounttablelib
+
+import (
+	"container/list"
+	"sync"
+	"time"
+
+	"v.io/v23/naming"
+	vdltime "v.io/v23/vdlroot/time"
+)
+
+type serverListClock interface {
+	Now() time.Time
+}
+
+type realTime bool
+
+func (t realTime) Now() time.Time {
+	return time.Now()
+}
+
+// TODO(caprita): Replace this with the timekeeper library.
+var slc = serverListClock(realTime(true))
+
+// server maintains the state of a single server.  Unless expires is refreshed before the
+// time is reached, the entry will be removed.
+type server struct {
+	expires time.Time
+	oa      string // object address of server
+}
+
+// serverList represents an ordered list of servers.
+type serverList struct {
+	sync.Mutex
+	l *list.List // contains entries of type *server
+}
+
+// newServerList creates a synchronized list of servers.
+func newServerList() *serverList {
+	return &serverList{l: list.New()}
+}
+
+func (sl *serverList) len() int {
+	sl.Lock()
+	defer sl.Unlock()
+	return sl.l.Len()
+}
+
+func (sl *serverList) Front() *server {
+	sl.Lock()
+	defer sl.Unlock()
+	return sl.l.Front().Value.(*server)
+}
+
+// add to the front of the list if not already in the list, otherwise,
+// update the expiration time and move to the front of the list.  That
+// way the most recently refreshed is always first.
+func (sl *serverList) add(oa string, ttl time.Duration) {
+	expires := slc.Now().Add(ttl)
+	sl.Lock()
+	defer sl.Unlock()
+	for e := sl.l.Front(); e != nil; e = e.Next() {
+		s := e.Value.(*server)
+		if s.oa == oa {
+			s.expires = expires
+			sl.l.MoveToFront(e)
+			return
+		}
+	}
+	s := &server{
+		oa:      oa,
+		expires: expires,
+	}
+	sl.l.PushFront(s) // innocent until proven guilty
+}
+
+// remove an element from the list.  Return the number of elements remaining.
+func (sl *serverList) remove(oa string) int {
+	sl.Lock()
+	defer sl.Unlock()
+	for e := sl.l.Front(); e != nil; e = e.Next() {
+		s := e.Value.(*server)
+		if s.oa == oa {
+			sl.l.Remove(e)
+			break
+		}
+	}
+	return sl.l.Len()
+}
+
+// removeExpired removes any expired servers.
+func (sl *serverList) removeExpired() (int, int) {
+	sl.Lock()
+	defer sl.Unlock()
+
+	now := slc.Now()
+	var next *list.Element
+	removed := 0
+	for e := sl.l.Front(); e != nil; e = next {
+		s := e.Value.(*server)
+		next = e.Next()
+		if now.After(s.expires) {
+			sl.l.Remove(e)
+			removed++
+		}
+	}
+	return sl.l.Len(), removed
+}
+
+// copyToSlice returns the contents of the list as a slice of MountedServer.
+func (sl *serverList) copyToSlice() []naming.MountedServer {
+	sl.Lock()
+	defer sl.Unlock()
+	var slice []naming.MountedServer
+	for e := sl.l.Front(); e != nil; e = e.Next() {
+		s := e.Value.(*server)
+		ms := naming.MountedServer{
+			Server:   s.oa,
+			Deadline: vdltime.Deadline{Time: s.expires},
+		}
+		slice = append(slice, ms)
+	}
+	return slice
+}
diff --git a/services/mounttable/mounttablelib/serverlist_test.go b/services/mounttable/mounttablelib/serverlist_test.go
new file mode 100644
index 0000000..3f8c56a
--- /dev/null
+++ b/services/mounttable/mounttablelib/serverlist_test.go
@@ -0,0 +1,56 @@
+// 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 mounttablelib
+
+import (
+	"reflect"
+	"testing"
+	"time"
+
+	"v.io/v23/naming"
+	vdltime "v.io/v23/vdlroot/time"
+)
+
+func TestServerList(t *testing.T) {
+	eps := []string{
+		"endpoint:adfasdf@@who",
+		"endpoint:sdfgsdfg@@x/",
+		"endpoint:sdfgsdfg@@y",
+		"endpoint:dfgsfdg@@",
+	}
+
+	// Test adding entries.
+	ft := NewFakeTimeClock()
+	SetServerListClock(ft)
+	sl := newServerList()
+	for i, ep := range eps {
+		sl.add(ep, time.Duration(5*i)*time.Second)
+	}
+	if sl.len() != len(eps) {
+		t.Fatalf("got %d, want %d", sl.len(), len(eps))
+	}
+
+	// Test timing out entries.
+	ft.Advance(6 * time.Second)
+	if numLeft, _ := sl.removeExpired(); numLeft != len(eps)-2 {
+		t.Fatalf("got %d, want %d", sl.len(), len(eps)-2)
+	}
+
+	// Test removing entries.
+	sl.remove(eps[2])
+	if sl.len() != len(eps)-3 {
+		t.Fatalf("got %d, want %d", sl.len(), len(eps)-3)
+	}
+
+	// Test copyToSlice.
+	if got, want := sl.copyToSlice(), []naming.MountedServer{
+		{
+			Server:   "endpoint:dfgsfdg@@",
+			Deadline: vdltime.Deadline{Time: now.Add(15 * time.Second)},
+		},
+	}; !reflect.DeepEqual(got, want) {
+		t.Errorf("Got %v, want %v", got, want)
+	}
+}
diff --git a/services/mounttable/mounttablelib/servers.go b/services/mounttable/mounttablelib/servers.go
new file mode 100644
index 0000000..2d919d1
--- /dev/null
+++ b/services/mounttable/mounttablelib/servers.go
@@ -0,0 +1,72 @@
+// 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 mounttablelib
+
+import (
+	"net"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/options"
+	"v.io/v23/rpc"
+	"v.io/x/ref/lib/xrpc"
+)
+
+func StartServers(ctx *context.T, listenSpec rpc.ListenSpec, mountName, nhName, permsFile, persistDir, debugPrefix string) (string, func(), error) {
+	var stopFuncs []func() error
+	stop := func() {
+		for i := len(stopFuncs) - 1; i >= 0; i-- {
+			stopFuncs[i]()
+		}
+	}
+
+	mt, err := NewMountTableDispatcher(ctx, permsFile, persistDir, debugPrefix)
+	if err != nil {
+		ctx.Errorf("NewMountTable failed: %v", err)
+		return "", nil, err
+	}
+	ctx = v23.WithListenSpec(ctx, listenSpec)
+	mtServer, err := xrpc.NewDispatchingServer(ctx, mountName, mt, options.ServesMountTable(true))
+	if err != nil {
+
+		ctx.Errorf("v23.NewServer failed: %v", err)
+		return "", nil, err
+	}
+	stopFuncs = append(stopFuncs, mtServer.Stop)
+	mtEndpoints := mtServer.Status().Endpoints
+	mtName := mtEndpoints[0].Name()
+	ctx.Infof("Mount table service at: %q endpoint: %s", mountName, mtName)
+
+	if len(nhName) > 0 {
+		// The ListenSpec code ensures that we have a valid address here.
+		host, port, _ := net.SplitHostPort(listenSpec.Addrs[0].Address)
+		if port != "" {
+			neighborhoodListenSpec := listenSpec.Copy()
+			neighborhoodListenSpec.Addrs[0].Address = net.JoinHostPort(host, "0")
+			ctx = v23.WithListenSpec(ctx, neighborhoodListenSpec)
+		}
+
+		names := []string{}
+		for _, ep := range mtEndpoints {
+			names = append(names, ep.Name())
+		}
+		var nh rpc.Dispatcher
+		if host == "127.0.0.1" || host == "localhost" {
+			nh, err = NewLoopbackNeighborhoodDispatcher(nhName, names...)
+		} else {
+			nh, err = NewNeighborhoodDispatcher(nhName, names...)
+		}
+
+		nhServer, err := xrpc.NewDispatchingServer(ctx, naming.Join(mtName, "nh"), nh, options.ServesMountTable(true))
+		if err != nil {
+			ctx.Errorf("v23.NewServer failed: %v", err)
+			stop()
+			return "", nil, err
+		}
+		stopFuncs = append(stopFuncs, nhServer.Stop)
+	}
+	return mtName, stop, nil
+}
diff --git a/services/mounttable/mounttablelib/testdata/intermediate.perms b/services/mounttable/mounttablelib/testdata/intermediate.perms
new file mode 100644
index 0000000..438d3f1
--- /dev/null
+++ b/services/mounttable/mounttablelib/testdata/intermediate.perms
@@ -0,0 +1,10 @@
+{
+"": {
+	"Read":  { "In": ["..."] },
+	"Admin": { "In": ["root"] }
+},
+"x/y/z": {
+	"Read":  { "In": ["bob", "root"] },
+        "Mount": { "In": ["root"] }
+}
+}
diff --git a/services/mounttable/mounttablelib/testdata/invalid.perms b/services/mounttable/mounttablelib/testdata/invalid.perms
new file mode 100644
index 0000000..9d68933
--- /dev/null
+++ b/services/mounttable/mounttablelib/testdata/invalid.perms
@@ -0,0 +1 @@
+"
\ No newline at end of file
diff --git a/services/mounttable/mounttablelib/testdata/noRoot.perms b/services/mounttable/mounttablelib/testdata/noRoot.perms
new file mode 100644
index 0000000..b663a07
--- /dev/null
+++ b/services/mounttable/mounttablelib/testdata/noRoot.perms
@@ -0,0 +1,6 @@
+{
+"/foo/bar": {
+	"Read":  { "In": ["fake/root"] },
+        "Write": { "In": ["fake/root"] }
+}
+}
diff --git a/services/mounttable/mounttablelib/testdata/test.perms b/services/mounttable/mounttablelib/testdata/test.perms
new file mode 100644
index 0000000..cbd4e28
--- /dev/null
+++ b/services/mounttable/mounttablelib/testdata/test.perms
@@ -0,0 +1,22 @@
+{
+"": {
+	"Read":  { "In": ["..."] },
+	"Admin": { "In": ["root"] },
+	"Create": { "In": ["root", "bob"] }
+},
+"stuff": {
+	"Read":  { "In": ["bob", "root"] },
+        "Mount": { "In": ["root"] }
+},
+"a": {
+	"Read":  { "In": ["alice", "root"] },
+        "Create": { "In": ["root"] }
+},
+"users": {
+	"Create": { "In": ["..."] }
+},
+"users/%%": {
+	"Admin":  { "In": ["%%"] },
+	"Resolve": { "In": ["..."] }
+}
+}
diff --git a/services/mounttable/mounttablelib/util_test.go b/services/mounttable/mounttablelib/util_test.go
new file mode 100644
index 0000000..b99129b
--- /dev/null
+++ b/services/mounttable/mounttablelib/util_test.go
@@ -0,0 +1,35 @@
+// 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 mounttablelib
+
+import "time"
+
+// These routines are used by external tests.
+
+// SetServerListClock sets up an alternate clock.
+func SetServerListClock(x serverListClock) {
+	slc = x
+}
+
+// DefaultMaxNodesPerUser returns the maximum number of nodes per user.
+func DefaultMaxNodesPerUser() int {
+	return defaultMaxNodesPerUser
+}
+
+var now = time.Now()
+
+type fakeTime struct {
+	theTime time.Time
+}
+
+func NewFakeTimeClock() *fakeTime {
+	return &fakeTime{theTime: now}
+}
+func (ft *fakeTime) Now() time.Time {
+	return ft.theTime
+}
+func (ft *fakeTime) Advance(d time.Duration) {
+	ft.theTime = ft.theTime.Add(d)
+}
diff --git a/services/mounttable/mounttablelib/versionedpermissions.go b/services/mounttable/mounttablelib/versionedpermissions.go
new file mode 100644
index 0000000..da884c4
--- /dev/null
+++ b/services/mounttable/mounttablelib/versionedpermissions.go
@@ -0,0 +1,180 @@
+// 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 mounttablelib
+
+import (
+	"encoding/json"
+	"io"
+	"os"
+	"sort"
+	"strconv"
+	"strings"
+
+	"v.io/v23/context"
+	"v.io/v23/conventions"
+	"v.io/v23/security"
+	"v.io/v23/security/access"
+	"v.io/v23/services/mounttable"
+	"v.io/v23/verror"
+)
+
+// Blessings can't include a comma so we use them in made up user ids.  The following distinctions are
+// made so that we can account for them differently.
+const localUser = ",LOCAL,"     // a client that has our public key but no blessing from which we can extract a user name
+const blessedUser = ",BLESSED," // a client with blessings we trust but from which we can't extract a user name
+const unknownUser = ",UNKNOWN," // a client which presents no blessing we trust
+
+// VersionedPermissions associates a Version with a Permissions
+type VersionedPermissions struct {
+	V int32
+	P access.Permissions
+}
+
+func NewVersionedPermissions() *VersionedPermissions {
+	return &VersionedPermissions{P: make(access.Permissions)}
+}
+
+// Set sets the Permissions iff Version matches the current Version.  If the set happens, the Version is advanced.
+// If b is nil, this creates a new VersionedPermissions.
+func (b *VersionedPermissions) Set(ctx *context.T, verstr string, perm access.Permissions) (*VersionedPermissions, error) {
+	if b == nil {
+		b = new(VersionedPermissions)
+	}
+	if len(verstr) > 0 {
+		gen, err := strconv.ParseInt(verstr, 10, 32)
+		if err != nil {
+			return b, verror.NewErrBadVersion(ctx)
+		}
+		if gen >= 0 && int32(gen) != b.V {
+			return b, verror.NewErrBadVersion(ctx)
+		}
+	}
+	b.P = perm
+	// Increment with possible wrap.
+	b.V++
+	if b.V < 0 {
+		b.V = 0
+	}
+	return b, nil
+}
+
+// Get returns the current Version and Permissions.
+func (b *VersionedPermissions) Get() (string, access.Permissions) {
+	if b == nil {
+		return "", nil
+	}
+	return strconv.FormatInt(int64(b.V), 10), b.P
+}
+
+// AccessListForTag returns the current access list for the given tag.
+func (b *VersionedPermissions) AccessListForTag(tag string) (access.AccessList, bool) {
+	al, exists := b.P[tag]
+	return al, exists
+}
+
+// Copy copies the receiver.
+func (b *VersionedPermissions) Copy() *VersionedPermissions {
+	nt := new(VersionedPermissions)
+	nt.P = b.P.Copy()
+	nt.V = b.V
+	return nt
+}
+
+// Add adds the blessing pattern to the tag in the reciever.
+func (b *VersionedPermissions) Add(pattern security.BlessingPattern, tag string) {
+	b.P.Add(pattern, tag)
+}
+
+// parsePermFile reads a file and parses the contained permissions.
+func (mt *mountTable) parsePermFile(ctx *context.T, path string) error {
+	ctx.VI(2).Infof("parsePermFile(%s)", path)
+	if path == "" {
+		return nil
+	}
+	// A map from node name to permissions.
+	var pm map[string]access.Permissions
+	f, err := os.Open(path)
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+	decoder := json.NewDecoder(f)
+	for {
+		if err = decoder.Decode(&pm); err != nil {
+			if err == io.EOF {
+				break
+			}
+			return err
+		}
+		cc := &callContext{ctx: ctx,
+			creatorSet:   true,
+			create:       true,
+			ignorePerms:  true,
+			ignoreLimits: true,
+		}
+		// Sort the map shortest key first.  That way configs for nodes higher up in the
+		// name tree happen first ensuring that lower nodes correctly inherit permissions.
+		var keys []string
+		for name := range pm {
+			keys = append(keys, name)
+		}
+		sort.Strings(keys)
+		for _, name := range keys {
+			perms := pm[name]
+			var elems []string
+			isPattern := false
+
+			// The configuration file allows patterns and also will cause the superuser to
+			// be set to the root's administrator.
+			if len(name) == 0 {
+				// If the config file has is an Admin tag on the root AccessList, the
+				// list of Admin users is the equivalent of a super user for
+				// the whole table.  Later SetPermissions do not update the set
+				// of super users.
+				if bp, exists := perms[string(mounttable.Admin)]; exists {
+					mt.superUsers = bp
+				}
+			} else {
+				// AccessList templates terminate with a %% element.  These are very
+				// constrained matches, i.e., the trailing element of the name
+				// is copied into every %% in the AccessList.
+				elems = strings.Split(name, "/")
+				if elems[len(elems)-1] == templateVar {
+					isPattern = true
+					elems = elems[:len(elems)-1]
+				}
+			}
+
+			// Create name and add the Permissions map to it.
+			cc.creator = mt.pickCreator(ctx, nil)
+			n, err := mt.findNode(cc, elems, nil, nil)
+			if err != nil {
+				ctx.Errorf("skipping node for %v; error: %v", elems, err)
+			}
+			if n == nil {
+				continue
+			}
+			if err == nil {
+				ctx.VI(2).Infof("added perms %v to %s", perms, name)
+				if isPattern {
+					n.permsTemplate = perms
+				} else {
+					n.vPerms, _ = n.vPerms.Set(nil, "", perms)
+					n.explicitPermissions = true
+				}
+			}
+			n.parent.Unlock()
+			n.Unlock()
+		}
+	}
+	return nil
+}
+
+// pickCreator returns a string matching the blessing of the user performing the creation.
+func (mt *mountTable) pickCreator(ctx *context.T, call security.Call) string {
+	ids := conventions.GetClientUserIds(ctx, call)
+	// Replace the slashes with something else or we'll confuse the stats package.
+	return strings.Replace(ids[0], "/", "\\", 0)
+}
diff --git a/services/profile/profile.vdl b/services/profile/profile.vdl
new file mode 100644
index 0000000..c2b91a5
--- /dev/null
+++ b/services/profile/profile.vdl
@@ -0,0 +1,36 @@
+// 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 profile defines types for the implementation of Vanadium profiles.
+package profile
+
+import "v.io/v23/services/build"
+
+// Library describes a shared library that applications may use.
+type Library struct {
+	// Name is the name of the library.
+	Name         string
+	// MajorVersion is the major version of the library.
+	MajorVersion string
+	// MinorVersion is the minor version of the library.
+	MinorVersion string
+}
+
+// Specification is how we represent a profile internally. It should
+// provide enough information to allow matching of binaries to devices.
+type Specification struct {
+	// Label is a human-friendly concise label for the profile,
+	// e.g. "linux-media".
+	Label       string
+	// Description is a human-friendly description of the profile.
+	Description string
+	// Arch is the target hardware architecture of the profile.
+	Arch        build.Architecture
+	// Os is the target operating system of the profile.
+	Os          build.OperatingSystem
+	// Format is the file format supported by the profile.
+	Format      build.Format
+	// Libraries is a set of libraries the profile requires.
+	Libraries   set[Library]
+}
diff --git a/services/profile/profile.vdl.go b/services/profile/profile.vdl.go
new file mode 100644
index 0000000..ba9db3a
--- /dev/null
+++ b/services/profile/profile.vdl.go
@@ -0,0 +1,60 @@
+// 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 by the vanadium vdl tool.
+// Source: profile.vdl
+
+// Package profile defines types for the implementation of Vanadium profiles.
+package profile
+
+import (
+	// VDL system imports
+	"v.io/v23/vdl"
+
+	// VDL user imports
+	"v.io/v23/services/build"
+)
+
+// Library describes a shared library that applications may use.
+type Library struct {
+	// Name is the name of the library.
+	Name string
+	// MajorVersion is the major version of the library.
+	MajorVersion string
+	// MinorVersion is the minor version of the library.
+	MinorVersion string
+}
+
+func (Library) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/services/profile.Library"`
+}) {
+}
+
+// Specification is how we represent a profile internally. It should
+// provide enough information to allow matching of binaries to devices.
+type Specification struct {
+	// Label is a human-friendly concise label for the profile,
+	// e.g. "linux-media".
+	Label string
+	// Description is a human-friendly description of the profile.
+	Description string
+	// Arch is the target hardware architecture of the profile.
+	Arch build.Architecture
+	// Os is the target operating system of the profile.
+	Os build.OperatingSystem
+	// Format is the file format supported by the profile.
+	Format build.Format
+	// Libraries is a set of libraries the profile requires.
+	Libraries map[Library]struct{}
+}
+
+func (Specification) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/services/profile.Specification"`
+}) {
+}
+
+func init() {
+	vdl.Register((*Library)(nil))
+	vdl.Register((*Specification)(nil))
+}
diff --git a/services/profile/profile/doc.go b/services/profile/profile/doc.go
new file mode 100644
index 0000000..ec5189b
--- /dev/null
+++ b/services/profile/profile/doc.go
@@ -0,0 +1,136 @@
+// 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
+
+/*
+Command profile manages the Vanadium profile repository.
+
+Usage:
+   profile <command>
+
+The profile commands are:
+   label         Shows a human-readable profile key for the profile.
+   description   Shows a human-readable profile description for the profile.
+   specification Shows the specification of the profile.
+   put           Sets a placeholder specification for the profile.
+   remove        removes the profile specification for the profile.
+   help          Display help for commands or topics
+
+The global flags are:
+ -alsologtostderr=true
+   log to standard error as well as files
+ -log_backtrace_at=:0
+   when logging hits line file:N, emit a stack trace
+ -log_dir=
+   if non-empty, write log files to this directory
+ -logtostderr=false
+   log to standard error instead of files
+ -max_stack_buf_size=4292608
+   max size in bytes of the buffer to use for logging stack traces
+ -metadata=<just specify -metadata to activate>
+   Displays metadata for the program and exits.
+ -stderrthreshold=2
+   logs at or above this threshold go to stderr
+ -v=0
+   log level for V logs
+ -v23.credentials=
+   directory to use for storing security credentials
+ -v23.i18n-catalogue=
+   18n catalogue files to load, comma separated
+ -v23.namespace.root=[/(dev.v.io/role/vprod/service/mounttabled)@ns.dev.v.io:8101]
+   local namespace root; can be repeated to provided multiple roots
+ -v23.proxy=
+   object name of proxy service to use to export services across network
+   boundaries
+ -v23.tcp.address=
+   address to listen on
+ -v23.tcp.protocol=wsh
+   protocol to listen with
+ -v23.vtrace.cache-size=1024
+   The number of vtrace traces to store in memory.
+ -v23.vtrace.collect-regexp=
+   Spans and annotations that match this regular expression will trigger trace
+   collection.
+ -v23.vtrace.dump-on-shutdown=true
+   If true, dump all stored traces on runtime shutdown.
+ -v23.vtrace.sample-rate=0
+   Rate (from 0.0 to 1.0) to sample vtrace traces.
+ -vmodule=
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
+
+Profile label
+
+Shows a human-readable profile key for the profile.
+
+Usage:
+   profile label <profile>
+
+<profile> is the full name of the profile.
+
+Profile description
+
+Shows a human-readable profile description for the profile.
+
+Usage:
+   profile description <profile>
+
+<profile> is the full name of the profile.
+
+Profile specification
+
+Shows the specification of the profile.
+
+Usage:
+   profile specification <profile>
+
+<profile> is the full name of the profile.
+
+Profile put
+
+Sets a placeholder specification for the profile.
+
+Usage:
+   profile put <profile>
+
+<profile> is the full name of the profile.
+
+Profile remove
+
+removes the profile specification for the profile.
+
+Usage:
+   profile remove <profile>
+
+<profile> is the full name of the profile.
+
+Profile help - Display help for commands or topics
+
+Help with no args displays the usage of the parent command.
+
+Help with args displays the usage of the specified sub-command or help topic.
+
+"help ..." recursively displays help for all commands and topics.
+
+Usage:
+   profile help [flags] [command/topic ...]
+
+[command/topic ...] optionally identifies a specific sub-command or help topic.
+
+The profile help flags are:
+ -style=compact
+   The formatting style for help output:
+      compact - Good for compact cmdline output.
+      full    - Good for cmdline output, shows all global flags.
+      godoc   - Good for godoc processing.
+   Override the default by setting the CMDLINE_STYLE environment variable.
+ -width=<terminal width>
+   Format output to this target width in runes, or unlimited if width < 0.
+   Defaults to the terminal width if available.  Override the default by setting
+   the CMDLINE_WIDTH environment variable.
+*/
+package main
diff --git a/services/profile/profile/impl.go b/services/profile/profile/impl.go
new file mode 100644
index 0000000..ea27f27
--- /dev/null
+++ b/services/profile/profile/impl.go
@@ -0,0 +1,171 @@
+// 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.
+
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go .
+
+package main
+
+import (
+	"fmt"
+	"time"
+
+	"v.io/v23/context"
+	"v.io/v23/services/build"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/v23cmd"
+	_ "v.io/x/ref/runtime/factories/generic"
+	"v.io/x/ref/services/profile"
+	"v.io/x/ref/services/repository"
+)
+
+func main() {
+	cmdline.Main(cmdRoot)
+}
+
+func init() {
+	cmdline.HideGlobalFlagsExcept()
+}
+
+var cmdLabel = &cmdline.Command{
+	Runner:   v23cmd.RunnerFunc(runLabel),
+	Name:     "label",
+	Short:    "Shows a human-readable profile key for the profile.",
+	Long:     "Shows a human-readable profile key for the profile.",
+	ArgsName: "<profile>",
+	ArgsLong: "<profile> is the full name of the profile.",
+}
+
+func runLabel(ctx *context.T, env *cmdline.Env, args []string) error {
+	if expected, got := 1, len(args); expected != got {
+		return env.UsageErrorf("label: incorrect number of arguments, expected %d, got %d", expected, got)
+	}
+	name := args[0]
+	p := repository.ProfileClient(name)
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
+	defer cancel()
+	label, err := p.Label(ctx)
+	if err != nil {
+		return err
+	}
+	fmt.Fprintln(env.Stdout, label)
+	return nil
+}
+
+var cmdDescription = &cmdline.Command{
+	Runner:   v23cmd.RunnerFunc(runDescription),
+	Name:     "description",
+	Short:    "Shows a human-readable profile description for the profile.",
+	Long:     "Shows a human-readable profile description for the profile.",
+	ArgsName: "<profile>",
+	ArgsLong: "<profile> is the full name of the profile.",
+}
+
+func runDescription(ctx *context.T, env *cmdline.Env, args []string) error {
+	if expected, got := 1, len(args); expected != got {
+		return env.UsageErrorf("description: incorrect number of arguments, expected %d, got %d", expected, got)
+	}
+	name := args[0]
+	p := repository.ProfileClient(name)
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
+	defer cancel()
+	desc, err := p.Description(ctx)
+	if err != nil {
+		return err
+	}
+	fmt.Fprintln(env.Stdout, desc)
+	return nil
+}
+
+var cmdSpecification = &cmdline.Command{
+	Runner:   v23cmd.RunnerFunc(runSpecification),
+	Name:     "specification",
+	Short:    "Shows the specification of the profile.",
+	Long:     "Shows the specification of the profile.",
+	ArgsName: "<profile>",
+	ArgsLong: "<profile> is the full name of the profile.",
+}
+
+func runSpecification(ctx *context.T, env *cmdline.Env, args []string) error {
+	if expected, got := 1, len(args); expected != got {
+		return env.UsageErrorf("specification: incorrect number of arguments, expected %d, got %d", expected, got)
+	}
+	name := args[0]
+	p := repository.ProfileClient(name)
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
+	defer cancel()
+	spec, err := p.Specification(ctx)
+	if err != nil {
+		return err
+	}
+	fmt.Fprintf(env.Stdout, "%#v\n", spec)
+	return nil
+}
+
+var cmdPut = &cmdline.Command{
+	Runner:   v23cmd.RunnerFunc(runPut),
+	Name:     "put",
+	Short:    "Sets a placeholder specification for the profile.",
+	Long:     "Sets a placeholder specification for the profile.",
+	ArgsName: "<profile>",
+	ArgsLong: "<profile> is the full name of the profile.",
+}
+
+func runPut(ctx *context.T, env *cmdline.Env, args []string) error {
+	if expected, got := 1, len(args); expected != got {
+		return env.UsageErrorf("put: incorrect number of arguments, expected %d, got %d", expected, got)
+	}
+	name := args[0]
+	p := repository.ProfileClient(name)
+
+	// TODO(rthellend): Read an actual specification from a file.
+	spec := profile.Specification{
+		Arch:        build.ArchitectureAmd64,
+		Description: "Example profile to test the profile manager implementation.",
+		Format:      build.FormatElf,
+		Libraries:   map[profile.Library]struct{}{profile.Library{Name: "foo", MajorVersion: "1", MinorVersion: "0"}: struct{}{}},
+		Label:       "example",
+		Os:          build.OperatingSystemLinux,
+	}
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
+	defer cancel()
+	if err := p.Put(ctx, spec); err != nil {
+		return err
+	}
+	fmt.Fprintln(env.Stdout, "Profile added successfully.")
+	return nil
+}
+
+var cmdRemove = &cmdline.Command{
+	Runner:   v23cmd.RunnerFunc(runRemove),
+	Name:     "remove",
+	Short:    "removes the profile specification for the profile.",
+	Long:     "removes the profile specification for the profile.",
+	ArgsName: "<profile>",
+	ArgsLong: "<profile> is the full name of the profile.",
+}
+
+func runRemove(ctx *context.T, env *cmdline.Env, args []string) error {
+	if expected, got := 1, len(args); expected != got {
+		return env.UsageErrorf("remove: incorrect number of arguments, expected %d, got %d", expected, got)
+	}
+	name := args[0]
+	p := repository.ProfileClient(name)
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
+	defer cancel()
+	if err := p.Remove(ctx); err != nil {
+		return err
+	}
+	fmt.Fprintln(env.Stdout, "Profile removed successfully.")
+	return nil
+}
+
+var cmdRoot = &cmdline.Command{
+	Name:  "profile",
+	Short: "manages the Vanadium profile repository",
+	Long: `
+Command profile manages the Vanadium profile repository.
+`,
+	Children: []*cmdline.Command{cmdLabel, cmdDescription, cmdSpecification, cmdPut, cmdRemove},
+}
diff --git a/services/profile/profile/impl_test.go b/services/profile/profile/impl_test.go
new file mode 100644
index 0000000..3879f0e
--- /dev/null
+++ b/services/profile/profile/impl_test.go
@@ -0,0 +1,148 @@
+// 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 (
+	"bytes"
+	"fmt"
+	"strings"
+	"testing"
+
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/services/build"
+
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/v23cmd"
+	"v.io/x/ref/lib/xrpc"
+	_ "v.io/x/ref/runtime/factories/generic"
+	"v.io/x/ref/services/profile"
+	"v.io/x/ref/services/repository"
+	"v.io/x/ref/test"
+)
+
+var (
+	// spec is an example profile specification used throughout the test.
+	spec = profile.Specification{
+		Arch:        build.ArchitectureAmd64,
+		Description: "Example profile to test the profile repository implementation.",
+		Format:      build.FormatElf,
+		Libraries:   map[profile.Library]struct{}{profile.Library{Name: "foo", MajorVersion: "1", MinorVersion: "0"}: struct{}{}},
+		Label:       "example",
+		Os:          build.OperatingSystemLinux,
+	}
+)
+
+//go:generate v23 test generate
+
+type server struct {
+	suffix string
+}
+
+func (s *server) Label(ctx *context.T, _ rpc.ServerCall) (string, error) {
+	ctx.VI(2).Infof("%v.Label() was called", s.suffix)
+	if s.suffix != "exists" {
+		return "", fmt.Errorf("profile doesn't exist: %v", s.suffix)
+	}
+	return spec.Label, nil
+}
+
+func (s *server) Description(ctx *context.T, _ rpc.ServerCall) (string, error) {
+	ctx.VI(2).Infof("%v.Description() was called", s.suffix)
+	if s.suffix != "exists" {
+		return "", fmt.Errorf("profile doesn't exist: %v", s.suffix)
+	}
+	return spec.Description, nil
+}
+
+func (s *server) Specification(ctx *context.T, _ rpc.ServerCall) (profile.Specification, error) {
+	ctx.VI(2).Infof("%v.Specification() was called", s.suffix)
+	if s.suffix != "exists" {
+		return profile.Specification{}, fmt.Errorf("profile doesn't exist: %v", s.suffix)
+	}
+	return spec, nil
+}
+
+func (s *server) Put(ctx *context.T, _ rpc.ServerCall, _ profile.Specification) error {
+	ctx.VI(2).Infof("%v.Put() was called", s.suffix)
+	return nil
+}
+
+func (s *server) Remove(ctx *context.T, _ rpc.ServerCall) error {
+	ctx.VI(2).Infof("%v.Remove() was called", s.suffix)
+	if s.suffix != "exists" {
+		return fmt.Errorf("profile doesn't exist: %v", s.suffix)
+	}
+	return nil
+}
+
+type dispatcher struct {
+}
+
+func (d *dispatcher) Lookup(_ *context.T, suffix string) (interface{}, security.Authorizer, error) {
+	return repository.ProfileServer(&server{suffix: suffix}), nil, nil
+}
+
+func TestProfileClient(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	server, err := xrpc.NewDispatchingServer(ctx, "", &dispatcher{})
+	if err != nil {
+		return
+	}
+
+	// Setup the command-line.
+	var stdout, stderr bytes.Buffer
+	env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
+	exists := naming.JoinAddressName(server.Status().Endpoints[0].String(), "exists")
+
+	// Test the 'label' command.
+	if err := v23cmd.ParseAndRunForTest(cmdRoot, ctx, env, []string{"label", exists}); err != nil {
+		t.Fatalf("%v", err)
+	}
+	if expected, got := spec.Label, strings.TrimSpace(stdout.String()); got != expected {
+		t.Errorf("Got %q, expected %q", got, expected)
+	}
+	stdout.Reset()
+
+	// Test the 'description' command.
+	if err := v23cmd.ParseAndRunForTest(cmdRoot, ctx, env, []string{"description", exists}); err != nil {
+		t.Fatalf("%v", err)
+	}
+	if expected, got := spec.Description, strings.TrimSpace(stdout.String()); got != expected {
+		t.Errorf("Got %q, expected %q", got, expected)
+	}
+	stdout.Reset()
+
+	// Test the 'spec' command.
+	if err := v23cmd.ParseAndRunForTest(cmdRoot, ctx, env, []string{"specification", exists}); err != nil {
+		t.Fatalf("%v", err)
+	}
+	if expected, got := fmt.Sprintf("%#v", spec), strings.TrimSpace(stdout.String()); got != expected {
+		t.Errorf("Got %q, expected %q", got, expected)
+	}
+	stdout.Reset()
+
+	// Test the 'put' command.
+	if err := v23cmd.ParseAndRunForTest(cmdRoot, ctx, env, []string{"put", exists}); err != nil {
+		t.Fatalf("%v", err)
+	}
+	if expected, got := "Profile added successfully.", strings.TrimSpace(stdout.String()); got != expected {
+		t.Errorf("Got %q, expected %q", got, expected)
+	}
+	stdout.Reset()
+
+	// Test the 'remove' command.
+	if err := v23cmd.ParseAndRunForTest(cmdRoot, ctx, env, []string{"remove", exists}); err != nil {
+		t.Fatalf("%v", err)
+	}
+	if expected, got := "Profile removed successfully.", strings.TrimSpace(stdout.String()); got != expected {
+		t.Errorf("Got %q, expected %q", got, expected)
+	}
+	stdout.Reset()
+}
diff --git a/services/profile/profile/v23_internal_test.go b/services/profile/profile/v23_internal_test.go
new file mode 100644
index 0000000..ae59080
--- /dev/null
+++ b/services/profile/profile/v23_internal_test.go
@@ -0,0 +1,20 @@
+// 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 main
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	os.Exit(m.Run())
+}
diff --git a/services/profile/profiled/dispatcher.go b/services/profile/profiled/dispatcher.go
new file mode 100644
index 0000000..dff2e59
--- /dev/null
+++ b/services/profile/profiled/dispatcher.go
@@ -0,0 +1,40 @@
+// 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 (
+	"path/filepath"
+
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/x/ref/services/internal/fs"
+	"v.io/x/ref/services/repository"
+)
+
+// dispatcher holds the state of the profile repository dispatcher.
+type dispatcher struct {
+	store     *fs.Memstore
+	auth      security.Authorizer
+	storeRoot string
+}
+
+var _ rpc.Dispatcher = (*dispatcher)(nil)
+
+// NewDispatcher is the dispatcher factory. storeDir is a path to a
+// directory in which the profile state is persisted.
+func NewDispatcher(storeDir string, authorizer security.Authorizer) (rpc.Dispatcher, error) {
+	store, err := fs.NewMemstore(filepath.Join(storeDir, "profilestate.db"))
+	if err != nil {
+		return nil, err
+	}
+	return &dispatcher{store: store, storeRoot: storeDir, auth: authorizer}, nil
+}
+
+// DISPATCHER INTERFACE IMPLEMENTATION
+
+func (d *dispatcher) Lookup(_ *context.T, suffix string) (interface{}, security.Authorizer, error) {
+	return repository.ProfileServer(NewProfileService(d.store, d.storeRoot, suffix)), d.auth, nil
+}
diff --git a/services/profile/profiled/doc.go b/services/profile/profiled/doc.go
new file mode 100644
index 0000000..85b8107
--- /dev/null
+++ b/services/profile/profiled/doc.go
@@ -0,0 +1,70 @@
+// 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
+
+/*
+Command profiled runs the profile daemon, which implements the
+v.io/x/ref/services/repository.Profile interface.
+
+Usage:
+   profiled [flags]
+
+The profiled flags are:
+ -name=
+   Name to mount the profile repository as.
+ -store=
+   Local directory to store profiles in.
+
+The global flags are:
+ -alsologtostderr=true
+   log to standard error as well as files
+ -log_backtrace_at=:0
+   when logging hits line file:N, emit a stack trace
+ -log_dir=
+   if non-empty, write log files to this directory
+ -logtostderr=false
+   log to standard error instead of files
+ -max_stack_buf_size=4292608
+   max size in bytes of the buffer to use for logging stack traces
+ -metadata=<just specify -metadata to activate>
+   Displays metadata for the program and exits.
+ -stderrthreshold=2
+   logs at or above this threshold go to stderr
+ -v=0
+   log level for V logs
+ -v23.credentials=
+   directory to use for storing security credentials
+ -v23.i18n-catalogue=
+   18n catalogue files to load, comma separated
+ -v23.namespace.root=[/(dev.v.io/role/vprod/service/mounttabled)@ns.dev.v.io:8101]
+   local namespace root; can be repeated to provided multiple roots
+ -v23.permissions.file=map[]
+   specify a perms file as <name>:<permsfile>
+ -v23.permissions.literal=
+   explicitly specify the runtime perms as a JSON-encoded access.Permissions.
+   Overrides all --v23.permissions.file flags.
+ -v23.proxy=
+   object name of proxy service to use to export services across network
+   boundaries
+ -v23.tcp.address=
+   address to listen on
+ -v23.tcp.protocol=wsh
+   protocol to listen with
+ -v23.vtrace.cache-size=1024
+   The number of vtrace traces to store in memory.
+ -v23.vtrace.collect-regexp=
+   Spans and annotations that match this regular expression will trigger trace
+   collection.
+ -v23.vtrace.dump-on-shutdown=true
+   If true, dump all stored traces on runtime shutdown.
+ -v23.vtrace.sample-rate=0
+   Rate (from 0.0 to 1.0) to sample vtrace traces.
+ -vmodule=
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
+*/
+package main
diff --git a/services/profile/profiled/impl_test.go b/services/profile/profiled/impl_test.go
new file mode 100644
index 0000000..89e5667
--- /dev/null
+++ b/services/profile/profiled/impl_test.go
@@ -0,0 +1,158 @@
+// 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 (
+	"io/ioutil"
+	"os"
+	"reflect"
+	"testing"
+
+	"v.io/v23/naming"
+	"v.io/v23/services/build"
+
+	"v.io/x/ref/lib/xrpc"
+	"v.io/x/ref/services/profile"
+	"v.io/x/ref/services/repository"
+	"v.io/x/ref/test"
+)
+
+var (
+	// spec is an example profile specification used throughout the test.
+	spec = profile.Specification{
+		Arch:        build.ArchitectureAmd64,
+		Description: "Example profile to test the profile repository implementation.",
+		Format:      build.FormatElf,
+		Libraries:   map[profile.Library]struct{}{profile.Library{Name: "foo", MajorVersion: "1", MinorVersion: "0"}: struct{}{}},
+		Label:       "example",
+		Os:          build.OperatingSystemLinux,
+	}
+)
+
+//go:generate v23 test generate
+
+// TestInterface tests that the implementation correctly implements
+// the Profile interface.
+func TestInterface(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	dir, prefix := "", ""
+	store, err := ioutil.TempDir(dir, prefix)
+	if err != nil {
+		t.Fatalf("TempDir(%q, %q) failed: %v", dir, prefix, err)
+	}
+	defer os.RemoveAll(store)
+	dispatcher, err := NewDispatcher(store, nil)
+	if err != nil {
+		t.Fatalf("NewDispatcher() failed: %v", err)
+	}
+	server, err := xrpc.NewDispatchingServer(ctx, "", dispatcher)
+	if err != nil {
+		t.Fatalf("NewServer() failed: %v", err)
+	}
+	endpoint := server.Status().Endpoints[0].String()
+
+	// Create client stubs for talking to the server.
+	stub := repository.ProfileClient(naming.JoinAddressName(endpoint, "linux/base"))
+	// Put
+	if err := stub.Put(ctx, spec); err != nil {
+		t.Fatalf("Put() failed: %v", err)
+	}
+
+	// Label
+	label, err := stub.Label(ctx)
+	if err != nil {
+		t.Fatalf("Label() failed: %v", err)
+	}
+	if label != spec.Label {
+		t.Fatalf("Unexpected output: expected %v, got %v", spec.Label, label)
+	}
+
+	// Description
+	description, err := stub.Description(ctx)
+	if err != nil {
+		t.Fatalf("Description() failed: %v", err)
+	}
+	if description != spec.Description {
+		t.Fatalf("Unexpected output: expected %v, got %v", spec.Description, description)
+	}
+
+	// Specification
+	specification, err := stub.Specification(ctx)
+	if err != nil {
+		t.Fatalf("Specification() failed: %v", err)
+	}
+	if !reflect.DeepEqual(spec, specification) {
+		t.Fatalf("Unexpected output: expected %v, got %v", spec, specification)
+	}
+
+	// Remove
+	if err := stub.Remove(ctx); err != nil {
+		t.Fatalf("Remove() failed: %v", err)
+	}
+}
+
+func TestPreserveAcrossRestarts(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	dir, prefix := "", ""
+	store, err := ioutil.TempDir(dir, prefix)
+	if err != nil {
+		t.Fatalf("TempDir(%q, %q) failed: %v", dir, prefix, err)
+	}
+	defer os.RemoveAll(store)
+	dispatcher, err := NewDispatcher(store, nil)
+	if err != nil {
+		t.Fatalf("NewDispatcher() failed: %v", err)
+	}
+	server, err := xrpc.NewDispatchingServer(ctx, "", dispatcher)
+	if err != nil {
+		t.Fatalf("NewServer() failed: %v", err)
+	}
+	endpoint := server.Status().Endpoints[0].String()
+
+	// Create client stubs for talking to the server.
+	stub := repository.ProfileClient(naming.JoinAddressName(endpoint, "linux/base"))
+
+	if err := stub.Put(ctx, spec); err != nil {
+		t.Fatalf("Put() failed: %v", err)
+	}
+
+	label, err := stub.Label(ctx)
+	if err != nil {
+		t.Fatalf("Label() failed: %v", err)
+	}
+	if label != spec.Label {
+		t.Fatalf("Unexpected output: expected %v, got %v", spec.Label, label)
+	}
+
+	// Stop the first server.
+	server.Stop()
+
+	// Setup and start a second server.
+	dispatcher, err = NewDispatcher(store, nil)
+	if err != nil {
+		t.Fatalf("NewDispatcher() failed: %v", err)
+	}
+	server, err = xrpc.NewDispatchingServer(ctx, "", dispatcher)
+	if err != nil {
+		t.Fatalf("NewServer() failed: %v", err)
+	}
+	endpoint = server.Status().Endpoints[0].String()
+
+	// Create client stubs for talking to the server.
+	stub = repository.ProfileClient(naming.JoinAddressName(endpoint, "linux/base"))
+
+	// Label
+	label, err = stub.Label(ctx)
+	if err != nil {
+		t.Fatalf("Label() failed: %v", err)
+	}
+	if label != spec.Label {
+		t.Fatalf("Unexpected output: expected %v, got %v", spec.Label, label)
+	}
+}
diff --git a/services/profile/profiled/main.go b/services/profile/profiled/main.go
new file mode 100644
index 0000000..93cf635
--- /dev/null
+++ b/services/profile/profiled/main.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.
+
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go . -help
+
+package main
+
+import (
+	"fmt"
+
+	"v.io/v23/context"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/security/securityflag"
+	"v.io/x/ref/lib/signals"
+	"v.io/x/ref/lib/v23cmd"
+	"v.io/x/ref/lib/xrpc"
+	_ "v.io/x/ref/runtime/factories/roaming"
+)
+
+var name, store string
+
+func main() {
+	cmdProfileD.Flags.StringVar(&name, "name", "", "Name to mount the profile repository as.")
+	cmdProfileD.Flags.StringVar(&store, "store", "", "Local directory to store profiles in.")
+
+	cmdline.HideGlobalFlagsExcept()
+	cmdline.Main(cmdProfileD)
+}
+
+var cmdProfileD = &cmdline.Command{
+	Runner: v23cmd.RunnerFunc(runProfileD),
+	Name:   "profiled",
+	Short:  "Runs the profile daemon.",
+	Long: `
+Command profiled runs the profile daemon, which implements the
+v.io/x/ref/services/repository.Profile interface.
+`,
+}
+
+func runProfileD(ctx *context.T, env *cmdline.Env, args []string) error {
+	if store == "" {
+		return env.UsageErrorf("Specify a directory for storing profiles using --store=<name>")
+	}
+
+	dispatcher, err := NewDispatcher(store, securityflag.NewAuthorizerOrDie())
+	if err != nil {
+		return fmt.Errorf("NewDispatcher() failed: %v", err)
+	}
+
+	server, err := xrpc.NewDispatchingServer(ctx, name, dispatcher)
+	if err != nil {
+		return fmt.Errorf("NewServer() failed: %v", err)
+	}
+	ctx.Infof("Profile repository running at endpoint=%v", server.Status().Endpoints[0])
+
+	// Wait until shutdown.
+	<-signals.ShutdownOnSignals(ctx)
+	return nil
+}
diff --git a/services/profile/profiled/profiled_v23_test.go b/services/profile/profiled/profiled_v23_test.go
new file mode 100644
index 0000000..a91cfbe
--- /dev/null
+++ b/services/profile/profiled/profiled_v23_test.go
@@ -0,0 +1,95 @@
+// 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_test
+
+import (
+	"fmt"
+	"os"
+	"strings"
+
+	"v.io/v23/naming"
+	"v.io/v23/services/build"
+	"v.io/x/ref/test/v23tests"
+)
+
+//go:generate v23 test generate
+
+func profileCommandOutput(i *v23tests.T, profileBin *v23tests.Binary, expectError bool, command, name, suffix string) string {
+	labelArgs := []string{
+		command, naming.Join(name, suffix),
+	}
+	labelCmd := profileBin.Start(labelArgs...)
+	out := labelCmd.Output()
+	err := labelCmd.Wait(os.Stdout, os.Stderr)
+	if err != nil && !expectError {
+		i.Fatalf("%s %q failed: %v\n%v", profileBin.Path(), strings.Join(labelArgs, " "), err, out)
+	}
+	if err == nil && expectError {
+		i.Fatalf("%s %q did not fail when it should", profileBin.Path(), strings.Join(labelArgs, " "))
+	}
+	return strings.TrimSpace(out)
+}
+
+func putProfile(i *v23tests.T, profileBin *v23tests.Binary, name, suffix string) {
+	putArgs := []string{
+		"put", naming.Join(name, suffix),
+	}
+	profileBin.Start(putArgs...).WaitOrDie(os.Stdout, os.Stderr)
+}
+
+func removeProfile(i *v23tests.T, profileBin *v23tests.Binary, name, suffix string) {
+	removeArgs := []string{
+		"remove", naming.Join(name, suffix),
+	}
+	profileBin.Start(removeArgs...).WaitOrDie(os.Stdout, os.Stderr)
+}
+
+func V23TestProfileRepository(i *v23tests.T) {
+	v23tests.RunRootMT(i, "--v23.tcp.address=127.0.0.1:0")
+
+	// Start the profile repository.
+	profileRepoName := "test-profile-repo"
+	profileRepoStore := i.NewTempDir("")
+	args := []string{
+		"-name=" + profileRepoName, "-store=" + profileRepoStore,
+		"-v23.tcp.address=127.0.0.1:0",
+	}
+	i.BuildV23Pkg("v.io/x/ref/services/profile/profiled").Start(args...)
+
+	clientBin := i.BuildV23Pkg("v.io/x/ref/services/profile/profile")
+
+	// Create a profile.
+	const profile = "test-profile"
+	putProfile(i, clientBin, profileRepoName, profile)
+
+	// Retrieve the profile label and check it matches the
+	// expected label.
+	profileLabel := profileCommandOutput(i, clientBin, false, "label", profileRepoName, profile)
+	if got, want := profileLabel, "example"; got != want {
+		i.Fatalf("unexpected output: got %v, want %v", got, want)
+	}
+
+	// Retrieve the profile description and check it matches the
+	// expected description.
+	profileDesc := profileCommandOutput(i, clientBin, false, "description", profileRepoName, profile)
+	if got, want := profileDesc, "Example profile to test the profile manager implementation."; got != want {
+		i.Fatalf("unexpected output: got %v, want %v", got, want)
+	}
+
+	// Retrieve the profile specification and check it matches the
+	// expected specification.
+	profileSpec := profileCommandOutput(i, clientBin, false, "specification", profileRepoName, profile)
+	if got, want := profileSpec, fmt.Sprintf(`profile.Specification{Label:"example", Description:"Example profile to test the profile manager implementation.", Arch:%d, Os:%d, Format:%d, Libraries:map[profile.Library]struct {}{profile.Library{Name:"foo", MajorVersion:"1", MinorVersion:"0"}:struct {}{}}}`, build.ArchitectureAmd64, build.OperatingSystemLinux, build.FormatElf); got != want {
+		i.Fatalf("unexpected output: got %v, want %v", got, want)
+	}
+
+	// Remove the profile.
+	removeProfile(i, clientBin, profileRepoName, profile)
+
+	// Check that the profile no longer exists.
+	profileCommandOutput(i, clientBin, true, "label", profileRepoName, profile)
+	profileCommandOutput(i, clientBin, true, "description", profileRepoName, profile)
+	profileCommandOutput(i, clientBin, true, "specification", profileRepoName, profile)
+}
diff --git a/services/profile/profiled/service.go b/services/profile/profiled/service.go
new file mode 100644
index 0000000..9f8f689
--- /dev/null
+++ b/services/profile/profiled/service.go
@@ -0,0 +1,129 @@
+// 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 (
+	"errors"
+
+	"v.io/x/ref/services/internal/fs"
+	"v.io/x/ref/services/profile"
+	"v.io/x/ref/services/repository"
+
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+)
+
+// profileService implements the Profile server interface.
+type profileService struct {
+	// store is the storage server used for storing profile data.
+	store *fs.Memstore
+	// storeRoot is a name in the Store under which all data will be stored.
+	storeRoot string
+	// suffix is the name of the profile specification.
+	suffix string
+}
+
+var (
+	errNotFound        = errors.New("not found")
+	errOperationFailed = errors.New("operation failed")
+)
+
+// NewProfileService returns a new Profile service implementation.
+func NewProfileService(store *fs.Memstore, storeRoot, suffix string) repository.ProfileServerMethods {
+	return &profileService{store: store, storeRoot: storeRoot, suffix: suffix}
+}
+
+// STORE MANAGEMENT INTERFACE IMPLEMENTATION
+
+func (i *profileService) Put(ctx *context.T, call rpc.ServerCall, profile profile.Specification) error {
+	ctx.VI(0).Infof("%v.Put(%v)", i.suffix, profile)
+	// Transaction is rooted at "", so tname == tid.
+	i.store.Lock()
+	defer i.store.Unlock()
+	tname, err := i.store.BindTransactionRoot("").CreateTransaction(call)
+	if err != nil {
+		return err
+	}
+	path := naming.Join(tname, "/profiles", i.suffix)
+	object := i.store.BindObject(path)
+	if _, err := object.Put(call, profile); err != nil {
+		return errOperationFailed
+	}
+	if err := i.store.BindTransaction(tname).Commit(call); err != nil {
+		return errOperationFailed
+	}
+	return nil
+}
+
+func (i *profileService) Remove(ctx *context.T, call rpc.ServerCall) error {
+	ctx.VI(0).Infof("%v.Remove()", i.suffix)
+	i.store.Lock()
+	defer i.store.Unlock()
+	// Transaction is rooted at "", so tname == tid.
+	tname, err := i.store.BindTransactionRoot("").CreateTransaction(call)
+	if err != nil {
+		return err
+	}
+	path := naming.Join(tname, "/profiles", i.suffix)
+	object := i.store.BindObject(path)
+	found, err := object.Exists(call)
+	if err != nil {
+		return errOperationFailed
+	}
+	if !found {
+		return errNotFound
+	}
+	if err := object.Remove(call); err != nil {
+		return errOperationFailed
+	}
+	if err := i.store.BindTransaction(tname).Commit(call); err != nil {
+		return errOperationFailed
+	}
+	return nil
+}
+
+// PROFILE INTERACE IMPLEMENTATION
+
+func (i *profileService) lookup(call rpc.ServerCall) (profile.Specification, error) {
+	empty := profile.Specification{}
+	path := naming.Join("/profiles", i.suffix)
+
+	i.store.Lock()
+	defer i.store.Unlock()
+
+	entry, err := i.store.BindObject(path).Get(call)
+	if err != nil {
+		return empty, errNotFound
+	}
+	s, ok := entry.Value.(profile.Specification)
+	if !ok {
+		return empty, errOperationFailed
+	}
+	return s, nil
+}
+
+func (i *profileService) Label(ctx *context.T, call rpc.ServerCall) (string, error) {
+	ctx.VI(0).Infof("%v.Label()", i.suffix)
+	s, err := i.lookup(call)
+	if err != nil {
+		return "", err
+	}
+	return s.Label, nil
+}
+
+func (i *profileService) Description(ctx *context.T, call rpc.ServerCall) (string, error) {
+	ctx.VI(0).Infof("%v.Description()", i.suffix)
+	s, err := i.lookup(call)
+	if err != nil {
+		return "", err
+	}
+	return s.Description, nil
+}
+
+func (i *profileService) Specification(ctx *context.T, call rpc.ServerCall) (profile.Specification, error) {
+	ctx.VI(0).Infof("%v.Specification()", i.suffix)
+	return i.lookup(call)
+}
diff --git a/services/profile/profiled/v23_test.go b/services/profile/profiled/v23_test.go
new file mode 100644
index 0000000..9df2772
--- /dev/null
+++ b/services/profile/profiled/v23_test.go
@@ -0,0 +1,30 @@
+// 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 main_test
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
+	"v.io/x/ref/test/v23tests"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	modules.DispatchAndExitIfChild()
+	cleanup := v23tests.UseSharedBinDir()
+	r := m.Run()
+	cleanup()
+	os.Exit(r)
+}
+
+func TestV23ProfileRepository(t *testing.T) {
+	v23tests.RunTest(t, V23TestProfileRepository)
+}
diff --git a/services/proxy/proxyd/doc.go b/services/proxy/proxyd/doc.go
new file mode 100644
index 0000000..cd0d3ce
--- /dev/null
+++ b/services/proxy/proxyd/doc.go
@@ -0,0 +1,79 @@
+// 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
+
+/*
+Command proxyd is a daemon that listens for connections from Vanadium services
+(typically behind NATs) and proxies these services to the outside world.
+
+Usage:
+   proxyd [flags]
+
+The proxyd flags are:
+ -access-list=
+   Blessings that are authorized to listen via the proxy.  JSON-encoded
+   representation of access.AccessList.  An empty string implies the default
+   authorization policy.
+ -healthz-address=
+   Network address on which the HTTP healthz server runs.  It is intended to be
+   used with a load balancer.  The load balancer must be able to reach this
+   address in order to verify that the proxy server is running.
+ -name=
+   Name to mount the proxy as.
+ -published-address=
+   DEPRECATED - the proxy now uses listenspecs and the address chooser
+   mechanism.
+
+The global flags are:
+ -alsologtostderr=true
+   log to standard error as well as files
+ -log_backtrace_at=:0
+   when logging hits line file:N, emit a stack trace
+ -log_dir=
+   if non-empty, write log files to this directory
+ -logtostderr=false
+   log to standard error instead of files
+ -max_stack_buf_size=4292608
+   max size in bytes of the buffer to use for logging stack traces
+ -metadata=<just specify -metadata to activate>
+   Displays metadata for the program and exits.
+ -stderrthreshold=2
+   logs at or above this threshold go to stderr
+ -v=0
+   log level for V logs
+ -v23.credentials=
+   directory to use for storing security credentials
+ -v23.i18n-catalogue=
+   18n catalogue files to load, comma separated
+ -v23.namespace.root=[/(dev.v.io/role/vprod/service/mounttabled)@ns.dev.v.io:8101]
+   local namespace root; can be repeated to provided multiple roots
+ -v23.permissions.file=map[]
+   specify a perms file as <name>:<permsfile>
+ -v23.permissions.literal=
+   explicitly specify the runtime perms as a JSON-encoded access.Permissions.
+   Overrides all --v23.permissions.file flags.
+ -v23.proxy=
+   object name of proxy service to use to export services across network
+   boundaries
+ -v23.tcp.address=
+   address to listen on
+ -v23.tcp.protocol=wsh
+   protocol to listen with
+ -v23.vtrace.cache-size=1024
+   The number of vtrace traces to store in memory.
+ -v23.vtrace.collect-regexp=
+   Spans and annotations that match this regular expression will trigger trace
+   collection.
+ -v23.vtrace.dump-on-shutdown=true
+   If true, dump all stored traces on runtime shutdown.
+ -v23.vtrace.sample-rate=0
+   Rate (from 0.0 to 1.0) to sample vtrace traces.
+ -vmodule=
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
+*/
+package main
diff --git a/services/proxy/proxyd/main.go b/services/proxy/proxyd/main.go
new file mode 100644
index 0000000..c7e6950
--- /dev/null
+++ b/services/proxy/proxyd/main.go
@@ -0,0 +1,135 @@
+// 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.
+
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go . -help
+
+package main
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"time"
+
+	"v.io/x/lib/cmdline"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/logging"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/security/access"
+
+	"v.io/x/ref/lib/signals"
+	"v.io/x/ref/lib/v23cmd"
+	"v.io/x/ref/lib/xrpc"
+	"v.io/x/ref/runtime/factories/static"
+)
+
+var pubAddress, healthzAddr, name, acl string
+
+func main() {
+	cmdProxyD.Flags.StringVar(&pubAddress, "published-address", "", "DEPRECATED - the proxy now uses listenspecs and the address chooser mechanism.")
+	cmdProxyD.Flags.StringVar(&healthzAddr, "healthz-address", "", "Network address on which the HTTP healthz server runs.  It is intended to be used with a load balancer.  The load balancer must be able to reach this address in order to verify that the proxy server is running.")
+	cmdProxyD.Flags.StringVar(&name, "name", "", "Name to mount the proxy as.")
+	cmdProxyD.Flags.StringVar(&acl, "access-list", "", "Blessings that are authorized to listen via the proxy.  JSON-encoded representation of access.AccessList.  An empty string implies the default authorization policy.")
+
+	cmdline.HideGlobalFlagsExcept()
+	cmdline.Main(cmdProxyD)
+}
+
+var cmdProxyD = &cmdline.Command{
+	Runner: v23cmd.RunnerFunc(runProxyD),
+	Name:   "proxyd",
+	Short:  "Proxies services to the outside world",
+	Long: `
+Command proxyd is a daemon that listens for connections from Vanadium services
+(typically behind NATs) and proxies these services to the outside world.
+`,
+}
+
+func runProxyD(ctx *context.T, env *cmdline.Env, args []string) error {
+	listenSpec := v23.GetListenSpec(ctx)
+	if len(listenSpec.Addrs) != 1 {
+		return env.UsageErrorf("proxyd can only listen on one address: %v", listenSpec.Addrs)
+	}
+	if listenSpec.Proxy != "" {
+		return env.UsageErrorf("proxyd cannot listen through another proxy")
+	}
+	var authorizer security.Authorizer
+	if len(acl) > 0 {
+		var list access.AccessList
+		if err := json.NewDecoder(bytes.NewBufferString(acl)).Decode(&list); err != nil {
+			return env.UsageErrorf("invalid -access-list: %v", err)
+		}
+		// Always add ourselves, for the the reserved methods server
+		// started below.
+		list.In = append(list.In, security.DefaultBlessingPatterns(v23.GetPrincipal(ctx))...)
+		ctx.Infof("Using access list to control proxy use: %v", list)
+		authorizer = list
+	}
+
+	proxyShutdown, proxyEndpoint, err := static.NewProxy(ctx, listenSpec, authorizer, name)
+	if err != nil {
+		return err
+	}
+	defer proxyShutdown()
+
+	if len(name) > 0 {
+		// Print out a directly accessible name for the proxy table so
+		// that integration tests can reliably read it from stdout.
+		fmt.Printf("NAME=%s\n", proxyEndpoint.Name())
+	} else {
+		fmt.Printf("Proxy listening on %s\n", proxyEndpoint)
+	}
+
+	if len(healthzAddr) != 0 {
+		go startHealthzServer(ctx, healthzAddr)
+	}
+
+	// Start an RPC Server that listens through the proxy itself. This
+	// server will serve reserved methods only.
+	var monitoringName string
+	if len(name) > 0 {
+		monitoringName = name + "-mon"
+	}
+	ctx = v23.WithListenSpec(ctx, rpc.ListenSpec{Proxy: proxyEndpoint.Name()})
+	server, err := xrpc.NewDispatchingServer(ctx, monitoringName, &nilDispatcher{})
+	if err != nil {
+		return fmt.Errorf("NewServer failed: %v", err)
+	}
+	defer server.Stop()
+	<-signals.ShutdownOnSignals(ctx)
+	return nil
+}
+
+type nilDispatcher struct{}
+
+func (nilDispatcher) Lookup(_ *context.T, suffix string) (interface{}, security.Authorizer, error) {
+	return nil, nil, nil
+}
+
+// healthzHandler implements net/http.Handler
+type healthzHandler struct{}
+
+func (healthzHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	w.Write([]byte("ok"))
+}
+
+// startHealthzServer starts a HTTP server that simply returns "ok" to every
+// request. This is needed to let the load balancer know that the proxy server
+// is running.
+func startHealthzServer(logger logging.Logger, addr string) {
+	s := http.Server{
+		Addr:         addr,
+		Handler:      healthzHandler{},
+		ReadTimeout:  10 * time.Second,
+		WriteTimeout: 10 * time.Second,
+	}
+	if err := s.ListenAndServe(); err != nil {
+		logger.Fatal(err)
+	}
+}
diff --git a/services/proxy/proxyd/proxyd_v23_test.go b/services/proxy/proxyd/proxyd_v23_test.go
new file mode 100644
index 0000000..86b7c64
--- /dev/null
+++ b/services/proxy/proxyd/proxyd_v23_test.go
@@ -0,0 +1,85 @@
+// 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_test
+
+import (
+	"fmt"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/x/ref/lib/xrpc"
+	"v.io/x/ref/test/modules"
+	"v.io/x/ref/test/v23tests"
+)
+
+//go:generate v23 test generate
+
+const (
+	proxyName   = "proxy"    // Name which the proxy mounts itself at
+	serverName  = "server"   // Name which the server mounts itself at
+	responseVar = "RESPONSE" // Name of the variable used by client program to output the response
+)
+
+func V23TestProxyd(t *v23tests.T) {
+	v23tests.RunRootMT(t, "--v23.tcp.address=127.0.0.1:0")
+	var (
+		proxydCreds, _ = t.Shell().NewChildCredentials("proxyd")
+		serverCreds, _ = t.Shell().NewChildCredentials("server")
+		clientCreds, _ = t.Shell().NewChildCredentials("client")
+		proxyd         = t.BuildV23Pkg("v.io/x/ref/services/proxy/proxyd")
+	)
+	// Start proxyd
+	proxyd.WithStartOpts(proxyd.StartOpts().WithCustomCredentials(proxydCreds)).
+		Start("--v23.tcp.address=127.0.0.1:0", "--name="+proxyName, "--access-list", "{\"In\":[\"root/server\"]}")
+	// Start the server that only listens via the proxy
+	if _, err := t.Shell().StartWithOpts(
+		t.Shell().DefaultStartOpts().WithCustomCredentials(serverCreds),
+		nil,
+		runServer); err != nil {
+		t.Fatal(err)
+	}
+	// Run the client.
+	client, err := t.Shell().StartWithOpts(
+		t.Shell().DefaultStartOpts().WithCustomCredentials(clientCreds),
+		nil,
+		runClient)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if got, want := client.ExpectVar(responseVar), "server [root/server] saw client [root/client]"; got != want {
+		t.Fatalf("Got %q, want %q", got, want)
+	}
+}
+
+var runServer = modules.Register(func(env *modules.Env, args ...string) error {
+	ctx, shutdown := v23.Init()
+	defer shutdown()
+	if _, err := xrpc.NewServer(ctx, serverName, service{}, security.AllowEveryone()); err != nil {
+		return err
+	}
+	modules.WaitForEOF(env.Stdin)
+	return nil
+}, "runServer")
+
+var runClient = modules.Register(func(env *modules.Env, args ...string) error {
+	ctx, shutdown := v23.Init()
+	defer shutdown()
+	var response string
+	if err := v23.GetClient(ctx).Call(ctx, serverName, "Echo", nil, []interface{}{&response}); err != nil {
+		return err
+	}
+	fmt.Fprintf(env.Stdout, "%v=%v\n", responseVar, response)
+	return nil
+}, "runClient")
+
+type service struct{}
+
+func (service) Echo(ctx *context.T, call rpc.ServerCall) (string, error) {
+	client, _ := security.RemoteBlessingNames(ctx, call.Security())
+	server := security.LocalBlessingNames(ctx, call.Security())
+	return fmt.Sprintf("server %v saw client %v", server, client), nil
+}
diff --git a/services/proxy/proxyd/v23_test.go b/services/proxy/proxyd/v23_test.go
new file mode 100644
index 0000000..98cd476
--- /dev/null
+++ b/services/proxy/proxyd/v23_test.go
@@ -0,0 +1,30 @@
+// 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 main_test
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
+	"v.io/x/ref/test/v23tests"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	modules.DispatchAndExitIfChild()
+	cleanup := v23tests.UseSharedBinDir()
+	r := m.Run()
+	cleanup()
+	os.Exit(r)
+}
+
+func TestV23Proxyd(t *testing.T) {
+	v23tests.RunTest(t, V23TestProxyd)
+}
diff --git a/services/repository/repository.vdl b/services/repository/repository.vdl
new file mode 100644
index 0000000..0e4cf1b
--- /dev/null
+++ b/services/repository/repository.vdl
@@ -0,0 +1,48 @@
+// 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 repository augments the v.io/v23/services/repository interfaces with
+// implementation-specific configuration methods.
+package repository
+
+import (
+	"v.io/v23/security/access"
+	"v.io/v23/services/application"
+	"v.io/x/ref/services/profile"
+	public "v.io/v23/services/repository"
+)
+
+// Application describes an application repository internally. Besides
+// the public Application interface, it allows to add and remove
+// application envelopes.
+type Application interface {
+	public.Application
+	// Put adds the given tuple of application version (specified
+	// through the object name suffix) and application envelope to all
+	// of the given application profiles.
+	Put(Profiles []string, Envelope application.Envelope) error {access.Write}
+	// Remove removes the application envelope for the given profile
+	// name and application version (specified through the object name
+	// suffix). If no version is specified as part of the suffix, the
+	// method removes all versions for the given profile.
+	//
+	// TODO(jsimsa): Add support for using "*" to specify all profiles
+	// when Matt implements Globing (or Ken implements querying).
+	Remove(Profile string) error {access.Write}
+}
+
+// Profile describes a profile internally. Besides the public Profile
+// interface, it allows to add and remove profile specifications.
+type Profile interface {
+	public.Profile
+	// Specification returns the profile specification for the profile
+	// identified through the object name suffix.
+	Specification() (profile.Specification | error) {access.Read}
+	// Put sets the profile specification for the profile identified
+	// through the object name suffix.
+	Put(Specification profile.Specification) error {access.Write}
+	// Remove removes the profile specification for the profile
+	// identified through the object name suffix.
+	Remove() error {access.Write}
+}
diff --git a/services/repository/repository.vdl.go b/services/repository/repository.vdl.go
new file mode 100644
index 0000000..86454e4
--- /dev/null
+++ b/services/repository/repository.vdl.go
@@ -0,0 +1,371 @@
+// 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 by the vanadium vdl tool.
+// Source: repository.vdl
+
+// Package repository augments the v.io/v23/services/repository interfaces with
+// implementation-specific configuration methods.
+package repository
+
+import (
+	// VDL system imports
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/vdl"
+
+	// VDL user imports
+	"v.io/v23/security/access"
+	"v.io/v23/services/application"
+	"v.io/v23/services/permissions"
+	"v.io/v23/services/repository"
+	"v.io/v23/services/tidyable"
+	"v.io/x/ref/services/profile"
+)
+
+// ApplicationClientMethods is the client interface
+// containing Application methods.
+//
+// Application describes an application repository internally. Besides
+// the public Application interface, it allows to add and remove
+// application envelopes.
+type ApplicationClientMethods interface {
+	// Application provides access to application envelopes. An
+	// application envelope is identified by an application name and an
+	// application version, which are specified through the object name,
+	// and a profile name, which is specified using a method argument.
+	//
+	// Example:
+	// /apps/search/v1.Match([]string{"base", "media"})
+	//   returns an application envelope that can be used for downloading
+	//   and executing the "search" application, version "v1", runnable
+	//   on either the "base" or "media" profile.
+	repository.ApplicationClientMethods
+	// Put adds the given tuple of application version (specified
+	// through the object name suffix) and application envelope to all
+	// of the given application profiles.
+	Put(ctx *context.T, Profiles []string, Envelope application.Envelope, opts ...rpc.CallOpt) error
+	// Remove removes the application envelope for the given profile
+	// name and application version (specified through the object name
+	// suffix). If no version is specified as part of the suffix, the
+	// method removes all versions for the given profile.
+	//
+	// TODO(jsimsa): Add support for using "*" to specify all profiles
+	// when Matt implements Globing (or Ken implements querying).
+	Remove(ctx *context.T, Profile string, opts ...rpc.CallOpt) error
+}
+
+// ApplicationClientStub adds universal methods to ApplicationClientMethods.
+type ApplicationClientStub interface {
+	ApplicationClientMethods
+	rpc.UniversalServiceMethods
+}
+
+// ApplicationClient returns a client stub for Application.
+func ApplicationClient(name string) ApplicationClientStub {
+	return implApplicationClientStub{name, repository.ApplicationClient(name)}
+}
+
+type implApplicationClientStub struct {
+	name string
+
+	repository.ApplicationClientStub
+}
+
+func (c implApplicationClientStub) Put(ctx *context.T, i0 []string, i1 application.Envelope, opts ...rpc.CallOpt) (err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "Put", []interface{}{i0, i1}, nil, opts...)
+	return
+}
+
+func (c implApplicationClientStub) Remove(ctx *context.T, i0 string, opts ...rpc.CallOpt) (err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "Remove", []interface{}{i0}, nil, opts...)
+	return
+}
+
+// ApplicationServerMethods is the interface a server writer
+// implements for Application.
+//
+// Application describes an application repository internally. Besides
+// the public Application interface, it allows to add and remove
+// application envelopes.
+type ApplicationServerMethods interface {
+	// Application provides access to application envelopes. An
+	// application envelope is identified by an application name and an
+	// application version, which are specified through the object name,
+	// and a profile name, which is specified using a method argument.
+	//
+	// Example:
+	// /apps/search/v1.Match([]string{"base", "media"})
+	//   returns an application envelope that can be used for downloading
+	//   and executing the "search" application, version "v1", runnable
+	//   on either the "base" or "media" profile.
+	repository.ApplicationServerMethods
+	// Put adds the given tuple of application version (specified
+	// through the object name suffix) and application envelope to all
+	// of the given application profiles.
+	Put(ctx *context.T, call rpc.ServerCall, Profiles []string, Envelope application.Envelope) error
+	// Remove removes the application envelope for the given profile
+	// name and application version (specified through the object name
+	// suffix). If no version is specified as part of the suffix, the
+	// method removes all versions for the given profile.
+	//
+	// TODO(jsimsa): Add support for using "*" to specify all profiles
+	// when Matt implements Globing (or Ken implements querying).
+	Remove(ctx *context.T, call rpc.ServerCall, Profile string) error
+}
+
+// ApplicationServerStubMethods is the server interface containing
+// Application methods, as expected by rpc.Server.
+// There is no difference between this interface and ApplicationServerMethods
+// since there are no streaming methods.
+type ApplicationServerStubMethods ApplicationServerMethods
+
+// ApplicationServerStub adds universal methods to ApplicationServerStubMethods.
+type ApplicationServerStub interface {
+	ApplicationServerStubMethods
+	// Describe the Application interfaces.
+	Describe__() []rpc.InterfaceDesc
+}
+
+// ApplicationServer returns a server stub for Application.
+// It converts an implementation of ApplicationServerMethods into
+// an object that may be used by rpc.Server.
+func ApplicationServer(impl ApplicationServerMethods) ApplicationServerStub {
+	stub := implApplicationServerStub{
+		impl: impl,
+		ApplicationServerStub: repository.ApplicationServer(impl),
+	}
+	// Initialize GlobState; always check the stub itself first, to handle the
+	// case where the user has the Glob method defined in their VDL source.
+	if gs := rpc.NewGlobState(stub); gs != nil {
+		stub.gs = gs
+	} else if gs := rpc.NewGlobState(impl); gs != nil {
+		stub.gs = gs
+	}
+	return stub
+}
+
+type implApplicationServerStub struct {
+	impl ApplicationServerMethods
+	repository.ApplicationServerStub
+	gs *rpc.GlobState
+}
+
+func (s implApplicationServerStub) Put(ctx *context.T, call rpc.ServerCall, i0 []string, i1 application.Envelope) error {
+	return s.impl.Put(ctx, call, i0, i1)
+}
+
+func (s implApplicationServerStub) Remove(ctx *context.T, call rpc.ServerCall, i0 string) error {
+	return s.impl.Remove(ctx, call, i0)
+}
+
+func (s implApplicationServerStub) Globber() *rpc.GlobState {
+	return s.gs
+}
+
+func (s implApplicationServerStub) Describe__() []rpc.InterfaceDesc {
+	return []rpc.InterfaceDesc{ApplicationDesc, repository.ApplicationDesc, permissions.ObjectDesc, tidyable.TidyableDesc}
+}
+
+// ApplicationDesc describes the Application interface.
+var ApplicationDesc rpc.InterfaceDesc = descApplication
+
+// descApplication hides the desc to keep godoc clean.
+var descApplication = rpc.InterfaceDesc{
+	Name:    "Application",
+	PkgPath: "v.io/x/ref/services/repository",
+	Doc:     "// Application describes an application repository internally. Besides\n// the public Application interface, it allows to add and remove\n// application envelopes.",
+	Embeds: []rpc.EmbedDesc{
+		{"Application", "v.io/v23/services/repository", "// Application provides access to application envelopes. An\n// application envelope is identified by an application name and an\n// application version, which are specified through the object name,\n// and a profile name, which is specified using a method argument.\n//\n// Example:\n// /apps/search/v1.Match([]string{\"base\", \"media\"})\n//   returns an application envelope that can be used for downloading\n//   and executing the \"search\" application, version \"v1\", runnable\n//   on either the \"base\" or \"media\" profile."},
+	},
+	Methods: []rpc.MethodDesc{
+		{
+			Name: "Put",
+			Doc:  "// Put adds the given tuple of application version (specified\n// through the object name suffix) and application envelope to all\n// of the given application profiles.",
+			InArgs: []rpc.ArgDesc{
+				{"Profiles", ``}, // []string
+				{"Envelope", ``}, // application.Envelope
+			},
+			Tags: []*vdl.Value{vdl.ValueOf(access.Tag("Write"))},
+		},
+		{
+			Name: "Remove",
+			Doc:  "// Remove removes the application envelope for the given profile\n// name and application version (specified through the object name\n// suffix). If no version is specified as part of the suffix, the\n// method removes all versions for the given profile.\n//\n// TODO(jsimsa): Add support for using \"*\" to specify all profiles\n// when Matt implements Globing (or Ken implements querying).",
+			InArgs: []rpc.ArgDesc{
+				{"Profile", ``}, // string
+			},
+			Tags: []*vdl.Value{vdl.ValueOf(access.Tag("Write"))},
+		},
+	},
+}
+
+// ProfileClientMethods is the client interface
+// containing Profile methods.
+//
+// Profile describes a profile internally. Besides the public Profile
+// interface, it allows to add and remove profile specifications.
+type ProfileClientMethods interface {
+	// Profile abstracts a device's ability to run binaries, and hides
+	// specifics such as the operating system, hardware architecture, and
+	// the set of installed libraries. Profiles describe binaries and
+	// devices, and are used to match them.
+	repository.ProfileClientMethods
+	// Specification returns the profile specification for the profile
+	// identified through the object name suffix.
+	Specification(*context.T, ...rpc.CallOpt) (profile.Specification, error)
+	// Put sets the profile specification for the profile identified
+	// through the object name suffix.
+	Put(ctx *context.T, Specification profile.Specification, opts ...rpc.CallOpt) error
+	// Remove removes the profile specification for the profile
+	// identified through the object name suffix.
+	Remove(*context.T, ...rpc.CallOpt) error
+}
+
+// ProfileClientStub adds universal methods to ProfileClientMethods.
+type ProfileClientStub interface {
+	ProfileClientMethods
+	rpc.UniversalServiceMethods
+}
+
+// ProfileClient returns a client stub for Profile.
+func ProfileClient(name string) ProfileClientStub {
+	return implProfileClientStub{name, repository.ProfileClient(name)}
+}
+
+type implProfileClientStub struct {
+	name string
+
+	repository.ProfileClientStub
+}
+
+func (c implProfileClientStub) Specification(ctx *context.T, opts ...rpc.CallOpt) (o0 profile.Specification, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "Specification", nil, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implProfileClientStub) Put(ctx *context.T, i0 profile.Specification, opts ...rpc.CallOpt) (err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "Put", []interface{}{i0}, nil, opts...)
+	return
+}
+
+func (c implProfileClientStub) Remove(ctx *context.T, opts ...rpc.CallOpt) (err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "Remove", nil, nil, opts...)
+	return
+}
+
+// ProfileServerMethods is the interface a server writer
+// implements for Profile.
+//
+// Profile describes a profile internally. Besides the public Profile
+// interface, it allows to add and remove profile specifications.
+type ProfileServerMethods interface {
+	// Profile abstracts a device's ability to run binaries, and hides
+	// specifics such as the operating system, hardware architecture, and
+	// the set of installed libraries. Profiles describe binaries and
+	// devices, and are used to match them.
+	repository.ProfileServerMethods
+	// Specification returns the profile specification for the profile
+	// identified through the object name suffix.
+	Specification(*context.T, rpc.ServerCall) (profile.Specification, error)
+	// Put sets the profile specification for the profile identified
+	// through the object name suffix.
+	Put(ctx *context.T, call rpc.ServerCall, Specification profile.Specification) error
+	// Remove removes the profile specification for the profile
+	// identified through the object name suffix.
+	Remove(*context.T, rpc.ServerCall) error
+}
+
+// ProfileServerStubMethods is the server interface containing
+// Profile methods, as expected by rpc.Server.
+// There is no difference between this interface and ProfileServerMethods
+// since there are no streaming methods.
+type ProfileServerStubMethods ProfileServerMethods
+
+// ProfileServerStub adds universal methods to ProfileServerStubMethods.
+type ProfileServerStub interface {
+	ProfileServerStubMethods
+	// Describe the Profile interfaces.
+	Describe__() []rpc.InterfaceDesc
+}
+
+// ProfileServer returns a server stub for Profile.
+// It converts an implementation of ProfileServerMethods into
+// an object that may be used by rpc.Server.
+func ProfileServer(impl ProfileServerMethods) ProfileServerStub {
+	stub := implProfileServerStub{
+		impl:              impl,
+		ProfileServerStub: repository.ProfileServer(impl),
+	}
+	// Initialize GlobState; always check the stub itself first, to handle the
+	// case where the user has the Glob method defined in their VDL source.
+	if gs := rpc.NewGlobState(stub); gs != nil {
+		stub.gs = gs
+	} else if gs := rpc.NewGlobState(impl); gs != nil {
+		stub.gs = gs
+	}
+	return stub
+}
+
+type implProfileServerStub struct {
+	impl ProfileServerMethods
+	repository.ProfileServerStub
+	gs *rpc.GlobState
+}
+
+func (s implProfileServerStub) Specification(ctx *context.T, call rpc.ServerCall) (profile.Specification, error) {
+	return s.impl.Specification(ctx, call)
+}
+
+func (s implProfileServerStub) Put(ctx *context.T, call rpc.ServerCall, i0 profile.Specification) error {
+	return s.impl.Put(ctx, call, i0)
+}
+
+func (s implProfileServerStub) Remove(ctx *context.T, call rpc.ServerCall) error {
+	return s.impl.Remove(ctx, call)
+}
+
+func (s implProfileServerStub) Globber() *rpc.GlobState {
+	return s.gs
+}
+
+func (s implProfileServerStub) Describe__() []rpc.InterfaceDesc {
+	return []rpc.InterfaceDesc{ProfileDesc, repository.ProfileDesc}
+}
+
+// ProfileDesc describes the Profile interface.
+var ProfileDesc rpc.InterfaceDesc = descProfile
+
+// descProfile hides the desc to keep godoc clean.
+var descProfile = rpc.InterfaceDesc{
+	Name:    "Profile",
+	PkgPath: "v.io/x/ref/services/repository",
+	Doc:     "// Profile describes a profile internally. Besides the public Profile\n// interface, it allows to add and remove profile specifications.",
+	Embeds: []rpc.EmbedDesc{
+		{"Profile", "v.io/v23/services/repository", "// Profile abstracts a device's ability to run binaries, and hides\n// specifics such as the operating system, hardware architecture, and\n// the set of installed libraries. Profiles describe binaries and\n// devices, and are used to match them."},
+	},
+	Methods: []rpc.MethodDesc{
+		{
+			Name: "Specification",
+			Doc:  "// Specification returns the profile specification for the profile\n// identified through the object name suffix.",
+			OutArgs: []rpc.ArgDesc{
+				{"", ``}, // profile.Specification
+			},
+			Tags: []*vdl.Value{vdl.ValueOf(access.Tag("Read"))},
+		},
+		{
+			Name: "Put",
+			Doc:  "// Put sets the profile specification for the profile identified\n// through the object name suffix.",
+			InArgs: []rpc.ArgDesc{
+				{"Specification", ``}, // profile.Specification
+			},
+			Tags: []*vdl.Value{vdl.ValueOf(access.Tag("Write"))},
+		},
+		{
+			Name: "Remove",
+			Doc:  "// Remove removes the profile specification for the profile\n// identified through the object name suffix.",
+			Tags: []*vdl.Value{vdl.ValueOf(access.Tag("Write"))},
+		},
+	},
+}
diff --git a/services/role/role.vdl b/services/role/role.vdl
new file mode 100644
index 0000000..ec5897f
--- /dev/null
+++ b/services/role/role.vdl
@@ -0,0 +1,26 @@
+// 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 role defines an interface for requesting blessings from a role
+// account server.
+package role
+
+import "v.io/v23/security"
+
+// Role is an interface to request blessings from a role account server. The
+// returned blessings are bound to the client's public key thereby authorizing
+// the client to acquire the role. The server may tie the returned blessings
+// with the client's presented blessing name in order to maintain audit
+// information in the blessing.
+//
+// In order to avoid granting role blessings to all delegates of a principal,
+// the role server requires that each authorized blessing presented by the
+// client have the string "_role" as suffix.
+type Role interface {
+	SeekBlessings() (security.WireBlessings | error)
+}
+
+// Role.SeekBlessings will return an error if the requestor does not present
+// blessings that end in this suffix.
+const RoleSuffix = "_role"
diff --git a/services/role/role.vdl.go b/services/role/role.vdl.go
new file mode 100644
index 0000000..1628fb9
--- /dev/null
+++ b/services/role/role.vdl.go
@@ -0,0 +1,141 @@
+// 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 by the vanadium vdl tool.
+// Source: role.vdl
+
+// Package role defines an interface for requesting blessings from a role
+// account server.
+package role
+
+import (
+	// VDL system imports
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+
+	// VDL user imports
+	"v.io/v23/security"
+)
+
+// Role.SeekBlessings will return an error if the requestor does not present
+// blessings that end in this suffix.
+const RoleSuffix = "_role"
+
+// RoleClientMethods is the client interface
+// containing Role methods.
+//
+// Role is an interface to request blessings from a role account server. The
+// returned blessings are bound to the client's public key thereby authorizing
+// the client to acquire the role. The server may tie the returned blessings
+// with the client's presented blessing name in order to maintain audit
+// information in the blessing.
+//
+// In order to avoid granting role blessings to all delegates of a principal,
+// the role server requires that each authorized blessing presented by the
+// client have the string "_role" as suffix.
+type RoleClientMethods interface {
+	SeekBlessings(*context.T, ...rpc.CallOpt) (security.Blessings, error)
+}
+
+// RoleClientStub adds universal methods to RoleClientMethods.
+type RoleClientStub interface {
+	RoleClientMethods
+	rpc.UniversalServiceMethods
+}
+
+// RoleClient returns a client stub for Role.
+func RoleClient(name string) RoleClientStub {
+	return implRoleClientStub{name}
+}
+
+type implRoleClientStub struct {
+	name string
+}
+
+func (c implRoleClientStub) SeekBlessings(ctx *context.T, opts ...rpc.CallOpt) (o0 security.Blessings, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "SeekBlessings", nil, []interface{}{&o0}, opts...)
+	return
+}
+
+// RoleServerMethods is the interface a server writer
+// implements for Role.
+//
+// Role is an interface to request blessings from a role account server. The
+// returned blessings are bound to the client's public key thereby authorizing
+// the client to acquire the role. The server may tie the returned blessings
+// with the client's presented blessing name in order to maintain audit
+// information in the blessing.
+//
+// In order to avoid granting role blessings to all delegates of a principal,
+// the role server requires that each authorized blessing presented by the
+// client have the string "_role" as suffix.
+type RoleServerMethods interface {
+	SeekBlessings(*context.T, rpc.ServerCall) (security.Blessings, error)
+}
+
+// RoleServerStubMethods is the server interface containing
+// Role methods, as expected by rpc.Server.
+// There is no difference between this interface and RoleServerMethods
+// since there are no streaming methods.
+type RoleServerStubMethods RoleServerMethods
+
+// RoleServerStub adds universal methods to RoleServerStubMethods.
+type RoleServerStub interface {
+	RoleServerStubMethods
+	// Describe the Role interfaces.
+	Describe__() []rpc.InterfaceDesc
+}
+
+// RoleServer returns a server stub for Role.
+// It converts an implementation of RoleServerMethods into
+// an object that may be used by rpc.Server.
+func RoleServer(impl RoleServerMethods) RoleServerStub {
+	stub := implRoleServerStub{
+		impl: impl,
+	}
+	// Initialize GlobState; always check the stub itself first, to handle the
+	// case where the user has the Glob method defined in their VDL source.
+	if gs := rpc.NewGlobState(stub); gs != nil {
+		stub.gs = gs
+	} else if gs := rpc.NewGlobState(impl); gs != nil {
+		stub.gs = gs
+	}
+	return stub
+}
+
+type implRoleServerStub struct {
+	impl RoleServerMethods
+	gs   *rpc.GlobState
+}
+
+func (s implRoleServerStub) SeekBlessings(ctx *context.T, call rpc.ServerCall) (security.Blessings, error) {
+	return s.impl.SeekBlessings(ctx, call)
+}
+
+func (s implRoleServerStub) Globber() *rpc.GlobState {
+	return s.gs
+}
+
+func (s implRoleServerStub) Describe__() []rpc.InterfaceDesc {
+	return []rpc.InterfaceDesc{RoleDesc}
+}
+
+// RoleDesc describes the Role interface.
+var RoleDesc rpc.InterfaceDesc = descRole
+
+// descRole hides the desc to keep godoc clean.
+var descRole = rpc.InterfaceDesc{
+	Name:    "Role",
+	PkgPath: "v.io/x/ref/services/role",
+	Doc:     "// Role is an interface to request blessings from a role account server. The\n// returned blessings are bound to the client's public key thereby authorizing\n// the client to acquire the role. The server may tie the returned blessings\n// with the client's presented blessing name in order to maintain audit\n// information in the blessing.\n//\n// In order to avoid granting role blessings to all delegates of a principal,\n// the role server requires that each authorized blessing presented by the\n// client have the string \"_role\" as suffix.",
+	Methods: []rpc.MethodDesc{
+		{
+			Name: "SeekBlessings",
+			OutArgs: []rpc.ArgDesc{
+				{"", ``}, // security.Blessings
+			},
+		},
+	},
+}
diff --git a/services/role/roled/doc.go b/services/role/roled/doc.go
new file mode 100644
index 0000000..0da6feb
--- /dev/null
+++ b/services/role/roled/doc.go
@@ -0,0 +1,69 @@
+// 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
+
+/*
+Command roled runs the Role interface daemon.
+
+Usage:
+   roled [flags]
+
+The roled flags are:
+ -config-dir=
+   The directory where the role configuration files are stored.
+ -name=
+   The name to publish for this service.
+
+The global flags are:
+ -alsologtostderr=true
+   log to standard error as well as files
+ -log_backtrace_at=:0
+   when logging hits line file:N, emit a stack trace
+ -log_dir=
+   if non-empty, write log files to this directory
+ -logtostderr=false
+   log to standard error instead of files
+ -max_stack_buf_size=4292608
+   max size in bytes of the buffer to use for logging stack traces
+ -metadata=<just specify -metadata to activate>
+   Displays metadata for the program and exits.
+ -stderrthreshold=2
+   logs at or above this threshold go to stderr
+ -v=0
+   log level for V logs
+ -v23.credentials=
+   directory to use for storing security credentials
+ -v23.i18n-catalogue=
+   18n catalogue files to load, comma separated
+ -v23.namespace.root=[/(dev.v.io/role/vprod/service/mounttabled)@ns.dev.v.io:8101]
+   local namespace root; can be repeated to provided multiple roots
+ -v23.permissions.file=map[]
+   specify a perms file as <name>:<permsfile>
+ -v23.permissions.literal=
+   explicitly specify the runtime perms as a JSON-encoded access.Permissions.
+   Overrides all --v23.permissions.file flags.
+ -v23.proxy=
+   object name of proxy service to use to export services across network
+   boundaries
+ -v23.tcp.address=
+   address to listen on
+ -v23.tcp.protocol=wsh
+   protocol to listen with
+ -v23.vtrace.cache-size=1024
+   The number of vtrace traces to store in memory.
+ -v23.vtrace.collect-regexp=
+   Spans and annotations that match this regular expression will trigger trace
+   collection.
+ -v23.vtrace.dump-on-shutdown=true
+   If true, dump all stored traces on runtime shutdown.
+ -v23.vtrace.sample-rate=0
+   Rate (from 0.0 to 1.0) to sample vtrace traces.
+ -vmodule=
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
+*/
+package main
diff --git a/services/role/roled/internal/caveats.vdl b/services/role/roled/internal/caveats.vdl
new file mode 100644
index 0000000..0ad318c
--- /dev/null
+++ b/services/role/roled/internal/caveats.vdl
@@ -0,0 +1,18 @@
+// 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
+
+import (
+	"v.io/v23/security"
+	"v.io/v23/uniqueid"
+)
+
+const (
+	// LoggingCaveat is a caveat that will always validate but it logs the parameter on every attempt to validate it.
+	LoggingCaveat = security.CaveatDescriptor{
+		Id:        uniqueid.Id{0xb0, 0x34, 0x1c, 0xed, 0xe2, 0xdf, 0x81, 0xbd, 0xed, 0x70, 0x97, 0xbb, 0x55, 0xad, 0x80, 0x0},
+		ParamType: typeobject([]string),
+	}
+)
diff --git a/services/role/roled/internal/caveats.vdl.go b/services/role/roled/internal/caveats.vdl.go
new file mode 100644
index 0000000..9a22ce7
--- /dev/null
+++ b/services/role/roled/internal/caveats.vdl.go
@@ -0,0 +1,40 @@
+// 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 by the vanadium vdl tool.
+// Source: caveats.vdl
+
+package internal
+
+import (
+	// VDL system imports
+	"v.io/v23/vdl"
+
+	// VDL user imports
+	"v.io/v23/security"
+	"v.io/v23/uniqueid"
+)
+
+// LoggingCaveat is a caveat that will always validate but it logs the parameter on every attempt to validate it.
+var LoggingCaveat = security.CaveatDescriptor{
+	Id: uniqueid.Id{
+		176,
+		52,
+		28,
+		237,
+		226,
+		223,
+		129,
+		189,
+		237,
+		112,
+		151,
+		187,
+		85,
+		173,
+		128,
+		0,
+	},
+	ParamType: vdl.TypeOf([]string(nil)),
+}
diff --git a/services/role/roled/internal/config.vdl b/services/role/roled/internal/config.vdl
new file mode 100644
index 0000000..e940903
--- /dev/null
+++ b/services/role/roled/internal/config.vdl
@@ -0,0 +1,35 @@
+// 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
+
+import "v.io/v23/security"
+
+// Config contains the attributes of the role, and the list of members who have
+// access to it.
+type Config struct {
+	// List of role objects, relative to this role, from which to import
+	// the set of members. File path notation like "." and ".." may be used.
+	// The set of members who have access to this role is the union of this
+	// role's members and those of all the imported roles.
+	ImportMembers []string
+	// Blessings that match at least one of the patterns in this set are
+	// allowed to act on behalf of the role.
+	Members []security.BlessingPattern
+	// Indicates that the blessing name of the caller should be appended to
+	// the role blessing name.
+	Extend bool
+	// If Audit is true, each use of the role blessing will be reported to
+	// an auditing service and will be usable only if the report was
+	// successful.
+	Audit bool
+	// The amount of time for which the role blessing will be valid. It is a
+	// string representation of a time.Duration, e.g. "24h". An empty string
+	// indicates that the role blessing will not expire.
+	Expiry string
+	// The blessings issued for this role will only be valid for
+	// communicating with peers that match at least one of these patterns.
+	// If the list is empty, all peers are allowed.
+	Peers []security.BlessingPattern
+}
diff --git a/services/role/roled/internal/config.vdl.go b/services/role/roled/internal/config.vdl.go
new file mode 100644
index 0000000..1349dcc
--- /dev/null
+++ b/services/role/roled/internal/config.vdl.go
@@ -0,0 +1,53 @@
+// 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 by the vanadium vdl tool.
+// Source: config.vdl
+
+package internal
+
+import (
+	// VDL system imports
+	"v.io/v23/vdl"
+
+	// VDL user imports
+	"v.io/v23/security"
+)
+
+// Config contains the attributes of the role, and the list of members who have
+// access to it.
+type Config struct {
+	// List of role objects, relative to this role, from which to import
+	// the set of members. File path notation like "." and ".." may be used.
+	// The set of members who have access to this role is the union of this
+	// role's members and those of all the imported roles.
+	ImportMembers []string
+	// Blessings that match at least one of the patterns in this set are
+	// allowed to act on behalf of the role.
+	Members []security.BlessingPattern
+	// Indicates that the blessing name of the caller should be appended to
+	// the role blessing name.
+	Extend bool
+	// If Audit is true, each use of the role blessing will be reported to
+	// an auditing service and will be usable only if the report was
+	// successful.
+	Audit bool
+	// The amount of time for which the role blessing will be valid. It is a
+	// string representation of a time.Duration, e.g. "24h". An empty string
+	// indicates that the role blessing will not expire.
+	Expiry string
+	// The blessings issued for this role will only be valid for
+	// communicating with peers that match at least one of these patterns.
+	// If the list is empty, all peers are allowed.
+	Peers []security.BlessingPattern
+}
+
+func (Config) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/services/role/roled/internal.Config"`
+}) {
+}
+
+func init() {
+	vdl.Register((*Config)(nil))
+}
diff --git a/services/role/roled/internal/discharger.go b/services/role/roled/internal/discharger.go
new file mode 100644
index 0000000..f0321ee
--- /dev/null
+++ b/services/role/roled/internal/discharger.go
@@ -0,0 +1,65 @@
+// 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
+
+import (
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/glob"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/verror"
+
+	"v.io/x/ref/services/discharger"
+)
+
+func init() {
+	security.RegisterCaveatValidator(LoggingCaveat, func(ctx *context.T, _ security.Call, params []string) error {
+		ctx.Infof("Params: %#v", params)
+		return nil
+	})
+
+}
+
+type dischargerImpl struct {
+	serverConfig *serverConfig
+}
+
+func (dischargerImpl) Discharge(ctx *context.T, call rpc.ServerCall, caveat security.Caveat, impetus security.DischargeImpetus) (security.Discharge, error) {
+	details := caveat.ThirdPartyDetails()
+	if details == nil {
+		return security.Discharge{}, discharger.NewErrNotAThirdPartyCaveat(ctx, caveat)
+	}
+	if err := details.Dischargeable(ctx, call.Security()); err != nil {
+		return security.Discharge{}, err
+	}
+	// TODO(rthellend,ashankar): Do proper logging when the API allows it.
+	ctx.Infof("Discharge() impetus: %#v", impetus)
+
+	expiry, err := security.NewExpiryCaveat(time.Now().Add(5 * time.Minute))
+	if err != nil {
+		return security.Discharge{}, verror.Convert(verror.ErrInternal, ctx, err)
+	}
+	// Bind the discharge to precisely the purpose the requestor claims it will be used.
+	method, err := security.NewMethodCaveat(impetus.Method)
+	if err != nil {
+		return security.Discharge{}, verror.Convert(verror.ErrInternal, ctx, err)
+	}
+	peer, err := security.NewCaveat(security.PeerBlessingsCaveat, impetus.Server)
+	if err != nil {
+		return security.Discharge{}, verror.Convert(verror.ErrInternal, ctx, err)
+	}
+	discharge, err := v23.GetPrincipal(ctx).MintDischarge(caveat, expiry, method, peer)
+	if err != nil {
+		return security.Discharge{}, verror.Convert(verror.ErrInternal, ctx, err)
+	}
+	return discharge, nil
+}
+
+func (d *dischargerImpl) GlobChildren__(ctx *context.T, call rpc.GlobChildrenServerCall, m *glob.Element) error {
+	return globChildren(ctx, call, d.serverConfig, m)
+}
diff --git a/services/role/roled/internal/dispatcher.go b/services/role/roled/internal/dispatcher.go
new file mode 100644
index 0000000..fdeeedd
--- /dev/null
+++ b/services/role/roled/internal/dispatcher.go
@@ -0,0 +1,152 @@
+// 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
+
+import (
+	"encoding/json"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/verror"
+
+	"v.io/x/ref/internal/logger"
+	"v.io/x/ref/services/discharger"
+	"v.io/x/ref/services/role"
+)
+
+const requiredSuffix = security.ChainSeparator + role.RoleSuffix
+
+// NewDispatcher returns a dispatcher object for a role service and its
+// associated discharger service.
+// The configRoot is the top level directory where the role configuration files
+// are stored.
+// The dischargerLocation is the object name or address of the discharger
+// service for the third-party caveats attached to the role blessings returned
+// by the role service.
+func NewDispatcher(configRoot, dischargerLocation string) rpc.Dispatcher {
+	return &dispatcher{&serverConfig{configRoot, dischargerLocation}}
+}
+
+type serverConfig struct {
+	root               string
+	dischargerLocation string
+}
+
+type dispatcher struct {
+	config *serverConfig
+}
+
+func (d *dispatcher) Lookup(_ *context.T, suffix string) (interface{}, security.Authorizer, error) {
+	if len(suffix) == 0 {
+		return discharger.DischargerServer(&dischargerImpl{d.config}), security.AllowEveryone(), nil
+	}
+	fileName := filepath.Join(d.config.root, filepath.FromSlash(suffix+".conf"))
+	if !strings.HasPrefix(fileName, d.config.root) {
+		// Guard against ".." in the suffix that could be used to read
+		// files outside of the config root.
+		return nil, nil, verror.New(verror.ErrNoExistOrNoAccess, nil)
+	}
+	roleConfig, err := loadExpandedConfig(fileName, nil)
+	if err != nil && !os.IsNotExist(err) {
+		// The config file exists, but we failed to read it for some
+		// reason. This is likely a server configuration error.
+		logger.Global().Errorf("loadConfig(%q, %q): %v", d.config.root, suffix, err)
+		return nil, nil, verror.Convert(verror.ErrInternal, nil, err)
+	}
+	obj := &roleService{serverConfig: d.config, role: suffix, roleConfig: roleConfig}
+	return role.RoleServer(obj), &authorizer{roleConfig}, nil
+}
+
+type authorizer struct {
+	config *Config
+}
+
+func (a *authorizer) Authorize(ctx *context.T, call security.Call) error {
+	if call.Method() == "__Glob" {
+		// The Glob implementation only shows objects that the caller
+		// has access to. So this blanket approval is OK.
+		return nil
+	}
+	if a.config == nil {
+		return verror.New(verror.ErrNoExistOrNoAccess, ctx)
+	}
+	remoteBlessingNames, _ := security.RemoteBlessingNames(ctx, call)
+
+	if hasAccess(a.config, remoteBlessingNames) {
+		return nil
+	}
+	return verror.New(verror.ErrNoExistOrNoAccess, ctx)
+}
+
+func hasAccess(c *Config, blessingNames []string) bool {
+	for _, pattern := range c.Members {
+		if pattern.MatchedBy(blessingNames...) {
+			return true
+		}
+	}
+	return false
+}
+
+func loadExpandedConfig(fileName string, seenFiles map[string]struct{}) (*Config, error) {
+	if seenFiles == nil {
+		seenFiles = make(map[string]struct{})
+	}
+	if _, seen := seenFiles[fileName]; seen {
+		return nil, nil
+	}
+	seenFiles[fileName] = struct{}{}
+	c, err := loadConfig(fileName)
+	if err != nil {
+		return nil, err
+	}
+	parentDir := filepath.Dir(fileName)
+	for _, imp := range c.ImportMembers {
+		f := filepath.Join(parentDir, filepath.FromSlash(imp+".conf"))
+		ic, err := loadExpandedConfig(f, seenFiles)
+		if err != nil {
+			continue
+		}
+		if ic == nil {
+			continue
+		}
+		c.Members = append(c.Members, ic.Members...)
+	}
+	c.ImportMembers = nil
+	dedupMembers(c)
+	return c, nil
+}
+
+func loadConfig(fileName string) (*Config, error) {
+	contents, err := ioutil.ReadFile(fileName)
+	if err != nil {
+		return nil, err
+	}
+	var c Config
+	if err := json.Unmarshal(contents, &c); err != nil {
+		return nil, err
+	}
+	for i, pattern := range c.Members {
+		if p := string(pattern); !strings.HasSuffix(p, requiredSuffix) {
+			c.Members[i] = security.BlessingPattern(p + requiredSuffix)
+		}
+	}
+	return &c, nil
+}
+
+func dedupMembers(c *Config) {
+	members := make(map[security.BlessingPattern]struct{})
+	for _, m := range c.Members {
+		members[m] = struct{}{}
+	}
+	c.Members = []security.BlessingPattern{}
+	for m := range members {
+		c.Members = append(c.Members, m)
+	}
+}
diff --git a/services/role/roled/internal/doc.go b/services/role/roled/internal/doc.go
new file mode 100644
index 0000000..f72d7c5
--- /dev/null
+++ b/services/role/roled/internal/doc.go
@@ -0,0 +1,6 @@
+// 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 implements the role service defined in v.io/x/ref/services/role
+package internal
diff --git a/services/role/roled/internal/glob.go b/services/role/roled/internal/glob.go
new file mode 100644
index 0000000..12735e0
--- /dev/null
+++ b/services/role/roled/internal/glob.go
@@ -0,0 +1,90 @@
+// 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
+
+import (
+	"os"
+	"path/filepath"
+	"strings"
+
+	"v.io/v23/context"
+	"v.io/v23/glob"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/verror"
+)
+
+func globChildren(ctx *context.T, call rpc.GlobChildrenServerCall, serverConfig *serverConfig, m *glob.Element) error {
+	sCall := call.Security()
+	n := findRoles(ctx, sCall, serverConfig.root)
+	suffix := sCall.Suffix()
+	if len(suffix) > 0 {
+		n = n.find(strings.Split(suffix, "/"), false)
+	}
+	if n == nil {
+		return verror.New(verror.ErrNoExistOrNoAccess, ctx)
+	}
+	for c := range n.children {
+		if m.Match(c) {
+			call.SendStream().Send(naming.GlobChildrenReplyName{Value: c})
+		}
+	}
+	return nil
+}
+
+// findRoles finds all the roles to which the caller has access.
+func findRoles(ctx *context.T, call security.Call, root string) *node {
+	blessingNames, _ := security.RemoteBlessingNames(ctx, call)
+	tree := newNode()
+	filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
+		if info.IsDir() || !strings.HasSuffix(path, ".conf") {
+			return nil
+		}
+		c, err := loadExpandedConfig(path, nil)
+		if err != nil {
+			return nil
+		}
+		if !hasAccess(c, blessingNames) {
+			return nil
+		}
+		relPath, err := filepath.Rel(root, path)
+		if err != nil {
+			return nil
+		}
+		tree.find(strings.Split(strings.TrimSuffix(relPath, ".conf"), string(filepath.Separator)), true)
+		return nil
+	})
+	return tree
+}
+
+type node struct {
+	children map[string]*node
+}
+
+func newNode() *node {
+	return &node{children: make(map[string]*node)}
+}
+
+func (n *node) find(names []string, create bool) *node {
+	for {
+		if len(names) == 0 {
+			return n
+		}
+		if next, ok := n.children[names[0]]; ok {
+			n = next
+			names = names[1:]
+			continue
+		}
+		if create {
+			nn := newNode()
+			n.children[names[0]] = nn
+			n = nn
+			names = names[1:]
+			continue
+		}
+		return nil
+	}
+}
diff --git a/services/role/roled/internal/role.go b/services/role/roled/internal/role.go
new file mode 100644
index 0000000..5f4c733
--- /dev/null
+++ b/services/role/roled/internal/role.go
@@ -0,0 +1,163 @@
+// 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
+
+import (
+	"strings"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/glob"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/verror"
+
+	"v.io/x/ref/services/role"
+)
+
+var (
+	errNoLocalBlessings = verror.Register("v.io/x/ref/services/role/roled/internal/noLocalBlessings", verror.NoRetry, "{1:}{2:} no local blessings")
+)
+
+type roleService struct {
+	serverConfig *serverConfig
+	role         string
+	roleConfig   *Config
+}
+
+func (i *roleService) SeekBlessings(ctx *context.T, call rpc.ServerCall) (security.Blessings, error) {
+	remoteBlessingNames, _ := security.RemoteBlessingNames(ctx, call.Security())
+	ctx.Infof("%q.SeekBlessings() called by %q", i.role, remoteBlessingNames)
+
+	members := i.filterNonMembers(remoteBlessingNames)
+	if len(members) == 0 {
+		// The Authorizer should already have caught that.
+		return security.Blessings{}, verror.New(verror.ErrNoAccess, ctx)
+	}
+
+	extensions := extensions(i.roleConfig, i.role, members)
+	caveats, err := caveats(ctx, i.roleConfig)
+	if err != nil {
+		return security.Blessings{}, err
+	}
+
+	return createBlessings(ctx, call.Security(), i.roleConfig, v23.GetPrincipal(ctx), extensions, caveats, i.serverConfig.dischargerLocation)
+}
+
+func (i *roleService) GlobChildren__(ctx *context.T, call rpc.GlobChildrenServerCall, m *glob.Element) error {
+	return globChildren(ctx, call, i.serverConfig, m)
+}
+
+// filterNonMembers returns only the blessing names that are authorized members
+// for the role.
+func (i *roleService) filterNonMembers(blessingNames []string) []string {
+	var results []string
+	for _, name := range blessingNames {
+		// It is not enough to know if the pattern is matched by the
+		// blessings. We need to know exactly which names matched.
+		// These names will be used later to construct the role
+		// blessings.
+		for _, pattern := range i.roleConfig.Members {
+			if pattern.MatchedBy(name) {
+				results = append(results, name)
+				break
+			}
+		}
+	}
+	return results
+}
+
+func extensions(config *Config, roleStr string, blessingNames []string) []string {
+	if !config.Extend {
+		return []string{roleStr}
+	}
+	var extensions []string
+	for _, b := range blessingNames {
+		b = strings.TrimSuffix(b, security.ChainSeparator+role.RoleSuffix)
+		extensions = append(extensions, roleStr+security.ChainSeparator+b)
+	}
+	return extensions
+}
+
+func caveats(ctx *context.T, config *Config) ([]security.Caveat, error) {
+	var caveats []security.Caveat
+	if config.Expiry != "" {
+		d, err := time.ParseDuration(config.Expiry)
+		if err != nil {
+			return nil, verror.Convert(verror.ErrInternal, ctx, err)
+		}
+		expiry, err := security.NewExpiryCaveat(time.Now().Add(d))
+		if err != nil {
+			return nil, verror.Convert(verror.ErrInternal, ctx, err)
+		}
+		caveats = append(caveats, expiry)
+	}
+	if len(config.Peers) != 0 {
+		peer, err := security.NewCaveat(security.PeerBlessingsCaveat, config.Peers)
+		if err != nil {
+			return nil, verror.Convert(verror.ErrInternal, ctx, err)
+		}
+		caveats = append(caveats, peer)
+	}
+	return caveats, nil
+}
+
+func createBlessings(ctx *context.T, call security.Call, config *Config, principal security.Principal, extensions []string, caveats []security.Caveat, dischargerLocation string) (security.Blessings, error) {
+	blessWith := call.LocalBlessings()
+	blessWithNames := security.LocalBlessingNames(ctx, call)
+	publicKey := call.RemoteBlessings().PublicKey()
+	if len(blessWithNames) == 0 {
+		return security.Blessings{}, verror.New(errNoLocalBlessings, ctx)
+	}
+
+	var ret security.Blessings
+	for _, ext := range extensions {
+		cav := caveats
+		if config.Audit {
+			// TODO(rthellend): This third-party caveat will only work with a single
+			// discharger service. We need a way to allow multiple instances of this
+			// service to be interchangeable.
+
+			fullNames := make([]string, len(blessWithNames))
+			for i, n := range blessWithNames {
+				fullNames[i] = n + security.ChainSeparator + ext
+			}
+			loggingCaveat, err := security.NewCaveat(LoggingCaveat, fullNames)
+			if err != nil {
+				return security.Blessings{}, verror.Convert(verror.ErrInternal, ctx, err)
+			}
+			thirdParty, err := security.NewPublicKeyCaveat(principal.PublicKey(), dischargerLocation, security.ThirdPartyRequirements{
+				ReportServer:    true,
+				ReportMethod:    true,
+				ReportArguments: true,
+			}, loggingCaveat)
+			if err != nil {
+				return security.Blessings{}, verror.Convert(verror.ErrInternal, ctx, err)
+			}
+			cav = append(cav, thirdParty)
+		}
+		if len(cav) == 0 {
+			// TODO(rthellend,ashankar): the use of unconstrained
+			// use is concerning. We should figure out how to get
+			// rid of it.
+			// Some options:
+			//  - have the seeker specify a set of caveats in the
+			//    request (and forcefully insert a restrictive one
+			//    or fail if the role server thinks that they are
+			//    too loose or something).
+			//  - have a set of caveats in the config of the role.
+			cav = []security.Caveat{security.UnconstrainedUse()}
+		}
+		b, err := principal.Bless(publicKey, blessWith, ext, cav[0], cav[1:]...)
+		if err != nil {
+			return security.Blessings{}, verror.Convert(verror.ErrInternal, ctx, err)
+		}
+		if ret, err = security.UnionOfBlessings(ret, b); err != nil {
+			verror.Convert(verror.ErrInternal, ctx, err)
+		}
+	}
+	return ret, nil
+}
diff --git a/services/role/roled/internal/role_internal_test.go b/services/role/roled/internal/role_internal_test.go
new file mode 100644
index 0000000..b575f69
--- /dev/null
+++ b/services/role/roled/internal/role_internal_test.go
@@ -0,0 +1,77 @@
+// 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
+
+import (
+	"encoding/json"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"reflect"
+	"sort"
+	"testing"
+
+	"v.io/v23/security"
+)
+
+func TestImportMembers(t *testing.T) {
+	workdir, err := ioutil.TempDir("", "test-role-server-")
+	if err != nil {
+		t.Fatalf("ioutil.TempDir failed: %v", err)
+	}
+	defer os.RemoveAll(workdir)
+	os.Mkdir(filepath.Join(workdir, "sub"), 0700)
+
+	configs := map[string]Config{
+		"role1":     Config{Members: []security.BlessingPattern{"A", "B", "C"}},
+		"role2":     Config{Members: []security.BlessingPattern{"C", "D", "E"}},
+		"sub/role3": Config{ImportMembers: []string{"../role2"}},
+		"sub/role4": Config{ImportMembers: []string{"../role1", "../role2"}},
+		"sub/role5": Config{ImportMembers: []string{"../role1", "../role6"}},
+		"role6":     Config{ImportMembers: []string{"sub/role5"}, Members: []security.BlessingPattern{"F"}},
+	}
+	for role, config := range configs {
+		WriteConfig(t, config, filepath.Join(workdir, role+".conf"))
+	}
+
+	testcases := []struct {
+		role    string
+		members []security.BlessingPattern
+	}{
+		{"role1", []security.BlessingPattern{"A/_role", "B/_role", "C/_role"}},
+		{"role2", []security.BlessingPattern{"C/_role", "D/_role", "E/_role"}},
+		{"sub/role3", []security.BlessingPattern{"C/_role", "D/_role", "E/_role"}},
+		{"sub/role4", []security.BlessingPattern{"A/_role", "B/_role", "C/_role", "D/_role", "E/_role"}},
+		{"sub/role5", []security.BlessingPattern{"A/_role", "B/_role", "C/_role", "F/_role"}},
+		{"role6", []security.BlessingPattern{"A/_role", "B/_role", "C/_role", "F/_role"}},
+	}
+	for _, tc := range testcases {
+		c, err := loadExpandedConfig(filepath.Join(workdir, tc.role+".conf"), nil)
+		if err != nil {
+			t.Errorf("unexpected error for %q: %v", tc.role, err)
+			continue
+		}
+		sort.Sort(BlessingPatternSlice(c.Members))
+		if !reflect.DeepEqual(tc.members, c.Members) {
+			t.Errorf("unexpected results. Got %#v, expected %#v", c.Members, tc.members)
+		}
+	}
+}
+
+type BlessingPatternSlice []security.BlessingPattern
+
+func (p BlessingPatternSlice) Len() int           { return len(p) }
+func (p BlessingPatternSlice) Less(i, j int) bool { return p[i] < p[j] }
+func (p BlessingPatternSlice) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
+
+func WriteConfig(t *testing.T, config Config, fileName string) {
+	mConf, err := json.Marshal(config)
+	if err != nil {
+		t.Fatalf("json.MarshalIndent failed: %v", err)
+	}
+	if err := ioutil.WriteFile(fileName, mConf, 0644); err != nil {
+		t.Fatalf("ioutil.WriteFile(%q, %q) failed: %v", fileName, string(mConf), err)
+	}
+}
diff --git a/services/role/roled/internal/role_test.go b/services/role/roled/internal/role_test.go
new file mode 100644
index 0000000..e59f6d6
--- /dev/null
+++ b/services/role/roled/internal/role_test.go
@@ -0,0 +1,311 @@
+// 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 (
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"reflect"
+	"testing"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/verror"
+	vsecurity "v.io/x/ref/lib/security"
+	"v.io/x/ref/lib/xrpc"
+	"v.io/x/ref/services/role"
+	irole "v.io/x/ref/services/role/roled/internal"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/testutil"
+
+	_ "v.io/x/ref/runtime/factories/generic"
+)
+
+func TestSeekBlessings(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	workdir, err := ioutil.TempDir("", "test-role-server-")
+	if err != nil {
+		t.Fatalf("ioutil.TempDir failed: %v", err)
+	}
+	defer os.RemoveAll(workdir)
+
+	// Role A is a restricted role, i.e. it can be used in sensitive Permissions.
+	roleAConf := irole.Config{
+		Members: []security.BlessingPattern{
+			"test-blessing/users/user1/_role",
+			"test-blessing/users/user2/_role",
+			"test-blessing/users/user3", // _role implied
+		},
+		Extend: true,
+	}
+	irole.WriteConfig(t, roleAConf, filepath.Join(workdir, "A.conf"))
+
+	// Role B is an unrestricted role.
+	roleBConf := irole.Config{
+		Members: []security.BlessingPattern{
+			"test-blessing/users/user1/_role",
+			"test-blessing/users/user3/_role",
+		},
+		Audit:  true,
+		Extend: false,
+	}
+	irole.WriteConfig(t, roleBConf, filepath.Join(workdir, "B.conf"))
+
+	root := testutil.IDProviderFromPrincipal(v23.GetPrincipal(ctx))
+
+	var (
+		user1  = newPrincipalContext(t, ctx, root, "users/user1")
+		user1R = newPrincipalContext(t, ctx, root, "users/user1/_role")
+		user2  = newPrincipalContext(t, ctx, root, "users/user2")
+		user2R = newPrincipalContext(t, ctx, root, "users/user2/_role")
+		user3  = newPrincipalContext(t, ctx, root, "users/user3")
+		user3R = newPrincipalContext(t, ctx, root, "users/user3", "users/user3/_role/foo", "users/user3/_role/bar")
+	)
+
+	testServerCtx := newPrincipalContext(t, ctx, root, "testserver")
+	tDisp := &testDispatcher{}
+	_, err = xrpc.NewDispatchingServer(testServerCtx, "test", tDisp)
+	if err != nil {
+		t.Fatalf("NewDispatchingServer failed: %v", err)
+	}
+
+	const noErr = ""
+	testcases := []struct {
+		ctx       *context.T
+		role      string
+		errID     verror.ID
+		blessings []string
+	}{
+		{user1, "", verror.ErrUnknownMethod.ID, nil},
+		{user1, "unknown", verror.ErrNoAccess.ID, nil},
+		{user2, "unknown", verror.ErrNoAccess.ID, nil},
+		{user3, "unknown", verror.ErrNoAccess.ID, nil},
+
+		{user1, "A", verror.ErrNoAccess.ID, nil},
+		{user1R, "A", noErr, []string{"test-blessing/roles/A/test-blessing/users/user1"}},
+		{user2, "A", verror.ErrNoAccess.ID, nil},
+		{user2R, "A", noErr, []string{"test-blessing/roles/A/test-blessing/users/user2"}},
+		{user3, "A", verror.ErrNoAccess.ID, nil},
+		{user3R, "A", noErr, []string{"test-blessing/roles/A/test-blessing/users/user3/_role/bar", "test-blessing/roles/A/test-blessing/users/user3/_role/foo"}},
+
+		{user1, "B", verror.ErrNoAccess.ID, nil},
+		{user1R, "B", noErr, []string{"test-blessing/roles/B"}},
+		{user2, "B", verror.ErrNoAccess.ID, nil},
+		{user2R, "B", verror.ErrNoAccess.ID, nil},
+		{user3, "B", verror.ErrNoAccess.ID, nil},
+		{user3R, "B", noErr, []string{"test-blessing/roles/B"}},
+	}
+	addr := newRoleServer(t, newPrincipalContext(t, ctx, root, "roles"), workdir)
+	for _, tc := range testcases {
+		user := v23.GetPrincipal(tc.ctx).BlessingStore().Default()
+		c := role.RoleClient(naming.Join(addr, tc.role))
+		blessings, err := c.SeekBlessings(tc.ctx)
+		if verror.ErrorID(err) != tc.errID {
+			t.Errorf("unexpected error ID for (%q, %q). Got %#v, expected %#v", user, tc.role, verror.ErrorID(err), tc.errID)
+		}
+		if err != nil {
+			continue
+		}
+		previousBlessings, _ := v23.GetPrincipal(tc.ctx).BlessingStore().Set(blessings, security.AllPrincipals)
+		blessingNames, rejected := callTest(t, tc.ctx, "test")
+		if !reflect.DeepEqual(blessingNames, tc.blessings) {
+			t.Errorf("unexpected blessings for (%q, %q). Got %q, expected %q", user, tc.role, blessingNames, tc.blessings)
+		}
+		if len(rejected) != 0 {
+			t.Errorf("unexpected rejected blessings for (%q, %q): %q", user, tc.role, rejected)
+		}
+		v23.GetPrincipal(tc.ctx).BlessingStore().Set(previousBlessings, security.AllPrincipals)
+	}
+}
+
+func TestPeerBlessingCaveats(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	workdir, err := ioutil.TempDir("", "test-role-server-")
+	if err != nil {
+		t.Fatalf("ioutil.TempDir failed: %v", err)
+	}
+	defer os.RemoveAll(workdir)
+
+	roleConf := irole.Config{
+		Members: []security.BlessingPattern{"test-blessing/users/user/_role"},
+		Peers: []security.BlessingPattern{
+			security.BlessingPattern("test-blessing/peer1"),
+			security.BlessingPattern("test-blessing/peer3"),
+		},
+	}
+	irole.WriteConfig(t, roleConf, filepath.Join(workdir, "role.conf"))
+
+	var (
+		root  = testutil.IDProviderFromPrincipal(v23.GetPrincipal(ctx))
+		user  = newPrincipalContext(t, ctx, root, "users/user/_role")
+		peer1 = newPrincipalContext(t, ctx, root, "peer1")
+		peer2 = newPrincipalContext(t, ctx, root, "peer2")
+		peer3 = newPrincipalContext(t, ctx, root, "peer3")
+	)
+
+	roleAddr := newRoleServer(t, newPrincipalContext(t, ctx, root, "roles"), workdir)
+
+	tDisp := &testDispatcher{}
+	_, err = xrpc.NewDispatchingServer(peer1, "peer1", tDisp)
+	if err != nil {
+		t.Fatalf("NewDispatchingServer failed: %v", err)
+	}
+	_, err = xrpc.NewDispatchingServer(peer2, "peer2", tDisp)
+	if err != nil {
+		t.Fatalf("NewDispatchingServer failed: %v", err)
+	}
+	_, err = xrpc.NewDispatchingServer(peer3, "peer3", tDisp)
+	if err != nil {
+		t.Fatalf("NewDispatchingServer failed: %v", err)
+	}
+
+	c := role.RoleClient(naming.Join(roleAddr, "role"))
+	blessings, err := c.SeekBlessings(user)
+	if err != nil {
+		t.Error("unexpected error:", err)
+	}
+	v23.GetPrincipal(user).BlessingStore().Set(blessings, security.AllPrincipals)
+
+	testcases := []struct {
+		peer          string
+		blessingNames []string
+		rejectedNames []string
+	}{
+		{"peer1", []string{"test-blessing/roles/role"}, nil},
+		{"peer2", nil, []string{"test-blessing/roles/role"}},
+		{"peer3", []string{"test-blessing/roles/role"}, nil},
+	}
+	for i, tc := range testcases {
+		blessingNames, rejected := callTest(t, user, tc.peer)
+		var rejectedNames []string
+		for _, r := range rejected {
+			rejectedNames = append(rejectedNames, r.Blessing)
+		}
+		if !reflect.DeepEqual(blessingNames, tc.blessingNames) {
+			t.Errorf("Unexpected blessing names for #%d. Got %q, expected %q", i, blessingNames, tc.blessingNames)
+		}
+		if !reflect.DeepEqual(rejectedNames, tc.rejectedNames) {
+			t.Errorf("Unexpected rejected names for #%d. Got %q, expected %q", i, rejectedNames, tc.rejectedNames)
+		}
+	}
+}
+
+func TestGlob(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	workdir, err := ioutil.TempDir("", "test-role-server-")
+	if err != nil {
+		t.Fatalf("ioutil.TempDir failed: %v", err)
+	}
+	defer os.RemoveAll(workdir)
+	os.Mkdir(filepath.Join(workdir, "sub1"), 0700)
+	os.Mkdir(filepath.Join(workdir, "sub1", "sub2"), 0700)
+	os.Mkdir(filepath.Join(workdir, "sub3"), 0700)
+
+	// Role that user1 has access to.
+	roleAConf := irole.Config{Members: []security.BlessingPattern{"test-blessing/user1"}}
+	irole.WriteConfig(t, roleAConf, filepath.Join(workdir, "A.conf"))
+	irole.WriteConfig(t, roleAConf, filepath.Join(workdir, "sub1/B.conf"))
+	irole.WriteConfig(t, roleAConf, filepath.Join(workdir, "sub1/C.conf"))
+	irole.WriteConfig(t, roleAConf, filepath.Join(workdir, "sub1/sub2/D.conf"))
+
+	// Role that user2 has access to.
+	roleBConf := irole.Config{Members: []security.BlessingPattern{"test-blessing/user2"}}
+	irole.WriteConfig(t, roleBConf, filepath.Join(workdir, "sub1/sub2/X.conf"))
+
+	root := testutil.IDProviderFromPrincipal(v23.GetPrincipal(ctx))
+	user1 := newPrincipalContext(t, ctx, root, "user1/_role")
+	user2 := newPrincipalContext(t, ctx, root, "user2/_role")
+	user3 := newPrincipalContext(t, ctx, root, "user3/_role")
+	addr := newRoleServer(t, newPrincipalContext(t, ctx, root, "roles"), workdir)
+
+	testcases := []struct {
+		user    *context.T
+		name    string
+		pattern string
+		results []string
+	}{
+		{user1, "", "*", []string{"A", "sub1"}},
+		{user1, "sub1", "*", []string{"B", "C", "sub2"}},
+		{user1, "sub1/sub2", "*", []string{"D"}},
+		{user1, "", "...", []string{"", "A", "sub1", "sub1/B", "sub1/C", "sub1/sub2", "sub1/sub2/D"}},
+		{user2, "", "*", []string{"sub1"}},
+		{user2, "", "...", []string{"", "sub1", "sub1/sub2", "sub1/sub2/X"}},
+		{user3, "", "*", []string{}},
+		{user3, "", "...", []string{""}},
+	}
+	for i, tc := range testcases {
+		matches, _, _ := testutil.GlobName(tc.user, naming.Join(addr, tc.name), tc.pattern)
+		if !reflect.DeepEqual(matches, tc.results) {
+			t.Errorf("unexpected results for tc #%d. Got %q, expected %q", i, matches, tc.results)
+		}
+	}
+}
+
+func newPrincipalContext(t *testing.T, ctx *context.T, root *testutil.IDProvider, names ...string) *context.T {
+	principal := testutil.NewPrincipal()
+	var blessings []security.Blessings
+	for _, n := range names {
+		blessing, err := root.NewBlessings(principal, n)
+		if err != nil {
+			t.Fatalf("root.Bless failed for %q: %v", n, err)
+		}
+		blessings = append(blessings, blessing)
+	}
+	bUnion, err := security.UnionOfBlessings(blessings...)
+	if err != nil {
+		t.Fatalf("security.UnionOfBlessings failed: %v", err)
+	}
+	vsecurity.SetDefaultBlessings(principal, bUnion)
+	ctx, err = v23.WithPrincipal(ctx, principal)
+	if err != nil {
+		t.Fatalf("v23.WithPrincipal failed: %v", err)
+	}
+	return ctx
+}
+
+func newRoleServer(t *testing.T, ctx *context.T, dir string) string {
+	_, err := xrpc.NewDispatchingServer(ctx, "role", irole.NewDispatcher(dir, "role"))
+	if err != nil {
+		t.Fatalf("ServeDispatcher failed: %v", err)
+	}
+	return "role"
+}
+
+func callTest(t *testing.T, ctx *context.T, addr string) (blessingNames []string, rejected []security.RejectedBlessing) {
+	call, err := v23.GetClient(ctx).StartCall(ctx, addr, "Test", nil)
+	if err != nil {
+		t.Fatalf("StartCall failed: %v", err)
+	}
+	if err := call.Finish(&blessingNames, &rejected); err != nil {
+		t.Fatalf("Finish failed: %v", err)
+	}
+	return
+}
+
+type testDispatcher struct {
+}
+
+func (d *testDispatcher) Lookup(_ *context.T, suffix string) (interface{}, security.Authorizer, error) {
+	return d, d, nil
+}
+
+func (d *testDispatcher) Authorize(*context.T, security.Call) error {
+	return nil
+}
+
+func (d *testDispatcher) Test(ctx *context.T, call rpc.ServerCall) ([]string, []security.RejectedBlessing, error) {
+	blessings, rejected := security.RemoteBlessingNames(ctx, call.Security())
+	return blessings, rejected, nil
+}
diff --git a/services/role/roled/main.go b/services/role/roled/main.go
new file mode 100644
index 0000000..e1a3857
--- /dev/null
+++ b/services/role/roled/main.go
@@ -0,0 +1,53 @@
+// 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.
+
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go . -help
+
+package main
+
+import (
+	"fmt"
+
+	"v.io/v23/context"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/signals"
+	"v.io/x/ref/lib/v23cmd"
+	"v.io/x/ref/lib/xrpc"
+	_ "v.io/x/ref/runtime/factories/static"
+	irole "v.io/x/ref/services/role/roled/internal"
+)
+
+var configDir, name string
+
+func main() {
+	cmdRoleD.Flags.StringVar(&configDir, "config-dir", "", "The directory where the role configuration files are stored.")
+	cmdRoleD.Flags.StringVar(&name, "name", "", "The name to publish for this service.")
+
+	cmdline.HideGlobalFlagsExcept()
+	cmdline.Main(cmdRoleD)
+}
+
+var cmdRoleD = &cmdline.Command{
+	Runner: v23cmd.RunnerFunc(runRoleD),
+	Name:   "roled",
+	Short:  "Runs the Role interface daemon.",
+	Long:   "Command roled runs the Role interface daemon.",
+}
+
+func runRoleD(ctx *context.T, env *cmdline.Env, args []string) error {
+	if len(configDir) == 0 {
+		return env.UsageErrorf("-config-dir must be specified")
+	}
+	if len(name) == 0 {
+		return env.UsageErrorf("-name must be specified")
+	}
+	_, err := xrpc.NewDispatchingServer(ctx, name, irole.NewDispatcher(configDir, name))
+	if err != nil {
+		return fmt.Errorf("NewServer failed: %v", err)
+	}
+	fmt.Printf("NAME=%s\n", name)
+	<-signals.ShutdownOnSignals(ctx)
+	return nil
+}
diff --git a/services/stats/types.go b/services/stats/types.go
new file mode 100644
index 0000000..ccb6b52
--- /dev/null
+++ b/services/stats/types.go
@@ -0,0 +1,55 @@
+// 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 stats
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"strconv"
+	"strings"
+)
+
+// Print writes textual output of the histogram values.
+func (v HistogramValue) Print(w io.Writer) {
+	avg := float64(v.Sum) / float64(v.Count)
+	fmt.Fprintf(w, "Count: %d  Min: %d  Max: %d  Avg: %.2f\n", v.Count, v.Min, v.Max, avg)
+	fmt.Fprintf(w, "%s\n", strings.Repeat("-", 60))
+	if v.Count <= 0 {
+		return
+	}
+
+	maxBucketDigitLen := len(strconv.FormatInt(v.Buckets[len(v.Buckets)-1].LowBound, 10))
+	if maxBucketDigitLen < 3 {
+		// For "inf".
+		maxBucketDigitLen = 3
+	}
+	maxCountDigitLen := len(strconv.FormatInt(v.Count, 10))
+	percentMulti := 100 / float64(v.Count)
+
+	accCount := int64(0)
+	for i, b := range v.Buckets {
+		fmt.Fprintf(w, "[%*d, ", maxBucketDigitLen, b.LowBound)
+		if i+1 < len(v.Buckets) {
+			fmt.Fprintf(w, "%*d)", maxBucketDigitLen, v.Buckets[i+1].LowBound)
+		} else {
+			fmt.Fprintf(w, "%*s)", maxBucketDigitLen, "inf")
+		}
+
+		accCount += b.Count
+		fmt.Fprintf(w, "  %*d  %5.1f%%  %5.1f%%", maxCountDigitLen, b.Count, float64(b.Count)*percentMulti, float64(accCount)*percentMulti)
+
+		const barScale = 0.1
+		barLength := int(float64(b.Count)*percentMulti*barScale + 0.5)
+		fmt.Fprintf(w, "  %s\n", strings.Repeat("#", barLength))
+	}
+}
+
+// String returns the textual output of the histogram values as string.
+func (v HistogramValue) String() string {
+	var b bytes.Buffer
+	v.Print(&b)
+	return b.String()
+}
diff --git a/services/stats/types.vdl b/services/stats/types.vdl
new file mode 100644
index 0000000..1835f80
--- /dev/null
+++ b/services/stats/types.vdl
@@ -0,0 +1,28 @@
+// 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.
+
+// Packages stats defines the non-native types exported by the stats service.
+package stats
+
+// HistogramValue is the value of Histogram objects.
+type HistogramValue struct {
+	// Count is the total number of values added to the histogram.
+	Count int64
+	// Sum is the sum of all the values added to the histogram.
+	Sum int64
+	// Min is the minimum of all the values added to the histogram.
+	Min int64
+	// Max is the maximum of all the values added to the histogram.
+	Max int64
+	// Buckets contains all the buckets of the histogram.
+	Buckets []HistogramBucket
+}
+
+// HistogramBucket is one histogram bucket.
+type HistogramBucket struct {
+	// LowBound is the lower bound of the bucket.
+	LowBound int64
+	// Count is the number of values in the bucket.
+	Count int64
+}
diff --git a/services/stats/types.vdl.go b/services/stats/types.vdl.go
new file mode 100644
index 0000000..5028b34
--- /dev/null
+++ b/services/stats/types.vdl.go
@@ -0,0 +1,51 @@
+// 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 by the vanadium vdl tool.
+// Source: types.vdl
+
+// Packages stats defines the non-native types exported by the stats service.
+package stats
+
+import (
+	// VDL system imports
+	"v.io/v23/vdl"
+)
+
+// HistogramValue is the value of Histogram objects.
+type HistogramValue struct {
+	// Count is the total number of values added to the histogram.
+	Count int64
+	// Sum is the sum of all the values added to the histogram.
+	Sum int64
+	// Min is the minimum of all the values added to the histogram.
+	Min int64
+	// Max is the maximum of all the values added to the histogram.
+	Max int64
+	// Buckets contains all the buckets of the histogram.
+	Buckets []HistogramBucket
+}
+
+func (HistogramValue) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/services/stats.HistogramValue"`
+}) {
+}
+
+// HistogramBucket is one histogram bucket.
+type HistogramBucket struct {
+	// LowBound is the lower bound of the bucket.
+	LowBound int64
+	// Count is the number of values in the bucket.
+	Count int64
+}
+
+func (HistogramBucket) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/services/stats.HistogramBucket"`
+}) {
+}
+
+func init() {
+	vdl.Register((*HistogramValue)(nil))
+	vdl.Register((*HistogramBucket)(nil))
+}
diff --git a/services/wspr/browsprd/main_nacl.go b/services/wspr/browsprd/main_nacl.go
new file mode 100644
index 0000000..08940f7
--- /dev/null
+++ b/services/wspr/browsprd/main_nacl.go
@@ -0,0 +1,453 @@
+// 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.
+
+// Daemon browsprd implements the wspr web socket proxy as a Native Client
+// executable, to be run as a Chrome extension.
+package main
+
+import (
+	"bytes"
+	"crypto/ecdsa"
+	"encoding/base64"
+	"fmt"
+	"runtime"
+	"runtime/ppapi"
+
+	"v.io/v23"
+	"v.io/v23/logging"
+	"v.io/v23/security"
+	"v.io/v23/vdl"
+	"v.io/x/lib/vlog"
+	"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/services/wspr/internal/app"
+	"v.io/x/ref/services/wspr/internal/browspr"
+	"v.io/x/ref/services/wspr/internal/channel/channel_nacl"
+	"v.io/x/ref/services/wspr/internal/principal"
+	"v.io/x/ref/services/wspr/internal/rpc/server"
+)
+
+func main() {
+	security.OverrideCaveatValidation(server.CaveatValidation)
+	ppapi.Init(newBrowsprInstance)
+}
+
+// browsprInstance represents an instance of a PPAPI client and receives
+// callbacks from PPAPI to handle events.
+type browsprInstance struct {
+	ppapi.Instance
+	fs      ppapi.FileSystem
+	browspr *browspr.Browspr
+	channel *channel_nacl.Channel
+	logger  logging.Logger
+}
+
+var _ ppapi.InstanceHandlers = (*browsprInstance)(nil)
+
+func newBrowsprInstance(inst ppapi.Instance) ppapi.InstanceHandlers {
+	runtime.GOMAXPROCS(4)
+	browsprInst := &browsprInstance{Instance: inst, logger: logger.Global()}
+	browsprInst.initFileSystem()
+
+	// Give the websocket interface the ppapi instance.
+	websocket.PpapiInstance = inst
+
+	// Set up the channel and register start rpc handler.
+	browsprInst.channel = channel_nacl.NewChannel(inst)
+	browsprInst.channel.RegisterRequestHandler("start", browsprInst.HandleStartMessage)
+
+	return browsprInst
+}
+
+func (inst *browsprInstance) initFileSystem() {
+	var err error
+	// Create a filesystem.
+	if inst.fs, err = inst.CreateFileSystem(ppapi.PP_FILESYSTEMTYPE_LOCALPERSISTENT); err != nil {
+		panic(err.Error())
+	}
+	if ty := inst.fs.Type(); ty != ppapi.PP_FILESYSTEMTYPE_LOCALPERSISTENT {
+		panic(fmt.Errorf("unexpected filesystem type: %d", ty))
+	}
+	// Open filesystem with expected size of 2K
+	if err = inst.fs.OpenFS(1 << 11); err != nil {
+		panic(fmt.Errorf("failed to open filesystem:%s", err))
+	}
+	// Create directory to store browspr keys
+	if err = inst.fs.MkdirAll(browsprDir); err != nil {
+		panic(fmt.Errorf("failed to create directory:%s", err))
+	}
+}
+
+const browsprDir = "/browspr/data"
+
+func (inst *browsprInstance) loadKeyFromStorage(browsprKeyFile string) (*ecdsa.PrivateKey, error) {
+	inst.logger.VI(1).Infof("Attempting to read key from file %v", browsprKeyFile)
+
+	rFile, err := inst.fs.Open(browsprKeyFile)
+	if err != nil {
+		inst.logger.VI(1).Infof("Key not found in file %v", browsprKeyFile)
+		return nil, err
+	}
+
+	inst.logger.VI(1).Infof("Attempting to load cached browspr ecdsaPrivateKey in file %v", browsprKeyFile)
+	defer rFile.Release()
+	key, err := vsecurity.LoadPEMKey(rFile, nil)
+	if err != nil {
+		return nil, fmt.Errorf("failed to load browspr key:%s", err)
+	}
+	if ecdsaKey, ok := key.(*ecdsa.PrivateKey); !ok {
+		return nil, fmt.Errorf("got key of type %T, want *ecdsa.PrivateKey", key)
+	} else {
+		return ecdsaKey, nil
+	}
+}
+
+// Loads a saved key if one exists, otherwise creates a new one and persists it.
+func (inst *browsprInstance) initKey() (*ecdsa.PrivateKey, error) {
+	browsprKeyFile := browsprDir + "/privateKey.pem."
+	if ecdsaKey, err := inst.loadKeyFromStorage(browsprKeyFile); err == nil {
+		return ecdsaKey, nil
+	} else {
+		inst.logger.VI(1).Infof("inst.loadKeyFromStorage(%v) failed: %v", browsprKeyFile, err)
+	}
+
+	inst.logger.VI(1).Infof("Generating new browspr ecdsaPrivateKey")
+
+	// Generate new keys and store them.
+	var ecdsaKey *ecdsa.PrivateKey
+	var err error
+	if _, ecdsaKey, err = vsecurity.NewPrincipalKey(); err != nil {
+		return nil, fmt.Errorf("failed to generate security key:%s", err)
+	}
+	// Persist the keys in a local file.
+	wFile, err := inst.fs.Create(browsprKeyFile)
+	if err != nil {
+		return nil, fmt.Errorf("failed to create file to persist browspr keys:%s", err)
+	}
+	defer wFile.Release()
+	var b bytes.Buffer
+	if err = vsecurity.SavePEMKey(&b, ecdsaKey, nil); err != nil {
+		return nil, fmt.Errorf("failed to save browspr key:%s", err)
+	}
+	if n, err := wFile.Write(b.Bytes()); n != b.Len() || err != nil {
+		return nil, fmt.Errorf("failed to write browspr key:%s", err)
+	}
+	return ecdsaKey, nil
+}
+
+func (inst *browsprInstance) newPrincipal(ecdsaKey *ecdsa.PrivateKey, blessingRootsData, blessingRootsSig, blessingStoreData, blessingStoreSig string) (security.Principal, error) {
+	roots, err := principal.NewFileSerializer(blessingRootsData, blessingRootsSig, inst.fs)
+	if err != nil {
+		return nil, fmt.Errorf("failed to create blessing roots serializer:%s", err)
+	}
+	store, err := principal.NewFileSerializer(blessingStoreData, blessingStoreSig, inst.fs)
+	if err != nil {
+		return nil, fmt.Errorf("failed to create blessing store serializer:%s", err)
+	}
+	state := &vsecurity.PrincipalStateSerializer{
+		BlessingRoots: roots,
+		BlessingStore: store,
+	}
+	return vsecurity.NewPrincipalFromSigner(security.NewInMemoryECDSASigner(ecdsaKey), state)
+}
+
+func (inst *browsprInstance) newPersistantPrincipal(peerNames []string) (security.Principal, error) {
+	ecdsaKey, err := inst.initKey()
+	if err != nil {
+		return nil, fmt.Errorf("failed to initialize ecdsa key:%s", err)
+	}
+
+	blessingRootsData := browsprDir + "/blessingroots.data"
+	blessingRootsSig := browsprDir + "/blessingroots.sig"
+	blessingStoreData := browsprDir + "/blessingstore.data"
+	blessingStoreSig := browsprDir + "/blessingstore.sig"
+
+	principal, err := inst.newPrincipal(ecdsaKey, blessingRootsData, blessingRootsSig, blessingStoreData, blessingStoreSig)
+	if err != nil {
+		inst.logger.VI(1).Infof("inst.newPrincipal(%v, %v, %v, %v, %v) failed: %v", ecdsaKey, blessingRootsData, blessingRootsSig, blessingStoreData, blessingStoreSig)
+
+		// Delete the files and try again.
+		for _, file := range []string{blessingRootsData, blessingRootsSig, blessingStoreData, blessingStoreSig} {
+			inst.fs.Remove(file)
+		}
+		principal, err = inst.newPrincipal(ecdsaKey, blessingRootsData, blessingRootsSig, blessingStoreData, blessingStoreSig)
+	}
+	return principal, err
+}
+
+// Base64-decode and unmarshal a public key.
+func decodeAndUnmarshalPublicKey(k string) (security.PublicKey, error) {
+	decodedK, err := base64.URLEncoding.DecodeString(k)
+	if err != nil {
+		return nil, err
+	}
+	return security.UnmarshalPublicKey(decodedK)
+}
+
+func (inst *browsprInstance) HandleStartMessage(val *vdl.Value) (*vdl.Value, error) {
+	inst.logger.VI(1).Info("Starting Browspr")
+	var msg browspr.StartMessage
+	if err := vdl.Convert(&msg, val); err != nil {
+		return nil, fmt.Errorf("HandleStartMessage did not receive StartMessage, received: %v, %v", val, err)
+	}
+
+	p, err := inst.newPersistantPrincipal(msg.IdentitydBlessingRoot.Names)
+	if err != nil {
+		return nil, err
+	}
+
+	blessingName := "browspr-default-blessing"
+	blessing, err := p.BlessSelf(blessingName)
+	if err != nil {
+		return nil, fmt.Errorf("p.BlessSelf(%v) failed: %v", blessingName, err)
+	}
+
+	// If msg.IdentitydBlessingRoot has a public key and names, then add
+	// the public key to our set of trusted roots, and limit our blessing
+	// to only talk to those names.
+	if msg.IdentitydBlessingRoot.PublicKey != "" {
+		if len(msg.IdentitydBlessingRoot.Names) == 0 {
+			return nil, fmt.Errorf("invalid IdentitydBlessingRoot: Names is empty")
+		}
+
+		inst.logger.VI(1).Infof("Using blessing roots for identity with key %v and names %v", msg.IdentitydBlessingRoot.PublicKey, msg.IdentitydBlessingRoot.Names)
+		key, err := decodeAndUnmarshalPublicKey(msg.IdentitydBlessingRoot.PublicKey)
+		if err != nil {
+			inst.logger.Fatalf("decodeAndUnmarshalPublicKey(%v) failed: %v", msg.IdentitydBlessingRoot.PublicKey, err)
+		}
+
+		for _, name := range msg.IdentitydBlessingRoot.Names {
+			pattern := security.BlessingPattern(name)
+
+			// Trust the identity servers blessing root.
+			p.Roots().Add(key, pattern)
+
+			// Use our blessing to only talk to the identity server.
+			if _, err := p.BlessingStore().Set(blessing, pattern); err != nil {
+				return nil, fmt.Errorf("p.BlessingStore().Set(%v, %v) failed: %v", blessing, pattern, err)
+			}
+		}
+	} else {
+		inst.logger.VI(1).Infof("IdentitydBlessingRoot.PublicKey is empty.  Will allow browspr blessing to be shareable with all principals.")
+		// Set our blessing as shareable with all peers.
+		if _, err := p.BlessingStore().Set(blessing, security.AllPrincipals); err != nil {
+			return nil, fmt.Errorf("p.BlessingStore().Set(%v, %v) failed: %v", blessing, security.AllPrincipals, err)
+		}
+	}
+
+	// Initialize the runtime.
+	// TODO(suharshs,mattr): Should we worried about not shutting down here?
+	ctx, _ := v23.Init()
+
+	ctx, err = v23.WithPrincipal(ctx, p)
+	if err != nil {
+		return nil, err
+	}
+
+	// TODO(cnicolaou): provide a means of configuring logging that
+	// doesn't depend on vlog - e.g. ConfigureFromArgs(args []string) to
+	// pair with ConfigureFromFlags(). See v.io/i/556
+	// Configure logger with level and module from start message.
+	inst.logger.VI(1).Infof("Configuring vlog with v=%v, modulesSpec=%v", msg.LogLevel, msg.LogModule)
+	moduleSpec := vlog.ModuleSpec{}
+	moduleSpec.Set(msg.LogModule)
+	if err := vlog.Log.Configure(vlog.OverridePriorConfiguration(true), vlog.Level(msg.LogLevel), moduleSpec); err != nil {
+		return nil, err
+	}
+
+	// TODO(ataly, bprosnitz, caprita): The runtime MUST be cleaned up
+	// after use. Figure out the appropriate place to add the Cleanup call.
+
+	v23.GetNamespace(ctx).SetRoots(msg.NamespaceRoot)
+
+	listenSpec := v23.GetListenSpec(ctx)
+	listenSpec.Proxy = msg.Proxy
+
+	principalSerializer, err := principal.NewFileSerializer(browsprDir+"/principalData", browsprDir+"/principalSignature", inst.fs)
+	if err != nil {
+		return nil, fmt.Errorf("principal.NewFileSerializer() failed: %v", err)
+	}
+
+	inst.logger.VI(1).Infof("Starting browspr with config: proxy=%q mounttable=%q identityd=%q identitydBlessingRoot=%q ", msg.Proxy, msg.NamespaceRoot, msg.Identityd, msg.IdentitydBlessingRoot)
+	inst.browspr = browspr.NewBrowspr(ctx,
+		inst.BrowsprOutgoingPostMessage,
+		&listenSpec,
+		msg.Identityd,
+		[]string{msg.NamespaceRoot},
+		principalSerializer)
+
+	// Add the rpc handlers that depend on inst.browspr.
+	inst.channel.RegisterRequestHandler("auth:create-account", inst.browspr.HandleAuthCreateAccountRpc)
+	inst.channel.RegisterRequestHandler("auth:associate-account", inst.browspr.HandleAuthAssociateAccountRpc)
+	inst.channel.RegisterRequestHandler("auth:get-accounts", inst.browspr.HandleAuthGetAccountsRpc)
+	inst.channel.RegisterRequestHandler("auth:origin-has-account", inst.browspr.HandleAuthOriginHasAccountRpc)
+	inst.channel.RegisterRequestHandler("create-instance", inst.browspr.HandleCreateInstanceRpc)
+	inst.channel.RegisterRequestHandler("cleanup", inst.browspr.HandleCleanupRpc)
+
+	return nil, nil
+}
+
+func (inst *browsprInstance) BrowsprOutgoingPostMessage(instanceId int32, ty string, message string) {
+	if message == "" {
+		// TODO(nlacasse,bprosnitz): VarFromString crashes if the
+		// string is empty, so we must use a placeholder.
+		message = "."
+	}
+	dict := ppapi.NewDictVar()
+	instVar := ppapi.VarFromInt(instanceId)
+	bodyVar := ppapi.VarFromString(message)
+	tyVar := ppapi.VarFromString(ty)
+	dict.DictionarySet("instanceId", instVar)
+	dict.DictionarySet("type", tyVar)
+	dict.DictionarySet("body", bodyVar)
+	inst.PostMessage(dict)
+	instVar.Release()
+	bodyVar.Release()
+	tyVar.Release()
+	dict.Release()
+}
+
+// HandleBrowsprMessage handles one-way messages of the type "browsprMsg" by
+// sending them to browspr's handler.
+func (inst *browsprInstance) HandleBrowsprMessage(instanceId int32, origin string, varMsg ppapi.Var) error {
+	msg, err := varToMessage(varMsg)
+	if err != nil {
+		return fmt.Errorf("Invalid message: %v", err)
+	}
+
+	inst.logger.VI(1).Infof("Calling browspr's HandleMessage: instanceId %d origin %s message %s", instanceId, origin, msg)
+	if err := inst.browspr.HandleMessage(instanceId, origin, msg); err != nil {
+		return fmt.Errorf("Error while handling message in browspr: %v", err)
+	}
+	return nil
+}
+
+// HandleIntentionalPanic intentionally triggers a panic. This is used in tests
+// of the extension's crash handling behavior.
+// TODO(bprosnitz) We probably should conditionally compile this in via build
+// tags so we don't hit it in production code.
+func (inst *browsprInstance) HandleIntentionalPanic(instanceId int32, origin string, message ppapi.Var) error {
+	// NOTE(nlacasse): Calling panic directly (not inside a goroutine)
+	// sometimes blocks during the panic, causing the plugin to not emit a
+	// "crash" event. I'm not sure why this happens, but it breaks the
+	// test-nacl-plugin-crash test. Calling panic from within a goroutine seems
+	// to reliably panic "all the way" and emit a "crash" event.
+	go panic("Crashing intentionally")
+	return nil
+}
+
+// HandleBrowsprRpc handles two-way rpc messages of the type "browsprRpc"
+// sending them to the channel's handler.
+func (inst *browsprInstance) HandleBrowsprRpc(instanceId int32, origin string, message ppapi.Var) error {
+	inst.logger.VI(1).Infof("Got to HandleBrowsprRpc: instanceId: %d origin %s", instanceId, origin)
+	inst.channel.HandleMessage(message)
+	return nil
+}
+
+// handleGoError handles error returned by go code.
+func (inst *browsprInstance) handleGoError(err error) {
+	inst.logger.VI(2).Info(err)
+	inst.LogString(ppapi.PP_LOGLEVEL_ERROR, fmt.Sprintf("Error in go code: %v", err.Error()))
+	inst.logger.Error(err)
+}
+
+// HandleMessage receives messages from Javascript and uses them to perform actions.
+// A message is of the form {"type": "typeName", "body": { stuff here }},
+// where the body is passed to the message handler.
+func (inst *browsprInstance) HandleMessage(message ppapi.Var) {
+	inst.logger.VI(2).Infof("Got to HandleMessage")
+	instanceId, err := message.LookupIntValuedKey("instanceId")
+	if err != nil {
+		inst.handleGoError(err)
+		return
+	}
+	origin, err := message.LookupStringValuedKey("origin")
+	if err != nil {
+		inst.handleGoError(err)
+		return
+	}
+	ty, err := message.LookupStringValuedKey("type")
+	if err != nil {
+		inst.handleGoError(err)
+		return
+	}
+	var messageHandlers = map[string]func(int32, string, ppapi.Var) error{
+		"browsprMsg":         inst.HandleBrowsprMessage,
+		"browsprRpc":         inst.HandleBrowsprRpc,
+		"intentionallyPanic": inst.HandleIntentionalPanic,
+	}
+	h, ok := messageHandlers[ty]
+	if !ok {
+		inst.handleGoError(fmt.Errorf("No handler found for message type: %q", ty))
+		return
+	}
+	body, err := message.LookupKey("body")
+	if err != nil {
+		body = ppapi.VarUndefined
+	}
+	err = h(int32(instanceId), origin, body)
+	body.Release()
+	if err != nil {
+		inst.handleGoError(err)
+	}
+}
+
+func (inst browsprInstance) DidCreate(args map[string]string) bool {
+	inst.logger.VI(2).Infof("Got to DidCreate")
+	return true
+}
+
+func (inst *browsprInstance) DidDestroy() {
+	inst.logger.VI(2).Infof("Got to DidDestroy()")
+}
+
+func (inst *browsprInstance) DidChangeView(view ppapi.View) {
+	inst.logger.VI(2).Infof("Got to DidChangeView(%v)", view)
+}
+
+func (inst *browsprInstance) DidChangeFocus(has_focus bool) {
+	inst.logger.VI(2).Infof("Got to DidChangeFocus(%v)", has_focus)
+}
+
+func (inst *browsprInstance) HandleDocumentLoad(url_loader ppapi.Resource) bool {
+	inst.logger.VI(2).Infof("Got to HandleDocumentLoad(%v)", url_loader)
+	return true
+}
+
+func (inst *browsprInstance) HandleInputEvent(event ppapi.InputEvent) bool {
+	inst.logger.VI(2).Infof("Got to HandleInputEvent(%v)", event)
+	return true
+}
+
+func (inst *browsprInstance) Graphics3DContextLost() {
+	inst.logger.VI(2).Infof("Got to Graphics3DContextLost()")
+}
+
+func (inst *browsprInstance) MouseLockLost() {
+	inst.logger.VI(2).Infof("Got to MouseLockLost()")
+}
+
+func varToMessage(v ppapi.Var) (app.Message, error) {
+	var msg app.Message
+	id, err := v.LookupIntValuedKey("id")
+	if err != nil {
+		return msg, err
+	}
+	ty, err := v.LookupIntValuedKey("type")
+	if err != nil {
+		return msg, err
+	}
+	data, err := v.LookupStringValuedKey("data")
+	if err != nil {
+		// OK for message to have empty data.
+		data = ""
+	}
+
+	msg.Id = int32(id)
+	msg.Data = data
+	msg.Type = app.MessageType(ty)
+	return msg, nil
+}
diff --git a/services/wspr/internal/account/account.go b/services/wspr/internal/account/account.go
new file mode 100644
index 0000000..213404e
--- /dev/null
+++ b/services/wspr/internal/account/account.go
@@ -0,0 +1,158 @@
+// 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.
+
+// The account package contains logic for creating accounts and associating them with origins.
+package account
+
+import (
+	"fmt"
+	"strings"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/x/ref/services/wspr/internal/principal"
+)
+
+type BlesserService interface {
+	BlessUsingAccessToken(ctx *context.T, token string, opts ...rpc.CallOpt) (blessingObj security.Blessings, account string, err error)
+}
+
+type bs struct {
+	name string
+}
+
+func (s *bs) BlessUsingAccessToken(ctx *context.T, token string, opts ...rpc.CallOpt) (blessingObj security.Blessings, account string, err error) {
+	client := v23.GetClient(ctx)
+	var call rpc.ClientCall
+	if call, err = client.StartCall(ctx, s.name, "BlessUsingAccessToken", []interface{}{token}, opts...); err != nil {
+		return
+	}
+	var email string
+	if err := call.Finish(&blessingObj, &email); err != nil {
+		return security.Blessings{}, "", err
+	}
+	serverBlessings, _ := call.RemoteBlessings()
+	return blessingObj, accountName(serverBlessings, email), nil
+}
+
+func accountName(serverBlessings []string, email string) string {
+	return strings.Join(serverBlessings, "#") + security.ChainSeparator + email
+}
+
+type AccountManager struct {
+	ctx              *context.T
+	blesser          BlesserService
+	principalManager *principal.PrincipalManager
+}
+
+func NewAccountManager(identitydEndpoint string, principalManager *principal.PrincipalManager) *AccountManager {
+	return &AccountManager{
+		blesser:          &bs{name: identitydEndpoint},
+		principalManager: principalManager,
+	}
+}
+
+func (am *AccountManager) CreateAccount(ctx *context.T, accessToken string) (string, error) {
+	// Get a blessing for the access token from blessing server.
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
+	defer cancel()
+	blessings, account, err := am.blesser.BlessUsingAccessToken(ctx, accessToken)
+	if err != nil {
+		return "", fmt.Errorf("Error getting blessing for access token: %v", err)
+	}
+
+	// Add blessings to principalManager under the provided
+	// account.
+	if err := am.principalManager.AddAccount(account, blessings); err != nil {
+		return "", fmt.Errorf("Error adding account: %v", err)
+	}
+
+	return account, nil
+}
+
+func (am *AccountManager) GetAccounts() []string {
+	return am.principalManager.GetAccounts()
+}
+
+func (am *AccountManager) AssociateAccount(ctx *context.T, origin, account string, cavs []Caveat) error {
+	caveats, expirations, err := constructCaveats(cavs)
+	if err != nil {
+		return fmt.Errorf("failed to construct caveats: %v", err)
+	}
+	// Store the origin.
+	if err := am.principalManager.AddOrigin(origin, account, caveats, expirations); err != nil {
+		return fmt.Errorf("failed to associate account: %v", err)
+	}
+	ctx.VI(1).Infof("Associated origin %v with account %v and cavs %v", origin, account, caveats)
+	return nil
+}
+
+func (am *AccountManager) LookupPrincipal(origin string) (security.Principal, error) {
+	return am.principalManager.Principal(origin)
+}
+
+func (am *AccountManager) OriginHasAccount(origin string) bool {
+	return am.principalManager.OriginHasAccount(origin)
+}
+
+func (am *AccountManager) PrincipalManager() *principal.PrincipalManager {
+	return am.principalManager
+}
+
+// TODO(bprosnitz) Refactor WSPR to remove this.
+func (am *AccountManager) SetMockBlesser(blesser BlesserService) {
+	am.blesser = blesser
+}
+
+func constructCaveats(cavs []Caveat) ([]security.Caveat, []time.Time, error) {
+	var caveats []security.Caveat
+	var expirations []time.Time
+
+	for _, cav := range cavs {
+		var (
+			caveat     security.Caveat
+			expiration time.Time
+			err        error
+		)
+		switch cav.Type {
+		case "ExpiryCaveat":
+			caveat, expiration, err = createExpiryCaveat(cav.Args)
+			expirations = append(expirations, expiration)
+		case "MethodCaveat":
+			caveat, err = createMethodCaveat(cav.Args)
+		default:
+			return nil, nil, fmt.Errorf("caveat %v does not exist", cav.Type)
+		}
+		if err != nil {
+			return nil, nil, err
+		}
+		caveats = append(caveats, caveat)
+	}
+	return caveats, expirations, nil
+}
+
+func createExpiryCaveat(arg string) (security.Caveat, time.Time, error) {
+	var zeroTime time.Time
+	dur, err := time.ParseDuration(arg)
+	if err != nil {
+		return security.Caveat{}, zeroTime, fmt.Errorf("time.parseDuration(%v) failed: %v", arg, err)
+	}
+	expirationTime := time.Now().Add(dur)
+	cav, err := security.NewExpiryCaveat(expirationTime)
+	if err != nil {
+		return security.Caveat{}, zeroTime, fmt.Errorf("security.NewExpiryCaveat(%v) failed: %v", expirationTime, err)
+	}
+	return cav, expirationTime, nil
+}
+
+func createMethodCaveat(a string) (security.Caveat, error) {
+	args := strings.Split(a, ",")
+	if len(args) == 0 {
+		return security.Caveat{}, fmt.Errorf("must pass at least one method")
+	}
+	return security.NewMethodCaveat(args[0], args[1:]...)
+}
diff --git a/services/wspr/internal/account/account.vdl b/services/wspr/internal/account/account.vdl
new file mode 100644
index 0000000..25d7f0d
--- /dev/null
+++ b/services/wspr/internal/account/account.vdl
@@ -0,0 +1,12 @@
+// 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 account
+
+// Caveat describes a restriction on the validity of a blessing/discharge.
+// TODO Remove this
+type Caveat struct {
+  Type string
+  Args string
+}
diff --git a/services/wspr/internal/account/account.vdl.go b/services/wspr/internal/account/account.vdl.go
new file mode 100644
index 0000000..eac5bd3
--- /dev/null
+++ b/services/wspr/internal/account/account.vdl.go
@@ -0,0 +1,29 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// This file was auto-generated by the vanadium vdl tool.
+// Source: account.vdl
+
+package account
+
+import (
+	// VDL system imports
+	"v.io/v23/vdl"
+)
+
+// Caveat describes a restriction on the validity of a blessing/discharge.
+// TODO Remove this
+type Caveat struct {
+	Type string
+	Args string
+}
+
+func (Caveat) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/services/wspr/internal/account.Caveat"`
+}) {
+}
+
+func init() {
+	vdl.Register((*Caveat)(nil))
+}
diff --git a/services/wspr/internal/app/app.go b/services/wspr/internal/app/app.go
new file mode 100644
index 0000000..6edb6c0
--- /dev/null
+++ b/services/wspr/internal/app/app.go
@@ -0,0 +1,882 @@
+// 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.
+
+// The app package contains the struct that keeps per javascript app state and handles translating
+// javascript requests to vanadium requests and vice versa.
+package app
+
+import (
+	"bytes"
+	"encoding/hex"
+	"fmt"
+	"io"
+	"reflect"
+	"sync"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/i18n"
+	"v.io/v23/naming"
+	"v.io/v23/options"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/vdl"
+	"v.io/v23/vdlroot/signature"
+	"v.io/v23/verror"
+	"v.io/v23/vom"
+	"v.io/v23/vtrace"
+	"v.io/x/ref/services/wspr/internal/lib"
+	"v.io/x/ref/services/wspr/internal/namespace"
+	"v.io/x/ref/services/wspr/internal/principal"
+	"v.io/x/ref/services/wspr/internal/rpc/server"
+)
+
+const (
+	// pkgPath is the prefix os errors in this package.
+	pkgPath = "v.io/x/ref/services/wspr/internal/app"
+
+	typeFlow = 0
+)
+
+// Errors
+var (
+	marshallingError = verror.Register(pkgPath+".marshallingError", verror.NoRetry, "{1} {2} marshalling error {_}")
+	noResults        = verror.Register(pkgPath+".noResults", verror.NoRetry, "{1} {2} no results from call {_}")
+	badCaveatType    = verror.Register(pkgPath+".badCaveatType", verror.NoRetry, "{1} {2} bad caveat type {_}")
+	unknownBlessings = verror.Register(pkgPath+".unknownBlessings", verror.NoRetry, "{1} {2} unknown public id {_}")
+)
+
+type outstandingRequest struct {
+	stream *outstandingStream
+	cancel context.CancelFunc
+}
+
+// Controller represents all the state of a Vanadium Web App.  This is the struct
+// that is in charge performing all the vanadium options.
+type Controller struct {
+	// Protects everything.
+	// TODO(bjornick): We need to split this up.
+	sync.Mutex
+
+	// The context of this controller.
+	ctx *context.T
+
+	// The cleanup function for this controller.
+	cancel context.CancelFunc
+
+	// The rpc.ListenSpec to use with server.Listen
+	listenSpec *rpc.ListenSpec
+
+	// Used to generate unique ids for requests initiated by the proxy.
+	// These ids will be even so they don't collide with the ids generated
+	// by the client.
+	lastGeneratedId int32
+
+	// Used to keep track of data (streams and cancellation functions) for
+	// outstanding requests.
+	outstandingRequests map[int32]*outstandingRequest
+
+	// Maps flowids to the server that owns them.
+	flowMap map[int32]interface{}
+
+	// A manager that Handles fetching and caching signature of remote services
+	signatureManager lib.SignatureManager
+
+	// We maintain multiple Vanadium server per pipe for serving JavaScript
+	// services.
+	servers map[uint32]*server.Server
+
+	// Creates a client writer for a given flow.  This is a member so that tests can override
+	// the default implementation.
+	writerCreator func(id int32) lib.ClientWriter
+
+	// Cache of Blessings that were sent to Javascript.
+	blessingsCache *principal.BlessingsCache
+
+	// reservedServices contains a map of reserved service names.  These
+	// are objects that serve requests in wspr without actually making
+	// an outgoing rpc call.
+	reservedServices map[string]rpc.Invoker
+
+	typeEncoder *vom.TypeEncoder
+
+	typeDecoder *vom.TypeDecoder
+	typeReader  *lib.TypeReader
+}
+
+var _ ControllerServerMethods = (*Controller)(nil)
+
+// NewController creates a new Controller.  writerCreator will be used to create a new flow for rpcs to
+// javascript server.
+func NewController(ctx *context.T, writerCreator func(id int32) lib.ClientWriter, listenSpec *rpc.ListenSpec, namespaceRoots []string, p security.Principal) (*Controller, error) {
+	ctx, cancel := context.WithCancel(ctx)
+
+	if namespaceRoots != nil {
+		var err error
+		ctx, _, err = v23.WithNewNamespace(ctx, namespaceRoots...)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	ctx, _ = vtrace.WithNewTrace(ctx)
+
+	ctx, err := v23.WithPrincipal(ctx, p)
+	if err != nil {
+		return nil, err
+	}
+
+	controller := &Controller{
+		ctx:           ctx,
+		cancel:        cancel,
+		writerCreator: writerCreator,
+		listenSpec:    listenSpec,
+	}
+
+	controller.blessingsCache = principal.NewBlessingsCache(controller.SendBlessingsCacheMessages, principal.PeriodicGcPolicy(1*time.Minute))
+
+	controllerInvoker, err := rpc.ReflectInvoker(ControllerServer(controller))
+	if err != nil {
+		return nil, err
+	}
+	namespaceInvoker, err := rpc.ReflectInvoker(namespace.New(ctx))
+	if err != nil {
+		return nil, err
+	}
+	controller.reservedServices = map[string]rpc.Invoker{
+		"__controller": controllerInvoker,
+		"__namespace":  namespaceInvoker,
+	}
+
+	controller.setup()
+	return controller, nil
+}
+
+// finishCall waits for the call to finish and write out the response to w.
+func (c *Controller) finishCall(ctx *context.T, w lib.ClientWriter, clientCall rpc.ClientCall, msg *RpcRequest, span vtrace.Span) {
+	if msg.IsStreaming {
+		for {
+			var item interface{}
+			if err := clientCall.Recv(&item); err != nil {
+				if err == io.EOF {
+					break
+				}
+				w.Error(err) // Send streaming error as is
+				return
+			}
+			vomItem, err := lib.HexVomEncode(item, c.typeEncoder)
+			if err != nil {
+				w.Error(verror.New(marshallingError, ctx, item, err))
+				continue
+			}
+			if err := w.Send(lib.ResponseStream, vomItem); err != nil {
+				w.Error(verror.New(marshallingError, ctx, item))
+			}
+		}
+		if err := w.Send(lib.ResponseStreamClose, nil); err != nil {
+			w.Error(verror.New(marshallingError, ctx, "ResponseStreamClose"))
+		}
+	}
+	results := make([]*vdl.Value, msg.NumOutArgs)
+	wireBlessingsType := vdl.TypeOf(security.WireBlessings{})
+	// This array will have pointers to the values in results.
+	resultptrs := make([]interface{}, msg.NumOutArgs)
+	for i := range results {
+		resultptrs[i] = &results[i]
+	}
+	if err := clientCall.Finish(resultptrs...); err != nil {
+		// return the call system error as is
+		w.Error(err)
+		return
+	}
+	for i, val := range results {
+		if val.Type() == wireBlessingsType {
+			var blessings security.Blessings
+			if err := vdl.Convert(&blessings, val); err != nil {
+				w.Error(err)
+				return
+			}
+			results[i] = vdl.ValueOf(c.blessingsCache.Put(blessings))
+		}
+	}
+	c.sendRPCResponse(ctx, w, span, results)
+}
+
+func (c *Controller) sendRPCResponse(ctx *context.T, w lib.ClientWriter, span vtrace.Span, results []*vdl.Value) {
+	span.Finish()
+	response := RpcResponse{
+		OutArgs:       results,
+		TraceResponse: vtrace.GetResponse(ctx),
+	}
+	var buf bytes.Buffer
+	encoder := vom.NewEncoderWithTypeEncoder(&buf, c.typeEncoder)
+	if err := encoder.Encode(response); err != nil {
+		w.Error(err)
+		return
+	}
+	encoded := hex.EncodeToString(buf.Bytes())
+	if err := w.Send(lib.ResponseFinal, encoded); err != nil {
+		w.Error(verror.Convert(marshallingError, ctx, err))
+	}
+}
+
+// callOpts turns a slice of type []RpcCallOption object into an array of rpc.CallOpt.
+func (c *Controller) callOpts(opts []RpcCallOption) ([]rpc.CallOpt, error) {
+	var callOpts []rpc.CallOpt
+
+	for _, opt := range opts {
+		switch v := opt.(type) {
+		case RpcCallOptionAllowedServersPolicy:
+			callOpts = append(callOpts, options.AllowedServersPolicy(v.Value))
+		case RpcCallOptionRetryTimeout:
+			callOpts = append(callOpts, options.RetryTimeout(v.Value))
+		case RpcCallOptionGranter:
+			callOpts = append(callOpts, &jsGranter{c, v.Value})
+		default:
+			return nil, fmt.Errorf("Unknown RpcCallOption type %T", v)
+		}
+	}
+
+	return callOpts, nil
+}
+
+// serverOpts turns a slice of type []RpcServerOptions object into an array of rpc.ServerOpt.
+func (c *Controller) serverOpts(opts []RpcServerOption) ([]rpc.ServerOpt, error) {
+	var serverOpts []rpc.ServerOpt
+
+	for _, opt := range opts {
+		switch v := opt.(type) {
+		case RpcServerOptionIsLeaf:
+			serverOpts = append(serverOpts, options.IsLeaf(v.Value))
+		case RpcServerOptionServesMountTable:
+			serverOpts = append(serverOpts, options.ServesMountTable(v.Value))
+		default:
+			return nil, fmt.Errorf("Unknown RpcServerOption type %T", v)
+		}
+	}
+
+	return serverOpts, nil
+}
+
+func (c *Controller) startCall(ctx *context.T, w lib.ClientWriter, msg *RpcRequest, inArgs []interface{}) (rpc.ClientCall, error) {
+	methodName := lib.UppercaseFirstCharacter(msg.Method)
+	callOpts, err := c.callOpts(msg.CallOptions)
+	if err != nil {
+		return nil, err
+	}
+	clientCall, err := v23.GetClient(ctx).StartCall(ctx, msg.Name, methodName, inArgs, callOpts...)
+	if err != nil {
+		return nil, fmt.Errorf("error starting call (name: %v, method: %v, args: %v): %v", msg.Name, methodName, inArgs, err)
+	}
+
+	return clientCall, nil
+}
+
+// Implements the serverHelper interface
+
+// CreateNewFlow creats a new server flow that will be used to write out
+// streaming messages to Javascript.
+func (c *Controller) CreateNewFlow(s interface{}, stream rpc.Stream) *server.Flow {
+	c.Lock()
+	defer c.Unlock()
+	id := c.lastGeneratedId
+	c.lastGeneratedId += 2
+	c.flowMap[id] = s
+	os := newStream(c.typeDecoder)
+	os.init(stream)
+	c.outstandingRequests[id] = &outstandingRequest{
+		stream: os,
+	}
+	return &server.Flow{ID: id, Writer: c.writerCreator(id)}
+}
+
+// CleanupFlow removes the bookkeeping for a previously created flow.
+func (c *Controller) CleanupFlow(id int32) {
+	c.Lock()
+	request := c.outstandingRequests[id]
+	delete(c.outstandingRequests, id)
+	delete(c.flowMap, id)
+	c.Unlock()
+	if request != nil && request.stream != nil {
+		request.stream.end()
+		request.stream.waitUntilDone()
+	}
+}
+
+// BlessingsCache gets the blessings cache used by the controller.
+func (c *Controller) BlessingsCache() *principal.BlessingsCache {
+	return c.blessingsCache
+}
+
+// RT returns the runtime of the app.
+func (c *Controller) Context() *context.T {
+	return c.ctx
+}
+
+// Cleanup cleans up any outstanding rpcs.
+func (c *Controller) Cleanup(ctx *context.T) {
+	ctx.VI(0).Info("Cleaning up controller")
+	c.Lock()
+
+	for _, request := range c.outstandingRequests {
+		if request.cancel != nil {
+			request.cancel()
+		}
+		if request.stream != nil {
+			request.stream.end()
+		}
+	}
+
+	servers := []*server.Server{}
+	for _, server := range c.servers {
+		servers = append(servers, server)
+	}
+
+	c.Unlock()
+
+	// We must unlock before calling server.Stop otherwise it can deadlock.
+	for _, server := range servers {
+		server.Stop()
+	}
+
+	c.typeReader.Close()
+	c.cancel()
+}
+
+func (c *Controller) setup() {
+	c.signatureManager = lib.NewSignatureManager()
+	c.outstandingRequests = make(map[int32]*outstandingRequest)
+	c.flowMap = make(map[int32]interface{})
+	c.servers = make(map[uint32]*server.Server)
+	c.typeReader = lib.NewTypeReader()
+	c.typeDecoder = vom.NewTypeDecoder(c.typeReader)
+	c.typeEncoder = vom.NewTypeEncoder(lib.NewTypeWriter(c.writerCreator(typeFlow)))
+	c.lastGeneratedId += 2
+}
+
+// SendOnStream writes data on id's stream.  The actual network write will be
+// done asynchronously.  If there is an error, it will be sent to w.
+func (c *Controller) SendOnStream(ctx *context.T, id int32, data string, w lib.ClientWriter) {
+	c.Lock()
+	request := c.outstandingRequests[id]
+	if request == nil || request.stream == nil {
+		ctx.Errorf("unknown stream: %d", id)
+		c.Unlock()
+		return
+	}
+	stream := request.stream
+	c.Unlock()
+	stream.send(data, w)
+}
+
+// SendVeyronRequest makes a vanadium request for the given flowId.  If signal is non-nil, it will receive
+// the call object after it has been constructed.
+func (c *Controller) sendVeyronRequest(ctx *context.T, id int32, msg *RpcRequest, inArgs []interface{}, w lib.ClientWriter, stream *outstandingStream, span vtrace.Span) {
+	// We have to make the start call synchronous so we can make sure that we populate
+	// the call map before we can Handle a recieve call.
+	call, err := c.startCall(ctx, w, msg, inArgs)
+	if err != nil {
+		w.Error(verror.Convert(verror.ErrInternal, ctx, err))
+		return
+	}
+
+	if stream != nil {
+		stream.init(call)
+	}
+
+	c.finishCall(ctx, w, call, msg, span)
+	c.Lock()
+	if request, ok := c.outstandingRequests[id]; ok {
+		delete(c.outstandingRequests, id)
+		if request.cancel != nil {
+			request.cancel()
+		}
+	}
+	c.Unlock()
+}
+
+// TODO(mattr): This is a very limited implementation of ServerCall,
+// but currently none of the methods the controller exports require
+// any of this context information.
+type localCall struct {
+	ctx         *context.T
+	vrpc        *RpcRequest
+	tags        []*vdl.Value
+	w           lib.ClientWriter
+	typeEncoder *vom.TypeEncoder
+}
+
+var (
+	_ rpc.StreamServerCall = (*localCall)(nil)
+	_ security.Call        = (*localCall)(nil)
+)
+
+func (l *localCall) Send(item interface{}) error {
+	vomItem, err := lib.HexVomEncode(item, l.typeEncoder)
+	if err != nil {
+		err = verror.New(marshallingError, l.ctx, item, err)
+		l.w.Error(err)
+		return err
+	}
+	if err := l.w.Send(lib.ResponseStream, vomItem); err != nil {
+		err = verror.New(marshallingError, l.ctx, item)
+		l.w.Error(err)
+		return err
+	}
+	return nil
+}
+func (l *localCall) Recv(interface{}) error                          { return nil }
+func (l *localCall) GrantedBlessings() security.Blessings            { return security.Blessings{} }
+func (l *localCall) Server() rpc.Server                              { return nil }
+func (l *localCall) Timestamp() (t time.Time)                        { return }
+func (l *localCall) Method() string                                  { return l.vrpc.Method }
+func (l *localCall) MethodTags() []*vdl.Value                        { return l.tags }
+func (l *localCall) Suffix() string                                  { return l.vrpc.Name }
+func (l *localCall) LocalDischarges() map[string]security.Discharge  { return nil }
+func (l *localCall) RemoteDischarges() map[string]security.Discharge { return nil }
+func (l *localCall) LocalPrincipal() security.Principal              { return nil }
+func (l *localCall) LocalBlessings() security.Blessings              { return security.Blessings{} }
+func (l *localCall) RemoteBlessings() security.Blessings             { return security.Blessings{} }
+func (l *localCall) LocalEndpoint() naming.Endpoint                  { return nil }
+func (l *localCall) RemoteEndpoint() naming.Endpoint                 { return nil }
+func (l *localCall) Security() security.Call                         { return l }
+
+func (c *Controller) handleInternalCall(ctx *context.T, invoker rpc.Invoker, msg *RpcRequest, w lib.ClientWriter, span vtrace.Span, decoder *vom.Decoder) {
+	argptrs, tags, err := invoker.Prepare(ctx, msg.Method, int(msg.NumInArgs))
+	if err != nil {
+		w.Error(verror.Convert(verror.ErrInternal, ctx, err))
+		return
+	}
+	for _, argptr := range argptrs {
+		if err := decoder.Decode(argptr); err != nil {
+			w.Error(verror.Convert(verror.ErrInternal, ctx, err))
+			return
+		}
+	}
+	results, err := invoker.Invoke(ctx, &localCall{ctx, msg, tags, w, c.typeEncoder}, msg.Method, argptrs)
+	if err != nil {
+		w.Error(verror.Convert(verror.ErrInternal, ctx, err))
+		return
+	}
+	if msg.IsStreaming {
+		if err := w.Send(lib.ResponseStreamClose, nil); err != nil {
+			w.Error(verror.New(marshallingError, ctx, "ResponseStreamClose"))
+		}
+	}
+
+	// Convert results from []interface{} to []*vdl.Value.
+	vresults := make([]*vdl.Value, len(results))
+	for i, res := range results {
+		vv, err := vdl.ValueFromReflect(reflect.ValueOf(res))
+		if err != nil {
+			w.Error(verror.Convert(verror.ErrInternal, ctx, err))
+			return
+		}
+		vresults[i] = vv
+	}
+	c.sendRPCResponse(ctx, w, span, vresults)
+}
+
+// HandleCaveatValidationResponse handles the response to caveat validation
+// requests.
+func (c *Controller) HandleCaveatValidationResponse(ctx *context.T, id int32, data string) {
+	c.Lock()
+	server, ok := c.flowMap[id].(*server.Server)
+	c.Unlock()
+	if !ok {
+		ctx.Errorf("unexpected result from JavaScript. No server found matching id %d.", id)
+		return // ignore unknown server
+	}
+	server.HandleCaveatValidationResponse(ctx, id, data)
+}
+
+// HandleVeyronRequest starts a vanadium rpc and returns before the rpc has been completed.
+func (c *Controller) HandleVeyronRequest(ctx *context.T, id int32, data string, w lib.ClientWriter) {
+	binBytes, err := hex.DecodeString(data)
+	if err != nil {
+		w.Error(verror.Convert(verror.ErrInternal, ctx, fmt.Errorf("Error decoding hex string %q: %v", data, err)))
+		return
+	}
+	decoder := vom.NewDecoderWithTypeDecoder(bytes.NewReader(binBytes), c.typeDecoder)
+	if err != nil {
+		w.Error(verror.Convert(verror.ErrInternal, ctx, fmt.Errorf("Error decoding hex string %q: %v", data, err)))
+		return
+	}
+	var msg RpcRequest
+	if err := decoder.Decode(&msg); err != nil {
+		w.Error(verror.Convert(verror.ErrInternal, ctx, err))
+		return
+	}
+	ctx.VI(2).Infof("Rpc: %s.%s(..., streaming=%v)", msg.Name, msg.Method, msg.IsStreaming)
+	spanName := fmt.Sprintf("<wspr>%q.%s", msg.Name, msg.Method)
+	ctx, span := vtrace.WithContinuedTrace(ctx, spanName, msg.TraceRequest)
+	ctx = i18n.WithLangID(ctx, i18n.LangID(msg.Context.Language))
+
+	var cctx *context.T
+	var cancel context.CancelFunc
+
+	// TODO(mattr): To be consistent with go, we should not ignore 0 timeouts.
+	// However as a rollout strategy we must, otherwise there is a circular
+	// dependency between the WSPR change and the JS change that will follow.
+	if msg.Deadline.IsZero() {
+		cctx, cancel = context.WithCancel(ctx)
+	} else {
+		cctx, cancel = context.WithDeadline(ctx, msg.Deadline.Time)
+	}
+
+	// If this message is for an internal service, do a short-circuit dispatch here.
+	if invoker, ok := c.reservedServices[msg.Name]; ok {
+		go c.handleInternalCall(ctx, invoker, &msg, w, span, decoder)
+		return
+	}
+
+	inArgs := make([]interface{}, msg.NumInArgs)
+	for i := range inArgs {
+		var v *vdl.Value
+		if err := decoder.Decode(&v); err != nil {
+			w.Error(err)
+			return
+		}
+		inArgs[i] = v
+	}
+
+	request := &outstandingRequest{
+		cancel: cancel,
+	}
+	if msg.IsStreaming {
+		// If this rpc is streaming, we would expect that the client would try to send
+		// on this stream.  Since the initial handshake is done asynchronously, we have
+		// to put the outstanding stream in the map before we make the async call so that
+		// the future send know which queue to write to, even if the client call isn't
+		// actually ready yet.
+		request.stream = newStream(c.typeDecoder)
+	}
+	c.Lock()
+	c.outstandingRequests[id] = request
+	go c.sendVeyronRequest(cctx, id, &msg, inArgs, w, request.stream, span)
+	c.Unlock()
+}
+
+// HandleVeyronCancellation cancels the request corresponding to the
+// given id if it is still outstanding.
+func (c *Controller) HandleVeyronCancellation(ctx *context.T, id int32) {
+	c.Lock()
+	defer c.Unlock()
+	if request, ok := c.outstandingRequests[id]; ok && request.cancel != nil {
+		request.cancel()
+	}
+}
+
+// CloseStream closes the stream for a given id.
+func (c *Controller) CloseStream(ctx *context.T, id int32) {
+	c.Lock()
+	defer c.Unlock()
+	if request, ok := c.outstandingRequests[id]; ok && request.stream != nil {
+		request.stream.end()
+		return
+	}
+	ctx.Errorf("close called on non-existent call: %v", id)
+}
+
+func (c *Controller) maybeCreateServer(serverId uint32, opts ...rpc.ServerOpt) (*server.Server, error) {
+	c.Lock()
+	defer c.Unlock()
+	if server, ok := c.servers[serverId]; ok {
+		return server, nil
+	}
+	server, err := server.NewServer(serverId, c.listenSpec, c, opts...)
+	if err != nil {
+		return nil, err
+	}
+	c.servers[serverId] = server
+	return server, nil
+}
+
+// HandleLookupResponse handles the result of a Dispatcher.Lookup call that was
+// run by the Javascript server.
+func (c *Controller) HandleLookupResponse(ctx *context.T, id int32, data string) {
+	c.Lock()
+	server, ok := c.flowMap[id].(*server.Server)
+	c.Unlock()
+	if !ok {
+		ctx.Errorf("unexpected result from JavaScript. No channel "+
+			"for MessageId: %d exists. Ignoring the results.", id)
+		//Ignore unknown responses that don't belong to any channel
+		return
+	}
+	server.HandleLookupResponse(ctx, id, data)
+}
+
+// HandleAuthResponse handles the result of a Authorizer.Authorize call that was
+// run by the Javascript server.
+func (c *Controller) HandleAuthResponse(ctx *context.T, id int32, data string) {
+	c.Lock()
+	server, ok := c.flowMap[id].(*server.Server)
+	c.Unlock()
+	if !ok {
+		ctx.Errorf("unexpected result from JavaScript. No channel "+
+			"for MessageId: %d exists. Ignoring the results.", id)
+		//Ignore unknown responses that don't belong to any channel
+		return
+	}
+	server.HandleAuthResponse(ctx, id, data)
+}
+
+// Serve instructs WSPR to start listening for calls on behalf
+// of a javascript server.
+func (c *Controller) Serve(ctx *context.T, _ rpc.ServerCall, name string, serverId uint32, rpcServerOpts []RpcServerOption) error {
+
+	opts, err := c.serverOpts(rpcServerOpts)
+	if err != nil {
+		return verror.Convert(verror.ErrInternal, nil, err)
+	}
+	server, err := c.maybeCreateServer(serverId, opts...)
+	if err != nil {
+		return verror.Convert(verror.ErrInternal, nil, err)
+	}
+	ctx.VI(2).Infof("serving under name: %q", name)
+	if err := server.Serve(name); err != nil {
+		return verror.Convert(verror.ErrInternal, nil, err)
+	}
+	return nil
+}
+
+// Stop instructs WSPR to stop listening for calls for the
+// given javascript server.
+func (c *Controller) Stop(_ *context.T, _ rpc.ServerCall, serverId uint32) error {
+	c.Lock()
+	server, ok := c.servers[serverId]
+	if !ok {
+		c.Unlock()
+		return nil
+	}
+	delete(c.servers, serverId)
+	c.Unlock()
+
+	server.Stop()
+	return nil
+}
+
+// AddName adds a published name to an existing server.
+func (c *Controller) AddName(_ *context.T, _ rpc.ServerCall, serverId uint32, name string) error {
+	// Create a server for the pipe, if it does not exist already
+	server, err := c.maybeCreateServer(serverId)
+	if err != nil {
+		return verror.Convert(verror.ErrInternal, nil, err)
+	}
+	// Add name
+	if err := server.AddName(name); err != nil {
+		return verror.Convert(verror.ErrInternal, nil, err)
+	}
+	return nil
+}
+
+// RemoveName removes a published name from an existing server.
+func (c *Controller) RemoveName(_ *context.T, _ rpc.ServerCall, serverId uint32, name string) error {
+	// Create a server for the pipe, if it does not exist already
+	server, err := c.maybeCreateServer(serverId)
+	if err != nil {
+		return verror.Convert(verror.ErrInternal, nil, err)
+	}
+	// Remove name
+	server.RemoveName(name)
+	// Remove name from signature cache as well
+	c.signatureManager.FlushCacheEntry(name)
+	return nil
+}
+
+// HandleServerResponse handles the completion of outstanding calls to JavaScript services
+// by filling the corresponding channel with the result from JavaScript.
+func (c *Controller) HandleServerResponse(ctx *context.T, id int32, data string) {
+	c.Lock()
+	server, ok := c.flowMap[id].(*server.Server)
+	c.Unlock()
+	if !ok {
+		ctx.Errorf("unexpected result from JavaScript. No channel "+
+			"for MessageId: %d exists. Ignoring the results.", id)
+		//Ignore unknown responses that don't belong to any channel
+		return
+	}
+	server.HandleServerResponse(ctx, id, data)
+}
+
+// getSignature uses the signature manager to get and cache the signature of a remote server.
+func (c *Controller) getSignature(ctx *context.T, name string) ([]signature.Interface, error) {
+	return c.signatureManager.Signature(ctx, name)
+}
+
+// Signature uses the signature manager to get and cache the signature of a remote server.
+func (c *Controller) Signature(ctx *context.T, _ rpc.ServerCall, name string) ([]signature.Interface, error) {
+	return c.getSignature(ctx, name)
+}
+
+// Bless binds extensions of blessings held by this principal to
+// another principal (represented by its public key).
+func (c *Controller) Bless(_ *context.T, _ rpc.ServerCall, publicKey []byte, inputBlessings security.Blessings, extension string, caveats []security.Caveat) (principal.BlessingsId, error) {
+	key, err := security.UnmarshalPublicKey(publicKey)
+	if err != nil {
+		return 0, err
+	}
+
+	if len(caveats) == 0 {
+		caveats = append(caveats, security.UnconstrainedUse())
+	}
+
+	p := v23.GetPrincipal(c.ctx)
+	blessings, err := p.Bless(key, inputBlessings, extension, caveats[0], caveats[1:]...)
+	if err != nil {
+		return 0, err
+	}
+	return c.blessingsCache.Put(blessings), nil
+}
+
+// BlessSelf creates a blessing with the provided name for this principal.
+func (c *Controller) BlessSelf(_ *context.T, _ rpc.ServerCall, extension string, caveats []security.Caveat) (principal.BlessingsId, error) {
+	p := v23.GetPrincipal(c.ctx)
+	blessings, err := p.BlessSelf(extension, caveats...)
+	if err != nil {
+		return 0, verror.Convert(verror.ErrInternal, nil, err)
+	}
+
+	return c.blessingsCache.Put(blessings), err
+}
+
+// BlessingStoreSet puts the specified blessing in the blessing store under the
+// provided pattern.
+func (c *Controller) BlessingStoreSet(_ *context.T, _ rpc.ServerCall, inputBlessings security.Blessings, pattern security.BlessingPattern) (principal.BlessingsId, error) {
+	p := v23.GetPrincipal(c.ctx)
+	outBlessings, err := p.BlessingStore().Set(inputBlessings, security.BlessingPattern(pattern))
+	if err != nil {
+		return 0, err
+	}
+
+	if outBlessings.IsZero() {
+		return 0, nil
+	}
+
+	return c.blessingsCache.Put(outBlessings), nil
+}
+
+// BlessingStoreForPeer puts the specified blessing in the blessing store under the
+// provided pattern.
+func (c *Controller) BlessingStoreForPeer(_ *context.T, _ rpc.ServerCall, peerBlessings []string) (principal.BlessingsId, error) {
+	p := v23.GetPrincipal(c.ctx)
+	outBlessings := p.BlessingStore().ForPeer(peerBlessings...)
+
+	if outBlessings.IsZero() {
+		return 0, nil
+	}
+
+	return c.blessingsCache.Put(outBlessings), nil
+}
+
+// BlessingStoreSetDefault sets the default blessings in the blessing store.
+func (c *Controller) BlessingStoreSetDefault(_ *context.T, _ rpc.ServerCall, inputBlessings security.Blessings) error {
+	p := v23.GetPrincipal(c.ctx)
+	return p.BlessingStore().SetDefault(inputBlessings)
+}
+
+// BlessingStoreDefault fetches the default blessings for the principal of the controller.
+func (c *Controller) BlessingStoreDefault(*context.T, rpc.ServerCall) (principal.BlessingsId, error) {
+	p := v23.GetPrincipal(c.ctx)
+	outBlessings := p.BlessingStore().Default()
+
+	if outBlessings.IsZero() {
+		return 0, nil
+	}
+
+	return c.blessingsCache.Put(outBlessings), nil
+}
+
+// BlessingStorePublicKey fetches the public key used by the principal associated with the blessing store.
+func (c *Controller) BlessingStorePublicKey(*context.T, rpc.ServerCall) ([]byte, error) {
+	p := v23.GetPrincipal(c.ctx)
+	return p.BlessingStore().PublicKey().MarshalBinary()
+}
+
+// BlessingStorePeerBlessings returns all the blessings that the BlessingStore holds.
+func (c *Controller) BlessingStorePeerBlessings(*context.T, rpc.ServerCall) (map[security.BlessingPattern]principal.BlessingsId, error) {
+	p := v23.GetPrincipal(c.ctx)
+	outBlessingsMap := map[security.BlessingPattern]principal.BlessingsId{}
+	for pattern, blessings := range p.BlessingStore().PeerBlessings() {
+		outBlessingsMap[pattern] = c.blessingsCache.Put(blessings)
+	}
+	return outBlessingsMap, nil
+}
+
+// BlessingStoreDebugString retrieves a debug string describing the state of the blessing store
+func (c *Controller) BlessingStoreDebugString(*context.T, rpc.ServerCall) (string, error) {
+	p := v23.GetPrincipal(c.ctx)
+	ds := p.BlessingStore().DebugString()
+	return ds, nil
+}
+
+// AddToRoots adds the provided blessing as a root.
+func (c *Controller) AddToRoots(_ *context.T, _ rpc.ServerCall, inputBlessings security.Blessings) error {
+	p := v23.GetPrincipal(c.ctx)
+	return p.AddToRoots(inputBlessings)
+}
+
+// HandleGranterResponse handles the result of a Granter request.
+func (c *Controller) HandleGranterResponse(ctx *context.T, id int32, data string) {
+	c.Lock()
+	granterStr, ok := c.flowMap[id].(*granterStream)
+	c.Unlock()
+	if !ok {
+		ctx.Errorf("unexpected result from JavaScript. Flow was not a granter "+
+			"stream for MessageId: %d exists. Ignoring the results.", id)
+		//Ignore unknown responses that don't belong to any channel
+		return
+	}
+	granterStr.Send(data)
+}
+
+func (c *Controller) HandleTypeMessage(ctx *context.T, data string) {
+	c.typeReader.Add(data)
+}
+
+func (c *Controller) RemoteBlessings(ctx *context.T, _ rpc.ServerCall, name, method string) ([]string, error) {
+	ctx.VI(2).Infof("requesting remote blessings for %q", name)
+
+	cctx, cancel := context.WithTimeout(ctx, 5*time.Second)
+	defer cancel()
+
+	clientCall, err := v23.GetClient(cctx).StartCall(cctx, name, method, nil)
+	if err != nil {
+		return nil, verror.Convert(verror.ErrInternal, cctx, err)
+	}
+
+	blessings, _ := clientCall.RemoteBlessings()
+	return blessings, nil
+}
+
+func (c *Controller) SendLogMessage(level lib.LogLevel, msg string) error {
+	c.Lock()
+	defer c.Unlock()
+	id := c.lastGeneratedId
+	c.lastGeneratedId += 2
+	return c.writerCreator(id).Send(lib.ResponseLog, lib.LogMessage{
+		Level:   level,
+		Message: msg,
+	})
+}
+
+func (c *Controller) SendBlessingsCacheMessages(messages []principal.BlessingsCacheMessage) {
+	c.Lock()
+	defer c.Unlock()
+	id := c.lastGeneratedId
+	c.lastGeneratedId += 2
+	if err := c.writerCreator(id).Send(lib.ResponseBlessingsCacheMessage, messages); err != nil {
+		c.ctx.Errorf("unexpected error sending blessings cache message: %v", err)
+	}
+}
+
+func (c *Controller) TypeEncoder() *vom.TypeEncoder {
+	return c.typeEncoder
+}
+
+func (c *Controller) TypeDecoder() *vom.TypeDecoder {
+	return c.typeDecoder
+}
diff --git a/services/wspr/internal/app/app.vdl b/services/wspr/internal/app/app.vdl
new file mode 100644
index 0000000..e274ad2
--- /dev/null
+++ b/services/wspr/internal/app/app.vdl
@@ -0,0 +1,68 @@
+// 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.
+
+// The app package contains the struct that keeps per javascript app state and handles translating
+// javascript requests to vanadium requests and vice versa.
+package app
+
+import (
+	"time"
+
+	"v.io/v23/security"
+	"v.io/v23/vtrace"
+	"v.io/x/ref/services/wspr/internal/rpc/server"
+)
+
+type RpcRequest struct {
+	Name         string
+	Method       string
+	NumInArgs    int32
+	NumOutArgs   int32
+	IsStreaming  bool
+	Deadline     time.WireDeadline
+	TraceRequest vtrace.Request
+        Context      server.Context
+	CallOptions  []RpcCallOption
+}
+
+// TODO(nlacasse): It would be nice if RpcCallOption and RpcServerOption
+// were a struct with optional types, like:
+//
+//   type RpcCallOptions struct {
+//     AllowedServersPolicy ?[]security.BlessingPattern
+//     RetryTimeout         ?time.Duration
+//   }
+//
+// Unfortunately, optional types are currently only supported for structs, not
+// slices or primitive types.  Once optional types are better supported, switch
+// this to a struct with optional types.
+// Waiting for v.io/i/213
+
+type RpcCallOption union {
+	AllowedServersPolicy []security.BlessingPattern
+	RetryTimeout         time.Duration
+	Granter GranterHandle
+}
+
+type RpcServerOption union {
+  IsLeaf bool
+  ServesMountTable bool
+}
+
+type RpcResponse struct {
+	OutArgs       []any
+	TraceResponse vtrace.Response
+}
+
+type GranterHandle int32
+
+type GranterRequest struct {
+       GranterHandle GranterHandle
+       Call server.SecurityCall
+}
+
+type GranterResponse struct {
+       Blessings security.WireBlessings
+       Err error
+}
diff --git a/services/wspr/internal/app/app.vdl.go b/services/wspr/internal/app/app.vdl.go
new file mode 100644
index 0000000..ba0a40b
--- /dev/null
+++ b/services/wspr/internal/app/app.vdl.go
@@ -0,0 +1,168 @@
+// 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 by the vanadium vdl tool.
+// Source: app.vdl
+
+// The app package contains the struct that keeps per javascript app state and handles translating
+// javascript requests to vanadium requests and vice versa.
+package app
+
+import (
+	// VDL system imports
+	"v.io/v23/vdl"
+
+	// VDL user imports
+	"time"
+	"v.io/v23/security"
+	time_2 "v.io/v23/vdlroot/time"
+	"v.io/v23/vtrace"
+	"v.io/x/ref/services/wspr/internal/rpc/server"
+)
+
+type RpcRequest struct {
+	Name         string
+	Method       string
+	NumInArgs    int32
+	NumOutArgs   int32
+	IsStreaming  bool
+	Deadline     time_2.Deadline
+	TraceRequest vtrace.Request
+	Context      server.Context
+	CallOptions  []RpcCallOption
+}
+
+func (RpcRequest) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/services/wspr/internal/app.RpcRequest"`
+}) {
+}
+
+type (
+	// RpcCallOption represents any single field of the RpcCallOption union type.
+	RpcCallOption interface {
+		// Index returns the field index.
+		Index() int
+		// Interface returns the field value as an interface.
+		Interface() interface{}
+		// Name returns the field name.
+		Name() string
+		// __VDLReflect describes the RpcCallOption union type.
+		__VDLReflect(__RpcCallOptionReflect)
+	}
+	// RpcCallOptionAllowedServersPolicy represents field AllowedServersPolicy of the RpcCallOption union type.
+	RpcCallOptionAllowedServersPolicy struct{ Value []security.BlessingPattern }
+	// RpcCallOptionRetryTimeout represents field RetryTimeout of the RpcCallOption union type.
+	RpcCallOptionRetryTimeout struct{ Value time.Duration }
+	// RpcCallOptionGranter represents field Granter of the RpcCallOption union type.
+	RpcCallOptionGranter struct{ Value GranterHandle }
+	// __RpcCallOptionReflect describes the RpcCallOption union type.
+	__RpcCallOptionReflect struct {
+		Name  string `vdl:"v.io/x/ref/services/wspr/internal/app.RpcCallOption"`
+		Type  RpcCallOption
+		Union struct {
+			AllowedServersPolicy RpcCallOptionAllowedServersPolicy
+			RetryTimeout         RpcCallOptionRetryTimeout
+			Granter              RpcCallOptionGranter
+		}
+	}
+)
+
+func (x RpcCallOptionAllowedServersPolicy) Index() int                          { return 0 }
+func (x RpcCallOptionAllowedServersPolicy) Interface() interface{}              { return x.Value }
+func (x RpcCallOptionAllowedServersPolicy) Name() string                        { return "AllowedServersPolicy" }
+func (x RpcCallOptionAllowedServersPolicy) __VDLReflect(__RpcCallOptionReflect) {}
+
+func (x RpcCallOptionRetryTimeout) Index() int                          { return 1 }
+func (x RpcCallOptionRetryTimeout) Interface() interface{}              { return x.Value }
+func (x RpcCallOptionRetryTimeout) Name() string                        { return "RetryTimeout" }
+func (x RpcCallOptionRetryTimeout) __VDLReflect(__RpcCallOptionReflect) {}
+
+func (x RpcCallOptionGranter) Index() int                          { return 2 }
+func (x RpcCallOptionGranter) Interface() interface{}              { return x.Value }
+func (x RpcCallOptionGranter) Name() string                        { return "Granter" }
+func (x RpcCallOptionGranter) __VDLReflect(__RpcCallOptionReflect) {}
+
+type (
+	// RpcServerOption represents any single field of the RpcServerOption union type.
+	RpcServerOption interface {
+		// Index returns the field index.
+		Index() int
+		// Interface returns the field value as an interface.
+		Interface() interface{}
+		// Name returns the field name.
+		Name() string
+		// __VDLReflect describes the RpcServerOption union type.
+		__VDLReflect(__RpcServerOptionReflect)
+	}
+	// RpcServerOptionIsLeaf represents field IsLeaf of the RpcServerOption union type.
+	RpcServerOptionIsLeaf struct{ Value bool }
+	// RpcServerOptionServesMountTable represents field ServesMountTable of the RpcServerOption union type.
+	RpcServerOptionServesMountTable struct{ Value bool }
+	// __RpcServerOptionReflect describes the RpcServerOption union type.
+	__RpcServerOptionReflect struct {
+		Name  string `vdl:"v.io/x/ref/services/wspr/internal/app.RpcServerOption"`
+		Type  RpcServerOption
+		Union struct {
+			IsLeaf           RpcServerOptionIsLeaf
+			ServesMountTable RpcServerOptionServesMountTable
+		}
+	}
+)
+
+func (x RpcServerOptionIsLeaf) Index() int                            { return 0 }
+func (x RpcServerOptionIsLeaf) Interface() interface{}                { return x.Value }
+func (x RpcServerOptionIsLeaf) Name() string                          { return "IsLeaf" }
+func (x RpcServerOptionIsLeaf) __VDLReflect(__RpcServerOptionReflect) {}
+
+func (x RpcServerOptionServesMountTable) Index() int                            { return 1 }
+func (x RpcServerOptionServesMountTable) Interface() interface{}                { return x.Value }
+func (x RpcServerOptionServesMountTable) Name() string                          { return "ServesMountTable" }
+func (x RpcServerOptionServesMountTable) __VDLReflect(__RpcServerOptionReflect) {}
+
+type RpcResponse struct {
+	OutArgs       []*vdl.Value
+	TraceResponse vtrace.Response
+}
+
+func (RpcResponse) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/services/wspr/internal/app.RpcResponse"`
+}) {
+}
+
+type GranterHandle int32
+
+func (GranterHandle) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/services/wspr/internal/app.GranterHandle"`
+}) {
+}
+
+type GranterRequest struct {
+	GranterHandle GranterHandle
+	Call          server.SecurityCall
+}
+
+func (GranterRequest) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/services/wspr/internal/app.GranterRequest"`
+}) {
+}
+
+type GranterResponse struct {
+	Blessings security.Blessings
+	Err       error
+}
+
+func (GranterResponse) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/services/wspr/internal/app.GranterResponse"`
+}) {
+}
+
+func init() {
+	vdl.Register((*RpcRequest)(nil))
+	vdl.Register((*RpcCallOption)(nil))
+	vdl.Register((*RpcServerOption)(nil))
+	vdl.Register((*RpcResponse)(nil))
+	vdl.Register((*GranterHandle)(nil))
+	vdl.Register((*GranterRequest)(nil))
+	vdl.Register((*GranterResponse)(nil))
+}
diff --git a/services/wspr/internal/app/app_test.go b/services/wspr/internal/app/app_test.go
new file mode 100644
index 0000000..11e1793
--- /dev/null
+++ b/services/wspr/internal/app/app_test.go
@@ -0,0 +1,589 @@
+// 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 app
+
+import (
+	"bytes"
+	"encoding/base64"
+	"encoding/hex"
+	"fmt"
+	"reflect"
+	"sync"
+	"testing"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/options"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/vdl"
+	"v.io/v23/vdlroot/signature"
+	vdltime "v.io/v23/vdlroot/time"
+	"v.io/v23/verror"
+	"v.io/v23/vom"
+	"v.io/v23/vtrace"
+	vsecurity "v.io/x/ref/lib/security"
+	"v.io/x/ref/lib/xrpc"
+	"v.io/x/ref/runtime/factories/generic"
+	"v.io/x/ref/services/mounttable/mounttablelib"
+	"v.io/x/ref/services/wspr/internal/lib"
+	"v.io/x/ref/services/wspr/internal/lib/testwriter"
+	"v.io/x/ref/services/wspr/internal/rpc/server"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/testutil"
+)
+
+//go:generate v23 test generate
+
+var testPrincipal = testutil.NewPrincipal("test")
+
+// newBlessedPrincipal returns a new principal that has a blessing from the
+// provided runtime's principal which is set on its BlessingStore such
+// that it is revealed to all clients and servers.
+func newBlessedPrincipal(ctx *context.T) security.Principal {
+	principal := v23.GetPrincipal(ctx)
+	p := testutil.NewPrincipal()
+	b, err := principal.Bless(p.PublicKey(), principal.BlessingStore().Default(), "delegate", security.UnconstrainedUse())
+	if err != nil {
+		panic(err)
+	}
+	if err := vsecurity.SetDefaultBlessings(p, b); err != nil {
+		panic(err)
+	}
+	return p
+}
+
+type simpleAdder struct{}
+
+func (s simpleAdder) Add(_ *context.T, _ rpc.ServerCall, a, b int32) (int32, error) {
+	return a + b, nil
+}
+
+func (s simpleAdder) Divide(_ *context.T, _ rpc.ServerCall, a, b int32) (int32, error) {
+	if b == 0 {
+		return 0, verror.New(verror.ErrBadArg, nil, "div 0")
+	}
+	return a / b, nil
+}
+
+func (s simpleAdder) StreamingAdd(_ *context.T, call rpc.StreamServerCall) (int32, error) {
+	total := int32(0)
+	var value int32
+	for err := call.Recv(&value); err == nil; err = call.Recv(&value) {
+		total += value
+		call.Send(total)
+	}
+	return total, nil
+}
+
+var simpleAddrSig = signature.Interface{
+	Doc: "The empty interface contains methods not attached to any interface.",
+	Methods: []signature.Method{
+		{
+			Name:    "Add",
+			InArgs:  []signature.Arg{{Type: vdl.Int32Type}, {Type: vdl.Int32Type}},
+			OutArgs: []signature.Arg{{Type: vdl.Int32Type}},
+		},
+		{
+			Name:    "Divide",
+			InArgs:  []signature.Arg{{Type: vdl.Int32Type}, {Type: vdl.Int32Type}},
+			OutArgs: []signature.Arg{{Type: vdl.Int32Type}},
+		},
+		{
+			Name:      "StreamingAdd",
+			OutArgs:   []signature.Arg{{Type: vdl.Int32Type}},
+			InStream:  &signature.Arg{Type: vdl.AnyType},
+			OutStream: &signature.Arg{Type: vdl.AnyType},
+		},
+	},
+}
+
+func createWriterCreator(w lib.ClientWriter) func(id int32) lib.ClientWriter {
+	return func(int32) lib.ClientWriter {
+		return w
+	}
+}
+func TestGetGoServerSignature(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	s, err := xrpc.NewServer(ctx, "", simpleAdder{}, nil)
+	if err != nil {
+		t.Fatalf("unable to start server: %v", err)
+	}
+	name := s.Status().Endpoints[0].Name()
+
+	spec := v23.GetListenSpec(ctx)
+	spec.Proxy = "mockVeyronProxyEP"
+	writer := &testwriter.Writer{}
+	controller, err := NewController(ctx, createWriterCreator(writer), &spec, nil, newBlessedPrincipal(ctx))
+
+	if err != nil {
+		t.Fatalf("Failed to create controller: %v", err)
+	}
+	sig, err := controller.getSignature(ctx, name)
+	if err != nil {
+		t.Fatalf("Failed to get signature: %v", err)
+	}
+	if got, want := len(sig), 2; got != want {
+		t.Fatalf("got signature %#v len %d, want %d", sig, got, want)
+	}
+	if got, want := sig[0], simpleAddrSig; !reflect.DeepEqual(got, want) {
+		t.Errorf("got sig[0] %#v, want: %#v", got, want)
+	}
+	if got, want := sig[1].Name, "__Reserved"; got != want {
+		t.Errorf("got sig[1].Name %#v, want: %#v", got, want)
+	}
+}
+
+type goServerTestCase struct {
+	expectedTypeStream []lib.Response
+	method             string
+	inArgs             []interface{}
+	numOutArgs         int32
+	streamingInputs    []interface{}
+	expectedStream     []lib.Response
+	expectedError      error
+}
+
+func runGoServerTestCase(t *testing.T, testCase goServerTestCase) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	s, err := xrpc.NewServer(ctx, "", simpleAdder{}, nil)
+	if err != nil {
+		t.Fatalf("unable to start server: %v", err)
+	}
+	name := s.Status().Endpoints[0].Name()
+
+	spec := v23.GetListenSpec(ctx)
+	spec.Proxy = "mockVeyronProxyEP"
+	writer := testwriter.Writer{}
+	controller, err := NewController(ctx, createWriterCreator(&writer), &spec, nil, newBlessedPrincipal(ctx))
+
+	if err != nil {
+		t.Errorf("unable to create controller: %v", err)
+		t.Fail()
+		return
+	}
+	var stream *outstandingStream
+	if len(testCase.streamingInputs) > 0 {
+		stream = newStream(nil)
+		controller.outstandingRequests[0] = &outstandingRequest{
+			stream: stream,
+		}
+		go func() {
+			for _, value := range testCase.streamingInputs {
+				controller.SendOnStream(ctx, 0, lib.HexVomEncodeOrDie(value, nil), &writer)
+			}
+			controller.CloseStream(ctx, 0)
+		}()
+	}
+
+	request := RpcRequest{
+		Name:        name,
+		Method:      testCase.method,
+		NumInArgs:   int32(len(testCase.inArgs)),
+		NumOutArgs:  testCase.numOutArgs,
+		IsStreaming: stream != nil,
+	}
+	controller.sendVeyronRequest(ctx, 0, &request, testCase.inArgs, &writer, stream, vtrace.GetSpan(ctx))
+
+	if err := testwriter.CheckResponses(&writer, testCase.expectedStream, testCase.expectedTypeStream, testCase.expectedError); err != nil {
+		t.Error(err)
+	}
+}
+
+type typeWriter struct {
+	resps []lib.Response
+}
+
+func (t *typeWriter) Write(p []byte) (int, error) {
+	t.resps = append(t.resps, lib.Response{
+		Type:    lib.ResponseTypeMessage,
+		Message: base64.StdEncoding.EncodeToString(p),
+	})
+	return len(p), nil
+}
+
+func makeRPCResponse(outArgs ...*vdl.Value) (string, []lib.Response) {
+	writer := typeWriter{}
+	typeEncoder := vom.NewTypeEncoder(&writer)
+	var buf bytes.Buffer
+	encoder := vom.NewEncoderWithTypeEncoder(&buf, typeEncoder)
+	var output = RpcResponse{
+		OutArgs:       outArgs,
+		TraceResponse: vtrace.Response{},
+	}
+	if err := encoder.Encode(output); err != nil {
+		panic(err)
+	}
+	return hex.EncodeToString(buf.Bytes()), writer.resps
+}
+
+func TestCallingGoServer(t *testing.T) {
+	resp, typeMessages := makeRPCResponse(vdl.Int32Value(5))
+	runGoServerTestCase(t, goServerTestCase{
+		expectedTypeStream: typeMessages,
+		method:             "Add",
+		inArgs:             []interface{}{2, 3},
+		numOutArgs:         1,
+		expectedStream: []lib.Response{
+			lib.Response{
+				Message: resp,
+				Type:    lib.ResponseFinal,
+			},
+		},
+	})
+}
+
+func TestCallingGoServerWithError(t *testing.T) {
+	runGoServerTestCase(t, goServerTestCase{
+		method:        "Divide",
+		inArgs:        []interface{}{1, 0},
+		numOutArgs:    1,
+		expectedError: verror.New(verror.ErrBadArg, nil, "div 0"),
+	})
+}
+
+func TestCallingGoWithStreaming(t *testing.T) {
+	resp, typeMessages := makeRPCResponse(vdl.Int32Value(10))
+	runGoServerTestCase(t, goServerTestCase{
+		expectedTypeStream: typeMessages,
+		method:             "StreamingAdd",
+		streamingInputs:    []interface{}{1, 2, 3, 4},
+		numOutArgs:         1,
+		expectedStream: []lib.Response{
+			lib.Response{
+				Message: lib.HexVomEncodeOrDie(int32(1), nil),
+				Type:    lib.ResponseStream,
+			},
+			lib.Response{
+				Message: lib.HexVomEncodeOrDie(int32(3), nil),
+				Type:    lib.ResponseStream,
+			},
+			lib.Response{
+				Message: lib.HexVomEncodeOrDie(int32(6), nil),
+				Type:    lib.ResponseStream,
+			},
+			lib.Response{
+				Message: lib.HexVomEncodeOrDie(int32(10), nil),
+				Type:    lib.ResponseStream,
+			},
+			lib.Response{
+				Message: nil,
+				Type:    lib.ResponseStreamClose,
+			},
+			lib.Response{
+				Message: resp,
+				Type:    lib.ResponseFinal,
+			},
+		},
+	})
+}
+
+type runningTest struct {
+	controller    *Controller
+	writer        *testwriter.Writer
+	proxyShutdown func()
+	typeEncoder   *vom.TypeEncoder
+}
+
+func makeRequest(typeEncoder *vom.TypeEncoder, rpc RpcRequest, args ...interface{}) (string, error) {
+	var buf bytes.Buffer
+	encoder := vom.NewEncoderWithTypeEncoder(&buf, typeEncoder)
+	if err := encoder.Encode(rpc); err != nil {
+		return "", err
+	}
+	for _, arg := range args {
+		if err := encoder.Encode(arg); err != nil {
+			return "", err
+		}
+	}
+	return hex.EncodeToString(buf.Bytes()), nil
+}
+
+type typeEncoderWriter struct {
+	c   *Controller
+	ctx *context.T
+}
+
+func (t *typeEncoderWriter) Write(p []byte) (int, error) {
+	t.c.HandleTypeMessage(t.ctx, hex.EncodeToString(p))
+	return len(p), nil
+}
+
+func serveServer(ctx *context.T, writer lib.ClientWriter, setController func(*Controller)) (*runningTest, error) {
+	mt, err := mounttablelib.NewMountTableDispatcher(ctx, "", "", "mounttable")
+	if err != nil {
+		return nil, fmt.Errorf("unable to start mounttable: %v", err)
+	}
+	s, err := xrpc.NewDispatchingServer(ctx, "", mt, options.ServesMountTable(true))
+	if err != nil {
+		return nil, fmt.Errorf("unable to start mounttable: %v", err)
+	}
+	mtName := s.Status().Endpoints[0].Name()
+
+	proxySpec := rpc.ListenSpec{
+		Addrs: rpc.ListenAddrs{
+			// This '0' label is required by go vet.
+			// TODO(suharshs): Remove the '0' label once []ListenAddr is used
+			// instead of ListenAdders.
+			0: {
+				Protocol: "tcp",
+				Address:  "127.0.0.1:0",
+			},
+		},
+	}
+	proxyShutdown, proxyEndpoint, err := generic.NewProxy(ctx, proxySpec, security.AllowEveryone())
+	if err != nil {
+		return nil, fmt.Errorf("unable to start proxy: %v", err)
+	}
+
+	writerCreator := func(int32) lib.ClientWriter {
+		return writer
+	}
+	spec := v23.GetListenSpec(ctx)
+	spec.Proxy = proxyEndpoint.Name()
+	controller, err := NewController(ctx, writerCreator, &spec, nil, testPrincipal)
+	if err != nil {
+		return nil, err
+	}
+
+	if setController != nil {
+		setController(controller)
+	}
+
+	v23.GetNamespace(controller.Context()).SetRoots(mtName)
+	typeStream := &typeEncoderWriter{c: controller, ctx: controller.Context()}
+	typeEncoder := vom.NewTypeEncoder(typeStream)
+	req, err := makeRequest(typeEncoder, RpcRequest{
+		Name:       "__controller",
+		Method:     "Serve",
+		NumInArgs:  3,
+		NumOutArgs: 1,
+		Deadline:   vdltime.Deadline{},
+	}, "adder", 0, []RpcServerOption{})
+
+	controller.HandleVeyronRequest(ctx, 0, req, writer)
+
+	testWriter, _ := writer.(*testwriter.Writer)
+	return &runningTest{
+		controller, testWriter, proxyShutdown,
+		typeEncoder,
+	}, nil
+}
+
+// A test case to simulate a Javascript server talking to the App.  All the
+// responses from Javascript are mocked and sent back through the method calls.
+// All messages from the client are sent using a go client.
+type jsServerTestCase struct {
+	method string
+	inArgs []interface{}
+	// The set of streaming inputs from the client to the server.
+	// This is passed to the client, which then passes it to the app.
+	clientStream []interface{}
+	// The set of JSON streaming messages sent from Javascript to the
+	// app.
+	serverStream []interface{}
+	// The final response sent by the Javascript server to the
+	// app.
+	finalResponse *vdl.Value
+	// The final error sent by the Javascript server to the app.
+	err error
+
+	// Whether or not the Javascript server has an authorizer or not.
+	// If it does have an authorizer, then err is sent back from the server
+	// to the app.
+	hasAuthorizer bool
+}
+
+func runJsServerTestCase(t *testing.T, testCase jsServerTestCase) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	vomClientStream := []string{}
+	for _, m := range testCase.clientStream {
+		vomClientStream = append(vomClientStream, lib.HexVomEncodeOrDie(m, nil))
+	}
+	mock := &mockJSServer{
+		t:                    t,
+		method:               testCase.method,
+		serviceSignature:     []signature.Interface{simpleAddrSig},
+		expectedClientStream: vomClientStream,
+		serverStream:         testCase.serverStream,
+		hasAuthorizer:        testCase.hasAuthorizer,
+		inArgs:               testCase.inArgs,
+		finalResponse:        testCase.finalResponse,
+		finalError:           testCase.err,
+		controllerReady:      sync.RWMutex{},
+		flowCount:            2,
+		typeReader:           lib.NewTypeReader(),
+		ctx:                  ctx,
+	}
+	mock.typeDecoder = vom.NewTypeDecoder(mock.typeReader)
+	rt, err := serveServer(ctx, mock, func(controller *Controller) {
+		mock.controller = controller
+	})
+
+	mock.typeEncoder = rt.typeEncoder
+	defer rt.proxyShutdown()
+	defer rt.controller.Cleanup(ctx)
+
+	if err != nil {
+		t.Fatalf("could not serve server %v", err)
+	}
+
+	// Get the client that is relevant to the controller so it talks
+	// to the right mounttable.
+	client := v23.GetClient(rt.controller.Context())
+	// And have the client recognize the server, otherwise it won't
+	// authorize calls to it.
+	v23.GetPrincipal(rt.controller.Context()).AddToRoots(v23.GetPrincipal(ctx).BlessingStore().Default())
+
+	if err != nil {
+		t.Fatalf("unable to create client: %v", err)
+	}
+
+	call, err := client.StartCall(rt.controller.Context(), "adder/adder", testCase.method, testCase.inArgs)
+	if err != nil {
+		t.Fatalf("failed to start call: %v", err)
+	}
+
+	for _, msg := range testCase.clientStream {
+		if err := call.Send(msg); err != nil {
+			t.Errorf("unexpected error while sending %v: %v", msg, err)
+		}
+	}
+	if err := call.CloseSend(); err != nil {
+		t.Errorf("unexpected error on close: %v", err)
+	}
+
+	expectedStream := testCase.serverStream
+	for {
+		var data interface{}
+		if err := call.Recv(&data); err != nil {
+			break
+		}
+		if len(expectedStream) == 0 {
+			t.Errorf("unexpected stream value: %v", data)
+			continue
+		}
+		if !reflect.DeepEqual(data, expectedStream[0]) {
+			t.Errorf("unexpected stream value: got %v, expected %v", data, expectedStream[0])
+		}
+		expectedStream = expectedStream[1:]
+	}
+
+	var result *vdl.Value
+	err = call.Finish(&result)
+
+	if verror.ErrorID(err) != verror.ErrorID(testCase.err) {
+		t.Errorf("unexpected err: got %#v, expected %#v", err, testCase.err)
+	}
+
+	if err != nil {
+		return
+	}
+
+	if got, want := result, testCase.finalResponse; !vdl.EqualValue(got, want) {
+		t.Errorf("unexected final response: got %v, want %v", got, want)
+	}
+
+	// ensure there is only one server and then stop the server
+	if len(rt.controller.servers) != 1 {
+		t.Errorf("expected only one server but got: %d", len(rt.controller.servers))
+		return
+	}
+	for serverId := range rt.controller.servers {
+		rt.controller.Stop(nil, nil, serverId)
+	}
+
+	// ensure there is no more servers now
+	if len(rt.controller.servers) != 0 {
+		t.Errorf("expected no server after stopping the only one but got: %d", len(rt.controller.servers))
+		return
+	}
+}
+
+func TestSimpleJSServer(t *testing.T) {
+	runJsServerTestCase(t, jsServerTestCase{
+		method:        "Add",
+		inArgs:        []interface{}{int32(1), int32(2)},
+		finalResponse: vdl.Int32Value(3),
+	})
+}
+
+func TestJSServerWithAuthorizer(t *testing.T) {
+	runJsServerTestCase(t, jsServerTestCase{
+		method:        "Add",
+		inArgs:        []interface{}{int32(1), int32(2)},
+		finalResponse: vdl.Int32Value(3),
+		hasAuthorizer: true,
+	})
+}
+
+func TestJSServerWithError(t *testing.T) {
+	err := verror.New(verror.ErrInternal, nil)
+	runJsServerTestCase(t, jsServerTestCase{
+		method: "Divide",
+		inArgs: []interface{}{int32(1), int32(0)},
+		err:    err,
+	})
+}
+
+func TestJSServerWithAuthorizerAndAuthError(t *testing.T) {
+	err := verror.New(verror.ErrNoAccess, nil)
+	runJsServerTestCase(t, jsServerTestCase{
+		method:        "Add",
+		inArgs:        []interface{}{int32(1), int32(2)},
+		hasAuthorizer: true,
+		finalResponse: vdl.Int32Value(3),
+		err:           err,
+	})
+}
+func TestJSServerWihStreamingInputs(t *testing.T) {
+	runJsServerTestCase(t, jsServerTestCase{
+		method:        "StreamingAdd",
+		clientStream:  []interface{}{int32(3), int32(4)},
+		finalResponse: vdl.Int32Value(10),
+	})
+}
+
+func TestJSServerWihStreamingOutputs(t *testing.T) {
+	runJsServerTestCase(t, jsServerTestCase{
+		method:        "StreamingAdd",
+		serverStream:  []interface{}{int32(3), int32(4)},
+		finalResponse: vdl.Int32Value(10),
+	})
+}
+
+func TestJSServerWihStreamingInputsAndOutputs(t *testing.T) {
+	runJsServerTestCase(t, jsServerTestCase{
+		method:        "StreamingAdd",
+		clientStream:  []interface{}{int32(1), int32(2)},
+		serverStream:  []interface{}{int32(3), int32(4)},
+		finalResponse: vdl.Int32Value(10),
+	})
+}
+
+func TestJSServerWithWrongNumberOfArgs(t *testing.T) {
+	err := verror.New(server.ErrWrongNumberOfArgs, nil, "Add", 3, 2)
+	runJsServerTestCase(t, jsServerTestCase{
+		method: "Add",
+		inArgs: []interface{}{int32(1), int32(2), int32(3)},
+		err:    err,
+	})
+}
+
+func TestJSServerWithMethodNotFound(t *testing.T) {
+	methodName := "UnknownMethod"
+	err := verror.New(server.ErrMethodNotFoundInSignature, nil, methodName)
+	runJsServerTestCase(t, jsServerTestCase{
+		method: methodName,
+		inArgs: []interface{}{int32(1), int32(2)},
+		err:    err,
+	})
+}
diff --git a/services/wspr/internal/app/controller.vdl b/services/wspr/internal/app/controller.vdl
new file mode 100644
index 0000000..e464467
--- /dev/null
+++ b/services/wspr/internal/app/controller.vdl
@@ -0,0 +1,54 @@
+// 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 app
+
+import (
+	"signature"
+
+	"v.io/v23/security"
+	"v.io/x/ref/services/wspr/internal/principal"
+)
+
+type Controller interface {
+	// Serve instructs WSPR to start listening for calls on behalf
+	// of a javascript server.
+	Serve(name string, serverId uint32, serverOpts []RpcServerOption) error
+	// Stop instructs WSPR to stop listening for calls for the
+	// given javascript server.
+	Stop(serverId uint32) error
+	// AddName adds a published name to an existing server.
+	AddName(serverId uint32, name string) error
+	// RemoveName removes a published name from an existing server.
+	RemoveName(serverId uint32, name string) error
+
+	// Bless binds extensions of blessings held by this principal to
+	// another principal (represented by its public key).
+	Bless(publicKey []byte, blessings security.WireBlessings, extension string, caveat []security.Caveat) (principal.BlessingsId | error)
+	// BlessSelf creates a blessing with the provided name for this principal.
+	BlessSelf(name string, caveats []security.Caveat) (principal.BlessingsId | error)
+	// AddToRoots adds the provided blessing as a root.
+	AddToRoots(blessings security.WireBlessings) error
+
+	// BlessingStoreSet puts the specified blessing in the blessing store under the provided pattern.
+	BlessingStoreSet(blessingsblessings security.WireBlessings, pattern security.BlessingPattern) (principal.BlessingsId | error)
+	// BlessingStoreForPeer retrieves the blessings marked for the given peers.
+	BlessingStoreForPeer(peerBlessings []string) (principal.BlessingsId | error)
+	// BlessingStoreSetDefault sets the default blessings.
+	BlessingStoreSetDefault(blessingsblessings security.WireBlessings) error
+	// BlessingStoreDefault fetches the default blessings for the principal of the controller.
+	BlessingStoreDefault() (principal.BlessingsId | error)
+	// BlessingStorePublicKey fetches the public key of the principal for which this store hosts blessings.
+	BlessingStorePublicKey() ([]byte | error)
+	// BlessingStorePeerBlessings returns all the blessings that the BlessingStore holds.
+	BlessingStorePeerBlessings() (map[security.BlessingPattern]principal.BlessingsId | error)
+	// BlessingStoreDebugString retrieves a debug string describing the state of the blessing store
+	BlessingStoreDebugString() (string | error)
+
+
+	// RemoteBlessings fetches the remote blessings for a given name and method.
+	RemoteBlessings(name, method string) ([]string | error)
+	// Signature fetches the signature for a given name.
+	Signature(name string) ([]signature.Interface | error)
+}
diff --git a/services/wspr/internal/app/controller.vdl.go b/services/wspr/internal/app/controller.vdl.go
new file mode 100644
index 0000000..439110e
--- /dev/null
+++ b/services/wspr/internal/app/controller.vdl.go
@@ -0,0 +1,453 @@
+// 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 by the vanadium vdl tool.
+// Source: controller.vdl
+
+package app
+
+import (
+	// VDL system imports
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+
+	// VDL user imports
+	"v.io/v23/security"
+	"v.io/v23/vdlroot/signature"
+	"v.io/x/ref/services/wspr/internal/principal"
+)
+
+// ControllerClientMethods is the client interface
+// containing Controller methods.
+type ControllerClientMethods interface {
+	// Serve instructs WSPR to start listening for calls on behalf
+	// of a javascript server.
+	Serve(ctx *context.T, name string, serverId uint32, serverOpts []RpcServerOption, opts ...rpc.CallOpt) error
+	// Stop instructs WSPR to stop listening for calls for the
+	// given javascript server.
+	Stop(ctx *context.T, serverId uint32, opts ...rpc.CallOpt) error
+	// AddName adds a published name to an existing server.
+	AddName(ctx *context.T, serverId uint32, name string, opts ...rpc.CallOpt) error
+	// RemoveName removes a published name from an existing server.
+	RemoveName(ctx *context.T, serverId uint32, name string, opts ...rpc.CallOpt) error
+	// Bless binds extensions of blessings held by this principal to
+	// another principal (represented by its public key).
+	Bless(ctx *context.T, publicKey []byte, blessings security.Blessings, extension string, caveat []security.Caveat, opts ...rpc.CallOpt) (principal.BlessingsId, error)
+	// BlessSelf creates a blessing with the provided name for this principal.
+	BlessSelf(ctx *context.T, name string, caveats []security.Caveat, opts ...rpc.CallOpt) (principal.BlessingsId, error)
+	// AddToRoots adds the provided blessing as a root.
+	AddToRoots(ctx *context.T, blessings security.Blessings, opts ...rpc.CallOpt) error
+	// BlessingStoreSet puts the specified blessing in the blessing store under the provided pattern.
+	BlessingStoreSet(ctx *context.T, blessingsblessings security.Blessings, pattern security.BlessingPattern, opts ...rpc.CallOpt) (principal.BlessingsId, error)
+	// BlessingStoreForPeer retrieves the blessings marked for the given peers.
+	BlessingStoreForPeer(ctx *context.T, peerBlessings []string, opts ...rpc.CallOpt) (principal.BlessingsId, error)
+	// BlessingStoreSetDefault sets the default blessings.
+	BlessingStoreSetDefault(ctx *context.T, blessingsblessings security.Blessings, opts ...rpc.CallOpt) error
+	// BlessingStoreDefault fetches the default blessings for the principal of the controller.
+	BlessingStoreDefault(*context.T, ...rpc.CallOpt) (principal.BlessingsId, error)
+	// BlessingStorePublicKey fetches the public key of the principal for which this store hosts blessings.
+	BlessingStorePublicKey(*context.T, ...rpc.CallOpt) ([]byte, error)
+	// BlessingStorePeerBlessings returns all the blessings that the BlessingStore holds.
+	BlessingStorePeerBlessings(*context.T, ...rpc.CallOpt) (map[security.BlessingPattern]principal.BlessingsId, error)
+	// BlessingStoreDebugString retrieves a debug string describing the state of the blessing store
+	BlessingStoreDebugString(*context.T, ...rpc.CallOpt) (string, error)
+	// RemoteBlessings fetches the remote blessings for a given name and method.
+	RemoteBlessings(ctx *context.T, name string, method string, opts ...rpc.CallOpt) ([]string, error)
+	// Signature fetches the signature for a given name.
+	Signature(ctx *context.T, name string, opts ...rpc.CallOpt) ([]signature.Interface, error)
+}
+
+// ControllerClientStub adds universal methods to ControllerClientMethods.
+type ControllerClientStub interface {
+	ControllerClientMethods
+	rpc.UniversalServiceMethods
+}
+
+// ControllerClient returns a client stub for Controller.
+func ControllerClient(name string) ControllerClientStub {
+	return implControllerClientStub{name}
+}
+
+type implControllerClientStub struct {
+	name string
+}
+
+func (c implControllerClientStub) Serve(ctx *context.T, i0 string, i1 uint32, i2 []RpcServerOption, opts ...rpc.CallOpt) (err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "Serve", []interface{}{i0, i1, i2}, nil, opts...)
+	return
+}
+
+func (c implControllerClientStub) Stop(ctx *context.T, i0 uint32, opts ...rpc.CallOpt) (err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "Stop", []interface{}{i0}, nil, opts...)
+	return
+}
+
+func (c implControllerClientStub) AddName(ctx *context.T, i0 uint32, i1 string, opts ...rpc.CallOpt) (err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "AddName", []interface{}{i0, i1}, nil, opts...)
+	return
+}
+
+func (c implControllerClientStub) RemoveName(ctx *context.T, i0 uint32, i1 string, opts ...rpc.CallOpt) (err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "RemoveName", []interface{}{i0, i1}, nil, opts...)
+	return
+}
+
+func (c implControllerClientStub) Bless(ctx *context.T, i0 []byte, i1 security.Blessings, i2 string, i3 []security.Caveat, opts ...rpc.CallOpt) (o0 principal.BlessingsId, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "Bless", []interface{}{i0, i1, i2, i3}, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implControllerClientStub) BlessSelf(ctx *context.T, i0 string, i1 []security.Caveat, opts ...rpc.CallOpt) (o0 principal.BlessingsId, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "BlessSelf", []interface{}{i0, i1}, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implControllerClientStub) AddToRoots(ctx *context.T, i0 security.Blessings, opts ...rpc.CallOpt) (err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "AddToRoots", []interface{}{i0}, nil, opts...)
+	return
+}
+
+func (c implControllerClientStub) BlessingStoreSet(ctx *context.T, i0 security.Blessings, i1 security.BlessingPattern, opts ...rpc.CallOpt) (o0 principal.BlessingsId, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "BlessingStoreSet", []interface{}{i0, i1}, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implControllerClientStub) BlessingStoreForPeer(ctx *context.T, i0 []string, opts ...rpc.CallOpt) (o0 principal.BlessingsId, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "BlessingStoreForPeer", []interface{}{i0}, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implControllerClientStub) BlessingStoreSetDefault(ctx *context.T, i0 security.Blessings, opts ...rpc.CallOpt) (err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "BlessingStoreSetDefault", []interface{}{i0}, nil, opts...)
+	return
+}
+
+func (c implControllerClientStub) BlessingStoreDefault(ctx *context.T, opts ...rpc.CallOpt) (o0 principal.BlessingsId, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "BlessingStoreDefault", nil, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implControllerClientStub) BlessingStorePublicKey(ctx *context.T, opts ...rpc.CallOpt) (o0 []byte, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "BlessingStorePublicKey", nil, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implControllerClientStub) BlessingStorePeerBlessings(ctx *context.T, opts ...rpc.CallOpt) (o0 map[security.BlessingPattern]principal.BlessingsId, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "BlessingStorePeerBlessings", nil, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implControllerClientStub) BlessingStoreDebugString(ctx *context.T, opts ...rpc.CallOpt) (o0 string, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "BlessingStoreDebugString", nil, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implControllerClientStub) RemoteBlessings(ctx *context.T, i0 string, i1 string, opts ...rpc.CallOpt) (o0 []string, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "RemoteBlessings", []interface{}{i0, i1}, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implControllerClientStub) Signature(ctx *context.T, i0 string, opts ...rpc.CallOpt) (o0 []signature.Interface, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "Signature", []interface{}{i0}, []interface{}{&o0}, opts...)
+	return
+}
+
+// ControllerServerMethods is the interface a server writer
+// implements for Controller.
+type ControllerServerMethods interface {
+	// Serve instructs WSPR to start listening for calls on behalf
+	// of a javascript server.
+	Serve(ctx *context.T, call rpc.ServerCall, name string, serverId uint32, serverOpts []RpcServerOption) error
+	// Stop instructs WSPR to stop listening for calls for the
+	// given javascript server.
+	Stop(ctx *context.T, call rpc.ServerCall, serverId uint32) error
+	// AddName adds a published name to an existing server.
+	AddName(ctx *context.T, call rpc.ServerCall, serverId uint32, name string) error
+	// RemoveName removes a published name from an existing server.
+	RemoveName(ctx *context.T, call rpc.ServerCall, serverId uint32, name string) error
+	// Bless binds extensions of blessings held by this principal to
+	// another principal (represented by its public key).
+	Bless(ctx *context.T, call rpc.ServerCall, publicKey []byte, blessings security.Blessings, extension string, caveat []security.Caveat) (principal.BlessingsId, error)
+	// BlessSelf creates a blessing with the provided name for this principal.
+	BlessSelf(ctx *context.T, call rpc.ServerCall, name string, caveats []security.Caveat) (principal.BlessingsId, error)
+	// AddToRoots adds the provided blessing as a root.
+	AddToRoots(ctx *context.T, call rpc.ServerCall, blessings security.Blessings) error
+	// BlessingStoreSet puts the specified blessing in the blessing store under the provided pattern.
+	BlessingStoreSet(ctx *context.T, call rpc.ServerCall, blessingsblessings security.Blessings, pattern security.BlessingPattern) (principal.BlessingsId, error)
+	// BlessingStoreForPeer retrieves the blessings marked for the given peers.
+	BlessingStoreForPeer(ctx *context.T, call rpc.ServerCall, peerBlessings []string) (principal.BlessingsId, error)
+	// BlessingStoreSetDefault sets the default blessings.
+	BlessingStoreSetDefault(ctx *context.T, call rpc.ServerCall, blessingsblessings security.Blessings) error
+	// BlessingStoreDefault fetches the default blessings for the principal of the controller.
+	BlessingStoreDefault(*context.T, rpc.ServerCall) (principal.BlessingsId, error)
+	// BlessingStorePublicKey fetches the public key of the principal for which this store hosts blessings.
+	BlessingStorePublicKey(*context.T, rpc.ServerCall) ([]byte, error)
+	// BlessingStorePeerBlessings returns all the blessings that the BlessingStore holds.
+	BlessingStorePeerBlessings(*context.T, rpc.ServerCall) (map[security.BlessingPattern]principal.BlessingsId, error)
+	// BlessingStoreDebugString retrieves a debug string describing the state of the blessing store
+	BlessingStoreDebugString(*context.T, rpc.ServerCall) (string, error)
+	// RemoteBlessings fetches the remote blessings for a given name and method.
+	RemoteBlessings(ctx *context.T, call rpc.ServerCall, name string, method string) ([]string, error)
+	// Signature fetches the signature for a given name.
+	Signature(ctx *context.T, call rpc.ServerCall, name string) ([]signature.Interface, error)
+}
+
+// ControllerServerStubMethods is the server interface containing
+// Controller methods, as expected by rpc.Server.
+// There is no difference between this interface and ControllerServerMethods
+// since there are no streaming methods.
+type ControllerServerStubMethods ControllerServerMethods
+
+// ControllerServerStub adds universal methods to ControllerServerStubMethods.
+type ControllerServerStub interface {
+	ControllerServerStubMethods
+	// Describe the Controller interfaces.
+	Describe__() []rpc.InterfaceDesc
+}
+
+// ControllerServer returns a server stub for Controller.
+// It converts an implementation of ControllerServerMethods into
+// an object that may be used by rpc.Server.
+func ControllerServer(impl ControllerServerMethods) ControllerServerStub {
+	stub := implControllerServerStub{
+		impl: impl,
+	}
+	// Initialize GlobState; always check the stub itself first, to handle the
+	// case where the user has the Glob method defined in their VDL source.
+	if gs := rpc.NewGlobState(stub); gs != nil {
+		stub.gs = gs
+	} else if gs := rpc.NewGlobState(impl); gs != nil {
+		stub.gs = gs
+	}
+	return stub
+}
+
+type implControllerServerStub struct {
+	impl ControllerServerMethods
+	gs   *rpc.GlobState
+}
+
+func (s implControllerServerStub) Serve(ctx *context.T, call rpc.ServerCall, i0 string, i1 uint32, i2 []RpcServerOption) error {
+	return s.impl.Serve(ctx, call, i0, i1, i2)
+}
+
+func (s implControllerServerStub) Stop(ctx *context.T, call rpc.ServerCall, i0 uint32) error {
+	return s.impl.Stop(ctx, call, i0)
+}
+
+func (s implControllerServerStub) AddName(ctx *context.T, call rpc.ServerCall, i0 uint32, i1 string) error {
+	return s.impl.AddName(ctx, call, i0, i1)
+}
+
+func (s implControllerServerStub) RemoveName(ctx *context.T, call rpc.ServerCall, i0 uint32, i1 string) error {
+	return s.impl.RemoveName(ctx, call, i0, i1)
+}
+
+func (s implControllerServerStub) Bless(ctx *context.T, call rpc.ServerCall, i0 []byte, i1 security.Blessings, i2 string, i3 []security.Caveat) (principal.BlessingsId, error) {
+	return s.impl.Bless(ctx, call, i0, i1, i2, i3)
+}
+
+func (s implControllerServerStub) BlessSelf(ctx *context.T, call rpc.ServerCall, i0 string, i1 []security.Caveat) (principal.BlessingsId, error) {
+	return s.impl.BlessSelf(ctx, call, i0, i1)
+}
+
+func (s implControllerServerStub) AddToRoots(ctx *context.T, call rpc.ServerCall, i0 security.Blessings) error {
+	return s.impl.AddToRoots(ctx, call, i0)
+}
+
+func (s implControllerServerStub) BlessingStoreSet(ctx *context.T, call rpc.ServerCall, i0 security.Blessings, i1 security.BlessingPattern) (principal.BlessingsId, error) {
+	return s.impl.BlessingStoreSet(ctx, call, i0, i1)
+}
+
+func (s implControllerServerStub) BlessingStoreForPeer(ctx *context.T, call rpc.ServerCall, i0 []string) (principal.BlessingsId, error) {
+	return s.impl.BlessingStoreForPeer(ctx, call, i0)
+}
+
+func (s implControllerServerStub) BlessingStoreSetDefault(ctx *context.T, call rpc.ServerCall, i0 security.Blessings) error {
+	return s.impl.BlessingStoreSetDefault(ctx, call, i0)
+}
+
+func (s implControllerServerStub) BlessingStoreDefault(ctx *context.T, call rpc.ServerCall) (principal.BlessingsId, error) {
+	return s.impl.BlessingStoreDefault(ctx, call)
+}
+
+func (s implControllerServerStub) BlessingStorePublicKey(ctx *context.T, call rpc.ServerCall) ([]byte, error) {
+	return s.impl.BlessingStorePublicKey(ctx, call)
+}
+
+func (s implControllerServerStub) BlessingStorePeerBlessings(ctx *context.T, call rpc.ServerCall) (map[security.BlessingPattern]principal.BlessingsId, error) {
+	return s.impl.BlessingStorePeerBlessings(ctx, call)
+}
+
+func (s implControllerServerStub) BlessingStoreDebugString(ctx *context.T, call rpc.ServerCall) (string, error) {
+	return s.impl.BlessingStoreDebugString(ctx, call)
+}
+
+func (s implControllerServerStub) RemoteBlessings(ctx *context.T, call rpc.ServerCall, i0 string, i1 string) ([]string, error) {
+	return s.impl.RemoteBlessings(ctx, call, i0, i1)
+}
+
+func (s implControllerServerStub) Signature(ctx *context.T, call rpc.ServerCall, i0 string) ([]signature.Interface, error) {
+	return s.impl.Signature(ctx, call, i0)
+}
+
+func (s implControllerServerStub) Globber() *rpc.GlobState {
+	return s.gs
+}
+
+func (s implControllerServerStub) Describe__() []rpc.InterfaceDesc {
+	return []rpc.InterfaceDesc{ControllerDesc}
+}
+
+// ControllerDesc describes the Controller interface.
+var ControllerDesc rpc.InterfaceDesc = descController
+
+// descController hides the desc to keep godoc clean.
+var descController = rpc.InterfaceDesc{
+	Name:    "Controller",
+	PkgPath: "v.io/x/ref/services/wspr/internal/app",
+	Methods: []rpc.MethodDesc{
+		{
+			Name: "Serve",
+			Doc:  "// Serve instructs WSPR to start listening for calls on behalf\n// of a javascript server.",
+			InArgs: []rpc.ArgDesc{
+				{"name", ``},       // string
+				{"serverId", ``},   // uint32
+				{"serverOpts", ``}, // []RpcServerOption
+			},
+		},
+		{
+			Name: "Stop",
+			Doc:  "// Stop instructs WSPR to stop listening for calls for the\n// given javascript server.",
+			InArgs: []rpc.ArgDesc{
+				{"serverId", ``}, // uint32
+			},
+		},
+		{
+			Name: "AddName",
+			Doc:  "// AddName adds a published name to an existing server.",
+			InArgs: []rpc.ArgDesc{
+				{"serverId", ``}, // uint32
+				{"name", ``},     // string
+			},
+		},
+		{
+			Name: "RemoveName",
+			Doc:  "// RemoveName removes a published name from an existing server.",
+			InArgs: []rpc.ArgDesc{
+				{"serverId", ``}, // uint32
+				{"name", ``},     // string
+			},
+		},
+		{
+			Name: "Bless",
+			Doc:  "// Bless binds extensions of blessings held by this principal to\n// another principal (represented by its public key).",
+			InArgs: []rpc.ArgDesc{
+				{"publicKey", ``}, // []byte
+				{"blessings", ``}, // security.Blessings
+				{"extension", ``}, // string
+				{"caveat", ``},    // []security.Caveat
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"", ``}, // principal.BlessingsId
+			},
+		},
+		{
+			Name: "BlessSelf",
+			Doc:  "// BlessSelf creates a blessing with the provided name for this principal.",
+			InArgs: []rpc.ArgDesc{
+				{"name", ``},    // string
+				{"caveats", ``}, // []security.Caveat
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"", ``}, // principal.BlessingsId
+			},
+		},
+		{
+			Name: "AddToRoots",
+			Doc:  "// AddToRoots adds the provided blessing as a root.",
+			InArgs: []rpc.ArgDesc{
+				{"blessings", ``}, // security.Blessings
+			},
+		},
+		{
+			Name: "BlessingStoreSet",
+			Doc:  "// BlessingStoreSet puts the specified blessing in the blessing store under the provided pattern.",
+			InArgs: []rpc.ArgDesc{
+				{"blessingsblessings", ``}, // security.Blessings
+				{"pattern", ``},            // security.BlessingPattern
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"", ``}, // principal.BlessingsId
+			},
+		},
+		{
+			Name: "BlessingStoreForPeer",
+			Doc:  "// BlessingStoreForPeer retrieves the blessings marked for the given peers.",
+			InArgs: []rpc.ArgDesc{
+				{"peerBlessings", ``}, // []string
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"", ``}, // principal.BlessingsId
+			},
+		},
+		{
+			Name: "BlessingStoreSetDefault",
+			Doc:  "// BlessingStoreSetDefault sets the default blessings.",
+			InArgs: []rpc.ArgDesc{
+				{"blessingsblessings", ``}, // security.Blessings
+			},
+		},
+		{
+			Name: "BlessingStoreDefault",
+			Doc:  "// BlessingStoreDefault fetches the default blessings for the principal of the controller.",
+			OutArgs: []rpc.ArgDesc{
+				{"", ``}, // principal.BlessingsId
+			},
+		},
+		{
+			Name: "BlessingStorePublicKey",
+			Doc:  "// BlessingStorePublicKey fetches the public key of the principal for which this store hosts blessings.",
+			OutArgs: []rpc.ArgDesc{
+				{"", ``}, // []byte
+			},
+		},
+		{
+			Name: "BlessingStorePeerBlessings",
+			Doc:  "// BlessingStorePeerBlessings returns all the blessings that the BlessingStore holds.",
+			OutArgs: []rpc.ArgDesc{
+				{"", ``}, // map[security.BlessingPattern]principal.BlessingsId
+			},
+		},
+		{
+			Name: "BlessingStoreDebugString",
+			Doc:  "// BlessingStoreDebugString retrieves a debug string describing the state of the blessing store",
+			OutArgs: []rpc.ArgDesc{
+				{"", ``}, // string
+			},
+		},
+		{
+			Name: "RemoteBlessings",
+			Doc:  "// RemoteBlessings fetches the remote blessings for a given name and method.",
+			InArgs: []rpc.ArgDesc{
+				{"name", ``},   // string
+				{"method", ``}, // string
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"", ``}, // []string
+			},
+		},
+		{
+			Name: "Signature",
+			Doc:  "// Signature fetches the signature for a given name.",
+			InArgs: []rpc.ArgDesc{
+				{"name", ``}, // string
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"", ``}, // []signature.Interface
+			},
+		},
+	},
+}
diff --git a/services/wspr/internal/app/granter.go b/services/wspr/internal/app/granter.go
new file mode 100644
index 0000000..e1a7a12
--- /dev/null
+++ b/services/wspr/internal/app/granter.go
@@ -0,0 +1,77 @@
+// 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 app
+
+import (
+	"fmt"
+	"time"
+
+	"v.io/v23/context"
+	"v.io/v23/security"
+	"v.io/x/ref/services/wspr/internal/lib"
+	"v.io/x/ref/services/wspr/internal/rpc/server"
+)
+
+// This is a Granter that redirects grant requests to javascript
+// and waits for the response.
+// Implements security.Granter
+type jsGranter struct {
+	c             server.ServerHelper
+	granterHandle GranterHandle
+}
+
+func (g *jsGranter) Grant(ctx *context.T, call security.Call) (blessings security.Blessings, err error) {
+	stream := &granterStream{make(chan *GranterResponse, 1)}
+	flow := g.c.CreateNewFlow(stream, stream)
+	request := &GranterRequest{
+		GranterHandle: g.granterHandle,
+		Call:          server.ConvertSecurityCall(g.c, ctx, call, true),
+	}
+	encoded, err := lib.HexVomEncode(request, nil)
+	if err != nil {
+		return security.Blessings{}, err
+	}
+	if err := flow.Writer.Send(lib.ResponseGranterRequest, encoded); err != nil {
+		return security.Blessings{}, err
+	}
+	timeoutTime := time.Second * 5 // get real timeout
+	select {
+	case <-time.After(timeoutTime):
+		return security.Blessings{}, fmt.Errorf("Timed out receiving response from javascript granter")
+	case response := <-stream.c:
+		if response.Err != nil {
+			return security.Blessings{}, response.Err
+		}
+		return response.Blessings, nil
+	}
+}
+
+func (g *jsGranter) RPCCallOpt() {}
+
+// Granter stream exists because our incoming request handling mechanism
+// works on streams.
+// It simply decodes the response from js and sends it to the granter.
+type granterStream struct {
+	c chan *GranterResponse
+}
+
+func (g *granterStream) Send(item interface{}) error {
+	dataString := item.(string)
+	var gr *GranterResponse
+	if err := lib.HexVomDecode(dataString, &gr, nil); err != nil {
+		return fmt.Errorf("error decoding granter response: %v", err)
+	}
+	g.c <- gr
+	return nil
+}
+
+func (g *granterStream) Recv(itemptr interface{}) error {
+	panic("Shouldn't be called")
+}
+
+func (g *granterStream) CloseSend() error {
+	close(g.c)
+	return nil
+}
diff --git a/services/wspr/internal/app/messaging.go b/services/wspr/internal/app/messaging.go
new file mode 100644
index 0000000..5389996
--- /dev/null
+++ b/services/wspr/internal/app/messaging.go
@@ -0,0 +1,184 @@
+// 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 app
+
+import (
+	"bytes"
+	"fmt"
+	"path/filepath"
+	"runtime"
+
+	"v.io/v23/context"
+	"v.io/v23/verror"
+	"v.io/v23/vom"
+	"v.io/v23/vtrace"
+	"v.io/x/ref/services/wspr/internal/lib"
+)
+
+const (
+	verrorPkgPath = "v.io/x/ref/services/wspr/internal/app"
+)
+
+var (
+	errUnknownMessageType = verror.Register(verrorPkgPath+".unkownMessage", verror.NoRetry, "{1} {2} Unknown message type {_}")
+)
+
+// Incoming message from the javascript client to WSPR.
+type MessageType int32
+
+const (
+	// Making a vanadium client request, streaming or otherwise.
+	VeyronRequestMessage MessageType = 0
+
+	// Serving this  under an object name.
+	ServeMessage = 1
+
+	// A response from a service in javascript to a request.
+	// from the proxy.
+	ServerResponseMessage = 2
+
+	// Sending streaming data, either from a JS client or JS service.
+	StreamingValueMessage = 3
+
+	// A response that means the stream is closed by the client.
+	StreamCloseMessage = 4
+
+	// A request to get signature of a remote server.
+	SignatureRequestMessage = 5
+
+	// A request to stop a server.
+	StopServerMessage = 6
+
+	// A request to unlink blessings.  This request means that
+	// we can remove the given handle from the handle store.
+	UnlinkBlessingsMessage = 8
+
+	// A request to run the lookup function on a dispatcher.
+	LookupResponseMessage = 11
+
+	// A request to run the authorizer for an rpc.
+	AuthResponseMessage = 12
+
+	// A request to run a namespace client method.
+	NamespaceRequestMessage = 13
+
+	// A request to cancel an rpc initiated by the JS.
+	CancelMessage = 17
+
+	// A request to add a new name to server.
+	AddName = 18
+
+	// A request to remove a name from server.
+	RemoveName = 19
+
+	// A request to get the remote blessings of a server.
+	RemoteBlessings = 20
+
+	// A response to a caveat validation request.
+	CaveatValidationResponse = 21
+
+	// A response to a granter request.
+	GranterResponseMessage = 22
+
+	TypeMessage = 23
+)
+
+type Message struct {
+	// TODO(bprosnitz) Consider changing this ID to a larger value.
+	// TODO(bprosnitz) Consider making the ID have positive / negative value
+	// depending on whether from/to JS.
+	Id int32
+	// This contains the json encoded payload.
+	Data string
+
+	// Whether it is an rpc request or a serve request.
+	Type MessageType
+}
+
+// HandleIncomingMessage handles most incoming messages from JS and calls the appropriate handler.
+func (c *Controller) HandleIncomingMessage(msg Message, w lib.ClientWriter) {
+	// TODO(mattr): Get the proper context information from javascript.
+	ctx, _ := vtrace.WithNewTrace(c.Context())
+
+	switch msg.Type {
+	case VeyronRequestMessage:
+		c.HandleVeyronRequest(ctx, msg.Id, msg.Data, w)
+	case CancelMessage:
+		go c.HandleVeyronCancellation(ctx, msg.Id)
+	case StreamingValueMessage:
+		// SendOnStream queues up the message to be sent, but doesn't do the send
+		// on this goroutine.  We need to queue the messages synchronously so that
+		// the order is preserved.
+		c.SendOnStream(ctx, msg.Id, msg.Data, w)
+	case StreamCloseMessage:
+		c.CloseStream(ctx, msg.Id)
+
+	case ServerResponseMessage:
+		go c.HandleServerResponse(ctx, msg.Id, msg.Data)
+	case LookupResponseMessage:
+		go c.HandleLookupResponse(ctx, msg.Id, msg.Data)
+	case AuthResponseMessage:
+		go c.HandleAuthResponse(ctx, msg.Id, msg.Data)
+	case CaveatValidationResponse:
+		go c.HandleCaveatValidationResponse(ctx, msg.Id, msg.Data)
+	case GranterResponseMessage:
+		go c.HandleGranterResponse(ctx, msg.Id, msg.Data)
+	case TypeMessage:
+		// These messages need to be handled in order so they are done in line.
+		c.HandleTypeMessage(ctx, msg.Data)
+	default:
+		w.Error(verror.New(errUnknownMessageType, ctx, msg.Type))
+	}
+}
+
+// ConstructOutgoingMessage constructs a message to send to javascript in a consistent format.
+func ConstructOutgoingMessage(messageId int32, messageType lib.ResponseType, data interface{}) (string, error) {
+	var buf bytes.Buffer
+	if _, err := buf.Write(lib.BinaryEncodeUint(uint64(messageId))); err != nil {
+		return "", err
+	}
+	if _, err := buf.Write(lib.BinaryEncodeUint(uint64(messageType))); err != nil {
+		return "", err
+	}
+	enc := vom.NewEncoder(&buf)
+	if err := enc.Encode(data); err != nil {
+		return "", err
+	}
+
+	return fmt.Sprintf("%x", buf.Bytes()), nil
+}
+
+// FormatAsVerror formats an error as a verror.
+// This also logs the error.
+func FormatAsVerror(ctx *context.T, err error) error {
+	verr := verror.Convert(verror.ErrUnknown, nil, err)
+
+	// Also log the error but write internal errors at a more severe log level
+	logLevel := 2
+	logErr := fmt.Sprintf("%v", verr)
+
+	// Prefix the message with the code locations associated with verr,
+	// except the last, which is the Convert() above.  This does nothing if
+	// err was not a verror error.
+	verrStack := verror.Stack(verr)
+	for i := 0; i < len(verrStack)-1; i++ {
+		pc := verrStack[i]
+		fnc := runtime.FuncForPC(pc)
+		file, line := fnc.FileLine(pc)
+		logErr = fmt.Sprintf("%s:%d: %s", file, line, logErr)
+	}
+
+	// We want to look at the stack three frames up to find where the error actually
+	// occurred.  (caller -> websocketErrorResponse/sendError -> generateErrorMessage).
+	if _, file, line, ok := runtime.Caller(3); ok {
+		logErr = fmt.Sprintf("%s:%d: %s", filepath.Base(file), line, logErr)
+	}
+	if verror.ErrorID(verr) == verror.ErrInternal.ID {
+		logLevel = 2
+	}
+	ctx.VI(logLevel).Info(logErr)
+
+	return verr
+}
diff --git a/services/wspr/internal/app/mock_jsServer_test.go b/services/wspr/internal/app/mock_jsServer_test.go
new file mode 100644
index 0000000..b9c60ee
--- /dev/null
+++ b/services/wspr/internal/app/mock_jsServer_test.go
@@ -0,0 +1,329 @@
+// 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 app
+
+import (
+	"bytes"
+	"encoding/hex"
+	"encoding/json"
+	"fmt"
+	"reflect"
+	"sync"
+	"testing"
+
+	"v.io/v23/context"
+	"v.io/v23/vdl"
+	"v.io/v23/vdlroot/signature"
+	"v.io/v23/verror"
+	"v.io/v23/vom"
+	"v.io/x/ref/internal/reflectutil"
+	"v.io/x/ref/services/wspr/internal/lib"
+	"v.io/x/ref/services/wspr/internal/rpc/server"
+)
+
+type mockJSServer struct {
+	controller           *Controller
+	t                    *testing.T
+	method               string
+	serviceSignature     []signature.Interface
+	sender               sync.WaitGroup
+	expectedClientStream []string
+	serverStream         []interface{}
+	hasAuthorizer        bool
+	authError            error
+	inArgs               []interface{}
+	controllerReady      sync.RWMutex
+	finalResponse        *vdl.Value
+	receivedResponse     *vdl.Value
+	finalError           error
+	hasCalledAuth        bool
+	// Right now we keep track of the flow count by hand, but maybe we
+	// should setup a different object to handle each flow, so we
+	// can make sure that both sides are using the same flowId.  This
+	// isn't a problem right now because the test doesn't do multiple flows
+	// at the same time.
+	flowCount int32
+	rpcFlow   int32
+
+	typeReader  *lib.TypeReader
+	typeDecoder *vom.TypeDecoder
+
+	typeEncoder *vom.TypeEncoder
+
+	ctx *context.T
+}
+
+func (m *mockJSServer) Send(responseType lib.ResponseType, msg interface{}) error {
+	switch responseType {
+	case lib.ResponseDispatcherLookup:
+		return m.handleDispatcherLookup(m.ctx, msg)
+	case lib.ResponseAuthRequest:
+		return m.handleAuthRequest(m.ctx, msg)
+	case lib.ResponseServerRequest:
+		return m.handleServerRequest(m.ctx, msg)
+	case lib.ResponseValidate:
+		return m.handleValidationRequest(m.ctx, msg)
+	case lib.ResponseStream:
+		return m.handleStream(m.ctx, msg)
+	case lib.ResponseStreamClose:
+		return m.handleStreamClose(m.ctx, msg)
+	case lib.ResponseFinal:
+		if m.receivedResponse != nil {
+			return fmt.Errorf("Two responses received. First was: %#v. Second was: %#v", m.receivedResponse, msg)
+		}
+		m.receivedResponse = vdl.ValueOf(msg)
+		return nil
+	case lib.ResponseLog, lib.ResponseBlessingsCacheMessage:
+		m.flowCount += 2
+		return nil
+	case lib.ResponseTypeMessage:
+		m.handleTypeMessage(msg)
+		return nil
+	}
+	return fmt.Errorf("Unknown message type: %d", responseType)
+}
+
+func internalErr(args interface{}, typeEncoder *vom.TypeEncoder) string {
+	err := verror.E{
+		ID:        verror.ID("v.io/v23/verror.Internal"),
+		Action:    verror.ActionCode(0),
+		ParamList: []interface{}{args},
+	}
+
+	return lib.HexVomEncodeOrDie(server.LookupReply{
+		Err: err,
+	}, typeEncoder)
+}
+
+func (m *mockJSServer) Error(err error) {
+	panic(err)
+}
+
+func normalize(msg interface{}) (map[string]interface{}, error) {
+	// We serialize and deserialize the reponse so that we can do deep equal with
+	// messages that contain non-exported structs.
+	var buf bytes.Buffer
+	if err := json.NewEncoder(&buf).Encode(msg); err != nil {
+		return nil, err
+	}
+
+	var r interface{}
+
+	if err := json.NewDecoder(&buf).Decode(&r); err != nil {
+		return nil, err
+	}
+	return r.(map[string]interface{}), nil
+}
+
+func (m *mockJSServer) handleTypeMessage(v interface{}) {
+	m.typeReader.Add(hex.EncodeToString(v.([]byte)))
+}
+func (m *mockJSServer) handleDispatcherLookup(ctx *context.T, v interface{}) error {
+	defer func() {
+		m.flowCount += 2
+	}()
+	m.controllerReady.RLock()
+	defer m.controllerReady.RUnlock()
+
+	msg, err := normalize(v)
+	if err != nil {
+		m.controller.HandleLookupResponse(ctx, m.flowCount, internalErr(err, m.typeEncoder))
+		return nil
+	}
+	expected := map[string]interface{}{"serverId": 0.0, "suffix": "adder"}
+	if !reflect.DeepEqual(msg, expected) {
+		m.controller.HandleLookupResponse(ctx, m.flowCount, internalErr(fmt.Sprintf("got: %v, want: %v", msg, expected), m.typeEncoder))
+		return nil
+	}
+	lookupReply := lib.HexVomEncodeOrDie(server.LookupReply{
+		Handle:        0,
+		Signature:     m.serviceSignature,
+		HasAuthorizer: m.hasAuthorizer,
+	}, m.typeEncoder)
+	m.controller.HandleLookupResponse(ctx, m.flowCount, lookupReply)
+	return nil
+}
+
+func validateEndpoint(ep string) bool {
+	return ep != ""
+}
+
+func (m *mockJSServer) handleAuthRequest(ctx *context.T, v interface{}) error {
+	defer func() {
+		m.flowCount += 2
+	}()
+
+	m.hasCalledAuth = true
+	if !m.hasAuthorizer {
+		m.controller.HandleAuthResponse(ctx, m.flowCount, internalErr("unexpected auth request", m.typeEncoder))
+		return nil
+	}
+
+	var msg server.AuthRequest
+	if err := lib.HexVomDecode(v.(string), &msg, m.typeDecoder); err != nil {
+		m.controller.HandleAuthResponse(ctx, m.flowCount, internalErr(fmt.Sprintf("error decoding %v:", err), m.typeEncoder))
+		return nil
+	}
+
+	if msg.Handle != 0 {
+		m.controller.HandleAuthResponse(ctx, m.flowCount, internalErr(fmt.Sprintf("unexpected handled: %v", msg.Handle), m.typeEncoder))
+		return nil
+	}
+
+	call := msg.Call
+	if field, got, want := "Method", call.Method, lib.LowercaseFirstCharacter(m.method); got != want {
+		m.controller.HandleAuthResponse(ctx, m.flowCount, internalErr(fmt.Sprintf("unexpected value for %s: got %v, want %v", field, got, want), m.typeEncoder))
+		return nil
+	}
+
+	if field, got, want := "Suffix", call.Suffix, "adder"; got != want {
+		m.controller.HandleAuthResponse(ctx, m.flowCount, internalErr(fmt.Sprintf("unexpected value for %s: got %v, want %v", field, got, want), m.typeEncoder))
+		return nil
+	}
+
+	// We expect localBlessings and remoteBlessings to be a non-zero id
+	if call.LocalBlessings == 0 {
+		m.controller.HandleAuthResponse(ctx, m.flowCount, internalErr(fmt.Sprintf("bad local blessing: %v", call.LocalBlessings), m.typeEncoder))
+		return nil
+	}
+	if call.RemoteBlessings == 0 {
+		m.controller.HandleAuthResponse(ctx, m.flowCount, internalErr(fmt.Sprintf("bad remote blessing: %v", call.RemoteBlessings), m.typeEncoder))
+		return nil
+	}
+
+	// We expect endpoints to be set
+	if !validateEndpoint(call.LocalEndpoint) {
+		m.controller.HandleAuthResponse(ctx, m.flowCount, internalErr(fmt.Sprintf("bad endpoint:%v", call.LocalEndpoint), m.typeEncoder))
+		return nil
+	}
+
+	if !validateEndpoint(call.RemoteEndpoint) {
+		m.controller.HandleAuthResponse(ctx, m.flowCount, internalErr(fmt.Sprintf("bad endpoint:%v", call.RemoteEndpoint), m.typeEncoder))
+		return nil
+	}
+
+	authReply := lib.HexVomEncodeOrDie(server.AuthReply{
+		Err: m.authError,
+	}, m.typeEncoder)
+
+	m.controller.HandleAuthResponse(ctx, m.flowCount, authReply)
+	return nil
+}
+
+func (m *mockJSServer) handleServerRequest(ctx *context.T, v interface{}) error {
+	defer func() {
+		m.flowCount += 2
+	}()
+
+	if m.hasCalledAuth != m.hasAuthorizer {
+		m.controller.HandleServerResponse(ctx, m.flowCount, internalErr("authorizer hasn't been called yet", m.typeEncoder))
+		return nil
+	}
+
+	var msg server.ServerRpcRequest
+	if err := lib.HexVomDecode(v.(string), &msg, m.typeDecoder); err != nil {
+		m.controller.HandleServerResponse(ctx, m.flowCount, internalErr(err, m.typeEncoder))
+		return nil
+	}
+
+	if field, got, want := "Method", msg.Method, lib.LowercaseFirstCharacter(m.method); got != want {
+		m.controller.HandleServerResponse(ctx, m.flowCount, internalErr(fmt.Sprintf("unexpected value for %s: got %v, want %v", field, got, want), m.typeEncoder))
+		return nil
+	}
+
+	if field, got, want := "Handle", msg.Handle, int32(0); got != want {
+		m.controller.HandleServerResponse(ctx, m.flowCount, internalErr(fmt.Sprintf("unexpected value for %s: got %v, want %v", field, got, want), m.typeEncoder))
+		return nil
+	}
+
+	vals := make([]interface{}, len(msg.Args))
+	for i, vArg := range msg.Args {
+		if err := vdl.Convert(&vals[i], vArg); err != nil {
+			panic(err)
+		}
+	}
+	if field, got, want := "Args", vals, m.inArgs; !reflectutil.DeepEqual(got, want, &reflectutil.DeepEqualOpts{SliceEqNilEmpty: true}) {
+		m.controller.HandleServerResponse(ctx, m.flowCount, internalErr(fmt.Sprintf("unexpected value for %s: got %v, want %v", field, got, want), m.typeEncoder))
+		return nil
+	}
+
+	call := msg.Call.SecurityCall
+	if field, got, want := "Suffix", call.Suffix, "adder"; got != want {
+		m.controller.HandleServerResponse(ctx, m.flowCount, internalErr(fmt.Sprintf("unexpected value for %s: got %v, want %v", field, got, want), m.typeEncoder))
+		return nil
+	}
+
+	// We expect localBlessings and remoteBlessings to be a non-zero id
+	if call.LocalBlessings == 0 {
+		m.controller.HandleAuthResponse(ctx, m.flowCount, internalErr(fmt.Sprintf("bad local blessing: %v", call.LocalBlessings), m.typeEncoder))
+		return nil
+	}
+	if call.RemoteBlessings == 0 {
+		m.controller.HandleAuthResponse(ctx, m.flowCount, internalErr(fmt.Sprintf("bad remote blessing: %v", call.RemoteBlessings), m.typeEncoder))
+		return nil
+	}
+
+	m.rpcFlow = m.flowCount
+
+	// We don't return the final response until the stream is closed.
+	m.sender.Add(1)
+	go m.sendServerStream(ctx)
+	return nil
+}
+
+func (m *mockJSServer) handleValidationRequest(ctx *context.T, v interface{}) error {
+	defer func() {
+		m.flowCount += 2
+	}()
+
+	req := v.(server.CaveatValidationRequest)
+	resp := server.CaveatValidationResponse{
+		Results: make([]error, len(req.Cavs)),
+	}
+
+	res := lib.HexVomEncodeOrDie(resp, m.typeEncoder)
+
+	m.controllerReady.RLock()
+	m.controller.HandleCaveatValidationResponse(ctx, m.flowCount, res)
+	m.controllerReady.RUnlock()
+	return nil
+}
+
+func (m *mockJSServer) sendServerStream(ctx *context.T) {
+	defer m.sender.Done()
+	m.controllerReady.RLock()
+	for _, v := range m.serverStream {
+		m.controller.SendOnStream(ctx, m.rpcFlow, lib.HexVomEncodeOrDie(v, m.typeEncoder), m)
+	}
+	m.controllerReady.RUnlock()
+}
+
+func (m *mockJSServer) handleStream(ctx *context.T, msg interface{}) error {
+	smsg, ok := msg.(string)
+	if !ok || len(m.expectedClientStream) == 0 {
+		m.t.Errorf("unexpected stream message: %v", msg)
+		return nil
+	}
+
+	curr := m.expectedClientStream[0]
+	m.expectedClientStream = m.expectedClientStream[1:]
+	if smsg != curr {
+		m.t.Errorf("unexpected stream message, got %s, want: %s", smsg, curr)
+	}
+	return nil
+}
+
+func (m *mockJSServer) handleStreamClose(ctx *context.T, msg interface{}) error {
+	m.sender.Wait()
+	reply := lib.ServerRpcReply{
+		Results: []*vdl.Value{m.finalResponse},
+		Err:     m.finalError,
+	}
+
+	m.controllerReady.RLock()
+	m.controller.HandleServerResponse(ctx, m.rpcFlow, lib.HexVomEncodeOrDie(reply, m.typeEncoder))
+	m.controllerReady.RUnlock()
+	return nil
+}
diff --git a/services/wspr/internal/app/stream.go b/services/wspr/internal/app/stream.go
new file mode 100644
index 0000000..5ad1b07
--- /dev/null
+++ b/services/wspr/internal/app/stream.go
@@ -0,0 +1,101 @@
+// 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 app
+
+import (
+	"fmt"
+
+	"v.io/v23/rpc"
+	"v.io/v23/vom"
+	"v.io/x/ref/services/wspr/internal/lib"
+)
+
+type initConfig struct {
+	stream rpc.Stream
+}
+
+type Closer interface {
+	CloseSend() error
+}
+
+type message struct {
+	data   string
+	writer lib.ClientWriter
+}
+
+// oustandingStream provides a stream-like api with the added ability to
+// queue up messages if the stream hasn't been initialized first.  send
+// can be called before init has been called, but no data will be sent
+// until init is called.
+type outstandingStream struct {
+	// The channel on which the stream and the type
+	// of data on the stream is sent after the stream
+	// has been constructed.
+	initChan chan *initConfig
+	// The queue of messages to write out.
+	messages chan *message
+	// done will be notified when the stream has been closed.
+	done chan bool
+	// true if the stream has been closed.
+	closed bool
+
+	typeDecoder *vom.TypeDecoder
+}
+
+func newStream(typeDecoder *vom.TypeDecoder) *outstandingStream {
+	os := &outstandingStream{
+		initChan: make(chan *initConfig, 1),
+		// We allow queueing up to 100 messages before init is called.
+		// TODO(bjornick): Deal with the case that the queue is full.
+		messages:    make(chan *message, 100),
+		done:        make(chan bool),
+		typeDecoder: typeDecoder,
+	}
+	go os.loop()
+	return os
+}
+
+func (os *outstandingStream) send(data string, w lib.ClientWriter) {
+	if !os.closed {
+		os.messages <- &message{data, w}
+	}
+}
+
+func (os *outstandingStream) end() {
+	if !os.closed {
+		close(os.messages)
+		os.closed = true
+	}
+}
+
+// Waits until the stream has been closed and all the messages
+// have been drained.
+func (os *outstandingStream) waitUntilDone() {
+	<-os.done
+}
+
+func (os *outstandingStream) loop() {
+	config := <-os.initChan
+	for msg := range os.messages {
+		var item interface{}
+		if err := lib.HexVomDecode(msg.data, &item, os.typeDecoder); err != nil {
+			msg.writer.Error(fmt.Errorf("failed to decode stream arg from %v: %v", msg.data, err))
+			break
+		}
+
+		if err := config.stream.Send(item); err != nil {
+			msg.writer.Error(fmt.Errorf("failed to send on stream: %v", err))
+		}
+	}
+	close(os.done)
+	// If this is a stream that has a CloseSend, we need to call it.
+	if call, ok := config.stream.(Closer); ok {
+		call.CloseSend()
+	}
+}
+
+func (os *outstandingStream) init(stream rpc.Stream) {
+	os.initChan <- &initConfig{stream}
+}
diff --git a/services/wspr/internal/app/v23_internal_test.go b/services/wspr/internal/app/v23_internal_test.go
new file mode 100644
index 0000000..70d6982
--- /dev/null
+++ b/services/wspr/internal/app/v23_internal_test.go
@@ -0,0 +1,20 @@
+// 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 app
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	os.Exit(m.Run())
+}
diff --git a/services/wspr/internal/browspr/browspr.go b/services/wspr/internal/browspr/browspr.go
new file mode 100644
index 0000000..e0cc5ec
--- /dev/null
+++ b/services/wspr/internal/browspr/browspr.go
@@ -0,0 +1,207 @@
+// 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.
+
+// Browspr is the browser version of WSPR, intended to communicate with javascript through postMessage.
+package browspr
+
+import (
+	"fmt"
+	"reflect"
+	"sync"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/vdl"
+	"v.io/v23/vtrace"
+	"v.io/x/ref/lib/security"
+	"v.io/x/ref/services/wspr/internal/account"
+	"v.io/x/ref/services/wspr/internal/app"
+	"v.io/x/ref/services/wspr/internal/principal"
+)
+
+// Browspr is an intermediary between our javascript code and the vanadium
+// network that allows our javascript library to use vanadium.
+type Browspr struct {
+	ctx              *context.T
+	listenSpec       *rpc.ListenSpec
+	namespaceRoots   []string
+	accountManager   *account.AccountManager
+	postMessage      func(instanceId int32, ty, msg string)
+	principalManager *principal.PrincipalManager
+
+	mu              sync.Mutex
+	activeInstances map[int32]*pipe // GUARDED_BY mu
+}
+
+// Create a new Browspr instance.
+func NewBrowspr(ctx *context.T,
+	postMessage func(instanceId int32, ty, msg string),
+	listenSpec *rpc.ListenSpec,
+	identd string,
+	wsNamespaceRoots []string,
+	principalSerializer security.SerializerReaderWriter) *Browspr {
+	if listenSpec.Proxy == "" {
+		ctx.Fatalf("a vanadium proxy must be set")
+	}
+	if identd == "" {
+		ctx.Fatalf("an identd server must be set")
+	}
+
+	browspr := &Browspr{
+		listenSpec:      listenSpec,
+		namespaceRoots:  wsNamespaceRoots,
+		postMessage:     postMessage,
+		ctx:             ctx,
+		activeInstances: make(map[int32]*pipe),
+	}
+
+	// TODO(nlacasse, bjornick) use a serializer that can actually persist.
+	var err error
+	p := v23.GetPrincipal(ctx)
+
+	if principalSerializer == nil {
+		ctx.Fatalf("principalSerializer must not be nil")
+	}
+
+	if browspr.principalManager, err = principal.NewPrincipalManager(p, principalSerializer); err != nil {
+		ctx.Fatalf("principal.NewPrincipalManager failed: %s", err)
+	}
+
+	browspr.accountManager = account.NewAccountManager(identd, browspr.principalManager)
+
+	return browspr
+}
+
+func (b *Browspr) Shutdown() {
+	// TODO(ataly, bprosnitz): Get rid of this method if possible.
+}
+
+// CreateInstance creates a new pipe and stores it in activeInstances map.
+func (b *Browspr) createInstance(instanceId int32, origin string, namespaceRoots []string, proxy string) (*pipe, error) {
+	b.mu.Lock()
+	defer b.mu.Unlock()
+
+	// Make sure we don't already have an instance.
+	p, ok := b.activeInstances[instanceId]
+	if ok {
+		return nil, fmt.Errorf("InstanceId %v already has an open instance.", instanceId)
+	}
+
+	p = newPipe(b, instanceId, origin, namespaceRoots, proxy)
+	if p == nil {
+		return nil, fmt.Errorf("Could not create pipe for instanceId %v and origin %v", instanceId, origin)
+	}
+	b.activeInstances[instanceId] = p
+	return p, nil
+}
+
+// HandleMessage handles most messages from javascript and forwards them to a
+// Controller.
+func (b *Browspr) HandleMessage(instanceId int32, origin string, msg app.Message) error {
+	b.mu.Lock()
+	p, ok := b.activeInstances[instanceId]
+	b.mu.Unlock()
+	if !ok {
+		return fmt.Errorf("No pipe found for instanceId %v. Must send CreateInstance message first.", instanceId)
+	}
+
+	if origin != p.origin {
+		return fmt.Errorf("Invalid message origin. InstanceId %v has origin %v, but message is from %v.", instanceId, p.origin, origin)
+	}
+
+	return p.handleMessage(msg)
+}
+
+// HandleCleanupRpc cleans up the specified instance state. (For instance,
+// when a browser tab is closed)
+func (b *Browspr) HandleCleanupRpc(val *vdl.Value) (*vdl.Value, error) {
+	var msg CleanupMessage
+	if err := vdl.Convert(&msg, val); err != nil {
+		return nil, fmt.Errorf("HandleCleanupRpc did not receive CleanupMessage, received: %v, %v", val, err)
+	}
+
+	b.mu.Lock()
+
+	if pipe, ok := b.activeInstances[msg.InstanceId]; ok {
+		// We must unlock the mutex before calling cleanunp, otherwise
+		// browspr deadlocks.
+		b.mu.Unlock()
+		pipe.cleanup(b.ctx)
+
+		b.mu.Lock()
+		delete(b.activeInstances, msg.InstanceId)
+	}
+
+	b.mu.Unlock()
+
+	return nil, nil
+}
+
+// Handler for creating an account in the principal manager.
+// A valid OAuth2 access token must be supplied in the request body,
+// which is exchanged for blessings from the vanadium blessing server.
+// An account based on the blessings is then added to WSPR's principal
+// manager, and the set of blessing strings are returned to the client.
+func (b *Browspr) HandleAuthCreateAccountRpc(val *vdl.Value) (*vdl.Value, error) {
+	var msg CreateAccountMessage
+	if err := vdl.Convert(&msg, val); err != nil {
+		return nil, fmt.Errorf("HandleAuthCreateAccountRpc did not receive CreateAccountMessage, received: %v, %v", val, err)
+	}
+
+	ctx, _ := vtrace.WithNewTrace(b.ctx)
+	account, err := b.accountManager.CreateAccount(ctx, msg.Token)
+	if err != nil {
+		return nil, err
+	}
+
+	return vdl.ValueFromReflect(reflect.ValueOf(account))
+}
+
+// HandleAssociateAccountMessage associates an account with the specified origin.
+func (b *Browspr) HandleAuthAssociateAccountRpc(val *vdl.Value) (*vdl.Value, error) {
+	var msg AssociateAccountMessage
+	if err := vdl.Convert(&msg, val); err != nil {
+		return nil, fmt.Errorf("HandleAuthAssociateAccountRpc did not receive AssociateAccountMessage, received: %v, %v", val, err)
+	}
+	ctx, _ := vtrace.WithNewTrace(b.ctx)
+	if err := b.accountManager.AssociateAccount(ctx, msg.Origin, msg.Account, msg.Caveats); err != nil {
+		return nil, err
+	}
+	return nil, nil
+}
+
+// HandleAuthGetAccountsRpc gets the root account name from the account manager.
+func (b *Browspr) HandleAuthGetAccountsRpc(*vdl.Value) (*vdl.Value, error) {
+	accounts := b.accountManager.GetAccounts()
+	return vdl.ValueFromReflect(reflect.ValueOf(accounts))
+}
+
+// HandleAuthOriginHasAccountRpc returns true iff the origin has an associated
+// principal.
+func (b *Browspr) HandleAuthOriginHasAccountRpc(val *vdl.Value) (*vdl.Value, error) {
+	var msg OriginHasAccountMessage
+	if err := vdl.Convert(&msg, val); err != nil {
+		return nil, fmt.Errorf("HandleAuthOriginHasAccountRpc did not receive OriginHasAccountMessage, received: %v, %v", val, err)
+	}
+
+	res := b.accountManager.OriginHasAccount(msg.Origin)
+	return vdl.ValueFromReflect(reflect.ValueOf(res))
+}
+
+// HandleCreateInstanceRpc sets the namespace root and proxy on the pipe, if
+// any are provided.
+func (b *Browspr) HandleCreateInstanceRpc(val *vdl.Value) (*vdl.Value, error) {
+	var msg CreateInstanceMessage
+	if err := vdl.Convert(&msg, val); err != nil {
+		return nil, fmt.Errorf("HandleCreateInstanceMessage did not receive CreateInstanceMessage, received: %v, %v", val, err)
+	}
+
+	_, err := b.createInstance(msg.InstanceId, msg.Origin, msg.NamespaceRoots, msg.Proxy)
+	if err != nil {
+		return nil, err
+	}
+
+	return nil, nil
+}
diff --git a/services/wspr/internal/browspr/browspr.vdl b/services/wspr/internal/browspr/browspr.vdl
new file mode 100644
index 0000000..f0016ed
--- /dev/null
+++ b/services/wspr/internal/browspr/browspr.vdl
@@ -0,0 +1,46 @@
+// 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 browspr
+
+import (
+	"v.io/x/ref/services/identity"
+	"v.io/x/ref/services/wspr/internal/account"
+)
+
+type StartMessage struct {
+	Identityd             string
+	IdentitydBlessingRoot identity.BlessingRootResponse
+	Proxy                 string
+	NamespaceRoot         string
+	LogLevel              int32
+	LogModule             string
+}
+
+type AssociateAccountMessage struct {
+	Account string
+	Origin  string
+	Caveats []account.Caveat
+}
+
+type CreateAccountMessage struct {
+	Token string
+}
+
+type CleanupMessage struct {
+	InstanceId int32
+}
+
+type OriginHasAccountMessage struct {
+	Origin string
+}
+
+type GetAccountsMessage struct{}
+
+type CreateInstanceMessage struct {
+	InstanceId     int32
+	Origin         string
+	NamespaceRoots []string
+	Proxy          string
+}
diff --git a/services/wspr/internal/browspr/browspr.vdl.go b/services/wspr/internal/browspr/browspr.vdl.go
new file mode 100644
index 0000000..4cfd019
--- /dev/null
+++ b/services/wspr/internal/browspr/browspr.vdl.go
@@ -0,0 +1,99 @@
+// 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 by the vanadium vdl tool.
+// Source: browspr.vdl
+
+package browspr
+
+import (
+	// VDL system imports
+	"v.io/v23/vdl"
+
+	// VDL user imports
+	"v.io/x/ref/services/identity"
+	"v.io/x/ref/services/wspr/internal/account"
+)
+
+type StartMessage struct {
+	Identityd             string
+	IdentitydBlessingRoot identity.BlessingRootResponse
+	Proxy                 string
+	NamespaceRoot         string
+	LogLevel              int32
+	LogModule             string
+}
+
+func (StartMessage) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/services/wspr/internal/browspr.StartMessage"`
+}) {
+}
+
+type AssociateAccountMessage struct {
+	Account string
+	Origin  string
+	Caveats []account.Caveat
+}
+
+func (AssociateAccountMessage) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/services/wspr/internal/browspr.AssociateAccountMessage"`
+}) {
+}
+
+type CreateAccountMessage struct {
+	Token string
+}
+
+func (CreateAccountMessage) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/services/wspr/internal/browspr.CreateAccountMessage"`
+}) {
+}
+
+type CleanupMessage struct {
+	InstanceId int32
+}
+
+func (CleanupMessage) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/services/wspr/internal/browspr.CleanupMessage"`
+}) {
+}
+
+type OriginHasAccountMessage struct {
+	Origin string
+}
+
+func (OriginHasAccountMessage) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/services/wspr/internal/browspr.OriginHasAccountMessage"`
+}) {
+}
+
+type GetAccountsMessage struct {
+}
+
+func (GetAccountsMessage) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/services/wspr/internal/browspr.GetAccountsMessage"`
+}) {
+}
+
+type CreateInstanceMessage struct {
+	InstanceId     int32
+	Origin         string
+	NamespaceRoots []string
+	Proxy          string
+}
+
+func (CreateInstanceMessage) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/services/wspr/internal/browspr.CreateInstanceMessage"`
+}) {
+}
+
+func init() {
+	vdl.Register((*StartMessage)(nil))
+	vdl.Register((*AssociateAccountMessage)(nil))
+	vdl.Register((*CreateAccountMessage)(nil))
+	vdl.Register((*CleanupMessage)(nil))
+	vdl.Register((*OriginHasAccountMessage)(nil))
+	vdl.Register((*GetAccountsMessage)(nil))
+	vdl.Register((*CreateInstanceMessage)(nil))
+}
diff --git a/services/wspr/internal/browspr/browspr_account_test.go b/services/wspr/internal/browspr/browspr_account_test.go
new file mode 100644
index 0000000..7c0ae4f
--- /dev/null
+++ b/services/wspr/internal/browspr/browspr_account_test.go
@@ -0,0 +1,227 @@
+// 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 browspr
+
+import (
+	"fmt"
+	"reflect"
+	"sort"
+	"testing"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/vdl"
+
+	_ "v.io/x/ref/runtime/factories/generic"
+	"v.io/x/ref/services/wspr/internal/principal"
+	"v.io/x/ref/test"
+)
+
+const topLevelName = "mock-blesser"
+
+type mockBlesserService struct {
+	p     security.Principal
+	count int
+}
+
+func newMockBlesserService(p security.Principal) *mockBlesserService {
+	return &mockBlesserService{
+		p:     p,
+		count: 0,
+	}
+}
+
+func (m *mockBlesserService) BlessUsingAccessToken(c *context.T, accessToken string, co ...rpc.CallOpt) (security.Blessings, string, error) {
+	m.count++
+	name := fmt.Sprintf("%s%s%d", topLevelName, security.ChainSeparator, m.count)
+	blessing, err := m.p.BlessSelf(name)
+	if err != nil {
+		return blessing, "", err
+	}
+	return blessing, name, nil
+}
+
+func setup(t *testing.T) (*Browspr, func()) {
+	ctx, shutdown := test.V23Init()
+
+	spec := v23.GetListenSpec(ctx)
+	spec.Proxy = "/mock/proxy"
+	mockPostMessage := func(_ int32, _, _ string) {}
+	browspr := NewBrowspr(ctx, mockPostMessage, &spec, "/mock:1234/identd", nil, principal.NewInMemorySerializer())
+	principal := v23.GetPrincipal(browspr.ctx)
+	browspr.accountManager.SetMockBlesser(newMockBlesserService(principal))
+
+	return browspr, func() {
+		browspr.Shutdown()
+		shutdown()
+	}
+}
+
+func TestHandleCreateAccount(t *testing.T) {
+	browspr, teardown := setup(t)
+	defer teardown()
+
+	// Verify that HandleAuthGetAccountsRpc returns empty.
+	nilValue := vdl.ValueOf(GetAccountsMessage{})
+	a, err := browspr.HandleAuthGetAccountsRpc(nilValue)
+	if err != nil {
+		t.Fatalf("browspr.HandleAuthGetAccountsRpc(%v) failed: %v", nilValue, err)
+	}
+	if a.Len() > 0 {
+		t.Fatalf("Expected accounts to be empty array but got %v", a)
+	}
+
+	// Add one account.
+	message1 := vdl.ValueOf(CreateAccountMessage{
+		Token: "mock-access-token-1",
+	})
+	account1, err := browspr.HandleAuthCreateAccountRpc(message1)
+	if err != nil {
+		t.Fatalf("browspr.HandleAuthCreateAccountRpc(%v) failed: %v", message1, err)
+	}
+
+	// Verify that principalManager has the new account
+	if b, err := browspr.principalManager.BlessingsForAccount(account1.RawString()); err != nil || b.IsZero() {
+		t.Fatalf("Failed to get Blessings for account %v: got %v, %v", account1, b, err)
+	}
+
+	// Verify that HandleAuthGetAccountsRpc returns the new account.
+	gotAccounts1, err := browspr.HandleAuthGetAccountsRpc(nilValue)
+	if err != nil {
+		t.Fatalf("browspr.HandleAuthGetAccountsRpc(%v) failed: %v", nilValue, err)
+	}
+	if want := vdl.ValueOf([]string{account1.RawString()}); !vdl.EqualValue(want, gotAccounts1) {
+		t.Fatalf("Expected account to be %v but got empty but got %v", want, gotAccounts1)
+	}
+
+	// Add another account
+	message2 := vdl.ValueOf(CreateAccountMessage{
+		Token: "mock-access-token-2",
+	})
+	account2, err := browspr.HandleAuthCreateAccountRpc(message2)
+	if err != nil {
+		t.Fatalf("browspr.HandleAuthCreateAccountsRpc(%v) failed: %v", message2, err)
+	}
+
+	// Verify that HandleAuthGetAccountsRpc returns the new account.
+	gotAccounts2, err := browspr.HandleAuthGetAccountsRpc(nilValue)
+	if err != nil {
+		t.Fatalf("browspr.HandleAuthGetAccountsRpc(%v) failed: %v", nilValue, err)
+	}
+	var got []string
+	if err := vdl.Convert(&got, gotAccounts2); err != nil {
+		t.Fatalf("vdl.Convert failed: %v", err)
+	}
+	sort.Strings(got)
+	if want := []string{account1.RawString(), account2.RawString()}; !reflect.DeepEqual(want, got) {
+		t.Fatalf("Expected account to be %v but got %v", want, got)
+	}
+
+	// Verify that principalManager has both accounts
+	if b, err := browspr.principalManager.BlessingsForAccount(account1.RawString()); err != nil || b.IsZero() {
+		t.Fatalf("Failed to get Blessings for account %v: got %v, %v", account1, b, err)
+	}
+	if b, err := browspr.principalManager.BlessingsForAccount(account2.RawString()); err != nil || b.IsZero() {
+		t.Fatalf("Failed to get Blessings for account %v: got %v, %v", account2, b, err)
+	}
+}
+
+func TestHandleAssocAccount(t *testing.T) {
+	browspr, teardown := setup(t)
+	defer teardown()
+
+	// First create an account.
+	account := "mock-account"
+	principal := v23.GetPrincipal(browspr.ctx)
+	blessing, err := principal.BlessSelf(account)
+	if err != nil {
+		t.Fatalf("browspr.rt.Principal.BlessSelf(%v) failed: %v", account, err)
+	}
+	if err := browspr.principalManager.AddAccount(account, blessing); err != nil {
+		t.Fatalf("browspr.principalManager.AddAccount(%v, %v) failed; %v", account, blessing, err)
+	}
+
+	origin := "https://my.webapp.com:443"
+
+	// Verify that HandleAuthOriginHasAccountRpc returns false
+	hasAccountMessage := vdl.ValueOf(OriginHasAccountMessage{
+		Origin: origin,
+	})
+	hasAccount, err := browspr.HandleAuthOriginHasAccountRpc(hasAccountMessage)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if hasAccount.Bool() {
+		t.Fatalf("Expected browspr.HandleAuthOriginHasAccountRpc(%v) to be false but was true", hasAccountMessage)
+	}
+
+	assocAccountMessage := vdl.ValueOf(AssociateAccountMessage{
+		Account: account,
+		Origin:  origin,
+	})
+
+	if _, err := browspr.HandleAuthAssociateAccountRpc(assocAccountMessage); err != nil {
+		t.Fatalf("browspr.HandleAuthAssociateAccountRpc(%v) failed: %v", assocAccountMessage, err)
+	}
+
+	// Verify that HandleAuthOriginHasAccountRpc returns true
+	hasAccount, err = browspr.HandleAuthOriginHasAccountRpc(hasAccountMessage)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !hasAccount.Bool() {
+		t.Fatalf("Expected browspr.HandleAuthOriginHasAccountRpc(%v) to be true but was false", hasAccountMessage)
+	}
+
+	// Verify that principalManager has the correct principal for the origin
+	got, err := browspr.principalManager.Principal(origin)
+	if err != nil {
+		t.Fatalf("browspr.principalManager.Principal(%v) failed: %v", origin, err)
+	}
+
+	if got == nil {
+		t.Fatalf("Expected browspr.principalManager.Principal(%v) to return a valid principal, but got %v", origin, got)
+	}
+}
+
+func TestHandleAssocAccountWithMissingAccount(t *testing.T) {
+	browspr, teardown := setup(t)
+	defer teardown()
+
+	account := "mock-account"
+	origin := "https://my.webapp.com:443"
+	message := vdl.ValueOf(AssociateAccountMessage{
+		Account: account,
+		Origin:  origin,
+	})
+
+	if _, err := browspr.HandleAuthAssociateAccountRpc(message); err == nil {
+		t.Fatalf("browspr.HandleAuthAssociateAccountRpc(%v) should have failed but did not.", message)
+	}
+
+	// Verify that principalManager creates no principal for the origin
+	got, err := browspr.principalManager.Principal(origin)
+	if err == nil {
+		t.Fatalf("Expected browspr.principalManager.Principal(%v) to fail, but got: %v", origin, got)
+	}
+
+	if got != nil {
+		t.Fatalf("Expected browspr.principalManager.Principal(%v) not to return a principal, but got %v", origin, got)
+	}
+
+	// Verify that HandleAuthOriginHasAccountRpc returns false
+	hasAccountMessage := vdl.ValueOf(OriginHasAccountMessage{
+		Origin: origin,
+	})
+	hasAccount, err := browspr.HandleAuthOriginHasAccountRpc(hasAccountMessage)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if hasAccount.Bool() {
+		t.Fatalf("Expected browspr.HandleAuthOriginHasAccountRpc(%v) to be false but was true", hasAccountMessage)
+	}
+}
diff --git a/services/wspr/internal/browspr/browspr_test.go b/services/wspr/internal/browspr/browspr_test.go
new file mode 100644
index 0000000..4ca50c8
--- /dev/null
+++ b/services/wspr/internal/browspr/browspr_test.go
@@ -0,0 +1,272 @@
+// 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 browspr
+
+import (
+	"bytes"
+	"encoding/hex"
+	"strings"
+	"testing"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/options"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/vdl"
+	vdltime "v.io/v23/vdlroot/time"
+	"v.io/v23/vom"
+
+	"v.io/x/ref/lib/xrpc"
+	"v.io/x/ref/runtime/factories/generic"
+	"v.io/x/ref/services/mounttable/mounttablelib"
+	"v.io/x/ref/services/wspr/internal/app"
+	"v.io/x/ref/services/wspr/internal/lib"
+	"v.io/x/ref/services/wspr/internal/principal"
+	"v.io/x/ref/test"
+)
+
+//go:generate v23 test generate
+
+type mockServer struct{}
+
+func (s mockServer) BasicCall(_ *context.T, _ rpc.StreamServerCall, txt string) (string, error) {
+	return "[" + txt + "]", nil
+}
+
+func parseBrowsperResponse(data string, t *testing.T) (uint64, uint64, []byte) {
+	receivedBytes, err := hex.DecodeString(data)
+	if err != nil {
+		t.Fatalf("Failed to hex decode outgoing message: %v", err)
+	}
+
+	id, bytesRead, err := lib.BinaryDecodeUint(receivedBytes)
+	if err != nil {
+		t.Fatalf("Failed to read mesage id: %v", err)
+	}
+
+	receivedBytes = receivedBytes[bytesRead:]
+	messageType, bytesRead, err := lib.BinaryDecodeUint(receivedBytes)
+	if err != nil {
+		t.Fatalf("Failed to read message type: %v", err)
+	}
+	receivedBytes = receivedBytes[bytesRead:]
+	return id, messageType, receivedBytes
+}
+
+func TestBrowspr(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	proxySpec := rpc.ListenSpec{
+		Addrs: rpc.ListenAddrs{
+			// This '0' label is required by go vet.
+			// TODO(suharshs): Remove the '0' label once []ListenAddr is used
+			// instead of ListenAdders.
+			0: {
+				Protocol: "tcp",
+				Address:  "127.0.0.1:0",
+			},
+		},
+	}
+	proxyShutdown, proxyEndpoint, err := generic.NewProxy(ctx, proxySpec, security.AllowEveryone())
+	if err != nil {
+		t.Fatalf("Failed to start proxy: %v", err)
+	}
+	defer proxyShutdown()
+
+	mt, err := mounttablelib.NewMountTableDispatcher(ctx, "", "", "mounttable")
+	if err != nil {
+		t.Fatalf("Failed to create mounttable: %v", err)
+	}
+	s, err := xrpc.NewDispatchingServer(ctx, "", mt, options.ServesMountTable(true))
+	if err != nil {
+		t.Fatalf("Failed to start mounttable server: %v", err)
+	}
+	mtEndpoint := s.Status().Endpoints[0]
+	root := mtEndpoint.Name()
+
+	if err := v23.GetNamespace(ctx).SetRoots(root); err != nil {
+		t.Fatalf("Failed to set namespace roots: %v", err)
+	}
+
+	mockServerName := "mock/server"
+	mockServer, err := xrpc.NewServer(ctx, mockServerName, mockServer{}, nil)
+	if err != nil {
+		t.Fatalf("Failed to start mock server: %v", err)
+	}
+	mockServerEndpoint := mockServer.Status().Endpoints[0]
+
+	then := time.Now()
+found:
+	for {
+		status := mockServer.Status()
+		for _, v := range status.Mounts {
+			if v.Name == mockServerName && v.Server == mockServerEndpoint.String() && !v.LastMount.IsZero() {
+				if v.LastMountErr != nil {
+					t.Fatalf("Failed to mount %s: %v", v.Name, v.LastMountErr)
+				}
+				break found
+			}
+		}
+		if time.Now().Sub(then) > time.Minute {
+			t.Fatalf("Failed to find mounted server and endpoint: %v: %v", mockServerName, mtEndpoint)
+		}
+		time.Sleep(100 * time.Millisecond)
+	}
+	mountEntry, err := v23.GetNamespace(ctx).Resolve(ctx, mockServerName)
+	if err != nil {
+		t.Fatalf("Error fetching published names from mounttable: %v", err)
+	}
+
+	servers := []string{}
+	for _, s := range mountEntry.Servers {
+		if strings.Index(s.Server, "@tcp") != -1 {
+			servers = append(servers, s.Server)
+		}
+	}
+	if len(servers) != 1 || servers[0] != mockServerEndpoint.String() {
+		t.Fatalf("Incorrect names retrieved from mounttable: %v", mountEntry)
+	}
+
+	spec := v23.GetListenSpec(ctx)
+	spec.Proxy = proxyEndpoint.String()
+
+	receivedResponse := make(chan bool, 1)
+	var receivedInstanceId int32
+	var receivedType string
+	var messageId uint64
+	var messageType uint64
+	var receivedBytes []byte
+	typeReader := lib.NewTypeReader()
+
+	var postMessageHandler = func(instanceId int32, ty, msg string) {
+		id, mType, bin := parseBrowsperResponse(msg, t)
+		if mType == lib.ResponseTypeMessage {
+			var decodedBytes []byte
+			if err := vom.Decode(bin, &decodedBytes); err != nil {
+				t.Fatalf("Failed to decode type bytes: %v", err)
+			}
+			typeReader.Add(hex.EncodeToString(decodedBytes))
+			return
+		}
+		receivedInstanceId = instanceId
+		receivedType = ty
+		messageType = mType
+		messageId = id
+		receivedBytes = bin
+		receivedResponse <- true
+	}
+
+	v23.GetNamespace(ctx).SetRoots(root)
+	browspr := NewBrowspr(ctx, postMessageHandler, &spec, "/mock:1234/identd", []string{root}, principal.NewInMemorySerializer())
+
+	// browspr sets its namespace root to use the "ws" protocol, but we want to force "tcp" here.
+	browspr.namespaceRoots = []string{root}
+
+	browspr.accountManager.SetMockBlesser(newMockBlesserService(v23.GetPrincipal(ctx)))
+
+	msgInstanceId := int32(11)
+	msgOrigin := "http://test-origin.com"
+
+	// Associate the origin with the root accounts' blessings, otherwise a
+	// dummy account will be used and will be rejected by the authorizer.
+	accountName := "test-account"
+	bp := v23.GetPrincipal(browspr.ctx)
+	if err := browspr.principalManager.AddAccount(accountName, bp.BlessingStore().Default()); err != nil {
+		t.Fatalf("Failed to add account: %v", err)
+	}
+	if err := browspr.accountManager.AssociateAccount(ctx, msgOrigin, accountName, nil); err != nil {
+		t.Fatalf("Failed to associate account: %v", err)
+	}
+
+	rpc := app.RpcRequest{
+		Name:        mockServerName,
+		Method:      "BasicCall",
+		NumInArgs:   1,
+		NumOutArgs:  1,
+		IsStreaming: false,
+		Deadline:    vdltime.Deadline{},
+	}
+
+	var typeBuf bytes.Buffer
+	typeEncoder := vom.NewTypeEncoder(&typeBuf)
+	var buf bytes.Buffer
+	encoder := vom.NewEncoderWithTypeEncoder(&buf, typeEncoder)
+	if err := encoder.Encode(rpc); err != nil {
+		t.Fatalf("Failed to vom encode rpc message: %v", err)
+	}
+	if err := encoder.Encode("InputValue"); err != nil {
+		t.Fatalf("Failed to vom encode rpc message: %v", err)
+	}
+
+	vomRPC := hex.EncodeToString(buf.Bytes())
+
+	msg := app.Message{
+		Id:   1,
+		Data: vomRPC,
+		Type: app.VeyronRequestMessage,
+	}
+
+	typeMessage := app.Message{
+		Id:   0,
+		Data: hex.EncodeToString(typeBuf.Bytes()),
+		Type: app.TypeMessage,
+	}
+	createInstanceMessage := CreateInstanceMessage{
+		InstanceId:     msgInstanceId,
+		Origin:         msgOrigin,
+		NamespaceRoots: nil,
+		Proxy:          "",
+	}
+	_, err = browspr.HandleCreateInstanceRpc(vdl.ValueOf(createInstanceMessage))
+
+	err = browspr.HandleMessage(msgInstanceId, msgOrigin, typeMessage)
+	if err != nil {
+		t.Fatalf("Error while handling type message: %v", err)
+	}
+
+	err = browspr.HandleMessage(msgInstanceId, msgOrigin, msg)
+	if err != nil {
+		t.Fatalf("Error while handling message: %v", err)
+	}
+
+	<-receivedResponse
+
+	if receivedInstanceId != msgInstanceId {
+		t.Errorf("Received unexpected instance id: %d. Expected: %d", receivedInstanceId, msgInstanceId)
+	}
+	if receivedType != "browsprMsg" {
+		t.Errorf("Received unexpected response type. Expected: %q, but got %q", "browsprMsg", receivedType)
+	}
+
+	if messageId != 1 {
+		t.Errorf("Id was %v, expected %v", messageId, int32(1))
+	}
+
+	if lib.ResponseType(messageType) != lib.ResponseFinal {
+		t.Errorf("Message type was %v, expected %v", messageType, lib.ResponseFinal)
+	}
+
+	var data string
+	if err := vom.NewDecoder(bytes.NewBuffer(receivedBytes)).Decode(&data); err != nil {
+		t.Fatalf("Failed to unmarshall outgoing response: %v", err)
+	}
+
+	var result app.RpcResponse
+	dataBytes, err := hex.DecodeString(data)
+	if err != nil {
+		t.Errorf("Failed to hex decode from %v: %v", data, err)
+	}
+
+	decoder := vom.NewDecoderWithTypeDecoder(bytes.NewBuffer(dataBytes), vom.NewTypeDecoder(typeReader))
+	if err := decoder.Decode(&result); err != nil {
+		t.Errorf("Failed to vom decode args from %v: %v", data, err)
+	}
+	if got, want := result.OutArgs[0], vdl.StringValue("[InputValue]"); !vdl.EqualValue(got, want) {
+		t.Errorf("Result got %v, want %v", got, want)
+	}
+}
diff --git a/services/wspr/internal/browspr/pipe.go b/services/wspr/internal/browspr/pipe.go
new file mode 100644
index 0000000..4385238
--- /dev/null
+++ b/services/wspr/internal/browspr/pipe.go
@@ -0,0 +1,90 @@
+// 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 browspr
+
+import (
+	"v.io/v23/context"
+	"v.io/x/ref/services/wspr/internal/app"
+	"v.io/x/ref/services/wspr/internal/lib"
+)
+
+// pipe controls the flow of messages for a specific instance (corresponding to a specific tab).
+type pipe struct {
+	browspr    *Browspr
+	controller *app.Controller
+	origin     string
+	instanceId int32
+}
+
+func newPipe(b *Browspr, instanceId int32, origin string, namespaceRoots []string, proxy string) *pipe {
+	pipe := &pipe{
+		browspr:    b,
+		origin:     origin,
+		instanceId: instanceId,
+	}
+
+	// TODO(bprosnitz) LookupPrincipal() maybe should not take a string in the future.
+	p, err := b.accountManager.LookupPrincipal(origin)
+	if err != nil {
+		// TODO(nlacasse, bjornick): This code should go away once we
+		// start requiring authentication.  At that point, we should
+		// just return an error to the client.
+		b.ctx.Errorf("No principal associated with origin %v, creating a new principal with self-signed blessing from browspr: %v", origin, err)
+
+		dummyAccount, err := b.principalManager.DummyAccount()
+		if err != nil {
+			b.ctx.Errorf("principalManager.DummyAccount() failed: %v", err)
+			return nil
+		}
+
+		if err := b.accountManager.AssociateAccount(b.ctx, origin, dummyAccount, nil); err != nil {
+			b.ctx.Errorf("accountManager.AssociateAccount(%v, %v, %v) failed: %v", origin, dummyAccount, nil, err)
+			return nil
+		}
+		p, err = b.accountManager.LookupPrincipal(origin)
+		if err != nil {
+			return nil
+		}
+	}
+
+	// Shallow copy browspr's default listenSpec.  If we have passed in a
+	// proxy, set listenSpec.proxy.
+	listenSpec := *b.listenSpec
+	if proxy != "" {
+		listenSpec.Proxy = proxy
+	}
+
+	// If we have been passed in namespace roots, pass them to NewController, otherwise pass browspr's default.
+	if namespaceRoots == nil {
+		namespaceRoots = b.namespaceRoots
+	}
+
+	pipe.controller, err = app.NewController(b.ctx, pipe.createWriter, &listenSpec, namespaceRoots, p)
+	if err != nil {
+		b.ctx.Errorf("Could not create controller: %v", err)
+		return nil
+	}
+
+	return pipe
+}
+
+func (p *pipe) createWriter(messageId int32) lib.ClientWriter {
+	return &postMessageWriter{
+		messageId: messageId,
+		p:         p,
+		ctx:       p.browspr.ctx,
+	}
+}
+
+func (p *pipe) cleanup(ctx *context.T) {
+	ctx.VI(0).Info("Cleaning up pipe")
+	p.controller.Cleanup(ctx)
+}
+
+func (p *pipe) handleMessage(msg app.Message) error {
+	writer := p.createWriter(msg.Id)
+	p.controller.HandleIncomingMessage(msg, writer)
+	return nil
+}
diff --git a/services/wspr/internal/browspr/v23_internal_test.go b/services/wspr/internal/browspr/v23_internal_test.go
new file mode 100644
index 0000000..6c94e49
--- /dev/null
+++ b/services/wspr/internal/browspr/v23_internal_test.go
@@ -0,0 +1,20 @@
+// 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 browspr
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	os.Exit(m.Run())
+}
diff --git a/services/wspr/internal/browspr/writer.go b/services/wspr/internal/browspr/writer.go
new file mode 100644
index 0000000..bc3a60a
--- /dev/null
+++ b/services/wspr/internal/browspr/writer.go
@@ -0,0 +1,31 @@
+// 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 browspr
+
+import (
+	"v.io/v23/context"
+	"v.io/x/ref/services/wspr/internal/app"
+	"v.io/x/ref/services/wspr/internal/lib"
+)
+
+// postMessageWriter is a lib.ClientWriter that handles sending messages over postMessage to the extension.
+type postMessageWriter struct {
+	messageId int32
+	p         *pipe
+	ctx       *context.T
+}
+
+func (w *postMessageWriter) Send(messageType lib.ResponseType, data interface{}) error {
+	outMsg, err := app.ConstructOutgoingMessage(w.messageId, messageType, data)
+	if err != nil {
+		return err
+	}
+	w.p.browspr.postMessage(w.p.instanceId, "browsprMsg", outMsg)
+	return nil
+}
+
+func (w *postMessageWriter) Error(err error) {
+	w.Send(lib.ResponseError, app.FormatAsVerror(w.p.browspr.ctx, err))
+}
diff --git a/services/wspr/internal/channel/channel.go b/services/wspr/internal/channel/channel.go
new file mode 100644
index 0000000..d7863f9
--- /dev/null
+++ b/services/wspr/internal/channel/channel.go
@@ -0,0 +1,113 @@
+// 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 channel
+
+import (
+	"fmt"
+	"sync"
+
+	"v.io/v23/vdl"
+)
+
+type RequestHandler func(*vdl.Value) (*vdl.Value, error)
+
+type MessageSender func(Message)
+
+type Channel struct {
+	messageHandler MessageSender
+
+	lastSeq          uint32
+	handlers         map[string]RequestHandler
+	pendingResponses map[uint32]chan Response
+	lock             sync.Mutex
+}
+
+func NewChannel(messageHandler MessageSender) *Channel {
+	return &Channel{
+		messageHandler:   messageHandler,
+		handlers:         map[string]RequestHandler{},
+		pendingResponses: map[uint32]chan Response{},
+	}
+}
+
+func (c *Channel) PerformRpc(typ string, body *vdl.Value) (*vdl.Value, error) {
+	c.lock.Lock()
+	c.lastSeq++
+	lastSeq := c.lastSeq
+	m := MessageRequest{Request{
+		Type: typ,
+		Seq:  lastSeq,
+		Body: body,
+	}}
+	pending := make(chan Response, 1)
+	c.pendingResponses[lastSeq] = pending
+	c.lock.Unlock()
+
+	go c.messageHandler(m)
+	response := <-pending
+
+	c.lock.Lock()
+	delete(c.pendingResponses, lastSeq)
+	c.lock.Unlock()
+
+	if response.Err == "" {
+		return response.Body, nil
+	}
+	return response.Body, fmt.Errorf(response.Err)
+}
+
+func (c *Channel) RegisterRequestHandler(typ string, handler RequestHandler) {
+	c.lock.Lock()
+	c.handlers[typ] = handler
+	c.lock.Unlock()
+}
+
+func (c *Channel) handleRequest(req Request) {
+	// Call handler.
+	c.lock.Lock()
+	handler, ok := c.handlers[req.Type]
+	c.lock.Unlock()
+	if !ok {
+		panic(fmt.Errorf("Unknown handler: %s", req.Type))
+	}
+
+	result, err := handler(req.Body)
+	errMsg := ""
+	if err != nil {
+		errMsg = err.Error()
+	}
+	m := MessageResponse{Response{
+		ReqSeq: req.Seq,
+		Err:    errMsg,
+		Body:   result,
+	}}
+	c.messageHandler(m)
+}
+
+func (c *Channel) handleResponse(resp Response) {
+	seq := resp.ReqSeq
+	c.lock.Lock()
+	pendingResponse, ok := c.pendingResponses[seq]
+	c.lock.Unlock()
+	if !ok {
+		panic("Received invalid response code")
+	}
+
+	pendingResponse <- resp
+}
+
+func (c *Channel) HandleMessage(m Message) {
+	switch r := m.(type) {
+	// Run the handlers in goroutines so we don't block the main thread.
+	// This is particularly important for the request handler, since it can
+	// potentially do a lot of work.
+	case MessageRequest:
+		go c.handleRequest(r.Value)
+	case MessageResponse:
+		go c.handleResponse(r.Value)
+	default:
+		panic(fmt.Sprintf("Unknown message type: %T", m))
+	}
+}
diff --git a/services/wspr/internal/channel/channel.vdl b/services/wspr/internal/channel/channel.vdl
new file mode 100644
index 0000000..0fc7ebc
--- /dev/null
+++ b/services/wspr/internal/channel/channel.vdl
@@ -0,0 +1,22 @@
+// 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 channel
+
+type Request struct {
+	Type string
+	Seq  uint32
+	Body any
+}
+
+type Response struct {
+	ReqSeq uint32
+	Err    string // TODO(bprosnitz) change this back to error when it is possible to do so. (issue 368)
+	Body   any
+}
+
+type Message union {
+	Request  Request
+	Response Response
+}
diff --git a/services/wspr/internal/channel/channel.vdl.go b/services/wspr/internal/channel/channel.vdl.go
new file mode 100644
index 0000000..c411dbb
--- /dev/null
+++ b/services/wspr/internal/channel/channel.vdl.go
@@ -0,0 +1,78 @@
+// 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 by the vanadium vdl tool.
+// Source: channel.vdl
+
+package channel
+
+import (
+	// VDL system imports
+	"v.io/v23/vdl"
+)
+
+type Request struct {
+	Type string
+	Seq  uint32
+	Body *vdl.Value
+}
+
+func (Request) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/services/wspr/internal/channel.Request"`
+}) {
+}
+
+type Response struct {
+	ReqSeq uint32
+	Err    string // TODO(bprosnitz) change this back to error when it is possible to do so. (issue 368)
+	Body   *vdl.Value
+}
+
+func (Response) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/services/wspr/internal/channel.Response"`
+}) {
+}
+
+type (
+	// Message represents any single field of the Message union type.
+	Message interface {
+		// Index returns the field index.
+		Index() int
+		// Interface returns the field value as an interface.
+		Interface() interface{}
+		// Name returns the field name.
+		Name() string
+		// __VDLReflect describes the Message union type.
+		__VDLReflect(__MessageReflect)
+	}
+	// MessageRequest represents field Request of the Message union type.
+	MessageRequest struct{ Value Request }
+	// MessageResponse represents field Response of the Message union type.
+	MessageResponse struct{ Value Response }
+	// __MessageReflect describes the Message union type.
+	__MessageReflect struct {
+		Name  string `vdl:"v.io/x/ref/services/wspr/internal/channel.Message"`
+		Type  Message
+		Union struct {
+			Request  MessageRequest
+			Response MessageResponse
+		}
+	}
+)
+
+func (x MessageRequest) Index() int                    { return 0 }
+func (x MessageRequest) Interface() interface{}        { return x.Value }
+func (x MessageRequest) Name() string                  { return "Request" }
+func (x MessageRequest) __VDLReflect(__MessageReflect) {}
+
+func (x MessageResponse) Index() int                    { return 1 }
+func (x MessageResponse) Interface() interface{}        { return x.Value }
+func (x MessageResponse) Name() string                  { return "Response" }
+func (x MessageResponse) __VDLReflect(__MessageReflect) {}
+
+func init() {
+	vdl.Register((*Request)(nil))
+	vdl.Register((*Response)(nil))
+	vdl.Register((*Message)(nil))
+}
diff --git a/services/wspr/internal/channel/channel_nacl/channel_nacl.go b/services/wspr/internal/channel/channel_nacl/channel_nacl.go
new file mode 100644
index 0000000..96ae623
--- /dev/null
+++ b/services/wspr/internal/channel/channel_nacl/channel_nacl.go
@@ -0,0 +1,65 @@
+// 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 channel_nacl
+
+import (
+	"bytes"
+	"fmt"
+	"runtime/ppapi"
+
+	"v.io/v23/vdl"
+	"v.io/v23/vom"
+	"v.io/x/ref/services/wspr/internal/channel" // contains most of the logic, factored out for testing
+)
+
+type Channel struct {
+	impl      *channel.Channel
+	ppapiInst ppapi.Instance
+}
+
+func sendMessageToBrowser(ppapiInst ppapi.Instance, m channel.Message) {
+	var outBuf bytes.Buffer
+	enc := vom.NewEncoder(&outBuf)
+	if err := enc.Encode(m); err != nil {
+		panic(fmt.Sprintf("Error encoding message %v: %v", m, err))
+	}
+	outVar := ppapi.VarFromByteSlice(outBuf.Bytes())
+	ppapiInst.PostMessage(outVar)
+}
+
+func NewChannel(ppapiInst ppapi.Instance) *Channel {
+	sendMessageFunc := func(m channel.Message) {
+		sendMessageToBrowser(ppapiInst, m)
+	}
+	return &Channel{
+		impl:      channel.NewChannel(sendMessageFunc),
+		ppapiInst: ppapiInst,
+	}
+}
+
+func (c *Channel) RegisterRequestHandler(typ string, handler channel.RequestHandler) {
+	c.impl.RegisterRequestHandler(typ, handler)
+}
+
+func (c *Channel) PerformRpc(typ string, body *vdl.Value) (*vdl.Value, error) {
+	return c.impl.PerformRpc(typ, body)
+}
+
+func (c *Channel) HandleMessage(v ppapi.Var) {
+	// Read input message
+	b, err := v.AsByteSlice()
+	if err != nil {
+		panic(fmt.Sprintf("Cannot convert message to byte slice: %v", err))
+	}
+
+	buf := bytes.NewBuffer(b)
+	dec := vom.NewDecoder(buf)
+	var m channel.Message
+	if err := dec.Decode(&m); err != nil {
+		panic(fmt.Sprintf("Error decoding message: %v", err))
+	}
+
+	c.impl.HandleMessage(m)
+}
diff --git a/services/wspr/internal/channel/channel_test.go b/services/wspr/internal/channel/channel_test.go
new file mode 100644
index 0000000..4513bd1
--- /dev/null
+++ b/services/wspr/internal/channel/channel_test.go
@@ -0,0 +1,109 @@
+// 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 channel_test
+
+import (
+	"fmt"
+	"sync"
+	"testing"
+
+	"v.io/v23/vdl"
+	"v.io/x/ref/services/wspr/internal/channel"
+)
+
+func TestChannelRpcs(t *testing.T) {
+	// Two channels are used and different test send in different directions.
+	var bHandler channel.MessageSender
+	channelA := channel.NewChannel(func(msg channel.Message) {
+		bHandler(msg)
+	})
+	channelB := channel.NewChannel(channelA.HandleMessage)
+	bHandler = channelB.HandleMessage
+
+	type testCase struct {
+		SendChannel *channel.Channel
+		RecvChannel *channel.Channel
+		Type        string
+		ReqVal      int
+		RespVal     int
+		Err         error
+	}
+
+	// The list of tests to run concurrently in goroutines.
+	// Half of the tests are with different type keys to test multiple type keys.
+	// Half of the tests use the same type key
+	// One test returns an error.
+	tests := []testCase{}
+	const reusedTypeName string = "reusedTypeName"
+	expectedNumSuccessfulEachDirection := 128
+	for i := 0; i < expectedNumSuccessfulEachDirection; i++ {
+		tests = append(tests, testCase{channelA, channelB, fmt.Sprintf("Type%d", i), i, i + 1000, nil})
+		tests = append(tests, testCase{channelB, channelA, reusedTypeName, -i - 1, -i - 1001, nil})
+	}
+	expectedNumFailures := 1
+	tests = append(tests, testCase{channelB, channelA, "Type3", 0, 0, fmt.Errorf("TestError")})
+	expectedNumCalls := expectedNumSuccessfulEachDirection*2 + expectedNumFailures
+	callCountLock := sync.Mutex{}
+	numCalls := 0
+
+	// reusedHandler handles requests to the same type name.
+	reusedHandler := func(v *vdl.Value) (*vdl.Value, error) {
+		callCountLock.Lock()
+		numCalls++
+		callCountLock.Unlock()
+
+		return vdl.Int64Value(v.Int() - 1000), nil
+	}
+
+	wg := sync.WaitGroup{}
+	wg.Add(len(tests))
+	var testGoRoutine = func(i int, test testCase) {
+		defer wg.Done()
+
+		// Get the message handler. Either the reused handle or a unique handle for this
+		// test, depending on the type name.
+		var handler func(v *vdl.Value) (*vdl.Value, error)
+		if test.Type == reusedTypeName {
+			handler = reusedHandler
+		} else {
+			handler = func(v *vdl.Value) (*vdl.Value, error) {
+				callCountLock.Lock()
+				numCalls++
+				callCountLock.Unlock()
+
+				if got, want := v, vdl.Int64Value(int64(test.ReqVal)); !vdl.EqualValue(got, want) {
+					t.Errorf("For test %d, got %v, want %v", i, got, want)
+				}
+				return vdl.Int64Value(int64(test.RespVal)), test.Err
+			}
+		}
+		test.RecvChannel.RegisterRequestHandler(test.Type, handler)
+
+		// Perform the RPC.
+		result, err := test.SendChannel.PerformRpc(test.Type, vdl.Int64Value(int64(test.ReqVal)))
+		if test.Err != nil {
+			if err == nil {
+				t.Errorf("For test %d, expected an error but didn't get one", i)
+			}
+		} else {
+			if err != nil {
+				t.Errorf("For test %d, received unexpected error %v", i, err)
+				return
+			}
+			if got, want := result, vdl.Int64Value(int64(test.RespVal)); !vdl.EqualValue(got, want) {
+				t.Errorf("For test %d, got %v, want %v", i, got, want)
+			}
+		}
+	}
+	for i, test := range tests {
+		go testGoRoutine(i, test)
+	}
+
+	wg.Wait()
+
+	if numCalls != expectedNumCalls {
+		t.Errorf("Expected to receive %d rpcs, but only got %d", expectedNumCalls, numCalls)
+	}
+}
diff --git a/services/wspr/internal/lib/binary_util.go b/services/wspr/internal/lib/binary_util.go
new file mode 100644
index 0000000..fb4afab
--- /dev/null
+++ b/services/wspr/internal/lib/binary_util.go
@@ -0,0 +1,78 @@
+// 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 lib
+
+import (
+	"v.io/v23/verror"
+)
+
+const pkgPath = "v.io/x/ref/services/wspr/internal/lib"
+
+const uint64Size = 8
+
+var (
+	errInvalid      = verror.Register(pkgPath+".errInvalid", verror.NoRetry, "{1:}{2:} wspr: invalid encoding{:_}")
+	errEOF          = verror.Register(pkgPath+".errEOF", verror.NoRetry, "{1:}{2:} wspr: eof{:_}")
+	errUintOverflow = verror.Register(pkgPath+".errUintOverflow", verror.NoRetry, "{1:}{2:} wspr: scalar larger than 8 bytes{:_}")
+)
+
+// This code has been copied from the vom package and should be kept up to date
+// with it.
+
+// Unsigned integers are the basis for all other primitive values.  This is a
+// two-state encoding.  If the number is less than 128 (0 through 0x7f), its
+// value is written directly.  Otherwise the value is written in big-endian byte
+// order preceded by the negated byte length.
+func BinaryEncodeUint(v uint64) []byte {
+	switch {
+	case v <= 0x7f:
+		return []byte{byte(v)}
+	case v <= 0xff:
+		return []byte{0xff, byte(v)}
+	case v <= 0xffff:
+		return []byte{0xfe, byte(v >> 8), byte(v)}
+	case v <= 0xffffff:
+		return []byte{0xfd, byte(v >> 16), byte(v >> 8), byte(v)}
+	case v <= 0xffffffff:
+		return []byte{0xfc, byte(v >> 24), byte(v >> 16), byte(v >> 8), byte(v)}
+	case v <= 0xffffffffff:
+		return []byte{0xfb, byte(v >> 32), byte(v >> 24),
+			byte(v >> 16), byte(v >> 8), byte(v)}
+	case v <= 0xffffffffffff:
+		return []byte{0xfa, byte(v >> 40), byte(v >> 32), byte(v >> 24),
+			byte(v >> 16), byte(v >> 8), byte(v)}
+	case v <= 0xffffffffffffff:
+		return []byte{0xf9, byte(v >> 48), byte(v >> 40), byte(v >> 32),
+			byte(v >> 24), byte(v >> 16), byte(v >> 8), byte(v)}
+	default:
+		return []byte{0xf9, byte(v >> 56), byte(v >> 48), byte(v >> 40),
+			byte(v >> 32), byte(v >> 24), byte(v >> 16), byte(v >> 8), byte(v)}
+	}
+}
+
+func BinaryDecodeUint(input []byte) (v uint64, byteLen int, err error) {
+	if len(input) == 0 {
+		return 0, 0, verror.New(errEOF, nil)
+	}
+	firstByte := input[0]
+	if firstByte <= 0x7f {
+		return uint64(firstByte), 1, nil
+	}
+
+	if firstByte <= 0xdf {
+		return 0, 0, verror.New(errInvalid, nil)
+	}
+	byteLen = int(-int8(firstByte))
+	if byteLen < 1 || byteLen > uint64Size {
+		return 0, 0, verror.New(errUintOverflow, nil)
+	}
+	if len(input) < byteLen {
+		return 0, 0, verror.New(errEOF, nil)
+	}
+	for i := 1; i < byteLen; i++ {
+		v = v<<8 | uint64(input[i])
+	}
+	return
+}
diff --git a/services/wspr/internal/lib/case.go b/services/wspr/internal/lib/case.go
new file mode 100644
index 0000000..4c5fbba
--- /dev/null
+++ b/services/wspr/internal/lib/case.go
@@ -0,0 +1,21 @@
+// 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 lib
+
+import "unicode"
+
+func LowercaseFirstCharacter(s string) string {
+	for _, r := range s {
+		return string(unicode.ToLower(r)) + s[1:]
+	}
+	return ""
+}
+
+func UppercaseFirstCharacter(s string) string {
+	for _, r := range s {
+		return string(unicode.ToUpper(r)) + s[1:]
+	}
+	return ""
+}
diff --git a/services/wspr/internal/lib/hex_vom.go b/services/wspr/internal/lib/hex_vom.go
new file mode 100644
index 0000000..a7ac6c0
--- /dev/null
+++ b/services/wspr/internal/lib/hex_vom.go
@@ -0,0 +1,50 @@
+// 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 lib
+
+import (
+	"bytes"
+	"encoding/hex"
+	"fmt"
+
+	"v.io/v23/vom"
+)
+
+func HexVomEncode(v interface{}, te *vom.TypeEncoder) (string, error) {
+	var buf bytes.Buffer
+	var encoder *vom.Encoder
+	if te != nil {
+		encoder = vom.NewEncoderWithTypeEncoder(&buf, te)
+	} else {
+
+		encoder = vom.NewEncoder(&buf)
+	}
+	if err := encoder.Encode(v); err != nil {
+		return "", err
+	}
+	return hex.EncodeToString(buf.Bytes()), nil
+}
+
+func HexVomEncodeOrDie(v interface{}, te *vom.TypeEncoder) string {
+	s, err := HexVomEncode(v, te)
+	if err != nil {
+		panic(err)
+	}
+	return s
+}
+
+func HexVomDecode(data string, v interface{}, td *vom.TypeDecoder) error {
+	binbytes, err := hex.DecodeString(data)
+	if err != nil {
+		return fmt.Errorf("Error decoding hex string %q: %v", data, err)
+	}
+	var decoder *vom.Decoder
+	if td != nil {
+		decoder = vom.NewDecoderWithTypeDecoder(bytes.NewReader(binbytes), td)
+	} else {
+		decoder = vom.NewDecoder(bytes.NewReader(binbytes))
+	}
+	return decoder.Decode(v)
+}
diff --git a/services/wspr/internal/lib/hex_vom_test.go b/services/wspr/internal/lib/hex_vom_test.go
new file mode 100644
index 0000000..728f976
--- /dev/null
+++ b/services/wspr/internal/lib/hex_vom_test.go
@@ -0,0 +1,63 @@
+// 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 lib
+
+import (
+	"encoding/hex"
+	"reflect"
+	"testing"
+	"time"
+)
+
+func TestReadBeforeData(t *testing.T) {
+	reader := NewTypeReader()
+	input := []byte{0, 2, 3, 4, 5}
+	data := make([]byte, 5)
+	go func() {
+		<-time.After(100 * time.Millisecond)
+		reader.Add(hex.EncodeToString(input))
+	}()
+	n, err := reader.Read(data)
+	if n != len(data) {
+		t.Errorf("Read the wrong number of bytes, wanted:%d, got:%d", len(data), n)
+	}
+
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+	if !reflect.DeepEqual(input, data) {
+		t.Errorf("wrong data, want:%x, got:%x", input, data)
+	}
+}
+
+func TestReadWithPartialData(t *testing.T) {
+	reader := NewTypeReader()
+	input := []byte{0, 2, 3, 4, 5}
+	data := make([]byte, 5)
+	reader.Add(hex.EncodeToString(input[:2]))
+	go func() {
+		<-time.After(300 * time.Millisecond)
+		reader.Add(hex.EncodeToString(input[2:]))
+	}()
+	totalRead := 0
+	for {
+		n, err := reader.Read(data[totalRead:])
+		totalRead += n
+		if err != nil {
+			t.Errorf("Unexpected error: %v", err)
+			break
+		}
+		if totalRead == 5 {
+			break
+		}
+	}
+	if totalRead != len(data) {
+		t.Errorf("Read the wrong number of bytes, wanted:%d, got:%d", len(data), totalRead)
+	}
+
+	if !reflect.DeepEqual(input, data) {
+		t.Errorf("wrong data, want:%x, got:%x", input, data)
+	}
+}
diff --git a/services/wspr/internal/lib/signature_manager.go b/services/wspr/internal/lib/signature_manager.go
new file mode 100644
index 0000000..41271e8
--- /dev/null
+++ b/services/wspr/internal/lib/signature_manager.go
@@ -0,0 +1,127 @@
+// 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 lib
+
+import (
+	"sync"
+	"time"
+
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/rpc/reserved"
+	"v.io/v23/vdlroot/signature"
+	"v.io/v23/verror"
+)
+
+type SignatureManager interface {
+	Signature(ctx *context.T, name string, opts ...rpc.CallOpt) ([]signature.Interface, error)
+	FlushCacheEntry(name string)
+}
+
+// signatureManager can be used to discover the signature of a remote service
+// It has built-in caching and TTL support.
+type signatureManager struct {
+	// protects the cache and initialization
+	sync.Mutex
+
+	// map of name to service signature and last-accessed time
+	// TODO(aghassemi) GC for expired cache entries
+	cache map[string]*cacheEntry
+
+	// we keep track of current pending request so we don't issue
+	// multiple signature requests to the same server simultaneously.
+	pendingSignatures map[string]chan struct{}
+}
+
+// NewSignatureManager creates and initialized a new Signature Manager
+func NewSignatureManager() SignatureManager {
+	return &signatureManager{
+		cache:             make(map[string]*cacheEntry),
+		pendingSignatures: map[string]chan struct{}{},
+	}
+}
+
+const (
+	// ttl from the last-accessed time.
+	ttl = time.Duration(time.Hour)
+)
+
+type cacheEntry struct {
+	sig          []signature.Interface
+	lastAccessed time.Time
+}
+
+// expired returns whether the cache entry is expired or not
+func (c cacheEntry) expired() bool {
+	return time.Now().Sub(c.lastAccessed) > ttl
+}
+
+func (sm *signatureManager) lookupCacheLocked(name string) []signature.Interface {
+	if entry := sm.cache[name]; entry != nil && !entry.expired() {
+		entry.lastAccessed = time.Now()
+		return entry.sig
+	}
+	return nil
+}
+
+// Signature fetches the signature for the given service name.  It either
+// returns the signature from the cache, or blocks until it fetches the
+// signature from the remote server.
+func (sm *signatureManager) Signature(ctx *context.T, name string, opts ...rpc.CallOpt) ([]signature.Interface, error) {
+	sm.Lock()
+
+	if sigs := sm.lookupCacheLocked(name); sigs != nil {
+		sm.Unlock()
+		return sigs, nil
+	}
+
+	ch, found := sm.pendingSignatures[name]
+
+	if !found {
+		ch = make(chan struct{})
+		sm.pendingSignatures[name] = ch
+	}
+	sm.Unlock()
+
+	if found {
+		<-ch
+		// If the channel is closed then we know that the outstanding request finished
+		// if it failed then there will not be a valid entry in the cache.
+		sm.Lock()
+		result := sm.lookupCacheLocked(name)
+		sm.Unlock()
+		var err error
+		if result == nil {
+			return nil, verror.New(verror.ErrNoServers, ctx, name, err)
+
+		}
+		return result, nil
+	}
+
+	// Fetch from the remote server.
+	sig, err := reserved.Signature(ctx, name, opts...)
+	sm.Lock()
+	// On cleanup we need to close the channel, remove the entry from the
+	defer func() {
+		close(ch)
+		delete(sm.pendingSignatures, name)
+		sm.Unlock()
+	}()
+	if err != nil {
+		return nil, verror.New(verror.ErrNoServers, ctx, name, err)
+	}
+
+	// Add to the cache.
+	sm.cache[name] = &cacheEntry{
+		sig:          sig,
+		lastAccessed: time.Now(),
+	}
+	return sig, nil
+}
+
+// FlushCacheEntry removes the cached signature for the given name
+func (sm *signatureManager) FlushCacheEntry(name string) {
+	delete(sm.cache, name)
+}
diff --git a/services/wspr/internal/lib/signature_manager_test.go b/services/wspr/internal/lib/signature_manager_test.go
new file mode 100644
index 0000000..689df80
--- /dev/null
+++ b/services/wspr/internal/lib/signature_manager_test.go
@@ -0,0 +1,169 @@
+// 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 lib
+
+import (
+	"reflect"
+	"sync"
+	"testing"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/vdl"
+	"v.io/v23/vdlroot/signature"
+	"v.io/x/ref/runtime/factories/fake"
+	"v.io/x/ref/test"
+)
+
+const (
+	name = "/vanadium/name"
+)
+
+func initRuntime(t *testing.T) (*context.T, clientWithTimesCalled, v23.Shutdown) {
+	ctx, shutdown := test.V23InitAnon()
+	initialSig := []signature.Interface{
+		{
+			Methods: []signature.Method{
+				{
+					Name:   "Method1",
+					InArgs: []signature.Arg{{Type: vdl.StringType}},
+				},
+			},
+		},
+	}
+	client := newSimpleClient(
+		map[string][]interface{}{
+			"__Signature": []interface{}{initialSig},
+		},
+	)
+	ctx = fake.SetClient(ctx, client)
+	return ctx, client, shutdown
+}
+
+func TestFetching(t *testing.T) {
+	ctx, _, shutdown := initRuntime(t)
+	defer shutdown()
+
+	sm := NewSignatureManager()
+	got, err := sm.Signature(ctx, name)
+	if err != nil {
+		t.Errorf(`Did not expect an error but got %v`, err)
+		return
+	}
+
+	want := []signature.Interface{
+		{
+			Methods: []signature.Method{
+				{
+					Name:   "Method1",
+					InArgs: []signature.Arg{{Type: vdl.StringType}},
+				},
+			},
+		},
+	}
+	if !reflect.DeepEqual(got, want) {
+		t.Errorf(`Signature got %v, want %v`, got, want)
+	}
+}
+
+func TestThatCachedAfterFetching(t *testing.T) {
+	ctx, _, shutdown := initRuntime(t)
+	defer shutdown()
+
+	sm := NewSignatureManager().(*signatureManager)
+	sig, _ := sm.Signature(ctx, name)
+	cache, ok := sm.cache[name]
+	if !ok {
+		t.Errorf(`Signature manager did not cache the results`)
+		return
+	}
+	if got, want := cache.sig, sig; !reflect.DeepEqual(got, want) {
+		t.Errorf(`Cached signature got %v, want %v`, got, want)
+	}
+}
+
+func TestThatCacheIsUsed(t *testing.T) {
+	ctx, client, shutdown := initRuntime(t)
+	defer shutdown()
+
+	// call twice
+	sm := NewSignatureManager()
+	sm.Signature(ctx, name)
+	sm.Signature(ctx, name)
+
+	// expect number of calls to Signature method of client to still be 1 since cache
+	// should have been used despite the second call
+	if client.TimesCalled("__Signature") != 1 {
+		t.Errorf("Signature cache was not used for the second call")
+	}
+}
+
+func TestThatLastAccessedGetUpdated(t *testing.T) {
+	ctx, _, shutdown := initRuntime(t)
+	defer shutdown()
+
+	sm := NewSignatureManager().(*signatureManager)
+	sm.Signature(ctx, name)
+	// make last accessed be in the past to account for the fact that
+	// two consecutive calls to time.Now() can return identical values.
+	sm.cache[name].lastAccessed = sm.cache[name].lastAccessed.Add(-ttl / 2)
+	prevAccess := sm.cache[name].lastAccessed
+
+	// access again
+	sm.Signature(ctx, name)
+	newAccess := sm.cache[name].lastAccessed
+
+	if !newAccess.After(prevAccess) {
+		t.Errorf("LastAccessed was not updated for cache entry")
+	}
+}
+
+func TestThatTTLExpires(t *testing.T) {
+	ctx, client, shutdown := initRuntime(t)
+	defer shutdown()
+
+	sm := NewSignatureManager().(*signatureManager)
+	sm.Signature(ctx, name)
+
+	// make last accessed go over the ttl
+	sm.cache[name].lastAccessed = sm.cache[name].lastAccessed.Add(-2 * ttl)
+
+	// make a second call
+	sm.Signature(ctx, name)
+
+	// expect number of calls to Signature method of client to be 2 since cache should have expired
+	if client.TimesCalled("__Signature") != 2 {
+		t.Errorf("Cache was still used but TTL had passed. It should have been fetched again")
+	}
+}
+
+func TestConcurrency(t *testing.T) {
+	ctx, client, shutdown := initRuntime(t)
+	defer shutdown()
+
+	sm := NewSignatureManager().(*signatureManager)
+	var wg sync.WaitGroup
+
+	wg.Add(2)
+	// Even though the signature calls return immediately in the fake client,
+	// running this with the race detector should find races if the locking is done
+	// poorly.
+	go func() {
+		sm.Signature(ctx, name)
+		wg.Done()
+	}()
+
+	go func() {
+		sm.Signature(ctx, name)
+		wg.Done()
+	}()
+
+	wg.Wait()
+	// expect number of calls to Signature method of client to be 1 since the second call should
+	// wait until the first finished.
+	if client.TimesCalled("__Signature") != 1 {
+		t.Errorf("__Signature should only be called once.")
+	}
+}
diff --git a/services/wspr/internal/lib/simple_client.go b/services/wspr/internal/lib/simple_client.go
new file mode 100644
index 0000000..a46fd82
--- /dev/null
+++ b/services/wspr/internal/lib/simple_client.go
@@ -0,0 +1,139 @@
+// 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 lib
+
+import (
+	"errors"
+	"fmt"
+	"sync"
+
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/vdl"
+	"v.io/v23/vom"
+)
+
+type clientWithTimesCalled interface {
+	rpc.Client
+	TimesCalled(method string) int
+}
+
+// NewSimpleClient creates a new mocked rpc client where the given map of method name
+// to outputs is used for evaluating the method calls.
+// It also adds some testing features such as counters for number of times a method is called
+func newSimpleClient(methodsResults map[string][]interface{}) clientWithTimesCalled {
+	return &simpleMockClient{
+		results:     methodsResults,
+		timesCalled: make(map[string]int),
+	}
+}
+
+// simpleMockClient implements rpc.Client
+type simpleMockClient struct {
+	// Protects timesCalled
+	sync.Mutex
+
+	// results is a map of method names to results
+	results map[string][]interface{}
+	// timesCalled is a counter for number of times StartCall is called on a specific method name
+	timesCalled map[string]int
+}
+
+// TimesCalled returns number of times the given method has been called.
+func (c *simpleMockClient) TimesCalled(method string) int {
+	return c.timesCalled[method]
+}
+
+// StartCall Implements rpc.Client
+func (c *simpleMockClient) StartCall(ctx *context.T, name, method string, args []interface{}, opts ...rpc.CallOpt) (rpc.ClientCall, error) {
+	results, ok := c.results[method]
+	if !ok {
+		return nil, fmt.Errorf("method %s not found", method)
+	}
+
+	// Copy the results so that they can be modified without effecting the original.
+	// This must be done via vom encode and decode rather than a direct deep copy because (among other reasons)
+	// reflect-based deep copy on vdl.Type objects will fail because of their private fields. This is not a problem with vom
+	// as it manually creates the type objects. It is also more realistic to use the same mechanism as the ultimate calls.
+	vomBytes, err := vom.Encode(results)
+	if err != nil {
+		panic(fmt.Sprintf("Error copying value with vom (failed on encode): %v", err))
+	}
+	var copiedResults []interface{}
+	if err := vom.Decode(vomBytes, &copiedResults); err != nil {
+		panic(fmt.Sprintf("Error copying value with vom (failed on decode): %v", err))
+	}
+
+	clientCall := mockCall{
+		results: copiedResults,
+	}
+
+	c.Lock()
+	c.timesCalled[method]++
+	c.Unlock()
+
+	return &clientCall, nil
+}
+
+func (c *simpleMockClient) Call(ctx *context.T, name, method string, inArgs, outArgs []interface{}, callOpts ...rpc.CallOpt) error {
+	call, err := c.StartCall(ctx, name, method, inArgs, callOpts...)
+	if err != nil {
+		return err
+	}
+	return call.Finish(outArgs...)
+}
+
+// Close implements rpc.Client
+func (*simpleMockClient) Close() {
+}
+
+// mockCall implements rpc.ClientCall
+type mockCall struct {
+	mockStream
+	results []interface{}
+}
+
+// Cancel implements rpc.ClientCall
+func (*mockCall) Cancel() {
+}
+
+// CloseSend implements rpc.ClientCall
+func (*mockCall) CloseSend() error {
+	return nil
+}
+
+// Finish implements rpc.ClientCall
+func (mc *mockCall) Finish(resultptrs ...interface{}) error {
+	if got, want := len(resultptrs), len(mc.results); got != want {
+		return errors.New(fmt.Sprintf("wrong number of output results; expected resultptrs of size %d but got %d", want, got))
+	}
+	for ax, res := range resultptrs {
+		if mc.results[ax] != nil {
+			if err := vdl.Convert(res, mc.results[ax]); err != nil {
+				panic(fmt.Sprintf("Error converting out argument %#v: %v", mc.results[ax], err))
+			}
+		}
+	}
+	return nil
+}
+
+// RemoteBlessings implements rpc.ClientCall
+func (*mockCall) RemoteBlessings() ([]string, security.Blessings) {
+	return []string{}, security.Blessings{}
+}
+
+//mockStream implements rpc.Stream
+type mockStream struct{}
+
+//Send implements rpc.Stream
+func (*mockStream) Send(interface{}) error {
+	return nil
+}
+
+//Recv implements rpc.Stream
+func (*mockStream) Recv(interface{}) error {
+	return nil
+}
diff --git a/services/wspr/internal/lib/simple_client_test.go b/services/wspr/internal/lib/simple_client_test.go
new file mode 100644
index 0000000..ed7f8c2
--- /dev/null
+++ b/services/wspr/internal/lib/simple_client_test.go
@@ -0,0 +1,143 @@
+// 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 lib
+
+import (
+	"testing"
+
+	"v.io/v23/context"
+)
+
+func testContext() *context.T {
+	ctx, _ := context.RootContext()
+	return ctx
+}
+
+func TestSuccessfulCalls(t *testing.T) {
+
+	method1ExpectedResult := []interface{}{"one", 2}
+	method2ExpectedResult := []interface{}{"one"}
+	method3ExpectedResult := []interface{}{nil}
+
+	client := newSimpleClient(map[string][]interface{}{
+		"method1": method1ExpectedResult,
+		"method2": method2ExpectedResult,
+		"method3": method3ExpectedResult,
+	})
+
+	ctx := testContext()
+
+	// method1
+	method1Call, err := client.StartCall(ctx, "name/obj", "method1", []interface{}{})
+	if err != nil {
+		t.Errorf("StartCall: did not expect an error return")
+		return
+	}
+	var resultOne string
+	var resultTwo int64
+	method1Call.Finish(&resultOne, &resultTwo)
+	if resultOne != "one" {
+		t.Errorf(`FinishCall: first result was "%v", want "one"`, resultOne)
+		return
+	}
+	if resultTwo != 2 {
+		t.Errorf(`FinishCall: second result was "%v", want 2`, resultTwo)
+		return
+	}
+
+	// method2
+	method2Call, err := client.StartCall(ctx, "name/obj", "method2", []interface{}{})
+	if err != nil {
+		t.Errorf(`StartCall: did not expect an error return`)
+		return
+	}
+	method2Call.Finish(&resultOne)
+	if resultOne != "one" {
+		t.Errorf(`FinishCall: result "%v", want "one"`, resultOne)
+		return
+	}
+
+	// method3
+	var result interface{}
+	method3Call, err := client.StartCall(ctx, "name/obj", "method3", []interface{}{})
+	if err != nil {
+		t.Errorf(`StartCall: did not expect an error return`)
+		return
+	}
+	method3Call.Finish(&result)
+	if result != nil {
+		t.Errorf(`FinishCall: result "%v", want nil`, result)
+		return
+	}
+}
+
+type sampleStruct struct {
+	Name string
+}
+
+func TestStructResult(t *testing.T) {
+	client := newSimpleClient(map[string][]interface{}{
+		"foo": []interface{}{
+			sampleStruct{Name: "bar"},
+		},
+	})
+	ctx := testContext()
+	call, _ := client.StartCall(ctx, "name/obj", "foo", []interface{}{})
+	var result sampleStruct
+	call.Finish(&result)
+	if result.Name != "bar" {
+		t.Errorf(`FinishCall: second result was "%v", want "bar"`, result.Name)
+		return
+	}
+}
+
+func TestErrorCall(t *testing.T) {
+	client := newSimpleClient(map[string][]interface{}{
+		"bar": []interface{}{},
+	})
+	ctx := testContext()
+	_, err := client.StartCall(ctx, "name/obj", "wrongMethodName", []interface{}{})
+	if err == nil {
+		t.Errorf(`StartCall: should have returned an error on invalid method name`)
+		return
+	}
+}
+
+func TestNumberOfCalls(t *testing.T) {
+	client := newSimpleClient(map[string][]interface{}{
+		"method1": []interface{}{},
+		"method2": []interface{}{},
+	})
+
+	errMsg := "Expected method to be called %d times but it was called %d"
+	ctx := testContext()
+
+	// method 1
+	if n := client.TimesCalled("method1"); n != 0 {
+		t.Errorf(errMsg, 0, n)
+		return
+	}
+	client.StartCall(ctx, "name/of/object", "method1", []interface{}{})
+	if n := client.TimesCalled("method1"); n != 1 {
+		t.Errorf(errMsg, 1, n)
+		return
+	}
+	client.StartCall(ctx, "name/of/object", "method1", []interface{}{})
+	if n := client.TimesCalled("method1"); n != 2 {
+		t.Errorf(errMsg, 2, n)
+		return
+	}
+
+	// method 2
+	if n := client.TimesCalled("method2"); n != 0 {
+		t.Errorf(errMsg, 0, n)
+		return
+	}
+	client.StartCall(ctx, "name/of/object", "method2", []interface{}{})
+	if n := client.TimesCalled("method2"); n != 1 {
+		t.Errorf(errMsg, 1, n)
+		return
+	}
+}
diff --git a/services/wspr/internal/lib/testwriter/writer.go b/services/wspr/internal/lib/testwriter/writer.go
new file mode 100644
index 0000000..2c9da53
--- /dev/null
+++ b/services/wspr/internal/lib/testwriter/writer.go
@@ -0,0 +1,120 @@
+// 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 testwriter
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"reflect"
+	"sync"
+	"time"
+
+	"v.io/v23/verror"
+	"v.io/x/ref/services/wspr/internal/lib"
+)
+
+type TestHarness interface {
+	Errorf(fmt string, a ...interface{})
+}
+
+type Writer struct {
+	sync.Mutex
+	TypeStream []lib.Response
+	Stream     []lib.Response // TODO Why not use channel?
+	err        error
+	// If this channel is set then a message will be sent
+	// to this channel after recieving a call to FinishMessage()
+	notifier chan bool
+}
+
+func (w *Writer) Send(responseType lib.ResponseType, msg interface{}) error {
+	// We serialize and deserialize the reponse so that we can do deep equal with
+	// messages that contain non-exported structs.
+	var buf bytes.Buffer
+	if err := json.NewEncoder(&buf).Encode(lib.Response{Type: responseType, Message: msg}); err != nil {
+		return err
+	}
+
+	var r lib.Response
+
+	if err := json.NewDecoder(&buf).Decode(&r); err != nil {
+		return err
+	}
+
+	w.Lock()
+	defer w.Unlock()
+	if responseType == lib.ResponseTypeMessage {
+		w.TypeStream = append(w.TypeStream, r)
+	} else {
+		w.Stream = append(w.Stream, r)
+	}
+	if w.notifier != nil {
+		w.notifier <- true
+	}
+	return nil
+
+}
+
+// ImmediatelyConsumeItem consumes an item on the stream without waiting.
+func (w *Writer) ImmediatelyConsumeItem() (lib.Response, error) {
+	w.Lock()
+	defer w.Unlock()
+
+	if len(w.Stream) < 1 {
+		return lib.Response{}, fmt.Errorf("Expected an item on the stream, none found")
+	}
+
+	item := w.Stream[0]
+	w.Stream = w.Stream[1:]
+
+	return item, nil
+}
+
+func (w *Writer) Error(err error) {
+	w.err = err
+}
+
+func (w *Writer) streamLength() int {
+	w.Lock()
+	defer w.Unlock()
+	return len(w.Stream)
+}
+
+// Waits until there is at least n messages in w.Stream. Returns an error if we timeout
+// waiting for the given number of messages.
+func (w *Writer) WaitForMessage(n int) error {
+	if w.streamLength() >= n {
+		return nil
+	}
+	w.Lock()
+	w.notifier = make(chan bool, 1)
+	w.Unlock()
+	for w.streamLength() < n {
+		select {
+		case <-w.notifier:
+			continue
+		case <-time.After(10 * time.Second):
+			return fmt.Errorf("timed out")
+		}
+	}
+	w.Lock()
+	w.notifier = nil
+	w.Unlock()
+	return nil
+}
+
+func CheckResponses(w *Writer, wantStream []lib.Response, wantTypes []lib.Response, wantErr error) error {
+	if got, want := w.Stream, wantStream; !reflect.DeepEqual(got, want) {
+		return fmt.Errorf("streams don't match: got %#v, want %#v", got, want)
+	}
+	if got, want := w.TypeStream, wantTypes; !reflect.DeepEqual(got, want) {
+		return fmt.Errorf("streams don't match: got %#v, want %#v", got, want)
+	}
+	if got, want := w.err, wantErr; verror.ErrorID(got) != verror.ErrorID(want) {
+		return fmt.Errorf("unexpected error, got: %#v, expected: %#v", got, want)
+	}
+	return nil
+}
diff --git a/services/wspr/internal/lib/time.go b/services/wspr/internal/lib/time.go
new file mode 100644
index 0000000..fa37d70
--- /dev/null
+++ b/services/wspr/internal/lib/time.go
@@ -0,0 +1,23 @@
+// 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 lib
+
+import "time"
+
+// Javascript uses millisecond time units.  This is both because they are the
+// native time unit, and because otherwise JS numbers would not be capable of
+// representing the full time range available to Go programs.
+//
+// TODO(bjornick,toddw): Pick a better sentry for no timeout, or change to using
+// VDL time.WireDeadline.
+const JSRPCNoTimeout = int64(6307200000000) // 200 years in milliseconds
+
+func GoToJSDuration(d time.Duration) int64 {
+	return int64(d / time.Millisecond)
+}
+
+func JSToGoDuration(d int64) time.Duration {
+	return time.Duration(d) * time.Millisecond
+}
diff --git a/services/wspr/internal/lib/type_reader.go b/services/wspr/internal/lib/type_reader.go
new file mode 100644
index 0000000..9b98161
--- /dev/null
+++ b/services/wspr/internal/lib/type_reader.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.
+
+package lib
+
+import (
+	"bytes"
+	"encoding/hex"
+	"io"
+	"sync"
+)
+
+// TypeReader implements io.Reader but allows changing the underlying buffer.
+// This is useful for merging discrete messages that are part of the same flow.
+type TypeReader struct {
+	buf      bytes.Buffer
+	mu       sync.Mutex
+	isClosed bool
+	cond     *sync.Cond
+}
+
+func NewTypeReader() *TypeReader {
+	reader := &TypeReader{}
+	reader.cond = sync.NewCond(&reader.mu)
+	return reader
+}
+
+func (r *TypeReader) Add(data string) error {
+	binBytes, err := hex.DecodeString(data)
+	if err != nil {
+		return err
+	}
+	r.mu.Lock()
+	_, err = r.buf.Write(binBytes)
+	r.mu.Unlock()
+	r.cond.Signal()
+	return err
+}
+
+func (r *TypeReader) Read(p []byte) (int, error) {
+	r.mu.Lock()
+	defer r.mu.Unlock()
+	for {
+		if r.buf.Len() > 0 {
+			return r.buf.Read(p)
+		}
+		if r.isClosed {
+			return 0, io.EOF
+		}
+		r.cond.Wait()
+	}
+
+}
+
+func (r *TypeReader) Close() {
+	r.mu.Lock()
+	r.isClosed = true
+	r.mu.Unlock()
+	r.cond.Broadcast()
+}
diff --git a/services/wspr/internal/lib/type_writer.go b/services/wspr/internal/lib/type_writer.go
new file mode 100644
index 0000000..3536db2
--- /dev/null
+++ b/services/wspr/internal/lib/type_writer.go
@@ -0,0 +1,23 @@
+// 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 lib
+
+// TypeWriter implements io.Writer but allows changing the underlying buffer.
+// This is useful for merging discrete messages that are part of the same flow.
+type TypeWriter struct {
+	writer ClientWriter
+}
+
+func NewTypeWriter(w ClientWriter) *TypeWriter {
+	return &TypeWriter{writer: w}
+}
+
+func (w *TypeWriter) Write(p []byte) (int, error) {
+	err := w.writer.Send(ResponseTypeMessage, p)
+	if err != nil {
+		return 0, err
+	}
+	return len(p), nil
+}
diff --git a/services/wspr/internal/lib/writer.go b/services/wspr/internal/lib/writer.go
new file mode 100644
index 0000000..1c90372
--- /dev/null
+++ b/services/wspr/internal/lib/writer.go
@@ -0,0 +1,36 @@
+// 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 lib
+
+type ResponseType int32
+
+const (
+	ResponseFinal                 ResponseType = 0
+	ResponseStream                             = 1
+	ResponseError                              = 2
+	ResponseServerRequest                      = 3
+	ResponseStreamClose                        = 4
+	ResponseDispatcherLookup                   = 5
+	ResponseAuthRequest                        = 6
+	ResponseCancel                             = 7
+	ResponseValidate                           = 8 // Request to validate caveats.
+	ResponseLog                                = 9 // Sends a message to be logged.
+	ResponseGranterRequest                     = 10
+	ResponseBlessingsCacheMessage              = 11 // Update the blessings cache
+	ResponseTypeMessage                        = 12
+)
+
+type Response struct {
+	Type    ResponseType
+	Message interface{}
+}
+
+// This is basically an io.Writer interface, that allows passing error message
+// strings.  This is how the proxy will talk to the javascript/java clients.
+type ClientWriter interface {
+	Send(messageType ResponseType, data interface{}) error
+
+	Error(err error)
+}
diff --git a/services/wspr/internal/lib/writer.vdl b/services/wspr/internal/lib/writer.vdl
new file mode 100644
index 0000000..ce94e21
--- /dev/null
+++ b/services/wspr/internal/lib/writer.vdl
@@ -0,0 +1,24 @@
+// 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 lib
+
+import "v.io/v23/vtrace"
+
+// The response from the javascript server to the proxy.
+type ServerRpcReply struct {
+	Results       []any
+	Err           error
+	TraceResponse vtrace.Response
+}
+
+type LogLevel enum {
+   Info
+   Error
+}
+
+type LogMessage struct {
+  Level LogLevel
+  Message string
+}
diff --git a/services/wspr/internal/lib/writer.vdl.go b/services/wspr/internal/lib/writer.vdl.go
new file mode 100644
index 0000000..56bb125
--- /dev/null
+++ b/services/wspr/internal/lib/writer.vdl.go
@@ -0,0 +1,92 @@
+// 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 by the vanadium vdl tool.
+// Source: writer.vdl
+
+package lib
+
+import (
+	// VDL system imports
+	"fmt"
+	"v.io/v23/vdl"
+
+	// VDL user imports
+	"v.io/v23/vtrace"
+)
+
+// The response from the javascript server to the proxy.
+type ServerRpcReply struct {
+	Results       []*vdl.Value
+	Err           error
+	TraceResponse vtrace.Response
+}
+
+func (ServerRpcReply) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/services/wspr/internal/lib.ServerRpcReply"`
+}) {
+}
+
+type LogLevel int
+
+const (
+	LogLevelInfo LogLevel = iota
+	LogLevelError
+)
+
+// LogLevelAll holds all labels for LogLevel.
+var LogLevelAll = [...]LogLevel{LogLevelInfo, LogLevelError}
+
+// LogLevelFromString creates a LogLevel from a string label.
+func LogLevelFromString(label string) (x LogLevel, err error) {
+	err = x.Set(label)
+	return
+}
+
+// Set assigns label to x.
+func (x *LogLevel) Set(label string) error {
+	switch label {
+	case "Info", "info":
+		*x = LogLevelInfo
+		return nil
+	case "Error", "error":
+		*x = LogLevelError
+		return nil
+	}
+	*x = -1
+	return fmt.Errorf("unknown label %q in lib.LogLevel", label)
+}
+
+// String returns the string label of x.
+func (x LogLevel) String() string {
+	switch x {
+	case LogLevelInfo:
+		return "Info"
+	case LogLevelError:
+		return "Error"
+	}
+	return ""
+}
+
+func (LogLevel) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/services/wspr/internal/lib.LogLevel"`
+	Enum struct{ Info, Error string }
+}) {
+}
+
+type LogMessage struct {
+	Level   LogLevel
+	Message string
+}
+
+func (LogMessage) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/services/wspr/internal/lib.LogMessage"`
+}) {
+}
+
+func init() {
+	vdl.Register((*ServerRpcReply)(nil))
+	vdl.Register((*LogLevel)(nil))
+	vdl.Register((*LogMessage)(nil))
+}
diff --git a/services/wspr/internal/namespace/namespace.vdl b/services/wspr/internal/namespace/namespace.vdl
new file mode 100644
index 0000000..fba8427
--- /dev/null
+++ b/services/wspr/internal/namespace/namespace.vdl
@@ -0,0 +1,44 @@
+// 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 namespace defines an RPC services that allows remoting of the
+// namespace client library over the wire.  This is useful for
+// javascript so it doesn't have to implement the library.
+// This should be kept in sync with the namespace library (v.io/v23/naming).
+package namespace
+
+import (
+	"time"
+
+	"v.io/v23/naming"
+	"v.io/v23/security/access"
+)
+
+type Namespace interface {
+	// Run a glob query and stream the results.
+	Glob(pattern string) stream<_, naming.GlobReply> error
+	// Mount mounts a server under the given name.
+	Mount(name, server string, ttl time.Duration, replace bool) error
+	// Unmount removes an existing mount point.
+	Unmount(name, server string) error
+	// Resolve resolves a name to an address.
+	Resolve(name string) ([]string | error)
+	// ResolveToMountTable resolves a name to the address of the mounttable
+	// directly hosting it.
+	ResolveToMountTable(name string) ([]string | error)
+	// FlushCacheEntry removes the namespace cache entry for a given name.
+	FlushCacheEntry(name string) (bool | error)
+	// DisableCache disables the naming cache.
+	DisableCache(disable bool) error
+	// Roots returns the addresses of the current mounttable roots.
+	Roots() ([]string | error)
+	// SetRoots sets the current mounttable roots.
+	SetRoots(roots []string) error
+	// SetPermissions sets the AccessList in a node in a mount table.
+	SetPermissions(name string, perms access.Permissions, version string) error
+	// GetPermissions returns the AccessList in a node in a mount table.
+	GetPermissions(name string) (perms access.Permissions, version string | error)
+	// Delete deletes the name from the mounttable and, if requested, any subtree.
+	Delete(name string, deleteSubtree bool) error
+}
diff --git a/services/wspr/internal/namespace/namespace.vdl.go b/services/wspr/internal/namespace/namespace.vdl.go
new file mode 100644
index 0000000..ade1ee0
--- /dev/null
+++ b/services/wspr/internal/namespace/namespace.vdl.go
@@ -0,0 +1,508 @@
+// 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 by the vanadium vdl tool.
+// Source: namespace.vdl
+
+// Package namespace defines an RPC services that allows remoting of the
+// namespace client library over the wire.  This is useful for
+// javascript so it doesn't have to implement the library.
+// This should be kept in sync with the namespace library (v.io/v23/naming).
+package namespace
+
+import (
+	// VDL system imports
+	"io"
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+
+	// VDL user imports
+	"time"
+	"v.io/v23/naming"
+	"v.io/v23/security/access"
+	_ "v.io/v23/vdlroot/time"
+)
+
+// NamespaceClientMethods is the client interface
+// containing Namespace methods.
+type NamespaceClientMethods interface {
+	// Run a glob query and stream the results.
+	Glob(ctx *context.T, pattern string, opts ...rpc.CallOpt) (NamespaceGlobClientCall, error)
+	// Mount mounts a server under the given name.
+	Mount(ctx *context.T, name string, server string, ttl time.Duration, replace bool, opts ...rpc.CallOpt) error
+	// Unmount removes an existing mount point.
+	Unmount(ctx *context.T, name string, server string, opts ...rpc.CallOpt) error
+	// Resolve resolves a name to an address.
+	Resolve(ctx *context.T, name string, opts ...rpc.CallOpt) ([]string, error)
+	// ResolveToMountTable resolves a name to the address of the mounttable
+	// directly hosting it.
+	ResolveToMountTable(ctx *context.T, name string, opts ...rpc.CallOpt) ([]string, error)
+	// FlushCacheEntry removes the namespace cache entry for a given name.
+	FlushCacheEntry(ctx *context.T, name string, opts ...rpc.CallOpt) (bool, error)
+	// DisableCache disables the naming cache.
+	DisableCache(ctx *context.T, disable bool, opts ...rpc.CallOpt) error
+	// Roots returns the addresses of the current mounttable roots.
+	Roots(*context.T, ...rpc.CallOpt) ([]string, error)
+	// SetRoots sets the current mounttable roots.
+	SetRoots(ctx *context.T, roots []string, opts ...rpc.CallOpt) error
+	// SetPermissions sets the AccessList in a node in a mount table.
+	SetPermissions(ctx *context.T, name string, perms access.Permissions, version string, opts ...rpc.CallOpt) error
+	// GetPermissions returns the AccessList in a node in a mount table.
+	GetPermissions(ctx *context.T, name string, opts ...rpc.CallOpt) (perms access.Permissions, version string, err error)
+	// Delete deletes the name from the mounttable and, if requested, any subtree.
+	Delete(ctx *context.T, name string, deleteSubtree bool, opts ...rpc.CallOpt) error
+}
+
+// NamespaceClientStub adds universal methods to NamespaceClientMethods.
+type NamespaceClientStub interface {
+	NamespaceClientMethods
+	rpc.UniversalServiceMethods
+}
+
+// NamespaceClient returns a client stub for Namespace.
+func NamespaceClient(name string) NamespaceClientStub {
+	return implNamespaceClientStub{name}
+}
+
+type implNamespaceClientStub struct {
+	name string
+}
+
+func (c implNamespaceClientStub) Glob(ctx *context.T, i0 string, opts ...rpc.CallOpt) (ocall NamespaceGlobClientCall, err error) {
+	var call rpc.ClientCall
+	if call, err = v23.GetClient(ctx).StartCall(ctx, c.name, "Glob", []interface{}{i0}, opts...); err != nil {
+		return
+	}
+	ocall = &implNamespaceGlobClientCall{ClientCall: call}
+	return
+}
+
+func (c implNamespaceClientStub) Mount(ctx *context.T, i0 string, i1 string, i2 time.Duration, i3 bool, opts ...rpc.CallOpt) (err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "Mount", []interface{}{i0, i1, i2, i3}, nil, opts...)
+	return
+}
+
+func (c implNamespaceClientStub) Unmount(ctx *context.T, i0 string, i1 string, opts ...rpc.CallOpt) (err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "Unmount", []interface{}{i0, i1}, nil, opts...)
+	return
+}
+
+func (c implNamespaceClientStub) Resolve(ctx *context.T, i0 string, opts ...rpc.CallOpt) (o0 []string, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "Resolve", []interface{}{i0}, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implNamespaceClientStub) ResolveToMountTable(ctx *context.T, i0 string, opts ...rpc.CallOpt) (o0 []string, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "ResolveToMountTable", []interface{}{i0}, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implNamespaceClientStub) FlushCacheEntry(ctx *context.T, i0 string, opts ...rpc.CallOpt) (o0 bool, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "FlushCacheEntry", []interface{}{i0}, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implNamespaceClientStub) DisableCache(ctx *context.T, i0 bool, opts ...rpc.CallOpt) (err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "DisableCache", []interface{}{i0}, nil, opts...)
+	return
+}
+
+func (c implNamespaceClientStub) Roots(ctx *context.T, opts ...rpc.CallOpt) (o0 []string, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "Roots", nil, []interface{}{&o0}, opts...)
+	return
+}
+
+func (c implNamespaceClientStub) SetRoots(ctx *context.T, i0 []string, opts ...rpc.CallOpt) (err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "SetRoots", []interface{}{i0}, nil, opts...)
+	return
+}
+
+func (c implNamespaceClientStub) SetPermissions(ctx *context.T, i0 string, i1 access.Permissions, i2 string, opts ...rpc.CallOpt) (err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "SetPermissions", []interface{}{i0, i1, i2}, nil, opts...)
+	return
+}
+
+func (c implNamespaceClientStub) GetPermissions(ctx *context.T, i0 string, opts ...rpc.CallOpt) (o0 access.Permissions, o1 string, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "GetPermissions", []interface{}{i0}, []interface{}{&o0, &o1}, opts...)
+	return
+}
+
+func (c implNamespaceClientStub) Delete(ctx *context.T, i0 string, i1 bool, opts ...rpc.CallOpt) (err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "Delete", []interface{}{i0, i1}, nil, opts...)
+	return
+}
+
+// NamespaceGlobClientStream is the client stream for Namespace.Glob.
+type NamespaceGlobClientStream interface {
+	// RecvStream returns the receiver side of the Namespace.Glob client stream.
+	RecvStream() interface {
+		// Advance stages an item so that it may be retrieved via Value.  Returns
+		// true iff there is an item to retrieve.  Advance must be called before
+		// Value is called.  May block if an item is not available.
+		Advance() bool
+		// Value returns the item that was staged by Advance.  May panic if Advance
+		// returned false or was not called.  Never blocks.
+		Value() naming.GlobReply
+		// Err returns any error encountered by Advance.  Never blocks.
+		Err() error
+	}
+}
+
+// NamespaceGlobClientCall represents the call returned from Namespace.Glob.
+type NamespaceGlobClientCall interface {
+	NamespaceGlobClientStream
+	// Finish blocks until the server is done, and returns the positional return
+	// values for call.
+	//
+	// Finish returns immediately if the call has been canceled; depending on the
+	// timing the output could either be an error signaling cancelation, or the
+	// valid positional return values from the server.
+	//
+	// Calling Finish is mandatory for releasing stream resources, unless the call
+	// has been canceled or any of the other methods return an error.  Finish should
+	// be called at most once.
+	Finish() error
+}
+
+type implNamespaceGlobClientCall struct {
+	rpc.ClientCall
+	valRecv naming.GlobReply
+	errRecv error
+}
+
+func (c *implNamespaceGlobClientCall) RecvStream() interface {
+	Advance() bool
+	Value() naming.GlobReply
+	Err() error
+} {
+	return implNamespaceGlobClientCallRecv{c}
+}
+
+type implNamespaceGlobClientCallRecv struct {
+	c *implNamespaceGlobClientCall
+}
+
+func (c implNamespaceGlobClientCallRecv) Advance() bool {
+	c.c.errRecv = c.c.Recv(&c.c.valRecv)
+	return c.c.errRecv == nil
+}
+func (c implNamespaceGlobClientCallRecv) Value() naming.GlobReply {
+	return c.c.valRecv
+}
+func (c implNamespaceGlobClientCallRecv) Err() error {
+	if c.c.errRecv == io.EOF {
+		return nil
+	}
+	return c.c.errRecv
+}
+func (c *implNamespaceGlobClientCall) Finish() (err error) {
+	err = c.ClientCall.Finish()
+	return
+}
+
+// NamespaceServerMethods is the interface a server writer
+// implements for Namespace.
+type NamespaceServerMethods interface {
+	// Run a glob query and stream the results.
+	Glob(ctx *context.T, call NamespaceGlobServerCall, pattern string) error
+	// Mount mounts a server under the given name.
+	Mount(ctx *context.T, call rpc.ServerCall, name string, server string, ttl time.Duration, replace bool) error
+	// Unmount removes an existing mount point.
+	Unmount(ctx *context.T, call rpc.ServerCall, name string, server string) error
+	// Resolve resolves a name to an address.
+	Resolve(ctx *context.T, call rpc.ServerCall, name string) ([]string, error)
+	// ResolveToMountTable resolves a name to the address of the mounttable
+	// directly hosting it.
+	ResolveToMountTable(ctx *context.T, call rpc.ServerCall, name string) ([]string, error)
+	// FlushCacheEntry removes the namespace cache entry for a given name.
+	FlushCacheEntry(ctx *context.T, call rpc.ServerCall, name string) (bool, error)
+	// DisableCache disables the naming cache.
+	DisableCache(ctx *context.T, call rpc.ServerCall, disable bool) error
+	// Roots returns the addresses of the current mounttable roots.
+	Roots(*context.T, rpc.ServerCall) ([]string, error)
+	// SetRoots sets the current mounttable roots.
+	SetRoots(ctx *context.T, call rpc.ServerCall, roots []string) error
+	// SetPermissions sets the AccessList in a node in a mount table.
+	SetPermissions(ctx *context.T, call rpc.ServerCall, name string, perms access.Permissions, version string) error
+	// GetPermissions returns the AccessList in a node in a mount table.
+	GetPermissions(ctx *context.T, call rpc.ServerCall, name string) (perms access.Permissions, version string, err error)
+	// Delete deletes the name from the mounttable and, if requested, any subtree.
+	Delete(ctx *context.T, call rpc.ServerCall, name string, deleteSubtree bool) error
+}
+
+// NamespaceServerStubMethods is the server interface containing
+// Namespace methods, as expected by rpc.Server.
+// The only difference between this interface and NamespaceServerMethods
+// is the streaming methods.
+type NamespaceServerStubMethods interface {
+	// Run a glob query and stream the results.
+	Glob(ctx *context.T, call *NamespaceGlobServerCallStub, pattern string) error
+	// Mount mounts a server under the given name.
+	Mount(ctx *context.T, call rpc.ServerCall, name string, server string, ttl time.Duration, replace bool) error
+	// Unmount removes an existing mount point.
+	Unmount(ctx *context.T, call rpc.ServerCall, name string, server string) error
+	// Resolve resolves a name to an address.
+	Resolve(ctx *context.T, call rpc.ServerCall, name string) ([]string, error)
+	// ResolveToMountTable resolves a name to the address of the mounttable
+	// directly hosting it.
+	ResolveToMountTable(ctx *context.T, call rpc.ServerCall, name string) ([]string, error)
+	// FlushCacheEntry removes the namespace cache entry for a given name.
+	FlushCacheEntry(ctx *context.T, call rpc.ServerCall, name string) (bool, error)
+	// DisableCache disables the naming cache.
+	DisableCache(ctx *context.T, call rpc.ServerCall, disable bool) error
+	// Roots returns the addresses of the current mounttable roots.
+	Roots(*context.T, rpc.ServerCall) ([]string, error)
+	// SetRoots sets the current mounttable roots.
+	SetRoots(ctx *context.T, call rpc.ServerCall, roots []string) error
+	// SetPermissions sets the AccessList in a node in a mount table.
+	SetPermissions(ctx *context.T, call rpc.ServerCall, name string, perms access.Permissions, version string) error
+	// GetPermissions returns the AccessList in a node in a mount table.
+	GetPermissions(ctx *context.T, call rpc.ServerCall, name string) (perms access.Permissions, version string, err error)
+	// Delete deletes the name from the mounttable and, if requested, any subtree.
+	Delete(ctx *context.T, call rpc.ServerCall, name string, deleteSubtree bool) error
+}
+
+// NamespaceServerStub adds universal methods to NamespaceServerStubMethods.
+type NamespaceServerStub interface {
+	NamespaceServerStubMethods
+	// Describe the Namespace interfaces.
+	Describe__() []rpc.InterfaceDesc
+}
+
+// NamespaceServer returns a server stub for Namespace.
+// It converts an implementation of NamespaceServerMethods into
+// an object that may be used by rpc.Server.
+func NamespaceServer(impl NamespaceServerMethods) NamespaceServerStub {
+	stub := implNamespaceServerStub{
+		impl: impl,
+	}
+	// Initialize GlobState; always check the stub itself first, to handle the
+	// case where the user has the Glob method defined in their VDL source.
+	if gs := rpc.NewGlobState(stub); gs != nil {
+		stub.gs = gs
+	} else if gs := rpc.NewGlobState(impl); gs != nil {
+		stub.gs = gs
+	}
+	return stub
+}
+
+type implNamespaceServerStub struct {
+	impl NamespaceServerMethods
+	gs   *rpc.GlobState
+}
+
+func (s implNamespaceServerStub) Glob(ctx *context.T, call *NamespaceGlobServerCallStub, i0 string) error {
+	return s.impl.Glob(ctx, call, i0)
+}
+
+func (s implNamespaceServerStub) Mount(ctx *context.T, call rpc.ServerCall, i0 string, i1 string, i2 time.Duration, i3 bool) error {
+	return s.impl.Mount(ctx, call, i0, i1, i2, i3)
+}
+
+func (s implNamespaceServerStub) Unmount(ctx *context.T, call rpc.ServerCall, i0 string, i1 string) error {
+	return s.impl.Unmount(ctx, call, i0, i1)
+}
+
+func (s implNamespaceServerStub) Resolve(ctx *context.T, call rpc.ServerCall, i0 string) ([]string, error) {
+	return s.impl.Resolve(ctx, call, i0)
+}
+
+func (s implNamespaceServerStub) ResolveToMountTable(ctx *context.T, call rpc.ServerCall, i0 string) ([]string, error) {
+	return s.impl.ResolveToMountTable(ctx, call, i0)
+}
+
+func (s implNamespaceServerStub) FlushCacheEntry(ctx *context.T, call rpc.ServerCall, i0 string) (bool, error) {
+	return s.impl.FlushCacheEntry(ctx, call, i0)
+}
+
+func (s implNamespaceServerStub) DisableCache(ctx *context.T, call rpc.ServerCall, i0 bool) error {
+	return s.impl.DisableCache(ctx, call, i0)
+}
+
+func (s implNamespaceServerStub) Roots(ctx *context.T, call rpc.ServerCall) ([]string, error) {
+	return s.impl.Roots(ctx, call)
+}
+
+func (s implNamespaceServerStub) SetRoots(ctx *context.T, call rpc.ServerCall, i0 []string) error {
+	return s.impl.SetRoots(ctx, call, i0)
+}
+
+func (s implNamespaceServerStub) SetPermissions(ctx *context.T, call rpc.ServerCall, i0 string, i1 access.Permissions, i2 string) error {
+	return s.impl.SetPermissions(ctx, call, i0, i1, i2)
+}
+
+func (s implNamespaceServerStub) GetPermissions(ctx *context.T, call rpc.ServerCall, i0 string) (access.Permissions, string, error) {
+	return s.impl.GetPermissions(ctx, call, i0)
+}
+
+func (s implNamespaceServerStub) Delete(ctx *context.T, call rpc.ServerCall, i0 string, i1 bool) error {
+	return s.impl.Delete(ctx, call, i0, i1)
+}
+
+func (s implNamespaceServerStub) Globber() *rpc.GlobState {
+	return s.gs
+}
+
+func (s implNamespaceServerStub) Describe__() []rpc.InterfaceDesc {
+	return []rpc.InterfaceDesc{NamespaceDesc}
+}
+
+// NamespaceDesc describes the Namespace interface.
+var NamespaceDesc rpc.InterfaceDesc = descNamespace
+
+// descNamespace hides the desc to keep godoc clean.
+var descNamespace = rpc.InterfaceDesc{
+	Name:    "Namespace",
+	PkgPath: "v.io/x/ref/services/wspr/internal/namespace",
+	Methods: []rpc.MethodDesc{
+		{
+			Name: "Glob",
+			Doc:  "// Run a glob query and stream the results.",
+			InArgs: []rpc.ArgDesc{
+				{"pattern", ``}, // string
+			},
+		},
+		{
+			Name: "Mount",
+			Doc:  "// Mount mounts a server under the given name.",
+			InArgs: []rpc.ArgDesc{
+				{"name", ``},    // string
+				{"server", ``},  // string
+				{"ttl", ``},     // time.Duration
+				{"replace", ``}, // bool
+			},
+		},
+		{
+			Name: "Unmount",
+			Doc:  "// Unmount removes an existing mount point.",
+			InArgs: []rpc.ArgDesc{
+				{"name", ``},   // string
+				{"server", ``}, // string
+			},
+		},
+		{
+			Name: "Resolve",
+			Doc:  "// Resolve resolves a name to an address.",
+			InArgs: []rpc.ArgDesc{
+				{"name", ``}, // string
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"", ``}, // []string
+			},
+		},
+		{
+			Name: "ResolveToMountTable",
+			Doc:  "// ResolveToMountTable resolves a name to the address of the mounttable\n// directly hosting it.",
+			InArgs: []rpc.ArgDesc{
+				{"name", ``}, // string
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"", ``}, // []string
+			},
+		},
+		{
+			Name: "FlushCacheEntry",
+			Doc:  "// FlushCacheEntry removes the namespace cache entry for a given name.",
+			InArgs: []rpc.ArgDesc{
+				{"name", ``}, // string
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"", ``}, // bool
+			},
+		},
+		{
+			Name: "DisableCache",
+			Doc:  "// DisableCache disables the naming cache.",
+			InArgs: []rpc.ArgDesc{
+				{"disable", ``}, // bool
+			},
+		},
+		{
+			Name: "Roots",
+			Doc:  "// Roots returns the addresses of the current mounttable roots.",
+			OutArgs: []rpc.ArgDesc{
+				{"", ``}, // []string
+			},
+		},
+		{
+			Name: "SetRoots",
+			Doc:  "// SetRoots sets the current mounttable roots.",
+			InArgs: []rpc.ArgDesc{
+				{"roots", ``}, // []string
+			},
+		},
+		{
+			Name: "SetPermissions",
+			Doc:  "// SetPermissions sets the AccessList in a node in a mount table.",
+			InArgs: []rpc.ArgDesc{
+				{"name", ``},    // string
+				{"perms", ``},   // access.Permissions
+				{"version", ``}, // string
+			},
+		},
+		{
+			Name: "GetPermissions",
+			Doc:  "// GetPermissions returns the AccessList in a node in a mount table.",
+			InArgs: []rpc.ArgDesc{
+				{"name", ``}, // string
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"perms", ``},   // access.Permissions
+				{"version", ``}, // string
+			},
+		},
+		{
+			Name: "Delete",
+			Doc:  "// Delete deletes the name from the mounttable and, if requested, any subtree.",
+			InArgs: []rpc.ArgDesc{
+				{"name", ``},          // string
+				{"deleteSubtree", ``}, // bool
+			},
+		},
+	},
+}
+
+// NamespaceGlobServerStream is the server stream for Namespace.Glob.
+type NamespaceGlobServerStream interface {
+	// SendStream returns the send side of the Namespace.Glob server stream.
+	SendStream() interface {
+		// Send places the item onto the output stream.  Returns errors encountered
+		// while sending.  Blocks if there is no buffer space; will unblock when
+		// buffer space is available.
+		Send(item naming.GlobReply) error
+	}
+}
+
+// NamespaceGlobServerCall represents the context passed to Namespace.Glob.
+type NamespaceGlobServerCall interface {
+	rpc.ServerCall
+	NamespaceGlobServerStream
+}
+
+// NamespaceGlobServerCallStub is a wrapper that converts rpc.StreamServerCall into
+// a typesafe stub that implements NamespaceGlobServerCall.
+type NamespaceGlobServerCallStub struct {
+	rpc.StreamServerCall
+}
+
+// Init initializes NamespaceGlobServerCallStub from rpc.StreamServerCall.
+func (s *NamespaceGlobServerCallStub) Init(call rpc.StreamServerCall) {
+	s.StreamServerCall = call
+}
+
+// SendStream returns the send side of the Namespace.Glob server stream.
+func (s *NamespaceGlobServerCallStub) SendStream() interface {
+	Send(item naming.GlobReply) error
+} {
+	return implNamespaceGlobServerCallSend{s}
+}
+
+type implNamespaceGlobServerCallSend struct {
+	s *NamespaceGlobServerCallStub
+}
+
+func (s implNamespaceGlobServerCallSend) Send(item naming.GlobReply) error {
+	return s.s.Send(item)
+}
diff --git a/services/wspr/internal/namespace/request_handler.go b/services/wspr/internal/namespace/request_handler.go
new file mode 100644
index 0000000..c262a50
--- /dev/null
+++ b/services/wspr/internal/namespace/request_handler.go
@@ -0,0 +1,104 @@
+// 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 namespace
+
+import (
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/namespace"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+	"v.io/v23/security/access"
+	"v.io/v23/verror"
+)
+
+type Server struct {
+	ns namespace.T
+}
+
+func New(ctx *context.T) *Server {
+	return &Server{v23.GetNamespace(ctx)}
+}
+
+func (s *Server) Glob(ctx *context.T, call *NamespaceGlobServerCallStub, pattern string) error {
+	// Call Glob on the namespace client instance
+	ch, err := s.ns.Glob(ctx, pattern)
+	if err != nil {
+		return err
+	}
+
+	stream := call.SendStream()
+
+	for mp := range ch {
+		if err = stream.Send(mp); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func (s *Server) Mount(ctx *context.T, _ rpc.ServerCall, name, server string, ttl time.Duration, replace bool) error {
+	rmOpt := naming.ReplaceMount(replace)
+	err := s.ns.Mount(ctx, name, server, ttl, rmOpt)
+	if err != nil {
+		err = verror.Convert(verror.ErrInternal, ctx, err)
+	}
+	return err
+}
+
+func (s *Server) Unmount(ctx *context.T, _ rpc.ServerCall, name, server string) error {
+	return s.ns.Unmount(ctx, name, server)
+}
+
+func (s *Server) Resolve(ctx *context.T, _ rpc.ServerCall, name string) ([]string, error) {
+	me, err := s.ns.Resolve(ctx, name)
+	if err != nil {
+		return nil, verror.Convert(verror.ErrInternal, ctx, err)
+	}
+	return me.Names(), nil
+}
+
+func (s *Server) ResolveToMountTable(ctx *context.T, _ rpc.ServerCall, name string) ([]string, error) {
+	me, err := s.ns.ResolveToMountTable(ctx, name)
+	if err != nil {
+		return nil, verror.Convert(verror.ErrInternal, ctx, err)
+	}
+	return me.Names(), nil
+}
+
+func (s *Server) FlushCacheEntry(ctx *context.T, _ rpc.ServerCall, name string) (bool, error) {
+	return s.ns.FlushCacheEntry(ctx, name), nil
+}
+
+func (s *Server) DisableCache(_ *context.T, _ rpc.ServerCall, disable bool) error {
+	disableCacheCtl := naming.DisableCache(disable)
+	_ = s.ns.CacheCtl(disableCacheCtl)
+	return nil
+}
+
+func (s *Server) Roots(*context.T, rpc.ServerCall) ([]string, error) {
+	return s.ns.Roots(), nil
+}
+
+func (s *Server) SetRoots(ctx *context.T, _ rpc.ServerCall, roots []string) error {
+	if err := s.ns.SetRoots(roots...); err != nil {
+		return verror.Convert(verror.ErrInternal, ctx, err)
+	}
+	return nil
+}
+
+func (s *Server) SetPermissions(ctx *context.T, _ rpc.ServerCall, name string, perms access.Permissions, version string) error {
+	return s.ns.SetPermissions(ctx, name, perms, version)
+}
+
+func (s *Server) GetPermissions(ctx *context.T, _ rpc.ServerCall, name string) (access.Permissions, string, error) {
+	return s.ns.GetPermissions(ctx, name)
+}
+
+func (s *Server) Delete(ctx *context.T, _ rpc.ServerCall, name string, deleteSubtree bool) error {
+	return s.ns.Delete(ctx, name, deleteSubtree)
+}
diff --git a/services/wspr/internal/principal/cache.go b/services/wspr/internal/principal/cache.go
new file mode 100644
index 0000000..fb4f8ea
--- /dev/null
+++ b/services/wspr/internal/principal/cache.go
@@ -0,0 +1,146 @@
+// 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 principal
+
+import (
+	"reflect"
+	"sync"
+	"time"
+
+	"v.io/v23/security"
+)
+
+// BlessingsCacheEntry is an entry in the blessing cache.
+type BlessingsCacheEntry struct {
+	refCount  uint32 // Keep track of uses so JS knows when to clean up.
+	blessings security.Blessings
+	dirty     bool // True iff modified since last GC.
+}
+
+// BlessingsCache is a cache for blessings.
+// This cache is synced with a corresponding cache in Javascript with the goal
+// of reducing the number of times that blessings objects are sent in their
+// entirety.
+// BlessingsCache provides an id for a blessing after a Put() call that is
+// understood by Javascript.
+// After a new blessings cache is created, recently unused blessings will be
+// cleaned up periodically.
+type BlessingsCache struct {
+	lock       sync.Mutex
+	nextId     BlessingsId
+	entries    map[BlessingsId]*BlessingsCacheEntry
+	notifier   func([]BlessingsCacheMessage)
+	inactiveGc bool
+}
+
+func NewBlessingsCache(notifier func([]BlessingsCacheMessage), gcPolicy *BlessingsCacheGCPolicy) *BlessingsCache {
+	bc := &BlessingsCache{
+		entries:  map[BlessingsId]*BlessingsCacheEntry{},
+		notifier: notifier,
+	}
+	go bc.gcLoop(gcPolicy)
+	return bc
+}
+
+// Put a blessing in the blessing cache and get an id for it that will
+// be understood by the Javascript cache.
+// If the blessings object is already in the cache, a new entry won't be created
+// and the id for the blessings from a previous put will be returned.
+func (bc *BlessingsCache) Put(blessings security.Blessings) BlessingsId {
+	if blessings.IsZero() {
+		return 0
+	}
+
+	bc.lock.Lock()
+	defer bc.lock.Unlock()
+
+	for id, entry := range bc.entries {
+		if reflect.DeepEqual(blessings, entry.blessings) {
+			entry.refCount++
+			entry.dirty = true
+			return id
+		}
+	}
+
+	bc.nextId++
+	id := bc.nextId
+	bc.entries[id] = &BlessingsCacheEntry{
+		refCount:  1,
+		blessings: blessings,
+		dirty:     true,
+	}
+	addMessage := BlessingsCacheAddMessage{
+		CacheId:   id,
+		Blessings: blessings,
+	}
+	bc.notifier([]BlessingsCacheMessage{
+		BlessingsCacheMessageAdd{
+			Value: addMessage,
+		},
+	})
+	return id
+}
+
+// BlessingsCacheGCPolicy specifies when GC will occur and provides a function
+// to pass notifications of deletions (used to notify Javascript to delete
+// the cache items).
+type BlessingsCacheGCPolicy struct {
+	nextTrigger func() <-chan time.Time
+}
+
+// PeriodicGcPolicy periodically performs gc at the specified interval.
+func PeriodicGcPolicy(interval time.Duration) *BlessingsCacheGCPolicy {
+	return &BlessingsCacheGCPolicy{
+		nextTrigger: func() <-chan time.Time { return time.After(interval) },
+	}
+}
+
+// Stop stops the GC loop and frees up resources.
+func (bc *BlessingsCache) Stop() {
+	bc.lock.Lock()
+	bc.inactiveGc = true
+	bc.lock.Unlock()
+}
+
+func (bc *BlessingsCache) gcLoop(policy *BlessingsCacheGCPolicy) {
+	for {
+		bc.lock.Lock()
+		if bc.inactiveGc {
+			return
+		}
+		bc.lock.Unlock()
+		<-policy.nextTrigger()
+		bc.gc(policy)
+	}
+}
+
+// Perform Garbage Collection.
+// All blessings that weren't referenced since the last GC will be removed from
+// the cache.
+func (bc *BlessingsCache) gc(policy *BlessingsCacheGCPolicy) {
+	bc.lock.Lock()
+	defer bc.lock.Unlock()
+
+	var toDelete []BlessingsCacheMessage
+	for id, entry := range bc.entries {
+		if !entry.dirty {
+			deleteEntry := BlessingsCacheDeleteMessage{
+				CacheId:     id,
+				DeleteAfter: entry.refCount,
+			}
+			toDelete = append(toDelete, BlessingsCacheMessageDelete{Value: deleteEntry})
+		} else {
+			entry.dirty = false
+		}
+	}
+
+	for _, deleteEntry := range toDelete {
+		delete(bc.entries, deleteEntry.(BlessingsCacheMessageDelete).Value.CacheId)
+	}
+
+	if len(toDelete) > 0 {
+		bc.notifier(toDelete)
+	}
+}
diff --git a/services/wspr/internal/principal/cache.vdl b/services/wspr/internal/principal/cache.vdl
new file mode 100644
index 0000000..6d42357
--- /dev/null
+++ b/services/wspr/internal/principal/cache.vdl
@@ -0,0 +1,30 @@
+// 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 principal
+
+import "v.io/v23/security"
+
+// Identifier of a blessings cache entry.
+type BlessingsId uint32
+
+type BlessingsCacheAddMessage struct {
+  CacheId BlessingsId
+  Blessings security.WireBlessings
+}
+
+// Message from Blessings Cache GC to delete a cache entry in Javascript.
+type BlessingsCacheDeleteMessage struct {
+  CacheId BlessingsId
+
+  // Number of references expected. Javascript should wait until this number
+  // has been received before deleting the entry because up until that point
+  // messages with further references are expected.
+  DeleteAfter uint32
+}
+
+type BlessingsCacheMessage union {
+  Add BlessingsCacheAddMessage
+  Delete BlessingsCacheDeleteMessage
+}
diff --git a/services/wspr/internal/principal/cache.vdl.go b/services/wspr/internal/principal/cache.vdl.go
new file mode 100644
index 0000000..a165582
--- /dev/null
+++ b/services/wspr/internal/principal/cache.vdl.go
@@ -0,0 +1,92 @@
+// 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 by the vanadium vdl tool.
+// Source: cache.vdl
+
+package principal
+
+import (
+	// VDL system imports
+	"v.io/v23/vdl"
+
+	// VDL user imports
+	"v.io/v23/security"
+)
+
+// Identifier of a blessings cache entry.
+type BlessingsId uint32
+
+func (BlessingsId) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/services/wspr/internal/principal.BlessingsId"`
+}) {
+}
+
+type BlessingsCacheAddMessage struct {
+	CacheId   BlessingsId
+	Blessings security.Blessings
+}
+
+func (BlessingsCacheAddMessage) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/services/wspr/internal/principal.BlessingsCacheAddMessage"`
+}) {
+}
+
+// Message from Blessings Cache GC to delete a cache entry in Javascript.
+type BlessingsCacheDeleteMessage struct {
+	CacheId BlessingsId
+	// Number of references expected. Javascript should wait until this number
+	// has been received before deleting the entry because up until that point
+	// messages with further references are expected.
+	DeleteAfter uint32
+}
+
+func (BlessingsCacheDeleteMessage) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/services/wspr/internal/principal.BlessingsCacheDeleteMessage"`
+}) {
+}
+
+type (
+	// BlessingsCacheMessage represents any single field of the BlessingsCacheMessage union type.
+	BlessingsCacheMessage interface {
+		// Index returns the field index.
+		Index() int
+		// Interface returns the field value as an interface.
+		Interface() interface{}
+		// Name returns the field name.
+		Name() string
+		// __VDLReflect describes the BlessingsCacheMessage union type.
+		__VDLReflect(__BlessingsCacheMessageReflect)
+	}
+	// BlessingsCacheMessageAdd represents field Add of the BlessingsCacheMessage union type.
+	BlessingsCacheMessageAdd struct{ Value BlessingsCacheAddMessage }
+	// BlessingsCacheMessageDelete represents field Delete of the BlessingsCacheMessage union type.
+	BlessingsCacheMessageDelete struct{ Value BlessingsCacheDeleteMessage }
+	// __BlessingsCacheMessageReflect describes the BlessingsCacheMessage union type.
+	__BlessingsCacheMessageReflect struct {
+		Name  string `vdl:"v.io/x/ref/services/wspr/internal/principal.BlessingsCacheMessage"`
+		Type  BlessingsCacheMessage
+		Union struct {
+			Add    BlessingsCacheMessageAdd
+			Delete BlessingsCacheMessageDelete
+		}
+	}
+)
+
+func (x BlessingsCacheMessageAdd) Index() int                                  { return 0 }
+func (x BlessingsCacheMessageAdd) Interface() interface{}                      { return x.Value }
+func (x BlessingsCacheMessageAdd) Name() string                                { return "Add" }
+func (x BlessingsCacheMessageAdd) __VDLReflect(__BlessingsCacheMessageReflect) {}
+
+func (x BlessingsCacheMessageDelete) Index() int                                  { return 1 }
+func (x BlessingsCacheMessageDelete) Interface() interface{}                      { return x.Value }
+func (x BlessingsCacheMessageDelete) Name() string                                { return "Delete" }
+func (x BlessingsCacheMessageDelete) __VDLReflect(__BlessingsCacheMessageReflect) {}
+
+func init() {
+	vdl.Register((*BlessingsId)(nil))
+	vdl.Register((*BlessingsCacheAddMessage)(nil))
+	vdl.Register((*BlessingsCacheDeleteMessage)(nil))
+	vdl.Register((*BlessingsCacheMessage)(nil))
+}
diff --git a/services/wspr/internal/principal/cache_test.go b/services/wspr/internal/principal/cache_test.go
new file mode 100644
index 0000000..4bbcebd
--- /dev/null
+++ b/services/wspr/internal/principal/cache_test.go
@@ -0,0 +1,253 @@
+// 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 principal
+
+import (
+	"crypto/ecdsa"
+	"crypto/elliptic"
+	"crypto/rand"
+	"reflect"
+	"sync"
+	"testing"
+	"time"
+
+	"v.io/v23/security"
+)
+
+// manualTrigger provides a gc trigger that can be signaled manually
+type manualTrigger struct {
+	gcShouldBeNext bool
+	lock           sync.Mutex
+	cond           *sync.Cond
+	gcHasRun       bool
+}
+
+func newManualTrigger() *manualTrigger {
+	mt := &manualTrigger{
+		gcShouldBeNext: true,
+	}
+	mt.cond = sync.NewCond(&mt.lock)
+	return mt
+}
+
+// policyTrigger is the trigger that should be provided in GC policy config.
+// It waits until it receives a signal and then returns a chan time.Time
+// that resolves immediately.
+func (mt *manualTrigger) waitForNextGc() <-chan time.Time {
+	mt.lock.Lock()
+	if !mt.gcHasRun {
+		mt.gcHasRun = true
+	} else {
+		mt.gcShouldBeNext = false // hand off control
+		mt.cond.Broadcast()
+		for !mt.gcShouldBeNext {
+			mt.cond.Wait()
+		}
+	}
+	mt.lock.Unlock()
+
+	trigger := make(chan time.Time, 1)
+	trigger <- time.Time{}
+	return trigger
+}
+
+// next should be called to trigger the next policy trigger event.
+func (mt *manualTrigger) next() {
+	mt.lock.Lock()
+	mt.gcShouldBeNext = true // hand off control
+	mt.cond.Broadcast()
+	for mt.gcShouldBeNext {
+		mt.cond.Wait()
+	}
+	mt.lock.Unlock()
+}
+
+// Test just to confirm it signals in order as expected.
+func TestManualTrigger(t *testing.T) {
+	mt := newManualTrigger()
+
+	countTriggers := 0
+	go func() {
+		for i := 1; i <= 100; i++ {
+			<-mt.waitForNextGc()
+			countTriggers++
+		}
+	}()
+
+	for i := 1; i <= 99; i++ {
+		mt.next()
+		if countTriggers != i {
+			t.Errorf("Expected %d triggers, got %d", i, countTriggers)
+		}
+	}
+}
+
+func newSigner() security.Signer {
+	key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+	if err != nil {
+		panic(err)
+	}
+	return security.NewInMemoryECDSASigner(key)
+}
+
+func TestBlessingsCache(t *testing.T) {
+	notificationCh := make(chan []BlessingsCacheMessage, 1)
+	notifier := func(msg []BlessingsCacheMessage) {
+		notificationCh <- msg
+	}
+
+	mt := newManualTrigger()
+
+	// Create a BlessingsCache with a GC policy that we can trigger on demand.
+	onDemandGCPolicy := &BlessingsCacheGCPolicy{
+		nextTrigger: mt.waitForNextGc,
+	}
+	bc := NewBlessingsCache(notifier, onDemandGCPolicy)
+
+	// Blessings for the tests.
+	p, err := security.CreatePrincipal(newSigner(), nil, nil)
+	if err != nil {
+		t.Fatal("Failed to create principal: ", err)
+	}
+	blessA, err := p.BlessSelf("A")
+	if err != nil {
+		t.Fatal("Failed to bless A: ", err)
+	}
+	blessB, err := p.BlessSelf("B")
+	if err != nil {
+		t.Fatal("Failed to bless B: ", err)
+	}
+	blessC, err := p.BlessSelf("C")
+	if err != nil {
+		t.Fatal("Failed to bless C: ", err)
+	}
+
+	// First do puts and make sure the ids are reasonable.
+	idA := bc.Put(blessA)
+	expectAddMessage(t, notificationCh, 1, blessA)
+	idB := bc.Put(blessB)
+	expectAddMessage(t, notificationCh, 2, blessB)
+	if idA == idB {
+		t.Errorf("A and B unexpectedly had same id: %v", idA)
+	}
+
+	idA2 := bc.Put(blessA)
+	expectNoMessage(t, notificationCh)
+	if idA2 != idA {
+		t.Errorf("A and A2 expected to have same id, but they were %v and %v",
+			idA, idA2)
+	}
+
+	// Now perform GC. Check that the values are still in the cache.
+	mt.next()
+	expectNoMessage(t, notificationCh)
+
+	idGc1A := bc.Put(blessA)
+	idGc1B := bc.Put(blessB)
+	expectNoMessage(t, notificationCh)
+	if idA != idGc1A {
+		t.Errorf("Expected to get same id after one gc of A, but got %v and %v",
+			idA, idGc1A)
+	}
+	if idB != idGc1B {
+		t.Errorf("Expected to get same id after one gc of B, but got %v and %v",
+			idB, idGc1B)
+	}
+
+	// Now perform GC to clear the dirty bits.
+	mt.next()
+	expectNoMessage(t, notificationCh)
+
+	// Update B and add C.
+	idGc2B := bc.Put(blessB)
+	expectNoMessage(t, notificationCh)
+	if idB != idGc2B {
+		t.Errorf("Expected to get same id after two gcs of B, but got %v and %v",
+			idB, idGc2B)
+	}
+	idC := bc.Put(blessC)
+	expectAddMessage(t, notificationCh, 3, blessC)
+	if idC == idA || idC == idB {
+		t.Error("C was unexpectedly the same as A or B")
+	}
+
+	// Perform GC. A should be removed but B and C should stay.
+	mt.next()
+	expectDeleteMessage(t, notificationCh, BlessingsCacheDeleteMessage{CacheId: 1, DeleteAfter: 3})
+	if idB != bc.Put(blessB) {
+		t.Errorf("B seems to have been cleaned up as it was given a new id")
+	}
+	if idC != bc.Put(blessC) {
+		t.Errorf("C seems to have been cleaned up as it was given a new id")
+	}
+	expectNoMessage(t, notificationCh)
+
+	// Perform GC twice to remove the other items.
+	mt.next()
+	expectNoMessage(t, notificationCh)
+	mt.next()
+	expectDeleteMessage(t, notificationCh,
+		BlessingsCacheDeleteMessage{CacheId: 2, DeleteAfter: 4},
+		BlessingsCacheDeleteMessage{CacheId: 3, DeleteAfter: 2})
+
+	// No notifications should occur on further GCs.
+	mt.next()
+	mt.next()
+	mt.next()
+	// Note that this should only be reached after the second GC is done because
+	// the GC trigger channel has size 1.
+	expectNoMessage(t, notificationCh)
+
+	bc.Stop()
+}
+
+func expectNoMessage(t *testing.T, notificationCh chan []BlessingsCacheMessage) {
+	select {
+	case <-notificationCh:
+		t.Errorf("Got message when none expected")
+	default:
+	}
+}
+
+func expectAddMessage(t *testing.T, notificationCh chan []BlessingsCacheMessage, id BlessingsId, bless security.Blessings) {
+	select {
+	case notifications := <-notificationCh:
+		if len(notifications) != 1 {
+			t.Fatalf("Got invalid add message with %d messages", len(notifications))
+		}
+		addMsg := notifications[0].(BlessingsCacheMessageAdd).Value
+		if got, want := addMsg.CacheId, id; got != want {
+			t.Errorf("Unexpected id in add message: %v. Wanted: %v", got, want)
+		}
+		if got, want := addMsg.Blessings, bless; !reflect.DeepEqual(got, want) {
+			t.Errorf("Blessings unexpectedly not equal. Got %v, want %v", got, want)
+		}
+	case <-time.After(10 * time.Second):
+		t.Fatalf("Timed out waiting for notification")
+	}
+}
+
+func expectDeleteMessage(t *testing.T, notificationCh chan []BlessingsCacheMessage, expected ...BlessingsCacheDeleteMessage) {
+	select {
+	case notifications := <-notificationCh:
+		if len(notifications) != len(expected) {
+			t.Fatalf("Got %d notifications but expected %d delete notifications", len(notifications), len(expected))
+		}
+		for _, notification := range notifications {
+			delNotif := notification.(BlessingsCacheMessageDelete).Value
+			var foundMatch bool
+			for _, expectedNotif := range expected {
+				if reflect.DeepEqual(delNotif, expectedNotif) {
+					foundMatch = true
+				}
+			}
+			if !foundMatch {
+				t.Errorf("Unexpected delete notification: %v", delNotif)
+			}
+		}
+	case <-time.After(10 * time.Second):
+		t.Fatalf("Timed out waiting for notification")
+	}
+}
diff --git a/services/wspr/internal/principal/file_serializer_nacl.go b/services/wspr/internal/principal/file_serializer_nacl.go
new file mode 100644
index 0000000..5a73758
--- /dev/null
+++ b/services/wspr/internal/principal/file_serializer_nacl.go
@@ -0,0 +1,69 @@
+// 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 principal
+
+import (
+	"io"
+	"os"
+	"runtime/ppapi"
+
+	"v.io/x/ref/lib/security"
+)
+
+// fileSerializer implements vsecurity.SerializerReaderWriter that persists state to
+// files with the pepper API.
+type fileSerializer struct {
+	system    ppapi.FileSystem
+	data      *ppapi.FileIO
+	signature *ppapi.FileIO
+
+	dataFile      string
+	signatureFile string
+}
+
+var _ security.SerializerReaderWriter = (*fileSerializer)(nil)
+
+func (fs *fileSerializer) Readers() (data io.ReadCloser, sig io.ReadCloser, err error) {
+	if fs.data == nil || fs.signature == nil {
+		return nil, nil, nil
+	}
+	return fs.data, fs.signature, nil
+}
+
+func (fs *fileSerializer) Writers() (data io.WriteCloser, sig io.WriteCloser, err error) {
+	// Remove previous version of the files
+	fs.system.Remove(fs.dataFile)
+	fs.system.Remove(fs.signatureFile)
+	if fs.data, err = fs.system.Create(fs.dataFile); err != nil {
+		return nil, nil, err
+	}
+	if fs.signature, err = fs.system.Create(fs.signatureFile); err != nil {
+		return nil, nil, err
+	}
+	return fs.data, fs.signature, nil
+}
+
+func fileNotExist(err error) bool {
+	pe, ok := err.(*os.PathError)
+	return ok && pe.Err.Error() == "file not found"
+}
+
+func NewFileSerializer(dataFile, signatureFile string, system ppapi.FileSystem) (*fileSerializer, error) {
+	data, err := system.Open(dataFile)
+	if err != nil && !fileNotExist(err) {
+		return nil, err
+	}
+	signature, err := system.Open(signatureFile)
+	if err != nil && !fileNotExist(err) {
+		return nil, err
+	}
+	return &fileSerializer{
+		system:        system,
+		data:          data,
+		signature:     signature,
+		dataFile:      dataFile,
+		signatureFile: signatureFile,
+	}, nil
+}
diff --git a/services/wspr/internal/principal/in_memory_serializer.go b/services/wspr/internal/principal/in_memory_serializer.go
new file mode 100644
index 0000000..5ae1dad
--- /dev/null
+++ b/services/wspr/internal/principal/in_memory_serializer.go
@@ -0,0 +1,39 @@
+// 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 principal
+
+import (
+	"io"
+
+	"v.io/x/ref/lib/security"
+)
+
+// inMemorySerializer implements SerializerReaderWriter. This Serializer should only
+// be used in tests.
+type inMemorySerializer struct {
+	data      bufferCloser
+	signature bufferCloser
+	hasData   bool
+}
+
+var _ security.SerializerReaderWriter = (*inMemorySerializer)(nil)
+
+func NewInMemorySerializer() *inMemorySerializer {
+	return &inMemorySerializer{}
+}
+
+func (s *inMemorySerializer) Readers() (io.ReadCloser, io.ReadCloser, error) {
+	if !s.hasData {
+		return nil, nil, nil
+	}
+	return &s.data, &s.signature, nil
+}
+
+func (s *inMemorySerializer) Writers() (io.WriteCloser, io.WriteCloser, error) {
+	s.hasData = true
+	s.data.Reset()
+	s.signature.Reset()
+	return &s.data, &s.signature, nil
+}
diff --git a/services/wspr/internal/principal/principal.go b/services/wspr/internal/principal/principal.go
new file mode 100644
index 0000000..dc7dca2
--- /dev/null
+++ b/services/wspr/internal/principal/principal.go
@@ -0,0 +1,331 @@
+// 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 principal implements a principal manager that maps origins to
+// vanadium principals.
+//
+// Each instance of wspr is expected to have a single (human) user at a time
+// that will be signed in and using various apps. A user may have different
+// Blessings, which in practice will be done by having multiple accounts
+// across many Blessing providers (e.g., google, facebook, etc). This is similar
+// to having a master identity that is linked to multiple identities in today's
+// technology. In our case, each user account is represented as a Blessing
+// obtained from the corresponding Blessing provider.
+//
+// Every app/origin is a different principal and has its own public/private key
+// pair, represented by a Principal object that is created by this manager on
+// the app's behalf. For each app/origin, the user may choose which account to
+// use for the app, which results in a blessing generated for the app principal
+// using the selected account's blessing.
+//
+// For example, a user Alice may have an account at Google and Facebook,
+// resulting in the blessings "google/alice@gmail.com" and
+// "facebook/alice@facebook.com". She may then choose to use her Google account
+// for the "googleplay" app, which would result in the creation of a new
+// principal for that app and a blessing "google/alice@gmail.com/googleplay" for
+// that principal.
+//
+// The principal manager only serializes the mapping from apps to (chosen)
+// accounts and the account information, but not the private keys for each app.
+package principal
+
+import (
+	"bytes"
+	"fmt"
+	"net/url"
+	"sync"
+	"time"
+
+	"v.io/v23/security"
+	"v.io/v23/verror"
+	"v.io/v23/vom"
+	vsecurity "v.io/x/ref/lib/security"
+	"v.io/x/ref/lib/security/serialization"
+)
+
+// permissions is a set of a permissions given to an app, containing the account
+// the app has access to and the caveats associated with it.
+type permissions struct {
+	// Account is the name of the account given to the app.
+	Account string
+	// Caveats that must be added to the blessing generated for the app from
+	// the account's blessing.
+	Caveats []security.Caveat
+	// Expirations is the expiration times of any expiration caveats.  Must
+	// be unix-time so it can be persisted.
+	Expirations []int64
+}
+
+// persistentState is the state of the manager that will be persisted to disk.
+type persistentState struct {
+	// A mapping of origins to the permissions provided for the origin (such as
+	// caveats and the account given to the origin)
+	Origins map[string]permissions
+
+	// A set of accounts that maps from an account name to the Blessings associated
+	// with the account.
+	Accounts map[string]security.Blessings
+}
+
+const pkgPath = "v.io/x/ref/services/wspr/internal/principal"
+
+// Errors.
+var (
+	errUnknownAccount                   = verror.Register(pkgPath+".errUnknownAccount", verror.NoRetry, "{1:}{2:} unknown account{:_}")
+	errFailedToCreatePrincipal          = verror.Register(pkgPath+".errFailedToCreatePrincipal", verror.NoRetry, "{1:}{2:} failed to create new principal{:_}")
+	errFailedToConstructBlessings       = verror.Register(pkgPath+".errFailedToConstructBlessings", verror.NoRetry, "{1:}{2:} failed to construct Blessings to bless with{:_}")
+	errFailedToBlessPrincipal           = verror.Register(pkgPath+".errFailedToBlessPrincipal", verror.NoRetry, "{1:}{2:} failed to bless new principal with the provided account{:_}")
+	errFailedToSetDefaultBlessings      = verror.Register(pkgPath+".errFailedToSetDefaultBlessings", verror.NoRetry, "{1:}{2:} failed to set account blessings as default{:_}")
+	errFailedToSetAllPrincipalBlessings = verror.Register(pkgPath+".errFailedToSetAllPrincipalBlessings", verror.NoRetry, "{1:}{2:} failed to set account blessings for all principals{:_}")
+	errFailedToAddRoots                 = verror.Register(pkgPath+".errFailedToAddRoots", verror.NoRetry, "{1:}{2:} failed to add roots of account blessing{:_}")
+)
+
+// bufferCloser implements io.ReadWriteCloser.
+type bufferCloser struct {
+	bytes.Buffer
+}
+
+func (*bufferCloser) Close() error {
+	return nil
+}
+
+// PrincipalManager manages app principals. We only serialize the accounts
+// associated with this principal manager and the mapping of apps to permissions
+// that they were given.
+type PrincipalManager struct {
+	mu    sync.Mutex
+	state persistentState
+
+	// root is the Principal that hosts this PrincipalManager.
+	root security.Principal
+
+	serializer vsecurity.SerializerReaderWriter
+
+	// Dummy account name
+	// TODO(bjornick, nlacasse): Remove this once the tests no longer need
+	// it.
+	dummyAccount string
+}
+
+// NewPrincipalManager returns a new PrincipalManager that creates new principals
+// for various app/origins and blessed them with blessings for the provided 'root'
+// principal from the specified blessing provider.
+// .
+//
+// It is initialized by reading data from the 'serializer' passed in which must
+// be non-nil.
+func NewPrincipalManager(root security.Principal, serializer vsecurity.SerializerReaderWriter) (*PrincipalManager, error) {
+	result := &PrincipalManager{
+		state: persistentState{
+			Origins:  map[string]permissions{},
+			Accounts: map[string]security.Blessings{},
+		},
+		root:       root,
+		serializer: serializer,
+	}
+	data, signature, err := serializer.Readers()
+	if err != nil {
+		return nil, err
+	}
+	if (data == nil) || (signature == nil) {
+		// No serialized data exists, returning an empty PrincipalManager.
+		return result, nil
+	}
+	vr, err := serialization.NewVerifyingReader(data, signature, root.PublicKey())
+	if err != nil {
+		return nil, err
+	}
+
+	decoder := vom.NewDecoder(vr)
+	if err := decoder.Decode(&result.state); err != nil {
+		return nil, err
+	}
+	return result, nil
+}
+
+func (i *PrincipalManager) save() error {
+	data, signature, err := i.serializer.Writers()
+	if err != nil {
+		return err
+	}
+	swc, err := serialization.NewSigningWriteCloser(data, signature, i.root, nil)
+	if err != nil {
+		return err
+	}
+
+	encoder := vom.NewEncoder(swc)
+	if err := encoder.Encode(i.state); err != nil {
+		return err
+	}
+	return swc.Close()
+}
+
+// Principal returns the Principal for an origin or an error if there is
+// no linked account.
+func (i *PrincipalManager) Principal(origin string) (security.Principal, error) {
+	i.mu.Lock()
+	defer i.mu.Unlock()
+	perm, found := i.state.Origins[origin]
+	if !found {
+		return nil, verror.New(verror.ErrNoExist, nil, origin)
+	}
+	blessings, found := i.state.Accounts[perm.Account]
+	if !found {
+		return nil, verror.New(errUnknownAccount, nil, perm.Account)
+	}
+	return i.createPrincipal(origin, blessings, perm.Caveats)
+}
+
+// OriginHasAccount returns true iff the origin has been associated with
+// permissions and an account for which blessings have been obtained from a
+// blessing provider, and if the blessings have no expiration caveats or an
+// expiration in the future.
+func (i *PrincipalManager) OriginHasAccount(origin string) bool {
+	i.mu.Lock()
+	defer i.mu.Unlock()
+	perm, found := i.state.Origins[origin]
+	if !found {
+		return false
+	}
+
+	// Check if all expiration caveats are satisfied.
+	now := time.Now()
+	for _, unixExp := range perm.Expirations {
+		exp := time.Unix(unixExp, 0)
+		if exp.Before(now) {
+			return false
+		}
+	}
+
+	_, found = i.state.Accounts[perm.Account]
+	return found
+}
+
+// BlessingsForAccount returns the Blessing associated with the provided
+// account.  It returns an error if account does not exist.
+//
+// TODO(ataly, ashankar, bjornick): Modify this method to allow searching
+// for accounts from a specific root blessing provider. One option is
+// that the method could take a set of root blessing providers as argument
+// and then return accounts whose blessings are from one of these providers.
+func (i *PrincipalManager) BlessingsForAccount(account string) (security.Blessings, error) {
+	i.mu.Lock()
+	defer i.mu.Unlock()
+
+	blessings, found := i.state.Accounts[account]
+	if !found {
+		return security.Blessings{}, verror.New(errUnknownAccount, nil, account)
+	}
+	return blessings, nil
+}
+
+// AddAccount associates the provided Blessing with the provided account.
+func (i *PrincipalManager) AddAccount(account string, blessings security.Blessings) error {
+	i.mu.Lock()
+	defer i.mu.Unlock()
+
+	old, existed := i.state.Accounts[account]
+	i.state.Accounts[account] = blessings
+
+	if err := i.save(); err != nil {
+		delete(i.state.Accounts, account)
+		if existed {
+			i.state.Accounts[account] = old
+		}
+		return err
+	}
+	return nil
+}
+
+// GetAccounts returns a list of account names known to the Principal Manager.
+func (i *PrincipalManager) GetAccounts() []string {
+	i.mu.Lock()
+	defer i.mu.Unlock()
+
+	accounts := make([]string, 0, len(i.state.Accounts))
+	for account := range i.state.Accounts {
+		accounts = append(accounts, account)
+	}
+
+	return accounts
+}
+
+// AddOrigin adds an origin to the manager linked to the given account.
+func (i *PrincipalManager) AddOrigin(origin string, account string, caveats []security.Caveat, expirations []time.Time) error {
+	i.mu.Lock()
+	defer i.mu.Unlock()
+	if _, found := i.state.Accounts[account]; !found {
+		return verror.New(errUnknownAccount, nil, account)
+	}
+
+	unixExpirations := []int64{}
+	for _, exp := range expirations {
+		unixExpirations = append(unixExpirations, exp.Unix())
+	}
+
+	old, existed := i.state.Origins[origin]
+	i.state.Origins[origin] = permissions{account, caveats, unixExpirations}
+
+	if err := i.save(); err != nil {
+		delete(i.state.Origins, origin)
+		if existed {
+			i.state.Origins[origin] = old
+		}
+		return err
+	}
+	return nil
+}
+
+func (i *PrincipalManager) createPrincipal(origin string, withBlessings security.Blessings, caveats []security.Caveat) (security.Principal, error) {
+	ret, err := vsecurity.NewPrincipal()
+	if err != nil {
+		return nil, verror.New(errFailedToCreatePrincipal, nil, err)
+	}
+
+	if len(caveats) == 0 {
+		caveats = append(caveats, security.UnconstrainedUse())
+	}
+	// Origins have the form protocol://hostname:port, which is not a valid
+	// blessing extension. Hence we must url-encode.
+	blessings, err := i.root.Bless(ret.PublicKey(), withBlessings, url.QueryEscape(origin), caveats[0], caveats[1:]...)
+	if err != nil {
+		return nil, verror.New(errFailedToBlessPrincipal, nil, err)
+	}
+
+	if err := ret.BlessingStore().SetDefault(blessings); err != nil {
+		return nil, verror.New(errFailedToSetDefaultBlessings, nil, err)
+	}
+	if _, err := ret.BlessingStore().Set(blessings, security.AllPrincipals); err != nil {
+		return nil, verror.New(errFailedToSetAllPrincipalBlessings, nil, err)
+	}
+	if err := ret.AddToRoots(blessings); err != nil {
+		return nil, verror.New(errFailedToAddRoots, nil, err)
+	}
+	return ret, nil
+}
+
+// Add dummy account with default blessings, for use by unauthenticated
+// clients.
+// TODO(nlacasse, bjornick): This should go away once unauthenticate clients
+// are no longer allowed.
+func (i *PrincipalManager) DummyAccount() (string, error) {
+	if i.dummyAccount == "" {
+		// Note: We only set i.dummyAccount once the account has been
+		// successfully created.  Otherwise, if an error occurs, the
+		// next time this function is called it the account won't exist
+		// but this function will return the name of the account
+		// without trying to create it.
+		dummyAccount := "unauthenticated-dummy-account"
+		blessings, err := i.root.BlessSelf(dummyAccount)
+		if err != nil {
+			return "", fmt.Errorf("i.root.BlessSelf(%v) failed: %v", dummyAccount, err)
+		}
+
+		if err := i.AddAccount(dummyAccount, blessings); err != nil {
+			return "", fmt.Errorf("browspr.principalManager.AddAccount(%v, %v) failed: %v", dummyAccount, blessings, err)
+		}
+		i.dummyAccount = dummyAccount
+	}
+	return i.dummyAccount, nil
+}
diff --git a/services/wspr/internal/principal/principal_test.go b/services/wspr/internal/principal/principal_test.go
new file mode 100644
index 0000000..64cb612
--- /dev/null
+++ b/services/wspr/internal/principal/principal_test.go
@@ -0,0 +1,244 @@
+// 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 principal
+
+import (
+	"bytes"
+	"fmt"
+	"net/url"
+	"reflect"
+	"testing"
+	"time"
+
+	"v.io/v23/context"
+	"v.io/v23/security"
+	"v.io/v23/verror"
+	"v.io/v23/vom"
+
+	"v.io/x/ref/test/testutil"
+)
+
+func accountBlessing(p security.Principal, name string) security.Blessings {
+	// Ideally the account blessing would be from a principal representing
+	// the identity provider but for testing purpose we mock it out using a
+	// self-blessing.
+	return blessSelf(p, name)
+}
+
+type tester struct {
+	googleAccount, facebookAccount, origin string
+	googleBlessings, facebookBlessings     security.Blessings
+}
+
+func (t *tester) testSetters(m *PrincipalManager) error {
+	// Test AddAccount.
+	if err := m.AddAccount(t.googleAccount, t.googleBlessings); err != nil {
+		return fmt.Errorf("AddAccount(%v, %v) failed: %v", t.googleAccount, t.googleBlessings, err)
+	}
+	if err := m.AddAccount(t.facebookAccount, t.facebookBlessings); err != nil {
+		return fmt.Errorf("AddAccount(%v, %v) failed: %v", t.facebookAccount, t.facebookBlessings, err)
+	}
+
+	// Test AddOrigin.
+	cav, err := security.NewMethodCaveat("Foo")
+	if err != nil {
+		return fmt.Errorf("security.MethodCaveat failed: %v", err)
+	}
+
+	if err := m.AddOrigin(t.origin, t.googleAccount, []security.Caveat{cav}, nil); err != nil {
+		return fmt.Errorf("AddOrigin failed: %v", err)
+	}
+
+	if err := matchesErrorID(m.AddOrigin(t.origin, "nonExistingAccount", nil, nil), errUnknownAccount.ID); err != nil {
+		return fmt.Errorf("AddOrigin(..., 'nonExistingAccount', ...): %v", err)
+	}
+	return nil
+}
+
+func (t *tester) testGetters(m *PrincipalManager) error {
+	// Test Principal.
+	pOrigin, err := m.Principal(t.origin)
+	if err != nil {
+		return fmt.Errorf("Principal failed: %v", err)
+	}
+
+	bOrigin := pOrigin.BlessingStore().Default()
+	// Validate the integrity of the bits.
+	buf := new(bytes.Buffer)
+
+	encoder := vom.NewEncoder(buf)
+	if encoder.Encode(bOrigin); err != nil {
+		return err
+	}
+	decoder := vom.NewDecoder(buf)
+	var decoded security.Blessings
+	if err := decoder.Decode(&decoded); err != nil {
+		return err
+	}
+	if !reflect.DeepEqual(decoded, bOrigin) {
+		return fmt.Errorf("reflect.DeepEqual(%v, %v) failed after validBlessing", decoded, bOrigin)
+	}
+	bnames := func(b security.Blessings, method string) ([]string, []security.RejectedBlessing) {
+		ctx, cancel := context.RootContext()
+		defer cancel()
+		return security.RemoteBlessingNames(ctx, security.NewCall(&security.CallParams{
+			LocalPrincipal:  pOrigin,
+			RemoteBlessings: b,
+			Method:          method}))
+	}
+
+	// Validate the blessings in various contexts.
+	want := []string{t.googleAccount + security.ChainSeparator + url.QueryEscape(t.origin)}
+	if got, _ := bnames(bOrigin, "Foo"); !reflect.DeepEqual(got, want) {
+		return fmt.Errorf("with method 'Foo', got blessing: %v, want: %v", got, want)
+	}
+	if got, _ := bnames(bOrigin, "Bar"); len(got) != 0 {
+		return fmt.Errorf("with method 'Bar', got blessing: %v, want empty", got)
+	}
+
+	unknownOrigin := "http://unknown.com:80"
+	_, err = m.Principal(unknownOrigin)
+	if merr := matchesErrorID(err, verror.ErrNoExist.ID); merr != nil {
+		return fmt.Errorf("Principal(%v): %v", unknownOrigin, merr)
+	}
+
+	// Test BlessingsForAccount.
+	if got, err := m.BlessingsForAccount(t.googleAccount); err != nil || !reflect.DeepEqual(got, t.googleBlessings) {
+		return fmt.Errorf("BlessingsForAccount(%v): got: %v, %v want: %v, nil", t.googleAccount, got, err, t.googleBlessings)
+	}
+	if got, err := m.BlessingsForAccount(t.facebookAccount); err != nil || !reflect.DeepEqual(got, t.facebookBlessings) {
+		return fmt.Errorf("BlessingsForAccount(%v): got: %v, %v, want: %v, nil", t.facebookAccount, got, err, t.facebookBlessings)
+	}
+	nonExistingAccount := "nonExistingAccount"
+	if got, err := m.BlessingsForAccount(nonExistingAccount); !got.IsZero() {
+		return fmt.Errorf("BlessingsForAccount(%v): got: %v, want nil", nonExistingAccount, got)
+	} else if merr := matchesError(err, "unknown account"); merr != nil {
+		return fmt.Errorf("BlessingsForAccount(%v) returned error: %v", nonExistingAccount, merr)
+	}
+	return nil
+}
+
+func newTester(root security.Principal) *tester {
+	googleAccount := "google/alice@gmail.com"
+	facebookAccount := "facebook/alice@facebook.com"
+	return &tester{
+		googleAccount:     googleAccount,
+		facebookAccount:   facebookAccount,
+		origin:            "https://sampleapp-1.com:443",
+		googleBlessings:   accountBlessing(root, googleAccount),
+		facebookBlessings: accountBlessing(root, facebookAccount),
+	}
+}
+
+func TestPrincipalManager(t *testing.T) {
+	root := testutil.NewPrincipal()
+	m, err := NewPrincipalManager(root, NewInMemorySerializer())
+	if err != nil {
+		t.Fatalf("NewPrincipalManager failed: %v", err)
+	}
+
+	mt := newTester(root)
+	if err := mt.testSetters(m); err != nil {
+		t.Fatal(err)
+	}
+	if err := mt.testGetters(m); err != nil {
+		t.Fatal(err)
+	}
+}
+
+func TestPrincipalManagerPersistence(t *testing.T) {
+	root := testutil.NewPrincipal()
+	serializer := NewInMemorySerializer()
+	m, err := NewPrincipalManager(root, serializer)
+	if err != nil {
+		t.Fatalf("NewPrincipalManager failed: %v", err)
+	}
+
+	mt := newTester(root)
+	if err := mt.testSetters(m); err != nil {
+		t.Fatal(err)
+	}
+	if err := mt.testGetters(m); err != nil {
+		t.Fatal(err)
+	}
+
+	if m, err = NewPrincipalManager(root, serializer); err != nil {
+		t.Fatalf("NewPrincipalManager failed: %v", err)
+	}
+	if err := mt.testGetters(m); err != nil {
+		t.Fatal(err)
+	}
+}
+
+func TestOriginHasAccount(t *testing.T) {
+	root := testutil.NewPrincipal()
+	m, err := NewPrincipalManager(root, NewInMemorySerializer())
+	if err != nil {
+		t.Fatalf("NewPrincipalManager failed: %v", err)
+	}
+
+	googleAccount := "fred/jim@gmail.com"
+	googleBlessings := accountBlessing(root, googleAccount)
+
+	if err := m.AddAccount(googleAccount, googleBlessings); err != nil {
+		t.Errorf("AddAccount(%v, %v) failed: %v", googleAccount, googleBlessings, err)
+	}
+
+	// Test with unknown origin.
+	unknownOrigin := "http://unknown.com"
+	if m.OriginHasAccount(unknownOrigin) {
+		t.Errorf("Expected m.OriginHasAccount(%v) to be false but it was true.", unknownOrigin)
+	}
+
+	// Test with no expiration caveat.
+	origin1 := "http://origin-1.com"
+
+	methodCav, err := security.NewMethodCaveat("Foo")
+	if err != nil {
+		t.Errorf("security.MethodCaveat failed: %v", err)
+	}
+
+	if err := m.AddOrigin(origin1, googleAccount, []security.Caveat{methodCav}, nil); err != nil {
+		t.Errorf("AddOrigin failed: %v", err)
+	}
+
+	if !m.OriginHasAccount(origin1) {
+		t.Errorf("Expected m.OriginHasAccount(%v) to be true but it was false.", origin1)
+	}
+
+	// Test with expiration caveat in the future.
+	origin2 := "http://origin-2.com"
+	futureTime := time.Now().Add(5 * time.Minute)
+
+	futureExpCav, err := security.NewExpiryCaveat(futureTime)
+	if err != nil {
+		t.Errorf("security.NewExpiryCaveat(%v) failed: %v", futureTime, err)
+	}
+
+	if err := m.AddOrigin(origin2, googleAccount, []security.Caveat{futureExpCav}, []time.Time{futureTime}); err != nil {
+		t.Errorf("AddOrigin failed: %v", err)
+	}
+
+	if !m.OriginHasAccount(origin2) {
+		t.Errorf("Expected m.OriginHasAccount(%v) to be true but it was false.", origin2)
+	}
+
+	// Test with expiration caveats in the past and future.
+	origin3 := "http://origin-3.com"
+	pastTime := time.Now().Add(-5 * time.Minute)
+
+	pastExpCav, err := security.NewExpiryCaveat(pastTime)
+	if err != nil {
+		t.Errorf("security.NewExpiryCaveat(%v) failed: %v", pastTime, err)
+	}
+
+	if err := m.AddOrigin(origin3, googleAccount, []security.Caveat{futureExpCav, pastExpCav}, []time.Time{futureTime, pastTime}); err != nil {
+		t.Errorf("AddOrigin failed: %v", err)
+	}
+
+	if m.OriginHasAccount(origin3) {
+		t.Errorf("Expected m.OriginHasAccount(%v) to be false but it was true.", origin3)
+	}
+}
diff --git a/services/wspr/internal/principal/util_test.go b/services/wspr/internal/principal/util_test.go
new file mode 100644
index 0000000..7ee21ed
--- /dev/null
+++ b/services/wspr/internal/principal/util_test.go
@@ -0,0 +1,47 @@
+// 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 principal
+
+import (
+	"fmt"
+	"strings"
+
+	"v.io/v23/security"
+	"v.io/v23/verror"
+)
+
+func blessSelf(p security.Principal, name string) security.Blessings {
+	b, err := p.BlessSelf(name)
+	if err != nil {
+		panic(err)
+	}
+	return b
+}
+
+func matchesError(got error, want string) error {
+	if (got == nil) && len(want) == 0 {
+		return nil
+	}
+	if got == nil {
+		return fmt.Errorf("Got nil error, wanted to match %q", want)
+	}
+	if !strings.Contains(got.Error(), want) {
+		return fmt.Errorf("Got error %q, wanted to match %q", got, want)
+	}
+	return nil
+}
+
+func matchesErrorID(got error, want verror.ID) error {
+	if (got == nil) && len(want) == 0 {
+		return nil
+	}
+	if got == nil {
+		return fmt.Errorf("Got nil error, wanted to match %q", want)
+	}
+	if verror.ErrorID(got) != want {
+		return fmt.Errorf("Got error %q, wanted to match %q", got, want)
+	}
+	return nil
+}
diff --git a/services/wspr/internal/rpc/server/dispatcher.go b/services/wspr/internal/rpc/server/dispatcher.go
new file mode 100644
index 0000000..03a974d
--- /dev/null
+++ b/services/wspr/internal/rpc/server/dispatcher.go
@@ -0,0 +1,144 @@
+// 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 server
+
+import (
+	"sync"
+
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/vdlroot/signature"
+	"v.io/v23/verror"
+	"v.io/x/ref/services/wspr/internal/lib"
+)
+
+type flowFactory interface {
+	createFlow() *Flow
+	cleanupFlow(id int32)
+}
+
+type invokerFactory interface {
+	createInvoker(handle int32, signature []signature.Interface, hasGlobber bool) (rpc.Invoker, error)
+}
+
+type authFactory interface {
+	createAuthorizer(handle int32, hasAuthorizer bool) (security.Authorizer, error)
+}
+
+type dispatcherRequest struct {
+	ServerId uint32 `json:"serverId"`
+	Suffix   string `json:"suffix"`
+}
+
+// dispatcher holds the invoker and the authorizer to be used for lookup.
+type dispatcher struct {
+	mu                 sync.Mutex
+	serverId           uint32
+	flowFactory        flowFactory
+	invokerFactory     invokerFactory
+	authFactory        authFactory
+	outstandingLookups map[int32]chan LookupReply
+	vomHelper          VomHelper
+	closed             bool
+}
+
+var _ rpc.Dispatcher = (*dispatcher)(nil)
+
+// newDispatcher is a dispatcher factory.
+func newDispatcher(serverId uint32, flowFactory flowFactory, invokerFactory invokerFactory, authFactory authFactory, vomHelper VomHelper) *dispatcher {
+	return &dispatcher{
+		serverId:           serverId,
+		flowFactory:        flowFactory,
+		invokerFactory:     invokerFactory,
+		authFactory:        authFactory,
+		outstandingLookups: make(map[int32]chan LookupReply),
+		vomHelper:          vomHelper,
+	}
+}
+
+func (d *dispatcher) Cleanup() {
+	d.mu.Lock()
+	defer d.mu.Unlock()
+	d.closed = true
+
+	for _, ch := range d.outstandingLookups {
+		verr := NewErrServerStopped(nil).(verror.E)
+		ch <- LookupReply{Err: &verr}
+	}
+}
+
+// Lookup implements dispatcher interface Lookup.
+func (d *dispatcher) Lookup(ctx *context.T, suffix string) (interface{}, security.Authorizer, error) {
+	// If the server has been closed, we immediately return a retryable error.
+	d.mu.Lock()
+	if d.closed {
+		d.mu.Unlock()
+		return nil, nil, NewErrServerStopped(ctx)
+	}
+	flow := d.flowFactory.createFlow()
+	ch := make(chan LookupReply, 1)
+	d.outstandingLookups[flow.ID] = ch
+	d.mu.Unlock()
+
+	message := dispatcherRequest{
+		ServerId: d.serverId,
+		Suffix:   suffix,
+	}
+	if err := flow.Writer.Send(lib.ResponseDispatcherLookup, message); err != nil {
+		verr := verror.Convert(verror.ErrInternal, ctx, err).(verror.E)
+		ch <- LookupReply{Err: &verr}
+	}
+	reply := <-ch
+
+	d.mu.Lock()
+	delete(d.outstandingLookups, flow.ID)
+	d.mu.Unlock()
+
+	d.flowFactory.cleanupFlow(flow.ID)
+
+	if reply.Err != nil {
+		return nil, nil, reply.Err
+	}
+	if reply.Handle < 0 {
+		return nil, nil, verror.New(verror.ErrNoExist, ctx, "Dispatcher", suffix)
+	}
+
+	invoker, err := d.invokerFactory.createInvoker(reply.Handle, reply.Signature, reply.HasGlobber)
+	if err != nil {
+		return nil, nil, err
+	}
+	auth, err := d.authFactory.createAuthorizer(reply.Handle, reply.HasAuthorizer)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	return invoker, auth, nil
+}
+
+func (d *dispatcher) handleLookupResponse(ctx *context.T, id int32, data string) {
+	d.mu.Lock()
+	ch := d.outstandingLookups[id]
+	d.mu.Unlock()
+
+	if ch == nil {
+		d.flowFactory.cleanupFlow(id)
+		ctx.Errorf("unknown invoke request for flow: %d", id)
+		return
+	}
+
+	var lookupReply LookupReply
+	if err := lib.HexVomDecode(data, &lookupReply, d.vomHelper.TypeDecoder()); err != nil {
+		err2 := verror.Convert(verror.ErrInternal, nil, err)
+		lookupReply = LookupReply{Err: err2}
+		ctx.Errorf("unmarshaling invoke request failed: %v, %s", err, data)
+	}
+
+	ch <- lookupReply
+}
+
+// StopServing implements dispatcher StopServing.
+func (*dispatcher) StopServing() {
+}
diff --git a/services/wspr/internal/rpc/server/dispatcher_test.go b/services/wspr/internal/rpc/server/dispatcher_test.go
new file mode 100644
index 0000000..472e339
--- /dev/null
+++ b/services/wspr/internal/rpc/server/dispatcher_test.go
@@ -0,0 +1,232 @@
+// 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 server
+
+import (
+	"fmt"
+	"reflect"
+	"testing"
+
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/vdl"
+	"v.io/v23/vdlroot/signature"
+	"v.io/v23/verror"
+	"v.io/v23/vom"
+	"v.io/x/ref/internal/logger"
+	"v.io/x/ref/services/wspr/internal/lib"
+	"v.io/x/ref/services/wspr/internal/lib/testwriter"
+)
+
+type mockVomHelper struct{}
+
+func (mockVomHelper) TypeEncoder() *vom.TypeEncoder {
+	return nil
+}
+
+func (mockVomHelper) TypeDecoder() *vom.TypeDecoder {
+	return nil
+}
+
+type mockFlowFactory struct {
+	writer testwriter.Writer
+}
+
+func (m *mockFlowFactory) createFlow() *Flow {
+	return &Flow{ID: 0, Writer: &m.writer}
+}
+
+func (*mockFlowFactory) cleanupFlow(int32) {}
+
+type mockInvoker struct {
+	handle     int32
+	sig        []signature.Interface
+	hasGlobber bool
+}
+
+func (m mockInvoker) Prepare(*context.T, string, int) ([]interface{}, []*vdl.Value, error) {
+	return nil, nil, nil
+}
+
+func (mockInvoker) Invoke(*context.T, rpc.StreamServerCall, string, []interface{}) ([]interface{}, error) {
+	return nil, nil
+}
+
+func (mockInvoker) Globber() *rpc.GlobState {
+	return nil
+}
+
+func (m mockInvoker) Signature(ctx *context.T, call rpc.ServerCall) ([]signature.Interface, error) {
+	return m.sig, nil
+}
+
+func (m mockInvoker) MethodSignature(ctx *context.T, call rpc.ServerCall, methodName string) (signature.Method, error) {
+	method, found := m.sig[0].FindMethod(methodName)
+	if !found {
+		return signature.Method{}, fmt.Errorf("Method %q not found", methodName)
+	}
+	return method, nil
+}
+
+type mockInvokerFactory struct{}
+
+func (mockInvokerFactory) createInvoker(handle int32, sig []signature.Interface, hasGlobber bool) (rpc.Invoker, error) {
+	return &mockInvoker{handle: handle, sig: sig, hasGlobber: hasGlobber}, nil
+}
+
+type mockAuthorizer struct {
+	handle        int32
+	hasAuthorizer bool
+}
+
+func (mockAuthorizer) Authorize(*context.T, security.Call) error { return nil }
+
+type mockAuthorizerFactory struct{}
+
+func (mockAuthorizerFactory) createAuthorizer(handle int32, hasAuthorizer bool) (security.Authorizer, error) {
+	return mockAuthorizer{handle: handle, hasAuthorizer: hasAuthorizer}, nil
+}
+
+func TestSuccessfulLookup(t *testing.T) {
+	ctx, cancel := context.RootContext()
+	defer cancel()
+	ctx = context.WithLogger(ctx, logger.Global())
+	flowFactory := &mockFlowFactory{}
+	d := newDispatcher(0, flowFactory, mockInvokerFactory{}, mockAuthorizerFactory{}, mockVomHelper{})
+	expectedSig := []signature.Interface{
+		{Name: "AName"},
+	}
+	go func() {
+		if err := flowFactory.writer.WaitForMessage(1); err != nil {
+			t.Errorf("failed to get dispatch request %v", err)
+			t.Fail()
+		}
+		reply := LookupReply{
+			Handle:        1,
+			HasAuthorizer: false,
+			Signature:     expectedSig,
+		}
+		d.handleLookupResponse(ctx, 0, lib.HexVomEncodeOrDie(reply, nil))
+	}()
+
+	invoker, auth, err := d.Lookup(ctx, "a/b")
+
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expectedInvoker := &mockInvoker{handle: 1, sig: expectedSig}
+	if !reflect.DeepEqual(invoker, expectedInvoker) {
+		t.Errorf("wrong invoker returned, expected: %#v, got :%#v", expectedInvoker, invoker)
+	}
+
+	expectedAuth := mockAuthorizer{handle: 1, hasAuthorizer: false}
+	if !reflect.DeepEqual(auth, expectedAuth) {
+		t.Errorf("wrong authorizer returned, expected: %v, got :%v", expectedAuth, auth)
+	}
+
+	expectedResponses := []lib.Response{
+		{
+			Type: lib.ResponseDispatcherLookup,
+			Message: map[string]interface{}{
+				"serverId": 0.0,
+				"suffix":   "a/b",
+			},
+		},
+	}
+	if err := testwriter.CheckResponses(&flowFactory.writer, expectedResponses, nil, nil); err != nil {
+		t.Error(err)
+	}
+}
+
+func TestSuccessfulLookupWithAuthorizer(t *testing.T) {
+	ctx, cancel := context.RootContext()
+	defer cancel()
+	ctx = context.WithLogger(ctx, logger.Global())
+	flowFactory := &mockFlowFactory{}
+	d := newDispatcher(0, flowFactory, mockInvokerFactory{}, mockAuthorizerFactory{}, mockVomHelper{})
+	expectedSig := []signature.Interface{
+		{Name: "AName"},
+	}
+	go func() {
+		if err := flowFactory.writer.WaitForMessage(1); err != nil {
+			t.Errorf("failed to get dispatch request %v", err)
+			t.Fail()
+		}
+		reply := LookupReply{
+			Handle:        1,
+			HasAuthorizer: true,
+			Signature:     expectedSig,
+		}
+		d.handleLookupResponse(ctx, 0, lib.HexVomEncodeOrDie(reply, nil))
+	}()
+
+	invoker, auth, err := d.Lookup(ctx, "a/b")
+
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
+	}
+
+	expectedInvoker := &mockInvoker{handle: 1, sig: expectedSig}
+	if !reflect.DeepEqual(invoker, expectedInvoker) {
+		t.Errorf("wrong invoker returned, expected: %v, got :%v", expectedInvoker, invoker)
+	}
+
+	expectedAuth := mockAuthorizer{handle: 1, hasAuthorizer: true}
+	if !reflect.DeepEqual(auth, expectedAuth) {
+		t.Errorf("wrong authorizer returned, expected: %v, got :%v", expectedAuth, auth)
+	}
+
+	expectedResponses := []lib.Response{
+		{
+			Type: lib.ResponseDispatcherLookup,
+			Message: map[string]interface{}{
+				"serverId": 0.0,
+				"suffix":   "a/b",
+			},
+		},
+	}
+	if err := testwriter.CheckResponses(&flowFactory.writer, expectedResponses, nil, nil); err != nil {
+		t.Error(err)
+	}
+}
+
+func TestFailedLookup(t *testing.T) {
+	ctx, cancel := context.RootContext()
+	defer cancel()
+	ctx = context.WithLogger(ctx, logger.Global())
+	flowFactory := &mockFlowFactory{}
+	d := newDispatcher(0, flowFactory, mockInvokerFactory{}, mockAuthorizerFactory{}, mockVomHelper{})
+	go func() {
+		if err := flowFactory.writer.WaitForMessage(1); err != nil {
+			t.Errorf("failed to get dispatch request %v", err)
+			t.Fail()
+		}
+		reply := LookupReply{
+			Err: verror.New(verror.ErrNoExist, nil),
+		}
+		d.handleLookupResponse(ctx, 0, lib.HexVomEncodeOrDie(reply, nil))
+	}()
+
+	_, _, err := d.Lookup(ctx, "a/b")
+
+	if err == nil {
+		t.Error("expected error, but got none")
+	}
+
+	expectedResponses := []lib.Response{
+		{
+			Type: lib.ResponseDispatcherLookup,
+			Message: map[string]interface{}{
+				"serverId": 0.0,
+				"suffix":   "a/b",
+			},
+		},
+	}
+	if err := testwriter.CheckResponses(&flowFactory.writer, expectedResponses, nil, nil); err != nil {
+		t.Error(err)
+	}
+}
diff --git a/services/wspr/internal/rpc/server/invoker.go b/services/wspr/internal/rpc/server/invoker.go
new file mode 100644
index 0000000..ae5c48a
--- /dev/null
+++ b/services/wspr/internal/rpc/server/invoker.go
@@ -0,0 +1,111 @@
+// 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 server
+
+import (
+	"v.io/v23/context"
+	"v.io/v23/glob"
+	"v.io/v23/rpc"
+	"v.io/v23/vdl"
+	"v.io/v23/vdlroot/signature"
+	"v.io/v23/verror"
+	"v.io/v23/vtrace"
+)
+
+var typedNil []int
+
+const pkgPath = "v.io/x/ref/services/wspr/internal/rpc/server"
+
+// Errors.
+var (
+	ErrWrongNumberOfArgs         = verror.Register(pkgPath+".ErrWrongNumberOfArgs", verror.NoRetry, "{1:}{2:} Method {3} got {4} args, want {5}{:_}")
+	ErrMethodNotFoundInSignature = verror.Register(pkgPath+".ErrMethodNotFoundInSignature", verror.NoRetry, "{1:}{2:} Method {3} not found in signature{:_}")
+)
+
+// invoker holds a delegate function to call on invoke and a list of methods that
+// are available for be called.
+type invoker struct {
+	// delegate function to call when an invoke request comes in
+	invokeFunc remoteInvokeFunc
+
+	signature []signature.Interface
+
+	globFunc remoteGlobFunc
+}
+
+var _ rpc.Invoker = (*invoker)(nil)
+
+// newInvoker is an invoker factory
+func newInvoker(signature []signature.Interface, invokeFunc remoteInvokeFunc, globFunc remoteGlobFunc) rpc.Invoker {
+	i := &invoker{invokeFunc, signature, globFunc}
+	return i
+}
+
+// Prepare implements the Invoker interface.
+func (i *invoker) Prepare(_ *context.T, methodName string, numArgs int) ([]interface{}, []*vdl.Value, error) {
+	method, err := i.MethodSignature(nil, nil, methodName)
+	if err != nil {
+		return nil, nil, err
+	}
+	if got, want := numArgs, len(method.InArgs); got != want {
+		return nil, nil, verror.New(ErrWrongNumberOfArgs, nil, methodName, got, want)
+	}
+	argptrs := make([]interface{}, len(method.InArgs))
+	for ix, arg := range method.InArgs {
+		argptrs[ix] = vdl.ZeroValue(arg.Type)
+	}
+	return argptrs, method.Tags, nil
+}
+
+// Invoke implements the Invoker interface.
+func (i *invoker) Invoke(ctx *context.T, call rpc.StreamServerCall, methodName string, argptrs []interface{}) ([]interface{}, error) {
+	replychan := i.invokeFunc(ctx, call, methodName, argptrs)
+
+	// Wait for the result
+	reply := <-replychan
+
+	if reply.Err != nil {
+		return nil, reply.Err
+	}
+
+	vtrace.GetStore(ctx).Merge(reply.TraceResponse)
+
+	// Convert the reply.Results from []*vdl.Value to []interface{}
+	results := make([]interface{}, len(reply.Results))
+	for i, r := range reply.Results {
+		results[i] = r
+	}
+	return results, nil
+}
+
+func (i *invoker) Globber() *rpc.GlobState {
+	if i.globFunc == nil {
+		return nil
+	}
+	return &rpc.GlobState{AllGlobber: i}
+}
+
+func (i *invoker) Glob__(ctx *context.T, call rpc.GlobServerCall, g *glob.Glob) error {
+	// TODO(rthellend,bjornick): Should we convert globFunc to match the
+	// new Glob__ interface?
+	ch, err := i.globFunc(ctx, call, g.String())
+	if ch != nil {
+		for reply := range ch {
+			call.SendStream().Send(reply)
+		}
+	}
+	return err
+}
+
+func (i *invoker) Signature(ctx *context.T, call rpc.ServerCall) ([]signature.Interface, error) {
+	return i.signature, nil
+}
+
+func (i *invoker) MethodSignature(ctx *context.T, call rpc.ServerCall, method string) (signature.Method, error) {
+	if methodSig, ok := signature.FirstMethod(i.signature, method); ok {
+		return methodSig, nil
+	}
+	return signature.Method{}, verror.New(ErrMethodNotFoundInSignature, ctx, method)
+}
diff --git a/services/wspr/internal/rpc/server/server.go b/services/wspr/internal/rpc/server/server.go
new file mode 100644
index 0000000..6205d91
--- /dev/null
+++ b/services/wspr/internal/rpc/server/server.go
@@ -0,0 +1,789 @@
+// 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.
+
+// An implementation of a server for WSPR
+
+package server
+
+import (
+	"fmt"
+	"sync"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/i18n"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/vdl"
+	"v.io/v23/vdlroot/signature"
+	vdltime "v.io/v23/vdlroot/time"
+	"v.io/v23/verror"
+	"v.io/v23/vom"
+	"v.io/v23/vtrace"
+	"v.io/x/ref/services/wspr/internal/lib"
+	"v.io/x/ref/services/wspr/internal/principal"
+)
+
+type Flow struct {
+	ID     int32
+	Writer lib.ClientWriter
+}
+
+type FlowHandler interface {
+	CreateNewFlow(server interface{}, sender rpc.Stream) *Flow
+
+	CleanupFlow(id int32)
+}
+
+type VomHelper interface {
+	TypeEncoder() *vom.TypeEncoder
+
+	TypeDecoder() *vom.TypeDecoder
+}
+
+type ServerHelper interface {
+	FlowHandler
+	VomHelper
+
+	SendLogMessage(level lib.LogLevel, msg string) error
+	BlessingsCache() *principal.BlessingsCache
+
+	Context() *context.T
+}
+
+// AuthRequest is a request for a javascript authorizer to run
+// This is exported to make the app test easier.
+type AuthRequest struct {
+	ServerId uint32       `json:"serverId"`
+	Handle   int32        `json:"handle"`
+	Call     SecurityCall `json:"call"`
+	Context  Context
+}
+
+type Server struct {
+	// serverStateLock should be aquired when starting or stopping the server.
+	// This should be locked before outstandingRequestLock.
+	serverStateLock sync.Mutex
+
+	// The rpc.ListenSpec to use with server.Listen
+	listenSpec *rpc.ListenSpec
+
+	// The server that handles the rpc layer.  Listen on this server is
+	// lazily started.
+	server rpc.Server
+
+	// The saved dispatcher to reuse when serve is called multiple times.
+	dispatcher *dispatcher
+
+	// Whether the server is listening.
+	isListening bool
+
+	// The server id.
+	id     uint32
+	helper ServerHelper
+
+	// outstandingRequestLock should be acquired only to update the outstanding request maps below.
+	outstandingRequestLock        sync.Mutex
+	outstandingServerRequests     map[int32]chan *lib.ServerRpcReply // GUARDED_BY outstandingRequestLock
+	outstandingAuthRequests       map[int32]chan error               // GUARDED_BY outstandingRequestLock
+	outstandingValidationRequests map[int32]chan []error             // GUARDED_BY outstandingRequestLock
+
+	// statusClose will be closed when the server is shutting down, this will
+	// cause the status poller to exit.
+	statusClose chan struct{}
+
+	ctx *context.T
+}
+
+type serverContextKey struct{}
+
+func NewServer(id uint32, listenSpec *rpc.ListenSpec, helper ServerHelper, opts ...rpc.ServerOpt) (*Server, error) {
+	server := &Server{
+		id:                            id,
+		helper:                        helper,
+		listenSpec:                    listenSpec,
+		outstandingServerRequests:     make(map[int32]chan *lib.ServerRpcReply),
+		outstandingAuthRequests:       make(map[int32]chan error),
+		outstandingValidationRequests: make(map[int32]chan []error),
+	}
+	var err error
+	ctx := helper.Context()
+	ctx = context.WithValue(ctx, serverContextKey{}, server)
+	if server.server, err = v23.NewServer(ctx, opts...); err != nil {
+		return nil, err
+	}
+	server.ctx = ctx
+	return server, nil
+}
+
+// remoteInvokeFunc is a type of function that can invoke a remote method and
+// communicate the result back via a channel to the caller
+type remoteInvokeFunc func(ctx *context.T, call rpc.StreamServerCall, methodName string, args []interface{}) <-chan *lib.ServerRpcReply
+
+func (s *Server) createRemoteInvokerFunc(handle int32) remoteInvokeFunc {
+	return func(ctx *context.T, call rpc.StreamServerCall, methodName string, args []interface{}) <-chan *lib.ServerRpcReply {
+		securityCall := ConvertSecurityCall(s.helper, ctx, call.Security(), true)
+
+		flow := s.helper.CreateNewFlow(s, call)
+		replyChan := make(chan *lib.ServerRpcReply, 1)
+		s.outstandingRequestLock.Lock()
+		s.outstandingServerRequests[flow.ID] = replyChan
+		s.outstandingRequestLock.Unlock()
+
+		var timeout vdltime.Deadline
+		if deadline, ok := ctx.Deadline(); ok {
+			timeout.Time = deadline
+		}
+
+		errHandler := func(err error) <-chan *lib.ServerRpcReply {
+			if ch := s.popServerRequest(flow.ID); ch != nil {
+				stdErr := verror.Convert(verror.ErrInternal, ctx, err).(verror.E)
+				ch <- &lib.ServerRpcReply{nil, &stdErr, vtrace.Response{}}
+				s.helper.CleanupFlow(flow.ID)
+			}
+			return replyChan
+		}
+
+		var grantedBlessings principal.BlessingsId
+		if !call.GrantedBlessings().IsZero() {
+			grantedBlessings = s.helper.BlessingsCache().Put(call.GrantedBlessings())
+		}
+
+		rpcCall := ServerRpcRequestCall{
+			SecurityCall: securityCall,
+			Deadline:     timeout,
+			TraceRequest: vtrace.GetRequest(ctx),
+			Context: Context{
+				Language: string(i18n.GetLangID(ctx)),
+			},
+			GrantedBlessings: grantedBlessings,
+		}
+
+		var vdlValArgs []*vdl.Value = make([]*vdl.Value, len(args))
+		for i, arg := range args {
+			vdlValArgs[i] = vdl.ValueOf(arg)
+		}
+
+		// Send a invocation request to JavaScript
+		message := ServerRpcRequest{
+			ServerId: s.id,
+			Handle:   handle,
+			Method:   lib.LowercaseFirstCharacter(methodName),
+			Args:     vdlValArgs,
+			Call:     rpcCall,
+		}
+		vomMessage, err := lib.HexVomEncode(message, s.helper.TypeEncoder())
+		if err != nil {
+			return errHandler(err)
+		}
+		if err := flow.Writer.Send(lib.ResponseServerRequest, vomMessage); err != nil {
+			return errHandler(err)
+		}
+
+		ctx.VI(3).Infof("calling method %q with args %v, MessageID %d assigned\n", methodName, args, flow.ID)
+
+		// Watch for cancellation.
+		go func() {
+			<-ctx.Done()
+			ch := s.popServerRequest(flow.ID)
+			if ch == nil {
+				return
+			}
+
+			// Send a cancel message to the JS server.
+			flow.Writer.Send(lib.ResponseCancel, nil)
+			s.helper.CleanupFlow(flow.ID)
+
+			err := verror.Convert(verror.ErrAborted, ctx, ctx.Err()).(verror.E)
+			ch <- &lib.ServerRpcReply{nil, &err, vtrace.Response{}}
+		}()
+
+		go s.proxyStream(call, flow, s.helper.TypeEncoder())
+
+		return replyChan
+	}
+}
+
+type globStream struct {
+	ch  chan naming.GlobReply
+	ctx *context.T
+}
+
+func (g *globStream) Send(item interface{}) error {
+	if v, ok := item.(naming.GlobReply); ok {
+		g.ch <- v
+		return nil
+	}
+	return verror.New(verror.ErrBadArg, g.ctx, item)
+}
+
+func (g *globStream) Recv(itemptr interface{}) error {
+	return verror.New(verror.ErrNoExist, g.ctx, "Can't call recieve on glob stream")
+}
+
+func (g *globStream) CloseSend() error {
+	close(g.ch)
+	return nil
+}
+
+// remoteGlobFunc is a type of function that can invoke a remote glob and
+// communicate the result back via the channel returned
+type remoteGlobFunc func(ctx *context.T, call rpc.ServerCall, pattern string) (<-chan naming.GlobReply, error)
+
+func (s *Server) createRemoteGlobFunc(handle int32) remoteGlobFunc {
+	return func(ctx *context.T, call rpc.ServerCall, pattern string) (<-chan naming.GlobReply, error) {
+		// Until the tests get fixed, we need to create a security context before creating the flow
+		// because creating the security context creates a flow and flow ids will be off.
+		// See https://github.com/vanadium/issues/issues/175
+		securityCall := ConvertSecurityCall(s.helper, ctx, call.Security(), true)
+
+		globChan := make(chan naming.GlobReply, 1)
+		flow := s.helper.CreateNewFlow(s, &globStream{
+			ch:  globChan,
+			ctx: ctx,
+		})
+		replyChan := make(chan *lib.ServerRpcReply, 1)
+		s.outstandingRequestLock.Lock()
+		s.outstandingServerRequests[flow.ID] = replyChan
+		s.outstandingRequestLock.Unlock()
+
+		var timeout vdltime.Deadline
+		if deadline, ok := ctx.Deadline(); ok {
+			timeout.Time = deadline
+		}
+
+		errHandler := func(err error) (<-chan naming.GlobReply, error) {
+			if ch := s.popServerRequest(flow.ID); ch != nil {
+				s.helper.CleanupFlow(flow.ID)
+			}
+			return nil, verror.Convert(verror.ErrInternal, ctx, err).(verror.E)
+		}
+
+		rpcCall := ServerRpcRequestCall{
+			SecurityCall:     securityCall,
+			Deadline:         timeout,
+			GrantedBlessings: s.helper.BlessingsCache().Put(call.GrantedBlessings()),
+			Context: Context{
+				Language: string(i18n.GetLangID(ctx)),
+			},
+		}
+
+		// Send a invocation request to JavaScript
+		message := ServerRpcRequest{
+			ServerId: s.id,
+			Handle:   handle,
+			Method:   "Glob__",
+			Args:     []*vdl.Value{vdl.ValueOf(pattern)},
+			Call:     rpcCall,
+		}
+		vomMessage, err := lib.HexVomEncode(message, s.helper.TypeEncoder())
+		if err != nil {
+			return errHandler(err)
+		}
+		if err := flow.Writer.Send(lib.ResponseServerRequest, vomMessage); err != nil {
+			return errHandler(err)
+		}
+
+		ctx.VI(3).Infof("calling method 'Glob__' with args %v, MessageID %d assigned\n", []interface{}{pattern}, flow.ID)
+
+		// Watch for cancellation.
+		go func() {
+			<-ctx.Done()
+			ch := s.popServerRequest(flow.ID)
+			if ch == nil {
+				return
+			}
+
+			// Send a cancel message to the JS server.
+			flow.Writer.Send(lib.ResponseCancel, nil)
+			s.helper.CleanupFlow(flow.ID)
+
+			err := verror.Convert(verror.ErrAborted, ctx, ctx.Err()).(verror.E)
+			ch <- &lib.ServerRpcReply{nil, &err, vtrace.Response{}}
+		}()
+
+		return globChan, nil
+	}
+}
+
+func (s *Server) proxyStream(stream rpc.Stream, flow *Flow, typeEncoder *vom.TypeEncoder) {
+	var item interface{}
+	var err error
+	w := flow.Writer
+	for err = stream.Recv(&item); err == nil; err = stream.Recv(&item) {
+		vomItem, err := lib.HexVomEncode(item, typeEncoder)
+		if err != nil {
+			w.Error(verror.Convert(verror.ErrInternal, nil, err))
+			return
+		}
+		if err := w.Send(lib.ResponseStream, vomItem); err != nil {
+			w.Error(verror.Convert(verror.ErrInternal, nil, err))
+			return
+		}
+	}
+	s.ctx.VI(1).Infof("Error reading from stream: %v\n", err)
+	s.outstandingRequestLock.Lock()
+	_, found := s.outstandingServerRequests[flow.ID]
+	s.outstandingRequestLock.Unlock()
+
+	if !found {
+		// The flow has already been closed.  This is usually because we got a response
+		// from the javascript server.
+		return
+	}
+
+	if err := w.Send(lib.ResponseStreamClose, nil); err != nil {
+		w.Error(verror.Convert(verror.ErrInternal, nil, err))
+		return
+	}
+}
+
+func makeListOfErrors(numErrors int, err error) []error {
+	errs := make([]error, numErrors)
+	for i := 0; i < numErrors; i++ {
+		errs[i] = err
+	}
+	return errs
+}
+
+// caveatValidationInGo validates caveats in Go, using the default logic.
+func caveatValidationInGo(ctx *context.T, call security.Call, sets [][]security.Caveat) []error {
+	results := make([]error, len(sets))
+	for i, set := range sets {
+		for _, cav := range set {
+			if err := cav.Validate(ctx, call); err != nil {
+				results[i] = err
+				break
+			}
+		}
+	}
+	return results
+}
+
+// caveatValidationInJavascript validates caveats in javascript.  It resolves
+// each []security.Caveat in cavs to an error (or nil) and collects them in a
+// slice.
+func (s *Server) caveatValidationInJavascript(ctx *context.T, call security.Call, cavs [][]security.Caveat) []error {
+	flow := s.helper.CreateNewFlow(s, nil)
+	req := CaveatValidationRequest{
+		Call: ConvertSecurityCall(s.helper, ctx, call, false),
+		Context: Context{
+			Language: string(i18n.GetLangID(ctx)),
+		},
+		Cavs: cavs,
+	}
+
+	replyChan := make(chan []error, 1)
+	s.outstandingRequestLock.Lock()
+	s.outstandingValidationRequests[flow.ID] = replyChan
+	s.outstandingRequestLock.Unlock()
+
+	defer func() {
+		s.outstandingRequestLock.Lock()
+		delete(s.outstandingValidationRequests, flow.ID)
+		s.outstandingRequestLock.Unlock()
+		s.cleanupFlow(flow.ID)
+	}()
+
+	if err := flow.Writer.Send(lib.ResponseValidate, req); err != nil {
+		ctx.VI(2).Infof("Failed to send validate response: %v", err)
+		replyChan <- makeListOfErrors(len(cavs), err)
+	}
+
+	// TODO(bprosnitz) Consider using a different timeout than the standard rpc timeout.
+	var timeoutChan <-chan time.Time
+	if deadline, ok := ctx.Deadline(); ok {
+		timeoutChan = time.After(deadline.Sub(time.Now()))
+	}
+
+	select {
+	case <-s.statusClose:
+		return caveatValidationInGo(ctx, call, cavs)
+	case <-timeoutChan:
+		return makeListOfErrors(len(cavs), NewErrCaveatValidationTimeout(ctx))
+	case reply := <-replyChan:
+		if len(reply) != len(cavs) {
+			ctx.VI(2).Infof("Wspr caveat validator received %d results from javascript but expected %d", len(reply), len(cavs))
+			return makeListOfErrors(len(cavs), NewErrInvalidValidationResponseFromJavascript(ctx))
+		}
+
+		return reply
+	}
+}
+
+// CaveatValidation implements a function suitable for passing to
+// security.OverrideCaveatValidation.
+//
+// Certain caveats (PublicKeyThirdPartyCaveat) are intercepted and handled in
+// go, while all other caveats are evaluated in javascript.
+func CaveatValidation(ctx *context.T, call security.Call, cavs [][]security.Caveat) []error {
+	// If the server isn't set in the context, we just perform validation in Go.
+	ctxServer := ctx.Value(serverContextKey{})
+	if ctxServer == nil {
+		return caveatValidationInGo(ctx, call, cavs)
+	}
+	// Otherwise we run our special logic.
+	server := ctxServer.(*Server)
+	type validationStatus struct {
+		err   error
+		isSet bool
+	}
+	valStatus := make([]validationStatus, len(cavs))
+
+	var caveatChainsToValidate [][]security.Caveat
+nextCav:
+	for i, chainCavs := range cavs {
+		var newChainCavs []security.Caveat
+		for _, cav := range chainCavs {
+			// If the server is closed handle all caveats in Go, because Javascript is
+			// no longer there.
+			select {
+			case <-server.statusClose:
+				res := cav.Validate(ctx, call)
+				if res != nil {
+					valStatus[i] = validationStatus{
+						err:   res,
+						isSet: true,
+					}
+					continue nextCav
+				}
+			default:
+			}
+			switch cav.Id {
+			case security.PublicKeyThirdPartyCaveat.Id:
+				res := cav.Validate(ctx, call)
+				if res != nil {
+					valStatus[i] = validationStatus{
+						err:   res,
+						isSet: true,
+					}
+					continue nextCav
+				}
+			default:
+				newChainCavs = append(newChainCavs, cav)
+			}
+		}
+		if len(newChainCavs) == 0 {
+			valStatus[i] = validationStatus{
+				err:   nil,
+				isSet: true,
+			}
+		} else {
+			caveatChainsToValidate = append(caveatChainsToValidate, newChainCavs)
+		}
+	}
+
+	jsRes := server.caveatValidationInJavascript(ctx, call, caveatChainsToValidate)
+
+	outResults := make([]error, len(cavs))
+	jsIndex := 0
+	for i, status := range valStatus {
+		if status.isSet {
+			outResults[i] = status.err
+		} else {
+			outResults[i] = jsRes[jsIndex]
+			jsIndex++
+		}
+	}
+
+	return outResults
+}
+
+func ConvertSecurityCall(helper ServerHelper, ctx *context.T, call security.Call, includeBlessingStrings bool) SecurityCall {
+	var localEndpoint string
+	if call.LocalEndpoint() != nil {
+		localEndpoint = call.LocalEndpoint().String()
+	}
+	var remoteEndpoint string
+	if call.RemoteEndpoint() != nil {
+		remoteEndpoint = call.RemoteEndpoint().String()
+	}
+	anymtags := make([]*vdl.Value, len(call.MethodTags()))
+	for i, mtag := range call.MethodTags() {
+		anymtags[i] = mtag
+	}
+	secCall := SecurityCall{
+		Method:          lib.LowercaseFirstCharacter(call.Method()),
+		Suffix:          call.Suffix(),
+		MethodTags:      anymtags,
+		LocalEndpoint:   localEndpoint,
+		RemoteEndpoint:  remoteEndpoint,
+		LocalBlessings:  helper.BlessingsCache().Put(call.LocalBlessings()),
+		RemoteBlessings: helper.BlessingsCache().Put(call.RemoteBlessings()),
+	}
+	if includeBlessingStrings {
+		secCall.LocalBlessingStrings = security.LocalBlessingNames(ctx, call)
+		secCall.RemoteBlessingStrings, _ = security.RemoteBlessingNames(ctx, call)
+	}
+	return secCall
+}
+
+type remoteAuth struct {
+	Func   func(*context.T, security.Call, int32) error
+	Handle int32
+}
+
+func (r remoteAuth) Authorize(ctx *context.T, call security.Call) error {
+	return r.Func(ctx, call, r.Handle)
+}
+
+func (s *Server) createRemoteAuthorizer(handle int32) security.Authorizer {
+	return remoteAuth{s.authorizeRemote, handle}
+}
+
+func (s *Server) authorizeRemote(ctx *context.T, call security.Call, handle int32) error {
+	// Until the tests get fixed, we need to create a security context before
+	// creating the flow because creating the security context creates a flow and
+	// flow ids will be off.
+	securityCall := ConvertSecurityCall(s.helper, ctx, call, true)
+
+	flow := s.helper.CreateNewFlow(s, nil)
+	replyChan := make(chan error, 1)
+	s.outstandingRequestLock.Lock()
+	s.outstandingAuthRequests[flow.ID] = replyChan
+	s.outstandingRequestLock.Unlock()
+	message := AuthRequest{
+		ServerId: s.id,
+		Handle:   handle,
+		Call:     securityCall,
+		Context: Context{
+			Language: string(i18n.GetLangID(ctx)),
+		},
+	}
+	ctx.VI(0).Infof("Sending out auth request for %v, %v", flow.ID, message)
+
+	vomMessage, err := lib.HexVomEncode(message, s.helper.TypeEncoder())
+	if err != nil {
+		replyChan <- verror.Convert(verror.ErrInternal, nil, err)
+	} else if err := flow.Writer.Send(lib.ResponseAuthRequest, vomMessage); err != nil {
+		replyChan <- verror.Convert(verror.ErrInternal, nil, err)
+	}
+
+	err = <-replyChan
+	ctx.VI(0).Infof("going to respond with %v", err)
+	s.outstandingRequestLock.Lock()
+	delete(s.outstandingAuthRequests, flow.ID)
+	s.outstandingRequestLock.Unlock()
+	s.helper.CleanupFlow(flow.ID)
+	return err
+}
+
+func (s *Server) readStatus() {
+	// A map of names to the last error message sent.
+	lastErrors := map[string]string{}
+	for {
+		status := s.server.Status()
+		for _, mountStatus := range status.Mounts {
+			var errMsg string
+			if mountStatus.LastMountErr != nil {
+				errMsg = mountStatus.LastMountErr.Error()
+			}
+			mountName := mountStatus.Name
+			if lastMessage, ok := lastErrors[mountName]; !ok || errMsg != lastMessage {
+				if errMsg == "" {
+					s.helper.SendLogMessage(
+						lib.LogLevelInfo, "serve: "+mountName+" successfully mounted ")
+				} else {
+					s.helper.SendLogMessage(
+						lib.LogLevelError, "serve: "+mountName+" failed with: "+errMsg)
+				}
+			}
+			lastErrors[mountName] = errMsg
+		}
+		select {
+		case <-time.After(10 * time.Second):
+			continue
+		case <-s.statusClose:
+			return
+		}
+	}
+}
+
+func (s *Server) Serve(name string) error {
+	s.serverStateLock.Lock()
+	defer s.serverStateLock.Unlock()
+
+	if s.dispatcher == nil {
+		s.dispatcher = newDispatcher(s.id, s, s, s, s.helper)
+	}
+
+	if !s.isListening {
+		_, err := s.server.Listen(*s.listenSpec)
+		if err != nil {
+			return err
+		}
+		s.isListening = true
+	}
+	if err := s.server.ServeDispatcher(name, s.dispatcher); err != nil {
+		return err
+	}
+	s.statusClose = make(chan struct{}, 1)
+	go s.readStatus()
+	return nil
+}
+
+func (s *Server) popServerRequest(id int32) chan *lib.ServerRpcReply {
+	s.outstandingRequestLock.Lock()
+	defer s.outstandingRequestLock.Unlock()
+	ch := s.outstandingServerRequests[id]
+	delete(s.outstandingServerRequests, id)
+
+	return ch
+}
+
+func (s *Server) HandleServerResponse(ctx *context.T, id int32, data string) {
+	ch := s.popServerRequest(id)
+	if ch == nil {
+		ctx.Errorf("unexpected result from JavaScript. No channel "+
+			"for MessageId: %d exists. Ignoring the results.", id)
+		// Ignore unknown responses that don't belong to any channel
+		return
+	}
+
+	// Decode the result and send it through the channel
+	var reply lib.ServerRpcReply
+	if err := lib.HexVomDecode(data, &reply, s.helper.TypeDecoder()); err != nil {
+		reply.Err = err
+	}
+
+	ctx.VI(0).Infof("response received from JavaScript server for "+
+		"MessageId %d with result %v", id, reply)
+	s.helper.CleanupFlow(id)
+	if reply.Err != nil {
+		ch <- &reply
+		return
+	}
+	ch <- &reply
+}
+
+func (s *Server) HandleLookupResponse(ctx *context.T, id int32, data string) {
+	s.dispatcher.handleLookupResponse(ctx, id, data)
+}
+
+func (s *Server) HandleAuthResponse(ctx *context.T, id int32, data string) {
+	s.outstandingRequestLock.Lock()
+	ch := s.outstandingAuthRequests[id]
+	s.outstandingRequestLock.Unlock()
+	if ch == nil {
+		ctx.Errorf("unexpected result from JavaScript. No channel "+
+			"for MessageId: %d exists. Ignoring the results(%s)", id, data)
+		// Ignore unknown responses that don't belong to any channel
+		return
+	}
+	// Decode the result and send it through the channel
+	var reply AuthReply
+	if err := lib.HexVomDecode(data, &reply, s.helper.TypeDecoder()); err != nil {
+		err = verror.Convert(verror.ErrInternal, nil, err)
+		reply = AuthReply{Err: err}
+	}
+
+	ctx.VI(0).Infof("response received from JavaScript server for "+
+		"MessageId %d with result %v", id, reply)
+	s.helper.CleanupFlow(id)
+	// A nil verror.E does not result in an nil error.  Instead, we have create
+	// a variable for the error interface and only set it's value if the struct is non-
+	// nil.
+	var err error
+	if reply.Err != nil {
+		err = reply.Err
+	}
+	ch <- err
+}
+
+func (s *Server) HandleCaveatValidationResponse(ctx *context.T, id int32, data string) {
+	s.outstandingRequestLock.Lock()
+	ch := s.outstandingValidationRequests[id]
+	s.outstandingRequestLock.Unlock()
+	if ch == nil {
+		ctx.Errorf("unexpected result from JavaScript. No channel "+
+			"for validation response with MessageId: %d exists. Ignoring the results(%s)", id, data)
+		// Ignore unknown responses that don't belong to any channel
+		return
+	}
+
+	var reply CaveatValidationResponse
+	if err := lib.HexVomDecode(data, &reply, s.helper.TypeDecoder()); err != nil {
+		ctx.Errorf("failed to decode validation response %q: error %v", data, err)
+		ch <- []error{}
+		return
+	}
+
+	ch <- reply.Results
+}
+
+func (s *Server) createFlow() *Flow {
+	return s.helper.CreateNewFlow(s, nil)
+}
+
+func (s *Server) cleanupFlow(id int32) {
+	s.helper.CleanupFlow(id)
+}
+
+func (s *Server) createInvoker(handle int32, sig []signature.Interface, hasGlobber bool) (rpc.Invoker, error) {
+	remoteInvokeFunc := s.createRemoteInvokerFunc(handle)
+	var globFunc remoteGlobFunc
+	if hasGlobber {
+		globFunc = s.createRemoteGlobFunc(handle)
+	}
+	return newInvoker(sig, remoteInvokeFunc, globFunc), nil
+}
+
+func (s *Server) createAuthorizer(handle int32, hasAuthorizer bool) (security.Authorizer, error) {
+	if hasAuthorizer {
+		return s.createRemoteAuthorizer(handle), nil
+	}
+	return nil, nil
+}
+
+func (s *Server) Stop() {
+	stdErr := verror.New(verror.ErrTimeout, nil).(verror.E)
+	result := lib.ServerRpcReply{
+		Results: nil,
+		Err:     &stdErr,
+	}
+	s.serverStateLock.Lock()
+
+	if s.statusClose != nil {
+		close(s.statusClose)
+	}
+	if s.dispatcher != nil {
+		s.dispatcher.Cleanup()
+	}
+
+	s.outstandingRequestLock.Lock()
+	for _, ch := range s.outstandingAuthRequests {
+		ch <- fmt.Errorf("Cleaning up server")
+	}
+
+	for _, ch := range s.outstandingServerRequests {
+		select {
+		case ch <- &result:
+		default:
+		}
+	}
+	s.outstandingAuthRequests = make(map[int32]chan error)
+	s.outstandingServerRequests = make(map[int32]chan *lib.ServerRpcReply)
+	s.outstandingRequestLock.Unlock()
+	s.serverStateLock.Unlock()
+	s.server.Stop()
+
+	// Only clear the validation requests map after stopping. Clearing them before
+	// can cause the publisher to get stuck waiting for a caveat validation that
+	// will never be answered, which prevents the server from stopping.
+	s.serverStateLock.Lock()
+	s.outstandingRequestLock.Lock()
+	s.outstandingValidationRequests = make(map[int32]chan []error)
+	s.outstandingRequestLock.Unlock()
+	s.serverStateLock.Unlock()
+}
+
+func (s *Server) AddName(name string) error {
+	return s.server.AddName(name)
+}
+
+func (s *Server) RemoveName(name string) {
+	s.server.RemoveName(name)
+}
diff --git a/services/wspr/internal/rpc/server/server.vdl b/services/wspr/internal/rpc/server/server.vdl
new file mode 100644
index 0000000..0ab325e
--- /dev/null
+++ b/services/wspr/internal/rpc/server/server.vdl
@@ -0,0 +1,77 @@
+// 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 server
+
+import (
+  "time"
+
+  "signature"
+  "v.io/v23/security"
+  "v.io/v23/vtrace"
+  "v.io/x/ref/services/wspr/internal/principal"
+)
+
+type Context struct {
+    Language string
+}
+
+type SecurityCall struct {
+    Method                string
+    Suffix                string
+    MethodTags            []any
+    LocalBlessings        principal.BlessingsId
+    LocalBlessingStrings  []string
+    RemoteBlessings       principal.BlessingsId
+    RemoteBlessingStrings []string
+    LocalEndpoint         string
+    RemoteEndpoint        string
+}
+
+type CaveatValidationRequest struct {
+    Call SecurityCall
+    Context Context
+    Cavs [][]security.Caveat
+}
+
+type CaveatValidationResponse struct {
+    Results []error
+}
+
+error (
+    CaveatValidationTimeout() {"en": "Caveat validation has timed out"}
+    InvalidValidationResponseFromJavascript() {"en": "Invalid validation response from javascript"}
+    ServerStopped() {RetryBackoff, "en": "Server has been stopped"}
+)
+
+type ServerRpcRequestCall struct {
+	SecurityCall SecurityCall
+	Deadline     time.WireDeadline
+        Context      Context
+	TraceRequest vtrace.Request
+  GrantedBlessings principal.BlessingsId
+}
+
+// A request from the proxy to javascript to handle an RPC
+type ServerRpcRequest struct {
+	ServerId uint32
+	Handle   int32
+	Method   string
+	Args     []any
+	Call     ServerRpcRequestCall
+}
+
+// A reply from javascript to a lookup request.
+type LookupReply struct {
+	Handle        int32
+	HasAuthorizer bool
+	HasGlobber    bool
+	Signature     []signature.Interface
+	Err           error
+}
+
+// A reply from javascript to an auth request.
+type AuthReply struct {
+	Err error
+}
diff --git a/services/wspr/internal/rpc/server/server.vdl.go b/services/wspr/internal/rpc/server/server.vdl.go
new file mode 100644
index 0000000..76bbb0f
--- /dev/null
+++ b/services/wspr/internal/rpc/server/server.vdl.go
@@ -0,0 +1,158 @@
+// 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 by the vanadium vdl tool.
+// Source: server.vdl
+
+package server
+
+import (
+	// VDL system imports
+	"v.io/v23/context"
+	"v.io/v23/i18n"
+	"v.io/v23/vdl"
+	"v.io/v23/verror"
+
+	// VDL user imports
+	"v.io/v23/security"
+	"v.io/v23/vdlroot/signature"
+	"v.io/v23/vdlroot/time"
+	"v.io/v23/vtrace"
+	"v.io/x/ref/services/wspr/internal/principal"
+)
+
+type Context struct {
+	Language string
+}
+
+func (Context) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/services/wspr/internal/rpc/server.Context"`
+}) {
+}
+
+type SecurityCall struct {
+	Method                string
+	Suffix                string
+	MethodTags            []*vdl.Value
+	LocalBlessings        principal.BlessingsId
+	LocalBlessingStrings  []string
+	RemoteBlessings       principal.BlessingsId
+	RemoteBlessingStrings []string
+	LocalEndpoint         string
+	RemoteEndpoint        string
+}
+
+func (SecurityCall) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/services/wspr/internal/rpc/server.SecurityCall"`
+}) {
+}
+
+type CaveatValidationRequest struct {
+	Call    SecurityCall
+	Context Context
+	Cavs    [][]security.Caveat
+}
+
+func (CaveatValidationRequest) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/services/wspr/internal/rpc/server.CaveatValidationRequest"`
+}) {
+}
+
+type CaveatValidationResponse struct {
+	Results []error
+}
+
+func (CaveatValidationResponse) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/services/wspr/internal/rpc/server.CaveatValidationResponse"`
+}) {
+}
+
+type ServerRpcRequestCall struct {
+	SecurityCall     SecurityCall
+	Deadline         time.Deadline
+	Context          Context
+	TraceRequest     vtrace.Request
+	GrantedBlessings principal.BlessingsId
+}
+
+func (ServerRpcRequestCall) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/services/wspr/internal/rpc/server.ServerRpcRequestCall"`
+}) {
+}
+
+// A request from the proxy to javascript to handle an RPC
+type ServerRpcRequest struct {
+	ServerId uint32
+	Handle   int32
+	Method   string
+	Args     []*vdl.Value
+	Call     ServerRpcRequestCall
+}
+
+func (ServerRpcRequest) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/services/wspr/internal/rpc/server.ServerRpcRequest"`
+}) {
+}
+
+// A reply from javascript to a lookup request.
+type LookupReply struct {
+	Handle        int32
+	HasAuthorizer bool
+	HasGlobber    bool
+	Signature     []signature.Interface
+	Err           error
+}
+
+func (LookupReply) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/services/wspr/internal/rpc/server.LookupReply"`
+}) {
+}
+
+// A reply from javascript to an auth request.
+type AuthReply struct {
+	Err error
+}
+
+func (AuthReply) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/services/wspr/internal/rpc/server.AuthReply"`
+}) {
+}
+
+func init() {
+	vdl.Register((*Context)(nil))
+	vdl.Register((*SecurityCall)(nil))
+	vdl.Register((*CaveatValidationRequest)(nil))
+	vdl.Register((*CaveatValidationResponse)(nil))
+	vdl.Register((*ServerRpcRequestCall)(nil))
+	vdl.Register((*ServerRpcRequest)(nil))
+	vdl.Register((*LookupReply)(nil))
+	vdl.Register((*AuthReply)(nil))
+}
+
+var (
+	ErrCaveatValidationTimeout                 = verror.Register("v.io/x/ref/services/wspr/internal/rpc/server.CaveatValidationTimeout", verror.NoRetry, "{1:}{2:} Caveat validation has timed out")
+	ErrInvalidValidationResponseFromJavascript = verror.Register("v.io/x/ref/services/wspr/internal/rpc/server.InvalidValidationResponseFromJavascript", verror.NoRetry, "{1:}{2:} Invalid validation response from javascript")
+	ErrServerStopped                           = verror.Register("v.io/x/ref/services/wspr/internal/rpc/server.ServerStopped", verror.RetryBackoff, "{1:}{2:} Server has been stopped")
+)
+
+func init() {
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrCaveatValidationTimeout.ID), "{1:}{2:} Caveat validation has timed out")
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrInvalidValidationResponseFromJavascript.ID), "{1:}{2:} Invalid validation response from javascript")
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrServerStopped.ID), "{1:}{2:} Server has been stopped")
+}
+
+// NewErrCaveatValidationTimeout returns an error with the ErrCaveatValidationTimeout ID.
+func NewErrCaveatValidationTimeout(ctx *context.T) error {
+	return verror.New(ErrCaveatValidationTimeout, ctx)
+}
+
+// NewErrInvalidValidationResponseFromJavascript returns an error with the ErrInvalidValidationResponseFromJavascript ID.
+func NewErrInvalidValidationResponseFromJavascript(ctx *context.T) error {
+	return verror.New(ErrInvalidValidationResponseFromJavascript, ctx)
+}
+
+// NewErrServerStopped returns an error with the ErrServerStopped ID.
+func NewErrServerStopped(ctx *context.T) error {
+	return verror.New(ErrServerStopped, ctx)
+}
diff --git a/services/wspr/wsprd/doc.go b/services/wspr/wsprd/doc.go
new file mode 100644
index 0000000..e0c33d7
--- /dev/null
+++ b/services/wspr/wsprd/doc.go
@@ -0,0 +1,69 @@
+// 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
+
+// +build wspr
+
+/*
+Command wsprd runs the wspr web socket proxy daemon.
+
+Usage:
+   wsprd [flags]
+
+The wsprd flags are:
+ -identd=
+   Name of identd server.
+ -port=8124
+   Port to listen on.
+
+The global flags are:
+ -alsologtostderr=true
+   log to standard error as well as files
+ -log_backtrace_at=:0
+   when logging hits line file:N, emit a stack trace
+ -log_dir=
+   if non-empty, write log files to this directory
+ -logtostderr=false
+   log to standard error instead of files
+ -max_stack_buf_size=4292608
+   max size in bytes of the buffer to use for logging stack traces
+ -stderrthreshold=2
+   logs at or above this threshold go to stderr
+ -v=0
+   log level for V logs
+ -v23.credentials=
+   directory to use for storing security credentials
+ -v23.i18n-catalogue=
+   18n catalogue files to load, comma separated
+ -v23.metadata=<just specify -v23.metadata to activate>
+   Displays metadata for the program and exits.
+ -v23.namespace.root=[/(dev.v.io/role/vprod/service/mounttabled)@ns.dev.v.io:8101]
+   local namespace root; can be repeated to provided multiple roots
+ -v23.permissions.file=map[]
+   specify a perms file as <name>:<permsfile>
+ -v23.permissions.literal=
+   explicitly specify the runtime perms as a JSON-encoded access.Permissions.
+   Overrides all --v23.permissions.file flags.
+ -v23.proxy=
+   object name of proxy service to use to export services across network
+   boundaries
+ -v23.tcp.address=
+   address to listen on
+ -v23.tcp.protocol=wsh
+   protocol to listen with
+ -v23.vtrace.cache-size=1024
+   The number of vtrace traces to store in memory.
+ -v23.vtrace.collect-regexp=
+   Spans and annotations that match this regular expression will trigger trace
+   collection.
+ -v23.vtrace.dump-on-shutdown=true
+   If true, dump all stored traces on runtime shutdown.
+ -v23.vtrace.sample-rate=0
+   Rate (from 0.0 to 1.0) to sample vtrace traces.
+ -vmodule=
+   comma-separated list of pattern=N settings for file-filtered logging
+*/
+package main
diff --git a/services/wspr/wsprd/main.go b/services/wspr/wsprd/main.go
new file mode 100644
index 0000000..dbc135f
--- /dev/null
+++ b/services/wspr/wsprd/main.go
@@ -0,0 +1,71 @@
+// 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 wspr
+//
+// We restrict wsprd to a special build-tag in order to enable
+// security.OverrideCaveatValidation, which isn't generally available.
+//
+// Manually run the following to generate the doc.go file.  This isn't a
+// go:generate comment, since generate also needs to be run with -tags=wspr,
+// which is troublesome for presubmit tests.
+//
+// cd $V23_ROOT/release/go/src && go run v.io/x/lib/cmdline/testdata/gendoc.go -tags=wspr v.io/x/ref/services/wspr/wsprd -help
+
+package main
+
+import (
+	"fmt"
+	"net"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/signals"
+	"v.io/x/ref/lib/v23cmd"
+	// TODO(cnicolaou,benj): figure out how to support roaming as a chrome plugin
+	_ "v.io/x/ref/runtime/factories/roaming"
+	"v.io/x/ref/services/wspr/wsprlib"
+)
+
+var (
+	port   int
+	identd string
+)
+
+func init() {
+	wsprlib.OverrideCaveatValidation()
+	cmdWsprD.Flags.IntVar(&port, "port", 8124, "Port to listen on.")
+	cmdWsprD.Flags.StringVar(&identd, "identd", "", "Name of identd server.")
+}
+
+func main() {
+	cmdline.HideGlobalFlagsExcept()
+	cmdline.Main(cmdWsprD)
+}
+
+var cmdWsprD = &cmdline.Command{
+	Runner: v23cmd.RunnerFunc(runWsprD),
+	Name:   "wsprd",
+	Short:  "Runs the wspr web socket proxy daemon",
+	Long: `
+Command wsprd runs the wspr web socket proxy daemon.
+`,
+}
+
+func runWsprD(ctx *context.T, env *cmdline.Env, args []string) error {
+	listenSpec := v23.GetListenSpec(ctx)
+	proxy := wsprlib.NewWSPR(ctx, port, &listenSpec, identd, nil)
+	defer proxy.Shutdown()
+
+	addr := proxy.Listen()
+	go func() {
+		proxy.Serve()
+	}()
+
+	nhost, nport, _ := net.SplitHostPort(addr.String())
+	fmt.Printf("Listening on host: %s port: %s\n", nhost, nport)
+	<-signals.ShutdownOnSignals(ctx)
+	return nil
+}
diff --git a/services/wspr/wsprlib/override.go b/services/wspr/wsprlib/override.go
new file mode 100644
index 0000000..4eb3074
--- /dev/null
+++ b/services/wspr/wsprlib/override.go
@@ -0,0 +1,21 @@
+// 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 wspr
+//
+// We restrict to a special build-tag in order to enable
+// security.OverrideCaveatValidation, which isn't generally available.
+
+package wsprlib
+
+import (
+	"v.io/v23/security"
+	"v.io/x/ref/services/wspr/internal/rpc/server"
+)
+
+// OverrideCaveatValidation overrides caveat validation.  This must be called in
+// order for wspr to be able to delegate caveat validation to javascript.
+func OverrideCaveatValidation() {
+	security.OverrideCaveatValidation(server.CaveatValidation)
+}
diff --git a/services/wspr/wsprlib/pipe.go b/services/wspr/wsprlib/pipe.go
new file mode 100644
index 0000000..90c98a3
--- /dev/null
+++ b/services/wspr/wsprlib/pipe.go
@@ -0,0 +1,175 @@
+// 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 wsprlib
+
+import (
+	"encoding/json"
+	"fmt"
+	"io"
+	"net/http"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/x/ref/services/wspr/internal/app"
+	"v.io/x/ref/services/wspr/internal/lib"
+
+	"github.com/gorilla/websocket"
+)
+
+// wsMessage is the struct that is put on the write queue.
+type wsMessage struct {
+	buf         []byte
+	messageType int
+}
+
+type pipe struct {
+	// The struct that handles the translation of javascript request to vanadium requests.
+	controller *app.Controller
+
+	ws *websocket.Conn
+
+	wspr *WSPR
+
+	// Creates a client writer for a given flow.  This is a member so that tests can override
+	// the default implementation.
+	writerCreator func(id int32) lib.ClientWriter
+
+	// There is a single write goroutine because ws.NewWriter() creates a new writer that
+	// writes to a shared buffer in the websocket, so it is not safe to have multiple go
+	// routines writing to different websocket writers.
+	writeQueue chan wsMessage
+
+	// This request is used to tell WSPR which pipe to remove when we shutdown.
+	req *http.Request
+}
+
+func newPipe(w http.ResponseWriter, req *http.Request, wspr *WSPR, creator func(id int32) lib.ClientWriter) *pipe {
+	pipe := &pipe{wspr: wspr, req: req}
+
+	if creator == nil {
+		creator = func(id int32) lib.ClientWriter {
+			return &websocketWriter{p: pipe, id: id, ctx: wspr.ctx}
+		}
+	}
+	pipe.writerCreator = creator
+	origin := req.Header.Get("Origin")
+	if origin == "" {
+		wspr.ctx.Errorf("Could not read origin from the request")
+		http.Error(w, "Could not read origin from the request", http.StatusBadRequest)
+		return nil
+	}
+
+	p, err := wspr.principalManager.Principal(origin)
+	if err != nil {
+		p = v23.GetPrincipal(wspr.ctx)
+		wspr.ctx.Errorf("no principal associated with origin %s: %v", origin, err)
+		// TODO(bjornick): Send an error to the client when all of the principal stuff is set up.
+	}
+
+	pipe.controller, err = app.NewController(wspr.ctx, creator, wspr.listenSpec, wspr.namespaceRoots, p)
+	if err != nil {
+		wspr.ctx.Errorf("Could not create controller: %v", err)
+		http.Error(w, fmt.Sprintf("Failed to create controller: %v", err), http.StatusInternalServerError)
+		return nil
+	}
+
+	pipe.start(w, req)
+	return pipe
+}
+
+// cleans up any outstanding rpcs.
+func (p *pipe) cleanup(ctx *context.T) {
+	ctx.VI(0).Info("Cleaning up websocket")
+	p.controller.Cleanup(ctx)
+	p.ws.Close()
+	p.wspr.CleanUpPipe(p.req)
+}
+
+func (p *pipe) setup() {
+	p.writeQueue = make(chan wsMessage, 50)
+	go p.writeLoop()
+}
+
+func (p *pipe) writeLoop() {
+	for {
+		msg, ok := <-p.writeQueue
+		if !ok {
+			p.wspr.ctx.Errorf("write queue was closed")
+			return
+		}
+
+		if msg.messageType == websocket.PingMessage {
+			p.wspr.ctx.Infof("sending ping")
+		}
+		if err := p.ws.WriteMessage(msg.messageType, msg.buf); err != nil {
+			p.wspr.ctx.Errorf("failed to write bytes: %s", err)
+		}
+	}
+}
+
+func (p *pipe) start(w http.ResponseWriter, req *http.Request) {
+	ws, err := websocket.Upgrade(w, req, 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)
+		p.wspr.ctx.Errorf("websocket upgrade failed: %s", err)
+		return
+	}
+
+	p.ws = ws
+	p.ws.SetPongHandler(p.pongHandler)
+	p.setup()
+
+	go p.readLoop()
+	go p.pingLoop()
+}
+
+func (p *pipe) pingLoop() {
+	for {
+		time.Sleep(pingInterval)
+		p.wspr.ctx.VI(2).Info("ws: ping")
+		p.writeQueue <- wsMessage{messageType: websocket.PingMessage, buf: []byte{}}
+	}
+}
+
+func (p *pipe) pongHandler(msg string) error {
+	p.wspr.ctx.VI(2).Infof("ws: pong")
+	p.ws.SetReadDeadline(time.Now().Add(pongTimeout))
+	return nil
+}
+
+func (p *pipe) readLoop() {
+	p.ws.SetReadDeadline(time.Now().Add(pongTimeout))
+	for {
+		op, r, err := p.ws.NextReader()
+		if err == io.ErrUnexpectedEOF { // websocket disconnected
+			break
+		}
+		if err != nil {
+			p.wspr.ctx.VI(1).Infof("websocket receive: %s", err)
+			break
+		}
+
+		if op != websocket.TextMessage {
+			p.wspr.ctx.Errorf("unexpected websocket op: %v", op)
+		}
+
+		var msg app.Message
+		decoder := json.NewDecoder(r)
+		if err := decoder.Decode(&msg); err != nil {
+			errMsg := fmt.Sprintf("can't unmarshall JSONMessage: %v", err)
+			p.wspr.ctx.Error(errMsg)
+			p.writeQueue <- wsMessage{messageType: websocket.TextMessage, buf: []byte(errMsg)}
+			continue
+		}
+
+		ww := p.writerCreator(msg.Id)
+		p.controller.HandleIncomingMessage(msg, ww)
+	}
+	p.cleanup(p.wspr.ctx)
+}
diff --git a/services/wspr/wsprlib/writer.go b/services/wspr/wsprlib/writer.go
new file mode 100644
index 0000000..8c1fabe
--- /dev/null
+++ b/services/wspr/wsprlib/writer.go
@@ -0,0 +1,73 @@
+// 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 wsprlib
+
+import (
+	"fmt"
+	"path/filepath"
+	"runtime"
+
+	"v.io/v23/context"
+	"v.io/v23/verror"
+	"v.io/x/ref/services/wspr/internal/app"
+	"v.io/x/ref/services/wspr/internal/lib"
+
+	"github.com/gorilla/websocket"
+)
+
+// Wraps a response to the proxy client and adds a message type.
+type response struct {
+	Type    lib.ResponseType
+	Message interface{}
+}
+
+// Implements clientWriter interface for sending messages over websockets.
+type websocketWriter struct {
+	p   *pipe
+	id  int32
+	ctx *context.T
+}
+
+func (w *websocketWriter) Send(messageType lib.ResponseType, data interface{}) error {
+	msg, err := app.ConstructOutgoingMessage(w.id, messageType, data)
+	if err != nil {
+		return err
+	}
+
+	w.p.writeQueue <- wsMessage{messageType: websocket.TextMessage, buf: []byte(msg)}
+
+	return nil
+}
+
+func (w *websocketWriter) Error(err error) {
+	verr := verror.Convert(verror.ErrUnknown, nil, err)
+
+	// Also log the error but write internal errors at a more severe log level
+	logLevel := 2
+	logErr := fmt.Sprintf("%v", verr)
+
+	// Prefix the message with the code locations associated with verr,
+	// except the last, which is the Convert() above.  This does nothing if
+	// err was not a verror error.
+	verrStack := verror.Stack(verr)
+	for i := 0; i < len(verrStack)-1; i++ {
+		pc := verrStack[i]
+		fnc := runtime.FuncForPC(pc)
+		file, line := fnc.FileLine(pc)
+		logErr = fmt.Sprintf("%s:%d: %s", file, line, logErr)
+	}
+
+	// We want to look at the stack three frames up to find where the error actually
+	// occurred.  (caller -> websocketErrorResponse/sendError -> generateErrorMessage).
+	if _, file, line, ok := runtime.Caller(3); ok {
+		logErr = fmt.Sprintf("%s:%d: %s", filepath.Base(file), line, logErr)
+	}
+	if verror.ErrorID(verr) == verror.ErrInternal.ID {
+		logLevel = 2
+	}
+	w.ctx.VI(logLevel).Info(logErr)
+
+	w.Send(lib.ResponseError, verr)
+}
diff --git a/services/wspr/wsprlib/wspr.go b/services/wspr/wsprlib/wspr.go
new file mode 100644
index 0000000..4622136
--- /dev/null
+++ b/services/wspr/wsprlib/wspr.go
@@ -0,0 +1,160 @@
+// 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 wsprlib implements utilities for the wspr web socket proxy, which
+// converts between the Vanadium RPC protocol and a custom web socket based
+// protocol.
+package wsprlib
+
+import (
+	"bytes"
+	"crypto/tls"
+	"fmt"
+	"io"
+	"net"
+	"net/http"
+	"sync"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+
+	"v.io/x/ref/services/wspr/internal/account"
+	"v.io/x/ref/services/wspr/internal/principal"
+)
+
+const (
+	pingInterval = 50 * time.Second              // how often the server pings the client.
+	pongTimeout  = pingInterval + 10*time.Second // maximum wait for pong.
+)
+
+type WSPR struct {
+	mu      sync.Mutex
+	tlsCert *tls.Certificate
+	ctx     *context.T
+	// HTTP port for WSPR to serve on. Note, WSPR always serves on localhost.
+	httpPort         int
+	ln               *net.TCPListener // HTTP listener
+	listenSpec       *rpc.ListenSpec
+	namespaceRoots   []string
+	principalManager *principal.PrincipalManager
+	accountManager   *account.AccountManager
+	pipes            map[*http.Request]*pipe
+}
+
+func readFromRequest(r *http.Request) (*bytes.Buffer, error) {
+	var buf bytes.Buffer
+	if readBytes, err := io.Copy(&buf, r.Body); err != nil {
+		return nil, fmt.Errorf("error copying message out of request: %v", err)
+	} else if wantBytes := r.ContentLength; readBytes != wantBytes {
+		return nil, fmt.Errorf("read %d bytes, wanted %d", readBytes, wantBytes)
+	}
+	return &buf, nil
+}
+
+// Starts listening for requests and returns the network endpoint address.
+func (wspr *WSPR) Listen() net.Addr {
+	addr := fmt.Sprintf("127.0.0.1:%d", wspr.httpPort)
+	ln, err := net.Listen("tcp", addr)
+	if err != nil {
+		wspr.ctx.Fatalf("Listen failed: %s", err)
+	}
+	wspr.ln = ln.(*net.TCPListener)
+	wspr.ctx.VI(1).Infof("Listening at %s", ln.Addr().String())
+	return ln.Addr()
+}
+
+// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted connections.
+// It's used by ListenAndServe and ListenAndServeTLS so dead TCP connections
+// (e.g. closing laptop mid-download) eventually go away.
+// Copied from http/server.go, since it's not exported.
+type tcpKeepAliveListener struct {
+	*net.TCPListener
+}
+
+func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
+	tc, err := ln.AcceptTCP()
+	if err != nil {
+		return
+	}
+	tc.SetKeepAlive(true)
+	tc.SetKeepAlivePeriod(3 * time.Minute)
+	return tc, nil
+}
+
+// Starts serving http requests. This method is blocking.
+func (wspr *WSPR) Serve() {
+	// Configure HTTP routes.
+	http.HandleFunc("/ws", wspr.handleWS)
+	// Everything else is a 404.
+	// Note: the pattern "/" matches all paths not matched by other registered
+	// patterns, not just the URL with Path == "/".
+	// (http://golang.org/pkg/net/http/#ServeMux)
+	http.Handle("/", http.NotFoundHandler())
+
+	if err := http.Serve(tcpKeepAliveListener{wspr.ln}, nil); err != nil {
+		wspr.ctx.Fatalf("Serve failed: %s", err)
+	}
+}
+
+func (wspr *WSPR) Shutdown() {
+	// TODO(ataly, bprosnitz): Get rid of this method if possible.
+}
+
+func (wspr *WSPR) CleanUpPipe(req *http.Request) {
+	wspr.mu.Lock()
+	defer wspr.mu.Unlock()
+	delete(wspr.pipes, req)
+}
+
+// Creates a new WebSocket Proxy object.
+func NewWSPR(ctx *context.T, httpPort int, listenSpec *rpc.ListenSpec, identdEP string, namespaceRoots []string) *WSPR {
+	if listenSpec.Proxy == "" {
+		ctx.Fatalf("a vanadium proxy must be set")
+	}
+
+	wspr := &WSPR{
+		ctx:            ctx,
+		httpPort:       httpPort,
+		listenSpec:     listenSpec,
+		namespaceRoots: namespaceRoots,
+		pipes:          map[*http.Request]*pipe{},
+	}
+
+	p := v23.GetPrincipal(ctx)
+	var err error
+	// TODO(nlacasse): Use a serializer that can actually persist, as we do in browspr.
+	if wspr.principalManager, err = principal.NewPrincipalManager(p, principal.NewInMemorySerializer()); err != nil {
+		ctx.Fatalf("principal.NewPrincipalManager failed: %s", err)
+	}
+
+	wspr.accountManager = account.NewAccountManager(identdEP, wspr.principalManager)
+
+	return wspr
+}
+
+func (wspr *WSPR) logAndSendBadReqErr(w http.ResponseWriter, msg string) {
+	wspr.ctx.Error(msg)
+	http.Error(w, msg, http.StatusBadRequest)
+	return
+}
+
+// HTTP Handlers
+
+func (wspr *WSPR) handleWS(w http.ResponseWriter, r *http.Request) {
+	if r.Method != "GET" {
+		http.Error(w, "Method not allowed.", http.StatusMethodNotAllowed)
+		return
+	}
+	wspr.ctx.VI(0).Info("Creating a new websocket")
+	p := newPipe(w, r, wspr, nil)
+
+	if p == nil {
+		return
+	}
+	wspr.mu.Lock()
+	defer wspr.mu.Unlock()
+	wspr.pipes[r] = p
+}
diff --git a/services/xproxyd/errors.vdl b/services/xproxyd/errors.vdl
new file mode 100644
index 0000000..0a2dbd5
--- /dev/null
+++ b/services/xproxyd/errors.vdl
@@ -0,0 +1,10 @@
+// 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 xproxyd
+
+error (
+  NotListening() {"en": "Proxy is not listening on any endpoints."}
+  UnexpectedMessage(msgType string) {"en": "Unexpected message of type{:msgType}"}
+)
\ No newline at end of file
diff --git a/services/xproxyd/errors.vdl.go b/services/xproxyd/errors.vdl.go
new file mode 100644
index 0000000..688e8ad
--- /dev/null
+++ b/services/xproxyd/errors.vdl.go
@@ -0,0 +1,35 @@
+// 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 by the vanadium vdl tool.
+// Source: errors.vdl
+
+package xproxyd
+
+import (
+	// VDL system imports
+	"v.io/v23/context"
+	"v.io/v23/i18n"
+	"v.io/v23/verror"
+)
+
+var (
+	ErrNotListening      = verror.Register("v.io/x/ref/services/xproxyd.NotListening", verror.NoRetry, "{1:}{2:} Proxy is not listening on any endpoints.")
+	ErrUnexpectedMessage = verror.Register("v.io/x/ref/services/xproxyd.UnexpectedMessage", verror.NoRetry, "{1:}{2:} Unexpected message of type{:3}")
+)
+
+func init() {
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrNotListening.ID), "{1:}{2:} Proxy is not listening on any endpoints.")
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrUnexpectedMessage.ID), "{1:}{2:} Unexpected message of type{:3}")
+}
+
+// NewErrNotListening returns an error with the ErrNotListening ID.
+func NewErrNotListening(ctx *context.T) error {
+	return verror.New(ErrNotListening, ctx)
+}
+
+// NewErrUnexpectedMessage returns an error with the ErrUnexpectedMessage ID.
+func NewErrUnexpectedMessage(ctx *context.T, msgType string) error {
+	return verror.New(ErrUnexpectedMessage, ctx, msgType)
+}
diff --git a/services/xproxyd/proxy_test.go b/services/xproxyd/proxy_test.go
new file mode 100644
index 0000000..b519dce
--- /dev/null
+++ b/services/xproxyd/proxy_test.go
@@ -0,0 +1,120 @@
+// 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 xproxyd_test
+
+import (
+	"bufio"
+	"strings"
+	"testing"
+
+	_ "v.io/x/ref/runtime/factories/generic"
+	"v.io/x/ref/services/xproxyd"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/flow"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+)
+
+func TestProxiedConnection(t *testing.T) {
+	pctx, shutdown := v23.Init()
+	defer shutdown()
+	actx, am, err := v23.ExperimentalWithNewFlowManager(pctx)
+	if err != nil {
+		t.Fatal(err)
+	}
+	dctx, dm, err := v23.ExperimentalWithNewFlowManager(pctx)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// Start the proxy.
+	addr := struct {
+		Protocol, Address string
+	}{
+		Protocol: "tcp",
+		Address:  "127.0.0.1:0",
+	}
+	pctx = v23.WithListenSpec(pctx, rpc.ListenSpec{Addrs: rpc.ListenAddrs{addr}})
+	p, err := xproxyd.New(pctx)
+	if err != nil {
+		t.Fatal(err)
+	}
+	peps := p.ListeningEndpoints()
+	if len(peps) == 0 {
+		t.Fatal("Proxy not listening on any endpoints")
+	}
+	pep := peps[0]
+
+	t.Logf("proxy endpoint: %s", pep.String())
+	// Start a accepting flow.Manager and make it listen through the proxy.
+	if err := am.Listen(actx, "v23", pep.String()); err != nil {
+		t.Fatal(err)
+	}
+	aeps := am.ListeningEndpoints()
+	if len(aeps) == 0 {
+		t.Fatal("Acceptor not listening on any endpoints")
+	}
+	aep := aeps[0]
+
+	// The dialing flow.Manager dials a flow to the accepting flow.Manager.
+	want := "Do you read me?"
+	bFn := func(
+		ctx *context.T,
+		localEndpoint, remoteEndpoint naming.Endpoint,
+		remoteBlessings security.Blessings,
+		remoteDischarges map[string]security.Discharge,
+	) (security.Blessings, map[string]security.Discharge, error) {
+		return v23.GetPrincipal(ctx).BlessingStore().Default(), nil, nil
+	}
+	df, err := dm.Dial(dctx, aep, bFn)
+	if err != nil {
+		t.Fatal(err)
+	}
+	// We write before accepting to ensure that the openFlow message is sent.
+	writeLine(df, want)
+	af, err := am.Accept(actx)
+	if err != nil {
+		t.Fatal(err)
+	}
+	got, err := readLine(af)
+	if err != nil {
+		pctx.Errorf("error")
+		t.Fatal(err)
+	}
+	if got != want {
+		pctx.Errorf("error")
+		t.Errorf("got %v, want %v", got, want)
+	}
+
+	// Writes in the opposite direction should work as well.
+	want = "I read you loud and clear."
+	writeLine(af, want)
+	got, err = readLine(df)
+	if err != nil {
+		pctx.Errorf("error")
+		t.Fatal(err)
+	}
+	if got != want {
+		pctx.Errorf("error")
+		t.Errorf("got %v, want %v", got, want)
+	}
+}
+
+// TODO(suharshs): Add tests for multiple proxies and bidirectional RPC through
+// a proxy once we have bidirpc working.
+
+func readLine(f flow.Flow) (string, error) {
+	s, err := bufio.NewReader(f).ReadString('\n')
+	return strings.TrimRight(s, "\n"), err
+}
+
+func writeLine(f flow.Flow, data string) error {
+	data += "\n"
+	_, err := f.Write([]byte(data))
+	return err
+}
diff --git a/services/xproxyd/proxyd.go b/services/xproxyd/proxyd.go
new file mode 100644
index 0000000..6857ec8
--- /dev/null
+++ b/services/xproxyd/proxyd.go
@@ -0,0 +1,151 @@
+// 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 xproxyd
+
+import (
+	"fmt"
+	"io"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/flow"
+	"v.io/v23/flow/message"
+	"v.io/v23/naming"
+	"v.io/v23/security"
+	"v.io/v23/vom"
+)
+
+// TODO(suharshs): Make sure that we don't leak any goroutines.
+
+type proxy struct {
+	m flow.Manager
+}
+
+func New(ctx *context.T) (*proxy, error) {
+	p := &proxy{
+		m: v23.ExperimentalGetFlowManager(ctx),
+	}
+	for _, addr := range v23.GetListenSpec(ctx).Addrs {
+		if err := p.m.Listen(ctx, addr.Protocol, addr.Address); err != nil {
+			return nil, err
+		}
+	}
+	go p.listenLoop(ctx)
+	return p, nil
+}
+
+func (p *proxy) listenLoop(ctx *context.T) {
+	for {
+		f, err := p.m.Accept(ctx)
+		if err != nil {
+			ctx.Infof("p.m.Accept failed: %v", err)
+			break
+		}
+		if p.shouldRoute(f) {
+			err = p.startRouting(ctx, f)
+		} else {
+			err = p.replyToServer(ctx, f)
+		}
+		if err != nil {
+			ctx.Errorf("failed to handle incoming connection: %v", err)
+		}
+	}
+}
+
+func (p *proxy) startRouting(ctx *context.T, f flow.Flow) error {
+	fout, err := p.dialNextHop(ctx, f)
+	if err != nil {
+		return err
+	}
+	go p.forwardLoop(ctx, f, fout)
+	go p.forwardLoop(ctx, fout, f)
+	return nil
+}
+
+func (p *proxy) replyToServer(ctx *context.T, f flow.Flow) error {
+	eps := p.ListeningEndpoints()
+	if len(eps) == 0 {
+		return NewErrNotListening(ctx)
+	}
+	// TODO(suharshs): handle listening on multiple endpoints.
+	ep := eps[0]
+	network, address := ep.Addr().Network(), ep.Addr().String()
+	// TODO(suharshs): deal with routes and such here, if we are replying to a proxy.
+	rid := f.Conn().RemoteEndpoint().RoutingID()
+	epString := naming.FormatEndpoint(network, address, rid)
+	if err := vom.NewEncoder(f).Encode(epString); err != nil {
+		return err
+	}
+	return nil
+}
+
+func (p *proxy) ListeningEndpoints() []naming.Endpoint {
+	return p.m.ListeningEndpoints()
+}
+
+func (p *proxy) forwardLoop(ctx *context.T, fin, fout flow.Flow) {
+	for {
+		_, err := io.Copy(fin, fout)
+		if err == io.EOF {
+			return
+		} else if err != nil {
+			ctx.Errorf("f.Read failed: %v", err)
+			return
+		}
+	}
+}
+
+func (p *proxy) dialNextHop(ctx *context.T, f flow.Flow) (flow.Flow, error) {
+	// TODO(suharshs): Read route information here when implementing multi proxy.
+	m, err := readSetupMessage(ctx, f)
+	if err != nil {
+		return nil, err
+	}
+	fout, err := p.m.Dial(ctx, m.PeerRemoteEndpoint, proxyBlessingsForPeer{}.run)
+	if err != nil {
+		return nil, err
+	}
+	// Write the setup message back onto the flow for the next hop to read.
+	return fout, writeSetupMessage(ctx, m, fout)
+}
+
+func readSetupMessage(ctx *context.T, f flow.Flow) (*message.Setup, error) {
+	b, err := f.ReadMsg()
+	if err != nil {
+		return nil, err
+	}
+	m, err := message.Read(ctx, b)
+	if err != nil {
+		return nil, err
+	}
+	if m, isSetup := m.(*message.Setup); isSetup {
+		return m, nil
+	}
+	return nil, NewErrUnexpectedMessage(ctx, fmt.Sprintf("%t", m))
+}
+
+func writeSetupMessage(ctx *context.T, m message.Message, f flow.Flow) error {
+	// TODO(suharshs): When reading the routes we should remove the read route from
+	// the endpoint.
+	w, err := message.Append(ctx, m, []byte{})
+	if err != nil {
+		return err
+	}
+	_, err = f.WriteMsg(w)
+	return err
+}
+
+func (p *proxy) shouldRoute(f flow.Flow) bool {
+	rid := f.Conn().LocalEndpoint().RoutingID()
+	return rid != p.m.RoutingID() && rid != naming.NullRoutingID
+}
+
+type proxyBlessingsForPeer struct{}
+
+// TODO(suharshs): Figure out what blessings to present here. And present discharges.
+func (proxyBlessingsForPeer) run(ctx *context.T, lep, rep naming.Endpoint, rb security.Blessings,
+	rd map[string]security.Discharge) (security.Blessings, map[string]security.Discharge, error) {
+	return v23.GetPrincipal(ctx).BlessingStore().Default(), nil, nil
+}
diff --git a/test/benchmark/stats.go b/test/benchmark/stats.go
new file mode 100644
index 0000000..fab9321
--- /dev/null
+++ b/test/benchmark/stats.go
@@ -0,0 +1,124 @@
+// 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 benchmark implements utilities to augment the standard Go
+// testing.Benchmark functionality.
+package benchmark
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"math"
+	"time"
+
+	"v.io/x/ref/lib/stats/histogram"
+)
+
+// Stats is a simple helper for gathering additional statistics like histogram
+// during benchmarks. This is not thread safe.
+type Stats struct {
+	numBuckets int
+	unit       time.Duration
+	min, max   int64
+	histogram  *histogram.Histogram
+
+	durations durationSlice
+	dirty     bool
+}
+
+type durationSlice []time.Duration
+
+// NewStats creates a new Stats instance. If numBuckets is not positive,
+// the default value (16) will be used.
+func NewStats(numBuckets int) *Stats {
+	if numBuckets <= 0 {
+		numBuckets = 16
+	}
+	return &Stats{
+		// Use one more bucket for the last unbounded bucket.
+		numBuckets: numBuckets + 1,
+		durations:  make(durationSlice, 0, 100000),
+	}
+}
+
+// Add adds an elapsed time per operation to the stats.
+func (stats *Stats) Add(d time.Duration) {
+	stats.durations = append(stats.durations, d)
+	stats.dirty = true
+}
+
+// Clear resets the stats, removing all values.
+func (stats *Stats) Clear() {
+	stats.durations = stats.durations[:0]
+	stats.histogram = nil
+	stats.dirty = false
+}
+
+// maybeUpdate updates internal stat data if there was any newly added
+// stats since this was updated.
+func (stats *Stats) maybeUpdate() {
+	if !stats.dirty {
+		return
+	}
+
+	stats.min = math.MaxInt64
+	stats.max = 0
+	for _, d := range stats.durations {
+		if stats.min > int64(d) {
+			stats.min = int64(d)
+		}
+		if stats.max < int64(d) {
+			stats.max = int64(d)
+		}
+	}
+
+	// Use the largest unit that can represent the minimum time duration.
+	stats.unit = time.Nanosecond
+	for _, u := range []time.Duration{time.Microsecond, time.Millisecond, time.Second} {
+		if stats.min <= int64(u) {
+			break
+		}
+		stats.unit = u
+	}
+
+	// Adjust the min/max according to the new unit.
+	stats.min /= int64(stats.unit)
+	stats.max /= int64(stats.unit)
+	numBuckets := stats.numBuckets
+	if n := int(stats.max - stats.min + 1); n < numBuckets {
+		numBuckets = n
+	}
+	stats.histogram = histogram.New(histogram.Options{
+		NumBuckets: numBuckets,
+		// max(i.e., Nth lower bound) = min + (1 + growthFactor)^(numBuckets-2).
+		GrowthFactor:       math.Pow(float64(stats.max-stats.min), 1/float64(stats.numBuckets-2)) - 1,
+		SmallestBucketSize: 1.0,
+		MinValue:           stats.min})
+
+	for _, d := range stats.durations {
+		stats.histogram.Add(int64(d / stats.unit))
+	}
+
+	stats.dirty = false
+}
+
+// Print writes textual output of the Stats.
+func (stats *Stats) Print(w io.Writer) {
+	stats.maybeUpdate()
+
+	if stats.histogram == nil {
+		fmt.Fprint(w, "Histogram (empty)\n")
+	} else {
+		fmt.Fprintf(w, "Histogram (unit: %s)\n", fmt.Sprintf("%v", stats.unit)[1:])
+		stats.histogram.Value().Print(w)
+	}
+}
+
+// String returns the textual output of the Stats as string.
+func (stats *Stats) String() string {
+	var b bytes.Buffer
+	stats.Print(&b)
+	return b.String()
+}
diff --git a/test/benchmark/stats_test.go b/test/benchmark/stats_test.go
new file mode 100644
index 0000000..f9fadcb
--- /dev/null
+++ b/test/benchmark/stats_test.go
@@ -0,0 +1,33 @@
+// 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 benchmark_test
+
+import (
+	"strings"
+	"testing"
+	"time"
+
+	"v.io/x/ref/test/benchmark"
+)
+
+func TestStatsBasic(t *testing.T) {
+	stats := benchmark.NewStats(16)
+	if !strings.Contains(stats.String(), "Histogram (empty)") {
+		t.Errorf("unexpect stats output:\n%s\n", stats.String())
+	}
+
+	for i := time.Duration(1); i <= 10; i++ {
+		stats.Add(i * time.Millisecond)
+	}
+
+	if !strings.Contains(stats.String(), "Count: 10 ") {
+		t.Errorf("unexpect stats output:\n%s\n", stats.String())
+	}
+
+	stats.Clear()
+	if !strings.Contains(stats.String(), "Histogram (empty)") {
+		t.Errorf("unexpect stats output:\n%s\n", stats.String())
+	}
+}
diff --git a/test/benchmark/util.go b/test/benchmark/util.go
new file mode 100644
index 0000000..a288151
--- /dev/null
+++ b/test/benchmark/util.go
@@ -0,0 +1,194 @@
+// 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 benchmark
+
+import (
+	"bufio"
+	"bytes"
+	"fmt"
+	"os"
+	"runtime"
+	"sort"
+	"strings"
+	"sync"
+	"testing"
+)
+
+var (
+	curB         *testing.B
+	curBenchName string
+	curStats     map[string]*Stats
+
+	orgStdout  *os.File
+	nextOutPos int
+
+	injectCond *sync.Cond
+	injectDone chan struct{}
+)
+
+// AddStats adds a new unnamed Stats instance to the current benchmark. You need
+// to run benchmarks by calling RunTestMain() to inject the stats to the
+// benchmark results. If numBuckets is not positive, the default value (16) will
+// be used. Please note that this calls b.ResetTimer() since it may be blocked
+// until the previous benchmark stats is printed out. So AddStats() should
+// typically be called at the very beginning of each benchmark function.
+func AddStats(b *testing.B, numBuckets int) *Stats {
+	return AddStatsWithName(b, "", numBuckets)
+}
+
+// AddStatsWithName adds a new named Stats instance to the current benchmark.
+// With this, you can add multiple stats in a single benchmark. You need
+// to run benchmarks by calling RunTestMain() to inject the stats to the
+// benchmark results. If numBuckets is not positive, the default value (16) will
+// be used. Please note that this calls b.ResetTimer() since it may be blocked
+// until the previous benchmark stats is printed out. So AddStatsWithName()
+// should typically be called at the very beginning of each benchmark function.
+func AddStatsWithName(b *testing.B, name string, numBuckets int) *Stats {
+	var benchName string
+	for i := 1; ; i++ {
+		pc, _, _, ok := runtime.Caller(i)
+		if !ok {
+			panic("benchmark function not found")
+		}
+		p := strings.Split(runtime.FuncForPC(pc).Name(), ".")
+		benchName = p[len(p)-1]
+		if strings.HasPrefix(benchName, "Benchmark") {
+			break
+		}
+	}
+	procs := runtime.GOMAXPROCS(-1)
+	if procs != 1 {
+		benchName = fmt.Sprintf("%s-%d", benchName, procs)
+	}
+	stats := NewStats(numBuckets)
+
+	if injectCond != nil {
+		// We need to wait until the previous benchmark stats is printed out.
+		injectCond.L.Lock()
+		for curB != nil && curBenchName != benchName {
+			injectCond.Wait()
+		}
+
+		curB = b
+		curBenchName = benchName
+		curStats[name] = stats
+
+		injectCond.L.Unlock()
+	}
+
+	b.ResetTimer()
+	return stats
+}
+
+// RunTestMain runs the tests with enabling injection of benchmark stats. It
+// returns an exit code to pass to os.Exit.
+func RunTestMain(m *testing.M) int {
+	startStatsInjector()
+	defer stopStatsInjector()
+	return m.Run()
+}
+
+// startStatsInjector starts stats injection to benchmark results.
+func startStatsInjector() {
+	orgStdout = os.Stdout
+	r, w, _ := os.Pipe()
+	os.Stdout = w
+	nextOutPos = 0
+
+	resetCurBenchStats()
+
+	injectCond = sync.NewCond(&sync.Mutex{})
+	injectDone = make(chan struct{})
+	go func() {
+		defer close(injectDone)
+
+		scanner := bufio.NewScanner(r)
+		scanner.Split(splitLines)
+		for scanner.Scan() {
+			injectStatsIfFinished(scanner.Text())
+		}
+		if err := scanner.Err(); err != nil {
+			panic(err)
+		}
+	}()
+}
+
+// stopStatsInjector stops stats injection and restores os.Stdout.
+func stopStatsInjector() {
+	os.Stdout.Close()
+	<-injectDone
+	injectCond = nil
+	os.Stdout = orgStdout
+}
+
+// splitLines is a split function for a bufio.Scanner that returns each line
+// of text, teeing texts to the original stdout even before each line ends.
+func splitLines(data []byte, eof bool) (advance int, token []byte, err error) {
+	if eof && len(data) == 0 {
+		return 0, nil, nil
+	}
+
+	if i := bytes.IndexByte(data, '\n'); i >= 0 {
+		orgStdout.Write(data[nextOutPos : i+1])
+		nextOutPos = 0
+		return i + 1, data[0:i], nil
+	}
+
+	orgStdout.Write(data[nextOutPos:])
+	nextOutPos = len(data)
+
+	if eof {
+		// This is a final, non-terminated line. Return it.
+		return len(data), data, nil
+	}
+
+	return 0, nil, nil
+}
+
+// injectStatsIfFinished prints out the stats if the current benchmark finishes.
+func injectStatsIfFinished(line string) {
+	injectCond.L.Lock()
+	defer injectCond.L.Unlock()
+
+	// We assume that the benchmark results start with the benchmark name.
+	if curB == nil || !strings.HasPrefix(line, curBenchName) {
+		return
+	}
+
+	if !curB.Failed() {
+		// Output all stats in alphabetical order.
+		names := make([]string, 0, len(curStats))
+		for name := range curStats {
+			names = append(names, name)
+		}
+		sort.Strings(names)
+		for _, name := range names {
+			stats := curStats[name]
+			// The output of stats starts with a header like "Histogram (unit: ms)"
+			// followed by statistical properties and the buckets. Add the stats name
+			// if it is a named stats and indent them as Go testing outputs.
+			lines := strings.Split(stats.String(), "\n")
+			if n := len(lines); n > 0 {
+				if name != "" {
+					name = ": " + name
+				}
+				fmt.Fprintf(orgStdout, "--- %s%s\n", lines[0], name)
+				for _, line := range lines[1 : n-1] {
+					fmt.Fprintf(orgStdout, "\t%s\n", line)
+				}
+			}
+		}
+	}
+
+	resetCurBenchStats()
+	injectCond.Signal()
+}
+
+// resetCurBenchStats resets the current benchmark stats.
+func resetCurBenchStats() {
+	curB = nil
+	curBenchName = ""
+	curStats = make(map[string]*Stats)
+}
diff --git a/test/benchmark/util_test.go b/test/benchmark/util_test.go
new file mode 100644
index 0000000..0da0c3f
--- /dev/null
+++ b/test/benchmark/util_test.go
@@ -0,0 +1,86 @@
+// 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 benchmark
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"os"
+	"regexp"
+	"runtime"
+	"strings"
+	"testing"
+	"time"
+)
+
+func BenchmarkTest(b *testing.B) {
+	stats := AddStats(b, 0)
+	for i := 1; i <= b.N; i++ {
+		time.Sleep(1 * time.Microsecond)
+		stats.Add(time.Duration(i) * time.Microsecond)
+	}
+}
+
+func BenchmarkTestMulti(b *testing.B) {
+	stats1 := AddStatsWithName(b, "S1", 0)
+	stats2 := AddStatsWithName(b, "S2", 0)
+	for i := 1; i <= b.N; i++ {
+		time.Sleep(1 * time.Microsecond)
+		stats1.Add(time.Duration(i) * time.Microsecond)
+		stats2.Add(time.Duration(i) * time.Millisecond)
+	}
+}
+
+func benchmarkName(name string) string {
+	procs := runtime.GOMAXPROCS(-1)
+	if procs != 1 {
+		return fmt.Sprintf("%s-%d", name, procs)
+	}
+	return name
+}
+
+func TestStatsInjection(t *testing.T) {
+	stdout := os.Stdout
+	r, w, _ := os.Pipe()
+	os.Stdout = w
+
+	outC := make(chan string)
+	go func() {
+		b := new(bytes.Buffer)
+		io.Copy(b, r)
+		r.Close()
+		outC <- b.String()
+	}()
+
+	startStatsInjector()
+
+	fmt.Printf("%s\t", benchmarkName("BenchmarkTest"))
+	result := testing.Benchmark(BenchmarkTest)
+	fmt.Println(result.String())
+
+	fmt.Printf("%s\t", benchmarkName("BenchmarkTestMulti"))
+	result = testing.Benchmark(BenchmarkTestMulti)
+	fmt.Println(result.String())
+
+	stopStatsInjector()
+
+	w.Close()
+	os.Stdout = stdout
+	out := <-outC
+
+	if strings.Count(out, "Histogram") != 3 {
+		t.Errorf("unexpected stats output:\n%s", out)
+	}
+	if matched, _ := regexp.MatchString("Histogram.*\\)\n", out); !matched {
+		t.Errorf("unnamed stats not found:\n%s", out)
+	}
+	if matched, _ := regexp.MatchString("Histogram.*\\): S1\n", out); !matched {
+		t.Errorf("stats S1 not found:\n%s", out)
+	}
+	if matched, _ := regexp.MatchString("Histogram.*\\): S2\n", out); !matched {
+		t.Errorf("stats S2 not found:\n%s", out)
+	}
+}
diff --git a/test/doc.go b/test/doc.go
new file mode 100644
index 0000000..206164e
--- /dev/null
+++ b/test/doc.go
@@ -0,0 +1,63 @@
+// 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 test implements initalization for unit and integration tests.
+//
+// Init configures logging, random number generators and other global state.
+// Typical usage in _test.go files:
+//
+// import "v.io/x/ref/test"
+//
+// func TestMain(m *testing.M) {
+//     test.Init()
+//     os.Exit(m.Run())
+// }
+//
+// V23Init can be used within test functions as a safe alternative to v23.Init.
+//
+// func TestFoo(t *testing.T) {
+//    ctx, shutdown := test.V23Init()
+//    defer shutdown()
+//    ...
+// }
+//
+// 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.
+//
+// Typical usage is:
+// $ v23 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.
+//
+// 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 v23 command is able to generate boilerplate code
+// to support these tests. In summary, v23 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:
+//
+// $ v23 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.
+//
+// The subdirectories are:
+// benchmark  - support for writing benchmarks.
+// testutil   - utility functions used in tests.
+// 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.
+package test
diff --git a/test/expect/expect.go b/test/expect/expect.go
new file mode 100644
index 0000000..f5d4664
--- /dev/null
+++ b/test/expect/expect.go
@@ -0,0 +1,446 @@
+// 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 expect implements support for checking expectations against a
+// buffered input stream. It supports literal and pattern based matching. It is
+// line oriented; all of the methods (expect ReadAll) strip trailing newlines
+// from their return values. It places a timeout on all its operations.  It will
+// generally be used to read from the stdout stream of subprocesses in tests and
+// other situations and to make 'assertions' about what is to be read.
+//
+// A Session type is used to store state, in particular error state, across
+// consecutive invocations of its method set. If a particular method call
+// encounters an error then subsequent calls on that Session will have no
+// effect. This allows for a series of assertions to be made, one per line,
+// and for errors to be checked at the end. In addition Session is designed
+// to be easily used with the testing package; passing a testing.T instance
+// to NewSession allows it to set errors directly and hence tests will pass or
+// fail according to whether the expect assertions are met or not.
+//
+// Care is taken to ensure that the file and line number of the first
+// failed assertion in the session are recorded in the error stored in
+// the Session.
+//
+// Examples
+//
+// func TestSomething(t *testing.T) {
+//     buf := []byte{}
+//     buffer := bytes.NewBuffer(buf)
+//     buffer.WriteString("foo\n")
+//     buffer.WriteString("bar\n")
+//     buffer.WriteString("baz\n")
+//     s := expect.NewSession(t, bufio.NewReader(buffer), time.Second)
+//     s.Expect("foo")
+//     s.Expect("bars)
+//     if got, want := s.ReadLine(), "baz"; got != want {
+//         t.Errorf("got %v, want %v", got, want)
+//     }
+// }
+//
+package expect
+
+import (
+	"bufio"
+	"errors"
+	"fmt"
+	"io"
+	"path/filepath"
+	"regexp"
+	"runtime"
+	"strings"
+	"time"
+
+	"v.io/x/ref/internal/logger"
+)
+
+var (
+	Timeout = errors.New("timeout")
+)
+
+// Session represents the state of an expect session.
+type Session struct {
+	input     *bufio.Reader
+	timeout   time.Duration
+	t         Testing
+	verbose   bool
+	oerr, err error
+}
+
+type Testing interface {
+	Error(args ...interface{})
+	Errorf(format string, args ...interface{})
+	Log(args ...interface{})
+	Logf(format string, args ...interface{})
+}
+
+// NewSession creates a new Session. The parameter t may be safely be nil.
+func NewSession(t Testing, input io.Reader, timeout time.Duration) *Session {
+	return &Session{t: t, timeout: timeout, input: bufio.NewReader(input)}
+}
+
+// Failed returns true if an error has been encountered by a prior call.
+func (s *Session) Failed() bool {
+	return s.err != nil
+}
+
+// Error returns the error code (possibly nil) currently stored in the Session.
+// This will include the file and line of the calling function that experienced
+// the error. Use OriginalError to obtain the original error code.
+func (s *Session) Error() error {
+	return s.err
+}
+
+// OriginalError returns any error code (possibly nil) returned by the
+// underlying library routines called.
+func (s *Session) OriginalError() error {
+	return s.oerr
+}
+
+// SetVerbosity enables/disable verbose debugging information, in particular,
+// every line of input read will be logged via Testing.Logf or, if it is nil,
+// to stderr.
+func (s *Session) SetVerbosity(v bool) {
+	s.verbose = v
+}
+
+func (s *Session) log(err error, format string, args ...interface{}) {
+	if !s.verbose {
+		return
+	}
+	_, path, line, _ := runtime.Caller(2)
+	errstr := ""
+	if err != nil {
+		errstr = err.Error() + ": "
+	}
+	loc := fmt.Sprintf("%s:%d", filepath.Base(path), line)
+	o := strings.TrimRight(fmt.Sprintf(format, args...), "\n\t ")
+	if s.t == nil {
+		logger.Global().Infof("%s: %s%s\n", loc, errstr, o)
+		return
+	}
+	s.t.Logf("%s: %s%s", loc, errstr, o)
+	s.t.Log(loc, o)
+}
+
+// ReportError calls Testing.Error to report any error currently stored
+// in the Session.
+func (s *Session) ReportError() {
+	if s.err != nil && s.t != nil {
+		s.t.Error(s.err)
+	}
+}
+
+// error must always be called from a public function that is called
+// directly by an external user, otherwise the file:line info will
+// be incorrect.
+func (s *Session) error(err error) error {
+	_, file, line, _ := runtime.Caller(2)
+	s.oerr = err
+	s.err = fmt.Errorf("%s:%d: %s", filepath.Base(file), line, err)
+	s.ReportError()
+	return s.err
+}
+
+type reader func(r *bufio.Reader) (string, error)
+
+func readAll(r *bufio.Reader) (string, error) {
+	all := ""
+	for {
+		l, err := r.ReadString('\n')
+		all += l
+		if err != nil {
+			if err == io.EOF {
+				return all, nil
+			}
+			return all, err
+		}
+	}
+}
+
+func readLine(r *bufio.Reader) (string, error) {
+	return r.ReadString('\n')
+}
+
+func (s *Session) read(f reader) (string, error) {
+	ch := make(chan string, 1)
+	ech := make(chan error, 1)
+	go func(fn reader, io *bufio.Reader) {
+		str, err := fn(io)
+		if err != nil {
+			ech <- err
+			return
+		}
+		ch <- str
+	}(f, s.input)
+	select {
+	case err := <-ech:
+		return "", err
+	case m := <-ch:
+		return m, nil
+	case <-time.After(s.timeout):
+		return "", Timeout
+	}
+}
+
+// Expect asserts that the next line in the input matches the supplied string.
+func (s *Session) Expect(expected string) {
+	if s.Failed() {
+		return
+	}
+	line, err := s.read(readLine)
+	s.log(err, "Expect: %s", line)
+	if err != nil {
+		s.error(err)
+		return
+	}
+	line = strings.TrimRight(line, "\n")
+
+	if line != expected {
+		s.error(fmt.Errorf("got %q, want %q", line, expected))
+	}
+	return
+}
+
+// Expectf asserts that the next line in the input matches the result of
+// formatting the supplied arguments. It's equivalent to
+// Expect(fmt.Sprintf(args))
+func (s *Session) Expectf(format string, args ...interface{}) {
+	if s.Failed() {
+		return
+	}
+	line, err := s.read(readLine)
+	s.log(err, "Expect: %s", line)
+	if err != nil {
+		s.error(err)
+		return
+	}
+	line = strings.TrimRight(line, "\n")
+	expected := fmt.Sprintf(format, args...)
+	if line != expected {
+		s.error(fmt.Errorf("got %q, want %q", line, expected))
+	}
+	return
+}
+
+func (s *Session) expectRE(pattern string, n int) (string, [][]string, error) {
+	if s.Failed() {
+		return "", nil, s.err
+	}
+	re, err := regexp.Compile(pattern)
+	if err != nil {
+		return "", nil, err
+	}
+	line, err := s.read(readLine)
+	if err != nil {
+		return "", nil, err
+	}
+	line = strings.TrimRight(line, "\n")
+	return line, re.FindAllStringSubmatch(line, n), err
+}
+
+// ExpectRE asserts that the next line in the input matches the pattern using
+// regexp.MustCompile(pattern).FindAllStringSubmatch(..., n).
+func (s *Session) ExpectRE(pattern string, n int) [][]string {
+	if s.Failed() {
+		return [][]string{}
+	}
+	l, m, err := s.expectRE(pattern, n)
+	s.log(err, "ExpectRE: %s", l)
+	if err != nil {
+		s.error(err)
+		return [][]string{}
+	}
+	if len(m) == 0 {
+		s.error(fmt.Errorf("%q found no match in %q", pattern, l))
+	}
+	return m
+}
+
+// ExpectVar asserts that the next line in the input matches the pattern
+// <name>=<value> and returns <value>.
+func (s *Session) ExpectVar(name string) string {
+	if s.Failed() {
+		return ""
+	}
+	l, m, err := s.expectRE(name+"=(.*)", 1)
+	s.log(err, "ExpectVar: %s", l)
+	if err != nil {
+		s.error(err)
+		return ""
+	}
+	if len(m) != 1 || len(m[0]) != 2 {
+		s.error(fmt.Errorf("failed to find value for %q in %q", name, l))
+		return ""
+	}
+	return m[0][1]
+}
+
+// ExpectSetRE verifies whether the supplied set of regular expression
+// parameters matches the next n (where n is the number of parameters)
+// lines of input. Each line is read and matched against the supplied
+// patterns in the order that they are supplied as parameters. Consequently
+// the set may contain repetitions if the same pattern is expected multiple
+// times. The value returned is either:
+//   * nil in the case of an error, or
+//   * nil if n lines are read or EOF is encountered before all expressions are
+//       matched, or
+//   * an array of length len(expected), whose ith element contains the result
+//       of FindStringSubmatch of expected[i] on the matching string (never
+//       nil). If there are no capturing groups in expected[i], the return
+//       value's [i][0] element will be the entire matching string
+func (s *Session) ExpectSetRE(expected ...string) [][]string {
+	if s.Failed() {
+		return nil
+	}
+	if match, err := s.expectSetRE(len(expected), expected...); err != nil {
+		s.error(err)
+		return nil
+	} else {
+		return match
+	}
+}
+
+// ExpectSetEventuallyRE is like ExpectSetRE except that it reads as much
+// output as required rather than just the next n lines. The value returned is
+// either:
+//   * nil in the case of an error, or
+//   * nil if EOF is encountered before all expressions are matched, or
+//   * an array of length len(expected), whose ith element contains the result
+//       of FindStringSubmatch of expected[i] on the matching string (never
+//       nil). If there are no capturing groups in expected[i], the return
+//       value's [i][0] will contain the entire matching string
+// This function stops consuming output as soon as all regular expressions are
+// matched.
+func (s *Session) ExpectSetEventuallyRE(expected ...string) [][]string {
+	if s.Failed() {
+		return nil
+	}
+	if matches, err := s.expectSetRE(-1, expected...); err != nil {
+		s.error(err)
+		return nil
+	} else {
+		return matches
+	}
+}
+
+// expectSetRE will look for the expected set of patterns in the next
+// numLines of output or in all remaining output. If all expressions are
+// matched, no more output is consumed.
+func (s *Session) expectSetRE(numLines int, expected ...string) ([][]string, error) {
+	matches := make([][]string, len(expected))
+	regexps := make([]*regexp.Regexp, len(expected))
+	for i, expRE := range expected {
+		re, err := regexp.Compile(expRE)
+		if err != nil {
+			return nil, err
+		}
+		regexps[i] = re
+	}
+	i := 0
+	matchCount := 0
+	for {
+		if matchCount == len(expected) {
+			break
+		}
+		line, err := s.read(readLine)
+		line = strings.TrimRight(line, "\n")
+		s.log(err, "ExpectSetRE: %s", line)
+		if err != nil {
+			if numLines >= 0 {
+				return nil, err
+			}
+			break
+		}
+
+		// Match the line against all regexp's and remove each regexp
+		// that matches.
+		for i, re := range regexps {
+			if re == nil {
+				continue
+			}
+			match := re.FindStringSubmatch(line)
+			if match != nil {
+				matchCount++
+				regexps[i] = nil
+				matches[i] = match
+				// Don't allow this line to be matched by more than one re.
+				break
+			}
+		}
+
+		i++
+		if numLines > 0 && i >= numLines {
+			break
+		}
+	}
+
+	// It's an error if there are any unmatched regexps.
+	unmatchedRes := make([]string, 0)
+	for i, re := range regexps {
+		if re != nil {
+			unmatchedRes = append(unmatchedRes, expected[i])
+		}
+	}
+	if len(unmatchedRes) > 0 {
+		return nil, fmt.Errorf("found no match for [%v]", strings.Join(unmatchedRes, ","))
+	}
+	return matches, nil
+}
+
+// ReadLine reads the next line, if any, from the input stream. It will set
+// the error state to io.EOF if it has read past the end of the stream.
+// ReadLine has no effect if an error has already occurred.
+func (s *Session) ReadLine() string {
+	if s.Failed() {
+		return ""
+	}
+	l, err := s.read(readLine)
+	s.log(err, "Readline: %s", l)
+	if err != nil {
+		s.error(err)
+	}
+	return strings.TrimRight(l, "\n")
+}
+
+// ReadAll reads all remaining input on the stream. Unlike all of the other
+// methods it does not strip newlines from the input.
+// ReadAll has no effect if an error has already occurred.
+func (s *Session) ReadAll() (string, error) {
+	if s.Failed() {
+		return "", s.err
+	}
+	return s.read(readAll)
+}
+
+func (s *Session) ExpectEOF() error {
+	if s.Failed() {
+		return s.err
+	}
+	buf := [1024]byte{}
+	n, err := s.input.Read(buf[:])
+	if n != 0 || err == nil {
+		s.error(fmt.Errorf("unexpected input %d bytes: %q", n, string(buf[:n])))
+		return s.err
+	}
+	if err != io.EOF {
+		s.error(err)
+		return s.err
+	}
+	return nil
+}
+
+// Finish reads all remaining input on the stream regardless of any
+// prior errors and writes it to the supplied io.Writer parameter if non-nil.
+// It returns both the data read and the prior error, if any, otherwise it
+// returns any error that occurred reading the rest of the input.
+func (s *Session) Finish(w io.Writer) (string, error) {
+	a, err := s.read(readAll)
+	if w != nil {
+		fmt.Fprint(w, a)
+	}
+	if s.Failed() {
+		return a, s.err
+	}
+	return a, err
+}
diff --git a/test/expect/expect_test.go b/test/expect/expect_test.go
new file mode 100644
index 0000000..d017265
--- /dev/null
+++ b/test/expect/expect_test.go
@@ -0,0 +1,276 @@
+// 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 expect_test
+
+import (
+	"bufio"
+	"bytes"
+	"fmt"
+	"reflect"
+	"strings"
+	"testing"
+	"time"
+
+	"v.io/x/ref/test/expect"
+)
+
+func TestSimple(t *testing.T) {
+	buf := []byte{}
+	buffer := bytes.NewBuffer(buf)
+	buffer.WriteString("bar\n")
+	buffer.WriteString("baz\n")
+	buffer.WriteString("oops\n")
+	s := expect.NewSession(nil, bufio.NewReader(buffer), time.Minute)
+	s.Expect("bar")
+	s.Expect("baz")
+	if err := s.Error(); err != nil {
+		t.Error(err)
+	}
+	// This will fail the test.
+	s.Expect("not oops")
+	if err := s.Error(); err == nil {
+		t.Error("unexpected success")
+	} else {
+		t.Log(s.Error())
+	}
+}
+
+func TestExpectf(t *testing.T) {
+	buf := []byte{}
+	buffer := bytes.NewBuffer(buf)
+	buffer.WriteString("bar 22\n")
+	s := expect.NewSession(nil, bufio.NewReader(buffer), time.Minute)
+	s.Expectf("bar %d", 22)
+	s.ExpectEOF()
+	if err := s.Error(); err != nil {
+		t.Error(err)
+	}
+}
+
+func TestEOF(t *testing.T) {
+	buf := []byte{}
+	buffer := bytes.NewBuffer(buf)
+	buffer.WriteString("bar 22\n")
+	buffer.WriteString("baz 22\n")
+	s := expect.NewSession(nil, bufio.NewReader(buffer), time.Minute)
+	s.Expectf("bar %d", 22)
+	s.ExpectEOF()
+	if err := s.Error(); err == nil {
+		t.Error("unexpected success")
+	} else {
+		t.Log(s.Error())
+	}
+}
+
+func TestExpectRE(t *testing.T) {
+	buf := []byte{}
+	buffer := bytes.NewBuffer(buf)
+	buffer.WriteString("bar=baz\n")
+	buffer.WriteString("aaa\n")
+	buffer.WriteString("bbb\n")
+	s := expect.NewSession(nil, bufio.NewReader(buffer), time.Minute)
+	if got, want := s.ExpectVar("bar"), "baz"; got != want {
+		t.Errorf("got %v, want %v", got, want)
+	}
+	s.ExpectRE("zzz|aaa", -1)
+	if err := s.Error(); err != nil {
+		t.Error(err)
+	}
+	if got, want := s.ExpectRE("(.*)", -1), [][]string{{"bbb", "bbb"}}; !reflect.DeepEqual(got, want) {
+		t.Errorf("got %v, want %v", got, want)
+	}
+	if got, want := s.ExpectRE("(.*", -1), [][]string{{"bbb", "bbb"}}; !reflect.DeepEqual(got, want) {
+		// this will have failed the test also.
+		if err := s.Error(); err == nil || !strings.Contains(err.Error(), "error parsing regexp") {
+			t.Errorf("missing or wrong error: %v", s.Error())
+		}
+	}
+}
+
+func TestExpectSetRE(t *testing.T) {
+	buf := []byte{}
+	buffer := bytes.NewBuffer(buf)
+	buffer.WriteString("bar=baz\n")
+	buffer.WriteString("abc\n")
+	buffer.WriteString("def\n")
+	buffer.WriteString("abc\n")
+	s := expect.NewSession(nil, bufio.NewReader(buffer), time.Minute)
+	got := s.ExpectSetRE("^bar=.*$", "def$", "^abc$", "^a..$")
+	if s.Error() != nil {
+		t.Errorf("unexpected error: %s", s.Error())
+	}
+	want := [][]string{{"bar=baz"}, {"def"}, {"abc"}, {"abc"}}
+	if !reflect.DeepEqual(got, want) {
+		t.Errorf("unexpected result from ExpectSetRE, got %v, want %v", got, want)
+	}
+	buffer.WriteString("ooh\n")
+	buffer.WriteString("aah\n")
+	s.ExpectSetRE("bar=.*", "def")
+	if got, want := s.Error(), "found no match for [bar=.*,def]"; got == nil || !strings.Contains(got.Error(), want) {
+		t.Errorf("got %v, wanted something containing %q", got, want)
+	}
+
+	buf = []byte{}
+	buffer = bytes.NewBuffer(buf)
+	s = expect.NewSession(nil, bufio.NewReader(buffer), time.Minute)
+	buffer.WriteString("hello world\n")
+	buffer.WriteString("this is a test\n")
+	matches := s.ExpectSetRE("hello (world)", "this (is) (a|b) test")
+	if want := [][]string{{"hello world", "world"}, {"this is a test", "is", "a"}}; !reflect.DeepEqual(want, matches) {
+		t.Errorf("unexpected result from ExpectSetRE, got %v, want %v", matches, want)
+	}
+
+	buf = []byte{}
+	buffer = bytes.NewBuffer(buf)
+	s = expect.NewSession(nil, bufio.NewReader(buffer), time.Minute)
+	buffer.WriteString("aaa\n")
+	buffer.WriteString("aaa\n")
+	buffer.WriteString("aaa\n")
+
+	// Expect 3 x aaa to match.
+	s.ExpectSetRE("aaa", "aaa", "aaa")
+	if s.Error() != nil {
+		t.Errorf("unexpected error: %v", s.Error())
+	}
+
+	// Expecting one more aaa should fail: the entire input should have been consumed.
+	s.ExpectSetRE("aaa")
+	if s.Error() == nil {
+		t.Errorf("expected error but got none")
+	}
+
+	// Test a buffer that contains a match but not within the number of lines we expect.
+	buf = []byte{}
+	buffer = bytes.NewBuffer(buf)
+	buffer.WriteString("aaa\n")
+	buffer.WriteString("bbb\n")
+	s = expect.NewSession(nil, bufio.NewReader(buffer), time.Minute)
+	s.ExpectSetRE("bbb")
+	if s.Error() == nil {
+		t.Fatalf("expected error but got none")
+	}
+
+	// Test a buffer that contains a match and leaves us with nothing more to read.
+	buf = []byte{}
+	buffer = bytes.NewBuffer(buf)
+	buffer.WriteString("aaa\n")
+	buffer.WriteString("bbb\n")
+	s = expect.NewSession(nil, bufio.NewReader(buffer), time.Minute)
+	s.ExpectSetRE("bbb")
+	if s.Error() == nil {
+		t.Fatalf("expected error but got none")
+	}
+
+	// Now ensure that each regular expression matches a unique line.
+	buf = []byte{}
+	buffer = bytes.NewBuffer(buf)
+	buffer.WriteString("a 1\n")
+	buffer.WriteString("a 2\n")
+	buffer.WriteString("a 3\n")
+	s = expect.NewSession(nil, bufio.NewReader(buffer), time.Minute)
+	matches = s.ExpectSetRE("\\w (\\d)", "a (\\d)", "a (\\d)")
+	want = [][]string{{"a 1", "1"}, {"a 2", "2"}, {"a 3", "3"}}
+	if !reflect.DeepEqual(matches, want) {
+		t.Fatalf("unexpected result from ExpectSetRE, got %v, want %v", matches, want)
+	}
+	if s.ExpectEOF() != nil {
+		t.Fatalf("expected EOF but did not get it")
+	}
+}
+
+func TestExpectSetEventuallyRE(t *testing.T) {
+	buf := []byte{}
+	buffer := bytes.NewBuffer(buf)
+	buffer.WriteString("bar=baz\n")
+	buffer.WriteString("abc\n")
+	buffer.WriteString("def\n")
+	buffer.WriteString("abc\n")
+	s := expect.NewSession(nil, bufio.NewReader(buffer), time.Minute)
+	s.SetVerbosity(testing.Verbose())
+	s.ExpectSetEventuallyRE("^bar=.*$", "def")
+	if s.Error() != nil {
+		t.Errorf("unexpected error: %s", s.Error())
+	}
+
+	// Should see one more abc match after the we read def.
+	s.ExpectSetEventuallyRE("abc")
+	if s.Error() != nil {
+		t.Errorf("unexpected error: %s", s.Error())
+	}
+
+	// Trying to match abc again should yield an error.
+	s.ExpectSetEventuallyRE("abc")
+	if got, want := s.Error(), "found no match for [abc]"; got == nil || !strings.Contains(got.Error(), want) {
+		t.Errorf("got %q, wanted something containing %q", got, want)
+	}
+
+	// Need to clear the EOF from the previous ExpectSetEventuallyRE call
+	buf = []byte{}
+	buffer = bytes.NewBuffer(buf)
+	s = expect.NewSession(nil, bufio.NewReader(buffer), time.Minute)
+	buffer.WriteString("ooh\n")
+	buffer.WriteString("aah\n")
+	s.ExpectSetEventuallyRE("zzz")
+	if got, want := s.Error(), "found no match for [zzz]"; got == nil || !strings.Contains(got.Error(), want) {
+		t.Errorf("got %q, wanted something containing %q", got, want)
+	}
+
+	buf = []byte{}
+	buffer = bytes.NewBuffer(buf)
+	s = expect.NewSession(nil, bufio.NewReader(buffer), time.Minute)
+	buffer.WriteString("not expected\n")
+	buffer.WriteString("hello world\n")
+	buffer.WriteString("this is a test\n")
+	matches := s.ExpectSetEventuallyRE("hello (world)", "this (is) (a|b) test")
+	if want := [][]string{{"hello world", "world"}, {"this is a test", "is", "a"}}; !reflect.DeepEqual(want, matches) {
+		t.Errorf("unexpected result from ExpectSetRE, got %v, want %v", matches, want)
+	}
+
+	// Test error output with multiple unmatched res.
+	buf = []byte{}
+	buffer = bytes.NewBuffer(buf)
+	s = expect.NewSession(nil, bufio.NewReader(buffer), time.Minute)
+	buffer.WriteString("not expected\n")
+	buffer.WriteString("hello world\n")
+	buffer.WriteString("this is a test\n")
+	s.ExpectSetEventuallyRE("blargh", "blerg", "blorg")
+	if got, want := s.Error(), "found no match for [blargh,blerg,blorg]"; !strings.Contains(got.Error(), want) {
+		t.Errorf("got %q, wanted something containing %q", got, want)
+	}
+}
+
+func TestRead(t *testing.T) {
+	buf := []byte{}
+	buffer := bytes.NewBuffer(buf)
+	lines := []string{"some words", "bar=baz", "more words"}
+	for _, l := range lines {
+		buffer.WriteString(l + "\n")
+	}
+	s := expect.NewSession(nil, bufio.NewReader(buffer), time.Minute)
+	for _, l := range lines {
+		if got, want := s.ReadLine(), l; got != want {
+			t.Errorf("got %q, want %q", got, want)
+		}
+	}
+	if s.Failed() {
+		t.Errorf("unexpected error: %s", s.Error())
+	}
+	want := ""
+	for i := 0; i < 100; i++ {
+		m := fmt.Sprintf("%d\n", i)
+		buffer.WriteString(m)
+		want += m
+	}
+	got, err := s.ReadAll()
+	if err != nil {
+		t.Errorf("unexpected error: %s", err)
+	}
+	if got != want {
+		t.Errorf("got %q, want %q", got, want)
+	}
+	if s.ExpectEOF() != nil {
+		t.Fatalf("expected EOF but did not get it")
+	}
+}
diff --git a/test/goroutines/goroutines.go b/test/goroutines/goroutines.go
new file mode 100644
index 0000000..fd914e5
--- /dev/null
+++ b/test/goroutines/goroutines.go
@@ -0,0 +1,229 @@
+// 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 goroutines
+
+import (
+	"bufio"
+	"bytes"
+	"fmt"
+	"io"
+	"regexp"
+	"runtime"
+	"strconv"
+	"strings"
+	"time"
+)
+
+var goroutineHeaderRE = regexp.MustCompile(`^goroutine (\d+) \[([^\]]+)\]:$`)
+var stackFileRE = regexp.MustCompile(`^\s+([^:]+):(\d+)(?: \+0x([0-9A-Fa-f]+))?$`)
+
+type Goroutine struct {
+	ID      int
+	State   string
+	Stack   []*Frame
+	Creator *Frame
+}
+
+// Get gets a set of currently running goroutines and parses them into a
+// structured representation.
+func Get() ([]*Goroutine, error) {
+	bufsize, read := 1<<20, 0
+	buf := make([]byte, bufsize)
+	for {
+		read = runtime.Stack(buf, true)
+		if read < bufsize {
+			buf = buf[:read]
+			break
+		}
+		bufsize *= 2
+		buf = make([]byte, bufsize)
+	}
+	return Parse(buf)
+}
+
+// Parse parses a stack trace into a structure representation.
+func Parse(buf []byte) ([]*Goroutine, error) {
+	scanner := bufio.NewScanner(bytes.NewReader(buf))
+	var out []*Goroutine
+	for scanner.Scan() {
+		if len(scanner.Bytes()) == 0 {
+			continue
+		}
+		g, err := parseGoroutine(scanner)
+		if err != nil {
+			return out, fmt.Errorf("Error %v parsing trace:\n%s", err, string(buf))
+		}
+		out = append(out, g)
+	}
+	return out, scanner.Err()
+}
+
+func parseGoroutine(scanner *bufio.Scanner) (*Goroutine, error) {
+	g := &Goroutine{}
+	matches := goroutineHeaderRE.FindSubmatch(scanner.Bytes())
+	if len(matches) != 3 {
+		return nil, fmt.Errorf("Could not parse goroutine header from: %s", scanner.Text())
+	}
+	id, err := strconv.ParseInt(string(matches[1]), 10, 64)
+	if err != nil {
+		return nil, err
+	}
+	g.ID = int(id)
+	g.State = string(matches[2])
+
+	for scanner.Scan() {
+		if len(scanner.Bytes()) == 0 {
+			break
+		}
+		frame, err := parseFrame(scanner)
+		if err != nil {
+			return nil, err
+		}
+		if strings.HasPrefix(frame.Call, "created by ") {
+			frame.Call = frame.Call[len("created by "):]
+			g.Creator = frame
+			break
+		}
+		g.Stack = append(g.Stack, frame)
+	}
+	return g, nil
+}
+
+func (g *Goroutine) writeTo(w io.Writer) {
+	fmt.Fprintf(w, "goroutine %d [%s]:\n", g.ID, g.State)
+	for _, f := range g.Stack {
+		f.writeTo(w)
+	}
+	if g.Creator != nil {
+		fmt.Fprint(w, "created by ")
+		g.Creator.writeTo(w)
+	}
+}
+
+// Frame represents a single stack frame.
+type Frame struct {
+	Call   string
+	File   string
+	Line   int
+	Offset int
+}
+
+func parseFrame(scanner *bufio.Scanner) (*Frame, error) {
+	f := &Frame{Call: scanner.Text()}
+	if !scanner.Scan() {
+		return nil, fmt.Errorf("Frame lacked a second line %s", f.Call)
+	}
+	matches := stackFileRE.FindSubmatch(scanner.Bytes())
+	if len(matches) < 4 {
+		return nil, fmt.Errorf("Could not parse file reference from %s", scanner.Text())
+	}
+	f.File = string(matches[1])
+	line, err := strconv.ParseInt(string(matches[2]), 10, 64)
+	if err != nil {
+		return nil, err
+	}
+	f.Line = int(line)
+	if len(matches[3]) > 0 {
+		offset, err := strconv.ParseInt(string(matches[3]), 16, 64)
+		if err != nil {
+			return nil, err
+		}
+		f.Offset = int(offset)
+	}
+	return f, nil
+}
+
+func (f *Frame) writeTo(w io.Writer) {
+	fmt.Fprintln(w, f.Call)
+	if f.Offset != 0 {
+		fmt.Fprintf(w, "\t%s:%d +0x%x\n", f.File, f.Line, f.Offset)
+	} else {
+		fmt.Fprintf(w, "\t%s:%d\n", f.File, f.Line)
+	}
+}
+
+// Format formats Goroutines back into the normal string representation.
+func Format(gs ...*Goroutine) []byte {
+	var buf bytes.Buffer
+	for i, g := range gs {
+		if i != 0 {
+			buf.WriteRune('\n')
+		}
+		g.writeTo(&buf)
+	}
+	return buf.Bytes()
+}
+
+// ErrorReporter is used by NoLeaks to report errors.  testing.T implements
+// this interface and is normally passed.
+type ErrorReporter interface {
+	Errorf(format string, args ...interface{})
+}
+
+// NoLeaks helps test that a test isn't leaving extra goroutines after it finishes.
+//
+// The normal way to use it is:
+// func TestFoo(t *testing.T) {
+//   defer goroutines.NoLeaks(t, time.Second)()
+//
+//   ... Normal test code here ...
+//
+// }
+//
+// The test will fail if there are goroutines running at the end of the test
+// that weren't running at the beginning.
+// Since testing for goroutines being finished can be racy, the detector
+// can wait the specified duration for the set of goroutines to return to the
+// initial set.
+func NoLeaks(t ErrorReporter, wait time.Duration) func() {
+	gs, err := Get()
+	if err != nil {
+		return func() {} // If we can't parse correctly we let the test pass.
+	}
+	bycreator := map[string]int{}
+	for _, g := range gs {
+		key := ""
+		if g.Creator != nil {
+			key = g.Creator.Call
+		}
+		bycreator[key]++
+	}
+	return func() {
+		var left []*Goroutine
+		backoff := 10 * time.Millisecond
+		start := time.Now()
+		until := start.Add(wait)
+		for {
+			cgs, err := Get()
+			if err != nil {
+				return // If we can't parse correctly we let the test pass.
+			}
+			left = left[:0]
+			cbycreator := map[string]int{}
+			for _, g := range cgs {
+				key := ""
+				if g.Creator != nil {
+					key = g.Creator.Call
+				}
+				cbycreator[key]++
+				if cbycreator[key] > bycreator[key] {
+					left = append(left, g)
+				}
+			}
+			if len(left) == 0 {
+				return
+			}
+			if time.Now().After(until) {
+				t.Errorf("%d extra Goroutines outstanding:\n %s", len(left),
+					string(Format(left...)))
+				return
+			}
+			time.Sleep(backoff)
+			if backoff = backoff * 2; backoff > time.Second {
+				backoff = time.Second
+			}
+		}
+	}
+}
diff --git a/test/goroutines/goroutines_test.go b/test/goroutines/goroutines_test.go
new file mode 100644
index 0000000..c0afc5d
--- /dev/null
+++ b/test/goroutines/goroutines_test.go
@@ -0,0 +1,161 @@
+// 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 goroutines
+
+import (
+	"bytes"
+	"runtime"
+	"strings"
+	"sync"
+	"testing"
+	"time"
+)
+
+func wrappedWaitForIt(wg *sync.WaitGroup, wait chan struct{}, n int64) {
+	if n == 0 {
+		waitForIt(wg, wait)
+	} else {
+		wrappedWaitForIt(wg, wait, n-1)
+	}
+}
+
+func waitForIt(wg *sync.WaitGroup, wait chan struct{}) {
+	wg.Done()
+	<-wait
+}
+
+func runGoA(wg *sync.WaitGroup, wait chan struct{}) {
+	go waitForIt(wg, wait)
+}
+
+func runGoB(wg *sync.WaitGroup, wait chan struct{}) {
+	go wrappedWaitForIt(wg, wait, 3)
+}
+
+func runGoC(wg *sync.WaitGroup, wait chan struct{}) {
+	go func() {
+		wg.Done()
+		<-wait
+	}()
+}
+
+func TestGet(t *testing.T) {
+	defer NoLeaks(t, 100*time.Millisecond)()
+	var wg sync.WaitGroup
+	wg.Add(3)
+	wait := make(chan struct{})
+	runGoA(&wg, wait)
+	runGoB(&wg, wait)
+	runGoC(&wg, wait)
+	wg.Wait()
+	gs, err := Get()
+	if err != nil {
+		t.Fatal(err)
+	}
+	close(wait)
+
+	if len(gs) < 4 {
+		t.Errorf("Got %d goroutines, expected at least 4", len(gs))
+	}
+	bycreator := map[string]*Goroutine{}
+	for _, g := range gs {
+		key := ""
+		if g.Creator != nil {
+			key = g.Creator.Call
+		}
+		bycreator[key] = g
+	}
+	a := bycreator["v.io/x/ref/test/goroutines.runGoA"]
+	if a == nil {
+		t.Errorf("runGoA is missing")
+	} else if len(a.Stack) != 1 {
+		t.Errorf("got %d expected 1: %#v", len(a.Stack), a.Stack)
+	} else if !strings.HasPrefix(a.Stack[0].Call, "v.io/x/ref/test/goroutines.waitForIt") {
+		t.Errorf("got %s, wanted it to start with v.io/x/ref/test/goroutines.waitForIt",
+			a.Stack[0].Call)
+	}
+	b := bycreator["v.io/x/ref/test/goroutines.runGoB"]
+	if b == nil {
+		t.Errorf("runGoB is missing")
+	} else if len(b.Stack) != 5 {
+		t.Errorf("got %d expected 1: %#v", len(b.Stack), b.Stack)
+	}
+	c := bycreator["v.io/x/ref/test/goroutines.runGoC"]
+	if c == nil {
+		t.Errorf("runGoC is missing")
+	} else if len(c.Stack) != 1 {
+		t.Errorf("got %d expected 1: %#v", len(c.Stack), c.Stack)
+	} else if !strings.HasPrefix(c.Stack[0].Call, "v.io/x/ref/test/goroutines.") {
+		t.Errorf("got %s, wanted it to start with v.io/x/ref/test/goroutines.",
+			c.Stack[0].Call)
+	}
+}
+
+func TestFormat(t *testing.T) {
+	defer NoLeaks(t, 100*time.Millisecond)()
+
+	var wg sync.WaitGroup
+	wg.Add(3)
+	wait := make(chan struct{})
+	runGoA(&wg, wait)
+	runGoB(&wg, wait)
+	runGoC(&wg, wait)
+	wg.Wait()
+
+	buf := make([]byte, 1<<20)
+	buf = buf[:runtime.Stack(buf, true)]
+	close(wait)
+
+	gs, err := Parse(buf)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if formatted := Format(gs...); !bytes.Equal(buf, formatted) {
+		t.Errorf("got:\n%s\nwanted:\n%s\n", string(formatted), string(buf))
+	}
+}
+
+type fakeErrorReporter struct {
+	calls     int
+	extra     int
+	formatted string
+}
+
+func (f *fakeErrorReporter) Errorf(format string, args ...interface{}) {
+	f.calls++
+	f.extra = args[0].(int)
+	f.formatted = args[1].(string)
+}
+
+func TestNoLeaks(t *testing.T) {
+	er := &fakeErrorReporter{}
+	f := NoLeaks(er, 100*time.Millisecond)
+
+	var wg sync.WaitGroup
+	wg.Add(3)
+	wait := make(chan struct{})
+	runGoA(&wg, wait)
+	runGoB(&wg, wait)
+	runGoC(&wg, wait)
+	wg.Wait()
+
+	f()
+	if er.calls != 1 {
+		t.Errorf("got %d, wanted 1: %s", er.calls, er.formatted)
+	}
+	if er.extra != 3 {
+		t.Errorf("got %d, wanted 3: %s", er.extra, er.formatted)
+	}
+	close(wait)
+
+	*er = fakeErrorReporter{}
+	f()
+	if er.calls != 0 {
+		t.Errorf("got %d, wanted 0: %s", er.calls, er.formatted)
+	}
+	if er.extra != 0 {
+		t.Errorf("got %d, wanted 0: %s", er.extra, er.formatted)
+	}
+}
diff --git a/test/hello/doc.go b/test/hello/doc.go
new file mode 100644
index 0000000..5713ac0
--- /dev/null
+++ b/test/hello/doc.go
@@ -0,0 +1,9 @@
+// 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 hello defines a simple client and server and uses them in a series
+// of regression tests.  The idea is for these programs to have stable interfaces
+// (command line flags, output, etc) so that the test code won't need to change
+// as the underlying framework changes.
+package hello
diff --git a/test/hello/hello_v23_test.go b/test/hello/hello_v23_test.go
new file mode 100644
index 0000000..622bb26
--- /dev/null
+++ b/test/hello/hello_v23_test.go
@@ -0,0 +1,150 @@
+// 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 hello_test
+
+import (
+	"fmt"
+	"os"
+	"time"
+
+	"v.io/x/ref"
+	"v.io/x/ref/lib/security"
+	_ "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 v23 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,
+}
+
+// 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) {
+	idp := testutil.NewIDProvider("root")
+	out := make(map[string]string, len(names))
+	for _, name := range names {
+		dir := i.NewTempDir("")
+		p, err := security.CreatePersistentPrincipal(dir, nil)
+		if err != nil {
+			return nil, err
+		}
+		if err := idp.Bless(p, name); err != nil {
+			return nil, err
+		}
+		out[name] = fmt.Sprintf("%s=%s", ref.EnvCredentials, dir)
+	}
+	return out, nil
+}
+
+func V23TestHelloDirect(i *v23tests.T) {
+	creds, err := setupCredentials(i, "helloclient", "helloserver")
+	if err != nil {
+		i.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.WithEnv(creds["helloclient"]).WithStartOpts(opts).Run("--name", name)
+}
+
+func V23TestHelloAgentd(i *v23tests.T) {
+	creds, err := setupCredentials(i, "helloclient", "helloserver")
+	if err != nil {
+		i.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.WithEnv(creds["helloclient"]).Run(clientbin.Path(), "--name", name)
+}
+
+func V23TestHelloMounttabled(i *v23tests.T) {
+	creds, err := setupCredentials(i, "helloclient", "helloserver", "mounttabled")
+	if err != nil {
+		i.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")
+	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["helloserver"]).Start(serverbin.Path(), "--name", name,
+		"--v23.namespace.root", mtname)
+	agentdbin.WithEnv(creds["helloclient"]).Run(clientbin.Path(), "--name", name,
+		"--v23.namespace.root", mtname)
+}
+
+func V23TestHelloProxy(i *v23tests.T) {
+	// Skipping this test for older binaries because of incompatibility in
+	// the proxyd commandline flags.
+	i.SkipInRegressionBefore("2015-04-25")
+
+	creds, err := setupCredentials(i, "helloclient", "helloserver",
+		"mounttabled", "proxyd")
+	if err != nil {
+		i.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")
+	proxydbin := i.BuildGoPkg("v.io/x/ref/services/proxy/proxyd")
+	serverbin := i.BuildGoPkg("v.io/x/ref/test/hello/helloserver")
+	clientbin := i.BuildGoPkg("v.io/x/ref/test/hello/helloclient")
+	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["proxyd"]).Start(proxydbin.Path(),
+		"--name", proxyname, "--v23.tcp.address", "127.0.0.1:0",
+		"--v23.namespace.root", mtname,
+		"--access-list", "{\"In\":[\"root\"]}")
+	server := agentdbin.WithEnv(creds["helloserver"]).Start(serverbin.Path(),
+		"--name", name, "--v23.proxy", proxyname, "--v23.tcp.address", "",
+		"--v23.namespace.root", mtname)
+	// Prove that we're listening on a proxy.
+	if sn := server.ExpectVar("SERVER_NAME"); sn != "proxy" {
+		i.Fatalf("helloserver not listening through proxy: %s.", sn)
+	}
+	agentdbin.WithEnv(creds["helloclient"]).Run(clientbin.Path(), "--name", name,
+		"--v23.namespace.root", mtname)
+}
diff --git a/test/hello/helloclient/doc.go b/test/hello/helloclient/doc.go
new file mode 100644
index 0000000..6f7c049
--- /dev/null
+++ b/test/hello/helloclient/doc.go
@@ -0,0 +1,62 @@
+// 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
+
+/*
+Command helloclient is a simple client mainly used in regression tests.
+
+Usage:
+   helloclient [flags]
+
+The helloclient flags are:
+ -name=
+   Name of the hello server.
+
+The global flags are:
+ -alsologtostderr=true
+   log to standard error as well as files
+ -log_backtrace_at=:0
+   when logging hits line file:N, emit a stack trace
+ -log_dir=
+   if non-empty, write log files to this directory
+ -logtostderr=false
+   log to standard error instead of files
+ -max_stack_buf_size=4292608
+   max size in bytes of the buffer to use for logging stack traces
+ -metadata=<just specify -metadata to activate>
+   Displays metadata for the program and exits.
+ -stderrthreshold=2
+   logs at or above this threshold go to stderr
+ -v=0
+   log level for V logs
+ -v23.credentials=
+   directory to use for storing security credentials
+ -v23.i18n-catalogue=
+   18n catalogue files to load, comma separated
+ -v23.namespace.root=[/(dev.v.io/role/vprod/service/mounttabled)@ns.dev.v.io:8101]
+   local namespace root; can be repeated to provided multiple roots
+ -v23.proxy=
+   object name of proxy service to use to export services across network
+   boundaries
+ -v23.tcp.address=
+   address to listen on
+ -v23.tcp.protocol=wsh
+   protocol to listen with
+ -v23.vtrace.cache-size=1024
+   The number of vtrace traces to store in memory.
+ -v23.vtrace.collect-regexp=
+   Spans and annotations that match this regular expression will trigger trace
+   collection.
+ -v23.vtrace.dump-on-shutdown=true
+   If true, dump all stored traces on runtime shutdown.
+ -v23.vtrace.sample-rate=0
+   Rate (from 0.0 to 1.0) to sample vtrace traces.
+ -vmodule=
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
+*/
+package main
diff --git a/test/hello/helloclient/helloclient.go b/test/hello/helloclient/helloclient.go
new file mode 100644
index 0000000..c5c931a
--- /dev/null
+++ b/test/hello/helloclient/helloclient.go
@@ -0,0 +1,54 @@
+// 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.
+
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go . -help
+
+package main
+
+import (
+	"errors"
+	"fmt"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/verror"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/v23cmd"
+	_ "v.io/x/ref/runtime/factories/generic"
+)
+
+var name string
+
+func main() {
+	cmdHelloClient.Flags.StringVar(&name, "name", "", "Name of the hello server.")
+	cmdline.HideGlobalFlagsExcept()
+	cmdline.Main(cmdHelloClient)
+}
+
+var cmdHelloClient = &cmdline.Command{
+	Runner: v23cmd.RunnerFunc(runHelloClient),
+	Name:   "helloclient",
+	Short:  "Simple client mainly used in regression tests.",
+	Long: `
+Command helloclient is a simple client mainly used in regression tests.
+`,
+}
+
+func runHelloClient(ctx *context.T, env *cmdline.Env, args []string) error {
+	ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
+	defer cancel()
+
+	var result string
+	err := v23.GetClient(ctx).Call(ctx, name, "Hello", nil, []interface{}{&result})
+	if err != nil {
+		return errors.New(verror.DebugString(err))
+	}
+
+	if got, want := result, "hello"; got != want {
+		return fmt.Errorf("Unexpected result, got %q, want %q", got, want)
+	}
+	return nil
+}
diff --git a/test/hello/helloserver/doc.go b/test/hello/helloserver/doc.go
new file mode 100644
index 0000000..4174c3c
--- /dev/null
+++ b/test/hello/helloserver/doc.go
@@ -0,0 +1,62 @@
+// 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
+
+/*
+Command helloserver is a simple server mainly used in regression tests.
+
+Usage:
+   helloserver [flags]
+
+The helloserver flags are:
+ -name=
+   Name to publish under.
+
+The global flags are:
+ -alsologtostderr=true
+   log to standard error as well as files
+ -log_backtrace_at=:0
+   when logging hits line file:N, emit a stack trace
+ -log_dir=
+   if non-empty, write log files to this directory
+ -logtostderr=false
+   log to standard error instead of files
+ -max_stack_buf_size=4292608
+   max size in bytes of the buffer to use for logging stack traces
+ -metadata=<just specify -metadata to activate>
+   Displays metadata for the program and exits.
+ -stderrthreshold=2
+   logs at or above this threshold go to stderr
+ -v=0
+   log level for V logs
+ -v23.credentials=
+   directory to use for storing security credentials
+ -v23.i18n-catalogue=
+   18n catalogue files to load, comma separated
+ -v23.namespace.root=[/(dev.v.io/role/vprod/service/mounttabled)@ns.dev.v.io:8101]
+   local namespace root; can be repeated to provided multiple roots
+ -v23.proxy=
+   object name of proxy service to use to export services across network
+   boundaries
+ -v23.tcp.address=
+   address to listen on
+ -v23.tcp.protocol=wsh
+   protocol to listen with
+ -v23.vtrace.cache-size=1024
+   The number of vtrace traces to store in memory.
+ -v23.vtrace.collect-regexp=
+   Spans and annotations that match this regular expression will trigger trace
+   collection.
+ -v23.vtrace.dump-on-shutdown=true
+   If true, dump all stored traces on runtime shutdown.
+ -v23.vtrace.sample-rate=0
+   Rate (from 0.0 to 1.0) to sample vtrace traces.
+ -vmodule=
+   comma-separated list of pattern=N settings for filename-filtered logging
+ -vpath=
+   comma-separated list of pattern=N settings for file pathname-filtered logging
+*/
+package main
diff --git a/test/hello/helloserver/helloserver.go b/test/hello/helloserver/helloserver.go
new file mode 100644
index 0000000..00d65fd
--- /dev/null
+++ b/test/hello/helloserver/helloserver.go
@@ -0,0 +1,59 @@
+// 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.
+
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go . -help
+
+package main
+
+import (
+	"fmt"
+
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/x/lib/cmdline"
+	"v.io/x/ref/lib/signals"
+	"v.io/x/ref/lib/v23cmd"
+	"v.io/x/ref/lib/xrpc"
+	_ "v.io/x/ref/runtime/factories/generic"
+)
+
+var name string
+
+func main() {
+	cmdHelloServer.Flags.StringVar(&name, "name", "", "Name to publish under.")
+	cmdline.HideGlobalFlagsExcept()
+	cmdline.Main(cmdHelloServer)
+}
+
+var cmdHelloServer = &cmdline.Command{
+	Runner: v23cmd.RunnerFunc(runHelloServer),
+	Name:   "helloserver",
+	Short:  "Simple server mainly used in regression tests.",
+	Long: `
+Command helloserver is a simple server mainly used in regression tests.
+`,
+}
+
+type helloServer struct{}
+
+func (*helloServer) Hello(ctx *context.T, call rpc.ServerCall) (string, error) {
+	return "hello", nil
+}
+
+func runHelloServer(ctx *context.T, env *cmdline.Env, args []string) error {
+	server, err := xrpc.NewServer(ctx, name, &helloServer{}, security.AllowEveryone())
+	if err != nil {
+		return fmt.Errorf("NewServer: %v", err)
+	}
+	eps := server.Status().Endpoints
+	if len(eps) > 0 {
+		fmt.Printf("SERVER_NAME=%s\n", eps[0].Name())
+	} else {
+		fmt.Println("SERVER_NAME=proxy")
+	}
+	<-signals.ShutdownOnSignals(ctx)
+	return nil
+}
diff --git a/test/hello/v23_test.go b/test/hello/v23_test.go
new file mode 100644
index 0000000..6364cfb
--- /dev/null
+++ b/test/hello/v23_test.go
@@ -0,0 +1,42 @@
+// 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"
+	"v.io/x/ref/test/modules"
+	"v.io/x/ref/test/v23tests"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	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/init.go b/test/init.go
new file mode 100644
index 0000000..c627309
--- /dev/null
+++ b/test/init.go
@@ -0,0 +1,128 @@
+// 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 test
+
+import (
+	"flag"
+	"os"
+	"runtime"
+	"sync"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/options"
+
+	"v.io/x/ref/internal/logger"
+	"v.io/x/ref/lib/flags"
+	"v.io/x/ref/lib/xrpc"
+	"v.io/x/ref/services/mounttable/mounttablelib"
+	"v.io/x/ref/test/testutil"
+)
+
+const (
+	TestBlessing = "test-blessing"
+)
+
+var once sync.Once
+var IntegrationTestsEnabled bool
+var IntegrationTestsDebugShellOnError bool
+
+const IntegrationTestsFlag = "v23.tests"
+const IntegrationTestsDebugShellOnErrorFlag = "v23.tests.shell-on-fail"
+
+func init() {
+	flag.BoolVar(&IntegrationTestsEnabled, IntegrationTestsFlag, false, "Run integration tests.")
+	flag.BoolVar(&IntegrationTestsDebugShellOnError, IntegrationTestsDebugShellOnErrorFlag, false, "Drop into a debug shell if an integration test fails.")
+}
+
+// Init sets up state for running tests: Adjusting GOMAXPROCS,
+// configuring the logging library, setting up the random number generator
+// etc.
+//
+// Doing so requires flags to be parsed, so this function explicitly parses
+// flags. Thus, it is NOT a good idea to call this from the init() function
+// of any module except "main" or _test.go files.
+func Init() {
+	init := func() {
+		if os.Getenv("GOMAXPROCS") == "" {
+			// Set the number of logical processors to the number of CPUs,
+			// if GOMAXPROCS is not set in the environment.
+			runtime.GOMAXPROCS(runtime.NumCPU())
+		}
+		flags.SetDefaultProtocol("tcp")
+		flags.SetDefaultHostPort("127.0.0.1:0")
+		flags.SetDefaultNamespaceRoot("/127.0.0.1:8101")
+		// At this point all of the flags that we're going to use for
+		// tests must be defined.
+		// This will be the case if this is called from the init()
+		// function of a _test.go file.
+		flag.Parse()
+		logger.Manager(logger.Global()).ConfigureFromFlags()
+	}
+	once.Do(init)
+}
+
+// V23Init initializes the runtime and sets up some convenient infrastructure for tests:
+// - Sets a freshly created principal (with a single self-signed blessing) on it.
+// - Creates a mounttable and sets the namespace roots appropriately
+// Both steps are skipped if this function is invoked from a process run
+// using the modules package.
+func V23Init() (*context.T, v23.Shutdown) {
+	moduleProcess := os.Getenv("V23_SHELL_HELPER_PROCESS_ENTRY_POINT") != ""
+	return initWithParams(initParams{
+		CreatePrincipal:  !moduleProcess,
+		CreateMounttable: !moduleProcess,
+	})
+}
+
+// initParams contains parameters for tests that need to control what happens during
+// init carefully.
+type initParams struct {
+	CreateMounttable bool // CreateMounttable creates a new mounttable.
+	CreatePrincipal  bool // CreatePrincipal creates a new principal with self-signed blessing.
+}
+
+// initWithParams initializes the runtime and returns a new context and shutdown function.
+// Specific aspects of initialization can be controlled via the params struct.
+func initWithParams(params initParams) (*context.T, v23.Shutdown) {
+	ctx, shutdown := v23.Init()
+	if params.CreatePrincipal {
+		var err error
+		if ctx, err = v23.WithPrincipal(ctx, testutil.NewPrincipal(TestBlessing)); err != nil {
+			panic(err)
+		}
+	}
+	ns := v23.GetNamespace(ctx)
+	ns.CacheCtl(naming.DisableCache(true))
+	if params.CreateMounttable {
+		disp, err := mounttablelib.NewMountTableDispatcher(ctx, "", "", "mounttable")
+		if err != nil {
+			panic(err)
+		}
+		s, err := xrpc.NewDispatchingServer(ctx, "", disp, options.ServesMountTable(true))
+		if err != nil {
+			panic(err)
+		}
+		ns.SetRoots(s.Status().Endpoints[0].Name())
+	}
+	return ctx, shutdown
+}
+
+// TestContext returns a *contect.T suitable for use in tests with logging
+// configured to use loggler.Global(), but nothing else. In particular it does
+// not call v23.Init and hence any of the v23 functions that
+func TestContext() (*context.T, context.CancelFunc) {
+	ctx, cancel := context.RootContext()
+	return context.WithLogger(ctx, logger.Global()), cancel
+}
+
+// V23InitEmpty initializes a runtime but with no principal.
+func V23InitAnon() (*context.T, v23.Shutdown) {
+	return initWithParams(initParams{
+		CreatePrincipal:  false,
+		CreateMounttable: false,
+	})
+}
diff --git a/test/modules/examples_test.go b/test/modules/examples_test.go
new file mode 100644
index 0000000..18d05a4
--- /dev/null
+++ b/test/modules/examples_test.go
@@ -0,0 +1,55 @@
+// 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 modules_test
+
+import (
+	"fmt"
+	"os"
+
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
+)
+
+var Echo = modules.Register(func(env *modules.Env, args ...string) error {
+	for i, a := range args {
+		fmt.Fprintf(env.Stdout, "%d: %s\n", i, a)
+	}
+	return nil
+}, "echo")
+
+func ExampleDispatch() {
+	if modules.IsChildProcess() {
+		// Child process dispatches to the echo program.
+		if err := modules.Dispatch(); err != nil {
+			panic(err)
+		}
+		return
+	}
+	// Parent process spawns the echo program.
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	sh, _ := modules.NewShell(ctx, nil, false, nil)
+	defer sh.Cleanup(nil, nil)
+	h, _ := sh.Start(nil, Echo, "a", "b")
+	h.Shutdown(os.Stdout, os.Stderr)
+	// Output:
+	// 0: a
+	// 1: b
+}
+
+func ExampleDispatchAndExitIfChild() {
+	// Child process dispatches to the echo program.
+	modules.DispatchAndExitIfChild()
+	// Parent process spawns the echo program.
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	sh, _ := modules.NewShell(ctx, nil, false, nil)
+	defer sh.Cleanup(nil, nil)
+	h, _ := sh.Start(nil, Echo, "c", "d")
+	h.Shutdown(os.Stdout, os.Stderr)
+	// Output:
+	// 0: c
+	// 1: d
+}
diff --git a/test/modules/exec.go b/test/modules/exec.go
new file mode 100644
index 0000000..44a6a1a
--- /dev/null
+++ b/test/modules/exec.go
@@ -0,0 +1,256 @@
+// 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 modules
+
+import (
+	"flag"
+	"io"
+	"os"
+	"os/exec"
+	"sync"
+	"time"
+
+	"v.io/x/lib/envvar"
+
+	"v.io/v23/logging"
+	"v.io/v23/verror"
+
+	"v.io/x/ref/internal/logger"
+	vexec "v.io/x/ref/lib/exec"
+	"v.io/x/ref/lib/mgmt"
+	"v.io/x/ref/test/expect"
+)
+
+// execHandle implements both the Handle interface.
+type execHandle struct {
+	*expect.Session
+	mu         sync.Mutex
+	cmd        *exec.Cmd
+	entryPoint string
+	desc       string
+	handle     *vexec.ParentHandle
+	sh         *Shell
+	stderr     *os.File
+	stdout     io.ReadCloser
+	stdin      io.WriteCloser
+	procErrCh  chan error
+	opts       *StartOpts
+	external   bool
+}
+
+func testFlags(l logging.Logger) []string {
+	var fl []string
+	// pass logging flags to any subprocesses
+
+	flags := logger.Manager(l).ExplicitlySetFlags()
+	for fname, fval := range flags {
+		fl = append(fl, "--"+fname+"="+fval)
+	}
+	timeout := flag.Lookup("test.timeout")
+	if timeout == nil {
+		// not a go test binary
+		return fl
+	}
+	// must be a go test binary
+	val := timeout.Value.(flag.Getter).Get().(time.Duration)
+	if val.String() != timeout.DefValue {
+		// use supplied value for subprocesses
+		fl = append(fl, "--test.timeout="+timeout.Value.String())
+	} else {
+		// translate default value into 3m for subproccesses.  The
+		// default of 10m is too long to wait in order to find out that
+		// our subprocess is wedged.
+		fl = append(fl, "--test.timeout=3m")
+	}
+	return fl
+}
+
+func newExecHandle(entry, desc string) *execHandle {
+	return &execHandle{entryPoint: entry, desc: desc, procErrCh: make(chan error, 1)}
+}
+
+func newExecHandleExternal(prog string) *execHandle {
+	return &execHandle{entryPoint: prog, desc: prog, procErrCh: make(chan error, 1), external: true}
+}
+
+func (eh *execHandle) Stdout() io.Reader {
+	eh.mu.Lock()
+	defer eh.mu.Unlock()
+	return eh.stdout
+}
+
+func (eh *execHandle) Stderr() io.Reader {
+	eh.mu.Lock()
+	defer eh.mu.Unlock()
+	return eh.stderr
+}
+
+func (eh *execHandle) Stdin() io.Writer {
+	eh.mu.Lock()
+	defer eh.mu.Unlock()
+	return eh.stdin
+}
+
+func (eh *execHandle) CloseStdin() {
+	eh.mu.Lock()
+	eh.stdin.Close()
+	eh.mu.Unlock()
+}
+
+func (eh *execHandle) envelope(sh *Shell, env []string, args []string) ([]string, []string) {
+	if eh.external {
+		newargs := append([]string{eh.entryPoint}, args...)
+		newenv := envvar.SliceToMap(env)
+		delete(newenv, shellEntryPoint)
+		return newargs, envvar.MapToSlice(newenv)
+	}
+	newargs := append([]string{os.Args[0]}, testFlags(sh.logger)...)
+	newargs = append(newargs, args...)
+	newenv := envvar.SliceToMap(env)
+	newenv[shellEntryPoint] = eh.entryPoint
+	return newargs, envvar.MapToSlice(newenv)
+}
+
+func (eh *execHandle) start(sh *Shell, agentPath string, opts *StartOpts, env []string, args []string) (*execHandle, error) {
+	eh.mu.Lock()
+	defer eh.mu.Unlock()
+	eh.sh = sh
+	eh.opts = opts
+	args, env = eh.envelope(sh, env, args)
+	cmd := exec.Command(args[0], args[1:]...)
+	cmd.Env = env
+
+	stderr, err := newLogfile("stderr", eh.entryPoint)
+	if err != nil {
+		return nil, err
+	}
+	cmd.Stderr = stderr
+	// We use a custom queue-based Writer implementation for stdout to
+	// decouple the consumers of eh.stdout from the file where the child
+	// sends its output.  This avoids data races between closing the file
+	// and reading from it (since cmd.Wait will wait for the all readers to
+	// be done before closing it).  It also enables Shutdown to drain stdout
+	// while respecting the timeout.
+	stdout := newRW()
+	cmd.Stdout = stdout
+
+	// If we have an explicit stdin to pass to the child, use that,
+	// otherwise create a pipe and return the write side of that pipe
+	// in the handle.
+	if eh.opts.Stdin != nil {
+		cmd.Stdin = eh.opts.Stdin
+	} else {
+		stdin, err := cmd.StdinPipe()
+		if err != nil {
+			return nil, err
+		}
+		eh.stdin = stdin
+	}
+	config := vexec.NewConfig()
+
+	execOpts := []vexec.ParentHandleOpt{}
+	if !eh.opts.ExecProtocol {
+		execOpts = append(execOpts, vexec.UseExecProtocolOpt(false))
+	} else {
+		serialized, err := sh.config.Serialize()
+		if err != nil {
+			return nil, err
+		}
+		config.MergeFrom(serialized)
+		if agentPath != "" {
+			config.Set(mgmt.SecurityAgentPathConfigKey, agentPath)
+		}
+		execOpts = append(execOpts, vexec.ConfigOpt{Config: config})
+	}
+
+	// TODO(cnicolaou): for external programs, vexec should either not be
+	// used or it should taken an option to not use its protocol, and in
+	// particular to share secrets with children.
+	handle := vexec.NewParentHandle(cmd, execOpts...)
+	eh.stdout = stdout
+	eh.stderr = stderr
+	eh.handle = handle
+	eh.cmd = cmd
+	eh.sh.logger.VI(1).Infof("Start: %q stderr: %s", eh.desc, stderr.Name())
+	eh.sh.logger.VI(1).Infof("Start: %q args: %v", eh.desc, cmd.Args)
+	eh.sh.logger.VI(2).Infof("Start: %q env: %v", eh.desc, cmd.Env)
+	if err := handle.Start(); err != nil {
+		// The child process failed to start, either because of some setup
+		// error (e.g. creating pipes for it to use), or a bad binary etc.
+		// A handle is returned, so that Shutdown etc may be called, hence
+		// the error must be sent over eh.procErrCh to allow Shutdown to
+		// terminate.
+		eh.procErrCh <- err
+		return eh, err
+	}
+	if eh.opts.ExecProtocol {
+		if err := eh.handle.WaitForReady(eh.opts.StartTimeout); err != nil {
+			// The child failed to call SetReady, most likely because of bad
+			// command line arguments or some other early exit in the child
+			// process.
+			// As per Start above, a handle is returned and the error
+			// sent over eh.procErrCh.
+			eh.procErrCh <- err
+			return eh, err
+		}
+	}
+	eh.sh.logger.VI(1).Infof("Started: %q, pid %d", eh.desc, cmd.Process.Pid)
+	go func() {
+		eh.procErrCh <- eh.handle.Wait(0)
+		// It's now safe to close eh.stdout, since Wait only returns
+		// once all writes from the pipe to the stdout Writer have
+		// completed.  Closing eh.stdout lets consumers of stdout wrap
+		// up (they'll receive EOF).
+		eh.stdout.Close()
+	}()
+	eh.Session = expect.NewSession(opts.ExpectTesting, stdout, opts.ExpectTimeout)
+	eh.Session.SetVerbosity(eh.sh.sessionVerbosity)
+	return eh, nil
+}
+
+func (eh *execHandle) Pid() int {
+	return eh.cmd.Process.Pid
+}
+
+func (eh *execHandle) Shutdown(stdout, stderr io.Writer) error {
+	eh.mu.Lock()
+	defer eh.mu.Unlock()
+	eh.sh.logger.VI(1).Infof("Shutdown: %q", eh.desc)
+	defer eh.sh.logger.VI(1).Infof("Shutdown: %q [DONE]", eh.desc)
+	if eh.stdin != nil {
+		eh.stdin.Close()
+	}
+	defer eh.sh.Forget(eh)
+
+	waitStdout := make(chan struct{})
+	if stdout != nil {
+		// Drain stdout.
+		go func() {
+			io.Copy(stdout, eh.stdout)
+			close(waitStdout)
+		}()
+	} else {
+		close(waitStdout)
+	}
+
+	var procErr error
+	select {
+	case procErr = <-eh.procErrCh:
+		// The child has exited already.
+	case <-time.After(eh.opts.ShutdownTimeout):
+		// Time out waiting for child to exit.
+		procErr = verror.New(vexec.ErrTimeout, nil)
+		// Force close stdout to unblock any readers of stdout
+		// (including the drain loop started above).
+		eh.stdout.Close()
+	}
+	<-waitStdout
+
+	// Transcribe stderr.
+	outputFromFile(eh.stderr, stderr)
+	os.Remove(eh.stderr.Name())
+
+	return procErr
+}
diff --git a/test/modules/modules_test.go b/test/modules/modules_test.go
new file mode 100644
index 0000000..993e060
--- /dev/null
+++ b/test/modules/modules_test.go
@@ -0,0 +1,790 @@
+// 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 modules_test
+
+import (
+	"bufio"
+	"bytes"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"math/rand"
+	"os"
+	"reflect"
+	"sort"
+	"strconv"
+	"strings"
+	"syscall"
+	"testing"
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/verror"
+	"v.io/x/ref"
+	"v.io/x/ref/lib/exec"
+	vsecurity "v.io/x/ref/lib/security"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
+	"v.io/x/ref/test/testutil"
+
+	_ "v.io/x/ref/runtime/factories/generic"
+)
+
+// We must call TestMain ourselves because using v23 test generate
+// creates an import cycle for this package.
+func TestMain(m *testing.M) {
+	test.Init()
+	modules.DispatchAndExitIfChild()
+	os.Exit(m.Run())
+}
+
+var ignoreStdin = modules.Register(func(*modules.Env, ...string) error {
+	<-time.After(time.Minute)
+	return nil
+}, "ignoreStdin")
+
+var echo = modules.Register(func(env *modules.Env, args ...string) error {
+	for _, a := range args {
+		fmt.Fprintf(env.Stdout, "stdout: %s\n", a)
+		fmt.Fprintf(env.Stderr, "stderr: %s\n", a)
+	}
+	return nil
+}, "Echo")
+
+var pipeEcho = modules.Register(pipeEchoFunc, "pipeEcho")
+
+func pipeEchoFunc(env *modules.Env, args ...string) error {
+	scanner := bufio.NewScanner(env.Stdin)
+	for scanner.Scan() {
+		fmt.Fprintf(env.Stdout, "%p: %s\n", pipeEchoFunc, scanner.Text())
+	}
+	return nil
+}
+
+var lifo = modules.Register(lifoFunc, "lifo")
+
+func lifoFunc(env *modules.Env, args ...string) error {
+	scanner := bufio.NewScanner(env.Stdin)
+	scanner.Scan()
+	msg := scanner.Text()
+	modules.WaitForEOF(env.Stdin)
+	fmt.Fprintf(env.Stdout, "%p: %s\n", lifoFunc, msg)
+	return nil
+}
+
+var printBlessing = modules.Register(func(env *modules.Env, args ...string) error {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	blessing := v23.GetPrincipal(ctx).BlessingStore().Default()
+	fmt.Fprintf(env.Stdout, "%s", blessing)
+	return nil
+}, "printBlessing")
+
+var envTest = modules.Register(func(env *modules.Env, args ...string) error {
+	for _, a := range args {
+		if v := env.Vars[a]; len(v) > 0 {
+			fmt.Fprintf(env.Stdout, "%s\n", a+"="+v)
+		} else {
+			fmt.Fprintf(env.Stderr, "missing %s\n", a)
+		}
+	}
+	modules.WaitForEOF(env.Stdin)
+	fmt.Fprintf(env.Stdout, "done\n")
+	return nil
+}, "envTest")
+
+const printEnvArgPrefix = "PRINTENV_ARG="
+
+var printEnv = modules.Register(func(env *modules.Env, args ...string) error {
+	for _, a := range args {
+		fmt.Fprintf(env.Stdout, "%s%s\n", printEnvArgPrefix, a)
+	}
+	for k, v := range env.Vars {
+		fmt.Fprintf(env.Stdout, "%q\n", k+"="+v)
+	}
+	return nil
+}, "printEnv")
+
+var errorMain = modules.Register(func(env *modules.Env, args ...string) error {
+	return fmt.Errorf("an error")
+}, "errorMain")
+
+func waitForInput(scanner *bufio.Scanner) bool {
+	ch := make(chan struct{})
+	go func(ch chan<- struct{}) {
+		scanner.Scan()
+		ch <- struct{}{}
+	}(ch)
+	select {
+	case <-ch:
+		return true
+	case <-time.After(10 * time.Second):
+		return false
+	}
+}
+
+func testProgram(t *testing.T, sh *modules.Shell, prog modules.Program, key, val string) {
+	h, err := sh.Start(nil, prog, key)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	defer func() {
+		var stdout, stderr bytes.Buffer
+		sh.Cleanup(&stdout, &stderr)
+		want := ""
+		if testing.Verbose() {
+			want = "---- Shell Cleanup ----\n---- Cleanup calling cancelCtx ----\n---- Shell Cleanup Complete ----\n"
+		}
+		if got := stdout.String(); got != "" && got != want {
+			t.Errorf("got %q, want %q", got, want)
+		}
+		if got := stderr.String(); got != "" && got != want {
+			t.Errorf("got %q, want %q", got, want)
+		}
+	}()
+	scanner := bufio.NewScanner(h.Stdout())
+	if !waitForInput(scanner) {
+		t.Errorf("timeout")
+		return
+	}
+	if got, want := scanner.Text(), key+"="+val; got != want {
+		t.Errorf("got %q, want %q", got, want)
+	}
+	h.CloseStdin()
+	if !waitForInput(scanner) {
+		t.Fatalf("timeout")
+		return
+	}
+	if got, want := scanner.Text(), "done"; got != want {
+		t.Errorf("got %q, want %q", got, want)
+	}
+	if err := h.Shutdown(nil, nil); err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+}
+
+func getBlessing(t *testing.T, sh *modules.Shell, env ...string) string {
+	h, err := sh.Start(env, printBlessing)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	scanner := bufio.NewScanner(h.Stdout())
+	if !waitForInput(scanner) {
+		t.Errorf("timeout")
+		return ""
+	}
+	return scanner.Text()
+}
+
+func getCustomBlessing(t *testing.T, sh *modules.Shell, creds *modules.CustomCredentials) string {
+	h, err := sh.StartWithOpts(sh.DefaultStartOpts().WithCustomCredentials(creds), nil, printBlessing)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	scanner := bufio.NewScanner(h.Stdout())
+	if !waitForInput(scanner) {
+		t.Errorf("timeout")
+		return ""
+	}
+	return scanner.Text()
+}
+
+func TestChild(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	sh, err := modules.NewShell(ctx, nil, testing.Verbose(), t)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	defer sh.Cleanup(nil, nil)
+	key, val := "simpleVar", "foo & bar"
+	sh.SetVar(key, val)
+	testProgram(t, sh, envTest, key, val)
+}
+
+func TestAgent(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	sh, err := modules.NewShell(ctx, nil, testing.Verbose(), t)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	defer sh.Cleanup(os.Stdout, os.Stderr)
+	a := getBlessing(t, sh)
+	b := getBlessing(t, sh)
+	if a != b {
+		t.Errorf("Expected same blessing for children, got %s and %s", a, b)
+	}
+}
+
+func TestCustomPrincipal(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	p, err := vsecurity.NewPrincipal()
+	if err != nil {
+		t.Fatal(err)
+	}
+	b, err := p.BlessSelf("myshell")
+	if err != nil {
+		t.Fatal(err)
+	}
+	if err := vsecurity.SetDefaultBlessings(p, b); err != nil {
+		t.Fatal(err)
+	}
+	cleanDebug := p.BlessingStore().DebugString()
+	sh, err := modules.NewShell(ctx, p, testing.Verbose(), t)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	defer sh.Cleanup(os.Stdout, os.Stderr)
+	if got, want := getBlessing(t, sh), "myshell/child"; got != want {
+		t.Errorf("Bad blessing. Got %q, want %q", got, want)
+	}
+	newDebug := p.BlessingStore().DebugString()
+	if cleanDebug != newDebug {
+		t.Errorf("Shell modified custom principal. Was:\n%q\nNow:\n%q", cleanDebug, newDebug)
+	}
+}
+
+func TestCustomCredentials(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	root := testutil.NewIDProvider("myshell")
+	sh, err := modules.NewShell(ctx, nil, testing.Verbose(), t)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer sh.Cleanup(os.Stdout, os.Stderr)
+
+	newCreds := func(ext string) *modules.CustomCredentials {
+		p, err := sh.NewCustomCredentials()
+		if err != nil {
+			t.Fatal(err)
+		}
+		b, err := root.NewBlessings(p.Principal(), ext)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if err := vsecurity.SetDefaultBlessings(p.Principal(), b); err != nil {
+			t.Fatal(err)
+		}
+		return p
+	}
+
+	a := newCreds("a")
+	cleanDebug := a.Principal().BlessingStore().DebugString()
+
+	blessing := getCustomBlessing(t, sh, a)
+	if blessing != "myshell/a" {
+		t.Errorf("Bad blessing. Expected myshell/a, go %q", blessing)
+	}
+
+	b := newCreds("bar")
+	blessing = getCustomBlessing(t, sh, b)
+	if blessing != "myshell/bar" {
+		t.Errorf("Bad blessing. Expected myshell/bar, go %q", blessing)
+	}
+
+	// Make sure we can re-use credentials
+	blessing = getCustomBlessing(t, sh, a)
+	if blessing != "myshell/a" {
+		t.Errorf("Bad blessing. Expected myshell/a, go %q", blessing)
+	}
+
+	newDebug := a.Principal().BlessingStore().DebugString()
+	if cleanDebug != newDebug {
+		t.Errorf("Shell modified custom principal. Was:\n%q\nNow:\n%q", cleanDebug, newDebug)
+	}
+}
+
+func createCredentials(blessing string) (string, error) {
+	dir, err := ioutil.TempDir("", "TestNoAgent_v23_credentials")
+	if err != nil {
+		return "", err
+	}
+	p, err := vsecurity.CreatePersistentPrincipal(dir, nil)
+	if err != nil {
+		os.RemoveAll(dir)
+		return "", err
+	}
+	b, err := p.BlessSelf(blessing)
+	if err != nil {
+		os.RemoveAll(dir)
+		return "", err
+	}
+	if err := vsecurity.SetDefaultBlessings(p, b); err != nil {
+		os.RemoveAll(dir)
+		return "", err
+	}
+	return dir, nil
+}
+
+func TestNoAgent(t *testing.T) {
+	const noagent = "noagent"
+	creds, err := createCredentials(noagent)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(creds)
+	sh, err := modules.NewShell(nil, nil, testing.Verbose(), t)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	defer sh.Cleanup(os.Stdout, os.Stderr)
+	if got, want := getBlessing(t, sh, fmt.Sprintf("%s=%s", ref.EnvCredentials, creds)), noagent; got != want {
+		t.Errorf("Bad blessing. Got %q, want %q", got, want)
+	}
+}
+
+func TestChildNoRegistration(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	sh, err := modules.NewShell(ctx, nil, testing.Verbose(), t)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	defer sh.Cleanup(os.Stderr, os.Stderr)
+	key, val := "simpleVar", "foo & bar"
+	sh.SetVar(key, val)
+	testProgram(t, sh, envTest, key, val)
+	_, err = sh.Start(nil, modules.Program("non-existent-program"), "random", "args")
+	if err == nil {
+		fmt.Fprintf(os.Stderr, "Failed: %v\n", err)
+		t.Fatalf("expected error")
+	}
+}
+
+func TestErrorChild(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	sh, err := modules.NewShell(ctx, nil, testing.Verbose(), t)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	defer sh.Cleanup(nil, nil)
+	h, err := sh.Start(nil, errorMain)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	if got, want := h.Shutdown(nil, nil), "exit status 1"; got == nil || got.Error() != want {
+		t.Errorf("got %q, want %q", got, want)
+	}
+}
+
+func testShutdown(t *testing.T, sh *modules.Shell, prog modules.Program) {
+	result := ""
+	args := []string{"a", "b c", "ddd"}
+	if _, err := sh.Start(nil, prog, args...); err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	var stdoutBuf bytes.Buffer
+	var stderrBuf bytes.Buffer
+	sh.Cleanup(&stdoutBuf, &stderrBuf)
+	var stdoutOutput, stderrOutput string
+	for _, a := range args {
+		stdoutOutput += fmt.Sprintf("stdout: %s\n", a)
+		stderrOutput += fmt.Sprintf("stderr: %s\n", a)
+	}
+	if got, want := stdoutBuf.String(), stdoutOutput+result; got != want {
+		t.Errorf("got %q want %q", got, want)
+	}
+	if got, want := stderrBuf.String(), stderrOutput; got != want {
+		t.Errorf("got %q want %q", got, want)
+	}
+}
+
+func TestShutdownSubprocess(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	sh, err := modules.NewShell(ctx, nil, false, t)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	defer sh.Cleanup(nil, nil)
+	testShutdown(t, sh, echo)
+}
+
+// TestShutdownSubprocessIgnoreStdin verifies that Shutdown doesn't wait
+// forever if a child does not die upon closing stdin; but instead times out and
+// returns an appropriate error.
+func TestShutdownSubprocessIgnoreStdin(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	sh, err := modules.NewShell(ctx, nil, false, t)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	opts := sh.DefaultStartOpts()
+	opts.ShutdownTimeout = time.Second
+	h, err := sh.StartWithOpts(opts, nil, ignoreStdin)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	var stdoutBuf, stderrBuf bytes.Buffer
+	if err := sh.Cleanup(&stdoutBuf, &stderrBuf); err == nil || verror.ErrorID(err) != exec.ErrTimeout.ID {
+		t.Errorf("unexpected error in Cleanup: got %v, want %v", err, exec.ErrTimeout.ID)
+	}
+	if err := syscall.Kill(h.Pid(), syscall.SIGINT); err != nil {
+		t.Errorf("Kill failed: %v", err)
+	}
+}
+
+// TestStdoutRace exemplifies a potential race between reading from child's
+// stdout and closing stdout in Wait (called by Shutdown).
+//
+// NOTE: triggering the actual --race failure is hard, even if the
+// implementation inappropriately sets stdout to the file that is to be closed
+// in Wait.
+func TestStdoutRace(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	sh, err := modules.NewShell(ctx, nil, testing.Verbose(), t)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	opts := sh.DefaultStartOpts()
+	opts.ShutdownTimeout = time.Second
+	h, err := sh.StartWithOpts(opts, nil, ignoreStdin)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	ch := make(chan error, 1)
+	go func() {
+		buf := make([]byte, 5)
+		// This will block since the child is not writing anything on
+		// stdout.
+		_, err := h.Stdout().Read(buf)
+		ch <- err
+	}()
+	// Give the goroutine above a chance to run, so that we're blocked on
+	// stdout.Read.
+	<-time.After(time.Second)
+	// Cleanup should close stdout, and unblock the goroutine.
+	sh.Cleanup(nil, nil)
+	if got, want := <-ch, io.EOF; got != want {
+		t.Errorf("Expected %v, got %v instead", want, got)
+	}
+
+	if err := syscall.Kill(h.Pid(), syscall.SIGINT); err != nil {
+		t.Errorf("Kill failed: %v", err)
+	}
+}
+
+func find(want string, in []string) bool {
+	for _, a := range in {
+		if a == want {
+			return true
+		}
+	}
+	return false
+}
+
+func TestEnvelope(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	sh, err := modules.NewShell(ctx, nil, testing.Verbose(), t)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	defer sh.Cleanup(nil, nil)
+	sh.SetVar("a", "1")
+	sh.SetVar("b", "2")
+	args := []string{"oh", "ah"}
+	h, err := sh.Start(nil, printEnv, args...)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	scanner := bufio.NewScanner(h.Stdout())
+	childArgs, childEnv := []string{}, []string{}
+	for scanner.Scan() {
+		o := scanner.Text()
+		if strings.HasPrefix(o, printEnvArgPrefix) {
+			childArgs = append(childArgs, strings.TrimPrefix(o, printEnvArgPrefix))
+		} else {
+			childEnv = append(childEnv, o)
+		}
+	}
+	shArgs, shEnv := sh.ProgramEnvelope(nil, printEnv, args...)
+	for i, ev := range shEnv {
+		shEnv[i] = fmt.Sprintf("%q", ev)
+	}
+	for _, want := range args {
+		if !find(want, childArgs) {
+			t.Errorf("failed to find %q in %s", want, childArgs)
+		}
+		if !find(want, shArgs) {
+			t.Errorf("failed to find %q in %s", want, shArgs)
+		}
+	}
+
+	for _, want := range shEnv {
+		if !find(want, childEnv) {
+			t.Errorf("failed to find %s in %#v", want, childEnv)
+		}
+	}
+
+	for _, want := range childEnv {
+		if want == "\""+exec.ExecVersionVariable+"=\"" {
+			continue
+		}
+		if !find(want, shEnv) {
+			t.Errorf("failed to find %s in %#v", want, shEnv)
+		}
+	}
+}
+
+func TestEnvMerge(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	sh, err := modules.NewShell(ctx, nil, testing.Verbose(), t)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	defer sh.Cleanup(nil, nil)
+	sh.SetVar("a", "1")
+	os.Setenv("a", "wrong, should be 1")
+	sh.SetVar("b", "2 also wrong")
+	os.Setenv("b", "wrong, should be 2")
+	h, err := sh.Start([]string{"b=2"}, printEnv)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	scanner := bufio.NewScanner(h.Stdout())
+	for scanner.Scan() {
+		o := scanner.Text()
+		if strings.HasPrefix(o, "a=") {
+			if got, want := o, "a=1"; got != want {
+				t.Errorf("got: %q, want %q", got, want)
+			}
+		}
+		if strings.HasPrefix(o, "b=") {
+			if got, want := o, "b=2"; got != want {
+				t.Errorf("got: %q, want %q", got, want)
+			}
+		}
+	}
+}
+
+func TestNoExec(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	sh, err := modules.NewShell(ctx, nil, testing.Verbose(), t)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	defer sh.Cleanup(nil, nil)
+	h, err := sh.StartWithOpts(sh.DefaultStartOpts().NoExecProgram(), nil, modules.Program("/bin/echo"), "hello", "world")
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	scanner := bufio.NewScanner(h.Stdout())
+	scanner.Scan()
+	if got, want := scanner.Text(), "hello world"; got != want {
+		t.Fatalf("got %v, want %v", got, want)
+	}
+}
+
+func TestExternal(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	sh, err := modules.NewShell(ctx, nil, testing.Verbose(), t)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	defer sh.Cleanup(nil, nil)
+	cookie := strconv.Itoa(rand.Int())
+	sh.SetConfigKey("cookie", cookie)
+	h, err := sh.StartWithOpts(sh.DefaultStartOpts().ExternalProgram(), nil, modules.Program(os.Args[0]), "--test.run=TestExternalTestHelper")
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	scanner := bufio.NewScanner(h.Stdout())
+	scanner.Scan()
+	if got, want := scanner.Text(), fmt.Sprintf("cookie: %s", cookie); got != want {
+		h.Shutdown(os.Stderr, os.Stderr)
+		t.Fatalf("got %v, want %v", got, want)
+	}
+}
+
+// TestExternalTestHelper is used by TestExternal above and has not utility
+// as a test in it's own right.
+func TestExternalTestHelper(t *testing.T) {
+	child, err := exec.GetChildHandle()
+	if err != nil {
+		return
+	}
+	child.SetReady()
+	val, err := child.Config.Get("cookie")
+	if err != nil {
+		t.Fatalf("failed to get child handle: %s", err)
+	}
+	fmt.Printf("cookie: %s\n", val)
+}
+
+func TestPipe(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+
+	sh, err := modules.NewShell(ctx, nil, testing.Verbose(), t)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	defer sh.Cleanup(nil, nil)
+
+	r, w, err := os.Pipe()
+	if err != nil {
+		t.Fatal(err)
+	}
+	opts := sh.DefaultStartOpts()
+	opts.Stdin = r
+	h, err := sh.StartWithOpts(opts, nil, pipeEcho)
+	if err != nil {
+		t.Fatal(err)
+	}
+	cookie := strconv.Itoa(rand.Int())
+	go func(w *os.File, s string) {
+		fmt.Fprintf(w, "hello world\n")
+		fmt.Fprintf(w, "%s\n", s)
+		w.Close()
+	}(w, cookie)
+
+	scanner := bufio.NewScanner(h.Stdout())
+	want := []string{
+		fmt.Sprintf("%p: hello world", pipeEchoFunc),
+		fmt.Sprintf("%p: %s", pipeEchoFunc, cookie),
+	}
+	i := 0
+	for scanner.Scan() {
+		if got, want := scanner.Text(), want[i]; got != want {
+			t.Fatalf("got %v, want %v", got, want)
+		}
+		i++
+	}
+	if got, want := i, 2; got != want {
+		t.Fatalf("got %v, want %v", got, want)
+	}
+	if err := h.Shutdown(os.Stderr, os.Stderr); err != nil {
+		t.Fatal(err)
+	}
+	r.Close()
+}
+
+func TestLIFO(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	sh, err := modules.NewShell(ctx, nil, testing.Verbose(), t)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	defer sh.Cleanup(nil, nil)
+
+	cases := []string{"a", "b", "c"}
+	for _, msg := range cases {
+		h, err := sh.Start(nil, lifo)
+		if err != nil {
+			t.Fatal(err)
+		}
+		fmt.Fprintf(h.Stdin(), "%s\n", msg)
+	}
+	var buf bytes.Buffer
+	if err := sh.Cleanup(&buf, nil); err != nil {
+		t.Fatal(err)
+	}
+	lines := strings.Split(strings.TrimRight(buf.String(), "\n"), "\n")
+	if got, want := len(lines), len(cases); got != want {
+		t.Fatalf("got %v, want %v", got, want)
+	}
+	sort.Sort(sort.Reverse(sort.StringSlice(cases)))
+	for i, msg := range cases {
+		if got, want := lines[i], fmt.Sprintf("%p: %s", lifoFunc, msg); got != want {
+			t.Fatalf("got %v, want %v", got, want)
+		}
+	}
+}
+
+func TestStartOpts(t *testing.T) {
+	sh, err := modules.NewShell(nil, nil, testing.Verbose(), t)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	opts := modules.StartOpts{
+		External: true,
+	}
+	sh.SetDefaultStartOpts(opts)
+	def := sh.DefaultStartOpts()
+	if got, want := def.External, opts.External; got != want {
+		t.Fatalf("got %v, want %v", got, want)
+	}
+	def.External = false
+	if got, want := def, (modules.StartOpts{}); !reflect.DeepEqual(got, want) {
+		t.Fatalf("got %v, want %v", got, want)
+	}
+
+	// Verify that the shell retains a copy.
+	opts.External = false
+	opts.ExecProtocol = true
+	def = sh.DefaultStartOpts()
+	if got, want := def.External, true; got != want {
+		t.Fatalf("got %v, want %v", got, want)
+	}
+	if got, want := def.ExecProtocol, false; got != want {
+		t.Fatalf("got %v, want %v", got, want)
+	}
+
+	sh.SetDefaultStartOpts(opts)
+	def = sh.DefaultStartOpts()
+	if got, want := def.ExecProtocol, true; got != want {
+		t.Fatalf("got %v, want %v", got, want)
+	}
+}
+
+func TestEmbeddedSession(t *testing.T) {
+	sh, err := modules.NewShell(nil, nil, testing.Verbose(), t)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	def := sh.DefaultStartOpts()
+	if def.ExpectTesting == nil {
+		t.Fatalf("ExpectTesting should be non nil")
+	}
+}
+
+func TestCredentialsAndNoExec(t *testing.T) {
+	ctx, shutdown := test.V23Init()
+	defer shutdown()
+	sh, err := modules.NewShell(ctx, nil, testing.Verbose(), t)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	opts := sh.DefaultStartOpts()
+	opts = opts.NoExecProgram()
+	creds, err := sh.NewCustomCredentials()
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	opts = opts.WithCustomCredentials(creds)
+	h, err := sh.StartWithOpts(opts, nil, echo, "a")
+
+	if got, want := err, modules.ErrNoExecAndCustomCreds; got != want {
+		t.Fatalf("got %v, want %v", got, want)
+	}
+	if got, want := h, modules.Handle(nil); got != want {
+		t.Fatalf("got %v, want %v", got, want)
+	}
+}
diff --git a/test/modules/only_for_test.go b/test/modules/only_for_test.go
new file mode 100644
index 0000000..48ea1f3
--- /dev/null
+++ b/test/modules/only_for_test.go
@@ -0,0 +1,12 @@
+// 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 modules
+
+import "io"
+
+// NewRW exposes newRW for unit tests.
+func NewRW() io.ReadWriteCloser {
+	return newRW()
+}
diff --git a/test/modules/queue_rw.go b/test/modules/queue_rw.go
new file mode 100644
index 0000000..2e5c20a
--- /dev/null
+++ b/test/modules/queue_rw.go
@@ -0,0 +1,50 @@
+// 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 modules
+
+import (
+	"bytes"
+	"io"
+	"sync"
+)
+
+// queueRW implements a ReadWriteCloser backed by an unbounded in-memory
+// buffer.
+type queueRW struct {
+	mu     sync.Mutex
+	cond   *sync.Cond
+	buf    bytes.Buffer
+	closed bool
+}
+
+func newRW() io.ReadWriteCloser {
+	q := &queueRW{}
+	q.cond = sync.NewCond(&q.mu)
+	return q
+}
+
+func (q *queueRW) Close() error {
+	q.mu.Lock()
+	defer q.mu.Unlock()
+	defer q.cond.Broadcast()
+	q.closed = true
+	return nil
+}
+
+func (q *queueRW) Read(p []byte) (n int, err error) {
+	q.mu.Lock()
+	defer q.mu.Unlock()
+	for q.buf.Len() == 0 && !q.closed {
+		q.cond.Wait()
+	}
+	return q.buf.Read(p)
+}
+
+func (q *queueRW) Write(p []byte) (n int, err error) {
+	q.mu.Lock()
+	defer q.mu.Unlock()
+	defer q.cond.Broadcast()
+	return q.buf.Write(p)
+}
diff --git a/test/modules/queue_rw_test.go b/test/modules/queue_rw_test.go
new file mode 100644
index 0000000..af20b44
--- /dev/null
+++ b/test/modules/queue_rw_test.go
@@ -0,0 +1,59 @@
+// 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 modules_test
+
+import (
+	"bytes"
+	"io"
+	"testing"
+
+	"v.io/x/ref/test/modules"
+	"v.io/x/ref/test/testutil"
+)
+
+func TestQueueRW(t *testing.T) {
+	testutil.InitRandGenerator(t.Logf)
+	q := modules.NewRW()
+	size := testutil.RandomIntn(1000)
+	data := testutil.RandomBytes(size)
+	begin := 0
+	for {
+		end := begin + testutil.RandomIntn(100) + 1
+		if end > len(data) {
+			end = len(data)
+		}
+		n, err := q.Write(data[begin:end])
+		if err != nil {
+			t.Fatalf("Write failed: %v", err)
+		}
+		begin = begin + n
+		if begin == len(data) {
+			break
+		}
+	}
+	if err := q.Close(); err != nil {
+		t.Fatalf("err %v", err)
+	}
+	readData := make([]byte, 0, size)
+	for {
+		buf := make([]byte, testutil.RandomIntn(100)+1)
+		n, err := q.Read(buf)
+		if n > 0 {
+			readData = append(readData, buf[:n]...)
+		}
+		if err != nil {
+			if err == io.EOF {
+				break
+			}
+			t.Fatalf("Read failed: %v", err)
+		}
+	}
+	if size != len(readData) {
+		t.Fatalf("Mismatching data size: %d != %d", size, len(readData))
+	}
+	if !bytes.Equal(data, readData) {
+		t.Fatalf("Diffing data:\n%v\n%v", data, readData)
+	}
+}
diff --git a/test/modules/registry.go b/test/modules/registry.go
new file mode 100644
index 0000000..c0309c0
--- /dev/null
+++ b/test/modules/registry.go
@@ -0,0 +1,166 @@
+// 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 modules
+
+import (
+	"flag"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"runtime"
+	"strconv"
+	"time"
+
+	"v.io/x/ref/internal/logger"
+	vexec "v.io/x/ref/lib/exec"
+)
+
+// Program is a symbolic representation of a registered Main function.
+type Program string
+
+type programInfo struct {
+	main    Main
+	factory func() *execHandle
+}
+
+type programRegistry struct {
+	programs []*programInfo
+}
+
+var registry = new(programRegistry)
+
+func (r *programRegistry) addProgram(main Main, description string) Program {
+	prog := strconv.Itoa(len(r.programs))
+	factory := func() *execHandle { return newExecHandle(prog, description) }
+	r.programs = append(r.programs, &programInfo{main, factory})
+	return Program(prog)
+}
+
+func (r *programRegistry) getProgram(prog Program) *programInfo {
+	index, err := strconv.Atoi(string(prog))
+	if err != nil || index < 0 || index >= len(r.programs) {
+		return nil
+	}
+	return r.programs[index]
+}
+
+func (r *programRegistry) getExternalProgram(prog Program) *programInfo {
+	h := newExecHandleExternal(string(prog))
+	return &programInfo{
+		factory: func() *execHandle { return h },
+	}
+}
+
+func (r *programRegistry) String() string {
+	var s string
+	for _, info := range r.programs {
+		h := info.factory()
+		s += fmt.Sprintf("%s: %s\n", h.entryPoint, h.desc)
+	}
+	return s
+}
+
+// Register adds a new program to the registry that will be run as a subprocess.
+// It must be called before Dispatch is called, typically from an init function.
+func Register(main Main, description string) Program {
+	if _, file, line, ok := runtime.Caller(1); ok {
+		description = fmt.Sprintf("%s:%d %s", shortFile(file), line, description)
+	}
+	return registry.addProgram(main, description)
+}
+
+// shortFile returns the last 3 components of the given file name.
+func shortFile(file string) string {
+	var short string
+	for i := 0; i < 3; i++ {
+		short = filepath.Join(filepath.Base(file), short)
+		file = filepath.Dir(file)
+	}
+	return short
+}
+
+const shellEntryPoint = "V23_SHELL_HELPER_PROCESS_ENTRY_POINT"
+
+// IsChildProcess returns true if this process was started by the modules
+// package.
+func IsChildProcess() bool {
+	return os.Getenv(shellEntryPoint) != ""
+}
+
+// Dispatch executes the requested subprocess program from within a subprocess.
+// Returns an error if it is executed by a process that does not specify an
+// entry point in its environment.
+func Dispatch() error {
+	return registry.dispatch()
+}
+
+// DispatchAndExitIfChild is a convenience function with three possible results:
+//   * os.Exit(0) if called within a child process, and the dispatch succeeds.
+//   * os.Exit(1) if called within a child process, and the dispatch fails.
+//   * return with no side-effects, if not called within a child process.
+func DispatchAndExitIfChild() {
+	if IsChildProcess() {
+		if err := Dispatch(); err != nil {
+			fmt.Fprintf(os.Stderr, "modules.Dispatch failed: %v\n", err)
+			os.Exit(1)
+		}
+		os.Exit(0)
+	}
+}
+
+func (r *programRegistry) dispatch() error {
+	ch, err := vexec.GetChildHandle()
+	if err != nil {
+		// This is for debugging only. It's perfectly reasonable for this
+		// error to occur if the process is started by a means other
+		// than the exec library.
+		logger.Global().VI(1).Infof("failed to get child handle: %s", err)
+	}
+
+	// Only signal that the child is ready or failed if we successfully get
+	// a child handle. We most likely failed to get a child handle
+	// because the subprocess was run directly from the command line.
+	prog := os.Getenv(shellEntryPoint)
+	if prog == "" {
+		err := fmt.Errorf("Failed to find entrypoint %q", prog)
+		if ch != nil {
+			ch.SetFailed(err)
+		}
+		return err
+	}
+
+	m := registry.getProgram(Program(prog))
+	if m == nil {
+		err := fmt.Errorf("%s: not registered\n%s", prog, registry.String())
+		if ch != nil {
+			ch.SetFailed(err)
+		}
+		return err
+	}
+
+	if ch != nil {
+		ch.SetReady()
+	}
+
+	go func(pid int) {
+		for {
+			_, err := os.FindProcess(pid)
+			if err != nil {
+				logger.Global().Fatalf("Looks like our parent exited: %v", err)
+			}
+			time.Sleep(time.Second)
+		}
+	}(os.Getppid())
+
+	flag.Parse()
+	return m.main(EnvFromOS(), flag.Args()...)
+}
+
+// WaitForEOF returns when a read on its io.Reader parameter returns io.EOF
+func WaitForEOF(r io.Reader) {
+	io.Copy(ioutil.Discard, r)
+}
diff --git a/test/modules/shell.go b/test/modules/shell.go
new file mode 100644
index 0000000..4b3400e
--- /dev/null
+++ b/test/modules/shell.go
@@ -0,0 +1,780 @@
+// 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 modules implements a mechanism for running commonly used services as
+// subprocesses, and client functionality for accessing those services.  Such
+// services and functions are collectively called 'programs' and are managed by
+// a 'Registry'. The Shell is analagous to the UNIX shell and maintains a key,
+// value store of environment variables and config settings that are accessible
+// to the programs that it hosts. Simple variable expansion is supported.
+//
+// Three types of 'programs' may be invoked via a Shell:
+//
+//   1) Functions of type Main as subprocesses via fork/exec.
+//   2) Arbitrary non-Vanadium programs available on the underlying operating
+//      system such as '/bin/cp', 'bash' etc.
+//   3) Arbitrary Vanadium programs available on the underlying operating system
+//      such as precompiled Vanadium services.
+//
+// The first type requires that the function to be executed is compiled into the
+// binary executing the calls to the Shell.  These functions are registered with
+// a single, per-process, registry.
+//
+// The second two types allow for arbitrary binaries to be executed. The
+// distinction between a Vanadium and non-Vanadium program is that the Vanadium
+// program implements the protocol used by v.io/x/ref/lib/exec package to
+// synchronise between the parent and child processes and to share information
+// such as the ConfigKey key,value store supported by the Shell, a shared
+// secret, shared file descriptors etc.
+//
+// When the exec protocol is not used the only form of communication with the
+// child processes are environment variables and command line flags and any
+// shared file descriptors explicitly created by the parent process and expected
+// by the child process; the Start method will not create any additional file
+// descriptors.
+//
+// The registry provides the following functions:
+//
+//   Register: registers a Main function to be executed in a subprocess,
+//     the returned Program is typically assigned to a global variable.
+//   Dispatch: must be called in the child process to lookup and invoke the
+//     requested function.  Typically called from TestMain.
+//
+// The v23 tool can automate generation of TestMain.  Adding the comment below
+// to a test file will generate the appropriate code.
+//
+//   //go:generate v23 test generate .
+//
+// Use 'v23 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
+// subprocess, and in particular to read/write data from/to it using io channels
+// that follow the stdin, stdout, stderr convention. The StartOpts struct is
+// used to control the detailed behaviour of each such invocation.  Various
+// helper functions are provided both for creating appropriate instances of
+// StartOpts and for common uses of StartWithOpts.
+//
+// Each successful call to StartWithOpts returns a handle representing the
+// running program. This handle can be used to gain access to that program's
+// stdin, stdout, stderr and to request or synchronize with its termination via
+// the Shutdown method. The Shutdown method can optionally be used to read any
+// remaining output from the programs stdout and stderr.  The Shell maintains a
+// record of all such handles and will call Shutdown on them in LIFO order when
+// the Shell's Cleanup method is called.
+//
+// A simple protocol must be followed by all programs, in particular, they
+// should wait for their stdin stream to be closed before exiting. The caller
+// can then coordinate with any program by writing to that stdin stream and
+// reading responses from the stdout stream, and it can close stdin when it's
+// ready for the program to exit using the CloseStdin method on the program's
+// handle. Any binary or script that follows this protocol can be used as well.
+//
+// By default, every Shell created by NewShell starts a security agent to manage
+// principals for child processes. These default credentials can be overridden
+// by passing a nil context to NewShell then specifying VeyronCredentials in the
+// environment provided as a parameter to the StartWithOpts method. It is also
+// possible to specify custom credentials via StartOpts.
+//
+// Interacting with Programs
+//
+// Handle.Stdout(), Stdin(), Stderr():
+//
+// StartWithOpts returns a Handle which can be used to interact with the running
+// program. In particular, its Stdin() and Stdout() methods give access to the
+// running process' corresponding stdin and stdout and hence can be used to
+// communicate with it. Stderr is handled differently and is configured so that
+// the child's stderr is written to a log file rather than a pipe. This is in
+// order to maximise the liklihood of capturing stderr output from a crashed
+// child process.
+//
+// Handle.Shutdown(stdout, stderr io.Writer):
+//
+// The Shutdown method is used to gracefully shutdown a program and to
+// synchronise with its termination. In particular, Shutdown can be used to read
+// any unread output from the program's stdout and stderr. Note that since
+// Stderr is buffered to a file, Shutdown is able to return the entire contents
+// of that file. This is useful for debugging misbehaving/crashing child
+// processes.
+//
+// Shell.Cleanup(stdout, stderr io.Writer):
+//
+// The Shell keeps track of all Handles that it has issued and in particular if
+// Shutdown (or Forget) have not been called, it will call Shutdown for each
+// such Handle in LIFO order. This ensures that all programs will be Shutdown
+// even if the developer does not explicitly take care to do so for every
+// invocation.
+//
+// Pipes:
+//
+// StartWithOpts allows the caller to pass an io.Reader to the program
+// (StartOpts.Stdin) for it to read from, rather than creating a new pipe
+// internally. This makes it possible to connect the output of one program to
+// the input of another directly.
+//
+// Command Line Arguments:
+//
+// The arguments passed in calls to Start are appended to any system required
+// ones (e.g. for propagating test timeouts, verbosity etc) and the child
+// process will call the program with the result of flag.Args(). In this way the
+// caller can provide flags used by libraries in the child process as well as
+// those specific to the program and the program will only receive the args
+// specific to it. The usual "--" convention can be used to override this
+// default behaviour.
+//
+// Caveats:
+//
+// Handle.Shutdown assumes that the child program/process will terminate when
+// its stdin stream is closed. This assumption is unlikely to be valid for
+// 'external' programs (e.g. /bin/cp) and in these cases Kill or some other
+// application specific mechanism will need to be used.
+package modules
+
+import (
+	"errors"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"sync"
+	"syscall"
+	"time"
+
+	"v.io/x/lib/envvar"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/logging"
+	"v.io/v23/security"
+
+	"v.io/x/ref"
+	"v.io/x/ref/internal/logger"
+	"v.io/x/ref/lib/exec"
+	"v.io/x/ref/services/agent"
+	"v.io/x/ref/services/agent/agentlib"
+	"v.io/x/ref/services/agent/keymgr"
+	"v.io/x/ref/test/expect"
+)
+
+const (
+	shellBlessingExtension = "test-shell"
+
+	defaultStartTimeout    = time.Minute
+	defaultShutdownTimeout = time.Minute
+	defaultExpectTimeout   = time.Minute
+)
+
+var defaultStartOpts = StartOpts{
+	StartTimeout:    defaultStartTimeout,
+	ShutdownTimeout: defaultShutdownTimeout,
+	ExpectTimeout:   defaultExpectTimeout,
+	ExecProtocol:    true,
+}
+
+// Shell represents the context within which programs are run.
+type Shell struct {
+	mu               sync.Mutex
+	env              map[string]string
+	handles          map[*execHandle]struct{}
+	lifoHandles      []*execHandle
+	defaultStartOpts StartOpts
+	// tmpCredDir is the temporary directory created by this
+	// shell. This must be removed when the shell is cleaned up.
+	tempCredDir      string
+	config           exec.Config
+	principal        security.Principal
+	agent            agent.KeyManager
+	ctx              *context.T
+	logger           logging.Logger
+	sessionVerbosity bool
+	cancelCtx        func()
+}
+
+// NewShell creates a new instance of Shell.
+//
+// If ctx is non-nil, the shell will manage Principals for child processes.
+//
+// If p is non-nil, any child process created has its principal blessed
+// by the default blessings of 'p', Else any child process created has its
+// principal blessed by the default blessings of ctx's principal.
+//
+// If verbosity is true additional debugging info will be displayed,
+// in particular by the Shutdown.
+//
+// If t is non-nil, then the expect Session created for every invocation
+// will be constructed with that value of t unless overridden by a
+// StartOpts provided to that invocation. Providing a non-nil value of
+// t enables expect.Session to call t.Error, Errorf and Log.
+func NewShell(ctx *context.T, p security.Principal, verbosity bool, t expect.Testing) (*Shell, error) {
+	sh := &Shell{
+		env:              make(map[string]string),
+		handles:          make(map[*execHandle]struct{}),
+		config:           exec.NewConfig(),
+		defaultStartOpts: defaultStartOpts,
+		sessionVerbosity: verbosity,
+	}
+	sh.defaultStartOpts = sh.defaultStartOpts.WithSessions(t, time.Minute)
+	if ctx == nil {
+		sh.logger = logger.Global()
+		return sh, nil
+	}
+	var err error
+	ctx, sh.cancelCtx = context.WithCancel(ctx)
+	if ctx, err = v23.WithNewStreamManager(ctx); err != nil {
+		return nil, err
+	}
+	sh.ctx = ctx
+	sh.logger = ctx
+
+	if sh.tempCredDir, err = ioutil.TempDir("", "shell_credentials-"); err != nil {
+		return nil, err
+	}
+	if sh.agent, err = keymgr.NewLocalAgent(sh.tempCredDir, nil); err != nil {
+		return nil, err
+	}
+	sh.principal = p
+	if sh.principal == nil {
+		sh.principal = v23.GetPrincipal(ctx)
+	}
+	return sh, nil
+}
+
+// DefaultStartOpts returns the current StartOpts stored with the Shell.
+func (sh *Shell) DefaultStartOpts() StartOpts {
+	return sh.defaultStartOpts
+}
+
+// SetDefaultStartOpts sets the default StartOpts stored with the Shell.
+func (sh *Shell) SetDefaultStartOpts(opts StartOpts) {
+	sh.defaultStartOpts = opts
+}
+
+// CustomCredentials encapsulates a Principal which can be shared with
+// one or more processes run by a Shell.
+type CustomCredentials struct {
+	p    security.Principal
+	path string
+}
+
+// Principal returns the Principal.
+func (c *CustomCredentials) Principal() security.Principal {
+	return c.p
+}
+
+// Path returns the path to the credential's agent.
+// Typically you would pass this to a child process in EnvAgentPath.
+func (c *CustomCredentials) Path() string {
+	return c.path
+}
+
+func dup(conn *os.File) (int, error) {
+	syscall.ForkLock.RLock()
+	fd, err := syscall.Dup(int(conn.Fd()))
+	if err != nil {
+		syscall.ForkLock.RUnlock()
+		return -1, err
+	}
+	syscall.CloseOnExec(fd)
+	syscall.ForkLock.RUnlock()
+	return fd, nil
+}
+
+// NewCustomCredentials creates a new Principal for StartWithOpts..
+// Returns nil if the shell is not managing principals.
+func (sh *Shell) NewCustomCredentials() (cred *CustomCredentials, err error) {
+	// Create child principal.
+	if sh.ctx == nil {
+		return nil, nil
+	}
+	id, err := sh.agent.NewPrincipal(true)
+	if err != nil {
+		return nil, err
+	}
+	dir, err := ioutil.TempDir(sh.tempCredDir, "agent")
+	if err != nil {
+		return nil, err
+	}
+	path := filepath.Join(dir, "sock")
+	if err := sh.agent.ServePrincipal(id, path); err != nil {
+		return nil, err
+	}
+	p, err := agentlib.NewAgentPrincipalX(path)
+	if err != nil {
+		return nil, err
+	}
+	return &CustomCredentials{p, path}, nil
+}
+
+// NewChildCredentials creates a new principal, served via the security agent
+// whose blessings are an extension of this shell's principal (with the
+// provided caveats).
+//
+// All processes started by this shell will recognize the credentials created
+// by this call.
+//
+// Returns nil if the shell is not managing principals.
+//
+// Since the Shell type is intended for tests, it is not required to provide
+// caveats.  In production scenarios though, one must think long and hard
+// before blessing anothing principal without any caveats.
+func (sh *Shell) NewChildCredentials(extension string, caveats ...security.Caveat) (*CustomCredentials, error) {
+	creds, err := sh.NewCustomCredentials()
+	if creds == nil {
+		return nil, err
+	}
+	return sh.AddToChildCredentials(creds, extension, caveats...)
+}
+
+func (sh *Shell) AddToChildCredentials(creds *CustomCredentials, extension string, caveats ...security.Caveat) (*CustomCredentials, error) {
+	parent := sh.principal
+	child := creds.p
+	if len(caveats) == 0 {
+		caveats = []security.Caveat{security.UnconstrainedUse()}
+	}
+
+	// Bless the child principal with blessings derived from the default blessings
+	// of shell's principal.
+	blessings, err := parent.Bless(child.PublicKey(), parent.BlessingStore().Default(), extension, caveats[0], caveats[1:]...)
+	if err != nil {
+		return nil, err
+	}
+
+	union, err := security.UnionOfBlessings(child.BlessingStore().Default(), blessings)
+	if err != nil {
+		return nil, err
+	}
+
+	if err := child.BlessingStore().SetDefault(union); err != nil {
+		return nil, err
+	}
+	if _, err := child.BlessingStore().Set(union, security.AllPrincipals); err != nil {
+		return nil, err
+	}
+	if err := child.AddToRoots(blessings); err != nil {
+		return nil, err
+	}
+
+	return creds, nil
+}
+
+// Env represents the environment for Main functions.
+type Env struct {
+	Stdin  io.Reader
+	Stdout io.Writer
+	Stderr io.Writer
+	Vars   map[string]string // Environment variables
+}
+
+// EnvFromOS returns a new Env based on the underlying OS.
+func EnvFromOS() *Env {
+	return &Env{
+		Stdin:  os.Stdin,
+		Stdout: os.Stdout,
+		Stderr: os.Stderr,
+		Vars:   envvar.SliceToMap(os.Environ()),
+	}
+}
+
+// Main describes the entry-point function type for registered programs.
+type Main func(env *Env, args ...string) error
+
+// Start is shorthand for StartWithOpts(sh.DefaultStartOpts(), ...)
+func (sh *Shell) Start(env []string, prog Program, args ...string) (Handle, error) {
+	return sh.StartWithOpts(sh.DefaultStartOpts(), env, prog, args...)
+}
+
+// StartOpts represents the options that can be passed to the
+// StartWithOpts method.
+type StartOpts struct {
+	// Error is set when creating/intializing instances of StartOpts
+	// via one of the factory methods and returned when StartWithOpts
+	// is called. This allows usage of of the form:
+	//
+	// err := sh.StartWithOpts(sh.DefaultStartOpts()...)
+	//
+	// as opposed to:
+	//
+	// opts, err := sh.DefaultStartOpts(....)
+	// if err != nil {
+	//     panic(...)
+	// }
+	// sh.StartWithOpts(opts, ....)
+	Error error
+
+	// Stdin, if non-nil, will be used as the stdin for the child process.
+	// If this option is set, then the Stdin() method on the returned Handle
+	// will return nil. The client of this API maintains ownership of stdin
+	// and must close it, i.e. the shell will not do so.
+	Stdin io.Reader
+	// Credentials, if non-nil, will be used as the credentials for the
+	// child process. If the creds are nil or the shell is not managing
+	// principals, the credentials are ignored.
+	Credentials *CustomCredentials
+	// ExecProtocol indicates whether the child process is expected to
+	// implement the v.io/x.ref/lib/exec parent/child protocol.
+	// It should be set to false when running non-vanadium programs
+	// (e.g. /bin/cp).
+	ExecProtocol bool
+	// External indicates if the program is an external process rather than
+	// a Main function.
+	External bool
+	// StartTimeout specifies the amount of time to wait for the
+	// child process to signal its correct intialization for Vanadium
+	// processes that implement the exec parent/child protocol. It has no
+	// effect if External is set to true.
+	StartTimeout time.Duration
+	// ShutdownTimeout specifics the amount of time to wait for the child
+	// process to exit when the Shutdown method is called on that
+	// child's handle.
+	ShutdownTimeout time.Duration
+	// ExpectTesting is used when creating an instance of expect.Session
+	// to embed in Handle.
+	ExpectTesting expect.Testing
+	// ExpectTimeout is the timeout to use with expect.Session.
+	ExpectTimeout time.Duration
+}
+
+// DefaultStartOpts returns an instance of Startops with the current default
+// values. The defaults have values for timeouts, no credentials
+// (StartWithOpts will then create credentials each time it is called),
+// and with ExecProtocol set to true.
+// This is expected to be the common use case.
+func DefaultStartOpts() StartOpts {
+	return defaultStartOpts
+}
+
+// WithCustomCredentials returns an instance of StartOpts with the specified
+// credentials.
+//
+// All other options are set to the current defaults.
+func (opts StartOpts) WithCustomCredentials(creds *CustomCredentials) StartOpts {
+	opts.Credentials = creds
+	return opts
+}
+
+// WithSessions returns a copy of opts with the specified expect.Testing and
+// associated timeout.
+func (opts StartOpts) WithSessions(t expect.Testing, timeout time.Duration) StartOpts {
+	opts.ExpectTesting = t
+	opts.ExpectTimeout = timeout
+	return opts
+}
+
+// WithStdin returns a copy of opts with the specified Stdin io.Reader.
+func (opts StartOpts) WithStdin(stdin io.Reader) StartOpts {
+	opts.Stdin = stdin
+	return opts
+}
+
+// NoExecProgram returns a copy of opts with the External option
+// enabled and ExecProtocol disabled.
+func (opts StartOpts) NoExecProgram() StartOpts {
+	opts.External = true
+	opts.ExecProtocol = false
+	return opts
+}
+
+// ExternalProgram returns a copy of StartOpts with the
+// External option enabled.
+func (opts StartOpts) ExternalProgram() StartOpts {
+	opts.External = true
+	return opts
+}
+
+var (
+	ErrNotRegistered        = errors.New("program not registered")
+	ErrNoExecAndCustomCreds = errors.New("ExecProtocol set to false but this invocation is attempting to use custome credentials")
+)
+
+// StartWithOpts starts the specified program according to the supplied
+// StartOpts and returns a Handle which can be used for interacting with
+// that program.
+//
+// The environment variables for the program are set by merging variables
+// from the OS environment, those in this Shell and those provided as a
+// parameter to it. In general, it prefers values from its parameter over
+// those from the Shell, over those from the OS. However, the VeyronCredentials
+// and agent FdEnvVar variables will never use the value from the Shell or OS.
+//
+// If the shell is managing principals, the program is configured to
+// connect to the shell's agent. Custom credentials may be specified
+// via StartOpts. If the shell is not managing principals, set
+// the VeyronCredentials environment variable in the 'env' parameter.
+//
+// The Shell tracks all of the Handles that it creates so that it can shut
+// them down when asked to. The returned Handle may be non-nil even when an
+// error is returned, in which case it may be used to retrieve any output
+// from the failed program.
+//
+// StartWithOpts will return a valid handle for errors that occur during the
+// child processes startup process. It is thus possible to call Shutdown
+// to obtain the error output. Handle will be nil if the error is due to
+// some other reason, such as failure to create pipes/files before starting
+// the child process. A common use will therefore be:
+//
+//    h, err := sh.Start(env, "/bin/echo", "hello")
+//    if err != nil {
+//        if h != nil {
+//            h.Shutdown(nil,os.Stderr)
+//        }
+//        t.Fatal(err)
+//    }
+func (sh *Shell) StartWithOpts(opts StartOpts, env []string, prog Program, args ...string) (Handle, error) {
+	var err error
+	if opts.Error != nil {
+		return nil, opts.Error
+	}
+
+	var info *programInfo
+	if opts.External {
+		info = registry.getExternalProgram(prog)
+	} else if info = registry.getProgram(prog); info == nil {
+		return nil, ErrNotRegistered
+	}
+
+	if !opts.ExecProtocol && opts.Credentials != nil {
+		return nil, ErrNoExecAndCustomCreds
+	}
+
+	if sh.ctx != nil && opts.ExecProtocol && opts.Credentials == nil {
+		opts.Credentials, err = sh.NewChildCredentials("child")
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	var agentPath string
+	if opts.Credentials != nil {
+		agentPath = opts.Credentials.Path()
+	}
+
+	handle := info.factory()
+	h, err := handle.start(sh, agentPath, &opts, sh.setupProgramEnv(env), sh.expand(args))
+	if err != nil {
+		return h, err
+	}
+	sh.mu.Lock()
+	sh.handles[h] = struct{}{}
+	sh.lifoHandles = append(sh.lifoHandles, h)
+	sh.mu.Unlock()
+	return h, nil
+}
+
+// ProgramEnvelope returns the command line and environment that would be used
+// for running the subprocess if it were started with the specifed arguments.
+func (sh *Shell) ProgramEnvelope(env []string, prog Program, args ...string) ([]string, []string) {
+	info := registry.getProgram(prog)
+	if info == nil {
+		return []string{}, []string{}
+	}
+	return info.factory().envelope(sh, sh.setupProgramEnv(env), sh.expand(args))
+}
+
+// Forget tells the Shell to stop tracking the supplied Handle. This is
+// generally used when the application wants to control the order that
+// programs are shutdown in.
+func (sh *Shell) Forget(h Handle) {
+	sh.mu.Lock()
+	if handle, ok := h.(*execHandle); ok {
+		delete(sh.handles, handle)
+	}
+	sh.mu.Unlock()
+}
+
+func (sh *Shell) expand(args []string) []string {
+	exp := []string{}
+	for _, a := range args {
+		if len(a) > 0 && a[0] == '$' {
+			if v, present := sh.env[a[1:]]; present {
+				exp = append(exp, v)
+				continue
+			}
+		}
+		exp = append(exp, a)
+	}
+	return exp
+}
+
+// GetVar returns the variable associated with the specified key
+// and an indication of whether it is defined or not.
+func (sh *Shell) GetVar(key string) (string, bool) {
+	sh.mu.Lock()
+	v, present := sh.env[key]
+	sh.mu.Unlock()
+	return v, present
+}
+
+// SetVar sets the value to be associated with key.
+func (sh *Shell) SetVar(key, value string) {
+	sh.mu.Lock()
+	// TODO(cnicolaou): expand value
+	sh.env[key] = value
+	sh.mu.Unlock()
+}
+
+// ClearVar removes the speficied variable from the Shell's environment
+func (sh *Shell) ClearVar(key string) {
+	sh.mu.Lock()
+	delete(sh.env, key)
+	sh.mu.Unlock()
+}
+
+// GetConfigKey returns the value associated with the specified key in
+// the Shell's config and an indication of whether it is defined or
+// not.
+func (sh *Shell) GetConfigKey(key string) (string, bool) {
+	v, err := sh.config.Get(key)
+	return v, err == nil
+}
+
+// SetConfigKey sets the value of the specified key in the Shell's
+// config.
+func (sh *Shell) SetConfigKey(key, value string) {
+	sh.config.Set(key, value)
+}
+
+// ClearConfigKey removes the speficied key from the Shell's config.
+func (sh *Shell) ClearConfigKey(key string) {
+	sh.config.Clear(key)
+}
+
+// Env returns the entire set of environment variables associated with this
+// Shell as a string slice.
+func (sh *Shell) Env() []string {
+	sh.mu.Lock()
+	vars := envvar.MapToSlice(sh.env)
+	sh.mu.Unlock()
+	return vars
+}
+
+// Cleanup calls Shutdown on all of the Handles currently being tracked
+// by the Shell and writes to stdout and stderr as per the Shutdown
+// method in the Handle interface. Cleanup returns the error from the
+// last Shutdown that returned a non-nil error. The order that the
+// Shutdown routines are executed is not defined.
+func (sh *Shell) Cleanup(stdout, stderr io.Writer) error {
+	sh.mu.Lock()
+	verbose := sh.sessionVerbosity
+	sh.mu.Unlock()
+
+	writeMsg := func(format string, args ...interface{}) {
+		if !verbose {
+			return
+		}
+		if stderr != nil {
+			fmt.Fprintf(stderr, format, args...)
+		}
+	}
+
+	writeMsg("---- Shell Cleanup ----\n")
+	defer writeMsg("---- Shell Cleanup Complete ----\n")
+
+	sh.mu.Lock()
+	handles := make([]*execHandle, 0, len(sh.lifoHandles))
+	for _, h := range sh.lifoHandles {
+		if _, present := sh.handles[h]; present {
+			handles = append(handles, h)
+		}
+	}
+	sh.handles = make(map[*execHandle]struct{})
+	sh.lifoHandles = nil
+	sh.mu.Unlock()
+	var err error
+	for i := len(handles); i > 0; i-- {
+		h := handles[i-1]
+		writeMsg("---- Cleanup calling Shutdown on program %q\n", h.desc)
+		cerr := h.Shutdown(stdout, stderr)
+		if cerr != nil {
+			err = cerr
+		}
+		fn := func() string {
+			if cerr == nil {
+				return ": done"
+			} else {
+				return ": error: " + err.Error()
+			}
+		}
+		writeMsg("---- Shutdown on program %q%s\n", h.desc, fn())
+	}
+
+	if sh.cancelCtx != nil {
+		writeMsg("---- Cleanup calling cancelCtx ----\n")
+		// Note(ribrdb, caprita): This will shutdown the agents.  If there
+		// were errors shutting down it is possible there could be child
+		// processes still running, and stopping the agent may cause
+		// additional failures.
+		sh.cancelCtx()
+	}
+	os.RemoveAll(sh.tempCredDir)
+	return err
+}
+
+func (sh *Shell) setupProgramEnv(env []string) []string {
+	osmap := envvar.SliceToMap(os.Environ())
+	evmap := envvar.SliceToMap(env)
+
+	sh.mu.Lock()
+	defer sh.mu.Unlock()
+	m1 := envvar.MergeMaps(osmap, sh.env)
+	// Clear any VeyronCredentials directory in m1 as we never
+	// want the child to directly use the directory specified
+	// by the shell's VeyronCredentials.
+	delete(m1, ref.EnvCredentials)
+	delete(m1, ref.EnvAgentEndpoint)
+	delete(m1, ref.EnvAgentPath)
+
+	m2 := envvar.MergeMaps(m1, evmap)
+	return envvar.MapToSlice(m2)
+}
+
+// ExpectSession is a subset of v.io/x/ref/tests/expect.Session's methods
+// that are embedded in Handle.
+type ExpectSession interface {
+	Expect(expected string)
+	ExpectEOF() error
+	ExpectRE(pattern string, n int) [][]string
+	ExpectSetEventuallyRE(expected ...string) [][]string
+	ExpectSetRE(expected ...string) [][]string
+	ExpectVar(name string) string
+	Expectf(format string, args ...interface{})
+	ReadAll() (string, error)
+	ReadLine() string
+	SetVerbosity(bool)
+	Failed() bool
+	Error() error
+}
+
+// Handle represents a running program.
+type Handle interface {
+	ExpectSession
+
+	// Stdout returns a reader to the running program's stdout stream.
+	Stdout() io.Reader
+
+	// Stderr returns a reader to the running program's stderr
+	// stream.
+	Stderr() io.Reader
+
+	// Stdin returns a writer to the running program's stdin. The
+	// convention is for programs to wait for stdin to be closed before
+	// they exit, thus the caller should close stdin when it wants the
+	// program to exit cleanly.
+	Stdin() io.Writer
+
+	// CloseStdin closes stdin in a manner that avoids a data race
+	// between any current readers on it.
+	CloseStdin()
+
+	// Shutdown closes the Stdin for the program and then reads output
+	// from the program's stdout until it encounters EOF, waits for
+	// the program to complete and then reads all of its stderr output.
+	// The stdout and stderr contents are written to the corresponding
+	// io.Writers if they are non-nil, otherwise the content is discarded.
+	Shutdown(stdout, stderr io.Writer) error
+
+	// Pid returns the pid of the process running the program
+	Pid() int
+}
diff --git a/test/modules/util.go b/test/modules/util.go
new file mode 100644
index 0000000..3469fbd
--- /dev/null
+++ b/test/modules/util.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.
+
+package modules
+
+import (
+	"fmt"
+	"hash/adler32"
+	"io"
+	"io/ioutil"
+	"os"
+
+	"v.io/v23/security"
+
+	"v.io/x/ref/internal/logger"
+	vsecurity "v.io/x/ref/lib/security"
+)
+
+func newLogfile(prefix, name string) (*os.File, error) {
+	nameHash := adler32.Checksum([]byte(name))
+	f, err := ioutil.TempFile("", fmt.Sprintf("__modules__%s-%x-", prefix, nameHash))
+	if err != nil {
+		return nil, err
+	}
+	return f, nil
+}
+
+func outputFromFile(f *os.File, out io.Writer) {
+	f.Close()
+	fName := f.Name()
+	defer os.Remove(fName)
+	if out == nil {
+		return
+	}
+	var err error
+	if f, err = os.Open(fName); err != nil {
+		logger.Global().VI(1).Infof("failed to open %q: %s\n", fName, err)
+		return
+	}
+	io.Copy(out, f)
+	f.Close()
+}
+
+func principalFromDir(dir string) (security.Principal, error) {
+	p, err := vsecurity.LoadPersistentPrincipal(dir, nil)
+	if err == nil {
+		return p, nil
+	}
+	if !os.IsNotExist(err) {
+		return nil, err
+	}
+	p, err = vsecurity.CreatePersistentPrincipal(dir, nil)
+	if err != nil {
+		return nil, err
+	}
+	if err := vsecurity.InitDefaultBlessings(p, shellBlessingExtension); err != nil {
+		return nil, err
+	}
+	return p, nil
+}
diff --git a/test/testutil/dispatcher.go b/test/testutil/dispatcher.go
new file mode 100644
index 0000000..83f6e2f
--- /dev/null
+++ b/test/testutil/dispatcher.go
@@ -0,0 +1,31 @@
+// 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 (
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/verror"
+)
+
+// LeafDispatcher returns a dispatcher for a single object obj, using
+// ReflectInvokerOrDie to invoke methods. Lookup only succeeds on the empty
+// suffix.  The provided auth is returned for successful lookups.
+func LeafDispatcher(obj interface{}, auth security.Authorizer) rpc.Dispatcher {
+	return &leafDispatcher{rpc.ReflectInvokerOrDie(obj), auth}
+}
+
+type leafDispatcher struct {
+	invoker rpc.Invoker
+	auth    security.Authorizer
+}
+
+func (d leafDispatcher) Lookup(_ *context.T, suffix string) (interface{}, security.Authorizer, error) {
+	if suffix != "" {
+		return nil, nil, verror.New(verror.ErrUnknownSuffix, nil, suffix)
+	}
+	return d.invoker, d.auth, nil
+}
diff --git a/test/testutil/doc.go b/test/testutil/doc.go
new file mode 100644
index 0000000..3088a2f
--- /dev/null
+++ b/test/testutil/doc.go
@@ -0,0 +1,6 @@
+// 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 implements utilities for unit and integration tests.
+package testutil
diff --git a/test/testutil/glob.go b/test/testutil/glob.go
new file mode 100644
index 0000000..156b578
--- /dev/null
+++ b/test/testutil/glob.go
@@ -0,0 +1,49 @@
+// 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 (
+	"io"
+	"sort"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+)
+
+// GlobName calls __Glob on the given object with the given pattern and returns
+// a sorted list of matching object names, or an error.
+func GlobName(ctx *context.T, name, pattern string) ([]string, []naming.GlobError, error) {
+	client := v23.GetClient(ctx)
+	call, err := client.StartCall(ctx, name, rpc.GlobMethod, []interface{}{pattern})
+	if err != nil {
+		return nil, nil, err
+	}
+	results := []string{}
+	globErrors := []naming.GlobError{}
+Loop:
+	for {
+		var gr naming.GlobReply
+		switch err := call.Recv(&gr); err {
+		case nil:
+			switch v := gr.(type) {
+			case naming.GlobReplyEntry:
+				results = append(results, v.Value.Name)
+			case naming.GlobReplyError:
+				globErrors = append(globErrors, v.Value)
+			}
+		case io.EOF:
+			break Loop
+		default:
+			return nil, nil, err
+		}
+	}
+	sort.Strings(results)
+	if err := call.Finish(); err != nil {
+		return nil, nil, err
+	}
+	return results, globErrors, nil
+}
diff --git a/test/testutil/rand.go b/test/testutil/rand.go
new file mode 100644
index 0000000..8e21640
--- /dev/null
+++ b/test/testutil/rand.go
@@ -0,0 +1,177 @@
+// 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 (
+	"fmt"
+	"math/rand"
+	"os"
+	"strconv"
+	"sync"
+	"time"
+)
+
+const (
+	SeedEnv = "V23_RNG_SEED"
+)
+
+// An instance of Random initialized by the InitRandomGenerator function.
+var (
+	Rand *Random
+	once sync.Once
+)
+
+const randPanicMsg = "It looks like the singleton random number generator has not been initialized, please call InitRandGenerator."
+
+// Random is a concurrent-access friendly source of randomness.
+type Random struct {
+	mu   sync.Mutex
+	rand *rand.Rand
+}
+
+// RandomInt returns a non-negative pseudo-random int.
+func (r *Random) RandomInt() int {
+	r.mu.Lock()
+	defer r.mu.Unlock()
+	return r.rand.Int()
+}
+
+// RandomIntn returns a non-negative pseudo-random int in the range [0, n).
+func (r *Random) RandomIntn(n int) int {
+	r.mu.Lock()
+	defer r.mu.Unlock()
+	return r.rand.Intn(n)
+}
+
+// RandomInt63 returns a non-negative 63-bit pseudo-random integer as an int64.
+func (r *Random) RandomInt63() int64 {
+	r.mu.Lock()
+	defer r.mu.Unlock()
+	return r.rand.Int63()
+}
+
+// RandomBytes generates the given number of random bytes.
+func (rand *Random) RandomBytes(size int) []byte {
+	buffer := make([]byte, size)
+	randomMutex.Lock()
+	defer randomMutex.Unlock()
+	// Generate a 10MB of random bytes since that is a value commonly
+	// used in the tests.
+	if len(random) == 0 {
+		random = generateRandomBytes(rand, 10<<20)
+	}
+	if size > len(random) {
+		extra := generateRandomBytes(rand, size-len(random))
+		random = append(random, extra...)
+	}
+	start := rand.RandomIntn(len(random) - size + 1)
+	copy(buffer, random[start:start+size])
+	return buffer
+}
+
+type loggingFunc func(format string, args ...interface{})
+
+// NewRandGenerator creates a new pseudo-random number generator; the seed may
+// be supplied by V23_RNG_SEED to allow for reproducing a previous sequence, and
+// is printed using the supplied logging function.
+func NewRandGenerator(logger loggingFunc) *Random {
+	seed := time.Now().UnixNano()
+	seedString := os.Getenv(SeedEnv)
+	if seedString != "" {
+		var err error
+		base, bitSize := 0, 64
+		seed, err = strconv.ParseInt(seedString, base, bitSize)
+		if err != nil {
+			panic(fmt.Sprintf("ParseInt(%v, %v, %v) failed: %v", seedString, base, bitSize, err))
+		}
+	}
+	logger("Seeded pseudo-random number generator with %v", seed)
+	return &Random{rand: rand.New(rand.NewSource(seed))}
+}
+
+// TODO(caprita): Consider deprecating InitRandGenerator in favor of using
+// NewRandGenerator directly.  There are several drawbacks to using the global
+// singleton Random object:
+//
+//   - tests that do not call InitRandGenerator themselves could depend on
+//   InitRandGenerator having been called by other tests in the same package and
+//   stop working when run standalone with test --run
+//
+//   - conversely, a test case may call InitRandGenerator without actually
+//   needing to; it's hard to figure out if some library called by a test
+//   actually uses the Random object or not
+//
+//   - when several test cases share the same Random object, there is
+//   interference in the stream of random numbers generated for each test case
+//   if run in parallel
+//
+// All these issues can be trivially addressed if the Random object is created
+// and plumbed through the call stack explicitly.
+
+// InitRandGenerator creates an instance of Random in the public variable Rand
+// and prints out the seed use when creating the number number generator using
+// the supplied logging function.
+func InitRandGenerator(logger loggingFunc) {
+	once.Do(func() {
+		Rand = NewRandGenerator(logger)
+	})
+}
+
+var (
+	random      []byte
+	randomMutex sync.Mutex
+)
+
+func generateRandomBytes(rand *Random, size int) []byte {
+	buffer := make([]byte, size)
+	offset := 0
+	for {
+		bits := int64(rand.RandomInt63())
+		for i := 0; i < 8; i++ {
+			buffer[offset] = byte(bits & 0xff)
+			size--
+			if size == 0 {
+				return buffer
+			}
+			offset++
+			bits >>= 8
+		}
+	}
+}
+
+// RandomInt returns a non-negative pseudo-random int using the public variable Rand.
+func RandomInt() int {
+	if Rand == nil {
+		panic(randPanicMsg)
+	}
+	return Rand.RandomInt()
+}
+
+// RandomIntn returns a non-negative pseudo-random int in the range [0, n) using
+// the public variable Rand.
+func RandomIntn(n int) int {
+	if Rand == nil {
+		panic(randPanicMsg)
+	}
+	return Rand.RandomIntn(n)
+}
+
+// RandomInt63 returns a non-negative 63-bit pseudo-random integer as an int64
+// using the public variable Rand.
+func RandomInt63() int64 {
+	if Rand == nil {
+		panic(randPanicMsg)
+	}
+	return Rand.RandomInt63()
+}
+
+// RandomBytes generates the given number of random bytes using
+// the public variable Rand.
+func RandomBytes(size int) []byte {
+	if Rand == nil {
+		panic(randPanicMsg)
+	}
+	return Rand.RandomBytes(size)
+}
diff --git a/test/testutil/security.go b/test/testutil/security.go
new file mode 100644
index 0000000..2c673dd
--- /dev/null
+++ b/test/testutil/security.go
@@ -0,0 +1,116 @@
+// 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 (
+	"v.io/v23/security"
+	vsecurity "v.io/x/ref/lib/security"
+)
+
+// NewPrincipal creates a new security.Principal.
+//
+// It is a convenience wrapper over utility functions available in the
+// v.io/x/ref/lib/security package.
+//
+// If the set of blessingNames provided is non-empty, it creates self-signed
+// blessings for each of those names and marks all of them as the default and
+// shareable with all peers on the principal's blessing store.
+//
+// Errors are truly rare events and since this is a utility intended only for
+// unittests, NewPrincipal will panic on any errors.
+func NewPrincipal(blessingNames ...string) security.Principal {
+	p, err := vsecurity.NewPrincipal()
+	if err != nil {
+		panic(err)
+	}
+	var def security.Blessings
+	for _, n := range blessingNames {
+		b, err := p.BlessSelf(n)
+		if err != nil {
+			panic(err)
+		}
+		if def, err = security.UnionOfBlessings(def, b); err != nil {
+			panic(err)
+		}
+	}
+	if !def.IsZero() {
+		if err := vsecurity.SetDefaultBlessings(p, def); err != nil {
+			panic(err)
+		}
+	}
+	return p
+}
+
+// IDProvider is a convenience type to act as an "identity provider", i.e., it
+// provides other principals with a blessing whose root certificate is signed
+// by the IDProvider.
+//
+// Typical usage:
+//
+//    p1, p2 := NewPrincipal(), NewPrincipal()
+//    idp := NewIDProvider("xyz")
+//    idp.Bless(p1, "alpha")
+//    idp.Bless(p2, "beta")
+//
+// Now, p1 and p2 will present "xyz/alpha" and "xyz/beta" as their blessing
+// names and when communicating with each other, p1 and p2 will recognize these
+// names as they both trust the root certificate (that of the IDProvider)
+type IDProvider struct {
+	p security.Principal
+	b security.Blessings
+}
+
+// NewIDProvider creates an IDProvider that will bless other principals with
+// extensions of 'name'.
+//
+// NewIDProvider panics on any errors.
+func NewIDProvider(name string) *IDProvider {
+	p, err := vsecurity.NewPrincipal()
+	if err != nil {
+		panic(err)
+	}
+	b, err := p.BlessSelf(name)
+	if err != nil {
+		panic(err)
+	}
+	return &IDProvider{p, b}
+}
+
+// IDProviderFromPrincipal creates and IDProvider for the given principal.  It
+// will bless other principals with extensions of its default blessing.
+func IDProviderFromPrincipal(p security.Principal) *IDProvider {
+	return &IDProvider{p, p.BlessingStore().Default()}
+}
+
+// Bless sets up the provided principal to use blessings from idp as its
+// default. It is shorthand for:
+//    b, _ := idp.NewBlessings(who, extension, caveats...)
+//    who.BlessingStore().SetDefault(b)
+//    who.BlessingStore().Set(b, security.AllPrincipals)
+//    who.AddToRoots(b)
+func (idp *IDProvider) Bless(who security.Principal, extension string, caveats ...security.Caveat) error {
+	b, err := idp.NewBlessings(who, extension, caveats...)
+	if err != nil {
+		return err
+	}
+	return vsecurity.SetDefaultBlessings(who, b)
+}
+
+// NewBlessings returns Blessings that extend the identity provider's blessing
+// with 'extension' and binds it to 'p.PublicKey'.
+//
+// Unlike Bless, it does not modify p's BlessingStore or set of recognized root
+// certificates.
+func (idp *IDProvider) NewBlessings(p security.Principal, extension string, caveats ...security.Caveat) (security.Blessings, error) {
+	if len(caveats) == 0 {
+		caveats = append(caveats, security.UnconstrainedUse())
+	}
+	return idp.p.Bless(p.PublicKey(), idp.b, extension, caveats[0], caveats[1:]...)
+}
+
+// PublicKey returns the public key of the identity provider.
+func (idp *IDProvider) PublicKey() security.PublicKey {
+	return idp.p.PublicKey()
+}
diff --git a/test/testutil/security_test.go b/test/testutil/security_test.go
new file mode 100644
index 0000000..b13eb6b
--- /dev/null
+++ b/test/testutil/security_test.go
@@ -0,0 +1,36 @@
+// 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_test
+
+import (
+	"reflect"
+	"testing"
+
+	"v.io/x/ref/test/testutil"
+)
+
+func TestIDProvider(t *testing.T) {
+	idp := testutil.NewIDProvider("foo")
+	p := testutil.NewPrincipal()
+	if err := idp.Bless(p, "bar"); err != nil {
+		t.Fatal(err)
+	}
+	if err := p.Roots().Recognized(idp.PublicKey(), "foo"); err != nil {
+		t.Error(err)
+	}
+	if err := p.Roots().Recognized(idp.PublicKey(), "foo/bar"); err != nil {
+		t.Error(err)
+	}
+	def := p.BlessingStore().Default()
+	peers := p.BlessingStore().ForPeer("anyone_else")
+	if def.IsZero() {
+		t.Errorf("BlessingStore should have a default blessing")
+	}
+	if !reflect.DeepEqual(peers, def) {
+		t.Errorf("ForPeer(...) returned %v, want %v", peers, def)
+	}
+	// TODO(ashankar): Implement a security.Call and test the string
+	// values as well.
+}
diff --git a/test/testutil/testdata/rand_test.go b/test/testutil/testdata/rand_test.go
new file mode 100644
index 0000000..7e403b7
--- /dev/null
+++ b/test/testutil/testdata/rand_test.go
@@ -0,0 +1,17 @@
+// 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_test
+
+import (
+	"testing"
+
+	"v.io/x/ref/test/testutil"
+)
+
+func TestRandSeed(t *testing.T) {
+	testutil.InitRandGenerator(t.Logf)
+	t.Logf("rand: %d", testutil.RandomInt())
+	t.FailNow()
+}
diff --git a/test/testutil/util.go b/test/testutil/util.go
new file mode 100644
index 0000000..05492e2
--- /dev/null
+++ b/test/testutil/util.go
@@ -0,0 +1,23 @@
+// 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 (
+	"fmt"
+	"path/filepath"
+	"runtime"
+)
+
+// FormatLogLine will prepend the file and line number of the caller
+// at the specificied depth (as per runtime.Caller) to the supplied
+// format and args and return a formatted string. It is useful when
+// implementing functions that factor out error handling and reporting
+// in tests.
+func FormatLogLine(depth int, format string, args ...interface{}) string {
+	_, file, line, _ := runtime.Caller(depth)
+	nargs := []interface{}{filepath.Base(file), line}
+	nargs = append(nargs, args...)
+	return fmt.Sprintf("%s:%d: "+format, nargs...)
+}
diff --git a/test/testutil/util_test.go b/test/testutil/util_test.go
new file mode 100644
index 0000000..56f1ebc
--- /dev/null
+++ b/test/testutil/util_test.go
@@ -0,0 +1,65 @@
+// 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_test
+
+import (
+	"regexp"
+	"testing"
+
+	_ "v.io/x/ref/runtime/factories/generic"
+	"v.io/x/ref/test/testutil"
+	"v.io/x/ref/test/v23tests"
+)
+
+func TestFormatLogline(t *testing.T) {
+	line, want := testutil.FormatLogLine(2, "test"), "testing.go:.*"
+	if ok, err := regexp.MatchString(want, line); !ok || err != nil {
+		t.Errorf("got %v, want %v", line, want)
+	}
+}
+
+func panicHelper(ch chan string) {
+	defer func() {
+		if r := recover(); r != nil {
+			ch <- r.(string)
+		}
+	}()
+	testutil.RandomInt()
+}
+
+func TestPanic(t *testing.T) {
+	testutil.Rand = nil
+	ch := make(chan string)
+	go panicHelper(ch)
+	str := <-ch
+	if got, want := str, "It looks like the singleton random number generator has not been initialized, please call InitRandGenerator."; got != want {
+		t.Fatalf("got %v, want %v", got, want)
+	}
+}
+
+//go:generate v23 test generate .
+
+func V23TestRandSeed(i *v23tests.T) {
+	v23bin := i.BinaryFromPath("v23")
+	inv := v23bin.Start("go", "test", "./testdata")
+	inv.ExpectRE("FAIL: TestRandSeed.*", 1)
+	parts := inv.ExpectRE(`Seeded pseudo-random number generator with (\d+)`, -1)
+	if len(parts) != 1 || len(parts[0]) != 2 {
+		i.Fatalf("failed to match regexp")
+	}
+	seed := parts[0][1]
+	parts = inv.ExpectRE(`rand: (\d+)`, -1)
+	if len(parts) != 1 || len(parts[0]) != 2 {
+		i.Fatalf("failed to match regexp")
+	}
+	randInt := parts[0][1]
+
+	// Rerun the test, this time with the seed that we want to use.
+	v23bin = v23bin.WithEnv("V23_RNG_SEED=" + seed)
+	inv = v23bin.Start("go", "test", "./testdata")
+	inv.ExpectRE("FAIL: TestRandSeed.*", 1)
+	inv.ExpectRE("Seeded pseudo-random number generator with "+seed, -1)
+	inv.ExpectRE("rand: "+randInt, 1)
+}
diff --git a/test/testutil/v23_test.go b/test/testutil/v23_test.go
new file mode 100644
index 0000000..0463ee0
--- /dev/null
+++ b/test/testutil/v23_test.go
@@ -0,0 +1,30 @@
+// 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 testutil_test
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
+	"v.io/x/ref/test/v23tests"
+)
+
+func TestMain(m *testing.M) {
+	test.Init()
+	modules.DispatchAndExitIfChild()
+	cleanup := v23tests.UseSharedBinDir()
+	r := m.Run()
+	cleanup()
+	os.Exit(r)
+}
+
+func TestV23RandSeed(t *testing.T) {
+	v23tests.RunTest(t, V23TestRandSeed)
+}
diff --git a/test/testutil/vtest.go b/test/testutil/vtest.go
new file mode 100644
index 0000000..34230e9
--- /dev/null
+++ b/test/testutil/vtest.go
@@ -0,0 +1,16 @@
+// 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
+
+// CallAndRecover calls the function f and returns the result of recover().
+// This minimizes the scope of the deferred recover, to ensure f is actually the
+// function that paniced.
+func CallAndRecover(f func()) (result interface{}) {
+	defer func() {
+		result = recover()
+	}()
+	f()
+	return
+}
diff --git a/test/testutil/vtest_test.go b/test/testutil/vtest_test.go
new file mode 100644
index 0000000..516fe06
--- /dev/null
+++ b/test/testutil/vtest_test.go
@@ -0,0 +1,29 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package testutil_test
+
+import (
+	"testing"
+
+	"v.io/x/ref/test/testutil"
+)
+
+func TestCallAndRecover(t *testing.T) {
+	tests := []struct {
+		f      func()
+		expect interface{}
+	}{
+		{func() {}, nil},
+		{func() { panic(nil) }, nil},
+		{func() { panic(123) }, 123},
+		{func() { panic("abc") }, "abc"},
+	}
+	for _, test := range tests {
+		got := testutil.CallAndRecover(test.f)
+		if got != test.expect {
+			t.Errorf(`CallAndRecover got "%v", want "%v"`, got, test.expect)
+		}
+	}
+}
diff --git a/test/timekeeper/manual_time.go b/test/timekeeper/manual_time.go
new file mode 100644
index 0000000..afc5b36
--- /dev/null
+++ b/test/timekeeper/manual_time.go
@@ -0,0 +1,122 @@
+// 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 timekeeper implements simulated time against the
+// v.io/x/ref/lib/timekeeper.TimeKeeper interface.
+package timekeeper
+
+import (
+	"container/heap"
+	"sync"
+	"time"
+
+	"v.io/x/ref/lib/timekeeper"
+)
+
+// ManualTime is a time keeper that allows control over the advancement of time.
+type ManualTime interface {
+	timekeeper.TimeKeeper
+	// AdvanceTime advances the current time by d.
+	AdvanceTime(d time.Duration)
+	// Requests provides a channel where the requested delays for After and
+	// Sleep can be observed.
+	Requests() <-chan time.Duration
+}
+
+// item is a heap element: every request for After is added to the heap with its
+// wake-up time as key (where wake-up time is current time + requested delay
+// duration).  As current time advances, items are plucked from the heap and the
+// clients are notified.
+type item struct {
+	t  time.Time        // Wake-up time.
+	ch chan<- time.Time // Client notification channel.
+}
+
+type timeHeap []*item
+
+func (th timeHeap) Len() int { return len(th) }
+
+func (th timeHeap) Less(i, j int) bool {
+	return th[i].t.Before(th[j].t)
+}
+
+func (th timeHeap) Swap(i, j int) {
+	th[i], th[j] = th[j], th[i]
+}
+
+func (th *timeHeap) Push(x interface{}) {
+	item := x.(*item)
+	*th = append(*th, item)
+}
+
+func (th *timeHeap) Pop() interface{} {
+	old := *th
+	n := len(old)
+	item := old[n-1]
+	*th = old[0 : n-1]
+	return item
+}
+
+// manualTime implements TimeKeeper.
+type manualTime struct {
+	sync.Mutex
+	current  time.Time // The current time.
+	schedule timeHeap  // The heap of items still to be woken up.
+	requests chan time.Duration
+}
+
+// After implements TimeKeeper.After.
+func (mt *manualTime) After(d time.Duration) <-chan time.Time {
+	mt.Lock()
+	defer mt.Unlock()
+	ch := make(chan time.Time, 1)
+	if d <= 0 {
+		ch <- mt.current
+	} else {
+		heap.Push(&mt.schedule, &item{t: mt.current.Add(d), ch: ch})
+	}
+	mt.requests <- d
+	return ch
+}
+
+// Sleep implements TimeKeeper.Sleep.
+func (mt *manualTime) Sleep(d time.Duration) {
+	<-mt.After(d)
+}
+
+// AdvanceTime implements ManualTime.AdvanceTime.
+func (mt *manualTime) AdvanceTime(d time.Duration) {
+	mt.Lock()
+	defer mt.Unlock()
+	if d > 0 {
+		mt.current = mt.current.Add(d)
+	}
+	for {
+		if mt.schedule.Len() == 0 {
+			break
+		}
+		top := mt.schedule[0]
+		if top.t.After(mt.current) {
+			break
+		}
+		top.ch <- mt.current
+		heap.Pop(&mt.schedule)
+	}
+}
+
+// Requests implements ManualTime.Requests.
+func (mt *manualTime) Requests() <-chan time.Duration { return mt.requests }
+
+// NewManualTime constructs a new instance of ManualTime, with current time set
+// at 0.
+func NewManualTime() ManualTime {
+	mt := &manualTime{
+		schedule: make([]*item, 0),
+		// 1000 should be plenty to avoid blocking on adding items
+		// to the channel, but technically we can still end up blocked.
+		requests: make(chan time.Duration, 1000),
+	}
+	heap.Init(&mt.schedule)
+	return mt
+}
diff --git a/test/timekeeper/manual_time_test.go b/test/timekeeper/manual_time_test.go
new file mode 100644
index 0000000..abb3546
--- /dev/null
+++ b/test/timekeeper/manual_time_test.go
@@ -0,0 +1,121 @@
+// 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 timekeeper
+
+import (
+	"testing"
+	"time"
+)
+
+func checkNotReady(t *testing.T, ch <-chan time.Time) {
+	select {
+	case <-ch:
+		t.Errorf("Channel not supposed to be ready")
+	default:
+	}
+}
+
+func checkReady(t *testing.T, ch <-chan time.Time) {
+	select {
+	case <-ch:
+	default:
+		t.Errorf("Channel supposed to be ready")
+	}
+}
+
+func expectRequest(t *testing.T, ch <-chan time.Duration, expect time.Duration) {
+	select {
+	case got := <-ch:
+		if got != expect {
+			t.Errorf("Expected %v, got %v instead", expect, got)
+		}
+	default:
+		t.Errorf("Nothing received on channel")
+	}
+}
+
+func TestAfter(t *testing.T) {
+	mt := NewManualTime()
+	ch1 := mt.After(5 * time.Second)
+	ch2 := mt.After(3 * time.Second)
+	checkNotReady(t, ch1)
+	checkNotReady(t, ch2)
+	expectRequest(t, mt.Requests(), 5*time.Second)
+	expectRequest(t, mt.Requests(), 3*time.Second)
+
+	mt.AdvanceTime(time.Second)
+	checkNotReady(t, ch1)
+	checkNotReady(t, ch2)
+	ch3 := mt.After(2 * time.Second)
+	checkNotReady(t, ch3)
+	expectRequest(t, mt.Requests(), 2*time.Second)
+
+	mt.AdvanceTime(2 * time.Second)
+	checkNotReady(t, ch1)
+	checkReady(t, ch2)
+	checkReady(t, ch3)
+
+	mt.AdvanceTime(time.Second)
+	checkNotReady(t, ch1)
+	checkNotReady(t, ch2)
+	checkNotReady(t, ch3)
+
+	mt.AdvanceTime(time.Second)
+	checkReady(t, ch1)
+	checkNotReady(t, ch2)
+	checkNotReady(t, ch3)
+
+	ch4 := mt.After(0)
+	checkReady(t, ch4)
+	expectRequest(t, mt.Requests(), 0)
+}
+
+func TestSleep(t *testing.T) {
+	mt := NewManualTime()
+	c := make(chan time.Time, 1)
+	go func() {
+		mt.Sleep(5 * time.Second)
+		c <- time.Time{}
+		mt.Sleep(3 * time.Second)
+		c <- time.Time{}
+	}()
+	if got, expect := <-mt.Requests(), 5*time.Second; got != expect {
+		t.Errorf("Expected %v, got %v instead", expect, got)
+	}
+	checkNotReady(t, c)
+	mt.AdvanceTime(5 * time.Second)
+	if got, expect := <-mt.Requests(), 3*time.Second; got != expect {
+		t.Errorf("Expected %v, got %v instead", expect, got)
+	}
+	checkReady(t, c)
+	mt.AdvanceTime(2 * time.Second)
+	checkNotReady(t, c)
+	mt.AdvanceTime(1 * time.Second)
+	<-c
+}
+
+func testBlocking(t *testing.T) {
+	mt := NewManualTime()
+	sync := make(chan bool)
+	go func() {
+		// Simulate blocking on a timer.
+		<-mt.After(10 * time.Second)
+		<-mt.After(11 * time.Second)
+		<-mt.After(3 * time.Second)
+		sync <- true
+		<-mt.After(4 * time.Second)
+		sync <- true
+	}()
+	<-mt.Requests()
+	<-mt.Requests()
+	mt.AdvanceTime(12 * time.Second)
+	<-mt.Requests()
+	mt.AdvanceTime(2 * time.Second)
+	mt.AdvanceTime(time.Second)
+	<-sync
+	<-mt.Requests()
+	mt.AdvanceTime(5 * time.Second)
+	<-sync
+}
diff --git a/test/v23tests/binary.go b/test/v23tests/binary.go
new file mode 100644
index 0000000..915a6ac
--- /dev/null
+++ b/test/v23tests/binary.go
@@ -0,0 +1,151 @@
+// 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
new file mode 100644
index 0000000..dca518d
--- /dev/null
+++ b/test/v23tests/doc.go
@@ -0,0 +1,75 @@
+// 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 v23 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
new file mode 100644
index 0000000..129b7e1
--- /dev/null
+++ b/test/v23tests/internal/cached_test.go
@@ -0,0 +1,99 @@
+// 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"
+	"v.io/x/ref/test/v23tests"
+)
+
+//go:generate v23 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) {
+	test.Init()
+	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
new file mode 100644
index 0000000..9272cb6
--- /dev/null
+++ b/test/v23tests/internal/dummy.go
@@ -0,0 +1,5 @@
+// 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
new file mode 100644
index 0000000..aad6a7a
--- /dev/null
+++ b/test/v23tests/internal/v23_test.go
@@ -0,0 +1,30 @@
+// 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
new file mode 100644
index 0000000..7f59374
--- /dev/null
+++ b/test/v23tests/invocation.go
@@ -0,0 +1,119 @@
+// 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
new file mode 100644
index 0000000..75abecb
--- /dev/null
+++ b/test/v23tests/v23tests.go
@@ -0,0 +1,661 @@
+// 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/v23/security"
+
+	"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
+
+	// The environment's root security principal.
+	principal security.Principal
+
+	// 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)
+}
+
+// Principal returns the security principal of this environment.
+func (t *T) Principal() security.Principal {
+	return t.principal
+}
+
+// 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(1)
+	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 principle available via Principal().
+//
+// 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,
+		principal:     principal,
+		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("v23", 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.
+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)
+	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 v23 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
new file mode 100644
index 0000000..5271ce1
--- /dev/null
+++ b/test/v23tests/v23tests_test.go
@@ -0,0 +1,452 @@
+// 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"
+	"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/proxy/proxyd")
+	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 init() {
+	test.Init()
+}
+
+func TestDeferHandling(t *testing.T) {
+	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:308"; !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:330"; !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:351: 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:373: 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.
+}