ref: Persist discharges.

The discharge cache is moved into the blessingStore so that discharges
may be used across processes.

On my machine, the first call of
'namespace glob identity' with a blessing that has a revocation caveat
takes .7s, but subsequent calls take .25 seconds.

MultiPart: 2/2

Change-Id: Ifdc23e618120baef1638b2ebda1f900b772ce727
diff --git a/lib/security/blessingstore.go b/lib/security/blessingstore.go
index 5ff62b7..0b09ee8 100644
--- a/lib/security/blessingstore.go
+++ b/lib/security/blessingstore.go
@@ -9,10 +9,13 @@
 
 import (
 	"bytes"
+	"crypto/sha256"
 	"fmt"
 	"reflect"
 	"sort"
+	"strings"
 	"sync"
+	"time"
 
 	"v.io/v23/security"
 	"v.io/v23/verror"
@@ -27,6 +30,8 @@
 	errDataOrSignerUnspecified = verror.Register(pkgPath+".errDataOrSignerUnspecified", verror.NoRetry, "{1:}{2:} persisted data or signer is not specified{:_}")
 )
 
+const cacheKeyFormat = uint32(1)
+
 // blessingStore implements security.BlessingStore.
 type blessingStore struct {
 	publicKey  security.PublicKey
@@ -116,6 +121,105 @@
 	return m
 }
 
+func (bs *blessingStore) CacheDischarge(discharge security.Discharge, caveat security.Caveat, impetus security.DischargeImpetus) {
+	id := discharge.ID()
+	tp := caveat.ThirdPartyDetails()
+	// Only add to the cache if the caveat did not require arguments.
+	if id == "" || tp == nil || tp.Requirements().ReportArguments {
+		return
+	}
+	key := dcacheKey(tp, impetus)
+	bs.mu.Lock()
+	defer bs.mu.Unlock()
+	old, hadold := bs.state.DischargeCache[key]
+	bs.state.DischargeCache[key] = discharge
+	if err := bs.save(); err != nil {
+		if hadold {
+			bs.state.DischargeCache[key] = old
+		} else {
+			delete(bs.state.DischargeCache, key)
+		}
+	}
+	return
+}
+
+func (bs *blessingStore) ClearDischarges(discharges ...security.Discharge) {
+	bs.mu.Lock()
+	bs.clearDischargesLocked(discharges...)
+	bs.mu.Unlock()
+	return
+}
+
+func (bs *blessingStore) clearDischargesLocked(discharges ...security.Discharge) {
+	for _, d := range discharges {
+		for k, cached := range bs.state.DischargeCache {
+			if cached.Equivalent(d) {
+				delete(bs.state.DischargeCache, k)
+			}
+		}
+	}
+}
+
+func (bs *blessingStore) Discharge(caveat security.Caveat, impetus security.DischargeImpetus) (out security.Discharge) {
+	defer bs.mu.Unlock()
+	bs.mu.Lock()
+	tp := caveat.ThirdPartyDetails()
+	if tp == nil || tp.Requirements().ReportArguments {
+		return
+	}
+	key := dcacheKey(tp, impetus)
+	if cached, exists := bs.state.DischargeCache[key]; exists {
+		out = cached
+		// If the discharge has expired, purge it from the cache.
+		if hasDischargeExpired(out) {
+			out = security.Discharge{}
+			bs.clearDischargesLocked(cached)
+		}
+	}
+	return
+}
+
+func hasDischargeExpired(dis security.Discharge) bool {
+	expiry := dis.Expiry()
+	if expiry.IsZero() {
+		return false
+	}
+	return expiry.Before(time.Now())
+}
+
+func dcacheKey(tp security.ThirdPartyCaveat, impetus security.DischargeImpetus) dischargeCacheKey {
+	// If the algorithm for computing dcacheKey changes, cacheKeyFormat must be changed as well.
+	id := tp.ID()
+	r := tp.Requirements()
+	var method, servers string
+	// We currently do not cache on impetus.Arguments because there it seems there is no
+	// general way to generate a key from them.
+	if r.ReportMethod {
+		method = impetus.Method
+	}
+	if r.ReportServer && len(impetus.Server) > 0 {
+		// Sort the server blessing patterns to increase cache usage.
+		var bps []string
+		for _, bp := range impetus.Server {
+			bps = append(bps, string(bp))
+		}
+		sort.Strings(bps)
+		servers = strings.Join(bps, ",")
+	}
+	h := sha256.New()
+	h.Write(hashString(id))
+	h.Write(hashString(method))
+	h.Write(hashString(servers))
+	var key [sha256.Size]byte
+	copy(key[:], h.Sum(nil))
+	return key
+}
+
+func hashString(d string) []byte {
+	h := sha256.Sum256([]byte(d))
+	return h[:]
+}
+
 // DebugString return a human-readable string encoding of the store
 // in the following format
 // Default Blessings <blessings>
