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	"encoding/json"
8	"fmt"
9	"regexp"
10	"strconv"
11	"strings"
12
13	"github.com/keybase/client/go/msgpack"
14	keybase1 "github.com/keybase/client/go/protocol/keybase1"
15	jsonw "github.com/keybase/go-jsonw"
16)
17
18const (
19	DBUser              = 0x00
20	DBSig               = 0x0f
21	DBTeamChain         = 0x10
22	DBUserPlusAllKeysV1 = 0x19
23
24	DBIncomingSharePreference        = 0xa4
25	DBChatUserEmojis                 = 0xa5
26	DBChatInboxIndex                 = 0xa6
27	DBChatInboxConvs                 = 0xa7
28	DBChatParticipants               = 0xa8
29	DBOpenTeams                      = 0xa9
30	DBNetworkInstrumentation         = 0xaa
31	DBFeaturedBots                   = 0xab
32	DBChatEphemeralTracker           = 0xac
33	DBLoginTimes                     = 0xad
34	DBChatJourney                    = 0xae
35	DBTeamRoleMap                    = 0xaf
36	DBMisc                           = 0xb0
37	DBTeamMerkleCheck                = 0xb1
38	DBUidToServiceMap                = 0xb2
39	DBChatPinIgnore                  = 0xb3
40	DBTeambotKey                     = 0xb4
41	DBTeambotKeyWrongKID             = 0xb5
42	DBChatBotCommands                = 0xb6
43	DBSavedContacts                  = 0xb7
44	DBChatLocation                   = 0xb8
45	DBHiddenChainStorage             = 0xb9
46	DBContactResolution              = 0xba
47	DBBoxAuditorPermanent            = 0xbb
48	DBBoxAuditor                     = 0xbc
49	DBUserPlusKeysVersionedUnstubbed = 0xbd
50	DBOfflineRPC                     = 0xbe
51	DBChatCollapses                  = 0xbf
52	DBSupportsHiddenFlagStorage      = 0xc0
53	DBMerkleAudit                    = 0xca
54	DBUnfurler                       = 0xcb
55	DBStellarDisclaimer              = 0xcc
56	DBFTLStorage                     = 0xcd
57	DBTeamAuditor                    = 0xce
58	DBAttachmentUploader             = 0xcf
59	DBLegacyHasRandomPW              = 0xd0
60	DBDiskLRUEntries                 = 0xda
61	DBDiskLRUIndex                   = 0xdb
62	DBImplicitTeamConflictInfo       = 0xdc
63	DBUidToFullName                  = 0xdd
64	DBUidToUsername                  = 0xde
65	DBUserPlusKeysVersioned          = 0xdf
66	DBLink                           = 0xe0
67	DBLocalTrack                     = 0xe1
68	DBPGPKey                         = 0xe3
69	DBSigHints                       = 0xe4
70	DBProofCheck                     = 0xe5
71	DBUserSecretKeys                 = 0xe6
72	DBSigChainTailPublic             = 0xe7
73	DBSigChainTailSemiprivate        = 0xe8
74	DBSigChainTailEncrypted          = 0xe9
75	DBChatActive                     = 0xea
76	DBUserEKBox                      = 0xeb
77	DBTeamEKBox                      = 0xec
78	DBChatIndex                      = 0xed
79	DBChatReacji                     = 0xef
80	DBMerkleRoot                     = 0xf0
81	DBTrackers                       = 0xf1
82	DBGregor                         = 0xf2
83	DBUnverifiedTrackersFollowers    = 0xf3
84	DBUnverifiedTrackersFollowing    = 0xf4
85	DBNotificationDismiss            = 0xf5
86	DBChatBlockIndex                 = 0xf6
87	DBChatBlocks                     = 0xf7
88	DBChatOutbox                     = 0xf8
89	DBChatInbox                      = 0xf9
90	DBIdentify                       = 0xfa
91	DBResolveUsernameToUID           = 0xfb
92	DBChatBodyHashIndex              = 0xfc
93	DBMerkleStore                    = 0xfd
94	DBChatConvFailures               = 0xfe
95	DBTeamList                       = 0xff
96)
97
98// Note(maxtaco) 2018.10.08 --- Note a bug here, that we used the `libkb.DBChatInbox` type here.
99// That's a copy-paste bug, but we get away with it since we have a `tid:` prefix that
100// disambiguates these entries from true Chat entries. We're not going to fix it now
101// since it would kill the team cache, but sometime in the future we should fix it.
102const (
103	DBSlowTeamsAlias = DBChatInbox
104)
105
106const (
107	DBLookupUsername = 0x00
108	// was once used to store latest merkle root with Key:"HEAD"
109	DBLookupMerkleRoot = 0x01
110)
111
112func DbKeyUID(t ObjType, uid keybase1.UID) DbKey {
113	return DbKey{Typ: t, Key: uid.String()}
114}
115
116func DbKeyNotificationDismiss(prefix string, username NormalizedUsername) DbKey {
117	return DbKey{
118		Typ: DBNotificationDismiss,
119		Key: fmt.Sprintf("%s:%s", prefix, username),
120	}
121}
122
123// IsPermDbKey returns true for keys ignored by the leveldb cleaner and always
124// persisted to disk. Ideally these keys handling some cleanup/size bounding
125// themselves.
126func IsPermDbKey(typ ObjType) bool {
127	switch typ {
128	case DBDiskLRUEntries,
129		DBDiskLRUIndex,
130		DBOfflineRPC,
131		DBChatCollapses,
132		DBLegacyHasRandomPW,
133		DBChatReacji,
134		DBStellarDisclaimer,
135		DBChatIndex,
136		DBBoxAuditorPermanent,
137		DBSavedContacts,
138		DBContactResolution,
139		DBTeambotKeyWrongKID,
140		DBMisc,
141		DBIncomingSharePreference:
142		return true
143	default:
144		return false
145	}
146}
147
148type ObjType byte
149
150type DbKey struct {
151	Typ ObjType
152	Key string
153}
154
155// tablePrefix builds a key prefix for the given table for use in `util.Range`
156// or `util.BytesPrefix`
157func tablePrefix(table string) []byte {
158	return []byte(fmt.Sprintf("%s:", table))
159}
160
161func prefixStringWithTable(table string, typ ObjType) string {
162	return fmt.Sprintf("%s:%02x", table, typ)
163}
164
165func PrefixString(typ ObjType) string {
166	return prefixStringWithTable(typ.table(), typ)
167}
168
169func (t ObjType) table() string {
170	if IsPermDbKey(t) {
171		return levelDbTablePerm
172	}
173	return levelDbTableKv
174}
175
176func (k DbKey) toBytes(prefixString string) []byte {
177	return []byte(fmt.Sprintf("%s:%s", prefixString, k.Key))
178}
179
180func (k DbKey) ToBytes() []byte {
181	return k.toBytes(PrefixString(k.Typ))
182}
183
184func (k DbKey) ToBytesLookup() []byte {
185	return k.toBytes(prefixStringWithTable(levelDbTableLo, k.Typ))
186}
187
188var fieldExp = regexp.MustCompile(`[a-f0-9]{2}`)
189
190func DbKeyParse(s string) (string, DbKey, error) {
191	v := strings.Split(s, ":")
192	if len(v) < 3 {
193		return "", DbKey{}, fmt.Errorf("expected 3 colon-separated fields, found %d", len(v))
194	}
195
196	if !fieldExp.MatchString(v[1]) {
197		return "", DbKey{}, fmt.Errorf("2nd field should be a 1-byte hex string")
198	}
199
200	b, err := strconv.ParseUint(v[1], 16, 8)
201	if err != nil {
202		return "", DbKey{}, err
203	}
204	dbKey := DbKey{
205		Typ: ObjType(b),
206		Key: strings.Join(v[2:], ":"),
207	}
208	return v[0], dbKey, nil
209}
210
211func jsonLocalDbPut(ops LocalDbOps, id DbKey, aliases []DbKey, val *jsonw.Wrapper) error {
212	bytes, err := val.Marshal()
213	if err == nil {
214		err = ops.Put(id, aliases, bytes)
215	}
216	return err
217}
218func jsonLocalDbGet(ops LocalDbOps, id DbKey) (*jsonw.Wrapper, error) {
219	bytes, found, err := ops.Get(id)
220	var ret *jsonw.Wrapper
221	if found {
222		ret, err = jsonw.Unmarshal(bytes)
223	}
224	return ret, err
225}
226
227func jsonLocalDbGetInto(ops LocalDbOps, obj interface{}, id DbKey) (found bool, err error) {
228	var buf []byte
229	buf, found, err = ops.Get(id)
230	if err == nil && found {
231		err = jsonw.EnsureMaxDepthBytesDefault(buf)
232		if err != nil {
233			return found, err
234		}
235		err = json.Unmarshal(buf, &obj)
236	}
237	return found, err
238}
239
240func jsonLocalDbPutObj(ops LocalDbOps, id DbKey, aliases []DbKey, obj interface{}) (err error) {
241	var bytes []byte
242	bytes, err = json.Marshal(obj)
243	if err == nil {
244		err = ops.Put(id, aliases, bytes)
245	}
246	return err
247}
248
249func jsonLocalDbLookup(ops LocalDbOps, id DbKey) (*jsonw.Wrapper, error) {
250	bytes, found, err := ops.Lookup(id)
251	var ret *jsonw.Wrapper
252	if found {
253		ret, err = jsonw.Unmarshal(bytes)
254	}
255	return ret, err
256}
257
258func jsonLocalDbLookupIntoMsgpack(ops LocalDbOps, obj interface{}, alias DbKey) (found bool, err error) {
259	var buf []byte
260	buf, found, err = ops.Lookup(alias)
261	if err != nil || !found {
262		return found, err
263	}
264	err = msgpack.Decode(obj, buf)
265	return true, err
266}
267
268func jsonLocalDbGetIntoMsgpack(ops LocalDbOps, obj interface{}, id DbKey) (found bool, err error) {
269	var buf []byte
270	buf, found, err = ops.Get(id)
271	if err != nil || !found {
272		return found, err
273	}
274	err = msgpack.Decode(obj, buf)
275	return true, err
276}
277
278func jsonLocalDbPutObjMsgpack(ops LocalDbOps, id DbKey, aliases []DbKey, obj interface{}) error {
279	bytes, err := msgpack.Encode(obj)
280	if err != nil {
281		return err
282	}
283	return ops.Put(id, aliases, bytes)
284}
285
286type JSONLocalDb struct {
287	engine LocalDb
288}
289
290func NewJSONLocalDb(e LocalDb) *JSONLocalDb   { return &JSONLocalDb{e} }
291func (j *JSONLocalDb) Open() error            { return j.engine.Open() }
292func (j *JSONLocalDb) ForceOpen() error       { return j.engine.ForceOpen() }
293func (j *JSONLocalDb) Close() error           { return j.engine.Close() }
294func (j *JSONLocalDb) Nuke() (string, error)  { return j.engine.Nuke() }
295func (j *JSONLocalDb) Clean(force bool) error { return j.engine.Clean(force) }
296func (j *JSONLocalDb) Stats() string          { return j.engine.Stats() }
297func (j *JSONLocalDb) CompactionStats() (bool, bool, error) {
298	return j.engine.CompactionStats()
299}
300func (j *JSONLocalDb) KeysWithPrefixes(prefixes ...[]byte) (DBKeySet, error) {
301	return j.engine.KeysWithPrefixes(prefixes...)
302}
303
304func (j *JSONLocalDb) PutRaw(id DbKey, b []byte) error       { return j.engine.Put(id, nil, b) }
305func (j *JSONLocalDb) GetRaw(id DbKey) ([]byte, bool, error) { return j.engine.Get(id) }
306func (j *JSONLocalDb) Delete(id DbKey) error                 { return j.engine.Delete(id) }
307
308func (j *JSONLocalDb) Put(id DbKey, aliases []DbKey, val *jsonw.Wrapper) error {
309	return jsonLocalDbPut(j.engine, id, aliases, val)
310}
311
312func (j *JSONLocalDb) Get(id DbKey) (*jsonw.Wrapper, error) {
313	return jsonLocalDbGet(j.engine, id)
314}
315
316func (j *JSONLocalDb) GetInto(obj interface{}, id DbKey) (found bool, err error) {
317	return jsonLocalDbGetInto(j.engine, obj, id)
318}
319
320func (j *JSONLocalDb) PutObj(id DbKey, aliases []DbKey, obj interface{}) (err error) {
321	return jsonLocalDbPutObj(j.engine, id, aliases, obj)
322}
323
324func (j *JSONLocalDb) Lookup(id DbKey) (*jsonw.Wrapper, error) {
325	return jsonLocalDbLookup(j.engine, id)
326}
327
328func (j *JSONLocalDb) LookupIntoMsgpack(obj interface{}, alias DbKey) (found bool, err error) {
329	return jsonLocalDbLookupIntoMsgpack(j.engine, obj, alias)
330}
331
332func (j *JSONLocalDb) GetIntoMsgpack(obj interface{}, id DbKey) (found bool, err error) {
333	return jsonLocalDbGetIntoMsgpack(j.engine, obj, id)
334}
335
336func (j *JSONLocalDb) PutObjMsgpack(id DbKey, aliases []DbKey, obj interface{}) (err error) {
337	return jsonLocalDbPutObjMsgpack(j.engine, id, aliases, obj)
338}
339
340func (j *JSONLocalDb) OpenTransaction() (JSONLocalDbTransaction, error) {
341	var (
342		jtr JSONLocalDbTransaction
343		err error
344	)
345	if jtr.tr, err = j.engine.OpenTransaction(); err != nil {
346		return JSONLocalDbTransaction{}, err
347	}
348	return jtr, nil
349}
350
351func (j *JSONLocalDb) GetEngine() LocalDb {
352	return j.engine
353}
354
355type JSONLocalDbTransaction struct {
356	tr LocalDbTransaction
357}
358
359func (j JSONLocalDbTransaction) PutRaw(id DbKey, b []byte) error       { return j.tr.Put(id, nil, b) }
360func (j JSONLocalDbTransaction) GetRaw(id DbKey) ([]byte, bool, error) { return j.tr.Get(id) }
361func (j JSONLocalDbTransaction) Delete(id DbKey) error                 { return j.tr.Delete(id) }
362
363func (j JSONLocalDbTransaction) Put(id DbKey, aliases []DbKey, val *jsonw.Wrapper) error {
364	return jsonLocalDbPut(j.tr, id, aliases, val)
365}
366
367func (j JSONLocalDbTransaction) Get(id DbKey) (*jsonw.Wrapper, error) {
368	return jsonLocalDbGet(j.tr, id)
369}
370
371func (j JSONLocalDbTransaction) GetInto(obj interface{}, id DbKey) (found bool, err error) {
372	return jsonLocalDbGetInto(j.tr, obj, id)
373}
374
375func (j JSONLocalDbTransaction) PutObj(id DbKey, aliases []DbKey, obj interface{}) (err error) {
376	return jsonLocalDbPutObj(j.tr, id, aliases, obj)
377}
378
379func (j JSONLocalDbTransaction) Lookup(id DbKey) (*jsonw.Wrapper, error) {
380	return jsonLocalDbLookup(j.tr, id)
381}
382
383func (j JSONLocalDbTransaction) Commit() error {
384	return j.tr.Commit()
385}
386
387func (j JSONLocalDbTransaction) Discard() {
388	j.tr.Discard()
389}
390