1// Copyright 2015 Keybase, Inc. All rights reserved. Use of
2// this source code is governed by the included BSD license.
3
4package libkb
5
6import (
7	keybase1 "github.com/keybase/client/go/protocol/keybase1"
8	jsonw "github.com/keybase/go-jsonw"
9)
10
11// RemoteProofLinks holds a set of RemoteProofChainLinks,
12// organized by service.
13type RemoteProofLinks struct {
14	Contextified
15	links map[string][]ProofLinkWithState
16}
17
18// ProofLinkWithState contains a RemoteProofChainLink and the
19// proof state.  In addition, it satisfies the TrackIdComponent interface.
20type ProofLinkWithState struct {
21	link  RemoteProofChainLink
22	state keybase1.ProofState
23}
24
25// NewRemoteProofLinks creates a new empty collection of proof
26// links.
27func NewRemoteProofLinks(g *GlobalContext) *RemoteProofLinks {
28	return &RemoteProofLinks{
29		Contextified: NewContextified(g),
30		links:        make(map[string][]ProofLinkWithState),
31	}
32}
33
34// Insert adds a link to the collection of proof links.
35func (r *RemoteProofLinks) Insert(link RemoteProofChainLink, err ProofError) {
36	key := link.TableKey()
37	if len(key) == 0 {
38		return
39	}
40	r.links[key] = append(r.links[key], ProofLinkWithState{link: link, state: ProofErrorToState(err)})
41}
42
43// ForService returns all the active proof links for a service.
44func (r *RemoteProofLinks) ForService(st ServiceType) []RemoteProofChainLink {
45	var links []RemoteProofChainLink
46	for _, linkWithState := range r.links[st.Key()] {
47		if linkWithState.link.LastWriterWins() {
48			// Clear the array if it's a last-writer wins service.
49			// (like many social networks)
50			links = nil
51		}
52		if linkWithState.link.IsRevoked() {
53			continue
54		}
55		links = append(links, linkWithState.link)
56	}
57	return links
58}
59
60// Active returns all the active proof links, deduplicating any and
61// honoring the LastWriterWins option.
62func (r *RemoteProofLinks) Active() []RemoteProofChainLink {
63	a := r.active()
64	links := make([]RemoteProofChainLink, len(a))
65	for i, b := range a {
66		links[i] = b.link
67	}
68	return links
69}
70
71// TrackingStatement generates the remote proofs portions of the
72// tracking statement from the active proofs.
73func (r *RemoteProofLinks) TrackingStatement() *jsonw.Wrapper {
74	var proofs []*jsonw.Wrapper
75	for _, x := range r.active() {
76		d, err := x.link.ToTrackingStatement(x.state)
77		if err != nil {
78			r.G().Log.Warning("Problem with a proof: %s", err)
79			continue
80		}
81		if d != nil {
82			proofs = append(proofs, d)
83		}
84	}
85
86	res := jsonw.NewArray(len(proofs))
87	for i, proof := range proofs {
88		_ = res.SetIndex(i, proof)
89	}
90	return res
91}
92
93// TrackSet creates a new TrackSet with all the active proofs.
94func (r *RemoteProofLinks) TrackSet() *TrackSet {
95	ret := NewTrackSet()
96	for _, ap := range r.active() {
97		ret.Add(ap)
98	}
99	return ret
100}
101
102// AddProofsToSet adds the active proofs to an existing ProofSet, if they're one of the
103// given OkStates. If okStates is nil, then we check only against keybase1.ProofState_OK.
104func (r *RemoteProofLinks) AddProofsToSet(existing *ProofSet, okStates []keybase1.ProofState) {
105	if okStates == nil {
106		okStates = []keybase1.ProofState{keybase1.ProofState_OK}
107	}
108	isOkState := func(s1 keybase1.ProofState) bool {
109		for _, s2 := range okStates {
110			if s1 == s2 {
111				return true
112			}
113		}
114		return false
115	}
116	for _, a := range r.active() {
117		if !isOkState(a.state) {
118			continue
119		}
120		AddToProofSetNoChecks(a.link, existing)
121	}
122}
123
124func RemoteProofChainLinkToProof(r RemoteProofChainLink) Proof {
125	k, v := r.ToKeyValuePair()
126	return Proof{Key: k, Value: v}
127}
128
129func AddToProofSetNoChecks(r RemoteProofChainLink, ps *ProofSet) {
130	ps.Add(RemoteProofChainLinkToProof(r))
131}
132
133func (r *RemoteProofLinks) active() []ProofLinkWithState {
134	var links []ProofLinkWithState
135	seen := make(map[string]bool)
136
137	// Loop over all types of services
138	for _, list := range r.links {
139
140		// Loop over all proofs for that type, from most recent,
141		// to oldest.
142		for i := len(list) - 1; i >= 0; i-- {
143			both := list[i]
144			link := both.link
145			id := CanonicalProofName(link)
146
147			if !link.IsRevoked() && !seen[id] {
148				links = append(links, both)
149			}
150
151			// We only want to use the last proof in the list
152			// if we have several (like for dns://chriscoyne.com)
153			seen[id] = true
154
155			// Things like Twitter, Github, etc, are last-writer wins.
156			// Things like dns/https can have multiples
157			if link.LastWriterWins() {
158				break
159			}
160		}
161	}
162	return links
163}
164
165// TrackIdComponent interface functions:
166
167func (p ProofLinkWithState) GetProofState() keybase1.ProofState {
168	return p.state
169}
170
171func (p ProofLinkWithState) LastWriterWins() bool {
172	return p.link.LastWriterWins()
173}
174
175func (p ProofLinkWithState) ToIDString() string {
176	return p.link.ToIDString()
177}
178
179func (p ProofLinkWithState) ToKeyValuePair() (string, string) {
180	return p.link.ToKeyValuePair()
181}
182
183func (p ProofLinkWithState) GetProofType() keybase1.ProofType { return p.link.GetProofType() }
184
185func (r *RemoteProofLinks) toServiceSummary() (ret UserServiceSummary) {
186	ret = make(UserServiceSummary, len(r.links))
187	activeProofs := r.Active()
188	for _, proof := range activeProofs {
189		key, val := proof.ToKeyValuePair()
190		ret[key] = val
191	}
192	return ret
193}
194