github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/teams/storage/generic.go (about)

     1  package storage
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"strings"
     7  	"sync"
     8  
     9  	lru "github.com/hashicorp/golang-lru"
    10  	context "golang.org/x/net/context"
    11  
    12  	"github.com/keybase/client/go/encrypteddb"
    13  	"github.com/keybase/client/go/libkb"
    14  	"github.com/keybase/client/go/protocol/keybase1"
    15  )
    16  
    17  // Store TeamData's of FastTeamData's on memory and disk. Threadsafe.
    18  type storageGeneric struct {
    19  	sync.Mutex
    20  	mem         *memoryStorageGeneric
    21  	disk        *diskStorageGeneric
    22  	description string
    23  }
    24  
    25  func newStorageGeneric(g *libkb.GlobalContext, lruSize int, version int, dbObjTyp libkb.ObjType, reason libkb.EncryptionReason, description string, gdi func() diskItemGeneric) *storageGeneric {
    26  	return &storageGeneric{
    27  		mem:         newMemoryStorageGeneric(lruSize),
    28  		disk:        newDiskStorageGeneric(g, version, dbObjTyp, reason, gdi),
    29  		description: description,
    30  	}
    31  }
    32  
    33  func (s *storageGeneric) put(mctx libkb.MetaContext, state teamDataGeneric) {
    34  	s.Lock()
    35  	defer s.Unlock()
    36  
    37  	s.mem.put(mctx, state)
    38  
    39  	err := s.disk.put(mctx, state)
    40  	if err != nil {
    41  		mctx.Warning("teams/storage.Generic#Put err: %v", err)
    42  	}
    43  }
    44  
    45  // Can return nil.
    46  func (s *storageGeneric) get(mctx libkb.MetaContext, teamID keybase1.TeamID, public bool) teamDataGeneric {
    47  	s.Lock()
    48  	defer s.Unlock()
    49  
    50  	item := s.mem.get(mctx, teamID, public)
    51  	if item != nil {
    52  		mctx.VLogf(libkb.VLog0, "teams/storage.Generic#Get(%v) hit mem (%s)", teamID, s.description)
    53  		// Mem hit
    54  		return item
    55  	}
    56  
    57  	res, found, err := s.disk.get(mctx, teamID, public)
    58  	if found && err == nil {
    59  		// Disk hit
    60  		mctx.VLogf(libkb.VLog0, "teams/storage.Generic#Get(%v) hit disk (%s)", teamID, s.description)
    61  		s.mem.put(mctx, res)
    62  		return res
    63  	}
    64  	if err != nil {
    65  		mctx.Debug("teams/storage.Generic#Get(%v) disk err: %v", teamID, err)
    66  	}
    67  	mctx.VLogf(libkb.VLog0, "teams/storage.Generic#Get(%v) missed (%s)", teamID, s.description)
    68  	return nil
    69  }
    70  
    71  // Clear the in-memory storage.
    72  func (s *storageGeneric) ClearMem() {
    73  	s.mem.clear()
    74  }
    75  
    76  func (s *storageGeneric) Shutdown() {
    77  	s.mem.shutdown()
    78  }
    79  
    80  func (s *storageGeneric) MemSize() int {
    81  	return s.mem.len()
    82  }
    83  
    84  // --------------------------------------------------
    85  
    86  type teamDataGeneric interface {
    87  	IsPublic() bool
    88  	ID() keybase1.TeamID
    89  }
    90  
    91  type diskItemGeneric interface {
    92  	version() int
    93  	value() teamDataGeneric
    94  	setVersion(i int)
    95  	setValue(o teamDataGeneric) error
    96  }
    97  
    98  // Store TeamData's on disk. Threadsafe.
    99  type diskStorageGeneric struct {
   100  	sync.Mutex
   101  	encryptedDB      *encrypteddb.EncryptedDB
   102  	version          int
   103  	dbObjTyp         libkb.ObjType
   104  	getEmptyDiskItem func() diskItemGeneric
   105  }
   106  
   107  func newDiskStorageGeneric(g *libkb.GlobalContext, version int, dbObjTyp libkb.ObjType, reason libkb.EncryptionReason, gdi func() diskItemGeneric) *diskStorageGeneric {
   108  	keyFn := func(ctx context.Context) ([32]byte, error) {
   109  		return encrypteddb.GetSecretBoxKey(ctx, g, reason, "encrypt teams storage")
   110  	}
   111  	dbFn := func(g *libkb.GlobalContext) *libkb.JSONLocalDb {
   112  		return g.LocalDb
   113  	}
   114  	return &diskStorageGeneric{
   115  		encryptedDB:      encrypteddb.New(g, dbFn, keyFn),
   116  		version:          version,
   117  		dbObjTyp:         dbObjTyp,
   118  		getEmptyDiskItem: gdi,
   119  	}
   120  }
   121  
   122  func (s *diskStorageGeneric) put(mctx libkb.MetaContext, state teamDataGeneric) error {
   123  	s.Lock()
   124  	defer s.Unlock()
   125  
   126  	if !mctx.ActiveDevice().Valid() && !state.IsPublic() {
   127  		mctx.Debug("skipping team store since user is logged out")
   128  		return nil
   129  	}
   130  
   131  	key := s.dbKey(mctx, state.ID(), state.IsPublic())
   132  	item := s.getEmptyDiskItem()
   133  	item.setVersion(s.version)
   134  	err := item.setValue(state)
   135  	if err != nil {
   136  		return err
   137  	}
   138  
   139  	err = s.encryptedDB.Put(mctx.Ctx(), key, item)
   140  	if err != nil {
   141  		return err
   142  	}
   143  	return nil
   144  }
   145  
   146  // Res is valid if (found && err == nil)
   147  func (s *diskStorageGeneric) get(mctx libkb.MetaContext, teamID keybase1.TeamID, public bool) (teamDataGeneric, bool, error) {
   148  	s.Lock()
   149  	defer s.Unlock()
   150  
   151  	key := s.dbKey(mctx, teamID, public)
   152  	item := s.getEmptyDiskItem()
   153  	found, err := s.encryptedDB.Get(mctx.Ctx(), key, item)
   154  	if (err != nil) || !found {
   155  		return nil, found, err
   156  	}
   157  
   158  	if item.version() != s.version {
   159  		// Pretend it wasn't found.
   160  		return nil, false, nil
   161  	}
   162  
   163  	ret := item.value()
   164  
   165  	// Sanity check
   166  	if len(ret.ID()) == 0 {
   167  		return nil, false, fmt.Errorf("decode from disk had empty team id")
   168  	}
   169  	if !ret.ID().Eq(teamID) {
   170  		return nil, false, fmt.Errorf("decode from disk had wrong team id %v != %v", ret.ID(), teamID)
   171  	}
   172  	if ret.IsPublic() != public {
   173  		return nil, false, fmt.Errorf("decode from disk had wrong publicness %v != %v (%v)", ret.IsPublic(), public, teamID)
   174  	}
   175  
   176  	return item.value(), true, nil
   177  }
   178  
   179  func (s *diskStorageGeneric) dbKey(mctx libkb.MetaContext, teamID keybase1.TeamID, public bool) libkb.DbKey {
   180  	key := fmt.Sprintf("tid:%s", teamID)
   181  	if public {
   182  		key = fmt.Sprintf("tid:%s|pub", teamID)
   183  	}
   184  	return libkb.DbKey{
   185  		Typ: s.dbObjTyp,
   186  		Key: key,
   187  	}
   188  }
   189  
   190  // --------------------------------------------------
   191  
   192  // Store some TeamSigChainState's in memory. Threadsafe.
   193  type memoryStorageGeneric struct {
   194  	sync.Mutex // protects the pointer on lru
   195  	lru        *lru.Cache
   196  }
   197  
   198  func newMemoryStorageGeneric(size int) *memoryStorageGeneric {
   199  	nlru, err := lru.New(size)
   200  	if err != nil {
   201  		// lru.New only panics if size <= 0
   202  		log.Panicf("Could not create lru cache: %v", err)
   203  	}
   204  	return &memoryStorageGeneric{
   205  		lru: nlru,
   206  	}
   207  }
   208  
   209  func (s *memoryStorageGeneric) getLRU() *lru.Cache {
   210  	s.Lock()
   211  	defer s.Unlock()
   212  	return s.lru
   213  }
   214  
   215  func (s *memoryStorageGeneric) put(mctx libkb.MetaContext, state teamDataGeneric) {
   216  	s.getLRU().Add(s.key(state.ID(), state.IsPublic()), state)
   217  }
   218  
   219  // Can return nil.
   220  func (s *memoryStorageGeneric) get(mctx libkb.MetaContext, teamID keybase1.TeamID, public bool) teamDataGeneric {
   221  	untyped, ok := s.getLRU().Get(s.key(teamID, public))
   222  	if !ok {
   223  		return nil
   224  	}
   225  	state, ok := untyped.(teamDataGeneric)
   226  	if !ok {
   227  		mctx.Warning("Team MemoryStorage got bad type from lru: %T", untyped)
   228  		return nil
   229  	}
   230  	return state
   231  }
   232  
   233  func (s *memoryStorageGeneric) shutdown() {
   234  	s.Lock()
   235  	defer s.Unlock()
   236  	var err error
   237  	s.lru, err = lru.New(1)
   238  	if err != nil {
   239  		panic(err)
   240  	}
   241  }
   242  
   243  func (s *memoryStorageGeneric) len() int {
   244  	return s.getLRU().Len()
   245  }
   246  
   247  func (s *memoryStorageGeneric) clear() {
   248  	s.getLRU().Purge()
   249  }
   250  
   251  func (s *memoryStorageGeneric) key(teamID keybase1.TeamID, public bool) (key string) {
   252  	return genericStringKey(teamID, public)
   253  }
   254  
   255  func genericStringKey(teamID keybase1.TeamID, public bool) (key string) {
   256  	key = fmt.Sprintf("tid:%s", teamID)
   257  	if public {
   258  		key = fmt.Sprintf("tid:%s|pub", teamID)
   259  	}
   260  	return key
   261  }
   262  
   263  // --------------------------------------------------
   264  
   265  // ParseTeamIDDBKey takes an tid:-style key (used by FTL and slow team loader)
   266  // and returns a regular team id. We can safely strip away the |pub marker
   267  // because the publicness of a team is encoded in its ID.
   268  func ParseTeamIDDBKey(s string) (teamID keybase1.TeamID, err error) {
   269  	if !strings.HasPrefix(s, "tid:") {
   270  		return "", fmt.Errorf("does not start with team id prefix")
   271  	}
   272  	s = strings.TrimPrefix(s, "tid:")
   273  	s = strings.TrimSuffix(s, "|pub")
   274  	return keybase1.TeamID(s), nil
   275  }