Line data Source code
1 : /*
2 : * Copyright (C) 2004-2026 Savoir-faire Linux Inc.
3 : *
4 : * This program is free software: you can redistribute it and/or modify
5 : * it under the terms of the GNU General Public License as published by
6 : * the Free Software Foundation, either version 3 of the License, or
7 : * (at your option) any later version.
8 : *
9 : * This program is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : * GNU General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU General Public License
15 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 : */
17 : #include "contact_list.h"
18 : #include "logger.h"
19 : #include "jamiaccount.h"
20 : #include "fileutils.h"
21 :
22 : #include "manager.h"
23 : #ifdef ENABLE_PLUGIN
24 : #include "plugin/jamipluginmanager.h"
25 : #endif
26 :
27 : #include "account_const.h"
28 :
29 : #include <fstream>
30 : #include <string_view>
31 : #include <gnutls/ocsp.h>
32 :
33 : namespace jami {
34 :
35 1037 : ContactList::ContactList(const std::string& accountId,
36 : const std::shared_ptr<crypto::Certificate>& cert,
37 : const std::filesystem::path& path,
38 1037 : OnChangeCallback cb)
39 1037 : : accountId_(accountId)
40 1037 : , path_(path)
41 1037 : , callbacks_(std::move(cb))
42 : {
43 1037 : if (cert) {
44 1037 : trust_ = std::make_unique<dhtnet::tls::TrustStore>(jami::Manager::instance().certStore(accountId_));
45 1037 : accountTrust_.add(*cert);
46 : }
47 1037 : }
48 :
49 1037 : ContactList::~ContactList() {}
50 :
51 : void
52 20 : ContactList::load()
53 : {
54 20 : loadContacts();
55 20 : loadTrustRequests();
56 20 : loadKnownDevices();
57 20 : }
58 :
59 : void
60 0 : ContactList::save()
61 : {
62 0 : saveContacts();
63 0 : saveTrustRequests();
64 0 : std::lock_guard lock(devicesMutex_);
65 0 : saveKnownDevices();
66 0 : }
67 :
68 : bool
69 97 : ContactList::setCertificateStatus(const std::string& cert_id, const dhtnet::tls::TrustStore::PermissionStatus status)
70 : {
71 97 : std::unique_lock lk(mutex_);
72 97 : if (contacts_.find(dht::InfoHash(cert_id)) != contacts_.end()) {
73 3 : JAMI_LOG("[Account {}] [Contacts] Unable to set certificate status for existing contacts {}",
74 : accountId_,
75 : cert_id);
76 3 : return false;
77 : }
78 94 : return trust_->setCertificateStatus(cert_id, status);
79 97 : }
80 :
81 : bool
82 1031 : ContactList::setCertificateStatus(const std::shared_ptr<crypto::Certificate>& cert,
83 : dhtnet::tls::TrustStore::PermissionStatus status,
84 : bool local)
85 : {
86 1031 : return trust_->setCertificateStatus(cert, status, local);
87 : }
88 :
89 : bool
90 926 : ContactList::addContact(const dht::InfoHash& h, bool confirmed, const std::string& conversationId)
91 : {
92 926 : std::unique_lock lk(mutex_);
93 926 : JAMI_WARNING("[Account {}] [Contacts] addContact: {}, conversation: {}", accountId_, h, conversationId);
94 926 : auto c = contacts_.find(h);
95 926 : if (c == contacts_.end())
96 494 : c = contacts_.emplace(h, Contact {}).first;
97 432 : else if (c->second.isActive() and c->second.confirmed == confirmed && c->second.conversationId == conversationId)
98 422 : return false;
99 504 : if (!c->second.isActive())
100 501 : c->second.added = nowMs();
101 : // NOTE: because we can re-add a contact after removing it
102 : // we should reset removed (as not removed anymore). This fix isActive()
103 : // if addContact is called just after removeContact within the same instant
104 504 : c->second.removed = TimePoint {};
105 504 : c->second.conversationId = conversationId;
106 504 : c->second.confirmed |= confirmed;
107 504 : auto hStr = h.toString();
108 504 : trust_->setCertificateStatus(hStr, dhtnet::tls::TrustStore::PermissionStatus::ALLOWED);
109 504 : saveContacts();
110 504 : lk.unlock();
111 504 : callbacks_.contactAdded(hStr, c->second.confirmed);
112 504 : return true;
113 926 : }
114 :
115 : bool
116 32 : ContactList::updateConversation(const dht::InfoHash& h, const std::string& conversationId)
117 : {
118 32 : std::lock_guard lk(mutex_);
119 32 : auto c = contacts_.find(h);
120 32 : if (c != contacts_.end() && c->second.conversationId != conversationId) {
121 17 : c->second.conversationId = conversationId;
122 17 : saveContacts();
123 17 : return true;
124 : }
125 15 : return false;
126 32 : }
127 :
128 : bool
129 20 : ContactList::removeContact(const dht::InfoHash& h, bool ban)
130 : {
131 20 : std::unique_lock lk(mutex_);
132 20 : JAMI_WARNING("[Account {}] [Contacts] removeContact: {} (banned: {})", accountId_, h, ban);
133 20 : auto c = contacts_.find(h);
134 20 : if (c == contacts_.end())
135 4 : c = contacts_.emplace(h, Contact {}).first;
136 20 : c->second.removed = nowMs();
137 20 : c->second.confirmed = false;
138 20 : c->second.banned = ban;
139 20 : c->second.conversationId = "";
140 20 : auto uri = h.toString();
141 20 : trust_->setCertificateStatus(uri,
142 : ban ? dhtnet::tls::TrustStore::PermissionStatus::BANNED
143 : : dhtnet::tls::TrustStore::PermissionStatus::UNDEFINED);
144 20 : if (trustRequests_.erase(h) > 0)
145 3 : saveTrustRequests();
146 20 : saveContacts();
147 20 : lk.unlock();
148 : #ifdef ENABLE_PLUGIN
149 20 : auto filename = path_.filename().string();
150 20 : jami::Manager::instance().getJamiPluginManager().getChatServicesManager().cleanChatSubjects(filename, uri);
151 : #endif
152 20 : callbacks_.contactRemoved(uri, ban);
153 20 : return true;
154 20 : }
155 :
156 : bool
157 2 : ContactList::removeContactConversation(const dht::InfoHash& h)
158 : {
159 2 : std::unique_lock lk(mutex_);
160 2 : auto c = contacts_.find(h);
161 2 : if (c == contacts_.end())
162 0 : return false;
163 2 : c->second.conversationId = "";
164 2 : saveContacts();
165 2 : return true;
166 2 : }
167 :
168 : std::map<std::string, std::string>
169 8 : ContactList::getContactDetails(const dht::InfoHash& h) const
170 : {
171 8 : std::unique_lock lk(mutex_);
172 8 : const auto c = contacts_.find(h);
173 16 : if (c == std::end(contacts_)) {
174 0 : JAMI_WARNING("[Account {}] [Contacts] Contact '{}' not found", accountId_, h.to_view());
175 0 : return {};
176 : }
177 :
178 8 : auto details = c->second.toMap();
179 8 : if (not details.empty())
180 24 : details["id"] = c->first.toString();
181 :
182 8 : return details;
183 8 : }
184 :
185 : std::optional<Contact>
186 14333 : ContactList::getContactInfo(const dht::InfoHash& h) const
187 : {
188 14333 : const auto c = contacts_.find(h);
189 28666 : if (c == std::end(contacts_)) {
190 3096 : JAMI_WARNING("[Account {}] [Contacts] Contact '{}' not found", accountId_, h.to_view());
191 3096 : return {};
192 : }
193 11237 : return c->second;
194 : }
195 :
196 : std::map<dht::InfoHash, Contact>
197 2540 : ContactList::getContacts() const
198 : {
199 2540 : std::lock_guard lk(mutex_);
200 5080 : return contacts_;
201 2540 : }
202 :
203 : void
204 1018 : ContactList::setContacts(const std::map<dht::InfoHash, Contact>& contacts)
205 : {
206 1018 : JAMI_LOG("[Account {}] [Contacts] replacing contact list (old: {} new: {})",
207 : accountId_,
208 : contacts_.size(),
209 : contacts.size());
210 1018 : contacts_ = contacts;
211 1018 : saveContacts();
212 : // Set contacts is used when creating a new device, so just announce new contacts
213 1040 : for (auto& peer : contacts)
214 22 : if (peer.second.isActive())
215 22 : callbacks_.contactAdded(peer.first.toString(), peer.second.confirmed);
216 1018 : }
217 :
218 : void
219 126 : ContactList::updateContact(const dht::InfoHash& id, const Contact& contact, bool emit)
220 : {
221 126 : if (not id) {
222 0 : JAMI_ERROR("[Account {}] [Contacts] updateContact: invalid contact ID", accountId_);
223 0 : return;
224 : }
225 126 : bool stateChanged {false};
226 126 : auto c = contacts_.find(id);
227 126 : if (c == contacts_.end()) {
228 : // JAMI_LOG("[Contacts] New contact: {}", id);
229 14 : c = contacts_.emplace(id, contact).first;
230 14 : stateChanged = c->second.isActive() or c->second.isBanned();
231 : } else {
232 : // JAMI_LOG("[Contacts] Updated contact: {}", id);
233 112 : stateChanged = c->second.update(contact);
234 : }
235 126 : if (stateChanged) {
236 : {
237 16 : std::lock_guard lk(mutex_);
238 16 : if (trustRequests_.erase(id) > 0)
239 6 : saveTrustRequests();
240 16 : }
241 16 : if (c->second.isActive()) {
242 13 : trust_->setCertificateStatus(id.toString(), dhtnet::tls::TrustStore::PermissionStatus::ALLOWED);
243 13 : if (emit)
244 11 : callbacks_.contactAdded(id.toString(), c->second.confirmed);
245 : } else {
246 3 : if (c->second.banned)
247 2 : trust_->setCertificateStatus(id.toString(), dhtnet::tls::TrustStore::PermissionStatus::BANNED);
248 3 : if (emit)
249 3 : callbacks_.contactRemoved(id.toString(), c->second.banned);
250 : }
251 : }
252 : }
253 :
254 : std::map<dht::InfoHash, Contact>
255 67 : ContactList::contactsFromPath(const std::filesystem::path& path)
256 : {
257 67 : std::map<dht::InfoHash, Contact> contacts;
258 : try {
259 67 : std::lock_guard fileLock(dhtnet::fileutils::getFileLock(path / "contacts"));
260 68 : auto file = fileutils::loadFile("contacts", path);
261 66 : msgpack::object_handle oh = msgpack::unpack((const char*) file.data(), file.size());
262 66 : oh.get().convert(contacts);
263 68 : } catch (const std::exception& e) {
264 1 : JAMI_WARNING("[Contacts] Error loading contacts from {}: {}", path.string(), e.what());
265 1 : }
266 67 : return contacts;
267 0 : }
268 :
269 : void
270 20 : ContactList::loadContacts()
271 : {
272 20 : auto contacts = contactsFromPath(path_);
273 20 : JAMI_WARNING("[Account {}] [Contacts] Loaded {} contacts", accountId_, contacts.size());
274 22 : for (auto& peer : contacts)
275 2 : updateContact(peer.first, peer.second, false);
276 20 : }
277 :
278 : void
279 1919 : ContactList::saveContacts() const
280 : {
281 1919 : JAMI_LOG("[Account {}] [Contacts] saving {} contacts", accountId_, contacts_.size());
282 1919 : std::lock_guard fileLock(dhtnet::fileutils::getFileLock(path_ / "contacts"));
283 1919 : std::ofstream file(path_ / "contacts", std::ios::trunc | std::ios::binary);
284 1919 : msgpack::pack(file, contacts_);
285 1919 : }
286 :
287 : void
288 554 : ContactList::saveTrustRequests() const
289 : {
290 : // mutex_ MUST BE locked
291 554 : std::ofstream file(path_ / "incomingTrustRequests", std::ios::trunc | std::ios::binary);
292 554 : msgpack::pack(file, trustRequests_);
293 554 : }
294 :
295 : void
296 20 : ContactList::loadTrustRequests()
297 : {
298 20 : if (!std::filesystem::is_regular_file(fileutils::getFullPath(path_, "incomingTrustRequests")))
299 19 : return;
300 1 : std::map<dht::InfoHash, TrustRequest> requests;
301 : try {
302 : // read file
303 1 : auto file = fileutils::loadFile("incomingTrustRequests", path_);
304 : // load values
305 1 : msgpack::object_handle oh = msgpack::unpack((const char*) file.data(), file.size());
306 1 : oh.get().convert(requests);
307 1 : } catch (const std::exception& e) {
308 0 : JAMI_WARNING("[Account {}] [Contacts] Error loading trust requests: {}", accountId_, e.what());
309 0 : return;
310 0 : }
311 :
312 1 : JAMI_WARNING("[Account {}] [Contacts] Loaded {} contact requests", accountId_, requests.size());
313 1 : for (auto& tr : requests)
314 0 : onTrustRequest(tr.first,
315 0 : tr.second.device,
316 : tr.second.received,
317 : false,
318 0 : tr.second.conversationId,
319 0 : std::move(tr.second.payload),
320 : tr.second.invited);
321 1 : }
322 :
323 : bool
324 858 : ContactList::onTrustRequest(const dht::InfoHash& peer_account,
325 : const std::shared_ptr<dht::crypto::PublicKey>& peer_device,
326 : TimePoint received,
327 : bool confirm,
328 : const std::string& conversationId,
329 : std::vector<uint8_t>&& payload,
330 : TimePoint invited)
331 : {
332 858 : bool accept = false;
333 : // Check existing contact
334 858 : std::unique_lock lk(mutex_);
335 858 : auto contact = contacts_.find(peer_account);
336 858 : bool active = false;
337 858 : if (contact != contacts_.end()) {
338 : // Banned contact: discard request
339 567 : if (contact->second.isBanned())
340 0 : return false;
341 :
342 567 : if (contact->second.isActive()) {
343 565 : active = true;
344 : // Send confirmation
345 565 : if (not confirm)
346 154 : accept = true;
347 565 : if (not contact->second.confirmed) {
348 239 : contact->second.confirmed = true;
349 239 : saveContacts();
350 239 : callbacks_.contactAdded(peer_account.toString(), true);
351 : }
352 : }
353 : }
354 858 : if (not active) {
355 293 : auto req = trustRequests_.find(peer_account);
356 293 : if (req == trustRequests_.end()) {
357 : // Add trust request
358 271 : req = trustRequests_
359 271 : .emplace(peer_account, TrustRequest {peer_device, conversationId, received, payload, invited})
360 : .first;
361 : } else {
362 22 : auto incomingTimestamp = invited != TimePoint {} ? invited : received;
363 22 : auto existingTimestamp = req->second.invited != TimePoint {} ? req->second.invited
364 0 : : req->second.received;
365 :
366 22 : if (incomingTimestamp > existingTimestamp) {
367 2 : req->second.device = peer_device;
368 2 : req->second.conversationId = conversationId;
369 2 : req->second.received = received;
370 2 : req->second.payload = payload;
371 2 : req->second.invited = invited;
372 : } else {
373 20 : JAMI_LOG("[Account {}] [Contacts] Ignoring outdated trust request from {}", accountId_, peer_account);
374 : }
375 : }
376 293 : saveTrustRequests();
377 : }
378 858 : lk.unlock();
379 : // Note: call JamiAccount's callback to build ConversationRequest anyway
380 858 : if (!confirm)
381 447 : callbacks_.trustRequest(peer_account.toString(),
382 : conversationId,
383 447 : std::move(payload),
384 : received,
385 : invited);
386 411 : else if (active) {
387 : // Only notify if confirmed + not removed
388 411 : callbacks_.onConfirmation(peer_account.toString(), conversationId);
389 : }
390 858 : return accept;
391 858 : }
392 :
393 : /* trust requests */
394 :
395 : std::vector<std::map<std::string, std::string>>
396 919 : ContactList::getTrustRequests() const
397 : {
398 : using Map = std::map<std::string, std::string>;
399 919 : std::vector<Map> ret;
400 919 : std::lock_guard lk(mutex_);
401 919 : ret.reserve(trustRequests_.size());
402 942 : for (const auto& r : trustRequests_) {
403 23 : ret.emplace_back(
404 184 : Map {{libjami::Account::TrustRequest::FROM, r.first.toString()},
405 46 : {libjami::Account::TrustRequest::RECEIVED, std::to_string(toSecondsSinceEpoch(r.second.received))},
406 23 : {libjami::Account::TrustRequest::CONVERSATIONID, r.second.conversationId},
407 : {libjami::Account::TrustRequest::PAYLOAD,
408 184 : std::string(r.second.payload.begin(), r.second.payload.end())}});
409 : }
410 1838 : return ret;
411 988 : }
412 :
413 : std::map<std::string, std::string>
414 643 : ContactList::getTrustRequest(const dht::InfoHash& from) const
415 : {
416 : using Map = std::map<std::string, std::string>;
417 643 : std::lock_guard lk(mutex_);
418 643 : auto r = trustRequests_.find(from);
419 643 : if (r == trustRequests_.end())
420 400 : return {};
421 243 : return Map {{libjami::Account::TrustRequest::FROM, r->first.toString()},
422 486 : {libjami::Account::TrustRequest::RECEIVED, std::to_string(toSecondsSinceEpoch(r->second.received))},
423 486 : {libjami::Account::TrustRequest::CONVERSATIONID, r->second.conversationId},
424 : {libjami::Account::TrustRequest::PAYLOAD,
425 2187 : std::string(r->second.payload.begin(), r->second.payload.end())}};
426 1615 : }
427 :
428 : bool
429 625 : ContactList::acceptTrustRequest(const dht::InfoHash& from)
430 : {
431 : // The contact sent us a TR so we are in its contact list
432 625 : std::unique_lock lk(mutex_);
433 625 : auto i = trustRequests_.find(from);
434 625 : if (i == trustRequests_.end())
435 383 : return false;
436 242 : auto convId = i->second.conversationId;
437 : // Clear trust request
438 242 : trustRequests_.erase(i);
439 242 : saveTrustRequests();
440 242 : lk.unlock();
441 242 : addContact(from, true, convId);
442 242 : return true;
443 625 : }
444 :
445 : void
446 396 : ContactList::acceptConversation(const std::string& convId, const std::string& deviceId)
447 : {
448 396 : if (callbacks_.acceptConversation)
449 396 : callbacks_.acceptConversation(convId, deviceId);
450 396 : }
451 :
452 : bool
453 6 : ContactList::discardTrustRequest(const dht::InfoHash& from)
454 : {
455 6 : return discardTrustRequest(from, {}, TimePoint::max());
456 : }
457 :
458 : bool
459 18 : ContactList::discardTrustRequest(const dht::InfoHash& from, const std::string& conversationId, TimePoint declined)
460 : {
461 18 : std::lock_guard lk(mutex_);
462 18 : auto request = trustRequests_.find(from);
463 18 : if (request == trustRequests_.end()
464 18 : || (!conversationId.empty() && request->second.conversationId != conversationId))
465 5 : return false;
466 13 : const auto invited = request->second.invited != TimePoint {} ? request->second.invited : request->second.received;
467 13 : if (invited > declined)
468 3 : return false;
469 10 : trustRequests_.erase(request);
470 10 : saveTrustRequests();
471 10 : return true;
472 18 : }
473 :
474 : void
475 20 : ContactList::loadKnownDevices()
476 : {
477 20 : auto& certStore = jami::Manager::instance().certStore(accountId_);
478 : try {
479 : // read file
480 21 : auto file = fileutils::loadFile("knownDevices", path_);
481 : // load values
482 19 : msgpack::object_handle oh = msgpack::unpack((const char*) file.data(), file.size());
483 :
484 19 : std::map<dht::PkId, KnownDeviceData> knownDevices;
485 19 : oh.get().convert(knownDevices);
486 38 : for (const auto& d : knownDevices) {
487 19 : if (auto crt = certStore.getCertificate(d.first.toString())) {
488 19 : auto lastSync = clock::time_point(std::chrono::milliseconds(d.second.lastSyncMs));
489 19 : if (not foundAccountDevice(crt, d.second.name, lastSync, false))
490 0 : JAMI_WARNING("[Account {}] [Contacts] Unable to add device {}", accountId_, d.first);
491 : } else {
492 0 : JAMI_WARNING("[Account {}] [Contacts] Unable to find certificate for device {}", accountId_, d.first);
493 19 : }
494 : }
495 19 : if (not knownDevices.empty()) {
496 19 : callbacks_.devicesChanged(getKnownDevices());
497 : }
498 20 : } catch (const std::exception& e) {
499 1 : JAMI_WARNING("[Account {}] [Contacts] Error loading devices: {}", accountId_, e.what());
500 1 : return;
501 1 : }
502 : }
503 :
504 : void
505 3387 : ContactList::saveKnownDevices() const
506 : {
507 3387 : std::ofstream file(path_ / "knownDevices", std::ios::trunc | std::ios::binary);
508 :
509 3387 : std::map<dht::PkId, KnownDeviceData> devices;
510 1016413 : for (const auto& id : knownDevices_) {
511 1013026 : auto lastSyncMs = std::chrono::duration_cast<std::chrono::milliseconds>(id.second.last_sync.time_since_epoch())
512 1013026 : .count();
513 1013026 : devices.emplace(id.first, KnownDeviceData {id.second.name, lastSyncMs});
514 : }
515 :
516 3387 : msgpack::pack(file, devices);
517 3387 : }
518 :
519 : void
520 2000 : ContactList::foundAccountDevice(const dht::PkId& device, const std::string& name, const time_point& updated)
521 : {
522 2000 : std::unique_lock lock(devicesMutex_);
523 : // insert device
524 2000 : auto it = knownDevices_.emplace(device, KnownDevice {{}, name, updated});
525 2000 : if (it.second) {
526 2000 : JAMI_LOG("[Account {}] [Contacts] Found account device: {} {}", accountId_, name, device);
527 2000 : saveKnownDevices();
528 2000 : lock.unlock();
529 2000 : callbacks_.devicesChanged(getKnownDevices());
530 : } else {
531 : // update device name
532 0 : if (not name.empty() and it.first->second.name != name) {
533 0 : JAMI_LOG("[Account {}] [Contacts] Updating device name: {} {}", accountId_, name, device);
534 0 : it.first->second.name = name;
535 0 : saveKnownDevices();
536 0 : lock.unlock();
537 0 : callbacks_.devicesChanged(getKnownDevices());
538 : }
539 : }
540 2000 : }
541 :
542 : bool
543 2450 : ContactList::foundAccountDevice(const std::shared_ptr<dht::crypto::Certificate>& crt,
544 : const std::string& name,
545 : const time_point& updated,
546 : bool notify)
547 : {
548 2450 : if (not crt)
549 0 : return false;
550 :
551 2450 : auto id = crt->getLongId();
552 :
553 : // match certificate chain
554 2450 : auto verifyResult = accountTrust_.verify(*crt);
555 2450 : if (not verifyResult) {
556 0 : JAMI_WARNING("[Account {}] [Contacts] Found invalid account device: {:s}: {:s}",
557 : accountId_,
558 : id,
559 : verifyResult.toString());
560 0 : return false;
561 : }
562 :
563 : // insert device
564 2450 : std::unique_lock lock(devicesMutex_);
565 2450 : auto it = knownDevices_.emplace(id, KnownDevice {crt, name, updated});
566 2450 : if (it.second) {
567 1111 : JAMI_LOG("[Account {}] [Contacts] Found account device: {} {}", accountId_, name, id);
568 1111 : jami::Manager::instance().certStore(accountId_).pinCertificate(crt);
569 1111 : if (crt->ocspResponse) {
570 0 : unsigned int status = crt->ocspResponse->getCertificateStatus();
571 0 : if (status == GNUTLS_OCSP_CERT_REVOKED) {
572 0 : JAMI_ERROR("[Account {}] Certificate {} has revoked OCSP status", accountId_, id);
573 0 : trust_->setCertificateStatus(crt, dhtnet::tls::TrustStore::PermissionStatus::BANNED, false);
574 : }
575 : }
576 1111 : if (notify) {
577 1092 : saveKnownDevices();
578 1092 : lock.unlock();
579 1092 : callbacks_.devicesChanged(getKnownDevices());
580 : }
581 : } else {
582 : // update device name
583 1339 : if (not name.empty() and it.first->second.name != name) {
584 68 : JAMI_LOG("[Account {}] [Contacts] updating device name: {} {}", accountId_, name, id);
585 68 : it.first->second.name = name;
586 68 : if (notify) {
587 68 : saveKnownDevices();
588 68 : lock.unlock();
589 68 : callbacks_.devicesChanged(getKnownDevices());
590 : }
591 : }
592 : }
593 2450 : return true;
594 2450 : }
595 :
596 : bool
597 2 : ContactList::removeAccountDevice(const dht::PkId& device)
598 : {
599 2 : std::lock_guard lock(devicesMutex_);
600 2 : if (knownDevices_.erase(device) > 0) {
601 2 : saveKnownDevices();
602 2 : return true;
603 : }
604 0 : return false;
605 2 : }
606 :
607 : void
608 18 : ContactList::setAccountDeviceName(const dht::PkId& device, const std::string& name)
609 : {
610 18 : std::unique_lock lock(devicesMutex_);
611 18 : auto dev = knownDevices_.find(device);
612 18 : if (dev != knownDevices_.end()) {
613 18 : if (dev->second.name != name) {
614 1 : dev->second.name = name;
615 1 : saveKnownDevices();
616 1 : lock.unlock();
617 1 : callbacks_.devicesChanged(getKnownDevices());
618 : }
619 : }
620 18 : }
621 :
622 : std::string
623 1013 : ContactList::getAccountDeviceName(const dht::PkId& device) const
624 : {
625 1013 : std::lock_guard lock(devicesMutex_);
626 1013 : auto dev = knownDevices_.find(device);
627 1013 : if (dev != knownDevices_.end()) {
628 1013 : return dev->second.name;
629 : }
630 0 : return {};
631 1013 : }
632 :
633 : DeviceSync
634 1639 : ContactList::getSyncData() const
635 : {
636 1639 : DeviceSync sync_data;
637 1639 : sync_data.date = clock::now().time_since_epoch().count();
638 : // sync_data.device_name = deviceName_;
639 1639 : sync_data.peers = getContacts();
640 :
641 : static constexpr size_t MAX_TRUST_REQUESTS = 20;
642 1639 : std::lock_guard lk(mutex_);
643 1639 : if (trustRequests_.size() <= MAX_TRUST_REQUESTS)
644 1643 : for (const auto& req : trustRequests_)
645 8 : sync_data.trust_requests.emplace(req.first,
646 8 : TrustRequest {req.second.device,
647 4 : req.second.conversationId,
648 : req.second.received,
649 : {},
650 : req.second.invited});
651 : else {
652 0 : size_t inserted = 0;
653 0 : auto req = trustRequests_.lower_bound(dht::InfoHash::getRandom());
654 0 : while (inserted++ < MAX_TRUST_REQUESTS) {
655 0 : if (req == trustRequests_.end())
656 0 : req = trustRequests_.begin();
657 0 : sync_data.trust_requests.emplace(req->first,
658 0 : TrustRequest {req->second.device,
659 0 : req->second.conversationId,
660 0 : req->second.received,
661 : {},
662 0 : req->second.invited});
663 0 : ++req;
664 : }
665 : }
666 :
667 8728 : for (const auto& dev : getKnownDevices()) {
668 7089 : if (!dev.second.certificate) {
669 5236 : JAMI_WARNING("[Account {}] [Contacts] No certificate found for {}", accountId_, dev.first);
670 5236 : continue;
671 : }
672 1853 : sync_data.devices.emplace(dev.second.certificate->getLongId(), KnownDeviceSync {dev.second.name});
673 1639 : }
674 3278 : return sync_data;
675 1639 : }
676 :
677 : std::map<dht::PkId, KnownDevice>
678 5866 : ContactList::getKnownDevices() const
679 : {
680 5866 : std::lock_guard lock(devicesMutex_);
681 11732 : return knownDevices_;
682 5866 : }
683 :
684 : bool
685 232 : ContactList::recordDeviceSync(const dht::PkId& device, const time_point& receivedAt)
686 : {
687 232 : std::unique_lock lock(devicesMutex_);
688 232 : auto it = knownDevices_.find(device);
689 232 : if (it == knownDevices_.end() || it->second.last_sync >= receivedAt)
690 8 : return false;
691 224 : it->second.last_sync = receivedAt;
692 224 : saveKnownDevices();
693 224 : auto devices = knownDevices_;
694 224 : lock.unlock();
695 224 : callbacks_.devicesChanged(devices);
696 224 : return true;
697 232 : }
698 :
699 : } // namespace jami
|