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