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 839 : ContactList::ContactList(const std::string& accountId,
36 : const std::shared_ptr<crypto::Certificate>& cert,
37 : const std::filesystem::path& path,
38 839 : OnChangeCallback cb)
39 839 : : accountId_(accountId)
40 839 : , path_(path)
41 839 : , callbacks_(std::move(cb))
42 : {
43 839 : if (cert) {
44 839 : trust_ = std::make_unique<dhtnet::tls::TrustStore>(jami::Manager::instance().certStore(accountId_));
45 839 : accountTrust_.add(*cert);
46 : }
47 839 : }
48 :
49 838 : 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 838 : ContactList::setCertificateStatus(const std::shared_ptr<crypto::Certificate>& cert,
82 : dhtnet::tls::TrustStore::PermissionStatus status,
83 : bool local)
84 : {
85 838 : return trust_->setCertificateStatus(cert, status, local);
86 : }
87 :
88 : bool
89 150 : ContactList::addContact(const dht::InfoHash& h, bool confirmed, const std::string& conversationId)
90 : {
91 150 : std::unique_lock lk(mutex_);
92 150 : JAMI_WARNING("[Account {}] [Contacts] addContact: {}, conversation: {}", accountId_, h, conversationId);
93 150 : auto c = contacts_.find(h);
94 150 : if (c == contacts_.end())
95 73 : c = contacts_.emplace(h, Contact {}).first;
96 77 : else if (c->second.isActive() and c->second.confirmed == confirmed && c->second.conversationId == conversationId)
97 72 : return false;
98 78 : 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 78 : c->second.removed = TimePoint {};
103 78 : c->second.conversationId = conversationId;
104 78 : c->second.confirmed |= confirmed;
105 78 : auto hStr = h.toString();
106 78 : trust_->setCertificateStatus(hStr, dhtnet::tls::TrustStore::PermissionStatus::ALLOWED);
107 78 : saveContacts();
108 78 : lk.unlock();
109 78 : callbacks_.contactAdded(hStr, c->second.confirmed);
110 78 : return true;
111 150 : }
112 :
113 : bool
114 15 : ContactList::updateConversation(const dht::InfoHash& h, const std::string& conversationId, bool added)
115 : {
116 15 : std::lock_guard lk(mutex_);
117 15 : auto c = contacts_.find(h);
118 15 : if (c != contacts_.end() && c->second.conversationId != conversationId) {
119 8 : c->second.conversationId = conversationId;
120 8 : if (added) {
121 0 : c->second.added = nowMs();
122 : }
123 8 : saveContacts();
124 8 : return true;
125 : }
126 7 : return false;
127 15 : }
128 :
129 : bool
130 8 : ContactList::removeContact(const dht::InfoHash& h, bool ban)
131 : {
132 8 : std::unique_lock lk(mutex_);
133 8 : JAMI_WARNING("[Account {}] [Contacts] removeContact: {} (banned: {})", accountId_, h, ban);
134 8 : auto c = contacts_.find(h);
135 8 : if (c == contacts_.end())
136 1 : c = contacts_.emplace(h, Contact {}).first;
137 8 : c->second.removed = nowMs();
138 8 : c->second.confirmed = false;
139 8 : c->second.banned = ban;
140 8 : c->second.conversationId = "";
141 8 : auto uri = h.toString();
142 8 : trust_->setCertificateStatus(uri,
143 : ban ? dhtnet::tls::TrustStore::PermissionStatus::BANNED
144 : : dhtnet::tls::TrustStore::PermissionStatus::UNDEFINED);
145 8 : if (trustRequests_.erase(h) > 0)
146 1 : saveTrustRequests();
147 8 : saveContacts();
148 8 : lk.unlock();
149 : #ifdef ENABLE_PLUGIN
150 8 : auto filename = path_.filename().string();
151 8 : jami::Manager::instance().getJamiPluginManager().getChatServicesManager().cleanChatSubjects(filename, uri);
152 : #endif
153 8 : callbacks_.contactRemoved(uri, ban);
154 8 : return true;
155 8 : }
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 4 : ContactList::getContactDetails(const dht::InfoHash& h) const
171 : {
172 4 : std::unique_lock lk(mutex_);
173 4 : const auto c = contacts_.find(h);
174 8 : if (c == std::end(contacts_)) {
175 0 : JAMI_WARNING("[Account {}] [Contacts] Contact '{}' not found", accountId_, h.to_view());
176 0 : return {};
177 : }
178 :
179 4 : auto details = c->second.toMap();
180 4 : if (not details.empty())
181 12 : details["id"] = c->first.toString();
182 :
183 4 : return details;
184 4 : }
185 :
186 : std::optional<Contact>
187 1125 : ContactList::getContactInfo(const dht::InfoHash& h) const
188 : {
189 1125 : const auto c = contacts_.find(h);
190 2250 : if (c == std::end(contacts_)) {
191 788 : JAMI_WARNING("[Account {}] [Contacts] Contact '{}' not found", accountId_, h.to_view());
192 788 : return {};
193 : }
194 337 : return c->second;
195 : }
196 :
197 : const std::map<dht::InfoHash, Contact>&
198 1741 : ContactList::getContacts() const
199 : {
200 1741 : return contacts_;
201 : }
202 :
203 : void
204 820 : ContactList::setContacts(const std::map<dht::InfoHash, Contact>& contacts)
205 : {
206 820 : JAMI_LOG("[Account {}] [Contacts] replacing contact list (old: {} new: {})",
207 : accountId_,
208 : contacts_.size(),
209 : contacts.size());
210 820 : contacts_ = contacts;
211 820 : saveContacts();
212 : // Set contacts is used when creating a new device, so just announce new contacts
213 823 : for (auto& peer : contacts)
214 3 : if (peer.second.isActive())
215 3 : callbacks_.contactAdded(peer.first.toString(), peer.second.confirmed);
216 820 : }
217 :
218 : void
219 67 : ContactList::updateContact(const dht::InfoHash& id, const Contact& contact, bool emit)
220 : {
221 67 : if (not id) {
222 0 : JAMI_ERROR("[Account {}] [Contacts] updateContact: invalid contact ID", accountId_);
223 0 : return;
224 : }
225 67 : bool stateChanged {false};
226 67 : auto c = contacts_.find(id);
227 67 : if (c == contacts_.end()) {
228 : // JAMI_LOG("[Contacts] New contact: {}", id);
229 11 : c = contacts_.emplace(id, contact).first;
230 11 : stateChanged = c->second.isActive() or c->second.isBanned();
231 : } else {
232 : // JAMI_LOG("[Contacts] Updated contact: {}", id);
233 56 : stateChanged = c->second.update(contact);
234 : }
235 67 : if (stateChanged) {
236 : {
237 16 : std::lock_guard lk(mutex_);
238 16 : if (trustRequests_.erase(id) > 0)
239 5 : 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 13 : 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 62 : ContactList::contactsFromPath(const std::filesystem::path& path)
256 : {
257 62 : std::map<dht::InfoHash, Contact> contacts;
258 : try {
259 62 : std::lock_guard fileLock(dhtnet::fileutils::getFileLock(path / "contacts"));
260 62 : auto file = fileutils::loadFile("contacts", path);
261 62 : msgpack::object_handle oh = msgpack::unpack((const char*) file.data(), file.size());
262 62 : oh.get().convert(contacts);
263 62 : } catch (const std::exception& e) {
264 0 : JAMI_WARNING("[Contacts] Error loading contacts from {}: {}", path.string(), e.what());
265 0 : }
266 62 : 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 1015 : ContactList::saveContacts() const
280 : {
281 1015 : JAMI_LOG("[Account {}] [Contacts] saving {} contacts", accountId_, contacts_.size());
282 1015 : std::lock_guard fileLock(dhtnet::fileutils::getFileLock(path_ / "contacts"));
283 1015 : std::ofstream file(path_ / "contacts", std::ios::trunc | std::ios::binary);
284 1015 : msgpack::pack(file, contacts_);
285 1015 : }
286 :
287 : void
288 93 : ContactList::saveTrustRequests() const
289 : {
290 : // mutex_ MUST BE locked
291 93 : std::ofstream file(path_ / "incomingTrustRequests", std::ios::trunc | std::ios::binary);
292 93 : msgpack::pack(file, trustRequests_);
293 93 : }
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 150 : 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 150 : bool accept = false;
333 : // Check existing contact
334 150 : std::unique_lock lk(mutex_);
335 150 : auto contact = contacts_.find(peer_account);
336 150 : bool active = false;
337 150 : if (contact != contacts_.end()) {
338 : // Banned contact: discard request
339 94 : if (contact->second.isBanned())
340 0 : return false;
341 :
342 94 : if (contact->second.isActive()) {
343 94 : active = true;
344 : // Send confirmation
345 94 : if (not confirm)
346 31 : accept = true;
347 94 : if (not contact->second.confirmed) {
348 34 : contact->second.confirmed = true;
349 34 : saveContacts();
350 34 : callbacks_.contactAdded(peer_account.toString(), true);
351 : }
352 : }
353 : }
354 150 : if (not active) {
355 56 : auto req = trustRequests_.find(peer_account);
356 56 : if (req == trustRequests_.end()) {
357 : // Add trust request
358 45 : req = trustRequests_
359 45 : .emplace(peer_account, TrustRequest {peer_device, conversationId, received, payload, invited})
360 : .first;
361 : } else {
362 11 : auto incomingTimestamp = invited != TimePoint {} ? invited : received;
363 11 : auto existingTimestamp = req->second.invited != TimePoint {} ? req->second.invited
364 0 : : req->second.received;
365 :
366 11 : if (incomingTimestamp > existingTimestamp) {
367 1 : req->second.device = peer_device;
368 1 : req->second.conversationId = conversationId;
369 1 : req->second.received = received;
370 1 : req->second.payload = payload;
371 1 : req->second.invited = invited;
372 : } else {
373 10 : JAMI_LOG("[Account {}] [Contacts] Ignoring outdated trust request from {}", accountId_, peer_account);
374 : }
375 : }
376 56 : saveTrustRequests();
377 : }
378 150 : lk.unlock();
379 : // Note: call JamiAccount's callback to build ConversationRequest anyway
380 150 : if (!confirm)
381 86 : callbacks_.trustRequest(peer_account.toString(),
382 : conversationId,
383 86 : std::move(payload),
384 : received,
385 : invited);
386 64 : else if (active) {
387 : // Only notify if confirmed + not removed
388 63 : callbacks_.onConfirmation(peer_account.toString(), conversationId);
389 : }
390 150 : return accept;
391 150 : }
392 :
393 : /* trust requests */
394 :
395 : std::vector<std::map<std::string, std::string>>
396 736 : ContactList::getTrustRequests() const
397 : {
398 : using Map = std::map<std::string, std::string>;
399 736 : std::vector<Map> ret;
400 736 : std::lock_guard lk(mutex_);
401 736 : ret.reserve(trustRequests_.size());
402 741 : for (const auto& r : trustRequests_) {
403 5 : ret.emplace_back(
404 40 : Map {{libjami::Account::TrustRequest::FROM, r.first.toString()},
405 10 : {libjami::Account::TrustRequest::RECEIVED, std::to_string(toSecondsSinceEpoch(r.second.received))},
406 5 : {libjami::Account::TrustRequest::CONVERSATIONID, r.second.conversationId},
407 : {libjami::Account::TrustRequest::PAYLOAD,
408 40 : std::string(r.second.payload.begin(), r.second.payload.end())}});
409 : }
410 1472 : return ret;
411 751 : }
412 :
413 : std::map<std::string, std::string>
414 206 : ContactList::getTrustRequest(const dht::InfoHash& from) const
415 : {
416 : using Map = std::map<std::string, std::string>;
417 206 : std::lock_guard lk(mutex_);
418 206 : auto r = trustRequests_.find(from);
419 206 : if (r == trustRequests_.end())
420 175 : return {};
421 31 : return Map {{libjami::Account::TrustRequest::FROM, r->first.toString()},
422 62 : {libjami::Account::TrustRequest::RECEIVED, std::to_string(toSecondsSinceEpoch(r->second.received))},
423 62 : {libjami::Account::TrustRequest::CONVERSATIONID, r->second.conversationId},
424 : {libjami::Account::TrustRequest::PAYLOAD,
425 279 : std::string(r->second.payload.begin(), r->second.payload.end())}};
426 330 : }
427 :
428 : bool
429 198 : ContactList::acceptTrustRequest(const dht::InfoHash& from)
430 : {
431 : // The contact sent us a TR so we are in its contact list
432 198 : std::unique_lock lk(mutex_);
433 198 : auto i = trustRequests_.find(from);
434 198 : if (i == trustRequests_.end())
435 167 : return false;
436 31 : auto convId = i->second.conversationId;
437 : // Clear trust request
438 31 : trustRequests_.erase(i);
439 31 : saveTrustRequests();
440 31 : lk.unlock();
441 31 : addContact(from, true, convId);
442 31 : return true;
443 198 : }
444 :
445 : void
446 62 : ContactList::acceptConversation(const std::string& convId, const std::string& deviceId)
447 : {
448 62 : if (callbacks_.acceptConversation)
449 62 : callbacks_.acceptConversation(convId, deviceId);
450 62 : }
451 :
452 : bool
453 0 : ContactList::discardTrustRequest(const dht::InfoHash& from)
454 : {
455 0 : std::lock_guard lk(mutex_);
456 0 : if (trustRequests_.erase(from) > 0) {
457 0 : saveTrustRequests();
458 0 : return true;
459 : }
460 0 : return false;
461 0 : }
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 2961 : ContactList::saveKnownDevices() const
495 : {
496 2961 : std::ofstream file(path_ / "knownDevices", std::ios::trunc | std::ios::binary);
497 :
498 2961 : std::map<dht::PkId, KnownDeviceData> devices;
499 1011012 : for (const auto& id : knownDevices_) {
500 1008059 : auto lastSyncMs = std::chrono::duration_cast<std::chrono::milliseconds>(id.second.last_sync.time_since_epoch())
501 1008070 : .count();
502 1008071 : devices.emplace(id.first, KnownDeviceData {id.second.name, lastSyncMs});
503 : }
504 :
505 2961 : msgpack::pack(file, devices);
506 2961 : }
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 2039 : 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 2039 : if (not crt)
535 0 : return false;
536 :
537 2039 : auto id = crt->getLongId();
538 :
539 : // match certificate chain
540 2039 : auto verifyResult = accountTrust_.verify(*crt);
541 2039 : 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 2039 : auto it = knownDevices_.emplace(id, KnownDevice {crt, name, updated});
551 2039 : if (it.second) {
552 912 : JAMI_LOG("[Account {}] [Contacts] Found account device: {} {}", accountId_, name, id);
553 912 : jami::Manager::instance().certStore(accountId_).pinCertificate(crt);
554 912 : 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 912 : if (notify) {
562 894 : saveKnownDevices();
563 894 : callbacks_.devicesChanged(knownDevices_);
564 : }
565 : } else {
566 : // update device name
567 1127 : 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 2039 : 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 820 : ContactList::getAccountDeviceName(const dht::PkId& device) const
604 : {
605 820 : auto dev = knownDevices_.find(device);
606 820 : if (dev != knownDevices_.end()) {
607 820 : return dev->second.name;
608 : }
609 0 : return {};
610 : }
611 :
612 : DeviceSync
613 1004 : ContactList::getSyncData() const
614 : {
615 1004 : DeviceSync sync_data;
616 1004 : sync_data.date = clock::now().time_since_epoch().count();
617 : // sync_data.device_name = deviceName_;
618 1004 : sync_data.peers = getContacts();
619 :
620 : static constexpr size_t MAX_TRUST_REQUESTS = 20;
621 1003 : std::lock_guard lk(mutex_);
622 1003 : if (trustRequests_.size() <= MAX_TRUST_REQUESTS)
623 1006 : for (const auto& req : trustRequests_)
624 6 : sync_data.trust_requests.emplace(req.first,
625 6 : TrustRequest {req.second.device,
626 3 : 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 6882 : for (const auto& dev : knownDevices_) {
647 5877 : if (!dev.second.certificate) {
648 4681 : JAMI_WARNING("[Account {}] [Contacts] No certificate found for {}", accountId_, dev.first);
649 4683 : continue;
650 : }
651 1195 : sync_data.devices.emplace(dev.second.certificate->getLongId(), KnownDeviceSync {dev.second.name});
652 : }
653 2008 : return sync_data;
654 1004 : }
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
|