1// Copyright 2015 Keybase, Inc. All rights reserved. Use of
2// this source code is governed by the included BSD license.
3
4package engine
5
6import (
7	"path"
8	"runtime"
9	"sync"
10
11	"github.com/keybase/client/go/libkb"
12	keybase1 "github.com/keybase/client/go/protocol/keybase1"
13)
14
15type start struct {
16	s string
17	r keybase1.IdentifyReason
18	f bool
19}
20
21type proofCheck struct {
22	social bool
23	p      keybase1.RemoteProof
24	l      keybase1.LinkCheckResult
25}
26
27type launchNetworkChecks struct {
28	i *keybase1.Identity
29	u *keybase1.User
30}
31
32type bufferedIdentifyUI struct {
33	libkb.Contextified
34	sync.Mutex
35	raw                 libkb.IdentifyUI
36	confirmIfSuppressed keybase1.ConfirmResult
37	bufferedMode        bool
38	start               *start
39	proofChecks         []proofCheck
40	cryptocurrency      []keybase1.Cryptocurrency
41	stellar             *keybase1.StellarAccount
42	launchNetworkChecks *launchNetworkChecks
43	keys                []keybase1.IdentifyKey
44	lastTrack           **keybase1.TrackSummary
45	suppressed          bool
46	userCard            *keybase1.UserCard
47}
48
49var _ libkb.IdentifyUI = (*bufferedIdentifyUI)(nil)
50
51func newBufferedIdentifyUI(g *libkb.GlobalContext, u libkb.IdentifyUI, c keybase1.ConfirmResult) *bufferedIdentifyUI {
52	return &bufferedIdentifyUI{
53		Contextified:        libkb.NewContextified(g),
54		raw:                 u,
55		confirmIfSuppressed: c,
56		bufferedMode:        true,
57	}
58}
59
60func (b *bufferedIdentifyUI) Start(m libkb.MetaContext, s string, r keybase1.IdentifyReason, f bool) error {
61	b.Lock()
62	defer b.Unlock()
63	b.start = &start{s, r, f}
64	return b.flush(m, false)
65}
66
67func (b *bufferedIdentifyUI) flush(m libkb.MetaContext, trackingBroke bool) (err error) {
68
69	// Look up the calling function for debugging purposes
70	pc := make([]uintptr, 10) // at least 1 entry needed
71	runtime.Callers(2, pc)
72	f := runtime.FuncForPC(pc[0])
73	caller := path.Base(f.Name())
74
75	m.Debug("+ bufferedIdentifyUI#flush(%v) [caller=%s, buffered=%v, suppressed=%v]", trackingBroke, caller, b.bufferedMode, b.suppressed)
76
77	if !trackingBroke && b.bufferedMode {
78		m.Debug("- bufferedIdentifyUI#flush: short-circuit")
79		return nil
80	}
81
82	defer func() {
83		b.flushCleanup()
84		m.Debug("- bufferedIdentifyUI#flush -> %v", err)
85	}()
86
87	if b.start != nil {
88		err = b.raw.Start(m, b.start.s, b.start.r, b.start.f)
89		if err != nil {
90			return err
91		}
92	}
93
94	for _, k := range b.keys {
95		err = b.raw.DisplayKey(m, k)
96		if err != nil {
97			return err
98		}
99	}
100
101	if b.lastTrack != nil {
102		err = b.raw.ReportLastTrack(m, *b.lastTrack)
103		if err != nil {
104			return err
105		}
106	}
107
108	if b.launchNetworkChecks != nil {
109		err = b.raw.LaunchNetworkChecks(m, b.launchNetworkChecks.i, b.launchNetworkChecks.u)
110		if err != nil {
111			return err
112		}
113	}
114
115	if b.userCard != nil {
116		err = b.raw.DisplayUserCard(m, *b.userCard)
117		if err != nil {
118			return err
119		}
120	}
121
122	for _, w := range b.proofChecks {
123		var err error
124		if w.social {
125			err = b.raw.FinishSocialProofCheck(m, w.p, w.l)
126		} else {
127			err = b.raw.FinishWebProofCheck(m, w.p, w.l)
128		}
129		if err != nil {
130			return err
131		}
132	}
133
134	for _, c := range b.cryptocurrency {
135		err = b.raw.DisplayCryptocurrency(m, c)
136		if err != nil {
137			return err
138		}
139	}
140
141	if b.stellar != nil {
142		err = b.raw.DisplayStellarAccount(m, *b.stellar)
143		if err != nil {
144			return err
145		}
146	}
147
148	return nil
149}
150
151func (b *bufferedIdentifyUI) flushCleanup() {
152	b.start = nil
153	b.proofChecks = nil
154	b.cryptocurrency = nil
155	b.stellar = nil
156	b.bufferedMode = false
157	b.launchNetworkChecks = nil
158	b.keys = nil
159	b.lastTrack = nil
160	b.userCard = nil
161}
162
163func (b *bufferedIdentifyUI) FinishWebProofCheck(m libkb.MetaContext, p keybase1.RemoteProof, l keybase1.LinkCheckResult) error {
164	b.Lock()
165	defer b.Unlock()
166	b.proofChecks = append(b.proofChecks, proofCheck{false, p, l})
167	return b.flush(m, l.BreaksTracking)
168}
169
170func (b *bufferedIdentifyUI) FinishSocialProofCheck(m libkb.MetaContext, p keybase1.RemoteProof, l keybase1.LinkCheckResult) error {
171	b.Lock()
172	defer b.Unlock()
173	b.proofChecks = append(b.proofChecks, proofCheck{true, p, l})
174	return b.flush(m, l.BreaksTracking)
175}
176
177func (b *bufferedIdentifyUI) Confirm(m libkb.MetaContext, o *keybase1.IdentifyOutcome) (keybase1.ConfirmResult, error) {
178	b.Lock()
179	defer b.Unlock()
180	if b.bufferedMode {
181		m.Debug("| bufferedIdentifyUI#Confirm: suppressing output")
182		b.suppressed = true
183		return b.confirmIfSuppressed, nil
184	}
185	m.Debug("| bufferedIdentifyUI#Confirm: enabling output")
186	b.flush(m, true)
187	return b.raw.Confirm(m, o)
188}
189
190func (b *bufferedIdentifyUI) DisplayCryptocurrency(m libkb.MetaContext, c keybase1.Cryptocurrency) error {
191	b.Lock()
192	defer b.Unlock()
193	b.cryptocurrency = append(b.cryptocurrency, c)
194	return b.flush(m, false)
195}
196
197func (b *bufferedIdentifyUI) DisplayStellarAccount(m libkb.MetaContext, c keybase1.StellarAccount) error {
198	b.Lock()
199	defer b.Unlock()
200	b.stellar = &c
201	return b.flush(m, false)
202}
203
204func (b *bufferedIdentifyUI) DisplayKey(m libkb.MetaContext, k keybase1.IdentifyKey) error {
205	b.Lock()
206	defer b.Unlock()
207	b.keys = append(b.keys, k)
208	return b.flush(m, k.BreaksTracking)
209}
210
211func (b *bufferedIdentifyUI) ReportLastTrack(m libkb.MetaContext, s *keybase1.TrackSummary) error {
212	b.Lock()
213	defer b.Unlock()
214	b.lastTrack = &s
215	return b.flush(m, false)
216}
217
218func (b *bufferedIdentifyUI) LaunchNetworkChecks(m libkb.MetaContext, i *keybase1.Identity, u *keybase1.User) error {
219	b.Lock()
220	defer b.Unlock()
221	b.launchNetworkChecks = &launchNetworkChecks{i, u}
222	return b.flush(m, i.BreaksTracking)
223}
224
225func (b *bufferedIdentifyUI) DisplayTrackStatement(m libkb.MetaContext, s string) error {
226	return b.raw.DisplayTrackStatement(m, s)
227}
228
229func (b *bufferedIdentifyUI) DisplayUserCard(m libkb.MetaContext, c keybase1.UserCard) error {
230	b.Lock()
231	defer b.Unlock()
232	b.userCard = &c
233	return b.flush(m, false)
234}
235
236func (b *bufferedIdentifyUI) ReportTrackToken(m libkb.MetaContext, t keybase1.TrackToken) error {
237	b.Lock()
238	defer b.Unlock()
239	if b.suppressed {
240		return nil
241	}
242	return b.raw.ReportTrackToken(m, t)
243}
244
245func (b *bufferedIdentifyUI) Cancel(m libkb.MetaContext) error {
246	b.Lock()
247	defer b.Unlock()
248
249	// Cancel should always go through to UI server
250	return b.raw.Cancel(m)
251}
252
253func (b *bufferedIdentifyUI) Finish(m libkb.MetaContext) error {
254	b.Lock()
255	defer b.Unlock()
256	if b.suppressed {
257		m.Debug("| bufferedIdentifyUI#Finish: suppressed")
258		return nil
259	}
260	m.Debug("| bufferedIdentifyUI#Finish: went through to UI")
261
262	// This is likely a noop since we already covered this case in the `Confirm` step
263	// above. However, if due a bug we forgot to call `Confirm` from the UI, this
264	// is still useful.
265	b.flush(m, true)
266
267	return b.raw.Finish(m)
268}
269
270func (b *bufferedIdentifyUI) DisplayTLFCreateWithInvite(m libkb.MetaContext, d keybase1.DisplayTLFCreateWithInviteArg) error {
271	return b.raw.DisplayTLFCreateWithInvite(m, d)
272}
273
274func (b *bufferedIdentifyUI) Dismiss(m libkb.MetaContext, s string, r keybase1.DismissReason) error {
275	return b.raw.Dismiss(m, s, r)
276}
277