Merge "veyron/services/store: Implement Object.WatchGlob()."
diff --git a/examples/pipetobrowser/browser/index.html b/examples/pipetobrowser/browser/index.html
index bea82d7..9315525 100644
--- a/examples/pipetobrowser/browser/index.html
+++ b/examples/pipetobrowser/browser/index.html
@@ -23,6 +23,7 @@
   <!-- TODO(aghassemi) Dynamic loading of plugins. Plugins should be able to use a provided module to load web components if they need to. We don't want to load all plugins for no reason! There could be hundreds of them -->
   <link rel="import" href="pipe-viewers/builtin/console/component.html">
   <link rel="import" href="pipe-viewers/builtin/git/status/component.html">
+  <link rel="import" href="pipe-viewers/builtin/vlog/component.html">
 
 </head>
 <body>
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/plugin.js b/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/plugin.js
index 610a0b1..b9d6e55 100644
--- a/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/plugin.js
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/plugin.js
@@ -18,8 +18,6 @@
   }
 
   play(stream) {
-    var listView = document.createElement('ul');
-
     // TODO(aghassemi) let's have the plugin specify if they expect data in
     // in binary or text so p2b can set the proper encoding for them rather
     // than each plugin doing it like this.
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/component.css b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/component.css
new file mode 100644
index 0000000..e99e5f8
--- /dev/null
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/component.css
@@ -0,0 +1,24 @@
+tr.error {
+  background-color: #FF9700;
+}
+
+tr.fatal {
+  background-color: #DA4336;
+}
+
+tr.warning {
+  background-color: #FFEA3A;
+}
+
+tr.info {
+  background-color: #FAFAFA;
+}
+
+.lineNumber::before {
+  content: '#';
+}
+
+.lineNumber {
+  color: rgb(51, 103, 214);
+  margin-left: 0.1em;
+}
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/component.html b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/component.html
new file mode 100644
index 0000000..d1a0fb7
--- /dev/null
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/component.html
@@ -0,0 +1,39 @@
+<link rel="import" href="/libs/vendor/polymer/polymer/polymer.html">
+
+<polymer-element name="p2b-plugin-vlog">
+  <template>
+    <link rel="stylesheet" href="component.css">
+    <table summary="Data Grid displaying veyron log items in a tabular format.">
+      <thead>
+        <tr>
+          <th>Log Level</th>
+          <th>File</th>
+          <th>Message</th>
+          <th>Date</th>
+          <th>ThreadId</th>
+        </tr>
+      </thead>
+      <tbody>
+        <template repeat="{{ item in logItems }}">
+          <tr class="{{ item.level }}">
+            <td>{{ item.level }}</td>
+            <th scope="row">{{ item.file }}<span class="lineNumber">{{ item.fileLine }}<span/></th>
+            <td>{{ item.message }}</td>
+            <td>{{ item.date }}</td>
+            <td>{{ item.threadId }}</td>
+          </tr>
+        </template>
+      </tbody>
+    </table>
+
+  </template>
+  <script>
+    Polymer('p2b-plugin-vlog', {
+      /*
+       * List of log items to display
+       * @type {object}
+       */
+      logItems: []
+    });
+    </script>
+</polymer-element>
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/parser.js b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/parser.js
new file mode 100644
index 0000000..340b8af
--- /dev/null
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/parser.js
@@ -0,0 +1,90 @@
+/*
+ * Parse utilities for veyron logs
+ * @fileoverview
+ */
+
+/*
+ * Parses a single line of text produced by veyron logger
+ * into an structured object representing it.
+ * Log lines have this form:
+ * Lmmdd hh:mm:ss.uuuuuu threadid file:line] msg...
+ * where the fields are defined as follows:
+ *  L                A single character, representing the log level (eg 'I' for INFO)
+ *  mm               The month (zero padded; ie May is '05')
+ *  dd               The day (zero padded)
+ *  hh:mm:ss.uuuuuu  Time in hours, minutes and fractional seconds
+ *  threadid         The space-padded thread ID as returned by GetTID()
+ *  file             The file name
+ *  line             The line number
+ *  msg              The user-supplied message
+ * @param {string} vlogLine A single line of
+ * by git status --short command
+ * @return {parser.item} A parsed object containing log level, date, file,
+ * line number, thread id and message.
+ */
+export function parse(vlogLine) {
+
+  var validLogLineRegEx =  /^([IWEF])(\d{2})(\d{2})\s(\d{2}:\d{2}:\d{2}\.\d+)\s(\d+)\s(.*):(\d+)]\s+(.*)$/
+  var logParts = vlogLine.match(validLogLineRegEx);
+  if(!logParts || logParts.length != 8+1) { // 8 parts + 1 whole match
+    throw new Error('Invalid vlog line format. ' + vlogLine +
+      ' Lmmdd hh:mm:ss.uuuuuu threadid file:line] msg.. pattern');
+  }
+
+  var L = logParts[1];
+  var month = logParts[2];
+  var day = logParts[3];
+  var time = logParts[4];
+  var treadId = parseInt(logParts[5]);
+  var file = logParts[6];
+  var fileLine = parseInt(logParts[7]);
+  var message = logParts[8];
+
+  var now = new Date();
+  var year = now.getFullYear();
+  var thisMonth = now.getMonth() + 1; // JS months are 0-11
+  // Year flip edge case, if log month > this month, we assume log line is from previous year
+  if(parseInt(month) > thisMonth) {
+    year--;
+  }
+
+  var date = new Date(year + '-' + month + '-' + day + ' ' + time);
+
+  return new item(
+    levelCodes[L],
+    date,
+    treadId,
+    file,
+    fileLine,
+    message
+  );
+}
+
+var levelCodes = {
+  'I': 'info',
+  'W': 'warning',
+  'E': 'error',
+  'F': 'fatal'
+}
+
+/*
+ * A structure representing a veyron log item
+ * @param {string} level, one of info, warning, error, fatal
+ * @param {date} date, The date and time of the log item
+ * @param {integer} threadId The thread ID as returned by GetTID()
+ * @param {string} file The file name
+ * @param {integer} fileLine The file line number
+ * @param {string} message The user-supplied message
+ * @class
+ * @private
+ */
+class item {
+  constructor(level, date, threadId, file, fileLine, message) {
+    this.level = level;
+    this.date = date;
+    this.threadId = threadId;
+    this.file = file;
+    this.fileLine = fileLine;
+    this.message = message;
+  }
+}
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/plugin.js b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/plugin.js
new file mode 100644
index 0000000..e7dc5af
--- /dev/null
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/plugin.js
@@ -0,0 +1,43 @@
+/*
+ * vlog is a Pipe Viewer that displays veyron logs in a graphical grid.
+ * Please note that Veyron writes logs to stderr stream, in *nix systems 2>&1
+ * can be used to redirect stderr to stdout which can be then piped to P2B.
+ * @tutorial myVeyronServerd -v=3 2>&1 | p2b google/p2b/[name]/vlog
+ * @tutorial cat logfile.text | p2b google/p2b/[name]/vlog
+ * @fileoverview
+ */
+import { View } from 'view';
+import { PipeViewer } from 'pipe-viewer';
+
+import { parse } from './parser';
+
+var streamUtil = require('event-stream');
+
+class vLogPipeViewer extends PipeViewer {
+  get name() {
+    return 'vlog';
+  }
+
+  play(stream) {
+    stream.setEncoding('utf8');
+
+    // split by new line
+    stream = stream.pipe(streamUtil.split(/\r?\n/));
+
+    var logItems = [];
+    var logView = document.createElement('p2b-plugin-vlog');
+    logView.logItems = logItems;
+
+    stream.on('data', (line) => {
+      // try to parse and display as much as we can.
+      try {
+        logItems.push( parse(line) );
+      } catch(e) {}
+
+    });
+
+    return new View(logView);
+  }
+}
+
+export default vLogPipeViewer;
\ No newline at end of file
diff --git a/examples/pipetobrowser/p2b/main.go b/examples/pipetobrowser/p2b/main.go
index d368b17..c2f500c 100644
--- a/examples/pipetobrowser/p2b/main.go
+++ b/examples/pipetobrowser/p2b/main.go
@@ -23,12 +23,21 @@
 
   For example:
 
-	ls -l | p2b pipetobrowser/jane/DataTable
+	ls -l | p2b google/p2b/jane/console
 
-  where <name> (pipetobrowser/jane) is the veyron name where p2b
-  service is running in the browser. <viewer> (DataTable) specifies what
+	or
+
+	cat cat.jpg | google/p2b/jane/image
+
+  where <name> (google/p2b/jane) is the veyron name where p2b
+  service is running in the browser. <viewer> (console, image) specifies what
   viewer should be used to display the data.
 
