func (u *user) isPopulated() bool {
	return u.isOK && u.Now().Sub(u.ctime) <= userTimeout
}
isPopulated returned true if this user is populated and current enough to
trust.
package auth
import (
	"fmt"
	libkb "github.com/keybase/client/go/libkb"
	logger "github.com/keybase/client/go/logger"
	keybase1 "github.com/keybase/client/go/protocol/keybase1"
	context "golang.org/x/net/context"
	"time"
)
const (
	userTimeout  = 5 * time.Minute
	cacheTimeout = 8 * time.Minute
)
type CredentialAuthority struct {
	log           logger.Logger
	api           UserKeyAPIer
	invalidateCh  chan keybase1.UID
	checkCh       chan checkArg
	shutdownCh    chan struct{}
	cleanItemCh   chan cleanItem
	users         map[keybase1.UID](*userWrapper)
	cleanSchedule []cleanItem
	eng           engine
}
type checkArg struct {
	uid      keybase1.UID
	username *libkb.NormalizedUsername
	kid      *keybase1.KID
	sibkeys  []keybase1.KID
	subkeys  []keybase1.KID
	retCh    chan error
}
func (ca checkArg) String() string {
	return fmt.Sprintf("{uid: %s, username: %s, kid: %s, sibkeys: %v, subkeys: %v}",
		ca.uid, ca.username, ca.kid, ca.sibkeys, ca.subkeys)
}
type userWrapper struct {
	u     *user
	atime time.Time
}
func (uw userWrapper) String() string {
	return fmt.Sprintf("{user: %s, atime: %s}", uw.u, uw.atime)
}
type cleanItem struct {
	uid   keybase1.UID
	ctime time.Time
}
func (ci cleanItem) String() string {
	return fmt.Sprintf("{uid: %s, ctime: %s}", ci.uid, ci.ctime)
}
type user struct {
	uid      keybase1.UID
	username libkb.NormalizedUsername
	sibkeys  map[keybase1.KID]struct{}
	subkeys  map[keybase1.KID]struct{}
	isOK     bool
	ctime    time.Time
	ca       *CredentialAuthority
	checkCh  chan checkArg
	stopCh   chan struct{}
}
func (u user) String() string {
	return fmt.Sprintf("{uid: %s, username: %s, sibkeys: %v, subkeys: %v, isOK: %v, ctime: %s}",
		u.uid, u.username, u.sibkeys, u.subkeys, u.isOK, u.ctime)
}
func newUser(uid keybase1.UID, ca *CredentialAuthority) *user {
	ca.log.Debug("newUser, uid %s", uid)
	ret := &user{
		uid:     uid,
		sibkeys: make(map[keybase1.KID]struct{}),
		subkeys: make(map[keybase1.KID]struct{}),
		ca:      ca,
		checkCh: make(chan checkArg),
		stopCh:  make(chan struct{}),
	}
	go ret.run()
	return ret
}
type UserKeyAPIer interface {
	GetUser(context.Context, keybase1.UID) (
		un libkb.NormalizedUsername, sibkeys, subkeys []keybase1.KID, err error)
	PollForChanges(context.Context) ([]keybase1.UID, error)
}
type engine interface {
	Now() time.Time              
	Evicted(uid keybase1.UID)    
	GetPokeCh() <-chan struct{}  
}
type standardEngine struct {
	pokeCh <-chan struct{}
}
func (se *standardEngine) Now() time.Time { return time.Now() }
func (se *standardEngine) Evicted(uid keybase1.UID) {}
func (se *standardEngine) GetPokeCh() <-chan struct{} { return se.pokeCh }
func newStandardEngine() engine {
	return &standardEngine{
		pokeCh: make(chan struct{}),
	}
}
func NewCredentialAuthority(log logger.Logger, api UserKeyAPIer) *CredentialAuthority {
	return newCredentialAuthorityWithEngine(log, api, newStandardEngine())
}
func newCredentialAuthorityWithEngine(log logger.Logger, api UserKeyAPIer, eng engine) *CredentialAuthority {
	ret := &CredentialAuthority{
		log:          log,
		api:          api,
		invalidateCh: make(chan keybase1.UID, 100),
		checkCh:      make(chan checkArg),
		shutdownCh:   make(chan struct{}),
		users:        make(map[keybase1.UID](*userWrapper)),
		cleanItemCh:  make(chan cleanItem),
		eng:          eng,
	}
	ret.run()
	return ret
}
func (v *CredentialAuthority) run() {
	go v.pollLoop()
	go v.runLoop()
}
func (v *CredentialAuthority) pollOnce() error {
	var err error
	var uids []keybase1.UID
	err = v.runWithCancel(func(ctx context.Context) error {
		var err error
		uids, err = v.api.PollForChanges(ctx)
		return err
	})
	if err == nil {
		for _, uid := range uids {
			v.invalidateCh <- uid
		}
	}
	return err
}
func (v *CredentialAuthority) runWithCancel(body func(ctx context.Context) error) error {
	ctx, cancel := context.WithCancel(context.Background())
	doneCh := make(chan error)
	var err error
	go func() {
		doneCh <- body(ctx)
	}()
	select {
	case err = <-doneCh:
	case <-v.shutdownCh:
		cancel()
		err = ErrShutdown
	}
	return err
}
func (v *CredentialAuthority) pollLoop() {
	var err error
	for err != ErrShutdown {
		err = v.pollOnce()
	}
}
func (v *CredentialAuthority) runLoop() {
	done := false
	for !done {
		select {
		case <-v.eng.GetPokeCh():
		case <-v.shutdownCh:
			done = true
		case ca := <-v.checkCh:
			v.log.Debug("Checking %s", ca)
			u := v.makeUser(ca.uid)
			go u.sendCheck(ca)
		case uid := <-v.invalidateCh:
			if uw := v.users[uid]; uw != nil {
				v.log.Debug("Invalidating %s", uw)
				delete(v.users, uid)
				go uw.u.sendStop()
			}
		case ci := <-v.cleanItemCh:
			v.cleanSchedule = append(v.cleanSchedule, ci)
		}
		v.clean()
	}
}
func (v *CredentialAuthority) clean() {
	cutoff := v.eng.Now().Add(-cacheTimeout)
	for i, e := range v.cleanSchedule {
		if e.ctime.After(cutoff) {
			v.cleanSchedule = v.cleanSchedule[i:]
			return
		}
		if uw := v.users[e.uid]; uw != nil && !uw.atime.After(e.ctime) {
			v.log.Debug("Cleaning %s, clean entry: %s", uw, e)
			delete(v.users, e.uid)
			go uw.u.sendStop()
		}
	}
	v.cleanSchedule = nil
}
func (v *CredentialAuthority) makeUser(uid keybase1.UID) *user {
	uw := v.users[uid]
	if uw == nil {
		u := newUser(uid, v)
		uw = &userWrapper{u: u}
		v.users[uid] = uw
	}
	uw.atime = v.eng.Now()
	return uw.u
}
func (u *user) sendCheck(ca checkArg) {
	u.checkCh <- ca
}
func (u *user) sendStop() {
	u.stopCh <- struct{}{}
}
func (u *user) run() {
	done := false
	for !done {
		select {
		case ca := <-u.checkCh:
			u.check(ca)
		case <-u.stopCh:
			u.ca.log.Debug("Stopping user loop for %s", u)
			done = true
		case <-u.ca.shutdownCh:
			done = true
		}
	}
	u.ca.eng.Evicted(u.uid)
}
func (u user) Now() time.Time { return u.ca.eng.Now() }
func (u *user) repopulate() error {
	if u.isPopulated() {
		return nil
	}
	ctime := u.Now()
	go func() {
		u.ca.cleanItemCh <- cleanItem{uid: u.uid, ctime: ctime}
	}()
	un, sibkeys, subkeys, err := u.ca.getUserFromServer(u.uid)
	if err != nil {
		u.isOK = false
		return err
	}
	u.username = un
	for _, k := range sibkeys {
		u.sibkeys[k] = struct{}{}
	}
	for _, k := range subkeys {
		u.subkeys[k] = struct{}{}
	}
	u.isOK = true
	u.ctime = ctime
	u.ca.log.Debug("Repopulated info for %s", u)
	return nil
}