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
|