github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/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)