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