Merge "profiles/internal: Add ServesLeaf/IsLeaf to the Endpoint"
diff --git a/lib/exec/child.go b/lib/exec/child.go
index cf2a141..b80033c 100644
--- a/lib/exec/child.go
+++ b/lib/exec/child.go
@@ -6,19 +6,22 @@
 
 import (
 	"encoding/binary"
-	"errors"
 	"io"
 	"os"
 	"strconv"
 	"sync"
 	"unicode/utf8"
 
+	"v.io/v23/verror"
 	"v.io/x/ref/lib/exec/consts"
 )
 
 var (
-	ErrNoVersion          = errors.New(consts.ExecVersionVariable + " environment variable missing")
-	ErrUnsupportedVersion = errors.New("Unsupported version of v.io/x/ref/lib/exec request by " + consts.ExecVersionVariable + " environment variable")
+	ErrNoVersion          = verror.Register(pkgPath+".ErrNoVersion", verror.NoRetry, "{1:}{2:} "+consts.ExecVersionVariable+" environment variable missing{:_}")
+	ErrUnsupportedVersion = verror.Register(pkgPath+".ErrUnsupportedVersion", verror.NoRetry, "{1:}{2:} Unsupported version of v.io/x/ref/lib/exec request by "+consts.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 {
@@ -92,7 +95,7 @@
 		_, c.statusErr = c.statusPipe.Write(toWrite)
 		c.statusPipe.Close()
 	} else if c.sentStatus != status {
-		return errors.New("A different status: " + c.sentStatus + " has already been sent.")
+		return verror.New(errDifferentStatusSent, nil, c.sentStatus)
 	}
 	return c.statusErr
 }
@@ -123,11 +126,11 @@
 	// version #s.
 	switch os.Getenv(consts.ExecVersionVariable) {
 	case "":
-		return nil, ErrNoVersion
+		return nil, verror.New(ErrNoVersion, nil)
 	case version1:
 		os.Setenv(consts.ExecVersionVariable, "")
 	default:
-		return nil, ErrUnsupportedVersion
+		return nil, verror.New(ErrUnsupportedVersion, nil)
 	}
 	dataPipe := os.NewFile(3, "data_rd")
 	serializedConfig, err := decodeString(dataPipe)
@@ -160,7 +163,7 @@
 		if err != nil {
 			return "", err
 		} else {
-			return "", errors.New("partial read")
+			return "", verror.New(errPartialRead, nil)
 		}
 	}
 	return string(data), nil
diff --git a/lib/exec/exec_test.go b/lib/exec/exec_test.go
index b930bd7..0eaf7af 100644
--- a/lib/exec/exec_test.go
+++ b/lib/exec/exec_test.go
@@ -17,6 +17,7 @@
 	"time"
 	"unicode/utf8"
 
+	"v.io/v23/verror"
 	vexec "v.io/x/ref/lib/exec"
 	"v.io/x/ref/lib/exec/consts"
 	// Use mock timekeeper to avoid actually sleeping during the test.