+  To redirect stderr of a process, in *nix system you can use 2>&1 before piping to P2B.
+
+  For example many daemons may write log lines to stderr instead of stdout:
+
+  serverd -alsologtostderr=true 2>&1 | google/p2b/jane/vlog
 `
 
 func Usage() {
diff --git a/runtimes/google/ipc/benchmarks/README.txt b/runtimes/google/ipc/benchmarks/README.txt
new file mode 100644
index 0000000..861911b
--- /dev/null
+++ b/runtimes/google/ipc/benchmarks/README.txt
@@ -0,0 +1,130 @@
+This directory contains code uses to measure the performance of the Veyron IPC
+stack.
+
+The ipc_test.go file uses 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
+
+The first benchmarks use the non-streaming version of Echo with a varying
+payload size. The other benchmarks use the streaming version with varying
+number of chunks and payload sizes.
+
+All these benchmarks create a VC before measurements begin. So, the VC creation
+overhead is excluded.
+
+On a ThinkPad X1 Carbon (2 × Intel(R) Core(TM) i7-3667U CPU @ 2.00GHz), we get:
+
+$ $VEYRON_ROOT/veyron/scripts/build/go test -test.bench=. -test.cpu=1 \
+	-test.benchtime=5s veyron/runtimes/google/ipc/benchmarks 2> /dev/null
+PASS
+Benchmark____1B	   10000	    545077 ns/op
+Benchmark___10B	   10000	    587312 ns/op
+Benchmark__100B	   10000	    523019 ns/op
+Benchmark___1KB	   10000	    605235 ns/op
+Benchmark__10KB	   10000	    957467 ns/op
+Benchmark_100KB	    5000	   4101891 ns/op
+Benchmark_N_RPCs____1_chunk_____1B	   10000	    554063 ns/op
+Benchmark_N_RPCs____1_chunk____10B	   10000	    551424 ns/op
+Benchmark_N_RPCs____1_chunk___100B	   10000	    538308 ns/op
+Benchmark_N_RPCs____1_chunk____1KB	   10000	    585579 ns/op
+Benchmark_N_RPCs____1_chunk___10KB	   10000	    904789 ns/op
+Benchmark_N_RPCs___10_chunks___1KB	   10000	   1460984 ns/op
+Benchmark_N_RPCs__100_chunks___1KB	    1000	   8491514 ns/op
+Benchmark_N_RPCs_1000_chunks___1KB	     100	 105269359 ns/op
+Benchmark_1_RPC_N_chunks_____1B	  200000	    763769 ns/op
+Benchmark_1_RPC_N_chunks____10B	  100000	    583134 ns/op
+Benchmark_1_RPC_N_chunks___100B	  100000	     80849 ns/op
+Benchmark_1_RPC_N_chunks____1KB	  100000	     88820 ns/op
+Benchmark_1_RPC_N_chunks___10KB	   50000	    361596 ns/op
+Benchmark_1_RPC_N_chunks__100KB	    5000	   3127193 ns/op
+ok  	veyron/runtimes/google/ipc/benchmarks	525.095s
+
+
+The Benchmark_Simple_____1KB line shows that it takes an average of 0.605 ms to
+execute a simple Echo RPC with a 1 KB payload.
+
+The Benchmark_N_RPCs____1_chunk____1KB line shows that a streaming RPC with the
+same payload (i.e. 1 chunk of 1 KB) takes about 0.586 ms on average.
+
+And Benchmark_1_RPC_N_chunks____1KB shows that sending a stream of 1 KB chunks
+takes an average of 0.088 ms per chunk.
+
+
+Running the client and server as separate processes.
+
+In this case, we can see the cost of name resolution, creating the VC, etc. in
+the first RPC.
+
+$ $VEYRON_ROOT/veyron/go/bin/bmserver --address=localhost:8888 --acl='{"*":"A"}'
+
+(In a different shell)
+$ $VEYRON_ROOT/veyron/go/bin/bmclient --server=/localhost:8888 --count=10 \
+	--payload_size=1000
+CallEcho 0 64133467
+CallEcho 1 766223
+CallEcho 2 703860
+CallEcho 3 697590
+CallEcho 4 601134
+CallEcho 5 601142
+CallEcho 6 624079
+CallEcho 7 644664
+CallEcho 8 605195
+CallEcho 9 637037
+
+It took about 64 ms to execute the first RPC, and then 0.60-0.70 ms to execute
+the next ones.
+
+
+On a Raspberry Pi, everything is much slower. The same tests show the following
+results:
+
+$ ./benchmarks.test -test.bench=. -test.cpu=1 -test.benchtime=5s 2>/dev/null
+PASS
+Benchmark____1B             500          21316148 ns/op
+Benchmark___10B             500          23304638 ns/op
+Benchmark__100B             500          21860446 ns/op
+Benchmark___1KB             500          24000346 ns/op
+Benchmark__10KB             200          37530575 ns/op
+Benchmark_100KB             100         136243310 ns/op
+Benchmark_N_RPCs____1_chunk_____1B           500          19957506 ns/op
+Benchmark_N_RPCs____1_chunk____10B           500          22868392 ns/op
+Benchmark_N_RPCs____1_chunk___100B           500          19635412 ns/op
+Benchmark_N_RPCs____1_chunk____1KB           500          22572190 ns/op
+Benchmark_N_RPCs____1_chunk___10KB           500          37570948 ns/op
+Benchmark_N_RPCs___10_chunks___1KB           100          51670740 ns/op
+Benchmark_N_RPCs__100_chunks___1KB            50         364938740 ns/op
+Benchmark_N_RPCs_1000_chunks___1KB             2        3586374500 ns/op
+Benchmark_1_RPC_N_chunks_____1B    10000           1034042 ns/op
+Benchmark_1_RPC_N_chunks____10B     5000           1894875 ns/op
+Benchmark_1_RPC_N_chunks___100B     5000           2857289 ns/op
+Benchmark_1_RPC_N_chunks____1KB     5000           6465839 ns/op
+Benchmark_1_RPC_N_chunks___10KB      100          80019430 ns/op
+Benchmark_1_RPC_N_chunks__100KB Killed
+
+The simple 1 KB RPCs take an average of 24 ms. The streaming equivalent takes
+about 22 ms, and streaming many 1 KB chunks takes about 6.5 ms per chunk.
+
+
+$ ./bmserver --address=localhost:8888 --acl='{"*":"A"}'
+
+$ ./bmclient --server=/localhost:8888 --count=10 --payload_size=1000
+CallEcho 0 2573406000
+CallEcho 1 44669000
+CallEcho 2 54442000
+CallEcho 3 33934000
+CallEcho 4 47985000
+CallEcho 5 61324000
+CallEcho 6 51654000
+CallEcho 7 47043000
+CallEcho 8 44995000
+CallEcho 9 53166000
+
+On the pi, the first RPC takes ~2.5 sec to execute.
+
diff --git a/runtimes/google/ipc/benchmarks/bmclient/main.go b/runtimes/google/ipc/benchmarks/bmclient/main.go
new file mode 100644
index 0000000..16c34c5
--- /dev/null
+++ b/runtimes/google/ipc/benchmarks/bmclient/main.go
@@ -0,0 +1,27 @@
+// a simple command-line tool to run the benchmark client.
+package main
+
+import (
+	"flag"
+	"os"
+
+	"veyron/runtimes/google/ipc/benchmarks"
+
+	"veyron2/rt"
+)
+
+var (
+	server      = flag.String("server", "", "veyron name of the server to connect to")
+	count       = flag.Int("count", 1, "number of RPCs to send")
+	chunkCount  = flag.Int("chunk_count", 0, "number of stream chunks to send")
+	payloadSize = flag.Int("payload_size", 32, "the size of the payload")
+)
+
+func main() {
+	rt.Init()
+	if *chunkCount == 0 {
+		benchmarks.CallEcho(*server, *count, *payloadSize, os.Stdout)
+	} else {
+		benchmarks.CallEchoStream(*server, *count, *chunkCount, *payloadSize, os.Stdout)
+	}
+}
diff --git a/runtimes/google/ipc/benchmarks/bmserver/main.go b/runtimes/google/ipc/benchmarks/bmserver/main.go
new file mode 100644
index 0000000..017ad69
--- /dev/null
+++ b/runtimes/google/ipc/benchmarks/bmserver/main.go
@@ -0,0 +1,25 @@
+// a simple command-line tool to run the benchmark server.
+package main
+
+import (
+	"flag"
+
+	"veyron/lib/signals"
+	"veyron/runtimes/google/ipc/benchmarks"
+
+	"veyron2/rt"
+	"veyron2/vlog"
+)
+
+var (
+	address  = flag.String("address", ":0", "address to listen on")
+	protocol = flag.String("protocol", "tcp", "protocol to listen on")
+)
+
+func main() {
+	rt.Init()
+	addr, stop := benchmarks.StartServer(*protocol, *address)
+	vlog.Infof("Listening on %s", addr)
+	defer stop()
+	<-signals.ShutdownOnSignals()
+}
diff --git a/runtimes/google/ipc/benchmarks/client.go b/runtimes/google/ipc/benchmarks/client.go
new file mode 100644
index 0000000..8117e8f
--- /dev/null
+++ b/runtimes/google/ipc/benchmarks/client.go
@@ -0,0 +1,101 @@
+package benchmarks
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"time"
+
+	"veyron2"
+	"veyron2/rt"
+	"veyron2/vlog"
+)
+
+// CallEcho calls the Echo method 'iterations' times with the given payload
+// size, and optionally logs the result.
+func CallEcho(address string, iterations, payloadSize int, log io.Writer) {
+	payload := make([]byte, payloadSize)
+	for _, i := range payload {
+		payload[i] = byte(i & 0xff)
+	}
+
+	stub, err := BindBenchmark(address)
+	if err != nil {
+		vlog.Fatalf("BindBenchmark(%q) failed: %v", address, err)
+	}
+
+	for i := 0; i < iterations; i++ {
+		start := time.Now()
+		result, err := stub.Echo(rt.R().TODOContext(), payload)
+		elapsed := time.Since(start)
+		if err != nil {
+			vlog.Fatalf("Echo failed: %v", err)
+		}
+		if !bytes.Equal(payload, result) {
+			vlog.Fatalf("Echo return different payload: got %v, expected %v", result, payload)
+		}
+		if log != nil {
+			log.Write([]byte(fmt.Sprintf("CallEcho %d %d\n", i, elapsed)))
+		}
+	}
+}
+
+// CallEchoStream calls the EchoStream method 'rpcCount' times. Each iteration
+// sends 'messageCount' messages on the stream and receives the same number
+// back. Each message has the given payload size. Optionally logs the result.
+func CallEchoStream(address string, rpcCount, messageCount, payloadSize int, log io.Writer) {
+	payload := make([]byte, payloadSize)
+	for _, i := range payload {
+		payload[i] = byte(i & 0xff)
+	}
+
+	stub, err := BindBenchmark(address)
+	if err != nil {
+		vlog.Fatalf("BindBenchmark(%q) failed: %v", address, err)
+	}
+
+	for i := 0; i < rpcCount; i++ {
+		start := time.Now()
+		stream, err := stub.EchoStream(rt.R().TODOContext(), veyron2.CallTimeout(time.Hour))
+		if err != nil {
+			vlog.Fatalf("EchoStream failed: %v", err)
+		}
+		done := make(chan error, 1)
+		go func() {
+			for {
+				chunk, err := stream.Recv()
+				if err == io.EOF {
+					done <- nil
+					return
+				}
+				if err != nil {
+					done <- err
+					return
+				}
+				if !bytes.Equal(payload, chunk) {
+					done <- fmt.Errorf("Recv got different payload: got %v, expected %v", chunk, payload)
+					return
+				}
+			}
+		}()
+		for j := 0; j < messageCount; j++ {
+			if err = stream.Send(payload); err != nil {
+				vlog.Fatalf("Send failed: %v", err)
+			}
+		}
+		if err = stream.CloseSend(); err != nil {
+			vlog.Fatalf("CloseSend() failed: %v", err)
+		}
+		if err = <-done; err != nil {
+			vlog.Fatalf("%v", err)
+		}
+
+		if err = stream.Finish(); err != nil {
+			vlog.Fatalf("Finish failed: %v", err)
+		}
+		elapsed := time.Since(start)
+		if log != nil {
+			log.Write([]byte(fmt.Sprintf("CallEchoStream %d %d\n", i, elapsed)))
+		}
+	}
+}
diff --git a/runtimes/google/ipc/benchmarks/ipc_test.go b/runtimes/google/ipc/benchmarks/ipc_test.go
new file mode 100644
index 0000000..411869f
--- /dev/null
+++ b/runtimes/google/ipc/benchmarks/ipc_test.go
@@ -0,0 +1,109 @@
+package benchmarks_test
+
+import (
+	"testing"
+
+	"veyron/runtimes/google/ipc/benchmarks"
+
+	"veyron2/rt"
+)
+
+func init() {
+	rt.Init()
+}
+
+func RunBenchmark(b *testing.B, payloadSize int) {
+	address, stop := benchmarks.StartServer("tcp", "127.0.0.1:0")
+	defer stop()
+	benchmarks.CallEcho(address, 1, 1, nil) // Create VC
+	b.ResetTimer()
+	benchmarks.CallEcho(address, b.N, payloadSize, nil)
+}
+
+func RunStreamBenchmark(b *testing.B, rpcCount, messageCount, payloadSize int) {
+	address, stop := benchmarks.StartServer("tcp", "127.0.0.1:0")
+	defer stop()
+	benchmarks.CallEchoStream(address, 1, 1, 1, nil) // Create VC
+	b.ResetTimer()
+	benchmarks.CallEchoStream(address, rpcCount, messageCount, payloadSize, nil)
+}
+
+func Benchmark____1B(b *testing.B) {
+	RunBenchmark(b, 1)
+}
+
+func Benchmark___10B(b *testing.B) {
+	RunBenchmark(b, 10)
+}
+
+func Benchmark__100B(b *testing.B) {
+	RunBenchmark(b, 100)
+}
+
+func Benchmark___1KB(b *testing.B) {
+	RunBenchmark(b, 1000)
+}
+
+func Benchmark__10KB(b *testing.B) {
+	RunBenchmark(b, 10000)
+}
+
+func Benchmark_100KB(b *testing.B) {
+	RunBenchmark(b, 100000)
+}
+
+func Benchmark_N_RPCs____1_chunk_____1B(b *testing.B) {
+	RunStreamBenchmark(b, b.N, 1, 1)
+}
+
+func Benchmark_N_RPCs____1_chunk____10B(b *testing.B) {
+	RunStreamBenchmark(b, b.N, 1, 10)
+}
+
+func Benchmark_N_RPCs____1_chunk___100B(b *testing.B) {
+	RunStreamBenchmark(b, b.N, 1, 100)
+}
+
+func Benchmark_N_RPCs____1_chunk____1KB(b *testing.B) {
+	RunStreamBenchmark(b, b.N, 1, 1000)
+}
+
+func Benchmark_N_RPCs____1_chunk___10KB(b *testing.B) {
+	RunStreamBenchmark(b, b.N, 1, 10000)
+}
+
+func Benchmark_N_RPCs___10_chunks___1KB(b *testing.B) {
+	RunStreamBenchmark(b, b.N, 10, 1000)
+}
+
+func Benchmark_N_RPCs__100_chunks___1KB(b *testing.B) {
+	RunStreamBenchmark(b, b.N, 100, 1000)
+}
+
+func Benchmark_N_RPCs_1000_chunks___1KB(b *testing.B) {
+	RunStreamBenchmark(b, b.N, 1000, 1000)
+}
+
+func Benchmark_1_RPC_N_chunks_____1B(b *testing.B) {
+	RunStreamBenchmark(b, 1, b.N, 1)
+}
+
+func Benchmark_1_RPC_N_chunks____10B(b *testing.B) {
+	RunStreamBenchmark(b, 1, b.N, 10)
+}
+
+func Benchmark_1_RPC_N_chunks___100B(b *testing.B) {
+	RunStreamBenchmark(b, 1, b.N, 100)
+}
+
+func Benchmark_1_RPC_N_chunks____1KB(b *testing.B) {
+	RunStreamBenchmark(b, 1, b.N, 1000)
+}
+
+func Benchmark_1_RPC_N_chunks___10KB(b *testing.B) {
+	RunStreamBenchmark(b, 1, b.N, 10000)
+}
+
+func Benchmark_1_RPC_N_chunks__100KB(b *testing.B) {
+	RunStreamBenchmark(b, 1, b.N, 100000)
+}
diff --git a/runtimes/google/ipc/benchmarks/server.go b/runtimes/google/ipc/benchmarks/server.go
new file mode 100644
index 0000000..d585d72
--- /dev/null
+++ b/runtimes/google/ipc/benchmarks/server.go
@@ -0,0 +1,57 @@
+package benchmarks
+
+import (
+	"io"
+
+	sflag "veyron/security/flag"
+
+	"veyron2/ipc"
+	"veyron2/naming"
+	"veyron2/rt"
+	"veyron2/vlog"
+)
+
+type impl struct {
+}
+
+func (i *impl) Echo(ctx ipc.ServerContext, payload []byte) ([]byte, error) {
+	return payload, nil
+}
+
+func (i *impl) EchoStream(ctx ipc.ServerContext, stream BenchmarkServiceEchoStreamStream) error {
+	for {
+		chunk, err := stream.Recv()
+		if err == io.EOF {
+			break
+		}
+		if err != nil {
+			return err
+		}
+		if err := stream.Send(chunk); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// StartServer starts a server that implements the Benchmark service. The
+// server listens to the given protocol and address, and returns the veyron
+// address of the server and a callback function to stop the server.
+func StartServer(protocol, address string) (string, func()) {
+	server, err := rt.R().NewServer()
+	if err != nil {
+		vlog.Fatalf("NewServer failed: %v", err)
+	}
+	if err := server.Register("", ipc.SoloDispatcher(NewServerBenchmark(&impl{}), sflag.NewAuthorizerOrDie())); err != nil {
+		vlog.Fatalf("Register failed: %v", err)
+	}
+	ep, err := server.Listen(protocol, address)
+	if err != nil {
+		vlog.Fatalf("Listen failed: %v", err)
+	}
+	return naming.JoinAddressName(ep.String(), ""), func() {
+		if err := server.Stop(); err != nil {
+			vlog.Fatalf("Stop() failed: %v", err)
+		}
+	}
+}
diff --git a/runtimes/google/ipc/benchmarks/service.vdl b/runtimes/google/ipc/benchmarks/service.vdl
new file mode 100644
index 0000000..ad0afcf
--- /dev/null
+++ b/runtimes/google/ipc/benchmarks/service.vdl
@@ -0,0 +1,10 @@
+// package benchmark provides simple tools to measure the performance of the
+// IPC system.
+package benchmarks
+
+type Benchmark interface {
+  // Echo returns the payload that it receives.
+  Echo(Payload []byte) ([]byte, error)
+  // EchoStream returns the payload that it receives via the stream.
+  EchoStream() stream<[]byte,[]byte> error
+}
diff --git a/runtimes/google/ipc/benchmarks/service.vdl.go b/runtimes/google/ipc/benchmarks/service.vdl.go
new file mode 100644
index 0000000..371285d
--- /dev/null
+++ b/runtimes/google/ipc/benchmarks/service.vdl.go
@@ -0,0 +1,293 @@
+// This file was auto-generated by the veyron vdl tool.
+// Source: service.vdl
+
+// package benchmark provides simple tools to measure the performance of the
+// IPC system.
+package benchmarks
+
+import (
+	// The non-user imports are prefixed with "_gen_" to prevent collisions.
+	_gen_veyron2 "veyron2"
+	_gen_context "veyron2/context"
+	_gen_ipc "veyron2/ipc"
+	_gen_naming "veyron2/naming"
+	_gen_rt "veyron2/rt"
+	_gen_vdl "veyron2/vdl"
+	_gen_wiretype "veyron2/wiretype"
+)
+
+// Benchmark is the interface the client binds and uses.
+// Benchmark_ExcludingUniversal is the interface without internal framework-added methods
+// to enable embedding without method collisions.  Not to be used directly by clients.
+type Benchmark_ExcludingUniversal interface {
+	// Echo returns the payload that it receives.
+	Echo(ctx _gen_context.T, Payload []byte, opts ..._gen_ipc.CallOpt) (reply []byte, err error)
+	// EchoStream returns the payload that it receives via the stream.
+	EchoStream(ctx _gen_context.T, opts ..._gen_ipc.CallOpt) (reply BenchmarkEchoStreamStream, err error)
+}
+type Benchmark interface {
+	_gen_ipc.UniversalServiceMethods
+	Benchmark_ExcludingUniversal
+}
+
+// BenchmarkService is the interface the server implements.
+type BenchmarkService interface {
+
+	// Echo returns the payload that it receives.
+	Echo(context _gen_ipc.ServerContext, Payload []byte) (reply []byte, err error)
+	// EchoStream returns the payload that it receives via the stream.
+	EchoStream(context _gen_ipc.ServerContext, stream BenchmarkServiceEchoStreamStream) (err error)
+}
+
+// BenchmarkEchoStreamStream is the interface for streaming responses of the method
+// EchoStream in the service interface Benchmark.
+type BenchmarkEchoStreamStream interface {
+
+	// Send places the item onto the output stream, blocking if there is no buffer
+	// space available.
+	Send(item []byte) error
+
+	// CloseSend indicates to the server that no more items will be sent; server
+	// Recv calls will receive io.EOF after all sent items.  Subsequent calls to
+	// Send on the client will fail.  This is an optional call - it's used by
+	// streaming clients that need the server to receive the io.EOF terminator.
+	CloseSend() error
+
+	// Recv returns the next item in the input stream, blocking until
+	// an item is available.  Returns io.EOF to indicate graceful end of input.
+	Recv() (item []byte, err error)
+
+	// Finish closes the stream and returns the positional return values for
+	// call.
+	Finish() (err error)
+
+	// Cancel cancels the RPC, notifying the server to stop processing.
+	Cancel()
+}
+
+// Implementation of the BenchmarkEchoStreamStream interface that is not exported.
+type implBenchmarkEchoStreamStream struct {
+	clientCall _gen_ipc.Call
+}
+
+func (c *implBenchmarkEchoStreamStream) Send(item []byte) error {
+	return c.clientCall.Send(item)
+}
+
+func (c *implBenchmarkEchoStreamStream) CloseSend() error {
+	return c.clientCall.CloseSend()
+}
+
+func (c *implBenchmarkEchoStreamStream) Recv() (item []byte, err error) {
+	err = c.clientCall.Recv(&item)
+	return
+}
+
+func (c *implBenchmarkEchoStreamStream) Finish() (err error) {
+	if ierr := c.clientCall.Finish(&err); ierr != nil {
+		err = ierr
+	}
+	return
+}
+
+func (c *implBenchmarkEchoStreamStream) Cancel() {
+	c.clientCall.Cancel()
+}
+
+// BenchmarkServiceEchoStreamStream is the interface for streaming responses of the method
+// EchoStream in the service interface Benchmark.
+type BenchmarkServiceEchoStreamStream interface {
+	// Send places the item onto the output stream, blocking if there is no buffer
+	// space available.
+	Send(item []byte) error
+
+	// Recv fills itemptr with the next item in the input stream, blocking until
+	// an item is available.  Returns io.EOF to indicate graceful end of input.
+	Recv() (item []byte, err error)
+}
+
+// Implementation of the BenchmarkServiceEchoStreamStream interface that is not exported.
+type implBenchmarkServiceEchoStreamStream struct {
+	serverCall _gen_ipc.ServerCall
+}
+
+func (s *implBenchmarkServiceEchoStreamStream) Send(item []byte) error {
+	return s.serverCall.Send(item)
+}
+
+func (s *implBenchmarkServiceEchoStreamStream) Recv() (item []byte, err error) {
+	err = s.serverCall.Recv(&item)
+	return
+}
+
+// BindBenchmark returns the client stub implementing the Benchmark
+// interface.
+//
+// If no _gen_ipc.Client is specified, the default _gen_ipc.Client in the
+// global Runtime is used.
+func BindBenchmark(name string, opts ..._gen_ipc.BindOpt) (Benchmark, error) {
+	var client _gen_ipc.Client
+	switch len(opts) {
+	case 0:
+		client = _gen_rt.R().Client()
+	case 1:
+		switch o := opts[0].(type) {
+		case _gen_veyron2.Runtime:
+			client = o.Client()
+		case _gen_ipc.Client:
+			client = o
+		default:
+			return nil, _gen_vdl.ErrUnrecognizedOption
+		}
+	default:
+		return nil, _gen_vdl.ErrTooManyOptionsToBind
+	}
+	stub := &clientStubBenchmark{client: client, name: name}
+
+	return stub, nil
+}
+
+// NewServerBenchmark creates a new server stub.
+//
+// It takes a regular server implementing the BenchmarkService
+// interface, and returns a new server stub.
+func NewServerBenchmark(server BenchmarkService) interface{} {
+	return &ServerStubBenchmark{
+		service: server,
+	}
+}
+
+// clientStubBenchmark implements Benchmark.
+type clientStubBenchmark struct {
+	client _gen_ipc.Client
+	name   string
+}
+
+func (__gen_c *clientStubBenchmark) Echo(ctx _gen_context.T, Payload []byte, opts ..._gen_ipc.CallOpt) (reply []byte, err error) {
+	var call _gen_ipc.Call
+	if call, err = __gen_c.client.StartCall(ctx, __gen_c.name, "Echo", []interface{}{Payload}, opts...); err != nil {
+		return
+	}
+	if ierr := call.Finish(&reply, &err); ierr != nil {
+		err = ierr
+	}
+	return
+}
+
+func (__gen_c *clientStubBenchmark) EchoStream(ctx _gen_context.T, opts ..._gen_ipc.CallOpt) (reply BenchmarkEchoStreamStream, err error) {
+	var call _gen_ipc.Call
+	if call, err = __gen_c.client.StartCall(ctx, __gen_c.name, "EchoStream", nil, opts...); err != nil {
+		return
+	}
+	reply = &implBenchmarkEchoStreamStream{clientCall: call}
+	return
+}
+
+func (__gen_c *clientStubBenchmark) UnresolveStep(ctx _gen_context.T, opts ..._gen_ipc.CallOpt) (reply []string, err error) {
+	var call _gen_ipc.Call
+	if call, err = __gen_c.client.StartCall(ctx, __gen_c.name, "UnresolveStep", nil, opts...); err != nil {
+		return
+	}
+	if ierr := call.Finish(&reply, &err); ierr != nil {
+		err = ierr
+	}
+	return
+}
+
+func (__gen_c *clientStubBenchmark) Signature(ctx _gen_context.T, opts ..._gen_ipc.CallOpt) (reply _gen_ipc.ServiceSignature, err error) {
+	var call _gen_ipc.Call
+	if call, err = __gen_c.client.StartCall(ctx, __gen_c.name, "Signature", nil, opts...); err != nil {
+		return
+	}
+	if ierr := call.Finish(&reply, &err); ierr != nil {
+		err = ierr
+	}
+	return
+}
+
+func (__gen_c *clientStubBenchmark) GetMethodTags(ctx _gen_context.T, method string, opts ..._gen_ipc.CallOpt) (reply []interface{}, err error) {
+	var call _gen_ipc.Call
+	if call, err = __gen_c.client.StartCall(ctx, __gen_c.name, "GetMethodTags", []interface{}{method}, opts...); err != nil {
+		return
+	}
+	if ierr := call.Finish(&reply, &err); ierr != nil {
+		err = ierr
+	}
+	return
+}
+
+// ServerStubBenchmark wraps a server that implements
+// BenchmarkService and provides an object that satisfies
+// the requirements of veyron2/ipc.ReflectInvoker.
+type ServerStubBenchmark struct {
+	service BenchmarkService
+}
+
+func (__gen_s *ServerStubBenchmark) GetMethodTags(call _gen_ipc.ServerCall, method string) ([]interface{}, error) {
+	// TODO(bprosnitz) GetMethodTags() will be replaces with Signature().
+	// Note: This exhibits some weird behavior like returning a nil error if the method isn't found.
+	// This will change when it is replaced with Signature().
+	switch method {
+	case "Echo":
+		return []interface{}{}, nil
+	case "EchoStream":
+		return []interface{}{}, nil
+	default:
+		return nil, nil
+	}
+}
+
+func (__gen_s *ServerStubBenchmark) Signature(call _gen_ipc.ServerCall) (_gen_ipc.ServiceSignature, error) {
+	result := _gen_ipc.ServiceSignature{Methods: make(map[string]_gen_ipc.MethodSignature)}
+	result.Methods["Echo"] = _gen_ipc.MethodSignature{
+		InArgs: []_gen_ipc.MethodArgument{
+			{Name: "Payload", Type: 66},
+		},
+		OutArgs: []_gen_ipc.MethodArgument{
+			{Name: "", Type: 66},
+			{Name: "", Type: 67},
+		},
+	}
+	result.Methods["EchoStream"] = _gen_ipc.MethodSignature{
+		InArgs: []_gen_ipc.MethodArgument{},
+		OutArgs: []_gen_ipc.MethodArgument{
+			{Name: "", Type: 67},
+		},
+		InStream:  66,
+		OutStream: 66,
+	}
+
+	result.TypeDefs = []_gen_vdl.Any{
+		_gen_wiretype.NamedPrimitiveType{Type: 0x32, Name: "byte", Tags: []string(nil)}, _gen_wiretype.SliceType{Elem: 0x41, Name: "", Tags: []string(nil)}, _gen_wiretype.NamedPrimitiveType{Type: 0x1, Name: "error", Tags: []string(nil)}}
+
+	return result, nil
+}
+
+func (__gen_s *ServerStubBenchmark) UnresolveStep(call _gen_ipc.ServerCall) (reply []string, err error) {
+	if unresolver, ok := __gen_s.service.(_gen_ipc.Unresolver); ok {
+		return unresolver.UnresolveStep(call)
+	}
+	if call.Server() == nil {
+		return
+	}
+	var published []string
+	if published, err = call.Server().Published(); err != nil || published == nil {
+		return
+	}
+	reply = make([]string, len(published))
+	for i, p := range published {
+		reply[i] = _gen_naming.Join(p, call.Name())
+	}
+	return
+}
+
+func (__gen_s *ServerStubBenchmark) Echo(call _gen_ipc.ServerCall, Payload []byte) (reply []byte, err error) {
+	reply, err = __gen_s.service.Echo(call, Payload)
+	return
+}
+
+func (__gen_s *ServerStubBenchmark) EchoStream(call _gen_ipc.ServerCall) (err error) {
+	stream := &implBenchmarkServiceEchoStreamStream{serverCall: call}
+	err = __gen_s.service.EchoStream(call, stream)
+	return
+}
diff --git a/runtimes/google/ipc/server.go b/runtimes/google/ipc/server.go
index f1c8718..fb4406d 100644
--- a/runtimes/google/ipc/server.go
+++ b/runtimes/google/ipc/server.go
@@ -370,9 +370,7 @@
 	}
 	acl := make(security.ACL)
 	for _, n := range id.Names() {
-		if !strings.HasPrefix(n, wire.UntrustedIDProviderPrefix) {
-			acl[security.PrincipalPattern(n+wire.ChainSeparator+security.AllPrincipals)] = security.AllLabels
-		}
+		acl[security.PrincipalPattern(n+wire.ChainSeparator+security.AllPrincipals)] = security.AllLabels
 	}
 	return acl
 }
diff --git a/runtimes/google/ipc/stream/crypto/box.go b/runtimes/google/ipc/stream/crypto/box.go
new file mode 100644
index 0000000..3ab2c33
--- /dev/null
+++ b/runtimes/google/ipc/stream/crypto/box.go
@@ -0,0 +1,87 @@
+package crypto
+
+import (
+	"bytes"
+	"code.google.com/p/go.crypto/nacl/box"
+	"crypto/rand"
+	"encoding/binary"
+	"fmt"
+	"io"
+	"net"
+	"veyron/runtimes/google/lib/iobuf"
+)
+
+type boxcrypter struct {
+	conn                  net.Conn
+	alloc                 *iobuf.Allocator
+	sharedKey             [32]byte
+	writeNonce, readNonce uint64
+}
+
+// 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(conn net.Conn, pool *iobuf.Pool) (Crypter, error) {
+	pk, sk, err := box.GenerateKey(rand.Reader)
+	if err != nil {
+		return nil, err
+	}
+	var theirPK [32]byte
+	errChan := make(chan error)
+	defer close(errChan)
+	go func() { _, err := conn.Write(pk[:]); errChan <- err }()
+	go func() { _, err := io.ReadFull(conn, theirPK[:]); errChan <- err }()
+	if err := <-errChan; err != nil {
+		return nil, err
+	}
+	if err := <-errChan; err != nil {
+		return nil, err
+	}
+	ret := &boxcrypter{conn: conn, alloc: iobuf.NewAllocator(pool, 0)}
+	box.Precompute(&ret.sharedKey, &theirPK, sk)
+	// 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(pk[:], theirPK[:]) < 0 {
+		ret.writeNonce, ret.readNonce = 0, 1
+	} else {
+		ret.writeNonce, ret.readNonce = 1, 0
+	}
+	return ret, nil
+}
+
+func (c *boxcrypter) Encrypt(src *iobuf.Slice) (*iobuf.Slice, error) {
+	defer src.Release()
+	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)
+	return ret, nil
+}
+
+func (c *boxcrypter) Decrypt(src *iobuf.Slice) (*iobuf.Slice, error) {
+	defer src.Release()
+	var nonce [24]byte
+	binary.LittleEndian.PutUint64(nonce[:], c.readNonce)
+	c.readNonce += 2
+	retLen := len(src.Contents) - box.Overhead
+	if retLen < 0 {
+		return nil, fmt.Errorf("ciphertext too short")
+	}
+	ret := c.alloc.Alloc(uint(retLen))
+	var ok bool
+	ret.Contents, ok = box.OpenAfterPrecomputation(ret.Contents[:0], src.Contents, &nonce, &c.sharedKey)
+	if !ok {
+		return nil, fmt.Errorf("message authentication failed")
+	}
+	return ret, nil
+}
+
+func (c *boxcrypter) String() string {
+	return fmt.Sprintf("%#v", *c)
+}
diff --git a/runtimes/google/ipc/stream/crypto/crypto_test.go b/runtimes/google/ipc/stream/crypto/crypto_test.go
index 063d222..4bbdd45 100644
--- a/runtimes/google/ipc/stream/crypto/crypto_test.go
+++ b/runtimes/google/ipc/stream/crypto/crypto_test.go
@@ -37,8 +37,8 @@
 	crypter.String() // Only to test that String does not crash.
 }
 
-func TestTLS(t *testing.T) {
-	c1, c2 := tlsCrypters(t)
+func testSimple(t *testing.T, crypters func(testing.TB) (Crypter, Crypter)) {
+	c1, c2 := crypters(t)
 	// Execute String just to check that it does not crash.
 	c1.String()
 	c2.String()
@@ -63,6 +63,9 @@
 	t.Logf("Byte overhead of encryption: %v", overhead)
 }
 
+func TestTLS(t *testing.T) { testSimple(t, tlsCrypters) }
+func TestBox(t *testing.T) { testSimple(t, boxCrypters) }
+
 func TestTLSNil(t *testing.T) {
 	c1, c2 := tlsCrypters(t)
 	if t.Failed() {
@@ -123,12 +126,27 @@
 	return c1, c2
 }
 
-func benchmarkEncrypt(b *testing.B, size int) {
+func boxCrypters(t testing.TB) (Crypter, Crypter) {
+	serverConn, clientConn := net.Pipe()
+	crypters := make(chan Crypter)
+	for _, conn := range []net.Conn{serverConn, clientConn} {
+		go func(conn net.Conn) {
+			crypter, err := NewBoxCrypter(conn, iobuf.NewPool(0))
+			if err != nil {
+				t.Fatal(err)
+			}
+			crypters <- crypter
+		}(conn)
+	}
+	return <-crypters, <-crypters
+}
+
+func benchmarkEncrypt(b *testing.B, crypters func(testing.TB) (Crypter, Crypter), size int) {
 	plaintext := make([]byte, size)
 	if _, err := rand.Read(plaintext); err != nil {
 		b.Fatal(err)
 	}
-	e, _ := tlsCrypters(b)
+	e, _ := crypters(b)
 	b.SetBytes(int64(size))
 	b.ResetTimer()
 	for i := 0; i < b.N; i++ {
@@ -140,13 +158,19 @@
 	}
 }
 
-func BenchmarkEncrypt_1B(b *testing.B)  { benchmarkEncrypt(b, 1) }
-func BenchmarkEncrypt_1K(b *testing.B)  { benchmarkEncrypt(b, 1<<10) }
-func BenchmarkEncrypt_10K(b *testing.B) { benchmarkEncrypt(b, 10<<10) }
-func BenchmarkEncrypt_1M(b *testing.B)  { benchmarkEncrypt(b, 1<<20) }
-func BenchmarkEncrypt_5M(b *testing.B)  { benchmarkEncrypt(b, 5<<20) }
+func BenchmarkTLSEncrypt_1B(b *testing.B)  { benchmarkEncrypt(b, tlsCrypters, 1) }
+func BenchmarkTLSEncrypt_1K(b *testing.B)  { benchmarkEncrypt(b, tlsCrypters, 1<<10) }
+func BenchmarkTLSEncrypt_10K(b *testing.B) { benchmarkEncrypt(b, tlsCrypters, 10<<10) }
+func BenchmarkTLSEncrypt_1M(b *testing.B)  { benchmarkEncrypt(b, tlsCrypters, 1<<20) }
+func BenchmarkTLSEncrypt_5M(b *testing.B)  { benchmarkEncrypt(b, tlsCrypters, 5<<20) }
 
-func benchmarkRoundTrip(b *testing.B, size int) {
+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 func(testing.TB) (Crypter, Crypter), size int) {
 	plaintext := make([]byte, size)
 	if _, err := rand.Read(plaintext); err != nil {
 		b.Fatal(err)
@@ -166,8 +190,14 @@
 		plainslice.Release()
 	}
 }
-func BenchmarkRoundTrip_1B(b *testing.B)  { benchmarkRoundTrip(b, 1) }
-func BenchmarkRoundTrip_1K(b *testing.B)  { benchmarkRoundTrip(b, 1<<10) }
-func BenchmarkRoundTrip_10K(b *testing.B) { benchmarkRoundTrip(b, 10<<10) }
-func BenchmarkRoundTrip_1M(b *testing.B)  { benchmarkRoundTrip(b, 1<<20) }
-func BenchmarkRoundTrip_5M(b *testing.B)  { benchmarkRoundTrip(b, 5<<20) }
+func BenchmarkTLSRoundTrip_1B(b *testing.B)  { benchmarkRoundTrip(b, tlsCrypters, 1) }
+func BenchmarkTLSRoundTrip_1K(b *testing.B)  { benchmarkRoundTrip(b, tlsCrypters, 1<<10) }
+func BenchmarkTLSRoundTrip_10K(b *testing.B) { benchmarkRoundTrip(b, tlsCrypters, 10<<10) }
+func BenchmarkTLSRoundTrip_1M(b *testing.B)  { benchmarkRoundTrip(b, tlsCrypters, 1<<20) }
+func BenchmarkTLSRoundTrip_5M(b *testing.B)  { benchmarkRoundTrip(b, tlsCrypters, 5<<20) }
+
+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) }
diff --git a/runtimes/google/security/identity_chain.go b/runtimes/google/security/identity_chain.go
index b8439e0..b6ad17b 100644
--- a/runtimes/google/security/identity_chain.go
+++ b/runtimes/google/security/identity_chain.go
@@ -19,6 +19,17 @@
 	"veyron2/vom"
 )
 
+const (
+	// unknownIDProviderPrefix is the prefix added when stringifying
+	// an identity for which there is no entry for the root certificate
+	// in the trusted keys set.
+	unknownIDProviderPrefix = "unknown/"
+	// mistrustedIDProviderPrefix is the prefix added when stringifying
+	// an identity whose root certificate has a public key that does
+	// not exist in the (non-empty) set of trusted keys for that root.
+	mistrustedIDProviderPrefix = "mistrusted/"
+)
+
 // chainPublicID implements security.PublicID.
 type chainPublicID struct {
 	certificates []wire.Certificate
@@ -50,10 +61,14 @@
 
 func (id *chainPublicID) String() string {
 	// Add a prefix if the identity provider is not trusted.
-	if keys.LevelOfTrust(id.rootKey, id.certificates[0].Name) != keys.Trusted {
-		return wire.UntrustedIDProviderPrefix + id.name
+	switch keys.LevelOfTrust(id.rootKey, id.certificates[0].Name) {
+	case keys.Trusted:
+		return id.name
+	case keys.Mistrusted:
+		return mistrustedIDProviderPrefix + id.name
+	default:
+		return unknownIDProviderPrefix + id.name
 	}
-	return id.name
 }
 
 func (id *chainPublicID) VomEncode() (*wire.ChainPublicID, error) {
@@ -80,24 +95,10 @@
 }
 
 // Authorize checks if all caveats on the PublicID validate with respect to the
-// provided context and that the identity provider (root public key) is not
-// mistrusted. If so returns the original PublicID. This method assumes that
+// provided context and if so returns the original PublicID. This method assumes that
 // the existing PublicID was obtained after successfully decoding a serialized
 // PublicID and hence has integrity.
 func (id *chainPublicID) Authorize(context security.Context) (security.PublicID, error) {
-	rootCert := id.certificates[0]
-	rootKey, err := rootCert.PublicKey.Decode()
-	if err != nil {
-		// unlikely to hit this case, as chainPublicID would have integrity.
-		return nil, err
-	}
-	// Implicit "caveat": The identity provider should not be mistrusted.
-	switch tl := keys.LevelOfTrust(rootKey, rootCert.Name); tl {
-	case keys.Unknown, keys.Trusted:
-		// No-op
-	default:
-		return nil, fmt.Errorf("%v public key(%v) for identity provider %q", tl, rootKey, rootCert.Name)
-	}
 	for _, c := range id.certificates {
 		if err := c.ValidateCaveats(context); err != nil {
 			return nil, fmt.Errorf("not authorized because %v", err)
@@ -217,6 +218,9 @@
 // private key, and a single self-signed certificate specifying the provided
 // name and the public key corresponding to the generated private key.
 func newChainPrivateID(name string) (security.PrivateID, error) {
+	if err := wire.ValidateBlessingName(name); err != nil {
+		return nil, err
+	}
 	key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
 	if err != nil {
 		return nil, err
diff --git a/runtimes/google/security/identity_test.go b/runtimes/google/security/identity_test.go
index 5c3a141..18b43bf 100644
--- a/runtimes/google/security/identity_test.go
+++ b/runtimes/google/security/identity_test.go
@@ -16,6 +16,28 @@
 
 type S []string
 
+func TestNewPrivateID(t *testing.T) {
+	testdata := []struct {
+		name, err string
+	}{
+		{"alice", ""},
+		{"alice#google", ""},
+		{"alice@google", ""},
+		{"bob.smith", ""},
+		{"", "invalid blessing name"},
+		{"/", "invalid blessing name"},
+		{"/alice", "invalid blessing name"},
+		{"alice/", "invalid blessing name"},
+		{"google/alice", "invalid blessing name"},
+		{"google/alice/bob", "invalid blessing name"},
+	}
+	for _, d := range testdata {
+		if _, err := NewPrivateID(d.name); !matchesErrorPattern(err, d.err) {
+			t.Errorf("NewPrivateID(%q): got: %s, want to match: %s", d.name, err, d.err)
+		}
+	}
+}
+
 func TestNameAndAuth(t *testing.T) {
 	var (
 		cUnknownAlice    = newChain("alice").PublicID()
@@ -30,11 +52,10 @@
 	testdata := []struct {
 		id    security.PublicID
 		names []string
-		err   string
 	}{
 		{id: cUnknownAlice},
 		{id: cTrustedAlice, names: S{"veyron/alice"}},
-		{id: cMistrustedAlice, err: "Mistrusted"},
+		{id: cMistrustedAlice},
 		{id: sAlice, names: S{"veyron/alice", "google/alice"}},
 		{id: sBadAlice},
 		{id: sGoogleAlice, names: S{"google/alice"}},
@@ -48,9 +69,6 @@
 			t.Errorf("%q.Authorize returned: (%v, %v), exactly one return value must be nil", d.id, authID, err)
 			continue
 		}
-		if !matchesErrorPattern(err, d.err) {
-			t.Errorf("%q.Authorize returned error: %v, want to match: %q", d.id, err, d.err)
-		}
 		if err := verifyAuthorizedID(d.id, authID, d.names); err != nil {
 			t.Error(err)
 		}
diff --git a/runtimes/google/security/publicid_store.go b/runtimes/google/security/publicid_store.go
new file mode 100644
index 0000000..70dcd7d
--- /dev/null
+++ b/runtimes/google/security/publicid_store.go
@@ -0,0 +1,132 @@
+package security
+
+import (
+	"bytes"
+	"crypto/ecdsa"
+	"errors"
+	"fmt"
+	"reflect"
+	"sync"
+
+	"veyron2/security"
+)
+
+var (
+	errStoreAddMismatch = errors.New("public key does not match that of existing PublicIDs in the store")
+	errNoMatchingIDs    = errors.New("no matching PublicIDs")
+)
+
+func errCombine(err error) error {
+	return fmt.Errorf("could not combine matching PublicIDs: %s", err)
+}
+
+type taggedIDStore map[security.PublicID][]security.PrincipalPattern
+
+// publicIDStore implements security.PublicIDStore.
+type publicIDStore struct {
+	// store contains a set of PublicIDs mapped to a set of (peer) patterns. The patterns
+	// indicate the set of peers against whom the PublicID can be used. All PublicIDs in
+	// the store must have the same public key.
+	store taggedIDStore
+	// publicKey is the common public key of all PublicIDs held in the store.
+	publicKey *ecdsa.PublicKey
+	// defaultPattern is the default PrincipalPattern to be used to select
+	// PublicIDs from the store in absence of any other search criterea.
+	defaultPattern security.PrincipalPattern
+	mu             sync.RWMutex
+}
+
+func (s *publicIDStore) addTaggedID(id security.PublicID, peerPattern security.PrincipalPattern) {
+	switch p := id.(type) {
+	case *setPublicID:
+		for _, ip := range *p {
+			s.addTaggedID(ip, peerPattern)
+		}
+	default:
+		// TODO(ataly): Should we restrict this case to just PublicIDs of type *chainPublicID?
+		s.store[id] = append(s.store[id], peerPattern)
+	}
+}
+
+func (s *publicIDStore) Add(id security.PublicID, peerPattern security.PrincipalPattern) error {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+	if s.publicKey != nil && !reflect.DeepEqual(id.PublicKey(), s.publicKey) {
+		return errStoreAddMismatch
+	}
+	if s.publicKey == nil {
+		s.publicKey = id.PublicKey()
+	}
+	s.addTaggedID(id, peerPattern)
+	return nil
+}
+
+func (s *publicIDStore) ForPeer(peer security.PublicID) (security.PublicID, error) {
+	s.mu.RLock()
+	defer s.mu.RUnlock()
+	var matchingIDs []security.PublicID
+	for id, peerPatterns := range s.store {
+		for _, peerPattern := range peerPatterns {
+			if peer.Match(peerPattern) {
+				matchingIDs = append(matchingIDs, id)
+				break
+			}
+		}
+	}
+	id, err := NewSetPublicID(matchingIDs...)
+	if err != nil {
+		return nil, errCombine(err)
+	}
+	if id == nil {
+		return nil, errNoMatchingIDs
+	}
+	return id, nil
+}
+
+func (s *publicIDStore) DefaultPublicID() (security.PublicID, error) {
+	s.mu.RLock()
+	defer s.mu.RUnlock()
+	var matchingIDs []security.PublicID
+	for id, _ := range s.store {
+		if id.Match(s.defaultPattern) {
+			matchingIDs = append(matchingIDs, id)
+		}
+	}
+	id, err := NewSetPublicID(matchingIDs...)
+	if err != nil {
+		return nil, errCombine(err)
+	}
+	if id == nil {
+		return nil, errNoMatchingIDs
+	}
+	return id, nil
+}
+
+func (s *publicIDStore) SetDefaultPrincipalPattern(pattern security.PrincipalPattern) {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+	// TODO(ataly, ashankar): Should we check that the pattern is well-formed?
+	s.defaultPattern = pattern
+}
+
+func (s *publicIDStore) String() string {
+	var buf bytes.Buffer
+	buf.WriteString("&publicIDStore{\n")
+	buf.WriteString("  store: {\n")
+	for id, peerPatterns := range s.store {
+		buf.WriteString(fmt.Sprintf("    %s: %s,\n", id, peerPatterns))
+	}
+	buf.WriteString(fmt.Sprintf("  },\n"))
+	buf.WriteString(fmt.Sprintf("  defaultPattern: %s,\n", s.defaultPattern))
+	buf.WriteString("}")
+	return buf.String()
+}
+
+// NewPublicIDStore returns a new security.PublicIDStore with an empty
+// set of PublicIDs, and the default pattern "*" matched by all PublicIDs.
+func NewPublicIDStore() security.PublicIDStore {
+	return &publicIDStore{
+		store:          make(taggedIDStore),
+		defaultPattern: security.AllPrincipals,
+	}
+}
diff --git a/runtimes/google/security/publicid_store_test.go b/runtimes/google/security/publicid_store_test.go
new file mode 100644
index 0000000..14b2f4c
--- /dev/null
+++ b/runtimes/google/security/publicid_store_test.go
@@ -0,0 +1,141 @@
+package security
+
+import (
+	"crypto/ecdsa"
+	"reflect"
+	"testing"
+
+	"veyron2/security"
+)
+
+// verifyNamesAndPublicKey checks that the provided id has exactly the provided
+// set of names and the provided public key. If the provided set is empty then
+// the provided error must be errNoMatchingIDs.
+func verifyNamesAndPublicKey(id security.PublicID, err error, names []string, pkey *ecdsa.PublicKey) bool {
+	if id == nil {
+		return err == errNoMatchingIDs && len(names) == 0
+	}
+	idNamesMap := make(map[string]bool)
+	namesMap := make(map[string]bool)
+	for _, n := range id.Names() {
+		idNamesMap[n] = true
+	}
+	for _, n := range names {
+		namesMap[n] = true
+	}
+	return reflect.DeepEqual(idNamesMap, namesMap) && reflect.DeepEqual(id.PublicKey(), pkey)
+}
+
+func TestStoreAdd(t *testing.T) {
+	var (
+		// test principals
+		cAlice       = newChain("alice")
+		cBob         = newChain("bob")
+		cVeyronAlice = derive(bless(cAlice.PublicID(), veyronChain, "alice", nil), cAlice)
+
+		sAlice = newSetPublicID(cAlice.PublicID(), cVeyronAlice.PublicID())
+	)
+	s := NewPublicIDStore()
+	// First Add should succeed for any PublicID (cAlice.PublicID() below)
+	if err := s.Add(cAlice.PublicID(), "alice/*"); err != nil {
+		t.Fatalf("%q.Add(%q, ...) failed unexpectedly: %s", s, cAlice, err)
+	}
+	// Subsequent Adds must succeed only for PublicIDs with cAlice's public key.
+	if err := s.Add(cVeyronAlice.PublicID(), "*"); err != nil {
+		t.Fatalf("%q.Add(%q, ...) failed unexpectedly: %s", s, cVeyronAlice, err)
+	}
+	if err := s.Add(sAlice, "alice/*"); err != nil {
+		t.Fatalf("%q.Add(%q, ...) failed unexpectedly: %s", s, sAlice, err)
+	}
+	if got, want := s.Add(cBob.PublicID(), "bob/*"), errStoreAddMismatch; got != want {
+		t.Fatalf("%q.Add(%q, ...): got: %s, want: %s", s, cBob, got, want)
+	}
+}
+
+func TestStoreGetters(t *testing.T) {
+	add := func(s security.PublicIDStore, id security.PublicID, peers security.PrincipalPattern) {
+		if err := s.Add(id, peers); err != nil {
+			t.Fatalf("Add(%q, %q) failed unexpectedly: %s", id, peers, err)
+		}
+	}
+	var (
+		// test principals
+		cAlice            = newChain("alice")
+		cBob              = newChain("bob")
+		cVService         = newChain("vservice")
+		cGService         = newChain("gservice")
+		cApp              = newChain("app")
+		cVeyronService    = derive(bless(cVService.PublicID(), veyronChain, "service", nil), cVService)
+		cGoogleService    = derive(bless(cGService.PublicID(), googleChain, "service", nil), cGService)
+		cGoogleServiceApp = derive(bless(cApp.PublicID(), cGoogleService, "app", nil), cApp)
+		// PublicIDs for Alice's PublicIDStore
+		cGoogleAlice        = bless(cAlice.PublicID(), googleChain, "alice", nil)
+		cVeyronAlice        = bless(cAlice.PublicID(), veyronChain, "alice", nil)
+		cGoogleServiceAlice = bless(cAlice.PublicID(), cGoogleService, "user-42", nil)
+		cVeyronServiceAlice = bless(cAlice.PublicID(), cVeyronService, "user-24", nil)
+
+		sGoogleAlice = newSetPublicID(cGoogleServiceAlice, cGoogleAlice)
+		sAllAlice    = newSetPublicID(sGoogleAlice, cVeyronAlice, cVeyronServiceAlice)
+		// TODO(ataly): Test with SetPublicIDs as well.
+	)
+
+	// Create a new PublicIDStore and add Add Alice's PublicIDs to the store.
+	s := NewPublicIDStore()
+	add(s, cGoogleAlice, "google")                  // use cGoogleAlice against all peers matching "google/*"
+	add(s, cGoogleAlice, "veyron")                  // use cGoogleAlice against all peers matching "veyron/*" as well
+	add(s, cVeyronAlice, "veyron/*")                // use cVeyronAlice against peers matching "veyron/*"
+	add(s, cVeyronAlice, "google")                  // use cVeyronAlice against peers matching "veyron/*"
+	add(s, cVeyronServiceAlice, "veyron/service/*") // use cVeyronAlice against peers matching "veyron/service*"
+	add(s, cGoogleServiceAlice, "google/service/*") // use cGoogleServiceAlice against peers matching "google/service/*"
+	add(s, sGoogleAlice, "google/service")          // use any PublicID from sGoogleAlice against peers matching "google/service"
+	add(s, sAllAlice, "veyron")                     // use any PublicID from sAllAlice against peers matching "veyron"
+
+	pkey := cAlice.PublicID().PublicKey()
+
+	// Test ForPeer.
+	testDataByPeer := []struct {
+		peer  security.PublicID
+		names []string
+	}{
+		{veyronChain.PublicID(), []string{"google/alice", "veyron/alice", "veyron/service/user-24", "google/service/user-42"}},
+		{cVeyronService.PublicID(), []string{"veyron/alice", "veyron/service/user-24"}},
+		{googleChain.PublicID(), []string{"veyron/alice", "google/alice", "google/service/user-42"}},
+		{cGoogleService.PublicID(), []string{"google/alice", "google/service/user-42"}},
+		{cGoogleServiceApp.PublicID(), []string{"google/service/user-42"}},
+		{cBob.PublicID(), nil},
+	}
+	for _, d := range testDataByPeer {
+		if got, err := s.ForPeer(d.peer); !verifyNamesAndPublicKey(got, err, d.names, pkey) {
+			t.Errorf("%s.ForPeer(%s): got: %q, want PublicID with the exact set of names %q", s, d.peer, got, d.names)
+		}
+	}
+
+	// Test initial DefaultPublicID -- we expect a PublicID with the union of the sets of names of all
+	// PublicIDs in the store.
+	defaultNames := []string{"google/alice", "veyron/alice", "veyron/service/user-24", "google/service/user-42"}
+	if got, err := s.DefaultPublicID(); !verifyNamesAndPublicKey(got, err, defaultNames, pkey) {
+		t.Errorf("%s.DefaultPublicID(): got: %s, want PublicID with the exact set of names: %s", s, got, defaultNames)
+	}
+
+	// Test SetDefaultPrincipalPattern.
+	testDataByPrincipalPattern := []struct {
+		defaultPattern security.PrincipalPattern
+		defaultNames   []string
+	}{
+		{"veyron", nil},
+		{"veyron/*", []string{"veyron/alice", "veyron/service/user-24"}},
+		{"veyron/alice", []string{"veyron/alice"}},
+		{"veyron/service/*", []string{"veyron/service/user-24"}},
+		{"google", nil},
+		{"google/*", []string{"google/alice", "google/service/user-42"}},
+		{"google/alice", []string{"google/alice"}},
+		{"google/service/*", []string{"google/service/user-42"}},
+		{"bob", nil},
+	}
+	for _, d := range testDataByPrincipalPattern {
+		s.SetDefaultPrincipalPattern(d.defaultPattern)
+		if got, err := s.DefaultPublicID(); !verifyNamesAndPublicKey(got, err, d.defaultNames, pkey) {
+			t.Errorf("%s.DefaultPublicID(): got: %s, want PublicID with the exact set of names: %s", s, got, d.defaultNames)
+		}
+	}
+}
diff --git a/services/mgmt/node/impl/invoker.go b/services/mgmt/node/impl/invoker.go
index 819bf73..bcd0e5a 100644
--- a/services/mgmt/node/impl/invoker.go
+++ b/services/mgmt/node/impl/invoker.go
@@ -522,6 +522,10 @@
 			}
 			return errOperationFailed
 		}
+		// Since the resolution of mtime for files is seconds,
+		// the test sleeps for a second to make sure it can
+		// check whether the "current" symlink is updated.
+		time.Sleep(time.Second)
 		if err := nmClient.Revert(rt.R().NewContext()); err != nil {
 			if err := handle.Clean(); err != nil {
 				vlog.Errorf("Clean() failed: %v", err)
diff --git a/services/mounttable/lib/neighborhood.go b/services/mounttable/lib/neighborhood.go
index 4165ac3..b11588e 100644
--- a/services/mounttable/lib/neighborhood.go
+++ b/services/mounttable/lib/neighborhood.go
@@ -19,7 +19,7 @@
 	"code.google.com/p/mdns"
 )
 
-const endpointPrefix = "endpoint:"
+const addressPrefix = "address:"
 
 // neighborhood defines a set of machines on the same multicast media.
 type neighborhood struct {
@@ -34,8 +34,14 @@
 	nh    *neighborhood
 }
 
-func getPort(e naming.Endpoint) uint16 {
-	addr := e.Addr()
+func getPort(address string) uint16 {
+	epAddr, _ := naming.SplitAddressName(address)
+
+	ep, err := rt.R().NewEndpoint(epAddr)
+	if err != nil {
+		return 0
+	}
+	addr := ep.Addr()
 	if addr == nil {
 		return 0
 	}
@@ -53,18 +59,18 @@
 	return uint16(port)
 }
 
-func newNeighborhoodServer(prefix, host string, eps []naming.Endpoint, loopback bool) (*neighborhood, error) {
-	// Create the TXT contents with endpoints to announce.   Also pick up a port number.
+func newNeighborhoodServer(prefix, host string, addresses []string, loopback bool) (*neighborhood, error) {
+	// Create the TXT contents with addresses to announce. Also pick up a port number.
 	var txt []string
 	var port uint16
-	for _, ep := range eps {
-		txt = append(txt, endpointPrefix+ep.String())
+	for _, addr := range addresses {
+		txt = append(txt, addressPrefix+addr)
 		if port == 0 {
-			port = getPort(ep)
+			port = getPort(addr)
 		}
 	}
 	if txt == nil {
-		return nil, errors.New("neighborhood passed no useful endpoint")
+		return nil, errors.New("neighborhood passed no useful addresses")
 	}
 	if port == 0 {
 		return nil, errors.New("neighborhood couldn't determine a port to use")
@@ -88,18 +94,18 @@
 }
 
 // NewLoopbackNeighborhoodServer creates a new instance of a neighborhood server on loopback interfaces for testing.
-func NewLoopbackNeighborhoodServer(prefix, host string, eps []naming.Endpoint) (*neighborhood, error) {
-	return newNeighborhoodServer(prefix, host, eps, true)
+func NewLoopbackNeighborhoodServer(prefix, host string, addresses ...string) (*neighborhood, error) {
+	return newNeighborhoodServer(prefix, host, addresses, true)
 }
 
 // NewNeighborhoodServer creates a new instance of a neighborhood server.
-func NewNeighborhoodServer(prefix, host string, eps []naming.Endpoint) (*neighborhood, error) {
-	return newNeighborhoodServer(prefix, host, eps, false)
+func NewNeighborhoodServer(prefix, host string, addresses ...string) (*neighborhood, error) {
+	return newNeighborhoodServer(prefix, host, addresses, false)
 }
 
 // Lookup implements ipc.Dispatcher.Lookup.
 func (nh *neighborhood) Lookup(name string) (ipc.Invoker, security.Authorizer, error) {
-	vlog.Infof("*********************LookupServer '%s'\n", name)
+	vlog.VI(1).Infof("*********************LookupServer '%s'\n", name)
 	elems := strings.Split(name, "/")[nh.nelems:]
 	if name == "" {
 		elems = nil
@@ -109,7 +115,13 @@
 		elems: elems,
 		nh:    nh,
 	}
-	return ipc.ReflectInvoker(mounttable.NewServerMountTable(ns)), nil, nil
+	return ipc.ReflectInvoker(mounttable.NewServerMountTable(ns)), nh, nil
+}
+
+func (nh *neighborhood) Authorize(context security.Context) error {
+	// TODO(rthellend): Figure out whether it's OK to accept all requests
+	// unconditionally.
+	return nil
 }
 
 // Stop performs cleanup.
@@ -122,30 +134,30 @@
 	var reply []mounttable.MountedServer
 	si := nh.mdns.ResolveInstance(instance, "veyron")
 
-	// If we have any TXT records with endpoints, they take precedence.
+	// Look for any TXT records with addresses.
 	for _, rr := range si.TxtRRs {
-		// Use a map to dedup any endpoints seen
-		epmap := make(map[string]uint32)
+		// Use a map to dedup any addresses seen
+		addrMap := make(map[string]uint32)
 		for _, s := range rr.Txt {
-			if !strings.HasPrefix(s, endpointPrefix) {
+			if !strings.HasPrefix(s, addressPrefix) {
 				continue
 			}
-			epstring := s[len(endpointPrefix):]
-			// Make sure its a legal endpoint string.
-			if _, err := rt.R().NewEndpoint(epstring); err != nil {
-				continue
-			}
-			epmap[epstring] = rr.Header().Ttl
+			addr := s[len(addressPrefix):]
+			addrMap[addr] = rr.Header().Ttl
 		}
-		for epstring, ttl := range epmap {
-			reply = append(reply, mounttable.MountedServer{naming.JoinAddressName(epstring, ""), ttl})
+		for addr, ttl := range addrMap {
+			reply = append(reply, mounttable.MountedServer{addr, ttl})
 		}
 	}
+
 	if reply != nil {
 		return reply
 	}
 
 	// If we didn't get any direct endpoints, make some up from the target and port.
+	// TODO(rthellend): Do we need the code below? If we haven't received
+	// any results at this point, it would seem to indicate a bug somewhere
+	// because NeighborhoodServer won't start without at least one address.
 	for _, rr := range si.SrvRRs {
 		ips, ttl := nh.mdns.ResolveAddress(rr.Target)
 		for _, ip := range ips {
diff --git a/services/mounttable/lib/neighborhood_test.go b/services/mounttable/lib/neighborhood_test.go
index 5528fb3..707241b 100644
--- a/services/mounttable/lib/neighborhood_test.go
+++ b/services/mounttable/lib/neighborhood_test.go
@@ -38,12 +38,16 @@
 		boom(t, "Failed to Listen mount table: %s", err)
 	}
 	estr := e.String()
-
+	addresses := []string{
+		naming.JoinAddressName(estr, ""),
+		naming.JoinAddressName(estr, "suffix1"),
+		naming.JoinAddressName(estr, "suffix2"),
+	}
 	// Add neighborhood server.
 	nhPrefix := "neighborhood"
-	nhd, err := NewLoopbackNeighborhoodServer(nhPrefix, "joeblow", []naming.Endpoint{e})
+	nhd, err := NewLoopbackNeighborhoodServer(nhPrefix, "joeblow", addresses...)
 	if err != nil {
-		t.Logf("Failed to create neighborhood server: %s\n", err)
+		boom(t, "Failed to create neighborhood server: %s\n", err)
 	}
 	defer nhd.Stop()
 	if err := server.Register(nhPrefix, nhd); err != nil {
@@ -90,14 +94,22 @@
 	if len(servers) == 0 {
 		boom(t, "resolveStep returns no severs")
 	}
-
+L2:
 	for _, s := range servers {
-		address, suffix := naming.SplitAddressName(s.Server)
-		if len(suffix) != 0 {
-			boom(t, "Endpoint %s: unexpected suffix", s.Server)
+		for _, a := range addresses {
+			if a == s.Server {
+				continue L2
+			}
 		}
-		if address != e.String() {
-			boom(t, "Expected %s got %s", e, address)
+		boom(t, "Unexpected address from resolveStep result: %v", s.Server)
+	}
+L3:
+	for _, a := range addresses {
+		for _, s := range servers {
+			if a == s.Server {
+				continue L3
+			}
 		}
+		boom(t, "Missing address from resolveStep result: %v", a)
 	}
 }
diff --git a/services/mounttable/mounttabled/mounttable.go b/services/mounttable/mounttabled/mounttable.go
index 5749146..b73dca2 100644
--- a/services/mounttable/mounttabled/mounttable.go
+++ b/services/mounttable/mounttabled/mounttable.go
@@ -1,10 +1,11 @@
-// mounttabled is a simple mount table daemon.
+// mounttabled is a mount table daemon.
 package main
 
 import (
 	"flag"
 	"fmt"
 	"os"
+	"time"
 
 	"veyron2"
 	"veyron2/naming"
@@ -20,20 +21,29 @@
 	mountName = flag.String("name", "", "Name to mount this mountable as.  Empty means don't mount.")
 	// TODO(rthellend): Remove the address flag when the config manager is working.
 	address = flag.String("address", ":0", "Address to listen on.  Default is to use a randomly assigned port")
-	prefix  = flag.String("prefix", "mt", "The prefix to register the server at.")
+	prefix  = flag.String("prefix", "mt", "The prefix to register the mounttable at.")
 	aclFile = flag.String("acls", "", "ACL file. Default is to allow all access.")
+	nhName  = flag.String("neighborhood_name", "", "If non-empty, publish in the local neighborhood under this name.")
 )
 
-const usage = `%s is a simple mount table daemon.
+const usage = `%s is a mount table daemon.
 
 Usage:
 
-  %s [--name=<name>]
+  %s [--address=<local address>] [--name=<name>] [--neighborhood_name=<nh name>]
+
+  <local address> is the the local address to listen on. By default, it will
+  use a random port.
 
   <name>, if provided, causes the mount table to mount itself under that 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").
+
+  <nh name>, if provided, will enable 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 with the "nh" prefix.
 `
 
 func Usage() {
@@ -68,13 +78,37 @@
 		return
 	}
 
