LCOV - code coverage report
Current view: top level - src/jamidht - presence_manager.cpp (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 90.5 % 158 143
Test Date: 2026-09-13 09:08:58 Functions: 93.8 % 16 15

            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              : 
      18              : #include "presence_manager.h"
      19              : #include "jami_contact.h" // For DeviceAnnouncement
      20              : #include "logger.h"
      21              : 
      22              : namespace jami {
      23              : 
      24          924 : PresenceManager::PresenceManager(const std::shared_ptr<dht::DhtRunner>& dht)
      25          924 :     : dht_(dht)
      26          924 : {}
      27              : 
      28          924 : PresenceManager::~PresenceManager()
      29              : {
      30          924 :     std::lock_guard lock(mutex_);
      31         1253 :     for (auto& [h, buddy] : trackedBuddies_) {
      32          329 :         if (dht_ && dht_->isRunning()) {
      33            0 :             dht_->cancelListen(h, std::move(buddy.listenToken));
      34              :         }
      35              :     }
      36          924 : }
      37              : 
      38              : void
      39         1007 : PresenceManager::trackBuddy(const std::string& uri)
      40              : {
      41         1007 :     dht::InfoHash h(uri);
      42         1007 :     if (!h)
      43            0 :         return;
      44              : 
      45         1007 :     std::lock_guard lock(mutex_);
      46         1007 :     auto it = trackedBuddies_.find(h);
      47         1007 :     if (it == trackedBuddies_.end()) {
      48          944 :         it = trackedBuddies_.emplace(h, TrackedBuddy {h}).first;
      49              :     }
      50              : 
      51         1007 :     it->second.refCount++;
      52         1007 :     if (it->second.refCount == 1) {
      53          944 :         trackPresence(h, it->second);
      54              :     }
      55         1007 : }
      56              : 
      57              : void
      58          654 : PresenceManager::untrackBuddy(const std::string& uri)
      59              : {
      60          654 :     dht::InfoHash h(uri);
      61          654 :     if (!h)
      62            0 :         return;
      63              : 
      64          654 :     std::lock_guard lock(mutex_);
      65          654 :     auto it = trackedBuddies_.find(h);
      66          654 :     if (it != trackedBuddies_.end()) {
      67          654 :         it->second.refCount--;
      68          654 :         if (it->second.refCount <= 0) {
      69          615 :             if (dht_ && dht_->isRunning()) {
      70          609 :                 dht_->cancelListen(h, std::move(it->second.listenToken));
      71              :             }
      72          615 :             trackedBuddies_.erase(it);
      73              :         }
      74              :     }
      75          654 : }
      76              : 
      77              : bool
      78         2592 : PresenceManager::isOnline(const std::string& uri) const
      79              : {
      80         2592 :     dht::InfoHash h(uri);
      81         2592 :     if (!h)
      82            0 :         return false;
      83         2592 :     std::lock_guard lock(mutex_);
      84         2592 :     auto it = trackedBuddies_.find(h);
      85         2592 :     return it != trackedBuddies_.end() && !it->second.deviceValues.empty();
      86         2592 : }
      87              : 
      88              : std::map<std::string, bool>
      89            2 : PresenceManager::getTrackedBuddyPresence() const
      90              : {
      91            2 :     std::lock_guard lock(mutex_);
      92            2 :     std::map<std::string, bool> presence_info;
      93            4 :     for (const auto& [h, buddy] : trackedBuddies_) {
      94            2 :         presence_info.emplace(h.toString(), !buddy.deviceValues.empty());
      95              :     }
      96            4 :     return presence_info;
      97            2 : }
      98              : 
      99              : std::vector<dht::PkId>
     100         1192 : PresenceManager::getDevices(const std::string& uri) const
     101              : {
     102         1192 :     dht::InfoHash h(uri);
     103         1192 :     if (!h)
     104            0 :         return {};
     105         1192 :     std::lock_guard lock(mutex_);
     106         1192 :     auto it = trackedBuddies_.find(h);
     107         1192 :     if (it == trackedBuddies_.end())
     108          232 :         return {};
     109          960 :     std::vector<dht::PkId> devices;
     110          960 :     devices.reserve(it->second.deviceValues.size());
     111         1132 :     for (const auto& [deviceId, valueIds] : it->second.deviceValues)
     112          172 :         devices.emplace_back(deviceId);
     113          960 :     return devices;
     114         1192 : }
     115              : 
     116              : uint64_t
     117          940 : PresenceManager::addListener(PresenceCallback cb)
     118              : {
     119          940 :     std::lock_guard lock(listenersMutex_);
     120          940 :     auto id = nextListenerId_++;
     121          940 :     listeners_.emplace(id, std::move(cb));
     122          940 :     return id;
     123          940 : }
     124              : 
     125              : void
     126            0 : PresenceManager::removeListener(uint64_t token)
     127              : {
     128            0 :     std::lock_guard lock(listenersMutex_);
     129            0 :     listeners_.erase(token);
     130            0 : }
     131              : 
     132              : uint64_t
     133         1434 : PresenceManager::addDeviceListener(DevicePresenceCallback cb)
     134              : {
     135         1434 :     std::lock_guard lock(listenersMutex_);
     136         1434 :     auto id = nextListenerId_++;
     137         1434 :     deviceListeners_.emplace(id, std::move(cb));
     138         1434 :     return id;
     139         1434 : }
     140              : 
     141              : void
     142          293 : PresenceManager::removeDeviceListener(uint64_t token)
     143              : {
     144          293 :     std::lock_guard lock(listenersMutex_);
     145          293 :     deviceListeners_.erase(token);
     146          293 : }
     147              : 
     148              : void
     149          821 : PresenceManager::refresh()
     150              : {
     151          821 :     std::lock_guard lock(mutex_);
     152          833 :     for (auto& [h, buddy] : trackedBuddies_) {
     153           12 :         buddy.listenToken = {};
     154           12 :         buddy.deviceValues.clear();
     155           12 :         trackPresence(h, buddy);
     156              :     }
     157          821 : }
     158              : 
     159              : void
     160          956 : PresenceManager::trackPresence(const dht::InfoHash& h, TrackedBuddy& buddy)
     161              : {
     162          956 :     if (!dht_ || !dht_->isRunning())
     163           19 :         return;
     164              : 
     165          937 :     if (buddy.listenToken.valid()) {
     166            0 :         JAMI_ERROR("PresenceManager: Already tracking presence for {}", h.toString());
     167            0 :         return;
     168              :     }
     169              : 
     170         3748 :     buddy.listenToken = dht_->listen(
     171              :         h,
     172          937 :         [this, h](const std::vector<std::shared_ptr<dht::Value>>& values, bool expired) {
     173              :             // A contact device can be advertised by more than one DeviceAnnouncement
     174              :             // value at the same time (e.g. a freshly re-generated announcement coexisting
     175              :             // with a previous one that is still living out its DHT TTL). All of them
     176              :             // decode to the same device id. We therefore reference-count, per device, the
     177              :             // set of live announcement value ids: a device stays present as long as at
     178              :             // least one of its announcement values is alive, and only goes offline once
     179              :             // the last one expires. Toggling the device on every single value expiry would
     180              :             // mark the contact offline while a healthy announcement is still on the DHT.
     181              : 
     182              :             // Decode and validate the announcements before taking the lock, so the
     183              :             // (potentially expensive) msgpack/crypto decoding does not block unrelated
     184              :             // presence queries or listener updates.
     185          854 :             std::vector<std::pair<dht::PkId, dht::Value::Id>> decoded;
     186          854 :             decoded.reserve(values.size());
     187         1726 :             for (const auto& value : values) {
     188              :                 try {
     189          872 :                     auto dev = dht::Value::unpack<DeviceAnnouncement>(*value);
     190          872 :                     if (!dev.pk) {
     191            0 :                         JAMI_WARNING("PresenceManager: Received DeviceAnnouncement without public "
     192              :                                      "key for {}",
     193              :                                      h.toString());
     194            0 :                         continue;
     195              :                     }
     196          872 :                     decoded.emplace_back(dev.pk->getLongId(), value->id);
     197          872 :                 } catch (const std::exception& e) {
     198            0 :                     JAMI_WARNING("PresenceManager: Failed to decode DeviceAnnouncement {} for {}: {}",
     199              :                                  value->id,
     200              :                                  h.toString(),
     201              :                                  e.what());
     202            0 :                 }
     203              :             }
     204              : 
     205          854 :             std::vector<std::pair<dht::PkId, bool>> deviceChanges;
     206              :             bool wasConnected, isConnected;
     207              :             {
     208          854 :                 std::lock_guard lock(mutex_);
     209          854 :                 auto it = trackedBuddies_.find(h);
     210          854 :                 if (it == trackedBuddies_.end())
     211           13 :                     return true;
     212              : 
     213          841 :                 auto& deviceValues = it->second.deviceValues;
     214          841 :                 wasConnected = !deviceValues.empty();
     215              : 
     216         1699 :                 for (const auto& [deviceId, valueId] : decoded) {
     217          858 :                     if (expired) {
     218           85 :                         auto dit = deviceValues.find(deviceId);
     219           85 :                         if (dit != deviceValues.end()) {
     220           85 :                             dit->second.erase(valueId);
     221           85 :                             if (dit->second.empty()) {
     222           83 :                                 deviceValues.erase(dit);
     223           83 :                                 deviceChanges.emplace_back(deviceId, false);
     224              :                             }
     225              :                         }
     226              :                     } else {
     227          773 :                         auto& ids = deviceValues[deviceId];
     228          773 :                         bool deviceWasAbsent = ids.empty();
     229          773 :                         ids.insert(valueId);
     230          773 :                         if (deviceWasAbsent)
     231          769 :                             deviceChanges.emplace_back(deviceId, true);
     232              :                     }
     233              :                 }
     234              : 
     235          841 :                 isConnected = !deviceValues.empty();
     236          854 :             }
     237              : 
     238         1693 :             for (const auto& [deviceId, deviceOnline] : deviceChanges)
     239          852 :                 notifyDeviceListeners(h.toString(), deviceId, deviceOnline);
     240              : 
     241          841 :             if (isConnected != wasConnected) {
     242          771 :                 notifyListeners(h.toString(), isConnected);
     243              :             }
     244          841 :             return true;
     245          854 :         },
     246         2811 :         dht::getFilterSet<DeviceAnnouncement>());
     247              : }
     248              : 
     249              : void
     250          771 : PresenceManager::notifyListeners(const std::string& uri, bool online)
     251              : {
     252          771 :     std::vector<PresenceCallback> cbs;
     253              :     {
     254          771 :         std::lock_guard lock(listenersMutex_);
     255          771 :         cbs.reserve(listeners_.size());
     256         1547 :         for (const auto& [id, cb] : listeners_) {
     257          776 :             cbs.emplace_back(cb);
     258              :         }
     259          771 :     }
     260         1547 :     for (const auto& cb : cbs) {
     261          776 :         cb(uri, online);
     262              :     }
     263          771 : }
     264              : 
     265              : void
     266          852 : PresenceManager::notifyDeviceListeners(const std::string& uri, const dht::PkId& deviceId, bool online)
     267              : {
     268          852 :     std::vector<DevicePresenceCallback> cbs;
     269              :     {
     270          852 :         std::lock_guard lock(listenersMutex_);
     271          852 :         cbs.reserve(deviceListeners_.size());
     272         2553 :         for (const auto& [id, cb] : deviceListeners_) {
     273         1701 :             cbs.emplace_back(cb);
     274              :         }
     275          852 :     }
     276         2553 :     for (const auto& cb : cbs) {
     277         1701 :         cb(uri, deviceId, online);
     278              :     }
     279          852 : }
     280              : 
     281              : } // namespace jami
        

Generated by: LCOV version 2.0-1