@@ -158,7 +262,10 @@
 func newInMemoryBlessingStore(publicKey security.PublicKey) security.BlessingStore {
 	return &blessingStore{
 		publicKey: publicKey,
-		state:     blessingStoreState{PeerBlessings: make(map[security.BlessingPattern]security.Blessings)},
+		state: blessingStoreState{
+			PeerBlessings:  make(map[security.BlessingPattern]security.Blessings),
+			DischargeCache: make(map[dischargeCacheKey]security.Discharge),
+		},
 	}
 }
 
@@ -185,6 +292,10 @@
 	if err := decodeFromStorage(&bs.state, data, signature, bs.signer.PublicKey()); err != nil {
 		return err
 	}
+	if bs.state.CacheKeyFormat != cacheKeyFormat {
+		bs.state.CacheKeyFormat = cacheKeyFormat
+		bs.state.DischargeCache = make(map[dischargeCacheKey]security.Discharge)
+	}
 	return bs.verifyState()
 }
 
@@ -197,12 +308,17 @@
 	}
 	bs := &blessingStore{
 		publicKey:  signer.PublicKey(),
-		state:      blessingStoreState{PeerBlessings: make(map[security.BlessingPattern]security.Blessings)},
 		serializer: serializer,
 		signer:     signer,
 	}
 	if err := bs.deserialize(); err != nil {
 		return nil, err
 	}
+	if bs.state.PeerBlessings == nil {
+		bs.state.PeerBlessings = make(map[security.BlessingPattern]security.Blessings)
+	}
+	if bs.state.DischargeCache == nil {
+		bs.state.DischargeCache = make(map[dischargeCacheKey]security.Discharge)
+	}
 	return bs, nil
 }
diff --git a/lib/security/blessingstore_test.go b/lib/security/blessingstore_test.go
index 339cd59..01cc3df 100644
--- a/lib/security/blessingstore_test.go
+++ b/lib/security/blessingstore_test.go
@@ -140,6 +140,7 @@
 	if err := tester.testSetDefault(s); err != nil {
 		t.Error(err)
 	}
