github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/uidmap/servicemap.go (about)

     1  package uidmap
     2  
     3  import (
     4  	"fmt"
     5  	"sync"
     6  	"time"
     7  
     8  	lru "github.com/hashicorp/golang-lru"
     9  	"github.com/keybase/client/go/libkb"
    10  	keybase1 "github.com/keybase/client/go/protocol/keybase1"
    11  	"golang.org/x/net/context"
    12  )
    13  
    14  // Like UIDMapper, a local service to obtain server-trust service summary (or
    15  // "serviceMap") for UIDs, cached in memory and in leveldb.
    16  
    17  // DefaultNetworkBudget is a networkBudget const which will make the request
    18  // use default timeout / retry settings.
    19  const DefaultNetworkBudget = time.Duration(0)
    20  
    21  // DisallowNetworkBudget is a networkBudget const equal to 1 ns, where we won't
    22  // even bother making a request that would inevitably not finish in time.
    23  const DisallowNetworkBudget = time.Duration(1)
    24  
    25  type ServiceSummaryMap struct {
    26  	sync.Mutex
    27  	memCache *lru.Cache
    28  }
    29  
    30  func NewServiceSummaryMap(memSize int) *ServiceSummaryMap {
    31  	memcache, err := lru.New(memSize)
    32  	if err != nil {
    33  		panic(fmt.Sprintf("failed to make LRU size=%d: %s", memSize, err))
    34  	}
    35  	return &ServiceSummaryMap{
    36  		memCache: memcache,
    37  	}
    38  }
    39  
    40  func serviceMapDBKey(u keybase1.UID) libkb.DbKey {
    41  	return libkb.DbKey{Typ: libkb.DBUidToServiceMap, Key: string(u)}
    42  }
    43  
    44  func (s *ServiceSummaryMap) findServiceSummaryLocally(ctx context.Context, g libkb.UIDMapperContext,
    45  	uid keybase1.UID, freshness time.Duration) (res libkb.UserServiceSummaryPackage, found bool, err error) {
    46  
    47  	voidp, ok := s.memCache.Get(uid)
    48  	if ok {
    49  		tmp, ok := voidp.(libkb.UserServiceSummaryPackage)
    50  		if !ok {
    51  			g.GetLog().CDebugf(ctx, "Found non-ServiceSummary in LRU cache for uid=%s", uid)
    52  		} else {
    53  			if freshness != time.Duration(0) && g.GetClock().Since(keybase1.FromTime(tmp.CachedAt)) > freshness {
    54  				// Data too stale for the request. Do not remove from caches
    55  				// though - maybe other callers will have more relaxed
    56  				// freshness requirements.
    57  				return res, false, nil
    58  			}
    59  			res = tmp
    60  		}
    61  	}
    62  
    63  	key := serviceMapDBKey(uid)
    64  	var tmp libkb.UserServiceSummaryPackage
    65  	found, err = g.GetKVStore().GetInto(&tmp, key)
    66  	if err != nil {
    67  		g.GetLog().CInfof(ctx, "failed to get servicemap dbkey %v: %s", key, err)
    68  		return res, false, err
    69  	}
    70  	if !found {
    71  		return res, false, nil
    72  	}
    73  
    74  	s.memCache.Add(uid, tmp)
    75  	if freshness != time.Duration(0) && g.GetClock().Since(keybase1.FromTime(tmp.CachedAt)) > freshness {
    76  		// We got the data back from disk cache but it's too stale for this
    77  		// caller.
    78  		return res, false, nil
    79  	}
    80  	return tmp, true, nil
    81  }
    82  
    83  // MapUIDsToServiceSummaries retrieves serviceMap for uids.
    84  //
    85  // - `freshness` determines time duration after which data is considered stale
    86  // and will be re-fetched (or not returned, depending if network requests are
    87  // possible and allowed). Default value of 0 makes all data eligible to return
    88  // no matter how old.
    89  //
    90  // - `networkTimeBudget` sets the timeout for network request. Default value of
    91  // 0 triggers the default API behavior. Special value `DisallowNetworkBudget`
    92  // (equal to tiny budget of 1 nanosecond) disallows any network access and will
    93  // result in only cached data being returned.
    94  //
    95  // If UID is present as a key in the result map, it means that it was either
    96  // found in cache or fetched from API server. The value for the key may be nil,
    97  // though, it means that the user has no services proven. To summarize, there is
    98  // a possibility that not all `uids` will be present as keys in the result map,
    99  // and also that not all keys will have non-nil value.
   100  //
   101  // This function does not return errors, but it might not return any requested
   102  // values if neither cache nor API connection is available.
   103  func (s *ServiceSummaryMap) MapUIDsToServiceSummaries(ctx context.Context, g libkb.UIDMapperContext, uids []keybase1.UID,
   104  	freshness time.Duration, networkTimeBudget time.Duration) (res map[keybase1.UID]libkb.UserServiceSummaryPackage) {
   105  
   106  	s.Lock()
   107  	defer s.Unlock()
   108  
   109  	res = make(map[keybase1.UID]libkb.UserServiceSummaryPackage, len(uids))
   110  	var uidsToQuery []keybase1.UID
   111  	for _, uid := range uids {
   112  		serviceMapPkg, found, err := s.findServiceSummaryLocally(ctx, g, uid, freshness)
   113  		if err != nil {
   114  			g.GetLog().CDebugf(ctx, "Failed to get cached serviceMap for %s: %s", uid, err)
   115  		} else if found {
   116  			res[uid] = serviceMapPkg
   117  		} else {
   118  			uidsToQuery = append(uidsToQuery, uid)
   119  		}
   120  	}
   121  
   122  	if len(uidsToQuery) > 0 {
   123  		if networkTimeBudget == DisallowNetworkBudget {
   124  			g.GetLog().CDebugf(ctx, "Not making the network request for %d UIDs because of networkBudget=disallow",
   125  				len(uidsToQuery))
   126  			return res
   127  		}
   128  
   129  		g.GetLog().CDebugf(ctx, "Looking up %d UIDs using API", len(uidsToQuery))
   130  
   131  		now := keybase1.ToTime(g.GetClock().Now())
   132  		apiResults, err := lookupServiceSummariesFromServer(ctx, g, uidsToQuery, networkTimeBudget)
   133  		if err != nil {
   134  			g.GetLog().CDebugf(ctx, "Failed API call for service maps: %s", err)
   135  		} else {
   136  			for _, uid := range uidsToQuery {
   137  				serviceMap := apiResults[uid]
   138  				// Returning or storing nil maps is fine
   139  				pkg := libkb.UserServiceSummaryPackage{
   140  					CachedAt:   now,
   141  					ServiceMap: serviceMap,
   142  				}
   143  				res[uid] = pkg
   144  				s.memCache.Add(uid, pkg)
   145  				key := serviceMapDBKey(uid)
   146  				err := g.GetKVStore().PutObj(key, nil, pkg)
   147  				if err != nil {
   148  					g.GetLog().CInfof(ctx, "Failed to put service map cache for %v: %s", key, err)
   149  				}
   150  			}
   151  		}
   152  	}
   153  
   154  	return res
   155  }
   156  
   157  func lookupServiceSummariesFromServer(ctx context.Context, g libkb.UIDMapperContext, uids []keybase1.UID, networkTimeBudget time.Duration) (map[keybase1.UID]libkb.UserServiceSummary, error) {
   158  	if len(uids) == 0 {
   159  		return make(map[keybase1.UID]libkb.UserServiceSummary), nil
   160  	}
   161  
   162  	type lookupRes struct {
   163  		libkb.AppStatusEmbed
   164  		ServiceMaps map[keybase1.UID]libkb.UserServiceSummary `json:"service_maps"`
   165  	}
   166  
   167  	arg := libkb.NewAPIArg("user/service_maps")
   168  	arg.SessionType = libkb.APISessionTypeNONE
   169  	arg.Args = libkb.HTTPArgs{
   170  		"uids": libkb.S{Val: libkb.UidsToString(uids)},
   171  	}
   172  	if networkTimeBudget > time.Duration(0) {
   173  		arg.InitialTimeout = networkTimeBudget
   174  		arg.RetryCount = 0
   175  	}
   176  	var resp lookupRes
   177  	err := g.GetAPI().PostDecodeCtx(ctx, arg, &resp)
   178  	if err != nil {
   179  		return nil, err
   180  	}
   181  	return resp.ServiceMaps, nil
   182  }
   183  
   184  func (s *ServiceSummaryMap) InformOfServiceSummary(ctx context.Context, g libkb.UIDMapperContext,
   185  	uid keybase1.UID, summary libkb.UserServiceSummary) error {
   186  
   187  	pkg := libkb.UserServiceSummaryPackage{
   188  		CachedAt:   keybase1.ToTime(g.GetClock().Now()),
   189  		ServiceMap: summary,
   190  	}
   191  	s.memCache.Add(uid, pkg)
   192  	key := serviceMapDBKey(uid)
   193  	return g.GetKVStore().PutObj(key, nil, pkg)
   194  }
   195  
   196  var _ libkb.ServiceSummaryMapper = (*ServiceSummaryMap)(nil)
   197  
   198  type OfflineServiceSummaryMap struct{}
   199  
   200  func NewOfflineServiceSummaryMap() *OfflineServiceSummaryMap {
   201  	return &OfflineServiceSummaryMap{}
   202  }
   203  
   204  func (s *OfflineServiceSummaryMap) MapUIDsToServiceSummaries(ctx context.Context, g libkb.UIDMapperContext, uids []keybase1.UID,
   205  	freshness time.Duration, networkTimeBudget time.Duration) (res map[keybase1.UID]libkb.UserServiceSummaryPackage) {
   206  	// Return empty map.
   207  	return make(map[keybase1.UID]libkb.UserServiceSummaryPackage)
   208  }
   209  
   210  func (s *OfflineServiceSummaryMap) InformOfServiceSummary(ctx context.Context, g libkb.UIDMapperContext,
   211  	uid keybase1.UID, summary libkb.UserServiceSummary) error {
   212  	// Do nothing, successfully.
   213  	return nil
   214  }
   215  
   216  var _ libkb.ServiceSummaryMapper = (*OfflineServiceSummaryMap)(nil)