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-11-09 10:41:14 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         1016 : PresenceManager::PresenceManager(const std::shared_ptr<dht::DhtRunner>& dht)
      25         1016 :     : dht_(dht)
      26         1016 : {}
      27              : 
      28         1016 : PresenceManager::~PresenceManager()
      29              : {
      30         1016 :     std::lock_guard lock(mutex_);
      31         1349 :     for (auto& [h, buddy] : trackedBuddies_) {
      32          333 :         if (dht_ && dht_->isRunning()) {
      33            0 :             dht_->cancelListen(h, std::move(buddy.listenToken));
      34              :         }
      35              :     }
      36         1016 : }
      37              : 
      38              : void
      39         1860 : PresenceManager::trackBuddy(const std::string& uri)
      40              : {
      41         1860 :     dht::InfoHash h(uri);
      42         1860 :     if (!h)
      43            0 :         return;
      44              : 
      45         1860 :     std::lock_guard lock(mutex_);
      46         1860 :     auto it = trackedBuddies_.find(h);
      47         1860 :     if (it == trackedBuddies_.end()) {
      48         1607 :         it = trackedBuddies_.emplace(h, TrackedBuddy {h}).first;
      49              :     }
      50              : 
      51         1860 :     it->second.refCount++;
      52         1860 :     if (it->second.refCount == 1) {
      53         1607 :         trackPresence(h, it->second);
      54              :     }
      55         1860 : }
      56              : 
      57              : void
      58         1481 : PresenceManager::untrackBuddy(const std::string& uri)
      59              : {
      60         1481 :     dht::InfoHash h(uri);
      61         1481 :     if (!h)
      62            0 :         return;
      63              : 
      64         1481 :     std::lock_guard lock(mutex_);
      65         1481 :     auto it = trackedBuddies_.find(h);
      66         1481 :     if (it != trackedBuddies_.end()) {
      67         1481 :         it->second.refCount--;
      68         1481 :         if (it->second.refCount <= 0) {
      69         1274 :             if (dht_ && dht_->isRunning()) {
      70         1274 :                 dht_->cancelListen(h, std::move(it->second.listenToken));
      71              :             }
      72         1274 :             trackedBuddies_.erase(it);
      73              :         }
      74              :     }
      75         1481 : }
      76              : 
      77              : bool
      78         2701 : PresenceManager::isOnline(const std::string& uri) const
      79              : {
      80         2701 :     dht::InfoHash h(uri);
      81         2701 :     if (!h)
      82            0 :         return false;
      83         2701 :     std::lock_guard lock(mutex_);
      84         2701 :     auto it = trackedBuddies_.find(h);
      85         2701 :     return it != trackedBuddies_.end() && !it->second.deviceValues.empty();
      86         2701 : }
      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         2234 : PresenceManager::getDevices(const std::string& uri) const
     101              : {
     102         2234 :     dht::InfoHash h(uri);
     103         2234 :     if (!h)
     104            0 :         return {};
     105         2234 :     std::lock_guard lock(mutex_);
     106         2234 :     auto it = trackedBuddies_.find(h);
     107         2234 :     if (it == trackedBuddies_.end())
     108          291 :         return {};
     109         1943 :     std::vector<dht::PkId> devices;
     110         1943 :     devices.reserve(it->second.deviceValues.size());
     111         2664 :     for (const auto& [deviceId, valueIds] : it->second.deviceValues)
     112          721 :         devices.emplace_back(deviceId);
     113         1943 :     return devices;
     114         2234 : }
     115              : 
     116              : uint64_t
     117         1032 : PresenceManager::addListener(PresenceCallback cb)
     118              : {
     119         1032 :     std::lock_guard lock(listenersMutex_);
     120         1032 :     auto id = nextListenerId_++;
     121         1032 :     listeners_.emplace(id, std::move(cb));
     122         1032 :     return id;
     123         1032 : }
     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         1968 : PresenceManager::addDeviceListener(DevicePresenceCallback cb)
     134              : {
     135         1968 :     std::lock_guard lock(listenersMutex_);
     136         1968 :     auto id = nextListenerId_++;
     137         1968 :     deviceListeners_.emplace(id, std::move(cb));
     138         1968 :     return id;
     139         1968 : }
     140              : 
     141              : void
     142          753 : PresenceManager::removeDeviceListener(uint64_t token)
     143              : {
     144          753 :     std::lock_guard lock(listenersMutex_);
     145          753 :     deviceListeners_.erase(token);
     146          753 : }
     147              : 
     148              : void
     149          916 : PresenceManager::refresh()
     150              : {
     151          916 :     std::lock_guard lock(mutex_);
     152          949 :     for (auto& [h, buddy] : trackedBuddies_) {
     153           33 :         buddy.listenToken = {};
     154           33 :         buddy.deviceValues.clear();
     155           33 :         trackPresence(h, buddy);
     156              :     }
     157          916 : }
     158              : 
     159              : void
     160         1640 : PresenceManager::trackPresence(const dht::InfoHash& h, TrackedBuddy& buddy)
     161              : {
     162         1640 :     if (!dht_ || !dht_->isRunning())
     163           22 :         return;
     164              : 
     165         1618 :     if (buddy.listenToken.valid()) {
     166            0 :         JAMI_ERROR("PresenceManager: Already tracking presence for {}", h.toString());
     167            0 :         return;
     168              :     }
     169              : 
     170         6472 :     buddy.listenToken = dht_->listen(
     171              :         h,
     172         1618 :         [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         1610 :             std::vector<std::pair<dht::PkId, dht::Value::Id>> decoded;
     186         1610 :             decoded.reserve(values.size());
     187         3240 :             for (const auto& value : values) {
     188              :                 try {
     189         1630 :                     auto dev = dht::Value::unpack<DeviceAnnouncement>(*value);
     190         1630 :                     if (!dev.pk) {
     191            0 :                         JAMI_WARNING("PresenceManager: Received DeviceAnnouncement without public "
     192              :                                      "key for {}",
     193              :                                      h.toString());
     194            0 :                         continue;
     195              :                     }
     196         1630 :                     decoded.emplace_back(dev.pk->getLongId(), value->id);
     197         1630 :                 } 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         1610 :             std::vector<std::pair<dht::PkId, bool>> deviceChanges;
     206              :             bool wasConnected, isConnected;
     207              :             {
     208         1610 :                 std::lock_guard lock(mutex_);
     209         1610 :                 auto it = trackedBuddies_.find(h);
     210         1610 :                 if (it == trackedBuddies_.end())
     211            4 :                     return true;
     212              : 
     213         1606 :                 auto& deviceValues = it->second.deviceValues;
     214         1606 :                 wasConnected = !deviceValues.empty();
     215              : 
     216         3232 :                 for (const auto& [deviceId, valueId] : decoded) {
     217         1626 :                     if (expired) {
     218          146 :                         auto dit = deviceValues.find(deviceId);
     219          146 :                         if (dit != deviceValues.end()) {
     220          146 :                             dit->second.erase(valueId);
     221          146 :                             if (dit->second.empty()) {
     222          139 :                                 deviceValues.erase(dit);
     223          139 :                                 deviceChanges.emplace_back(deviceId, false);
     224              :                             }
     225              :                         }
     226              :                     } else {
     227         1480 :                         auto& ids = deviceValues[deviceId];
     228         1480 :                         bool deviceWasAbsent = ids.empty();
     229         1480 :                         ids.insert(valueId);
     230         1480 :                         if (deviceWasAbsent)
     231         1471 :                             deviceChanges.emplace_back(deviceId, true);
     232              :                     }
     233              :                 }
     234              : 
     235         1606 :                 isConnected = !deviceValues.empty();
     236         1610 :             }
     237              : 
     238         3216 :             for (const auto& [deviceId, deviceOnline] : deviceChanges)
     239         1610 :                 notifyDeviceListeners(h.toString(), deviceId, deviceOnline);
     240              : 
     241         1606 :             if (isConnected != wasConnected) {
     242         1524 :                 notifyListeners(h.toString(), isConnected);
     243              :             }
     244         1606 :             return true;
     245         1610 :         },
     246         4854 :         dht::getFilterSet<DeviceAnnouncement>());
     247              : }
     248              : 
     249              : void
     250         1524 : PresenceManager::notifyListeners(const std::string& uri, bool online)
     251              : {
     252         1524 :     std::vector<PresenceCallback> cbs;
     253              :     {
     254         1524 :         std::lock_guard lock(listenersMutex_);
     255         1524 :         cbs.reserve(listeners_.size());
     256         3053 :         for (const auto& [id, cb] : listeners_) {
     257         1529 :             cbs.emplace_back(cb);
     258              :         }
     259         1524 :     }
     260         3053 :     for (const auto& cb : cbs) {
     261         1529 :         cb(uri, online);
     262              :     }
     263         1524 : }
     264              : 
     265              : void
     266         1610 : PresenceManager::notifyDeviceListeners(const std::string& uri, const dht::PkId& deviceId, bool online)
     267              : {
     268         1610 :     std::vector<DevicePresenceCallback> cbs;
     269              :     {
     270         1610 :         std::lock_guard lock(listenersMutex_);
     271         1610 :         cbs.reserve(deviceListeners_.size());
     272         4893 :         for (const auto& [id, cb] : deviceListeners_) {
     273         3283 :             cbs.emplace_back(cb);
     274              :         }
     275         1610 :     }
     276         4893 :     for (const auto& cb : cbs) {
     277         3283 :         cb(uri, deviceId, online);
     278              :     }
     279         1610 : }
     280              : 
     281              : } // namespace jami
        

Generated by: LCOV version 2.0-1