@@ -160,7 +161,7 @@
 func TestNoVersion(t *testing.T) {
 	// Make sure that Init correctly tests for the presence of VEXEC_VERSION
 	_, err := vexec.GetChildHandle()
-	if err != vexec.ErrNoVersion {
+	if verror.ErrorID(err) != vexec.ErrNoVersion.ID {
 		t.Errorf("Should be missing Version")
 	}
 }
@@ -266,7 +267,7 @@
 	cmd := helperCommand(name, "failed", "to", "start")
 	ph := vexec.NewParentHandle(cmd)
 	err := waitForReady(t, cmd, name, 4, ph)
-	if err == nil || err.Error() != "failed to start" {
+	if err == nil || !strings.Contains(err.Error(), "failed to start") {
 		t.Errorf("unexpected error: %v", err)
 	}
 }
@@ -276,7 +277,7 @@
 	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 || err.Error() != "invalid utf8 "+string(utf8.RuneError)+" in "+string(utf8.RuneError)+" error message" {
+	if err == nil || !strings.Contains(err.Error(), "invalid utf8 "+string(utf8.RuneError)+" in "+string(utf8.RuneError)+" error message") {
 		t.Errorf("unexpected error: %v", err)
 	}
 }
@@ -288,7 +289,7 @@
 	stderr, _ := cmd.StderrPipe()
 	ph := vexec.NewParentHandle(cmd)
 	err := waitForReady(t, cmd, name, 1, ph)
-	if err != vexec.ErrTimeout {
+	if verror.ErrorID(err) != vexec.ErrTimeout.ID {
 		t.Errorf("Failed to get timeout: got %v\n", err)
 	} else {
 		// block waiting for error from child
@@ -322,7 +323,7 @@
 		tk.AdvanceTime(toWait)
 	}()
 	err = waitForReady(t, cmd, name, 1, ph)
-	if err != vexec.ErrTimeout {
+	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
@@ -449,7 +450,7 @@
 		<-tk.Requests()
 		tk.AdvanceTime(2 * time.Second)
 	}()
-	if got, want := ph.Wait(time.Second), vexec.ErrTimeout; got == nil || got.Error() != want.Error() {
+	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 {
diff --git a/lib/exec/noprotocol_test.go b/lib/exec/noprotocol_test.go
index 8e9aa21..78564c9 100644
--- a/lib/exec/noprotocol_test.go
+++ b/lib/exec/noprotocol_test.go
@@ -12,6 +12,7 @@
 	"testing"
 	"time"
 
+	"v.io/v23/verror"
 	vexec "v.io/x/ref/lib/exec"
 	"v.io/x/ref/lib/exec/consts"
 )
@@ -23,7 +24,7 @@
 	if err := ph.Start(); err != nil {
 		t.Fatal(err)
 	}
-	if got, want := ph.WaitForReady(time.Minute), vexec.ErrNotUsingProtocol; got != want {
+	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=.*", consts.ExecVersionVariable))
diff --git a/lib/exec/parent.go b/lib/exec/parent.go
index fc11a2b..d011cfb 100644
--- a/lib/exec/parent.go
+++ b/lib/exec/parent.go
@@ -7,7 +7,6 @@
 import (
 	"bytes"
 	"encoding/binary"
-	"errors"
 	"fmt"
 	"io"
 	"os"
@@ -18,17 +17,27 @@
 	"syscall"
 	"time"
 
+	"v.io/v23/verror"
+
 	"v.io/x/lib/vlog"
 
 	"v.io/x/ref/lib/exec/consts"
 	"v.io/x/ref/lib/timekeeper"
 )
 
+const pkgPath = "v.io/x/ref/lib/exec"
+
 var (
-	ErrAuthTimeout      = errors.New("timeout in auth handshake")
-	ErrTimeout          = errors.New("timeout waiting for child")
-	ErrSecretTooLarge   = errors.New("secret is too large")
-	ErrNotUsingProtocol = errors.New("not using parent/child exec protocol")
+	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.
@@ -234,7 +243,7 @@
 // WaitForReady will wait for the child process to become ready.
 func (p *ParentHandle) WaitForReady(timeout time.Duration) error {
 	if !p.protocol {
-		return ErrNotUsingProtocol
+		return verror.New(ErrNotUsingProtocol, nil)
 	}
 	// An invariant of WaitForReady is that both statusWrite and statusRead
 	// get closed before WaitForStatus returns (statusRead gets closed by
@@ -260,11 +269,11 @@
 				return nil
 			}
 			if strings.HasPrefix(m, failedStatus) {
-				return fmt.Errorf("%s", strings.TrimPrefix(m, failedStatus))
+				return verror.New(errFailedStatus, nil, strings.TrimPrefix(m, failedStatus))
 			}
-			return fmt.Errorf("unrecognised status from subprocess: %q", m)
+			return verror.New(errUnrecognizedStatus, nil, m)
 		default:
-			return fmt.Errorf("unexpected type %T", m)
+			return verror.New(errUnexpectedType, nil, fmt.Sprintf("%T", m))
 		}
 	case <-p.tk.After(timeout):
 		vlog.Errorf("Timed out waiting for child status")
@@ -281,7 +290,7 @@
 		// c.  Waiting on c ensures that r.Close() in waitForStatus
 		// already executed.
 		<-c
-		return ErrTimeout
+		return verror.New(ErrTimeout, nil)
 	}
 	panic("unreachable")
 }
@@ -315,7 +324,7 @@
 	if timeout > 0 {
 		select {
 		case <-p.tk.After(timeout):
-			return ErrTimeout
+			return verror.New(ErrTimeout, nil)
 		case err := <-c:
 			return err
 		}
@@ -350,7 +359,7 @@
 // Kill kills the child process.
 func (p *ParentHandle) Kill() error {
 	if p.c.Process == nil {
-		return errors.New("no such process")
+		return verror.New(errNoSuchProcess, nil)
 	}
 	return p.c.Process.Kill()
 }
@@ -358,7 +367,7 @@
 // Signal sends the given signal to the child process.
 func (p *ParentHandle) Signal(sig syscall.Signal) error {
 	if p.c.Process == nil {
-		return errors.New("no such process")
+		return verror.New(errNoSuchProcess, nil)
 	}
 	return syscall.Kill(p.c.Process.Pid, sig)
 }
@@ -380,7 +389,7 @@
 		if err != nil {
 			return err
 		} else {
-			return errors.New("partial write")
+			return verror.New(errPartialWrite, nil)
 		}
 	}
 	return nil
diff --git a/lib/exec/util.go b/lib/exec/util.go
index 813730c..323097b 100644
--- a/lib/exec/util.go
+++ b/lib/exec/util.go
@@ -5,8 +5,13 @@
 package exec
 
 import (
-	"errors"
 	"strings"
+
+	"v.io/v23/verror"
+)
+
+var (
+	errNotFound = verror.Register(pkgPath+".errNotFound", verror.NoRetry, "{1:}{2:} not found{:_}")
 )
 
 // Getenv retrieves the value of the given variable from the given
@@ -17,7 +22,7 @@
 			return strings.TrimPrefix(v, name+"="), nil
 		}
 	}
