-
Notifications
You must be signed in to change notification settings - Fork 7
/
friend.go
235 lines (201 loc) · 5.67 KB
/
friend.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
// Copyright 2016 David Lazar. All rights reserved.
// Use of this source code is governed by the GNU AGPL
// license that can be found in the LICENSE file.
package alpenhorn
import (
"crypto/ed25519"
"fmt"
"time"
)
// Friend is an entry in the client's address book.
type Friend struct {
Username string
LongTermKey ed25519.PublicKey
// extraData stores application-specific data.
extraData []byte
client *Client
}
// GetFriends returns all the friends in the client's address book.
func (c *Client) GetFriends() []*Friend {
c.mu.Lock()
fs := make([]*Friend, 0, len(c.friends))
for _, friend := range c.friends {
fs = append(fs, friend)
}
c.mu.Unlock()
return fs
}
// GetFriend returns the friend object for the given username,
// or nil if username is not in the client's address book.
func (c *Client) GetFriend(username string) *Friend {
c.mu.Lock()
friend := c.friends[username]
c.mu.Unlock()
return friend
}
// Remove removes the friend from the client's address book.
func (f *Friend) Remove() error {
f.client.mu.Lock()
defer f.client.mu.Unlock()
delete(f.client.friends, f.Username)
f.client.wheel.Remove(f.Username)
// delete any outgoing calls for this friend
calls := f.client.outgoingCalls[:0]
for _, call := range f.client.outgoingCalls {
if call.Username != f.Username {
calls = append(calls, call)
}
}
f.client.outgoingCalls = calls
err := f.client.persistLocked()
return err
}
// SetExtraData overwrites the friend's extra data field with the given
// data. The extra data field is useful for application-specific data
// about the friend, such as additional contact info, notes, or a photo.
//
// Applications should use the extra data field to store information
// about friends instead of maintaining a separate friend list because
// the Alpenhorn client will (eventually) ensure that the size of the
// persisted data on disk does not leak metadata.
func (f *Friend) SetExtraData(data []byte) error {
f.client.mu.Lock()
f.extraData = make([]byte, len(data))
copy(f.extraData, data)
err := f.client.persistLocked()
f.client.mu.Unlock()
return err
}
// ExtraData returns a copy of the extra data field for the friend.
func (f *Friend) ExtraData() []byte {
f.client.mu.Lock()
data := make([]byte, len(f.extraData))
copy(data, f.extraData)
f.client.mu.Unlock()
return data
}
// UnsafeKeywheelState exposes the internal keywheel state for this friend.
// This should only be used for debugging.
func (f *Friend) UnsafeKeywheelState() (uint32, *[32]byte) {
return f.client.wheel.UnsafeGet(f.Username)
}
// SessionKey returns the shared key at the given round.
// This should only be used for debugging.
func (f *Friend) SessionKey(round uint32) *[32]byte {
return f.client.wheel.SessionKey(f.Username, round)
}
// Intents are the dialing intents passed to Call.
const IntentMax = 3
// Call is used to call a friend using Alpenhorn's dialing protocol.
// Call does not send the call right away but queues the call for an
// upcoming dialing round. The resulting OutgoingCall is the queued
// call object. Call does nothing and returns nil if the friend is
// not in the client's address book.
func (f *Friend) Call(intent int) *OutgoingCall {
if intent >= IntentMax {
panic(fmt.Sprintf("invalid intent: %d", intent))
}
if !f.client.wheel.Exists(f.Username) {
return nil
}
call := &OutgoingCall{
Username: f.Username,
Created: time.Now(),
client: f.client,
intent: intent,
}
f.client.mu.Lock()
f.client.outgoingCalls = append(f.client.outgoingCalls, call)
f.client.mu.Unlock()
return call
}
type IncomingCall struct {
Username string
Intent int
SessionKey *[32]byte
}
type OutgoingCall struct {
Username string
Created time.Time
client *Client
intent int
sentRound uint32
dialToken *[32]byte
sessionKey *[32]byte
}
// Sent returns true if the call has been sent and false otherwise.
func (r *OutgoingCall) Sent() bool {
r.client.mu.Lock()
sent := r.sentRound != 0
r.client.mu.Unlock()
return sent
}
func (r *OutgoingCall) Intent() int {
r.client.mu.Lock()
intent := r.intent
r.client.mu.Unlock()
return intent
}
func (r *OutgoingCall) UpdateIntent(intent int) error {
r.client.mu.Lock()
defer r.client.mu.Unlock()
if r.dialToken != nil {
return ErrTooLate
}
r.intent = intent
return nil
}
type computeKeysResult struct{ token, sessionKey *[32]byte }
func (r *OutgoingCall) computeKeys() computeKeysResult {
r.client.mu.Lock()
if r.sentRound == 0 || r.dialToken != nil {
r.client.mu.Unlock()
return computeKeysResult{
token: r.dialToken,
sessionKey: r.sessionKey,
}
}
intent := r.intent
round := r.sentRound
r.client.mu.Unlock()
dialToken := r.client.wheel.OutgoingDialToken(r.Username, round, intent)
sessionKey := r.client.wheel.SessionKey(r.Username, round)
r.client.mu.Lock()
defer r.client.mu.Unlock()
if r.dialToken != nil {
return computeKeysResult{
token: r.dialToken,
sessionKey: r.sessionKey,
}
}
r.intent = intent
r.dialToken = dialToken
r.sessionKey = sessionKey
return computeKeysResult{
token: r.dialToken,
sessionKey: r.sessionKey,
}
}
// SessionKey returns the session key established for this call,
// or nil if the call has not been sent yet.
func (r *OutgoingCall) SessionKey() *[32]byte {
return r.computeKeys().sessionKey
}
// Cancel removes the call from the outgoing queue, returning
// ErrTooLate if the call is not found in the queue.
func (r *OutgoingCall) Cancel() error {
r.client.mu.Lock()
defer r.client.mu.Unlock()
calls := r.client.outgoingCalls
index := -1
for i, c := range calls {
if r == c {
index = i
}
}
if index == -1 {
return ErrTooLate
}
r.client.outgoingCalls = append(calls[:index], calls[index+1:]...)
return nil
}