Merge branch 'master' into discovery
diff --git a/cmd/sb/shell.go b/cmd/sb/shell.go
index e3bfe93..9d5e2e2 100644
--- a/cmd/sb/shell.go
+++ b/cmd/sb/shell.go
@@ -169,7 +169,18 @@
 	return d, nil
 }
 
-func dumpDB(ctx *context.T, w io.Writer, d nosql.Database) error {
+func mergeErrors(errs []error) error {
+	if len(errs) == 0 {
+		return nil
+	}
+	err := errs[0]
+	for _, e := range errs[1:] {
+		err = fmt.Errorf("%v\n%v", err, e)
+	}
+	return err
+}
+
+func dumpTables(ctx *context.T, w io.Writer, d nosql.Database) error {
 	tables, err := d.ListTables(ctx)
 	if err != nil {
 		return fmt.Errorf("failed listing tables: %v", err)
@@ -182,15 +193,43 @@
 		}
 	}
 	if len(errs) > 0 {
-		err := fmt.Errorf("failed dumping %d of %d tables:", len(errs), len(tables))
-		for _, e := range errs {
-			err = fmt.Errorf("%v\n%v", err, e)
-		}
-		return err
+		return fmt.Errorf("failed dumping %d of %d tables:\n%v", len(errs), len(tables), mergeErrors(errs))
 	}
 	return nil
 }
 
