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

     1  package uidmap
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"strings"
     7  	"sync"
     8  	"time"
     9  
    10  	lru "github.com/hashicorp/golang-lru"
    11  	"github.com/keybase/client/go/libkb"
    12  	keybase1 "github.com/keybase/client/go/protocol/keybase1"
    13  	"golang.org/x/net/context"
    14  )
    15  
    16  type UIDMap struct {
    17  	sync.Mutex
    18  	usernameCache     map[keybase1.UID]libkb.NormalizedUsername
    19  	fullNameCache     *lru.Cache
    20  	testBatchIterHook func()
    21  	testNoCachingMode bool
    22  	serverRefreshers  map[keybase1.UID]keybase1.Seqno
    23  }
    24  
    25  func NewUIDMap(fullNameCacheSize int) *UIDMap {
    26  	cache, err := lru.New(fullNameCacheSize)
    27  	if err != nil {
    28  		panic(fmt.Sprintf("failed to make an LRU size=%d: %s", fullNameCacheSize, err))
    29  	}
    30  	return &UIDMap{
    31  		usernameCache:    make(map[keybase1.UID]libkb.NormalizedUsername),
    32  		fullNameCache:    cache,
    33  		serverRefreshers: make(map[keybase1.UID]keybase1.Seqno),
    34  	}
    35  }
    36  
    37  func usernameDBKey(u keybase1.UID) libkb.DbKey {
    38  	return libkb.DbKey{Typ: libkb.DBUidToUsername, Key: string(u)}
    39  }
    40  
    41  func fullNameDBKey(u keybase1.UID) libkb.DbKey {
    42  	return libkb.DbKey{Typ: libkb.DBUidToFullName, Key: string(u)}
    43  }
    44  
    45  type mapStatus int
    46  
    47  const (
    48  	foundHardCoded mapStatus = iota
    49  	foundInMem     mapStatus = iota
    50  	foundOnDisk    mapStatus = iota
    51  	notFound       mapStatus = iota
    52  	stale          mapStatus = iota
    53  )
    54  
    55  // The number of UIDs per batch to send. It's not `const` so we can twiddle it in our tests.
    56  var batchSize = 250
    57  
    58  func (u *UIDMap) SetTestingNoCachingMode(enabled bool) {
    59  	u.testNoCachingMode = enabled
    60  }
    61  
    62  func (u *UIDMap) Clear() {
    63  	u.Lock()
    64  	defer u.Unlock()
    65  	u.usernameCache = make(map[keybase1.UID]libkb.NormalizedUsername)
    66  	u.fullNameCache.Purge()
    67  }
    68  
    69  func (u *UIDMap) findUsernamePackageLocally(ctx context.Context, g libkb.UIDMapperContext, uid keybase1.UID, fullNameFreshness time.Duration) (ret *libkb.UsernamePackage, stats mapStatus) {
    70  	nun, usernameStatus := u.findUsernameLocally(ctx, g, uid)
    71  	if usernameStatus == notFound {
    72  		return nil, notFound
    73  	}
    74  	fullName, fullNameStatus := u.findFullNameLocally(ctx, g, uid, fullNameFreshness)
    75  	return &libkb.UsernamePackage{NormalizedUsername: nun, FullName: fullName}, fullNameStatus
    76  }
    77  
    78  const CurrentFullNamePackageVersion = keybase1.FullNamePackageVersion_V2
    79  
    80  func isStale(g libkb.UIDMapperContext, m keybase1.FullNamePackage, dur time.Duration) (time.Duration, bool) {
    81  	if dur == time.Duration(0) {
    82  		return time.Duration(0), false
    83  	}
    84  	now := g.GetClock().Now()
    85  	cachedAt := m.CachedAt.Time()
    86  	diff := now.Sub(cachedAt)
    87  	expired := (diff > dur)
    88  	return diff, expired
    89  }
    90  
    91  func (u *UIDMap) findFullNameLocally(ctx context.Context, g libkb.UIDMapperContext, uid keybase1.UID, fullNameFreshness time.Duration) (ret *keybase1.FullNamePackage, status mapStatus) {
    92  
    93  	var staleFullName *keybase1.FullNamePackage
    94  	var staleExpired time.Duration
    95  
    96  	doNotFoundReturn := func() (*keybase1.FullNamePackage, mapStatus) {
    97  		if staleFullName != nil {
    98  			return staleFullName, stale
    99  		}
   100  		return nil, notFound
   101  	}
   102  
   103  	voidp, ok := u.fullNameCache.Get(uid)
   104  	if ok {
   105  		tmp, ok := voidp.(keybase1.FullNamePackage)
   106  		if !ok {
   107  			g.GetLog().CDebugf(ctx, "Found non-FullNamePackage in LRU cache for uid=%s", uid)
   108  		} else if when, expired := isStale(g, tmp, fullNameFreshness); expired {
   109  			staleFullName = &tmp
   110  			staleExpired = when
   111  			g.GetVDebugLog().CLogf(ctx, libkb.VLog0, "fullName memory mapping %s -> %+v is expired (%s ago)", uid, tmp, when)
   112  		} else {
   113  			ret = &tmp
   114  			return ret, foundInMem
   115  		}
   116  	}
   117  
   118  	var tmp keybase1.FullNamePackage
   119  	key := fullNameDBKey(uid)
   120  	found, err := g.GetKVStore().GetInto(&tmp, key)
   121  	if err != nil {
   122  		g.GetLog().CDebugf(ctx, "findFullNameLocally: failed to get dbkey %v: %s", key, err)
   123  		return doNotFoundReturn()
   124  	}
   125  	if !found {
   126  		return doNotFoundReturn()
   127  	}
   128  
   129  	if tmp.Version != CurrentFullNamePackageVersion {
   130  		g.GetLog().CDebugf(ctx, "Old version (=%d) found for dbkey %s", tmp.Version, key)
   131  		return doNotFoundReturn()
   132  	}
   133  
   134  	if when, expired := isStale(g, tmp, fullNameFreshness); expired {
   135  		g.GetVDebugLog().CLogf(ctx, libkb.VLog0, "fullName disk mapping %s -> %+v is expired (%s ago)", uid, tmp, when)
   136  		if when < staleExpired {
   137  			staleFullName = &tmp
   138  		}
   139  		return doNotFoundReturn()
   140  	}
   141  
   142  	u.fullNameCache.Add(uid, tmp)
   143  	return ret, foundOnDisk
   144  }
   145  
   146  func (u *UIDMap) findUsernameLocally(ctx context.Context, g libkb.UIDMapperContext, uid keybase1.UID) (libkb.NormalizedUsername, mapStatus) {
   147  	un := findHardcoded(uid)
   148  	if !un.IsNil() {
   149  		return un, foundHardCoded
   150  	}
   151  	un, ok := u.usernameCache[uid]
   152  	if ok {
   153  		return un, foundInMem
   154  	}
   155  	var s string
   156  	key := usernameDBKey(uid)
   157  	found, err := g.GetKVStore().GetInto(&s, key)
   158  	if err != nil {
   159  		g.GetLog().CDebugf(ctx, "findUsernameLocally: failed to get dbkey %v: %s", key, err)
   160  		return libkb.NormalizedUsername(""), notFound
   161  	}
   162  	if !found {
   163  		return libkb.NormalizedUsername(""), notFound
   164  	}
   165  	ret := libkb.NewNormalizedUsername(s)
   166  	u.usernameCache[uid] = ret
   167  	return ret, foundOnDisk
   168  }
   169  
   170  type apiRow struct {
   171  	Username    string              `json:"username"`
   172  	FullName    string              `json:"full_name,omitempty"`
   173  	EldestSeqno keybase1.Seqno      `json:"eldest_seqno"`
   174  	Status      keybase1.StatusCode `json:"status"`
   175  }
   176  
   177  type apiReply struct {
   178  	Status libkb.AppStatus         `json:"status"`
   179  	Users  map[keybase1.UID]apiRow `json:"users"`
   180  }
   181  
   182  func (a *apiReply) GetAppStatus() *libkb.AppStatus {
   183  	return &a.Status
   184  }
   185  
   186  func (u *UIDMap) refreshersForUIDs(uids []keybase1.UID) string {
   187  	var v []string
   188  	for _, uid := range uids {
   189  		if eldestSeqno, found := u.serverRefreshers[uid]; found {
   190  			v = append(v, (keybase1.UserVersion{Uid: uid, EldestSeqno: eldestSeqno}).String())
   191  		}
   192  	}
   193  	return strings.Join(v, ",")
   194  }
   195  
   196  func (u *UIDMap) lookupFromServerBatch(ctx context.Context, g libkb.UIDMapperContext, uids []keybase1.UID, networkTimeBudget time.Duration) ([]libkb.UsernamePackage, error) {
   197  	noCache := u.testNoCachingMode
   198  	arg := libkb.NewRetryAPIArg("user/names")
   199  	arg.SessionType = libkb.APISessionTypeNONE
   200  	refreshers := u.refreshersForUIDs(uids)
   201  	if len(refreshers) > 0 {
   202  		g.GetLog().CDebugf(ctx, "user/names refreshers: %s", refreshers)
   203  	}
   204  	arg.Args = libkb.HTTPArgs{
   205  		"uids":       libkb.S{Val: libkb.UidsToString(uids)},
   206  		"no_cache":   libkb.B{Val: noCache},
   207  		"refreshers": libkb.S{Val: refreshers},
   208  	}
   209  	if networkTimeBudget > time.Duration(0) {
   210  		arg.InitialTimeout = networkTimeBudget
   211  		arg.RetryCount = 0
   212  	}
   213  	var r apiReply
   214  	err := g.GetAPI().PostDecodeCtx(ctx, arg, &r)
   215  	if err != nil {
   216  		return nil, err
   217  	}
   218  	ret := make([]libkb.UsernamePackage, len(uids))
   219  	cachedAt := keybase1.ToTime(g.GetClock().Now())
   220  	for i, uid := range uids {
   221  		if row, ok := r.Users[uid]; ok {
   222  			nun := libkb.NewNormalizedUsername(row.Username)
   223  			if !u.CheckUIDAgainstUsername(uid, nun) {
   224  				g.GetLog().CWarningf(ctx, "Server returned bad UID -> username mapping: %s -> %s", uid, nun)
   225  			} else {
   226  				ret[i] = libkb.UsernamePackage{
   227  					NormalizedUsername: nun,
   228  					FullName: &keybase1.FullNamePackage{
   229  						Version:     CurrentFullNamePackageVersion,
   230  						FullName:    keybase1.FullName(row.FullName),
   231  						EldestSeqno: row.EldestSeqno,
   232  						Status:      row.Status,
   233  						CachedAt:    cachedAt,
   234  					},
   235  				}
   236  			}
   237  		}
   238  	}
   239  	return ret, nil
   240  }
   241  
   242  func (u *UIDMap) lookupFromServer(ctx context.Context, g libkb.UIDMapperContext, uids []keybase1.UID, networkTimeBudget time.Duration) ([]libkb.UsernamePackage, error) {
   243  
   244  	start := g.GetClock().Now()
   245  	end := start.Add(networkTimeBudget)
   246  
   247  	g.GetLog().CDebugf(ctx, "looking up %d uids from server", len(uids))
   248  	var ret []libkb.UsernamePackage
   249  	for i := 0; i < len(uids); i += batchSize {
   250  		high := i + batchSize
   251  		if high > len(uids) {
   252  			high = len(uids)
   253  		}
   254  		inb := uids[i:high]
   255  		var budget time.Duration
   256  
   257  		// Only useful for testing...
   258  		if u.testBatchIterHook != nil {
   259  			u.testBatchIterHook()
   260  		}
   261  
   262  		if networkTimeBudget > time.Duration(0) {
   263  			now := g.GetClock().Now()
   264  			if now.After(end) {
   265  				return ret, errors.New("ran out of time")
   266  			}
   267  			budget = end.Sub(now)
   268  		}
   269  		outb, err := u.lookupFromServerBatch(ctx, g, inb, budget)
   270  		if err != nil {
   271  			return ret, err
   272  		}
   273  		ret = append(ret, outb...)
   274  	}
   275  	return ret, nil
   276  }
   277  
   278  // InformOfEldestSeqno informs the mapper of an up-to-date (uid,eldestSeqno) pair.
   279  // If the cache has a different value, it will clear the cache and then plumb
   280  // the pair all the way through to the server, whose cache may also be in need
   281  // of busting. Will return true if the cached value was up-to-date, and false
   282  // otherwise.
   283  func (u *UIDMap) InformOfEldestSeqno(ctx context.Context, g libkb.UIDMapperContext, uv keybase1.UserVersion) (isCurrent bool, err error) {
   284  
   285  	// No entry/exit tracing, or common-case tracing, in this function since otherwise
   286  	// the spam is overwhelming.
   287  
   288  	u.Lock()
   289  	defer u.Unlock()
   290  
   291  	uid := uv.Uid
   292  	isCurrent = true
   293  	updateDisk := true
   294  
   295  	voidp, ok := u.fullNameCache.Get(uid)
   296  	if ok {
   297  		tmp, ok := voidp.(keybase1.FullNamePackage)
   298  		switch {
   299  		case !ok:
   300  			g.GetLog().CDebugf(ctx, "Found non-FullNamePackage in LRU cache for uid=%s", uid)
   301  		case tmp.EldestSeqno < uv.EldestSeqno:
   302  			g.GetLog().CDebugf(ctx, "Stale eldest memory mapping for uid=%s; we had %d, but latest is %d", uid, tmp.EldestSeqno, uv.EldestSeqno)
   303  			u.fullNameCache.Remove(uid)
   304  			isCurrent = false
   305  		default:
   306  			// If the memory state of this UID->Eldest mapping is correct,
   307  			// then there is no reason to check the disk state, since we should
   308  			// never have a case that the memory state is newer than the disk
   309  			// state. And hopefully this is the common case!
   310  			updateDisk = false
   311  		}
   312  	}
   313  
   314  	if updateDisk {
   315  		var tmp keybase1.FullNamePackage
   316  		key := fullNameDBKey(uid)
   317  		found, err := g.GetKVStore().GetInto(&tmp, key)
   318  		if err != nil {
   319  			g.GetLog().CDebugf(ctx, "Error reading %s from UID map disk-backed cache: %s", uid, err)
   320  		}
   321  		if found && tmp.EldestSeqno < uv.EldestSeqno {
   322  			g.GetLog().CDebugf(ctx, "Stale eldest disk mapping for uid=%s; we had %d, but latest is %d", uid, tmp.EldestSeqno, uv.EldestSeqno)
   323  			if err := g.GetKVStore().Delete(key); err != nil {
   324  				return false, err
   325  			}
   326  			isCurrent = false
   327  		}
   328  	}
   329  
   330  	if !isCurrent {
   331  		u.serverRefreshers[uid] = uv.EldestSeqno
   332  	}
   333  
   334  	return isCurrent, nil
   335  }
   336  
   337  // MapUIDsToUsernamePackages maps the given set of UIDs to the username
   338  // packages, which include a username and a fullname, and when the mapping was
   339  // loaded from the server. It blocks on the network until all usernames are
   340  // known. If the `forceNetworkForFullNames` flag is specified, it will block on
   341  // the network too. If the flag is not specified, then stale values (or unknown
   342  // values) are OK, we won't go to network if we lack them. All network calls
   343  // are limited by the given timeBudget, or if 0 is specified, there is
   344  // indefinite budget. In the response, a nil FullNamePackage means that the
   345  // lookup failed. A non-nil FullNamePackage means that some previous lookup
   346  // worked, but might be arbitrarily out of date (depending on the cachedAt
   347  // time). A non-nil FullNamePackage with an empty fullName field means that the
   348  // user just hasn't supplied a fullName. FullNames can be cached by the UIDMap,
   349  // but expire after networkTimeBudget duration. If that value is 0, then
   350  // infinitely stale names are allowed. If non-zero, and some names aren't
   351  // stale, we'll have to go to the network.
   352  //
   353  // *NOTE* that this function can return useful data and an error. In this
   354  // regard, the error is more like a warning. But if, for instance, the mapper
   355  // runs out of time budget, it will return the data it was able to get, and
   356  // also the error.
   357  func (u *UIDMap) MapUIDsToUsernamePackages(ctx context.Context, g libkb.UIDMapperContext,
   358  	uids []keybase1.UID, fullNameFreshness, networkTimeBudget time.Duration,
   359  	forceNetworkForFullNames bool) (res []libkb.UsernamePackage, err error) {
   360  
   361  	u.Lock()
   362  	defer u.Unlock()
   363  
   364  	res = make([]libkb.UsernamePackage, len(uids))
   365  	apiLookupIndex := make(map[int]int)
   366  
   367  	var uidsToLookup []keybase1.UID
   368  	for i, uid := range uids {
   369  		up, status := u.findUsernamePackageLocally(ctx, g, uid, fullNameFreshness)
   370  		// If we successfully looked up some of the user, set the return slot here.
   371  		if up != nil {
   372  			res[i] = *up
   373  		}
   374  
   375  		// There are 3 important cases when we should go to network:
   376  		//
   377  		//  1. No username is found (up == nil)
   378  		//  2. No FullName found and we've asked to force network lookups (status == notFound && forceNetworkForNullNames)
   379  		//  3. The FullName found was stale (status == stale).
   380  		//
   381  		// Thus, if you provide forceNetworkForFullName=false, and fullNameFreshness=0, you can avoid
   382  		// the network trip as long as all of your username lookups hit the cache or are hardcoded.
   383  		if u.testNoCachingMode || up == nil ||
   384  			(status == notFound && forceNetworkForFullNames) || (status == stale) {
   385  			apiLookupIndex[len(uidsToLookup)] = i
   386  			uidsToLookup = append(uidsToLookup, uid)
   387  		}
   388  	}
   389  
   390  	if len(uidsToLookup) > 0 {
   391  		var apiResults []libkb.UsernamePackage
   392  
   393  		apiResults, err = u.lookupFromServer(ctx, g, uidsToLookup, networkTimeBudget)
   394  		if err == nil {
   395  			for i, row := range apiResults {
   396  				uid := uidsToLookup[i]
   397  				if row.FullName != nil {
   398  					g.GetVDebugLog().CLogf(ctx, libkb.VLog0, "| API server resolution %s -> (%s, %v, %v)", uid,
   399  						row.NormalizedUsername, row.FullName.FullName, row.FullName.EldestSeqno)
   400  				} else {
   401  					g.GetVDebugLog().CLogf(ctx, libkb.VLog0, "| API server resolution %s -> (%s, <no fn res>)", uid,
   402  						row.NormalizedUsername)
   403  				}
   404  
   405  				// Always write these results out if the cached value is unset.
   406  				// Or, see below for other case...
   407  				writeResults := res[apiLookupIndex[i]].NormalizedUsername.IsNil()
   408  
   409  				// Fill in caches independently after a successful return. First fill in
   410  				// the username cache...
   411  				if nun := row.NormalizedUsername; !nun.IsNil() {
   412  
   413  					// If we get a non-nil NormalizedUsername from the server, then also
   414  					// write results out...
   415  					writeResults = true
   416  					u.usernameCache[uid] = nun
   417  					key := usernameDBKey(uid)
   418  					err := g.GetKVStore().PutObj(key, nil, nun.String())
   419  					if err != nil {
   420  						g.GetLog().CDebugf(ctx, "failed to put %v -> %s: %s", key, nun, err)
   421  					}
   422  				}
   423  
   424  				// Then fill in the fullName cache...
   425  				if fn := row.FullName; fn != nil {
   426  					u.fullNameCache.Add(uid, *fn)
   427  					key := fullNameDBKey(uid)
   428  					err := g.GetKVStore().PutObj(key, nil, *fn)
   429  					if err != nil {
   430  						g.GetLog().CDebugf(ctx, "failed to put %v -> %v: %s", key, *fn, err)
   431  					}
   432  					// If we had previously busted this lookup, then clear the refresher
   433  					// on the server, so we don't have to do it next time through.
   434  					delete(u.serverRefreshers, uid)
   435  				}
   436  
   437  				if writeResults {
   438  					// Overwrite the row with whatever was returned from the server.
   439  					res[apiLookupIndex[i]] = row
   440  				}
   441  			}
   442  		}
   443  	}
   444  
   445  	return res, err
   446  }
   447  
   448  func (u *UIDMap) CheckUIDAgainstUsername(uid keybase1.UID, un libkb.NormalizedUsername) bool {
   449  	return checkUIDAgainstUsername(uid, un)
   450  }
   451  
   452  func (u *UIDMap) MapHardcodedUsernameToUID(un libkb.NormalizedUsername) keybase1.UID {
   453  	return findHardcodedUsername(un)
   454  }
   455  
   456  func (u *UIDMap) ClearUIDFullName(ctx context.Context, g libkb.UIDMapperContext, uid keybase1.UID) error {
   457  	u.Lock()
   458  	defer u.Unlock()
   459  
   460  	u.fullNameCache.Remove(uid)
   461  	key := fullNameDBKey(uid)
   462  	if err := g.GetKVStore().Delete(key); err != nil {
   463  		return err
   464  	}
   465  	return nil
   466  }
   467  
   468  func (u *UIDMap) ClearUIDAtEldestSeqno(ctx context.Context, g libkb.UIDMapperContext, uid keybase1.UID, s keybase1.Seqno) error {
   469  	u.Lock()
   470  	defer u.Unlock()
   471  
   472  	voidp, ok := u.fullNameCache.Get(uid)
   473  	clearDB := false
   474  	if ok {
   475  		tmp, ok := voidp.(keybase1.FullNamePackage)
   476  		if !ok || tmp.EldestSeqno == s {
   477  			g.GetLog().CDebugf(ctx, "UIDMap: Clearing %s%%%d", uid, s)
   478  			u.fullNameCache.Remove(uid)
   479  			clearDB = true
   480  		}
   481  	} else {
   482  		clearDB = true
   483  	}
   484  	if clearDB {
   485  		key := fullNameDBKey(uid)
   486  		if err := g.GetKVStore().Delete(key); err != nil {
   487  			return err
   488  		}
   489  	}
   490  	return nil
   491  }
   492  
   493  func (u *UIDMap) MapUIDsToUsernamePackagesOffline(ctx context.Context, g libkb.UIDMapperContext,
   494  	uids []keybase1.UID, fullNameFreshness time.Duration) (res []libkb.UsernamePackage, err error) {
   495  	// Like MapUIDsToUsernamePackages, but never makes any network calls,
   496  	// returns only cached values. UIDs that were not cached at all result in
   497  	// default UsernamePackage, caller has to check if the result is present
   498  	// using `res[i].NormalizedUsername.IsNil()`.
   499  
   500  	u.Lock()
   501  	defer u.Unlock()
   502  
   503  	res = make([]libkb.UsernamePackage, len(uids))
   504  	for i, uid := range uids {
   505  		up, _ := u.findUsernamePackageLocally(ctx, g, uid, fullNameFreshness)
   506  		// If we successfully looked up some of the user, set the return slot here.
   507  		if up != nil {
   508  			res[i] = *up
   509  		}
   510  	}
   511  
   512  	return res, nil
   513  }
   514  
   515  func MapUIDsReturnMap(ctx context.Context, u libkb.UIDMapper, g libkb.UIDMapperContext, uids []keybase1.UID, fullNameFreshness time.Duration,
   516  	networkTimeBudget time.Duration, forceNetworkForFullNames bool) (res map[keybase1.UID]libkb.UsernamePackage, err error) {
   517  
   518  	var uidList []keybase1.UID
   519  	uidSet := map[keybase1.UID]bool{}
   520  
   521  	for _, uid := range uids {
   522  		_, found := uidSet[uid]
   523  		if !found {
   524  			uidSet[uid] = true
   525  			uidList = append(uidList, uid)
   526  		}
   527  	}
   528  
   529  	resultList, err := u.MapUIDsToUsernamePackages(ctx, g, uidList, fullNameFreshness, networkTimeBudget, forceNetworkForFullNames)
   530  	if err != nil && len(resultList) != len(uidList) {
   531  		return res, err
   532  	}
   533  
   534  	res = make(map[keybase1.UID]libkb.UsernamePackage)
   535  	for i, uid := range uidList {
   536  		res[uid] = resultList[i]
   537  	}
   538  	return res, err
   539  }
   540  
   541  func MapUIDsReturnMapMctx(mctx libkb.MetaContext, uids []keybase1.UID, fullNameFreshness time.Duration, networkTimeBudget time.Duration,
   542  	forceNetworkForFullNames bool) (res map[keybase1.UID]libkb.UsernamePackage, err error) {
   543  	// Same as MapUIDsReturnMap, but takes less arguments because of mctx,
   544  	// which encapsulates ctx, g, and u (g.UIDMapper).
   545  	g := mctx.G()
   546  	return MapUIDsReturnMap(mctx.Ctx(), g.UIDMapper, g, uids, fullNameFreshness, networkTimeBudget, forceNetworkForFullNames)
   547  }
   548  
   549  var _ libkb.UIDMapper = (*UIDMap)(nil)
   550  
   551  type OfflineUIDMap struct{}
   552  
   553  func (o *OfflineUIDMap) CheckUIDAgainstUsername(uid keybase1.UID, un libkb.NormalizedUsername) bool {
   554  	return true
   555  }
   556  
   557  func (o *OfflineUIDMap) MapHardcodedUsernameToUID(un libkb.NormalizedUsername) keybase1.UID {
   558  	return findHardcodedUsername(un)
   559  }
   560  
   561  func (o *OfflineUIDMap) MapUIDsToUsernamePackages(ctx context.Context, g libkb.UIDMapperContext, uids []keybase1.UID, fullNameFreshness time.Duration, networktimeBudget time.Duration, forceNetworkForFullNames bool) ([]libkb.UsernamePackage, error) {
   562  	return nil, errors.New("offline uid map always fails")
   563  }
   564  
   565  func (o *OfflineUIDMap) SetTestingNoCachingMode(enabled bool) {
   566  
   567  }
   568  
   569  func (o *OfflineUIDMap) ClearUIDFullName(ctx context.Context, g libkb.UIDMapperContext, uid keybase1.UID) error {
   570  	return nil
   571  }
   572  
   573  func (o *OfflineUIDMap) ClearUIDAtEldestSeqno(ctx context.Context, g libkb.UIDMapperContext, uid keybase1.UID, s keybase1.Seqno) error {
   574  	return nil
   575  }
   576  
   577  func (o *OfflineUIDMap) InformOfEldestSeqno(ctx context.Context, g libkb.UIDMapperContext, uv keybase1.UserVersion) (bool, error) {
   578  	return true, nil
   579  }
   580  
   581  func (o *OfflineUIDMap) MapUIDsToUsernamePackagesOffline(ctx context.Context, g libkb.UIDMapperContext, uids []keybase1.UID, fullNameFreshness time.Duration) (res []libkb.UsernamePackage, err error) {
   582  	// Offline uid map offline call always succeeds but returns nothing.
   583  	res = make([]libkb.UsernamePackage, len(uids))
   584  	return res, nil
   585  }
   586  
   587  var _ libkb.UIDMapper = (*OfflineUIDMap)(nil)