-	return "", errors.New("not found")
+	return "", verror.New(errNotFound, nil)
 }
 
 // Setenv updates / adds the value assignment for the given variable
diff --git a/profiles/internal/rt/mgmt.go b/profiles/internal/rt/mgmt.go
index d27d689..6033e93 100644
--- a/profiles/internal/rt/mgmt.go
+++ b/profiles/internal/rt/mgmt.go
@@ -26,12 +26,14 @@
 
 func (rt *Runtime) initMgmt(ctx *context.T) error {
 	handle, err := exec.GetChildHandle()
-	if err == exec.ErrNoVersion {
+	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 if err != nil {
+	} else {
 		return err
 	}
 	parentName, err := handle.Config.Get(mgmt.ParentNameConfigKey)
diff --git a/profiles/internal/rt/security.go b/profiles/internal/rt/security.go
index ccdc157..9bdbb59 100644
--- a/profiles/internal/rt/security.go
+++ b/profiles/internal/rt/security.go
@@ -15,6 +15,7 @@
 	"v.io/v23/mgmt"
 	"v.io/v23/rpc"
 	"v.io/v23/security"
+	"v.io/v23/verror"
 
 	"v.io/x/ref/lib/exec"
 	vsecurity "v.io/x/ref/security"
@@ -71,7 +72,7 @@
 // agent.
 func agentFD() (int, error) {
 	handle, err := exec.GetChildHandle()
-	if err != nil && err != exec.ErrNoVersion {
+	if err != nil && verror.ErrorID(err) != exec.ErrNoVersion.ID {
 		return -1, err
 	}
 	var fd string
diff --git a/profiles/internal/testing/mocks/mocknet/mocknet.go b/profiles/internal/testing/mocks/mocknet/mocknet.go
new file mode 100644
index 0000000..0252003
--- /dev/null
+++ b/profiles/internal/testing/mocks/mocknet/mocknet.go
@@ -0,0 +1,262 @@
+// Copyright 2015 The Vanadium Authors. 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 (
+	"net"
+	"sync"
+	"time"
+)
+
+// 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
+)
+
+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)
+}
+
+// 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, 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{
+			opts:        opts,
+			conn:        c,
+			txDropAfter: opts.TxDropAfter(),
+		}
+	}
+	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)
+}
+
+// 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()
+}
diff --git a/profiles/internal/testing/mocks/mocknet/mocknet_test.go b/profiles/internal/testing/mocks/mocknet/mocknet_test.go
new file mode 100644
index 0000000..1e62ac7
--- /dev/null
+++ b/profiles/internal/testing/mocks/mocknet/mocknet_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 mocknet_test
+
+import (
+	"errors"
+	"io"
+	"net"
+	"reflect"
+	"sync"
+	"testing"
+	"time"
+
+	"v.io/x/ref/profiles/internal/testing/mocks/mocknet"
+)
+
+//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 rxerr error
+	var wg sync.WaitGroup
+	wg.Add(1)
+	go func() {
+		rxconn, rxerr = 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 rxerr error
+		var wg sync.WaitGroup
+		wg.Add(1)
+		go func() {
+			rxconn, rxerr = 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(); got != want {
+					t.Fatalf("%d: got %v, want %v", 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 rxerr error
+		var wg sync.WaitGroup
+		wg.Add(1)
+		go func() {
+			rxconn, rxerr = 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)
+			}
+		}
+	}
+}
diff --git a/profiles/internal/util.go b/profiles/internal/util.go
index a6c45c7..600ff0a 100644
--- a/profiles/internal/util.go
+++ b/profiles/internal/util.go
@@ -10,6 +10,7 @@
 	"strings"
 
 	"v.io/v23/rpc"
