LCOV - code coverage report
Current view: top level - src/jamidht - presence_manager.cpp (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 89.9 % 159 143
Test Date: 2026-07-06 08:25:38 Functions: 68.2 % 22 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          798 : PresenceManager::PresenceManager(const std::shared_ptr<dht::DhtRunner>& dht)
      25          798 :     : dht_(dht)
      26          798 : {}
      27              : 
      28          798 : PresenceManager::~PresenceManager()
      29              : {
      30          798 :     std::lock_guard lock(mutex_);
      31         1106 :     for (auto& [h, buddy] : trackedBuddies_) {
      32          308 :         if (dht_ && dht_->isRunning()) {
      33            0 :             dht_->cancelListen(h, std::move(buddy.listenToken));
      34              :         }
      35              :     }
      36          798 : }
      37              : 
      38              : void
      39          809 : PresenceManager::trackBuddy(const std::string& uri)
      40              : {
      41          809 :     dht::InfoHash h(uri);
      42          809 :     if (!h)
      43            0 :         return;
      44              : 
      45          809 :     std::lock_guard lock(mutex_);
      46          809 :     auto it = trackedBuddies_.find(h);
      47          809 :     if (it == trackedBuddies_.end()) {
      48          782 :         it = trackedBuddies_.emplace(h, TrackedBuddy {h}).first;
      49              :     }
      50              : 
      51          809 :     it->second.refCount++;
      52          809 :     if (it->second.refCount == 1) {
      53          782 :         trackPresence(h, it->second);
      54              :     }
      55          809 : }
      56              : 
      57              : void
      58          486 : PresenceManager::untrackBuddy(const std::string& uri)
      59              : {
      60          486 :     dht::InfoHash h(uri);
      61          487 :     if (!h)
      62            0 :         return;
      63              : 
      64          487 :     std::lock_guard lock(mutex_);
      65          486 :     auto it = trackedBuddies_.find(h);
      66          487 :     if (it != trackedBuddies_.end()) {
      67          487 :         it->second.refCount--;
      68          487 :         if (it->second.refCount <= 0) {
      69          474 :             if (dht_ && dht_->isRunning()) {
      70          466 :                 dht_->cancelListen(h, std::move(it->second.listenToken));
      71              :             }
      72          473 :             trackedBuddies_.erase(it);
      73              :         }
      74              :     }
      75          487 : }
      76              : 
      77              : bool
      78         2434 : PresenceManager::isOnline(const std::string& uri) const
      79              : {
      80         2434 :     dht::InfoHash h(uri);
      81         2434 :     if (!h)
      82            0 :         return false;
      83         2434 :     std::lock_guard lock(mutex_);
      84         2434 :     auto it = trackedBuddies_.find(h);
      85         2434 :     return it != trackedBuddies_.end() && !it->second.deviceValues.empty();
      86         2434 : }
      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         1011 : PresenceManager::getDevices(const std::string& uri) const
     101              : {
     102         1011 :     dht::InfoHash h(uri);
     103         1011 :     if (!h)
     104            0 :         return {};
     105         1011 :     std::lock_guard lock(mutex_);
     106         1011 :     auto it = trackedBuddies_.find(h);
     107         1011 :     if (it == trackedBuddies_.end())
     108          220 :         return {};
     109          791 :     std::vector<dht::PkId> devices;
     110          791 :     devices.reserve(it->second.deviceValues.size());
     111          927 :     for (const auto& [deviceId, valueIds] : it->second.deviceValues)
     112          136 :         devices.emplace_back(deviceId);
     113          791 :     return devices;
     114         1011 : }
     115              : 
     116              : uint64_t
     117          813 : PresenceManager::addListener(PresenceCallback cb)
     118              : {
     119          813 :     std::lock_guard lock(listenersMutex_);
     120          813 :     auto id = nextListenerId_++;
     121          813 :     listeners_.emplace(id, std::move(cb));
     122          813 :     return id;
     123          813 : }
     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         1188 : PresenceManager::addDeviceListener(DevicePresenceCallback cb)
     134              : {
     135         1188 :     std::lock_guard lock(listenersMutex_);
     136         1188 :     auto id = nextListenerId_++;
     137         1188 :     deviceListeners_.emplace(id, std::move(cb));
     138         1188 :     return id;
     139         1188 : }
     140              : 
     141              : void
     142          199 : PresenceManager::removeDeviceListener(uint64_t token)
     143              : {
     144          199 :     std::lock_guard lock(listenersMutex_);
     145          199 :     deviceListeners_.erase(token);
     146          199 : }
     147              : 
     148              : void
     149          716 : PresenceManager::refresh()
     150              : {
     151          716 :     std::lock_guard lock(mutex_);
     152          728 :     for (auto& [h, buddy] : trackedBuddies_) {
     153           12 :         buddy.listenToken = {};
     154           12 :         buddy.deviceValues.clear();
     155           12 :         trackPresence(h, buddy);
     156              :     }
     157          716 : }
     158              : 
     159              : void
     160          794 : PresenceManager::trackPresence(const dht::InfoHash& h, TrackedBuddy& buddy)
     161              : {
     162          794 :     if (!dht_ || !dht_->isRunning())
     163           22 :         return;
     164              : 
     165          772 :     if (buddy.listenToken.valid()) {
     166            0 :         JAMI_ERROR("PresenceManager: Already tracking presence for {}", h.toString());
     167            0 :         return;
     168              :     }
     169              : 
     170         3088 :     buddy.listenToken = dht_->listen(
     171              :         h,
     172          772 :         [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          681 :             std::vector<std::pair<dht::PkId, dht::Value::Id>> decoded;
     186          681 :             decoded.reserve(values.size());
     187         1381 :             for (const auto& value : values) {
     188              :                 try {
     189          700 :                     auto dev = dht::Value::unpack<DeviceAnnouncement>(*value);
     190          700 :                     if (!dev.pk) {
     191            0 :                         JAMI_WARNING("PresenceManager: Received DeviceAnnouncement without public "
     192              :                                      "key for {}",
     193              :                                      h.toString());
     194            0 :                         continue;
     195            0 :                     }
     196          700 :                     decoded.emplace_back(dev.pk->getLongId(), value->id);
     197          700 :                 } 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          681 :             std::vector<std::pair<dht::PkId, bool>> deviceChanges;
     206              :             bool wasConnected, isConnected;
     207              :             {
     208          681 :                 std::lock_guard lock(mutex_);
     209          681 :                 auto it = trackedBuddies_.find(h);
     210          681 :                 if (it == trackedBuddies_.end())
     211            8 :                     return true;
     212              : 
     213          673 :                 auto& deviceValues = it->second.deviceValues;
     214          673 :                 wasConnected = !deviceValues.empty();
     215              : 
     216         1363 :                 for (const auto& [deviceId, valueId] : decoded) {
     217          690 :                     if (expired) {
     218           72 :                         auto dit = deviceValues.find(deviceId);
     219           72 :                         if (dit != deviceValues.end()) {
     220           72 :                             dit->second.erase(valueId);
     221           72 :                             if (dit->second.empty()) {
     222           70 :                                 deviceValues.erase(dit);
     223           70 :                                 deviceChanges.emplace_back(deviceId, false);
     224              :                             }
     225              :                         }
     226              :                     } else {
     227          618 :                         auto& ids = deviceValues[deviceId];
     228          618 :                         bool deviceWasAbsent = ids.empty();
     229          618 :                         ids.insert(valueId);
     230          618 :                         if (deviceWasAbsent)
     231          614 :                             deviceChanges.emplace_back(deviceId, true);
     232              :                     }
     233              :                 }
     234              : 
     235          673 :                 isConnected = !deviceValues.empty();
     236          681 :             }
     237              : 
     238         1357 :             for (const auto& [deviceId, deviceOnline] : deviceChanges)
     239          684 :                 notifyDeviceListeners(h.toString(), deviceId, deviceOnline);
     240              : 
     241          673 :             if (isConnected != wasConnected) {
     242          608 :                 notifyListeners(h.toString(), isConnected);
     243              :             }
     244          673 :             return true;
     245          681 :         },
     246         2316 :         dht::getFilterSet<DeviceAnnouncement>());
     247              : }
     248              : 
     249              : void
     250          608 : PresenceManager::notifyListeners(const std::string& uri, bool online)
     251              : {
     252          608 :     std::vector<PresenceCallback> cbs;
     253              :     {
     254          608 :         std::lock_guard lock(listenersMutex_);
     255          608 :         cbs.reserve(listeners_.size());
     256         1220 :         for (const auto& [id, cb] : listeners_) {
     257          612 :             cbs.emplace_back(cb);
     258              :         }
     259          608 :     }
     260         1220 :     for (const auto& cb : cbs) {
     261          612 :         cb(uri, online);
     262              :     }
     263          608 : }
     264              : 
     265              : void
     266          684 : PresenceManager::notifyDeviceListeners(const std::string& uri, const dht::PkId& deviceId, bool online)
     267              : {
     268          684 :     std::vector<DevicePresenceCallback> cbs;
     269              :     {
     270          684 :         std::lock_guard lock(listenersMutex_);
     271          684 :         cbs.reserve(deviceListeners_.size());
     272         2036 :         for (const auto& [id, cb] : deviceListeners_) {
     273         1352 :             cbs.emplace_back(cb);
     274              :         }
     275          684 :     }
     276         2036 :     for (const auto& cb : cbs) {
     277         1352 :         cb(uri, deviceId, online);
     278              :     }
     279          684 : }
     280              : 
     281              : } // namespace jami
        

Generated by: LCOV version 2.0-1