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