"x/ref": Maintain a cache of IBE encryptions performed during connection setup

Presently, our RPC system implements Protocol 3 from the following
document for private mutual authentication
https://docs.google.com/document/d/1FpLJSiKy4sXxRUSZh1BQrhUEn7io-dGW7y-DMszI21Q/edit#heading=h.knkf4cn3hk6w

This protocol is very similar to our regular authentication protocol,
except that the sever sends its blessings encrypted using IBE.
Currently, this IBE encryption cost is incurred during each connection
created by the server. Since a server uses the same blessings
accross all connections it should possible to encrypt the server's
blessings once and thereby amortize the encryption cost.

This CL achieves this amortization by implementing a cache of all IBE
encryption performed during connection setup.

Change-Id: I6d0f4b1ba051b3727345af35a075dcaa6fcf9b53
diff --git a/runtime/internal/flow/conn/auth.go b/runtime/internal/flow/conn/auth.go
index a32d5dc..b5a388c 100644
--- a/runtime/internal/flow/conn/auth.go
+++ b/runtime/internal/flow/conn/auth.go
@@ -410,7 +410,7 @@
 			Blessings: blessings,
 		}})
 	}
-	ciphertexts, err := encrypt(ctx, blessings, peers)
+	ciphertexts, err := encrypt(ctx, peers, blessings)
 	if err != nil {
 		return NewErrCannotEncryptBlessings(ctx, peers, err)
 	}
@@ -429,7 +429,7 @@
 			BKey:       bkey,
 		}})
 	}
-	ciphertexts, err := encrypt(ctx, discharges, peers)
+	ciphertexts, err := encrypt(ctx, peers, discharges)
 	if err != nil {
 		return NewErrCannotEncryptDischarges(ctx, peers, err)
 	}
diff --git a/runtime/internal/flow/conn/ibe.go b/runtime/internal/flow/conn/ibe.go
index 4bebe97..c6fedf8 100644
--- a/runtime/internal/flow/conn/ibe.go
+++ b/runtime/internal/flow/conn/ibe.go
@@ -5,13 +5,67 @@
 package conn
 
 import (
+	"crypto/sha256"
+	"sync"
+
 	"v.io/v23/context"
 	"v.io/v23/security"
 	"v.io/v23/vom"
 	"v.io/x/ref/lib/security/bcrypter"
 )
 
-func encrypt(ctx *context.T, v interface{}, patterns []security.BlessingPattern) ([]bcrypter.WireCiphertext, error) {
+var encryptionCache = encCache{m: make(map[encCacheKey]bcrypter.WireCiphertext)}
+
+// Maximum no of elements in the cache is 64. This makes the size of
+// the cache = (160 + 16 + 32)*64 + (total size of the 64 plaintexts).
+// Assume that the average plaintext size is upperbounded by 1KB,
+// the size of the cache is upperbound by 77KB.
+const encCacheMaxSize = 1 << 6
+
+type encCacheKey struct {
+	pattern       security.BlessingPattern
+	plaintextHash [sha256.Size]byte
+}
+
+type encCache struct {
+	sync.RWMutex
+	m map[encCacheKey]bcrypter.WireCiphertext
+}
+
+func (c *encCache) evictIfNeededLocked() {
+	// Randomly evict an entry. Fortunately, map iteration is in random key order
+	// (see "Iteration Order" in http://blog.golang.org/go-maps-in-action)
+	toEvict := len(c.m) - encCacheMaxSize
+	if toEvict <= 0 {
+		return
+	}
+	n := 0
+	for key, _ := range c.m {
+		delete(c.m, key)
+		n++
+		if n >= toEvict {
+			break
+		}
+	}
+}
+
+func (c *encCache) cache(pattern security.BlessingPattern, plaintext []byte, ctxt bcrypter.WireCiphertext) {
+	key := encCacheKey{pattern: pattern, plaintextHash: sha256.Sum256(plaintext)}
+	c.Lock()
+	defer c.Unlock()
+	c.m[key] = ctxt
+	c.evictIfNeededLocked()
+}
+
+func (c *encCache) ciphertext(pattern security.BlessingPattern, plaintext []byte) (bcrypter.WireCiphertext, bool) {
+	key := encCacheKey{pattern: pattern, plaintextHash: sha256.Sum256(plaintext)}
+	c.RLock()
+	defer c.RUnlock()
+	ctxt, b := c.m[key]
+	return ctxt, b
+}
+
+func encrypt(ctx *context.T, patterns []security.BlessingPattern, v interface{}) ([]bcrypter.WireCiphertext, error) {
 	crypter := bcrypter.GetCrypter(ctx)
 	if crypter == nil {
 		return nil, NewErrNoCrypter(ctx)
@@ -22,11 +76,16 @@
 	}
 	ciphertexts := make([]bcrypter.WireCiphertext, len(patterns))
 	for i, p := range patterns {
+		if ctxt, exists := encryptionCache.ciphertext(p, b); exists {
+			ciphertexts[i] = ctxt
+			continue
+		}
 		ctxt, err := crypter.Encrypt(ctx, p, b)
 		if err != nil {
 			return nil, err
 		}
 		ctxt.ToWire(&ciphertexts[i])
+		encryptionCache.cache(p, b, ciphertexts[i])
 	}
 	return ciphertexts, nil
 }