+	if len(*nhName) > 0 {
+		mtAddr := naming.JoinAddressName(endpoint.String(), *prefix)
+
+		nh, err := mounttable.NewNeighborhoodServer("", *nhName, mtAddr)
+		if err != nil {
+			vlog.Errorf("NewNeighborhoodServer failed: %v", err)
+			return
+		}
+		nhPrefix := "nh"
+		if err := server.Register(nhPrefix, nh); err != nil {
+			vlog.Errorf("server.Register failed to register neighborhood: %v", err)
+			return
+		}
+		nhAddr := naming.JoinAddressName(endpoint.String(), nhPrefix)
+		nhMount := naming.Join(mtAddr, nhPrefix)
+
+		ns := rt.R().Namespace()
+		forever := time.Duration(0)
+		if err = ns.Mount(rt.R().NewContext(), nhMount, nhAddr, forever); err != nil {
+			vlog.Errorf("ns.Mount failed to mount neighborhood: %v", err)
+			return
+		}
+	}
+
 	if name := *mountName; len(name) > 0 {
 		if err := server.Publish(name); err != nil {
 			vlog.Errorf("Publish(%v) failed: %v", name, err)
 			return
 		}
 		vlog.Infof("Mount table service at: %s (%s)",
-			naming.JoinAddressName(name, *prefix),
+			naming.Join(name, *prefix),
 			naming.JoinAddressName(endpoint.String(), *prefix))
 
 	} else {