+func dumpSyncgroups(ctx *context.T, w io.Writer, d nosql.Database) error {
+	sgNames, err := d.GetSyncgroupNames(ctx)
+	if err != nil {
+		return fmt.Errorf("failed listing syncgroups: %v", err)
+	}
+	var errs []error
+	for _, sgName := range sgNames {
+		fmt.Fprintf(w, "syncgroup: %s\n", sgName)
+		sg := d.Syncgroup(sgName)
+		if spec, version, err := sg.GetSpec(ctx); err != nil {
+			errs = append(errs, err)
+		} else {
+			fmt.Fprintf(w, "%+v (version: \"%s\")\n", spec, version)
+		}
+	}
+	if len(errs) > 0 {
+		return fmt.Errorf("failed dumping %d of %d syncgroups:\n%v", len(errs), len(sgNames), mergeErrors(errs))
+	}
+	return nil
+}
+
+func dumpDB(ctx *context.T, w io.Writer, d nosql.Database) error {
+	var errors []error
+	if err := dumpTables(ctx, w, d); err != nil {
+		errors = append(errors, fmt.Errorf("failed dumping tables: %v", err))
+	}
+	if err := dumpSyncgroups(ctx, w, d); err != nil {
+		errors = append(errors, fmt.Errorf("failed dumping syncgroups: %v", err))
+	}
+	return mergeErrors(errors)
+}
+
 func makeDemoDB(ctx *context.T, w io.Writer, d nosql.Database) error {
 	if err := demodb.PopulateDemoDB(ctx, d); err == nil {
 		fmt.Fprintln(w, "Demo tables created and populated.")
diff --git a/lib/security/bcrypter/crypter.go b/lib/security/bcrypter/crypter.go
index 5e93126..5b59829 100644
--- a/lib/security/bcrypter/crypter.go
+++ b/lib/security/bcrypter/crypter.go
@@ -88,15 +88,15 @@
 	keys []ibe.PrivateKey
 }
 
-// Encrypt encrypts the provided fixed-length 'plaintext' so that it can
-// only be decrypted by a crypter possessing a private key for a blessing
-// matching the provided blessing pattern.
+// Encrypt encrypts the provided 'plaintext' so that it can only be decrypted
+// by a crypter possessing a private key for a blessing matching the provided
+// blessing pattern.
 //
 // Encryption makes use of the public parameters of the identity provider
 // that is authoritative on the set of blessings that match the provided
 // blessing pattern. These paramaters must have been previously added to
 // this crypter via AddParams.
-func (c *Crypter) Encrypt(ctx *context.T, forPattern security.BlessingPattern, plaintext *[32]byte) (*Ciphertext, error) {
+func (c *Crypter) Encrypt(ctx *context.T, forPattern security.BlessingPattern, plaintext []byte) (*Ciphertext, error) {
 	if !forPattern.IsValid() {
 		return nil, fmt.Errorf("provided blessing pattern %v is invalid", forPattern)
 	}
@@ -109,8 +109,8 @@
 			continue
 		}
 		for _, ibeParams := range ibeParamsList {
-			ctxt := make([]byte, ibe.CiphertextSize)
-			if err := ibeParams.Encrypt(string(forPattern), (*plaintext)[:], ctxt); err != nil {
+			ctxt := make([]byte, len(plaintext)+ibeParams.CiphertextOverhead())
+			if err := ibeParams.Encrypt(string(forPattern), plaintext, ctxt); err != nil {
 				return nil, NewErrInternal(ctx, err)
 			}
 			paramsId, err := idParams(ibeParams)
@@ -127,16 +127,24 @@
 	return ciphertext, nil
 }
 
+func decrypt(key ibe.PrivateKey, ciphertext []byte) ([]byte, error) {
+	overhead := key.Params().CiphertextOverhead()
+	if got := len(ciphertext); got < overhead {
+		return nil, fmt.Errorf("ciphertext is of size %v bytes, want at least %v bytes", got, overhead)
+	}
+	plaintext := make([]byte, len(ciphertext)-overhead)
+	if err := key.Decrypt(ciphertext, plaintext); err != nil {
+		return nil, err
+	}
+	return plaintext, nil
+}
+
 // Decrypt decrypts the provided 'ciphertext' and returns the corresponding
 // plaintext.
 //
 // Decryption succeeds only if this crypter possesses a private key for a
 // blessing that matches the blessing pattern corresponding to the ciphertext.
-func (c *Crypter) Decrypt(ctx *context.T, ciphertext *Ciphertext) (*[32]byte, error) {
-	var (
-		plaintext [32]byte
-		keyFound  bool
-	)
+func (c *Crypter) Decrypt(ctx *context.T, ciphertext *Ciphertext) ([]byte, error) {
 	c.mu.RLock()
 	defer c.mu.RUnlock()
 	for paramsId, cbytes := range ciphertext.wire.Bytes {
@@ -144,16 +152,13 @@
 			continue
 		} else if key, found := keys[ciphertext.wire.PatternId]; !found {
 			continue
-		} else if err := key.Decrypt(cbytes, plaintext[:]); err != nil {
-			return nil, NewErrInternal(ctx, err)
+		} else if ptxt, err := decrypt(key, cbytes); err != nil {
+			return nil, err
+		} else {
+			return ptxt, nil
 		}
-		keyFound = true
-		break
 	}
-	if !keyFound {
-		return nil, NewErrPrivateKeyNotFound(ctx)
-	}
-	return &plaintext, nil
+	return nil, NewErrPrivateKeyNotFound(ctx)
 }
 
 // AddKey adds the provided private key 'key' and the associated public
diff --git a/lib/security/bcrypter/crypter_test.go b/lib/security/bcrypter/crypter_test.go
index cea179c..7efaf28 100644
--- a/lib/security/bcrypter/crypter_test.go
+++ b/lib/security/bcrypter/crypter_test.go
@@ -25,12 +25,8 @@
 	return NewRoot(name, master)
 }
 
-func newPlaintext() [32]byte {
-	var m [32]byte
-	if n := copy(m[:], []byte("AThirtyTwoBytePieceOfTextThisIs!")); n != len(m) {
-		panic(fmt.Errorf("plaintext string must be %d bytes, not %d", len(m), n))
-	}
-	return m
+func newPlaintext() []byte {
+	return []byte("AThirtyTwoBytePieceOfTextThisIs!")
 }
 
 func TextExtract(t *testing.T) {
@@ -79,7 +75,7 @@
 	)
 
 	// empty encrypter should not be able to encrypt for any pattern.
-	if _, err := encrypter.Encrypt(ctx, "google/youtube/alice", &ptxt); verror.ErrorID(err) != ErrNoParams.ID {
+	if _, err := encrypter.Encrypt(ctx, "google/youtube/alice", ptxt); verror.ErrorID(err) != ErrNoParams.ID {
 		t.Fatalf("Got error %v, wanted error with ID %v", err, ErrNoParams.ID)
 	}
 
@@ -88,7 +84,7 @@
 		t.Fatal(err)
 	}
 	// encrypting for "google/youtube/alice" should now succeed.
-	if _, err := encrypter.Encrypt(ctx, "google/youtube/alice", &ptxt); err != nil {
+	if _, err := encrypter.Encrypt(ctx, "google/youtube/alice", ptxt); err != nil {
 		t.Fatal(err)
 	}
 
@@ -96,7 +92,7 @@
 	// does not have params that are authoritative on all blessings matching
 	// the pattern "google" (the googleYoutube params are authoritative on
 	// blessings matching "google/youtube").
-	if _, err := encrypter.Encrypt(ctx, "google", &ptxt); verror.ErrorID(err) != ErrNoParams.ID {
+	if _, err := encrypter.Encrypt(ctx, "google", ptxt); verror.ErrorID(err) != ErrNoParams.ID {
 		t.Fatalf("Got error %v, wanted error with ID %v", err, ErrNoParams.ID)
 	}
 	// add google's params to the encrypter.
@@ -104,21 +100,21 @@
 		t.Fatal(err)
 	}
 	// encrypting for "google" should now succeed.
-	if _, err := encrypter.Encrypt(ctx, "google", &ptxt); err != nil {
+	if _, err := encrypter.Encrypt(ctx, "google", ptxt); err != nil {
 		t.Fatal(err)
 	}
 
 	// Encryption should succeed for all of the following patterns
 	patterns := []security.BlessingPattern{"google", "google/$", "google/alice", "google/bob", "google/bob/phone"}
 	for _, p := range patterns {
-		if _, err := encrypter.Encrypt(ctx, p, &ptxt); err != nil {
+		if _, err := encrypter.Encrypt(ctx, p, ptxt); err != nil {
 			t.Fatal(err)
 		}
 	}
 
 	// Every ciphertext should be unique.
-	ctxt1, _ := encrypter.Encrypt(ctx, "google", &ptxt)
-	ctxt2, _ := encrypter.Encrypt(ctx, "google", &ptxt)
+	ctxt1, _ := encrypter.Encrypt(ctx, "google", ptxt)
+	ctxt2, _ := encrypter.Encrypt(ctx, "google", ptxt)
 	if reflect.DeepEqual(ctxt1, ctxt2) {
 		t.Fatal("Two Encrypt operations yielded the same Ciphertext")
 	}
@@ -156,7 +152,7 @@
 	addParams(encrypter, google1.Params())
 	addParams(encrypter, google2.Params())
 	// encrypt for the pattern "google/alice"
-	ctxt, err := encrypter.Encrypt(ctx, "google/alice", &ptxt)
+	ctxt, err := encrypter.Encrypt(ctx, "google/alice", ptxt)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -175,8 +171,8 @@
 	// Decryption should now succeed.
 	if got, err := decrypter.Decrypt(ctx, ctxt); err != nil {
 		t.Fatal(err)
-	} else if !bytes.Equal((*got)[:], ptxt[:]) {
-		t.Fatalf("Got plaintext %v, want %v", *got, ptxt)
+	} else if !bytes.Equal(got, ptxt) {
+		t.Fatalf("Got plaintext %v, want %v", got, ptxt)
 	}
 
 	// Decryption should have succeeded had the decrypter only contained
@@ -187,8 +183,8 @@
 	}
 	if got, err := decrypter.Decrypt(ctx, ctxt); err != nil {
 		t.Fatal(err)
-	} else if !bytes.Equal((*got)[:], ptxt[:]) {
-		t.Fatalf("Got plaintext %v, want %v", *got, ptxt)
+	} else if !bytes.Equal(got, ptxt) {
+		t.Fatalf("Got plaintext %v, want %v", got, ptxt)
 	}
 
 	// Decryption should fail for ciphertexts encrypted for the following
@@ -196,7 +192,7 @@
 	// "google/alice/tablet/app" from the root google2).
 	patterns := []security.BlessingPattern{"google/alice/$", "google/bob", "google/alice/tablet/$", "google/bob/tablet"}
 	for _, p := range patterns {
-		ctxt, err := encrypter.Encrypt(ctx, p, &ptxt)
+		ctxt, err := encrypter.Encrypt(ctx, p, ptxt)
 		if err != nil {
 			t.Fatal(err)
 		}
@@ -210,14 +206,14 @@
 	// patterns should succeed.
 	patterns = []security.BlessingPattern{"google", "google/$", "google/alice", "google/bob", "google/bob/phone"}
 	for _, p := range patterns {
-		if _, err := decrypter.Encrypt(ctx, p, &ptxt); err != nil {
+		if _, err := decrypter.Encrypt(ctx, p, ptxt); err != nil {
 			t.Fatal(err)
 		}
 	}
 	// But encrypting for the following patterns should fail.
 	patterns = []security.BlessingPattern{"youtube", "youtube/$", "youtube/alice"}
 	for _, p := range patterns {
-		if _, err := decrypter.Encrypt(ctx, p, &ptxt); verror.ErrorID(err) != ErrNoParams.ID {
+		if _, err := decrypter.Encrypt(ctx, p, ptxt); verror.ErrorID(err) != ErrNoParams.ID {
 			t.Fatalf("Got error %v, wanted error with ID %v", err, ErrNoParams.ID)
 		}
 	}
@@ -233,7 +229,7 @@
 		if err := enc.AddParams(ctx, params); err != nil {
 			t.Fatal(err)
 		}
-		ctxt, err := enc.Encrypt(ctx, pattern, &ptxt)
+		ctxt, err := enc.Encrypt(ctx, pattern, ptxt)
 		if err != nil {
 			t.Fatal(err)
 		}
@@ -246,8 +242,8 @@
 		}
 		if got, err := dec.Decrypt(ctx, ctxt); err != nil {
 			return err
-		} else if !bytes.Equal((*got)[:], ptxt[:]) {
-			return fmt.Errorf("got plaintext %v, want %v", *got, ptxt)
+		} else if !bytes.Equal(got, ptxt) {
+			return fmt.Errorf("got plaintext %v, want %v", got, ptxt)
 		}
 		return nil
 	}
diff --git a/services/syncbase/server/nosql/row.go b/services/syncbase/server/nosql/row.go
index 97cfc98..e95746e 100644
--- a/services/syncbase/server/nosql/row.go
+++ b/services/syncbase/server/nosql/row.go
@@ -126,6 +126,8 @@
 	if err != nil {
 		return err
 	}
+	// TODO(rogulenko): Avoid the redundant lookups since in theory we have all
+	// we need from the checkAccess.
 	permsKey := r.t.prefixPermsKey(permsPrefix)
 	if err := watchable.PutWithPerms(tx, []byte(r.stKey()), value, permsKey); err != nil {
 		return verror.New(verror.ErrInternal, ctx, err)
@@ -140,6 +142,8 @@
 	if err != nil {
 		return err
 	}
+	// TODO(rogulenko): Avoid the redundant lookups since in theory we have all
+	// we need from the checkAccess.
 	permsKey := r.t.prefixPermsKey(permsPrefix)
 	if err := watchable.DeleteWithPerms(tx, []byte(r.stKey()), permsKey); err != nil {
 		return verror.New(verror.ErrInternal, ctx, err)
diff --git a/services/syncbase/server/nosql/table.go b/services/syncbase/server/nosql/table.go
index 5850e62..34a9407 100644
--- a/services/syncbase/server/nosql/table.go
+++ b/services/syncbase/server/nosql/table.go
@@ -415,7 +415,7 @@
 	if err := t.UpdatePrefixPermsIndexForSet(ctx, tx, key); err != nil {
 		return err
 	}
-	return watchable.PutVOMWithPerms(ctx, tx, t.prefixPermsKey(key), perms, t.prefixPermsKey(parent))
+	return watchable.PutVomWithPerms(ctx, tx, t.prefixPermsKey(key), perms, t.prefixPermsKey(parent))
 }
 
 func (t *tableReq) deletePrefixPerms(ctx *context.T, tx store.Transaction, key string) error {
diff --git a/services/syncbase/server/watchable/transaction.go b/services/syncbase/server/watchable/transaction.go
index 49092dd..fdabb29 100644
--- a/services/syncbase/server/watchable/transaction.go
+++ b/services/syncbase/server/watchable/transaction.go
@@ -349,10 +349,10 @@
 	return nil
 }
 
-// PutWithPerms puts a VOM-encoded value for the managed key, recording the key
-// and version of the prefix permissions object that granted access to this put
-// operation.
-func PutVOMWithPerms(ctx *context.T, tx store.Transaction, k string, v interface{}, permsKey string) error {
+// PutVomWithPerms puts a VOM-encoded value for the managed key, recording
+// the key and the version of the prefix permissions object that granted access
+// to this put operation.
+func PutVomWithPerms(ctx *context.T, tx store.Transaction, k string, v interface{}, permsKey string) error {
 	bytes, err := vom.Encode(v)
 	if err != nil {
 		return verror.New(verror.ErrInternal, ctx, err)
diff --git a/services/syncbase/testutil/util.go b/services/syncbase/testutil/util.go
index 7af6f70..a3a8c72 100644
--- a/services/syncbase/testutil/util.go
+++ b/services/syncbase/testutil/util.go
@@ -249,7 +249,8 @@
 	name := s.Status().Endpoints[0].Name()
 	return name, func() {
 		cancel()
-		<-s.Closed()
+		s.Stop()
+		service.Close()
 		os.RemoveAll(rootDir)
 	}
 }