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
|