github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/chat/search/storage.go (about)

     1  package search
     2  
     3  import (
     4  	"context"
     5  	"crypto/hmac"
     6  	"crypto/sha256"
     7  	"fmt"
     8  	"strings"
     9  	"sync"
    10  
    11  	lru "github.com/hashicorp/golang-lru"
    12  	"github.com/keybase/client/go/chat/globals"
    13  	"github.com/keybase/client/go/chat/storage"
    14  	"github.com/keybase/client/go/chat/utils"
    15  	"github.com/keybase/client/go/encrypteddb"
    16  	"github.com/keybase/client/go/libkb"
    17  	"github.com/keybase/client/go/protocol/chat1"
    18  	"github.com/keybase/client/go/protocol/gregor1"
    19  )
    20  
    21  const (
    22  	indexVersion      = 16
    23  	tokenEntryVersion = 2
    24  	aliasEntryVersion = 3
    25  
    26  	mdDiskVersion    = 4
    27  	tokenDiskVersion = 1
    28  	aliasDiskVersion = 1
    29  )
    30  
    31  type tokenEntry struct {
    32  	Version string                                `codec:"v"`
    33  	MsgIDs  map[chat1.MessageID]chat1.EmptyStruct `codec:"m"`
    34  }
    35  
    36  func newTokenEntry() *tokenEntry {
    37  	return &tokenEntry{
    38  		Version: fmt.Sprintf("%d:%d", indexVersion, tokenEntryVersion),
    39  		MsgIDs:  make(map[chat1.MessageID]chat1.EmptyStruct),
    40  	}
    41  }
    42  
    43  func (t *tokenEntry) dup() (res *tokenEntry) {
    44  	if t == nil {
    45  		return nil
    46  	}
    47  	res = new(tokenEntry)
    48  	res.Version = t.Version
    49  	res.MsgIDs = make(map[chat1.MessageID]chat1.EmptyStruct, len(t.MsgIDs))
    50  	for m := range t.MsgIDs {
    51  		res.MsgIDs[m] = chat1.EmptyStruct{}
    52  	}
    53  	return res
    54  }
    55  
    56  var refTokenEntry = newTokenEntry()
    57  
    58  type aliasEntry struct {
    59  	Version string         `codec:"v"`
    60  	Aliases map[string]int `codec:"z"`
    61  }
    62  
    63  func newAliasEntry() *aliasEntry {
    64  	return &aliasEntry{
    65  		Version: fmt.Sprintf("%d:%d", indexVersion, aliasEntryVersion),
    66  		Aliases: make(map[string]int),
    67  	}
    68  }
    69  
    70  func (a *aliasEntry) dup() (res *aliasEntry) {
    71  	if a == nil {
    72  		return nil
    73  	}
    74  	res = new(aliasEntry)
    75  	res.Version = a.Version
    76  	res.Aliases = make(map[string]int, len(a.Aliases))
    77  	for k, v := range a.Aliases {
    78  		res.Aliases[k] = v
    79  	}
    80  	return res
    81  }
    82  
    83  func (a *aliasEntry) add(token string) {
    84  	a.Aliases[token]++
    85  }
    86  
    87  func (a *aliasEntry) remove(token string) bool {
    88  	a.Aliases[token]--
    89  	if a.Aliases[token] == 0 {
    90  		delete(a.Aliases, token)
    91  		return true
    92  	}
    93  	return false
    94  }
    95  
    96  var refAliasEntry = newAliasEntry()
    97  
    98  type diskStorage interface {
    99  	GetTokenEntry(ctx context.Context, convID chat1.ConversationID,
   100  		token string) (res *tokenEntry, err error)
   101  	PutTokenEntry(ctx context.Context, convID chat1.ConversationID,
   102  		token string, te *tokenEntry) error
   103  	RemoveTokenEntry(ctx context.Context, convID chat1.ConversationID, token string)
   104  	GetAliasEntry(ctx context.Context, alias string) (res *aliasEntry, err error)
   105  	PutAliasEntry(ctx context.Context, alias string, ae *aliasEntry) error
   106  	RemoveAliasEntry(ctx context.Context, alias string)
   107  	GetMetadata(ctx context.Context, convID chat1.ConversationID) (res *indexMetadata, err error)
   108  	PutMetadata(ctx context.Context, convID chat1.ConversationID, md *indexMetadata) error
   109  	Flush() error
   110  	Cancel()
   111  }
   112  
   113  type tokenBatch struct {
   114  	convID chat1.ConversationID
   115  	tokens map[string]*tokenEntry
   116  }
   117  
   118  func newTokenBatch(convID chat1.ConversationID) *tokenBatch {
   119  	return &tokenBatch{
   120  		convID: convID,
   121  		tokens: make(map[string]*tokenEntry),
   122  	}
   123  }
   124  
   125  type mdBatch struct {
   126  	convID chat1.ConversationID
   127  	md     *indexMetadata
   128  }
   129  
   130  type batchingStore struct {
   131  	utils.DebugLabeler
   132  	sync.Mutex
   133  
   134  	uid        gregor1.UID
   135  	mdb        *libkb.JSONLocalDb
   136  	edb        *encrypteddb.EncryptedDB
   137  	keyFn      func(ctx context.Context) ([32]byte, error)
   138  	aliasBatch map[string]*aliasEntry
   139  	tokenBatch map[chat1.ConvIDStr]*tokenBatch
   140  	mdBatch    map[chat1.ConvIDStr]*mdBatch
   141  }
   142  
   143  func newBatchingStore(g *globals.Context, uid gregor1.UID,
   144  	keyFn func(ctx context.Context) ([32]byte, error), edb *encrypteddb.EncryptedDB,
   145  	mdb *libkb.JSONLocalDb) *batchingStore {
   146  	b := &batchingStore{
   147  		DebugLabeler: utils.NewDebugLabeler(g.ExternalG(), "Search.batchingStore", false),
   148  		uid:          uid,
   149  		keyFn:        keyFn,
   150  		edb:          edb,
   151  		mdb:          mdb,
   152  	}
   153  	b.Lock()
   154  	b.resetLocked()
   155  	b.Unlock()
   156  	return b
   157  }
   158  
   159  func (b *batchingStore) resetLocked() {
   160  	b.aliasBatch = make(map[string]*aliasEntry)
   161  	b.tokenBatch = make(map[chat1.ConvIDStr]*tokenBatch)
   162  	b.mdBatch = make(map[chat1.ConvIDStr]*mdBatch)
   163  }
   164  
   165  func (b *batchingStore) GetTokenEntry(ctx context.Context, convID chat1.ConversationID,
   166  	token string) (res *tokenEntry, err error) {
   167  	b.Lock()
   168  	defer b.Unlock()
   169  	batch, ok := b.tokenBatch[convID.ConvIDStr()]
   170  	if ok && batch.tokens[token] != nil {
   171  		return batch.tokens[token].dup(), nil
   172  	}
   173  	key, err := tokenKey(ctx, b.uid, convID, token, b.keyFn)
   174  	if err != nil {
   175  		return nil, err
   176  	}
   177  	res = new(tokenEntry)
   178  	found, err := b.edb.Get(ctx, key, res)
   179  	if err != nil {
   180  		return nil, err
   181  	}
   182  	if !found {
   183  		return nil, nil
   184  	}
   185  	return res, nil
   186  }
   187  
   188  func (b *batchingStore) PutTokenEntry(ctx context.Context, convID chat1.ConversationID,
   189  	token string, te *tokenEntry) (err error) {
   190  	b.Lock()
   191  	defer b.Unlock()
   192  	key := convID.ConvIDStr()
   193  	batch, ok := b.tokenBatch[key]
   194  	if !ok {
   195  		batch = newTokenBatch(convID)
   196  	}
   197  	batch.tokens[token] = te
   198  	b.tokenBatch[key] = batch
   199  	return nil
   200  }
   201  
   202  func (b *batchingStore) RemoveTokenEntry(ctx context.Context, convID chat1.ConversationID,
   203  	token string) {
   204  	b.Lock()
   205  	defer b.Unlock()
   206  	batch, ok := b.tokenBatch[convID.ConvIDStr()]
   207  	if ok {
   208  		delete(batch.tokens, token)
   209  	}
   210  	key, err := tokenKey(ctx, b.uid, convID, token, b.keyFn)
   211  	if err != nil {
   212  		b.Debug(ctx, "RemoveTokenEntry: failed to get tokenkey: %s", err)
   213  		return
   214  	}
   215  	if err := b.mdb.Delete(key); err != nil {
   216  		b.Debug(ctx, "RemoveTokenEntry: failed to delete key: %s", err)
   217  	}
   218  }
   219  
   220  func (b *batchingStore) GetAliasEntry(ctx context.Context, alias string) (res *aliasEntry, err error) {
   221  	b.Lock()
   222  	defer b.Unlock()
   223  	var ok bool
   224  	if res, ok = b.aliasBatch[alias]; ok {
   225  		return res.dup(), nil
   226  	}
   227  	key, err := aliasKey(ctx, alias, b.keyFn)
   228  	if err != nil {
   229  		return res, err
   230  	}
   231  	res = new(aliasEntry)
   232  	found, err := b.edb.Get(ctx, key, res)
   233  	if err != nil {
   234  		return nil, err
   235  	}
   236  	if !found {
   237  		return nil, nil
   238  	}
   239  	return res, nil
   240  }
   241  
   242  func (b *batchingStore) PutAliasEntry(ctx context.Context, alias string, ae *aliasEntry) (err error) {
   243  	b.Lock()
   244  	defer b.Unlock()
   245  	b.aliasBatch[alias] = ae
   246  	return nil
   247  }
   248  
   249  func (b *batchingStore) RemoveAliasEntry(ctx context.Context, alias string) {
   250  	b.Lock()
   251  	defer b.Unlock()
   252  	delete(b.aliasBatch, alias)
   253  	key, err := aliasKey(ctx, alias, b.keyFn)
   254  	if err != nil {
   255  		b.Debug(ctx, "RemoveAliasEntry: failed to get key: %s", err)
   256  		return
   257  	}
   258  	if err := b.mdb.Delete(key); err != nil {
   259  		b.Debug(ctx, "RemoveAliasEntry: failed to delete key: %s", err)
   260  	}
   261  }
   262  
   263  func (b *batchingStore) GetMetadata(ctx context.Context, convID chat1.ConversationID) (res *indexMetadata, err error) {
   264  	b.Lock()
   265  	defer b.Unlock()
   266  	if md, ok := b.mdBatch[convID.ConvIDStr()]; ok {
   267  		return md.md.dup(), nil
   268  	}
   269  	key := metadataKey(b.uid, convID)
   270  	res = new(indexMetadata)
   271  	found, err := b.mdb.GetIntoMsgpack(res, key)
   272  	if err != nil {
   273  		return nil, err
   274  	}
   275  	if !found {
   276  		return nil, nil
   277  	}
   278  	return res, nil
   279  }
   280  
   281  func (b *batchingStore) PutMetadata(ctx context.Context, convID chat1.ConversationID, md *indexMetadata) (err error) {
   282  	b.Lock()
   283  	defer b.Unlock()
   284  	b.mdBatch[convID.ConvIDStr()] = &mdBatch{
   285  		md:     md,
   286  		convID: convID,
   287  	}
   288  	return nil
   289  }
   290  
   291  func (b *batchingStore) Flush() (err error) {
   292  	ctx := context.Background()
   293  	defer b.Trace(ctx, &err, "Flush")()
   294  	b.Lock()
   295  	defer b.Unlock()
   296  	if len(b.tokenBatch) == 0 && len(b.aliasBatch) == 0 && len(b.mdBatch) == 0 {
   297  		return nil
   298  	}
   299  	defer b.resetLocked()
   300  
   301  	b.Debug(ctx, "Flush: flushing tokens from %d convs", len(b.tokenBatch))
   302  	for _, tokenBatch := range b.tokenBatch {
   303  		b.Debug(ctx, "Flush: flushing %d tokens from %s", len(tokenBatch.tokens), tokenBatch.convID)
   304  		for token, te := range tokenBatch.tokens {
   305  			key, err := tokenKey(ctx, b.uid, tokenBatch.convID, token, b.keyFn)
   306  			if err != nil {
   307  				return err
   308  			}
   309  			if err := b.edb.Put(ctx, key, te); err != nil {
   310  				return err
   311  			}
   312  		}
   313  	}
   314  	b.Debug(ctx, "Flush: flushing %d aliases", len(b.aliasBatch))
   315  	for alias, ae := range b.aliasBatch {
   316  		key, err := aliasKey(ctx, alias, b.keyFn)
   317  		if err != nil {
   318  			return err
   319  		}
   320  		if err := b.edb.Put(ctx, key, ae); err != nil {
   321  			return err
   322  		}
   323  	}
   324  	b.Debug(ctx, "Flush: flushing %d conv metadata", len(b.mdBatch))
   325  	for _, mdBatch := range b.mdBatch {
   326  		b.Debug(ctx, "Flush: flushing md from %s", mdBatch.convID)
   327  		if err := b.mdb.PutObjMsgpack(metadataKey(b.uid, mdBatch.convID), nil, mdBatch.md); err != nil {
   328  			return err
   329  		}
   330  	}
   331  	return nil
   332  }
   333  
   334  func (b *batchingStore) Cancel() {
   335  	defer b.Trace(context.Background(), nil, "Cancel")()
   336  	b.Lock()
   337  	defer b.Unlock()
   338  	b.resetLocked()
   339  }
   340  
   341  type store struct {
   342  	globals.Contextified
   343  	utils.DebugLabeler
   344  	sync.RWMutex
   345  
   346  	uid         gregor1.UID
   347  	keyFn       func(ctx context.Context) ([32]byte, error)
   348  	aliasCache  *lru.Cache
   349  	tokenCache  *lru.Cache
   350  	diskStorage diskStorage
   351  }
   352  
   353  func newStore(g *globals.Context, uid gregor1.UID) *store {
   354  	ac, _ := lru.New(10000)
   355  	tc, _ := lru.New(3000)
   356  	keyFn := func(ctx context.Context) ([32]byte, error) {
   357  		return storage.GetSecretBoxKey(ctx, g.ExternalG())
   358  	}
   359  	dbFn := func(g *libkb.GlobalContext) *libkb.JSONLocalDb {
   360  		return g.LocalChatDb
   361  	}
   362  	return &store{
   363  		Contextified: globals.NewContextified(g),
   364  		DebugLabeler: utils.NewDebugLabeler(g.ExternalG(), "Search.store", false),
   365  		uid:          uid,
   366  		keyFn:        keyFn,
   367  		aliasCache:   ac,
   368  		tokenCache:   tc,
   369  		diskStorage: newBatchingStore(g, uid, keyFn, encrypteddb.New(g.ExternalG(), dbFn, keyFn),
   370  			g.LocalChatDb),
   371  	}
   372  }
   373  
   374  func metadataKey(uid gregor1.UID, convID chat1.ConversationID) libkb.DbKey {
   375  	return metadataKeyWithVersion(uid, convID, mdDiskVersion)
   376  }
   377  
   378  func metadataKeyWithVersion(uid gregor1.UID, convID chat1.ConversationID, version int) libkb.DbKey {
   379  	var key string
   380  	switch version {
   381  	case 1:
   382  		// original key
   383  		key = fmt.Sprintf("idx:%s:%s", convID, uid)
   384  	case 2:
   385  		// uid as a prefix makes more sense for leveldb to keep values
   386  		// co-located
   387  		key = fmt.Sprintf("idx:%s:%s", uid, convID)
   388  	case 3:
   389  		// changed to use chat1.ConversationIndexDisk to store arrays instead
   390  		// of maps.
   391  		key = fmt.Sprintf("idxd:%s:%s", uid, convID)
   392  	case 4:
   393  		// change to store metadata separate from tokens/aliases
   394  		key = fmt.Sprintf("md:%s:%s", uid, convID)
   395  	default:
   396  		panic("invalid index key version specified")
   397  	}
   398  	return libkb.DbKey{
   399  		Typ: libkb.DBChatIndex,
   400  		Key: key,
   401  	}
   402  }
   403  
   404  func tokenKey(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID, dat string,
   405  	keyFn func(ctx context.Context) ([32]byte, error)) (res libkb.DbKey, err error) {
   406  	return tokenKeyWithVersion(ctx, uid, convID, dat, tokenDiskVersion, keyFn)
   407  }
   408  
   409  func tokenKeyWithVersion(ctx context.Context, uid gregor1.UID,
   410  	convID chat1.ConversationID, dat string, version int, keyFn func(ctx context.Context) ([32]byte, error)) (res libkb.DbKey, err error) {
   411  	var key string
   412  	switch version {
   413  	case 1:
   414  		material, err := keyFn(ctx)
   415  		if err != nil {
   416  			return res, err
   417  		}
   418  		hasher := hmac.New(sha256.New, material[:])
   419  		_, err = hasher.Write([]byte(dat))
   420  		if err != nil {
   421  			return res, err
   422  		}
   423  		_, err = hasher.Write(convID.DbShortForm())
   424  		if err != nil {
   425  			return res, err
   426  		}
   427  		_, err = hasher.Write(uid.Bytes())
   428  		if err != nil {
   429  			return res, err
   430  		}
   431  		_, err = hasher.Write([]byte(libkb.EncryptionReasonChatIndexerTokenKey))
   432  		if err != nil {
   433  			return res, err
   434  		}
   435  		key = fmt.Sprintf("tm:%s:%s:%s", uid, convID, hasher.Sum(nil))
   436  	default:
   437  		return res, fmt.Errorf("unexpected token version %d", version)
   438  	}
   439  	return libkb.DbKey{
   440  		Typ: libkb.DBChatIndex,
   441  		Key: key,
   442  	}, nil
   443  }
   444  
   445  func aliasKey(ctx context.Context, dat string,
   446  	keyFn func(ctx context.Context) ([32]byte, error)) (res libkb.DbKey, err error) {
   447  	return aliasKeyWithVersion(ctx, dat, aliasDiskVersion, keyFn)
   448  }
   449  
   450  func aliasKeyWithVersion(ctx context.Context, dat string, version int,
   451  	keyFn func(ctx context.Context) ([32]byte, error)) (res libkb.DbKey, err error) {
   452  	var key string
   453  	switch version {
   454  	case 1:
   455  		material, err := keyFn(ctx)
   456  		if err != nil {
   457  			return res, err
   458  		}
   459  		hasher := hmac.New(sha256.New, material[:])
   460  		_, err = hasher.Write([]byte(dat))
   461  		if err != nil {
   462  			return res, err
   463  		}
   464  		_, err = hasher.Write([]byte(libkb.EncryptionReasonChatIndexerAliasKey))
   465  		if err != nil {
   466  			return res, err
   467  		}
   468  		key = fmt.Sprintf("al:%s", hasher.Sum(nil))
   469  	default:
   470  		return res, fmt.Errorf("unexpected token version %d", version)
   471  	}
   472  	return libkb.DbKey{
   473  		Typ: libkb.DBChatIndex,
   474  		Key: key,
   475  	}, nil
   476  }
   477  
   478  // deleteOldVersions purges old disk structures so we don't error out on msg
   479  // pack decode or strand indexes with ephemeral content.
   480  func (s *store) deleteOldVersions(ctx context.Context, keyFn func(int) (libkb.DbKey, error), minVersion, maxVersion int) {
   481  	for version := minVersion; version < maxVersion; version++ {
   482  		key, err := keyFn(version)
   483  		if err != nil {
   484  			s.Debug(ctx, "unable to get key for version %d, %v", version, err)
   485  			continue
   486  		}
   487  		if err := s.G().LocalChatDb.Delete(key); err != nil {
   488  			s.Debug(ctx, "deleteOldVersions: failed to delete key: %s", err)
   489  		}
   490  	}
   491  }
   492  
   493  func (s *store) deleteOldMetadataVersions(ctx context.Context, convID chat1.ConversationID) {
   494  	keyFn := func(version int) (libkb.DbKey, error) {
   495  		return metadataKeyWithVersion(s.uid, convID, version), nil
   496  	}
   497  	s.deleteOldVersions(ctx, keyFn, 3, mdDiskVersion)
   498  }
   499  
   500  func (s *store) deleteOldTokenVersions(ctx context.Context, convID chat1.ConversationID, token string) {
   501  	keyFn := func(version int) (libkb.DbKey, error) {
   502  		return tokenKeyWithVersion(ctx, s.uid, convID, token, version, s.keyFn)
   503  	}
   504  	s.deleteOldVersions(ctx, keyFn, 1, tokenDiskVersion)
   505  }
   506  
   507  func (s *store) deleteOldAliasVersions(ctx context.Context, alias string) {
   508  	keyFn := func(version int) (libkb.DbKey, error) {
   509  		return aliasKeyWithVersion(ctx, alias, version, s.keyFn)
   510  	}
   511  	s.deleteOldVersions(ctx, keyFn, 1, aliasDiskVersion)
   512  }
   513  
   514  func (s *store) GetHits(ctx context.Context, convID chat1.ConversationID, term string) (res map[chat1.MessageID]chat1.EmptyStruct, err error) {
   515  	defer s.Trace(ctx, &err, "GetHits")()
   516  	s.RLock()
   517  	defer s.RUnlock()
   518  	res = make(map[chat1.MessageID]chat1.EmptyStruct)
   519  	// Get all terms and aliases
   520  	terms := make(map[string]chat1.EmptyStruct)
   521  	ae, err := s.getAliasEntry(ctx, term)
   522  	if err != nil {
   523  		return res, err
   524  	}
   525  	aliases := ae.Aliases
   526  	terms[term] = chat1.EmptyStruct{}
   527  	for alias := range aliases {
   528  		terms[alias] = chat1.EmptyStruct{}
   529  	}
   530  	// Find all the msg IDs
   531  	for term := range terms {
   532  		te, err := s.getTokenEntry(ctx, convID, term)
   533  		if err != nil {
   534  			return nil, err
   535  		}
   536  		for msgID := range te.MsgIDs {
   537  			res[msgID] = chat1.EmptyStruct{}
   538  		}
   539  	}
   540  	return res, nil
   541  }
   542  
   543  func (s *store) tokenCacheKey(convID chat1.ConversationID, token string) string {
   544  	return fmt.Sprintf("%s:%s", convID, token)
   545  }
   546  
   547  func (s *store) getTokenEntry(ctx context.Context, convID chat1.ConversationID, token string) (res *tokenEntry, err error) {
   548  	cacheKey := s.tokenCacheKey(convID, token)
   549  	if te, ok := s.tokenCache.Get(cacheKey); ok {
   550  		return te.(*tokenEntry), nil
   551  	}
   552  	defer func() {
   553  		if err == nil {
   554  			s.tokenCache.Add(cacheKey, res.dup())
   555  		}
   556  	}()
   557  	if res, err = s.diskStorage.GetTokenEntry(ctx, convID, token); err != nil {
   558  		return nil, err
   559  	}
   560  	if res == nil {
   561  		s.deleteOldTokenVersions(ctx, convID, token)
   562  		return newTokenEntry(), nil
   563  	}
   564  	if res.Version != refTokenEntry.Version {
   565  		return newTokenEntry(), nil
   566  	}
   567  	return res, nil
   568  }
   569  
   570  func (s *store) getAliasEntry(ctx context.Context, alias string) (res *aliasEntry, err error) {
   571  	if dat, ok := s.aliasCache.Get(alias); ok {
   572  		return dat.(*aliasEntry), nil
   573  	}
   574  	defer func() {
   575  		if err == nil {
   576  			s.aliasCache.Add(alias, res.dup())
   577  		}
   578  	}()
   579  	if res, err = s.diskStorage.GetAliasEntry(ctx, alias); err != nil {
   580  		return nil, err
   581  	}
   582  	if res == nil {
   583  		s.deleteOldAliasVersions(ctx, alias)
   584  		return newAliasEntry(), nil
   585  	}
   586  	if res.Version != refAliasEntry.Version {
   587  		return newAliasEntry(), nil
   588  	}
   589  	return res, nil
   590  }
   591  
   592  func (s *store) putTokenEntry(ctx context.Context, convID chat1.ConversationID,
   593  	token string, te *tokenEntry) (err error) {
   594  	defer func() {
   595  		if err == nil {
   596  			s.tokenCache.Add(s.tokenCacheKey(convID, token), te.dup())
   597  		}
   598  	}()
   599  	return s.diskStorage.PutTokenEntry(ctx, convID, token, te)
   600  }
   601  
   602  func (s *store) putAliasEntry(ctx context.Context, alias string, ae *aliasEntry) (err error) {
   603  	defer func() {
   604  		if err == nil {
   605  			s.aliasCache.Add(alias, ae.dup())
   606  		}
   607  	}()
   608  	return s.diskStorage.PutAliasEntry(ctx, alias, ae)
   609  }
   610  
   611  func (s *store) deleteTokenEntry(ctx context.Context, convID chat1.ConversationID,
   612  	token string) {
   613  	s.tokenCache.Remove(s.tokenCacheKey(convID, token))
   614  	s.diskStorage.RemoveTokenEntry(ctx, convID, token)
   615  }
   616  
   617  func (s *store) deleteAliasEntry(ctx context.Context, alias string) {
   618  	s.aliasCache.Remove(alias)
   619  	s.diskStorage.RemoveAliasEntry(ctx, alias)
   620  }
   621  
   622  // addTokens add the given tokens to the index under the given message
   623  // id, when ingesting EDIT messages the msgID is of the superseded msg but the
   624  // tokens are from the EDIT itself.
   625  func (s *store) addTokens(ctx context.Context,
   626  	convID chat1.ConversationID, tokens tokenMap, msgID chat1.MessageID) error {
   627  	for token, aliases := range tokens {
   628  		// Update the token entry with the msg ID hit
   629  		te, err := s.getTokenEntry(ctx, convID, token)
   630  		if err != nil {
   631  			return err
   632  		}
   633  		te.MsgIDs[msgID] = chat1.EmptyStruct{}
   634  
   635  		// Update all the aliases to point at the token
   636  		for alias := range aliases {
   637  			aliasEntry, err := s.getAliasEntry(ctx, alias)
   638  			if err != nil {
   639  				return err
   640  			}
   641  			aliasEntry.add(token)
   642  			if err := s.putAliasEntry(ctx, alias, aliasEntry); err != nil {
   643  				return err
   644  			}
   645  		}
   646  		if err := s.putTokenEntry(ctx, convID, token, te); err != nil {
   647  			return err
   648  		}
   649  	}
   650  	return nil
   651  }
   652  
   653  func (s *store) addMsg(ctx context.Context, convID chat1.ConversationID,
   654  	msg chat1.MessageUnboxed) error {
   655  	tokens := tokensFromMsg(msg)
   656  	return s.addTokens(ctx, convID, tokens, msg.GetMessageID())
   657  }
   658  
   659  func (s *store) removeMsg(ctx context.Context, convID chat1.ConversationID,
   660  	msg chat1.MessageUnboxed) error {
   661  	// find the msgID that the index stores
   662  	var msgID chat1.MessageID
   663  	switch msg.GetMessageType() {
   664  	case chat1.MessageType_EDIT, chat1.MessageType_ATTACHMENTUPLOADED:
   665  		superIDs, err := utils.GetSupersedes(msg)
   666  		if err != nil || len(superIDs) != 1 {
   667  			return err
   668  		}
   669  		msgID = superIDs[0]
   670  	default:
   671  		msgID = msg.GetMessageID()
   672  	}
   673  
   674  	for token, aliases := range tokensFromMsg(msg) {
   675  		// handle token
   676  		te, err := s.getTokenEntry(ctx, convID, token)
   677  		if err != nil {
   678  			return err
   679  		}
   680  		delete(te.MsgIDs, msgID)
   681  		if len(te.MsgIDs) == 0 {
   682  			s.deleteTokenEntry(ctx, convID, token)
   683  		} else {
   684  			// If there are still IDs, just write out the updated version
   685  			if err := s.putTokenEntry(ctx, convID, token, te); err != nil {
   686  				return err
   687  			}
   688  		}
   689  		// take out aliases
   690  		for alias := range aliases {
   691  			aliasEntry, err := s.getAliasEntry(ctx, alias)
   692  			if err != nil {
   693  				return err
   694  			}
   695  			if aliasEntry.remove(token) {
   696  				s.deleteAliasEntry(ctx, alias)
   697  			} else {
   698  				if err := s.putAliasEntry(ctx, alias, aliasEntry); err != nil {
   699  					return err
   700  				}
   701  			}
   702  		}
   703  	}
   704  	return nil
   705  }
   706  
   707  func (s *store) GetMetadata(ctx context.Context, convID chat1.ConversationID) (res *indexMetadata, err error) {
   708  	if res, err = s.diskStorage.GetMetadata(ctx, convID); err != nil {
   709  		return res, err
   710  	}
   711  	if res == nil {
   712  		s.deleteOldMetadataVersions(ctx, convID)
   713  		return newIndexMetadata(), nil
   714  	}
   715  	if res.Version != refIndexMetadata.Version {
   716  		return newIndexMetadata(), nil
   717  	}
   718  	return res, nil
   719  }
   720  
   721  func (s *store) Add(ctx context.Context, convID chat1.ConversationID,
   722  	msgs []chat1.MessageUnboxed) (err error) {
   723  	defer s.Trace(ctx, &err, "Add")()
   724  	s.Lock()
   725  	defer s.Unlock()
   726  
   727  	fetchSupersededMsgs := func(msg chat1.MessageUnboxed) []chat1.MessageUnboxed {
   728  		superIDs, err := utils.GetSupersedes(msg)
   729  		if err != nil {
   730  			s.Debug(ctx, "unable to get supersedes: %v", err)
   731  			return nil
   732  		}
   733  		reason := chat1.GetThreadReason_INDEXED_SEARCH
   734  		supersededMsgs, err := s.G().ChatHelper.GetMessages(ctx, s.uid, convID, superIDs,
   735  			false /* resolveSupersedes*/, &reason)
   736  		if err != nil {
   737  			// Log but ignore error
   738  			s.Debug(ctx, "unable to get fetch messages: %v", err)
   739  			return nil
   740  		}
   741  		return supersededMsgs
   742  	}
   743  
   744  	modified := false
   745  	md, err := s.GetMetadata(ctx, convID)
   746  	if err != nil {
   747  		s.Debug(ctx, "failed to get metadata: %s", err)
   748  		return err
   749  	}
   750  	defer func() {
   751  		if modified {
   752  			if err := s.diskStorage.PutMetadata(ctx, convID, md); err != nil {
   753  				s.Debug(ctx, "failed to put metadata: %s", err)
   754  			}
   755  		}
   756  	}()
   757  	for _, msg := range msgs {
   758  		seenIDs := md.SeenIDs
   759  		// Don't add if we've seen
   760  		if _, ok := seenIDs[msg.GetMessageID()]; ok {
   761  			continue
   762  		}
   763  		modified = true
   764  		seenIDs[msg.GetMessageID()] = chat1.EmptyStruct{}
   765  		// NOTE DELETE and DELETEHISTORY are handled through calls to `remove`,
   766  		// other messages will be added if there is any content that can be
   767  		// indexed.
   768  		switch msg.GetMessageType() {
   769  		case chat1.MessageType_ATTACHMENTUPLOADED:
   770  			supersededMsgs := fetchSupersededMsgs(msg)
   771  			for _, sm := range supersededMsgs {
   772  				seenIDs[sm.GetMessageID()] = chat1.EmptyStruct{}
   773  				err := s.addMsg(ctx, convID, sm)
   774  				if err != nil {
   775  					return err
   776  				}
   777  			}
   778  		case chat1.MessageType_EDIT:
   779  			tokens := tokensFromMsg(msg)
   780  			supersededMsgs := fetchSupersededMsgs(msg)
   781  			// remove the original message text and replace it with the edited
   782  			// contents (using the original id in the index)
   783  			for _, sm := range supersededMsgs {
   784  				seenIDs[sm.GetMessageID()] = chat1.EmptyStruct{}
   785  				err := s.removeMsg(ctx, convID, sm)
   786  				if err != nil {
   787  					return err
   788  				}
   789  				err = s.addTokens(ctx, convID, tokens, sm.GetMessageID())
   790  				if err != nil {
   791  					return err
   792  				}
   793  			}
   794  		default:
   795  			err := s.addMsg(ctx, convID, msg)
   796  			if err != nil {
   797  				return err
   798  			}
   799  		}
   800  	}
   801  	return nil
   802  }
   803  
   804  // Remove tokenizes the message content and updates/removes index keys for each token.
   805  func (s *store) Remove(ctx context.Context, convID chat1.ConversationID,
   806  	msgs []chat1.MessageUnboxed) (err error) {
   807  	defer s.Trace(ctx, &err, "Remove")()
   808  	s.Lock()
   809  	defer s.Unlock()
   810  
   811  	md, err := s.GetMetadata(ctx, convID)
   812  	if err != nil {
   813  		return err
   814  	}
   815  
   816  	modified := false
   817  	seenIDs := md.SeenIDs
   818  	for _, msg := range msgs {
   819  		// Don't remove if we haven't seen
   820  		if _, ok := seenIDs[msg.GetMessageID()]; !ok {
   821  			continue
   822  		}
   823  		modified = true
   824  		seenIDs[msg.GetMessageID()] = chat1.EmptyStruct{}
   825  		err := s.removeMsg(ctx, convID, msg)
   826  		if err != nil {
   827  			return err
   828  		}
   829  	}
   830  	if modified {
   831  		return s.diskStorage.PutMetadata(ctx, convID, md)
   832  	}
   833  	return nil
   834  }
   835  
   836  func (s *store) ClearMemory() {
   837  	defer s.Trace(context.Background(), nil, "ClearMemory")()
   838  	s.aliasCache.Purge()
   839  	s.tokenCache.Purge()
   840  	s.diskStorage.Cancel()
   841  }
   842  
   843  func (s *store) Clear(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID) error {
   844  	mdKey := metadataKey(uid, convID)
   845  	tokKey := libkb.DbKey{
   846  		Typ: libkb.DBChatIndex,
   847  		Key: fmt.Sprintf("tm:%s:%s:", uid, convID),
   848  	}
   849  	dbKeys, err := s.G().LocalChatDb.KeysWithPrefixes(mdKey.ToBytes(), tokKey.ToBytes())
   850  	if err != nil {
   851  		return fmt.Errorf("could not get KeysWithPrefixes: %v", err)
   852  	}
   853  	epick := libkb.FirstErrorPicker{}
   854  	for dbKey := range dbKeys {
   855  		if dbKey.Typ == libkb.DBChatIndex &&
   856  			(strings.HasPrefix(dbKey.Key, mdKey.Key) ||
   857  				strings.HasPrefix(dbKey.Key, tokKey.Key)) {
   858  			epick.Push(s.G().LocalChatDb.Delete(dbKey))
   859  		}
   860  	}
   861  	s.ClearMemory()
   862  	return epick.Error()
   863  }
   864  
   865  func (s *store) Flush() error {
   866  	return s.diskStorage.Flush()
   867  }