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