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 799 : ContactList::ContactList(const std::string& accountId,
35 : const std::shared_ptr<crypto::Certificate>& cert,
36 : const std::filesystem::path& path,
37 799 : OnChangeCallback cb)
38 800 : : path_(path)
39 800 : , callbacks_(std::move(cb))
40 2399 : , 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 171 : ContactList::addContact(const dht::InfoHash& h, bool confirmed, const std::string& conversationId)
87 : {
88 171 : JAMI_WARN("[Contacts] addContact: %s, conversation: %s", h.to_c_str(), conversationId.c_str());
89 171 : auto c = contacts_.find(h);
90 171 : if (c == contacts_.end())
91 100 : c = contacts_.emplace(h, Contact {}).first;
92 71 : else if (c->second.isActive() and c->second.confirmed == confirmed && c->second.conversationId == conversationId)
93 61 : 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 13 : ContactList::updateConversation(const dht::InfoHash& h, const std::string& conversationId)
110 : {
111 13 : auto c = contacts_.find(h);
112 13 : if (c != contacts_.end() && c->second.conversationId != conversationId) {
113 13 : c->second.conversationId = conversationId;
114 13 : saveContacts();
115 : }
116 13 : }
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 866 : ContactList::getContactDetails(const dht::InfoHash& h) const
161 : {
162 866 : const auto c = contacts_.find(h);
163 865 : if (c == std::end(contacts_)) {
164 434 : JAMI_WARN("[Contacts] Contact '%s' not found", h.to_c_str());
165 434 : return {};
166 : }
167 :
168 431 : auto details = c->second.toMap();
169 431 : if (not details.empty())
170 431 : details["id"] = c->first.toString();
171 :
172 432 : return details;
173 432 : }
174 :
175 : const std::map<dht::InfoHash, Contact>&
176 1764 : ContactList::getContacts() const
177 : {
178 1764 : 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 67 : ContactList::updateContact(const dht::InfoHash& id, const Contact& contact, bool emit)
194 : {
195 67 : if (not id) {
196 0 : JAMI_ERR("[Contacts] updateContact: invalid contact ID");
197 0 : return;
198 : }
199 67 : bool stateChanged {false};
200 67 : auto c = contacts_.find(id);
201 67 : 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 54 : stateChanged = c->second.update(contact);
208 : }
209 67 : 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 13 : trust_->setCertificateStatus(id.toString(), dhtnet::tls::TrustStore::PermissionStatus::ALLOWED);
217 13 : if (emit)
218 13 : callbacks_.contactAdded(id.toString(), c->second.confirmed);
219 : } else {
220 7 : if (c->second.banned)
221 3 : trust_->setCertificateStatus(id.toString(),
222 : dhtnet::tls::TrustStore::PermissionStatus::BANNED);
223 7 : if (emit)
224 7 : 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 1318 : ContactList::saveContacts() const
250 : {
251 2636 : std::ofstream file(path_ / "contacts", std::ios::trunc | std::ios::binary);
252 1318 : msgpack::pack(file, contacts_);
253 1318 : }
254 :
255 : void
256 129 : ContactList::saveTrustRequests() const
257 : {
258 : // mutex_ MUST BE locked
259 258 : std::ofstream file(path_ / "incomingTrustRequests",
260 258 : std::ios::trunc | std::ios::binary);
261 129 : msgpack::pack(file, trustRequests_);
262 129 : }
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 128 : 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 128 : bool accept = false;
299 : // Check existing contact
300 128 : std::unique_lock lk(mutex_);
301 128 : auto contact = contacts_.find(peer_account);
302 128 : bool active = false;
303 128 : if (contact != contacts_.end()) {
304 : // Banned contact: discard request
305 59 : if (contact->second.isBanned())
306 0 : return false;
307 :
308 59 : if (contact->second.isActive()) {
309 56 : active = true;
310 : // Send confirmation
311 56 : if (not confirm)
312 11 : accept = true;
313 56 : 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 72 : auto req = trustRequests_.find(peer_account);
321 72 : 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 8 : if (received > req->second.received) {
330 2 : req->second.device = peer_device;
331 2 : req->second.conversationId = conversationId;
332 2 : req->second.received = received;
333 2 : req->second.payload = payload;
334 : } else {
335 6 : JAMI_DBG("[Contacts] Ignoring outdated trust request from %s",
336 : peer_account.toString().c_str());
337 : }
338 : }
339 72 : 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 128 : }
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 211 : ContactList::getTrustRequest(const dht::InfoHash& from) const
377 : {
378 : using Map = std::map<std::string, std::string>;
379 211 : std::lock_guard lk(mutex_);
380 211 : auto r = trustRequests_.find(from);
381 211 : if (r == trustRequests_.end())
382 167 : 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 211 : }
389 :
390 : bool
391 197 : ContactList::acceptTrustRequest(const dht::InfoHash& from)
392 : {
393 : // The contact sent us a TR so we are in its contact list
394 197 : std::unique_lock lk(mutex_);
395 197 : auto i = trustRequests_.find(from);
396 197 : if (i == trustRequests_.end())
397 154 : 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 197 : }
406 :
407 : void
408 54 : ContactList::acceptConversation(const std::string& convId, const std::string& deviceId)
409 : {
410 54 : if (callbacks_.acceptConversation)
411 54 : callbacks_.acceptConversation(convId, deviceId);
412 54 : }
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 2944 : ContactList::saveKnownDevices() const
469 : {
470 5888 : std::ofstream file(path_ / "knownDevices", std::ios::trunc | std::ios::binary);
471 :
472 2944 : std::map<dht::PkId, std::pair<std::string, uint64_t>> devices;
473 1010906 : for (const auto& id : knownDevices_)
474 1007968 : devices.emplace(id.first,
475 2015929 : std::make_pair(id.second.name, clock::to_time_t(id.second.last_sync)));
476 :
477 2944 : msgpack::pack(file, devices);
478 2944 : }
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 2004 : ContactList::foundAccountDevice(const std::shared_ptr<dht::crypto::Certificate>& crt,
506 : const std::string& name,
507 : const time_point& updated)
508 : {
509 2004 : if (not crt)
510 0 : return false;
511 :
512 2004 : auto id = crt->getLongId();
513 :
514 : // match certificate chain
515 2004 : auto verifyResult = accountTrust_.verify(*crt);
516 2004 : 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 2004 : auto it = knownDevices_.emplace(id, KnownDevice {crt, name, updated});
524 2004 : 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 1130 : if (not name.empty() and it.first->second.name != name) {
539 201 : JAMI_LOG("[Contacts] updating device name: {} {}", name, id);
540 67 : it.first->second.name = name;
541 67 : saveKnownDevices();
542 67 : callbacks_.devicesChanged(knownDevices_);
543 : }
544 : }
545 2004 : 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 1028 : ContactList::getSyncData() const
583 : {
584 1028 : DeviceSync sync_data;
585 1028 : sync_data.date = clock::now().time_since_epoch().count();
586 : // sync_data.device_name = deviceName_;
587 1028 : sync_data.peers = getContacts();
588 :
589 : static constexpr size_t MAX_TRUST_REQUESTS = 20;
590 1028 : std::lock_guard lk(mutex_);
591 1028 : if (trustRequests_.size() <= MAX_TRUST_REQUESTS)
592 1039 : for (const auto& req : trustRequests_)
593 11 : sync_data.trust_requests.emplace(req.first,
594 22 : TrustRequest {req.second.device,
595 11 : req.second.conversationId,
596 11 : 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 8241 : for (const auto& dev : knownDevices_) {
614 7214 : if (!dev.second.certificate) {
615 17916 : JAMI_WARNING("No certificate found for {}", dev.first);
616 5972 : continue;
617 5972 : }
618 1242 : sync_data.devices.emplace(dev.second.certificate->getLongId(),
619 2483 : KnownDeviceSync {dev.second.name,
620 1242 : dev.second.certificate->getId()});
621 : }
622 2055 : return sync_data;
623 1027 : }
624 :
625 : bool
626 17 : ContactList::syncDevice(const dht::PkId& device, const time_point& syncDate)
627 : {
628 17 : auto it = knownDevices_.find(device);
629 17 : if (it == knownDevices_.end()) {
630 0 : JAMI_WARN("[Contacts] Dropping sync data from unknown device");
631 0 : return false;
632 : }
633 17 : if (it->second.last_sync >= syncDate) {
634 0 : JAMI_DBG("[Contacts] Dropping outdated sync data");
635 0 : return false;
636 : }
637 17 : it->second.last_sync = syncDate;
638 17 : return true;
639 : }
640 :
641 : } // namespace jami
|