-
Notifications
You must be signed in to change notification settings - Fork 4
/
contacts.go
203 lines (175 loc) · 5.62 KB
/
contacts.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
// go-coronanet - Coronavirus social distancing network
// Copyright (c) 2020 Péter Szilágyi. All rights reserved.
package coronanet
import (
"encoding/json"
"errors"
"github.com/coronanet/go-coronanet/tornet"
)
var (
// dbContactPrefix is the database key for storing a remote user's profile.
dbContactPrefix = []byte("contact-")
// ErrSelfContact is returned if a new contact is attempted to be trusted
// but it is the local user.
ErrSelfContact = errors.New("cannot contact self")
// ErrContactNotFound is returned if a new contact is attempted to be accessed
// but it does not exist.
ErrContactNotFound = errors.New("contact not found")
// ErrContactExists is returned if a new contact is attempted to be trusted
// but it already is trusted.
ErrContactExists = errors.New("contact already exists")
)
// contact represents a remote user's profile information.
type contact struct {
Name string `json:"name` // Originally remote, can override
Avatar [32]byte `json:"avatar"` // Always remote, for now
}
// AddContact inserts a new remote identity into the local trust ring and adds
// it to the overlay network.
func (b *Backend) AddContact(keyring tornet.RemoteKeyRing) (tornet.IdentityFingerprint, error) {
b.logger.Info("Creating new contact", "contact", keyring.Identity.Fingerprint())
b.lock.Lock()
defer b.lock.Unlock()
// Sanity check that the contact does not exist
prof, err := b.Profile()
if err != nil {
return "", err
}
uid := keyring.Identity.Fingerprint()
if prof.KeyRing.Identity.Fingerprint() == uid {
return "", ErrSelfContact
}
if _, err := b.Contact(uid); err == nil {
return "", ErrContactExists
}
// Create the profile entry for the remote user
blob, err := json.Marshal(&contact{})
if err != nil {
return "", err
}
if err := b.database.Put(append(dbContactPrefix, uid...), blob, nil); err != nil {
return "", err
}
// Inject the security credentials into the overlay (cascading into the profile)
return uid, b.overlay.Trust(keyring)
}
// DeleteContact removes the contact from the trust ring, deletes all associated
// data and disconnects any active connections.
func (b *Backend) DeleteContact(uid tornet.IdentityFingerprint) error {
b.logger.Info("Deleting contact", "contact", uid)
b.lock.Lock()
defer b.lock.Unlock()
// Sanity check that the contact does exist
if _, err := b.Contact(uid); err != nil {
return ErrContactNotFound
}
// Break any pending connections from the overlay network
if err := b.overlay.Untrust(uid); err != nil {
return err
}
// Remove all data associated with the contact
if err := b.deleteContactPicture(uid); err != nil {
return err
}
return b.database.Delete(append(dbContactPrefix, uid...), nil)
}
// Contacts returns the unique ids of all the current contacts.
func (b *Backend) Contacts() ([]tornet.IdentityFingerprint, error) {
prof, err := b.Profile()
if err != nil {
return nil, ErrProfileNotFound
}
uids := make([]tornet.IdentityFingerprint, 0, len(prof.KeyRing.Trusted))
for uid := range prof.KeyRing.Trusted {
uids = append(uids, uid)
}
return uids, nil
}
// Contact retrieves a remote user's profile infos.
func (b *Backend) Contact(uid tornet.IdentityFingerprint) (*contact, error) {
blob, err := b.database.Get(append(dbContactPrefix, uid...), nil)
if err != nil {
return nil, ErrContactNotFound
}
info := new(contact)
if err := json.Unmarshal(blob, info); err != nil {
return nil, err
}
return info, nil
}
// UpdateContact overrides the profile information of an existing remote user.
func (b *Backend) UpdateContact(uid tornet.IdentityFingerprint, name string) error {
b.logger.Info("Updating contact infos", "contact", uid, "name", name)
b.lock.Lock()
defer b.lock.Unlock()
// Retrieve the current profile and abort if the update is a noop
info, err := b.Contact(uid)
if err != nil {
return err
}
if info.Name == name {
return nil
}
// Name changed, update and serialize back to disk
info.Name = name
blob, err := json.Marshal(info)
if err != nil {
return err
}
return b.database.Put(append(dbContactPrefix, uid...), blob, nil)
}
// uploadContactPicture uploads a new local profile picture for the remote user.
func (b *Backend) uploadContactPicture(uid tornet.IdentityFingerprint, data []byte) error {
b.logger.Info("Uploading contact picture", "contact", uid)
b.lock.Lock()
defer b.lock.Unlock()
// Retrieve the current profile to ensure the user exists
info, err := b.Contact(uid)
if err != nil {
return err
}
// Upload the image into the CDN and delete the old one
hash, err := b.uploadCDNImage(data)
if err != nil {
return err
}
if info.Avatar != ([32]byte{}) {
if err := b.deleteCDNImage(info.Avatar); err != nil {
return err
}
}
// If the hash changed, update the profile
if info.Avatar == hash {
return nil
}
info.Avatar = hash
blob, err := json.Marshal(info)
if err != nil {
return err
}
return b.database.Put(append(dbContactPrefix, uid...), blob, nil)
}
// deleteContactPicture deletes the existing local profile picture of the remote user.
func (b *Backend) deleteContactPicture(uid tornet.IdentityFingerprint) error {
b.logger.Info("Deleting contact picture", "contact", uid)
b.lock.Lock()
defer b.lock.Unlock()
// Retrieve the current profile to ensure the user exists
info, err := b.Contact(uid)
if err != nil {
return err
}
if info.Avatar == [32]byte{} {
return nil
}
// Profile picture exists, delete it from the CDN and update the profile
if err := b.deleteCDNImage(info.Avatar); err != nil {
return err
}
info.Avatar = [32]byte{}
blob, err := json.Marshal(info)
if err != nil {
return err
}
return b.database.Put(append(dbContactPrefix, uid...), blob, nil)
}