Line data Source code
1 : /*
2 : * Copyright (C) 2004-2024 Savoir-faire Linux Inc.
3 : * Author : Adrien BĂ©raud <adrien.beraud@savoirfairelinux.com>
4 : *
5 : * This program is free software; you can redistribute it and/or modify
6 : * it under the terms of the GNU General Public License as published by
7 : * the Free Software Foundation; either version 3 of the License, or
8 : * (at your option) any later version.
9 : *
10 : * This program is distributed in the hope that it will be useful,
11 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 : * GNU General Public License for more details.
14 : *
15 : * You should have received a copy of the GNU General Public License
16 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 : */
18 : #include "contact_list.h"
19 : #include "logger.h"
20 : #include "jamiaccount.h"
21 : #include "fileutils.h"
22 :
23 : #include "manager.h"
24 : #ifdef ENABLE_PLUGIN
25 : #include "plugin/jamipluginmanager.h"
26 : #endif
27 :
28 : #include "account_const.h"
29 :
30 : #include <fstream>
31 : #include <gnutls/ocsp.h>
32 :
33 : namespace jami {
34 :
35 791 : ContactList::ContactList(const std::string& accountId,
36 : const std::shared_ptr<crypto::Certificate>& cert,
37 : const std::filesystem::path& path,
38 791 : OnChangeCallback cb)
39 791 : : path_(path)
40 791 : , callbacks_(std::move(cb))
41 2373 : , accountId_(accountId)
42 : {
43 791 : if (cert) {
44 791 : trust_ = std::make_unique<dhtnet::tls::TrustStore>(jami::Manager::instance().certStore(accountId_));
45 791 : accountTrust_.add(*cert);
46 : }
47 791 : }
48 :
49 721 : ContactList::~ContactList() {}
50 :
51 : void
52 16 : ContactList::load()
53 : {
54 16 : loadContacts();
55 16 : loadTrustRequests();
56 16 : loadKnownDevices();
57 16 : }
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,
69 : const dhtnet::tls::TrustStore::PermissionStatus status)
70 : {
71 89 : if (contacts_.find(dht::InfoHash(cert_id)) != contacts_.end()) {
72 3 : JAMI_DBG("Can't set certificate status for existing contacts %s", cert_id.c_str());
73 3 : return false;
74 : }
75 86 : return trust_->setCertificateStatus(cert_id, status);
76 : }
77 :
78 : bool
79 0 : ContactList::setCertificateStatus(const std::shared_ptr<crypto::Certificate>& cert,
80 : dhtnet::tls::TrustStore::PermissionStatus status,
81 : bool local)
82 : {
83 0 : return trust_->setCertificateStatus(cert, status, local);
84 : }
85 :
86 : bool
87 168 : ContactList::addContact(const dht::InfoHash& h, bool confirmed, const std::string& conversationId)
88 : {
89 168 : JAMI_WARN("[Contacts] addContact: %s, conversation: %s", h.to_c_str(), conversationId.c_str());
90 168 : auto c = contacts_.find(h);
91 168 : if (c == contacts_.end())
92 98 : c = contacts_.emplace(h, Contact {}).first;
93 70 : else if (c->second.isActive() and c->second.confirmed == confirmed && c->second.conversationId == conversationId)
94 60 : return false;
95 108 : c->second.added = std::time(nullptr);
96 : // NOTE: because we can re-add a contact after removing it
97 : // we should reset removed (as not removed anymore). This fix isActive()
98 : // if addContact is called just after removeContact during the same second
99 108 : c->second.removed = 0;
100 108 : c->second.conversationId = conversationId;
101 108 : c->second.confirmed |= confirmed;
102 108 : auto hStr = h.toString();
103 108 : trust_->setCertificateStatus(hStr, dhtnet::tls::TrustStore::PermissionStatus::ALLOWED);
104 108 : saveContacts();
105 108 : callbacks_.contactAdded(hStr, c->second.confirmed);
106 108 : return true;
107 108 : }
108 :
109 : void
110 13 : ContactList::updateConversation(const dht::InfoHash& h, const std::string& conversationId)
111 : {
112 13 : auto c = contacts_.find(h);
113 13 : if (c != contacts_.end() && c->second.conversationId != conversationId) {
114 13 : c->second.conversationId = conversationId;
115 13 : saveContacts();
116 : }
117 13 : }
118 :
119 : bool
120 20 : ContactList::removeContact(const dht::InfoHash& h, bool ban)
121 : {
122 20 : std::unique_lock lk(mutex_);
123 20 : JAMI_WARN("[Contacts] removeContact: %s", h.to_c_str());
124 20 : auto c = contacts_.find(h);
125 20 : if (c == contacts_.end())
126 4 : c = contacts_.emplace(h, Contact {}).first;
127 20 : c->second.removed = std::time(nullptr);
128 20 : c->second.confirmed = false;
129 20 : c->second.banned = ban;
130 20 : auto uri = h.toString();
131 20 : trust_->setCertificateStatus(uri,
132 : ban ? dhtnet::tls::TrustStore::PermissionStatus::BANNED
133 : : dhtnet::tls::TrustStore::PermissionStatus::UNDEFINED);
134 20 : if (trustRequests_.erase(h) > 0)
135 3 : saveTrustRequests();
136 20 : saveContacts();
137 20 : lk.unlock();
138 : #ifdef ENABLE_PLUGIN
139 20 : auto filename = path_.filename().string();
140 20 : jami::Manager::instance()
141 20 : .getJamiPluginManager()
142 20 : .getChatServicesManager()
143 20 : .cleanChatSubjects(filename, uri);
144 : #endif
145 20 : callbacks_.contactRemoved(uri, ban);
146 20 : return true;
147 20 : }
148 :
149 : bool
150 0 : ContactList::removeContactConversation(const dht::InfoHash& h)
151 : {
152 0 : auto c = contacts_.find(h);
153 0 : if (c == contacts_.end())
154 0 : return false;
155 0 : c->second.conversationId = "";
156 0 : saveContacts();
157 0 : return true;
158 : }
159 :
160 : std::map<std::string, std::string>
161 750 : ContactList::getContactDetails(const dht::InfoHash& h) const
162 : {
163 750 : const auto c = contacts_.find(h);
164 750 : if (c == std::end(contacts_)) {
165 363 : JAMI_WARN("[Contacts] contact '%s' not found", h.to_c_str());
166 363 : return {};
167 : }
168 :
169 387 : auto details = c->second.toMap();
170 387 : if (not details.empty())
171 387 : details["id"] = c->first.toString();
172 :
173 387 : return details;
174 387 : }
175 :
176 : const std::map<dht::InfoHash, Contact>&
177 1820 : ContactList::getContacts() const
178 : {
179 1820 : return contacts_;
180 : }
181 :
182 : void
183 774 : ContactList::setContacts(const std::map<dht::InfoHash, Contact>& contacts)
184 : {
185 774 : contacts_ = contacts;
186 774 : saveContacts();
187 : // Set contacts is used when creating a new device, so just announce new contacts
188 777 : for (auto& peer : contacts)
189 3 : if (peer.second.isActive())
190 3 : callbacks_.contactAdded(peer.first.toString(), peer.second.confirmed);
191 774 : }
192 :
193 : void
194 75 : ContactList::updateContact(const dht::InfoHash& id, const Contact& contact, bool emit)
195 : {
196 75 : if (not id) {
197 0 : JAMI_ERR("[Contacts] updateContact: invalid contact ID");
198 0 : return;
199 : }
200 75 : bool stateChanged {false};
201 75 : auto c = contacts_.find(id);
202 75 : if (c == contacts_.end()) {
203 : // JAMI_DBG("[Contacts] new contact: %s", id.toString().c_str());
204 13 : c = contacts_.emplace(id, contact).first;
205 13 : stateChanged = c->second.isActive() or c->second.isBanned();
206 : } else {
207 62 : JAMI_DBG("[Contacts] updated contact: %s", id.toString().c_str());
208 62 : stateChanged = c->second.update(contact);
209 : }
210 75 : if (stateChanged) {
211 : {
212 20 : std::lock_guard lk(mutex_);
213 20 : if (trustRequests_.erase(id) > 0)
214 7 : saveTrustRequests();
215 20 : }
216 20 : if (c->second.isActive()) {
217 13 : trust_->setCertificateStatus(id.toString(), dhtnet::tls::TrustStore::PermissionStatus::ALLOWED);
218 13 : if (emit)
219 13 : callbacks_.contactAdded(id.toString(), c->second.confirmed);
220 : } else {
221 7 : if (c->second.banned)
222 3 : trust_->setCertificateStatus(id.toString(),
223 : dhtnet::tls::TrustStore::PermissionStatus::BANNED);
224 7 : if (emit)
225 7 : callbacks_.contactRemoved(id.toString(), c->second.banned);
226 : }
227 : }
228 : }
229 :
230 : void
231 16 : ContactList::loadContacts()
232 : {
233 16 : decltype(contacts_) contacts;
234 : try {
235 : // read file
236 16 : auto file = fileutils::loadFile("contacts", path_);
237 : // load values
238 16 : msgpack::object_handle oh = msgpack::unpack((const char*) file.data(), file.size());
239 16 : oh.get().convert(contacts);
240 16 : } catch (const std::exception& e) {
241 0 : JAMI_WARN("[Contacts] error loading contacts: %s", e.what());
242 0 : return;
243 0 : }
244 :
245 16 : for (auto& peer : contacts)
246 0 : updateContact(peer.first, peer.second, false);
247 16 : }
248 :
249 : void
250 1442 : ContactList::saveContacts() const
251 : {
252 2883 : std::ofstream file(path_ / "contacts", std::ios::trunc | std::ios::binary);
253 1442 : msgpack::pack(file, contacts_);
254 1442 : }
255 :
256 : void
257 126 : ContactList::saveTrustRequests() const
258 : {
259 : // mutex_ MUST BE locked
260 252 : std::ofstream file(path_ / "incomingTrustRequests",
261 252 : std::ios::trunc | std::ios::binary);
262 126 : msgpack::pack(file, trustRequests_);
263 126 : }
264 :
265 : void
266 16 : ContactList::loadTrustRequests()
267 : {
268 16 : if (!std::filesystem::is_regular_file(fileutils::getFullPath(path_, "incomingTrustRequests")))
269 16 : return;
270 0 : std::map<dht::InfoHash, TrustRequest> requests;
271 : try {
272 : // read file
273 0 : auto file = fileutils::loadFile("incomingTrustRequests", path_);
274 : // load values
275 0 : msgpack::object_handle oh = msgpack::unpack((const char*) file.data(), file.size());
276 0 : oh.get().convert(requests);
277 0 : } catch (const std::exception& e) {
278 0 : JAMI_WARN("[Contacts] error loading trust requests: %s", e.what());
279 0 : return;
280 0 : }
281 :
282 0 : for (auto& tr : requests)
283 0 : onTrustRequest(tr.first,
284 0 : tr.second.device,
285 : tr.second.received,
286 : false,
287 0 : tr.second.conversationId,
288 0 : std::move(tr.second.payload));
289 0 : }
290 :
291 : bool
292 130 : ContactList::onTrustRequest(const dht::InfoHash& peer_account,
293 : const std::shared_ptr<dht::crypto::PublicKey>& peer_device,
294 : time_t received,
295 : bool confirm,
296 : const std::string& conversationId,
297 : std::vector<uint8_t>&& payload)
298 : {
299 130 : bool accept = false;
300 : // Check existing contact
301 130 : std::unique_lock lk(mutex_);
302 130 : auto contact = contacts_.find(peer_account);
303 130 : bool active = false;
304 130 : if (contact != contacts_.end()) {
305 : // Banned contact: discard request
306 60 : if (contact->second.isBanned())
307 0 : return false;
308 :
309 60 : if (contact->second.isActive()) {
310 59 : active = true;
311 : // Send confirmation
312 59 : if (not confirm)
313 14 : accept = true;
314 59 : if (not contact->second.confirmed) {
315 43 : contact->second.confirmed = true;
316 43 : callbacks_.contactAdded(peer_account.toString(), true);
317 : }
318 : }
319 : }
320 130 : if (not active) {
321 71 : auto req = trustRequests_.find(peer_account);
322 71 : if (req == trustRequests_.end()) {
323 : // Add trust request
324 61 : req = trustRequests_
325 61 : .emplace(peer_account,
326 122 : TrustRequest {peer_device, conversationId, received, payload})
327 : .first;
328 : } else {
329 : // Update trust request
330 10 : if (received < req->second.received) {
331 0 : req->second.device = peer_device;
332 0 : req->second.received = received;
333 0 : req->second.payload = payload;
334 : } else {
335 10 : JAMI_DBG("[Contacts] Ignoring outdated trust request from %s",
336 : peer_account.toString().c_str());
337 : }
338 : }
339 71 : saveTrustRequests();
340 : }
341 130 : lk.unlock();
342 : // Note: call JamiAccount's callback to build ConversationRequest anyway
343 130 : if (!confirm)
344 85 : callbacks_.trustRequest(peer_account.toString(),
345 : conversationId,
346 85 : 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 130 : return accept;
353 130 : }
354 :
355 : /* trust requests */
356 :
357 : std::vector<std::map<std::string, std::string>>
358 696 : ContactList::getTrustRequests() const
359 : {
360 : using Map = std::map<std::string, std::string>;
361 696 : std::vector<Map> ret;
362 696 : std::lock_guard lk(mutex_);
363 696 : ret.reserve(trustRequests_.size());
364 709 : for (const auto& r : trustRequests_) {
365 13 : ret.emplace_back(
366 130 : Map {{libjami::Account::TrustRequest::FROM, r.first.toString()},
367 26 : {libjami::Account::TrustRequest::RECEIVED, std::to_string(r.second.received)},
368 13 : {libjami::Account::TrustRequest::CONVERSATIONID, r.second.conversationId},
369 : {libjami::Account::TrustRequest::PAYLOAD,
370 91 : std::string(r.second.payload.begin(), r.second.payload.end())}});
371 : }
372 1392 : return ret;
373 696 : }
374 :
375 : std::map<std::string, std::string>
376 207 : ContactList::getTrustRequest(const dht::InfoHash& from) const
377 : {
378 : using Map = std::map<std::string, std::string>;
379 207 : std::lock_guard lk(mutex_);
380 207 : auto r = trustRequests_.find(from);
381 207 : if (r == trustRequests_.end())
382 164 : return {};
383 86 : return Map {{libjami::Account::TrustRequest::FROM, r->first.toString()},
384 86 : {libjami::Account::TrustRequest::RECEIVED, std::to_string(r->second.received)},
385 43 : {libjami::Account::TrustRequest::CONVERSATIONID, r->second.conversationId},
386 : {libjami::Account::TrustRequest::PAYLOAD,
387 301 : std::string(r->second.payload.begin(), r->second.payload.end())}};
388 207 : }
389 :
390 : bool
391 193 : ContactList::acceptTrustRequest(const dht::InfoHash& from)
392 : {
393 : // The contact sent us a TR so we are in its contact list
394 193 : std::unique_lock lk(mutex_);
395 193 : auto i = trustRequests_.find(from);
396 193 : if (i == trustRequests_.end())
397 151 : return false;
398 42 : auto convId = i->second.conversationId;
399 : // Clear trust request
400 42 : trustRequests_.erase(i);
401 42 : saveTrustRequests();
402 42 : lk.unlock();
403 42 : addContact(from, true, convId);
404 42 : return true;
405 193 : }
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] can't add device %s", d.first.toString().c_str());
440 : } else {
441 0 : JAMI_WARN("[Contacts] can't 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] can't 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 2937 : ContactList::saveKnownDevices() const
469 : {
470 5874 : std::ofstream file(path_ / "knownDevices", std::ios::trunc | std::ios::binary);
471 :
472 2937 : std::map<dht::PkId, std::pair<std::string, uint64_t>> devices;
473 1011018 : for (const auto& id : knownDevices_)
474 1008081 : devices.emplace(id.first,
475 2016162 : std::make_pair(id.second.name, clock::to_time_t(id.second.last_sync)));
476 :
477 2937 : msgpack::pack(file, devices);
478 2937 : }
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 2143 : ContactList::foundAccountDevice(const std::shared_ptr<dht::crypto::Certificate>& crt,
506 : const std::string& name,
507 : const time_point& updated)
508 : {
509 2143 : if (not crt)
510 0 : return false;
511 :
512 2143 : auto id = crt->getLongId();
513 :
514 : // match certificate chain
515 2143 : auto verifyResult = accountTrust_.verify(*crt);
516 2143 : if (not verifyResult) {
517 0 : JAMI_WARN("[Contacts] Found invalid account device: %s: %s",
518 : id.toString().c_str(),
519 : verifyResult.toString().c_str());
520 0 : return false;
521 : }
522 :
523 : // insert device
524 2143 : auto it = knownDevices_.emplace(id, KnownDevice {crt, name, updated});
525 2142 : if (it.second) {
526 866 : JAMI_DBG("[Contacts] Found account device: %s %s", name.c_str(), id.toString().c_str());
527 866 : jami::Manager::instance().certStore(accountId_).pinCertificate(crt);
528 866 : if (crt->ocspResponse) {
529 0 : unsigned int status = crt->ocspResponse->getCertificateStatus();
530 0 : if (status == GNUTLS_OCSP_CERT_REVOKED) {
531 0 : JAMI_ERR("Certificate %s has revoked OCSP status", id.to_c_str());
532 0 : trust_->setCertificateStatus(crt, dhtnet::tls::TrustStore::PermissionStatus::BANNED, false);
533 : }
534 : }
535 866 : saveKnownDevices();
536 866 : callbacks_.devicesChanged(knownDevices_);
537 : } else {
538 : // update device name
539 1276 : if (not name.empty() and it.first->second.name != name) {
540 68 : JAMI_DBG("[Contacts] updating device name: %s %s", name.c_str(), id.to_c_str());
541 68 : it.first->second.name = name;
542 68 : saveKnownDevices();
543 68 : callbacks_.devicesChanged(knownDevices_);
544 : }
545 : }
546 2143 : return true;
547 : }
548 :
549 : bool
550 2 : ContactList::removeAccountDevice(const dht::PkId& device)
551 : {
552 2 : if (knownDevices_.erase(device) > 0) {
553 2 : saveKnownDevices();
554 2 : return true;
555 : }
556 0 : return false;
557 : }
558 :
559 : void
560 16 : ContactList::setAccountDeviceName(const dht::PkId& device, const std::string& name)
561 : {
562 16 : auto dev = knownDevices_.find(device);
563 16 : if (dev != knownDevices_.end()) {
564 16 : if (dev->second.name != name) {
565 1 : dev->second.name = name;
566 1 : saveKnownDevices();
567 1 : callbacks_.devicesChanged(knownDevices_);
568 : }
569 : }
570 16 : }
571 :
572 : std::string
573 774 : ContactList::getAccountDeviceName(const dht::PkId& device) const
574 : {
575 774 : auto dev = knownDevices_.find(device);
576 774 : if (dev != knownDevices_.end()) {
577 774 : return dev->second.name;
578 : }
579 0 : return {};
580 : }
581 :
582 : DeviceSync
583 1096 : ContactList::getSyncData() const
584 : {
585 1096 : DeviceSync sync_data;
586 1096 : sync_data.date = clock::now().time_since_epoch().count();
587 : // sync_data.device_name = deviceName_;
588 1096 : sync_data.peers = getContacts();
589 :
590 : static constexpr size_t MAX_TRUST_REQUESTS = 20;
591 1096 : std::lock_guard lk(mutex_);
592 1096 : if (trustRequests_.size() <= MAX_TRUST_REQUESTS)
593 1111 : for (const auto& req : trustRequests_)
594 15 : sync_data.trust_requests.emplace(req.first,
595 30 : TrustRequest {req.second.device,
596 15 : req.second.conversationId,
597 15 : req.second.received,
598 : {}});
599 : else {
600 0 : size_t inserted = 0;
601 0 : auto req = trustRequests_.lower_bound(dht::InfoHash::getRandom());
602 0 : while (inserted++ < MAX_TRUST_REQUESTS) {
603 0 : if (req == trustRequests_.end())
604 0 : req = trustRequests_.begin();
605 0 : sync_data.trust_requests.emplace(req->first,
606 0 : TrustRequest {req->second.device,
607 0 : req->second.conversationId,
608 0 : req->second.received,
609 : {}});
610 0 : ++req;
611 : }
612 : }
613 :
614 10488 : for (const auto& dev : knownDevices_) {
615 9392 : if (!dev.second.certificate) {
616 24000 : JAMI_WARNING("No certificate found for {}", dev.first);
617 8000 : continue;
618 8000 : }
619 1391 : sync_data.devices.emplace(dev.second.certificate->getLongId(),
620 2784 : KnownDeviceSync {dev.second.name,
621 1391 : dev.second.certificate->getId()});
622 : }
623 2192 : return sync_data;
624 1096 : }
625 :
626 : bool
627 15 : ContactList::syncDevice(const dht::PkId& device, const time_point& syncDate)
628 : {
629 15 : auto it = knownDevices_.find(device);
630 15 : if (it == knownDevices_.end()) {
631 0 : JAMI_WARN("[Contacts] dropping sync data from unknown device");
632 0 : return false;
633 : }
634 15 : if (it->second.last_sync >= syncDate) {
635 0 : JAMI_DBG("[Contacts] dropping outdated sync data");
636 0 : return false;
637 : }
638 15 : it->second.last_sync = syncDate;
639 15 : return true;
640 : }
641 :
642 : } // namespace jami
|