+	testDischargeCache(t, s)
 }
 
 func TestBlessingStorePersistence(t *testing.T) {
@@ -164,6 +165,7 @@
 	if err := tester.testSetDefault(s); err != nil {
 		t.Error(err)
 	}
+	testDischargeCache(t, s)
 
 	// Recreate the BlessingStore from the directory.
 	p2, err := LoadPersistentPrincipal(dir, nil)
diff --git a/lib/security/discharge_cache_test.go b/lib/security/discharge_cache_test.go
new file mode 100644
index 0000000..f974d1a
--- /dev/null
+++ b/lib/security/discharge_cache_test.go
@@ -0,0 +1,105 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package security
+
+import (
+	"testing"
+	"time"
+
+	"v.io/v23/security"
+	"v.io/v23/vdl"
+)
+
+func testDischargeCache(t *testing.T, s security.BlessingStore) {
+	var (
+		discharger = mkPrincipal()
+		expiredCav = mkCaveat(security.NewPublicKeyCaveat(discharger.PublicKey(), "moline", security.ThirdPartyRequirements{}, security.UnconstrainedUse()))
+		argsCav    = mkCaveat(security.NewPublicKeyCaveat(discharger.PublicKey(), "peoria", security.ThirdPartyRequirements{ReportArguments: true}, security.UnconstrainedUse()))
+		methodCav  = mkCaveat(security.NewPublicKeyCaveat(discharger.PublicKey(), "moline", security.ThirdPartyRequirements{ReportMethod: true}, security.UnconstrainedUse()))
+		serverCav  = mkCaveat(security.NewPublicKeyCaveat(discharger.PublicKey(), "peoria", security.ThirdPartyRequirements{ReportServer: true}, security.UnconstrainedUse()))
+
+		dEmpty   = security.Discharge{}
+		dExpired = mkDischarge(discharger.MintDischarge(expiredCav, mkCaveat(security.NewExpiryCaveat(time.Now().Add(-1*time.Minute)))))
+		dArgs    = mkDischarge(discharger.MintDischarge(argsCav, security.UnconstrainedUse()))
+		dMethod  = mkDischarge(discharger.MintDischarge(methodCav, security.UnconstrainedUse()))
+		dServer  = mkDischarge(discharger.MintDischarge(serverCav, security.UnconstrainedUse()))
+
+		emptyImp       = security.DischargeImpetus{}
+		argsImp        = security.DischargeImpetus{Arguments: []*vdl.Value{&vdl.Value{}}}
+		methodImp      = security.DischargeImpetus{Method: "foo"}
+		otherMethodImp = security.DischargeImpetus{Method: "bar"}
+		serverImp      = security.DischargeImpetus{Server: []security.BlessingPattern{security.BlessingPattern("fooserver")}}
+		otherServerImp = security.DischargeImpetus{Server: []security.BlessingPattern{security.BlessingPattern("barserver")}}
+	)
+
+	// Discharges for different cavs should not be cached.
+	d := mkDischarge(discharger.MintDischarge(argsCav, security.UnconstrainedUse()))
+	s.CacheDischarge(d, argsCav, emptyImp)
+	if d := s.Discharge(methodCav, emptyImp); d.ID() != "" {
+		t.Errorf("Discharge for different caveat should not have been in cache")
+	}
+	s.ClearDischarges(d)
+
+	// Add some discharges into the cache.
+	s.CacheDischarge(dArgs, argsCav, argsImp)
+	s.CacheDischarge(dMethod, methodCav, methodImp)
+	s.CacheDischarge(dServer, serverCav, serverImp)
+	s.CacheDischarge(dExpired, expiredCav, emptyImp)
+
+	testCases := []struct {
+		caveat          security.Caveat           // caveat that we are fetching discharges for.
+		queryImpetus    security.DischargeImpetus // Impetus used to  query the cache.
+		cachedDischarge security.Discharge        // Discharge that we expect to be returned from the cache, nil if the discharge should not be cached.
+	}{
+		// Expired discharges should not be returned by the cache.
+		{expiredCav, emptyImp, dEmpty},
+
+		// Discharges with Impetuses that have Arguments should not be cached.
+		{argsCav, argsImp, dEmpty},
+
+		{methodCav, methodImp, dMethod},
+		{methodCav, otherMethodImp, dEmpty},
+		{methodCav, emptyImp, dEmpty},
+
+		{serverCav, serverImp, dServer},
+		{serverCav, otherServerImp, dEmpty},
+		{serverCav, emptyImp, dEmpty},
+	}
+
+	for i, test := range testCases {
+		out := s.Discharge(test.caveat, test.queryImpetus)
+		if got := out.ID(); got != test.cachedDischarge.ID() {
+			t.Errorf("#%d: got discharge %v, want %v, queried with %v", i, got, test.cachedDischarge.ID(), test.queryImpetus)
+		}
+	}
+	if t.Failed() {
+		t.Logf("dArgs.ID():    %v", dArgs.ID())
+		t.Logf("dMethod.ID():  %v", dMethod.ID())
+		t.Logf("dServer.ID():  %v", dServer.ID())
+		t.Logf("dExpired.ID(): %v", dExpired.ID())
+	}
+}
+
+func mkPrincipal() security.Principal {
+	p, err := NewPrincipal()
+	if err != nil {
+		panic(err)
+	}
+	return p
+}
+
+func mkDischarge(d security.Discharge, err error) security.Discharge {
+	if err != nil {
+		panic(err)
+	}
+	return d
+}
+
+func mkCaveat(c security.Caveat, err error) security.Caveat {
+	if err != nil {
+		panic(err)
+	}
+	return c
+}
diff --git a/lib/security/type.vdl b/lib/security/type.vdl
index 9d65571..9539700 100644
--- a/lib/security/type.vdl
+++ b/lib/security/type.vdl
@@ -18,4 +18,11 @@
 	// DefaultBlessings is the default Blessings to be shared with peers for which
 	// no other information is available to select blessings.
 	DefaultBlessings security.WireBlessings
+	// DischargeCache is the cache of discharges.
+	DischargeCache map[dischargeCacheKey]security.WireDischarge
+	// CacheKeyFormat is the dischargeCacheKey format version. It should incremented
+	// any time the format of the dischargeCacheKey is changed.
+	CacheKeyFormat uint32
 }
+
+type dischargeCacheKey [32]byte
diff --git a/lib/security/type.vdl.go b/lib/security/type.vdl.go
index 3fabed7..5681451 100644
--- a/lib/security/type.vdl.go
+++ b/lib/security/type.vdl.go
@@ -32,6 +32,11 @@
 	// DefaultBlessings is the default Blessings to be shared with peers for which
 	// no other information is available to select blessings.
 	DefaultBlessings security.Blessings
+	// DischargeCache is the cache of discharges.
+	DischargeCache map[dischargeCacheKey]security.Discharge
+	// CacheKeyFormat is the dischargeCacheKey format version. It should incremented
+	// any time the format of the dischargeCacheKey is changed.
+	CacheKeyFormat uint32
 }
 
 func (blessingStoreState) __VDLReflect(struct {
@@ -39,7 +44,15 @@
 }) {
 }
 
+type dischargeCacheKey [32]byte
+
+func (dischargeCacheKey) __VDLReflect(struct {
+	Name string `vdl:"v.io/x/ref/lib/security.dischargeCacheKey"`
+}) {
+}
+
 func init() {
 	vdl.Register((*blessingRootsState)(nil))
 	vdl.Register((*blessingStoreState)(nil))
+	vdl.Register((*dischargeCacheKey)(nil))
 }