1package globals
2
3import (
4	"context"
5	"sync"
6
7	"github.com/keybase/client/go/chat/types"
8	"github.com/keybase/client/go/libkb"
9	"github.com/keybase/client/go/protocol/chat1"
10	"github.com/keybase/client/go/protocol/keybase1"
11	"github.com/keybase/go-framed-msgpack-rpc/rpc"
12)
13
14type keyfinderKey int
15type identifyNotifierKey int
16type chatTrace int
17type identifyModeKey int
18type upakfinderKey int
19type rateLimitKey int
20type nameInfoOverride int
21type localizerCancelableKeyTyp int
22type messageSkipsKeyTyp int
23type unboxModeKeyTyp int
24type emojiHarvesterKeyTyp int
25type ctxMutexKeyTyp int
26
27var kfKey keyfinderKey
28var inKey identifyNotifierKey
29var chatTraceKey chatTrace
30var identModeKey identifyModeKey
31var upKey upakfinderKey
32var rlKey rateLimitKey
33var nameInfoOverrideKey nameInfoOverride
34var localizerCancelableKey localizerCancelableKeyTyp
35var messageSkipsKey messageSkipsKeyTyp
36var unboxModeKey unboxModeKeyTyp
37var emojiHarvesterKey emojiHarvesterKeyTyp
38var ctxMutexKey ctxMutexKeyTyp
39
40type identModeData struct {
41	mode   keybase1.TLFIdentifyBehavior
42	breaks *[]keybase1.TLFIdentifyFailure
43}
44
45func CtxKeyFinder(ctx context.Context, g *Context) types.KeyFinder {
46	if kf, ok := ctx.Value(kfKey).(types.KeyFinder); ok {
47		return kf
48	}
49	return g.CtxFactory.NewKeyFinder()
50}
51
52func CtxUPAKFinder(ctx context.Context, g *Context) types.UPAKFinder {
53	if up, ok := ctx.Value(upKey).(types.UPAKFinder); ok {
54		return up
55	}
56	return g.CtxFactory.NewUPAKFinder()
57}
58
59func CtxIdentifyMode(ctx context.Context) (ib keybase1.TLFIdentifyBehavior, breaks *[]keybase1.TLFIdentifyFailure, ok bool) {
60	if imd, ok := ctx.Value(identModeKey).(identModeData); ok {
61		return imd.mode, imd.breaks, ok
62	}
63	return keybase1.TLFIdentifyBehavior_CHAT_CLI, nil, false
64}
65
66func CtxAddIdentifyMode(ctx context.Context, mode keybase1.TLFIdentifyBehavior,
67	breaks *[]keybase1.TLFIdentifyFailure) context.Context {
68	if mode == keybase1.TLFIdentifyBehavior_UNSET {
69		mode = keybase1.TLFIdentifyBehavior_CHAT_CLI
70	}
71	return context.WithValue(ctx, identModeKey, identModeData{mode: mode, breaks: breaks})
72}
73
74func CtxIdentifyNotifier(ctx context.Context) types.IdentifyNotifier {
75	if in, ok := ctx.Value(inKey).(types.IdentifyNotifier); ok {
76		return in
77	}
78	return nil
79}
80
81func CtxModifyIdentifyNotifier(ctx context.Context, notifier types.IdentifyNotifier) context.Context {
82	return context.WithValue(ctx, inKey, notifier)
83}
84
85func CtxAddRateLimit(ctx context.Context, rl []chat1.RateLimit) {
86	if l, ok := ctx.Value(ctxMutexKey).(*sync.RWMutex); ok {
87		l.Lock()
88		defer l.Unlock()
89		if existingRL, ok := ctx.Value(rlKey).(map[string]chat1.RateLimit); ok {
90			for _, r := range rl {
91				existingRL[r.Name] = r
92			}
93		}
94	}
95}
96
97func CtxRateLimits(ctx context.Context) (res []chat1.RateLimit) {
98	if l, ok := ctx.Value(ctxMutexKey).(*sync.RWMutex); ok {
99		l.RLock()
100		defer l.RUnlock()
101		if existingRL, ok := ctx.Value(rlKey).(map[string]chat1.RateLimit); ok {
102			for _, rl := range existingRL {
103				res = append(res, rl)
104			}
105		}
106	}
107	return res
108}
109
110func CtxAddMessageCacheSkips(ctx context.Context, convID chat1.ConversationID, msgs []chat1.MessageUnboxed) {
111	if l, ok := ctx.Value(ctxMutexKey).(*sync.RWMutex); ok {
112		l.Lock()
113		defer l.Unlock()
114		if existingSkips, ok := ctx.Value(messageSkipsKey).(map[chat1.ConvIDStr]MessageCacheSkip); ok {
115			existingSkips[convID.ConvIDStr()] = MessageCacheSkip{
116				ConvID: convID,
117				Msgs:   append(existingSkips[convID.ConvIDStr()].Msgs, msgs...),
118			}
119		}
120	}
121}
122
123type MessageCacheSkip struct {
124	ConvID chat1.ConversationID
125	Msgs   []chat1.MessageUnboxed
126}
127
128func CtxMessageCacheSkips(ctx context.Context) (res []MessageCacheSkip) {
129	if l, ok := ctx.Value(ctxMutexKey).(*sync.RWMutex); ok {
130		l.RLock()
131		defer l.RUnlock()
132		if existingSkips, ok := ctx.Value(messageSkipsKey).(map[chat1.ConvIDStr]MessageCacheSkip); ok {
133			for _, skips := range existingSkips {
134				res = append(res, skips)
135			}
136		}
137	}
138	return res
139}
140
141func CtxModifyUnboxMode(ctx context.Context, unboxMode types.UnboxMode) context.Context {
142	return context.WithValue(ctx, unboxModeKey, unboxMode)
143}
144
145func CtxUnboxMode(ctx context.Context) types.UnboxMode {
146	if unboxMode, ok := ctx.Value(unboxModeKey).(types.UnboxMode); ok {
147		return unboxMode
148	}
149	return types.UnboxModeFull
150}
151
152func CtxOverrideNameInfoSource(ctx context.Context) (types.NameInfoSource, bool) {
153	if ni, ok := ctx.Value(nameInfoOverrideKey).(types.NameInfoSource); ok {
154		return ni, true
155	}
156	return nil, false
157}
158
159func CtxAddOverrideNameInfoSource(ctx context.Context, ni types.NameInfoSource) context.Context {
160	return context.WithValue(ctx, nameInfoOverrideKey, ni)
161}
162
163func CtxTrace(ctx context.Context) (string, bool) {
164	if trace, ok := ctx.Value(chatTraceKey).(string); ok {
165		return trace, true
166	}
167	return "", false
168}
169
170func CtxAddLogTags(ctx context.Context, g *Context) context.Context {
171
172	// Add trace context value
173	trace := libkb.RandStringB64(3)
174	ctx = context.WithValue(ctx, chatTraceKey, trace)
175
176	// Add log tags
177	ctx = libkb.WithLogTagWithValue(ctx, "chat-trace", trace)
178
179	rpcTags := make(map[string]interface{})
180	rpcTags["user-agent"] = libkb.UserAgent
181	rpcTags["platform"] = libkb.GetPlatformString()
182	rpcTags["apptype"] = g.GetAppType()
183	ctx = rpc.AddRpcTagsToContext(ctx, rpcTags)
184
185	return ctx
186}
187
188func IsLocalizerCancelableCtx(ctx context.Context) bool {
189	if bval, ok := ctx.Value(localizerCancelableKey).(bool); ok && bval {
190		return true
191	}
192	return false
193}
194
195func CtxAddLocalizerCancelable(ctx context.Context) context.Context {
196	return context.WithValue(ctx, localizerCancelableKey, true)
197}
198
199func CtxRemoveLocalizerCancelable(ctx context.Context) context.Context {
200	if IsLocalizerCancelableCtx(ctx) {
201		return context.WithValue(ctx, localizerCancelableKey, false)
202	}
203	return ctx
204}
205
206func IsEmojiHarvesterCtx(ctx context.Context) bool {
207	if bval, ok := ctx.Value(emojiHarvesterKey).(bool); ok && bval {
208		return true
209	}
210	return false
211}
212
213func CtxMakeEmojiHarvester(ctx context.Context) context.Context {
214	return context.WithValue(ctx, emojiHarvesterKey, true)
215}
216
217func ChatCtx(ctx context.Context, g *Context, mode keybase1.TLFIdentifyBehavior,
218	breaks *[]keybase1.TLFIdentifyFailure, notifier types.IdentifyNotifier) context.Context {
219	if breaks == nil {
220		breaks = new([]keybase1.TLFIdentifyFailure)
221	}
222	res := ctx
223	if _, _, ok := CtxIdentifyMode(res); !ok {
224		res = CtxAddIdentifyMode(res, mode, breaks)
225	}
226	if _, ok := res.Value(kfKey).(types.KeyFinder); !ok {
227		res = context.WithValue(res, kfKey, g.CtxFactory.NewKeyFinder())
228	}
229	if _, ok := res.Value(inKey).(types.IdentifyNotifier); !ok {
230		res = context.WithValue(res, inKey, notifier)
231	}
232	if _, ok := res.Value(upKey).(types.UPAKFinder); !ok {
233		res = context.WithValue(res, upKey, g.CtxFactory.NewUPAKFinder())
234	}
235	if _, ok := res.Value(ctxMutexKey).(*sync.RWMutex); !ok {
236		res = context.WithValue(res, ctxMutexKey, &sync.RWMutex{})
237	}
238	if _, ok := res.Value(rlKey).(map[string]chat1.RateLimit); !ok {
239		res = context.WithValue(res, rlKey, make(map[string]chat1.RateLimit))
240	}
241	if _, ok := res.Value(messageSkipsKey).(map[chat1.ConvIDStr]MessageCacheSkip); !ok {
242		res = context.WithValue(res, messageSkipsKey, make(map[chat1.ConvIDStr]MessageCacheSkip))
243	}
244	if _, ok := res.Value(unboxModeKey).(types.UnboxMode); !ok {
245		res = context.WithValue(res, unboxModeKey, types.UnboxModeFull)
246	}
247	if _, ok := CtxTrace(res); !ok {
248		res = CtxAddLogTags(res, g)
249	}
250	return res
251}
252
253func BackgroundChatCtx(sourceCtx context.Context, g *Context) context.Context {
254	rctx := libkb.CopyTagsToBackground(sourceCtx)
255
256	in := CtxIdentifyNotifier(sourceCtx)
257	if ident, breaks, ok := CtxIdentifyMode(sourceCtx); ok {
258		rctx = ChatCtx(rctx, g, ident, breaks, in)
259	}
260
261	// Overwrite trace tag
262	if tr, ok := sourceCtx.Value(chatTraceKey).(string); ok {
263		rctx = context.WithValue(rctx, chatTraceKey, tr)
264	}
265
266	if ni, ok := CtxOverrideNameInfoSource(sourceCtx); ok {
267		rctx = CtxAddOverrideNameInfoSource(rctx, ni)
268	}
269	rctx = context.WithValue(rctx, kfKey, CtxKeyFinder(sourceCtx, g))
270	rctx = context.WithValue(rctx, upKey, CtxUPAKFinder(sourceCtx, g))
271	rctx = context.WithValue(rctx, inKey, in)
272	rctx = libkb.WithLogTag(rctx, "CHTBKG")
273	if IsLocalizerCancelableCtx(sourceCtx) {
274		rctx = CtxAddLocalizerCancelable(rctx)
275	}
276	if IsEmojiHarvesterCtx(sourceCtx) {
277		rctx = CtxMakeEmojiHarvester(rctx)
278	}
279	return rctx
280}
281