+	"v.io/v23/verror"
 	"v.io/x/lib/vlog"
 
 	"v.io/x/lib/netstate"
@@ -23,14 +24,13 @@
 // profile can use or modify the flags as it pleases.
 func ParseFlags(f *flags.Flags) error {
 	handle, err := exec.GetChildHandle()
-	switch err {
-	case exec.ErrNoVersion:
-		// The process has not been started through the vanadium exec
-		// library. No further action is needed.
-	case nil:
+	if err == nil {
 		// The process has been started through the vanadium exec
 		// library.
-	default:
+	} 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
 	}
 
diff --git a/services/mgmt/device/impl/callback.go b/services/mgmt/device/impl/callback.go
index 6ffef42..4241e60 100644
--- a/services/mgmt/device/impl/callback.go
+++ b/services/mgmt/device/impl/callback.go
@@ -7,6 +7,7 @@
 import (
 	"v.io/v23/context"
 	"v.io/v23/mgmt"
+	"v.io/v23/verror"
 	"v.io/x/lib/vlog"
 
 	"v.io/x/ref/lib/exec"
@@ -17,8 +18,7 @@
 // is expected to be this device manager's object name).
 func InvokeCallback(ctx *context.T, name string) {
 	handle, err := exec.GetChildHandle()
-	switch err {
-	case nil:
+	if err == nil {
 		// Device manager was started by self-update, notify the parent.
 		callbackName, err := handle.Config.Get(mgmt.ParentNameConfigKey)
 		if err != nil {
@@ -31,8 +31,7 @@
 		if err := client.Set(ctx, mgmt.ChildNameConfigKey, name); err != nil {
 			vlog.Fatalf("Set(%v, %v) failed: %v", mgmt.ChildNameConfigKey, name, err)
 		}
-	case exec.ErrNoVersion:
-	default:
+	} else if verror.ErrorID(err) != exec.ErrNoVersion.ID {
 		vlog.Fatalf("GetChildHandle() failed: %v", err)
 	}
 }
diff --git a/test/modules/exec.go b/test/modules/exec.go
index d9837c7..35c34e6 100644
--- a/test/modules/exec.go
+++ b/test/modules/exec.go
@@ -15,6 +15,7 @@
 	"time"
 
 	"v.io/v23/mgmt"
+	"v.io/v23/verror"
 	"v.io/x/lib/vlog"
 	vexec "v.io/x/ref/lib/exec"
 	"v.io/x/ref/test/expect"
@@ -248,7 +249,7 @@
 		// The child has exited already.
 	case <-time.After(eh.opts.ShutdownTimeout):
 		// Time out waiting for child to exit.
-		procErr = vexec.ErrTimeout
+		procErr = verror.New(vexec.ErrTimeout, nil)
 		// Force close stdout to unblock any readers of stdout
 		// (including the drain loop started above).
 		eh.stdout.Close()
diff --git a/test/modules/modules_test.go b/test/modules/modules_test.go
index ced8fc8..b0745f0 100644
--- a/test/modules/modules_test.go
+++ b/test/modules/modules_test.go
@@ -21,6 +21,7 @@
 	"time"
 
 	"v.io/v23"
+	"v.io/v23/verror"
 
 	"v.io/x/ref/lib/exec"
 	execconsts "v.io/x/ref/lib/exec/consts"
@@ -469,8 +470,8 @@
 		t.Fatalf("unexpected error: %s", err)
 	}
 	var stdoutBuf, stderrBuf bytes.Buffer
-	if err := sh.Cleanup(&stdoutBuf, &stderrBuf); err == nil || err.Error() != exec.ErrTimeout.Error() {
-		t.Errorf("unexpected error in Cleanup: got %v, want %v", err, exec.ErrTimeout)
+	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)