1// Copyright 2015 Keybase, Inc. All rights reserved. Use of
2// this source code is governed by the included BSD license.
3
4package libkb
5
6import (
7	"fmt"
8	"sync"
9	"time"
10
11	lru "github.com/hashicorp/golang-lru"
12	keybase1 "github.com/keybase/client/go/protocol/keybase1"
13	jsonw "github.com/keybase/go-jsonw"
14)
15
16type CheckResult struct {
17	Contextified
18	Status       ProofError // Or nil if it was a success
19	VerifiedHint *SigHint   // client provided verified hint if any
20	Time         time.Time  // When the last check was
21	PvlHash      string     // Added after other fields. Some entries may not have this packed.
22}
23
24func (cr CheckResult) Pack() *jsonw.Wrapper {
25	p := jsonw.NewDictionary()
26	if cr.Status != nil {
27		s := jsonw.NewDictionary()
28		_ = s.SetKey("code", jsonw.NewInt(int(cr.Status.GetProofStatus())))
29		_ = s.SetKey("desc", jsonw.NewString(cr.Status.GetDesc()))
30		_ = p.SetKey("status", s)
31		if cr.VerifiedHint != nil {
32			_ = p.SetKey("verified_hint", cr.VerifiedHint.MarshalToJSON())
33		}
34	}
35	_ = p.SetKey("time", jsonw.NewInt64(cr.Time.Unix()))
36	_ = p.SetKey("pvlhash", jsonw.NewString(cr.PvlHash))
37	return p
38}
39
40func (cr CheckResult) Freshness() keybase1.CheckResultFreshness {
41	now := cr.G().Clock().Now()
42	age := now.Sub(cr.Time)
43	switch {
44	case cr.Status == nil:
45		switch {
46		case age < cr.G().Env.GetProofCacheMediumDur():
47			return keybase1.CheckResultFreshness_FRESH
48		case age < cr.G().Env.GetProofCacheLongDur():
49			return keybase1.CheckResultFreshness_AGED
50		}
51	case ProofErrorIsPvlBad(cr.Status):
52		// Don't use cache results for pvl problems.
53		// The hope is that they will soon be resolved server-side.
54		return keybase1.CheckResultFreshness_RANCID
55	case !ProofErrorIsSoft(cr.Status):
56		if age < cr.G().Env.GetProofCacheShortDur() {
57			return keybase1.CheckResultFreshness_FRESH
58		}
59	default:
60		// don't use cache results for "soft" errors (500s, timeouts)
61		// see issue #140
62	}
63	return keybase1.CheckResultFreshness_RANCID
64}
65
66func NewNowCheckResult(g *GlobalContext, pe ProofError) *CheckResult {
67	return &CheckResult{
68		Contextified: NewContextified(g),
69		Status:       pe,
70		Time:         g.Clock().Now(),
71	}
72}
73
74func NewCheckResult(g *GlobalContext, jw *jsonw.Wrapper) (res *CheckResult, err error) {
75	var ignoreErr error
76	var t int64
77	var code int
78	var desc string
79	var pvlHash string
80
81	jw.AtKey("time").GetInt64Void(&t, &err)
82	jw.AtKey("pvlhash").GetStringVoid(&pvlHash, &ignoreErr)
83	verifiedHint, err := NewSigHint(jw.AtKey("verified_hint"))
84	if err != nil {
85		return nil, err
86	}
87
88	status := jw.AtKey("status")
89	var pe ProofError
90	if !status.IsNil() {
91		status.AtKey("desc").GetStringVoid(&desc, &err)
92		status.AtKey("code").GetIntVoid(&code, &err)
93		pe = NewProofError(keybase1.ProofStatus(code), desc)
94	}
95	if err != nil {
96		return nil, err
97	}
98	res = &CheckResult{
99		Contextified: NewContextified(g),
100		Status:       pe,
101		VerifiedHint: verifiedHint,
102		Time:         time.Unix(t, 0),
103		PvlHash:      pvlHash,
104	}
105	return res, nil
106}
107
108type ProofCache struct {
109	Contextified
110	capac int
111	lru   *lru.Cache
112	sync.RWMutex
113	noDisk bool
114}
115
116func NewProofCache(g *GlobalContext, capac int) *ProofCache {
117	return &ProofCache{Contextified: NewContextified(g), capac: capac}
118}
119
120func (pc *ProofCache) DisableDisk() {
121	pc.Lock()
122	defer pc.Unlock()
123	pc.noDisk = true
124}
125
126func (pc *ProofCache) Reset() error {
127	pc.Lock()
128	defer pc.Unlock()
129	return pc.initCache()
130}
131
132func (pc *ProofCache) setup() error {
133	pc.Lock()
134	defer pc.Unlock()
135	if pc.lru != nil {
136		return nil
137	}
138	return pc.initCache()
139}
140
141func (pc *ProofCache) initCache() error {
142	lru, err := lru.New(pc.capac)
143	if err != nil {
144		return err
145	}
146	pc.lru = lru
147	return nil
148}
149
150func (pc *ProofCache) memGet(sid keybase1.SigID) *CheckResult {
151	if err := pc.setup(); err != nil {
152		return nil
153	}
154
155	pc.RLock()
156	defer pc.RUnlock()
157
158	tmp, found := pc.lru.Get(sid)
159	if !found {
160		return nil
161	}
162	cr, ok := tmp.(CheckResult)
163	if !ok {
164		pc.G().Log.Errorf("Bad type assertion in ProofCache.Get")
165		return nil
166	}
167	if cr.Freshness() == keybase1.CheckResultFreshness_RANCID {
168		pc.lru.Remove(sid)
169		return nil
170	}
171	return &cr
172}
173
174func (pc *ProofCache) memPut(sid keybase1.SigID, cr CheckResult) {
175	if err := pc.setup(); err != nil {
176		return
177	}
178
179	pc.RLock()
180	defer pc.RUnlock()
181
182	pc.lru.Add(sid, cr)
183}
184
185func (pc *ProofCache) memDelete(sid keybase1.SigID) {
186	if err := pc.setup(); err != nil {
187		return
188	}
189	pc.RLock()
190	defer pc.RUnlock()
191	pc.lru.Remove(sid)
192}
193
194func (pc *ProofCache) Get(sid keybase1.SigID, pvlHash keybase1.MerkleStoreKitHash) *CheckResult {
195	if pc == nil {
196		return nil
197	}
198
199	cr := pc.memGet(sid)
200	if cr == nil {
201		cr = pc.dbGet(sid)
202	}
203	if cr == nil {
204		return nil
205	}
206
207	if cr.PvlHash == "" {
208		pc.G().Log.Debug("^ ProofCache ignoring entry with pvl-hash empty")
209		return nil
210	}
211	if cr.PvlHash != string(pvlHash) {
212		pc.G().Log.Debug("^ ProofCache ignoring entry with pvl-hash mismatch")
213		return nil
214	}
215
216	return cr
217}
218
219func (pc *ProofCache) dbKey(sid keybase1.SigID) (DbKey, string) {
220	sidstr := sid.String()
221	key := DbKey{Typ: DBProofCheck, Key: sidstr}
222	return key, sidstr
223}
224
225func (pc *ProofCache) dbGet(sid keybase1.SigID) (cr *CheckResult) {
226	dbkey, sidstr := pc.dbKey(sid)
227
228	pc.G().Log.Debug("+ ProofCache.dbGet(%s)", sidstr)
229	defer func() {
230		pc.G().Log.Debug("- ProofCache.dbGet(%s) -> %v", sidstr, (cr != nil))
231	}()
232
233	if pc.noDisk {
234		pc.G().Log.Debug("| disk proof cache disabled")
235		return nil
236	}
237
238	jw, err := pc.G().LocalDb.Get(dbkey)
239	if err != nil {
240		pc.G().Log.Errorf("Error lookup up proof check in DB: %s", err)
241		return nil
242	}
243	if jw == nil {
244		pc.G().Log.Debug("| Cached CheckResult for %s wasn't found ", sidstr)
245		return nil
246	}
247
248	cr, err = NewCheckResult(pc.G(), jw)
249	if err != nil {
250		pc.G().Log.Errorf("Bad cached CheckResult for %s", sidstr)
251		return nil
252	}
253
254	if cr.Freshness() == keybase1.CheckResultFreshness_RANCID {
255		if err := pc.G().LocalDb.Delete(dbkey); err != nil {
256			pc.G().Log.Errorf("Delete error: %s", err)
257		}
258		pc.G().Log.Debug("| Cached CheckResult for %s wasn't fresh", sidstr)
259		return nil
260	}
261
262	return cr
263}
264
265func (pc *ProofCache) dbPut(sid keybase1.SigID, cr CheckResult) error {
266	if pc.noDisk {
267		return nil
268	}
269
270	dbkey, _ := pc.dbKey(sid)
271	jw := cr.Pack()
272	return pc.G().LocalDb.Put(dbkey, []DbKey{}, jw)
273}
274
275func (pc *ProofCache) dbDelete(sid keybase1.SigID) error {
276	if pc.noDisk {
277		return nil
278	}
279	dbkey, _ := pc.dbKey(sid)
280	return pc.G().LocalDb.Delete(dbkey)
281}
282
283func (pc *ProofCache) Put(sid keybase1.SigID, lcr *LinkCheckResult, pvlHash keybase1.MerkleStoreKitHash) error {
284	if pc == nil {
285		return nil
286	}
287	cr := CheckResult{
288		Contextified: pc.Contextified,
289		Status:       lcr.err,
290		VerifiedHint: lcr.verifiedHint,
291		Time:         pc.G().Clock().Now(),
292		PvlHash:      string(pvlHash),
293	}
294	pc.memPut(sid, cr)
295	return pc.dbPut(sid, cr)
296}
297
298func (pc *ProofCache) Delete(sid keybase1.SigID) error {
299	if pc == nil {
300		return fmt.Errorf("nil ProofCache")
301	}
302	pc.memDelete(sid)
303	return pc.dbDelete(sid)
304}
305