LCOV - code coverage report
Current view: top level - src/jamidht - contact_list.cpp (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 80.0 % 409 327
Test Date: 2026-07-06 08:25:38 Functions: 66.7 % 87 58

            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
        

Generated by: LCOV version 2.0-1