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 : #ifdef HAVE_CONFIG_H
19 : #include "config.h"
20 : #endif
21 :
22 : #include "jamiaccount.h"
23 : #include "presence_manager.h"
24 :
25 : #include "logger.h"
26 :
27 : #include "accountarchive.h"
28 : #include "jami_contact.h"
29 : #include "configkeys.h"
30 : #include "contact_list.h"
31 : #include "archive_account_manager.h"
32 : #include "server_account_manager.h"
33 : #include "jamidht/channeled_transport.h"
34 : #include "conversation_channel_handler.h"
35 : #include "sync_channel_handler.h"
36 : #include "message_channel_handler.h"
37 : #include "auth_channel_handler.h"
38 : #include "transfer_channel_handler.h"
39 : #include "swarm/swarm_channel_handler.h"
40 : #include "jami/media_const.h"
41 :
42 : #include "sip/sdp.h"
43 : #include "sip/sipvoiplink.h"
44 : #include "sip/sipcall.h"
45 : #include "sip/siptransport.h"
46 : #include "connectivity/sip_utils.h"
47 :
48 : #include "uri.h"
49 :
50 : #include "client/jami_signal.h"
51 : #include "jami/call_const.h"
52 : #include "jami/account_const.h"
53 :
54 : #include "system_codec_container.h"
55 :
56 : #include "account_schema.h"
57 : #include "manager.h"
58 : #include "connectivity/utf8_utils.h"
59 : #include "connectivity/ip_utils.h"
60 :
61 : #ifdef ENABLE_PLUGIN
62 : #include "plugin/jamipluginmanager.h"
63 : #include "plugin/chatservicesmanager.h"
64 : #endif
65 :
66 : #ifdef ENABLE_VIDEO
67 : #include "libav_utils.h"
68 : #endif
69 : #include "fileutils.h"
70 : #include "string_utils.h"
71 : #include "archiver.h"
72 : #include "data_transfer.h"
73 : #include "json_utils.h"
74 :
75 : #include "libdevcrypto/Common.h"
76 : #include "base64.h"
77 : #include "vcard.h"
78 : #include "im/instant_messaging.h"
79 :
80 : #include <dhtnet/ice_transport.h>
81 : #include <dhtnet/ice_transport_factory.h>
82 : #include <dhtnet/upnp/upnp_control.h>
83 : #include <dhtnet/multiplexed_socket.h>
84 : #include <dhtnet/certstore.h>
85 :
86 : #include <opendht/thread_pool.h>
87 : #include <opendht/peer_discovery.h>
88 : #include <opendht/http.h>
89 :
90 : #include <yaml-cpp/yaml.h>
91 : #include <fmt/format.h>
92 :
93 : #include <unistd.h>
94 :
95 : #include <algorithm>
96 : #include <array>
97 : #include <cctype>
98 : #include <charconv>
99 : #include <cinttypes>
100 : #include <cstdarg>
101 : #include <fstream>
102 : #include <initializer_list>
103 : #include <memory>
104 : #include <regex>
105 : #include <sstream>
106 : #include <string>
107 : #include <system_error>
108 : #include <utility>
109 :
110 : using namespace std::placeholders;
111 :
112 : namespace jami {
113 :
114 : constexpr pj_str_t STR_MESSAGE_ID = jami::sip_utils::CONST_PJ_STR("Message-ID");
115 : static constexpr const char MIME_TYPE_IMDN[] {"message/imdn+xml"};
116 : static constexpr const char MIME_TYPE_PIDF[] {"application/pidf+xml"};
117 : static constexpr const char MIME_TYPE_INVITE_JSON[] {"application/invite+json"};
118 : static constexpr const char DEVICE_ID_PATH[] {"ring_device"};
119 : static constexpr auto TREATED_PATH = "treatedImMessages"sv;
120 :
121 : struct VCardMessageCtx
122 : {
123 : std::shared_ptr<std::atomic_int> success;
124 : int total;
125 : std::string path;
126 : };
127 :
128 : namespace Migration {
129 :
130 : enum class State { // Contains all the Migration states
131 : SUCCESS,
132 : INVALID
133 : };
134 :
135 : std::string
136 4 : mapStateNumberToString(const State migrationState)
137 : {
138 : #define CASE_STATE(X) \
139 : case Migration::State::X: \
140 : return #X
141 :
142 4 : switch (migrationState) {
143 0 : CASE_STATE(INVALID);
144 4 : CASE_STATE(SUCCESS);
145 : }
146 0 : return {};
147 : }
148 :
149 : void
150 4 : setState(const std::string& accountID, const State migrationState)
151 : {
152 4 : emitSignal<libjami::ConfigurationSignal::MigrationEnded>(accountID, mapStateNumberToString(migrationState));
153 4 : }
154 :
155 : } // namespace Migration
156 :
157 : struct JamiAccount::PendingCall
158 : {
159 : std::chrono::steady_clock::time_point start;
160 : std::shared_ptr<IceTransport> ice_sp;
161 : std::shared_ptr<IceTransport> ice_tcp_sp;
162 : std::weak_ptr<SIPCall> call;
163 : std::future<size_t> listen_key;
164 : dht::InfoHash call_key;
165 : dht::InfoHash from;
166 : dht::InfoHash from_account;
167 : std::shared_ptr<dht::crypto::Certificate> from_cert;
168 : };
169 :
170 : struct JamiAccount::PendingMessage
171 : {
172 : std::set<DeviceId> to;
173 : };
174 :
175 : struct AccountPeerInfo
176 : {
177 : dht::InfoHash accountId;
178 : std::string displayName;
179 0 : MSGPACK_DEFINE(accountId, displayName)
180 : };
181 :
182 : struct JamiAccount::DiscoveredPeer
183 : {
184 : std::string displayName;
185 : std::unique_ptr<asio::steady_timer> cleanupTimer;
186 : };
187 :
188 : /**
189 : * Track sending state for a single message to one or more devices.
190 : */
191 : class JamiAccount::SendMessageContext
192 : {
193 : public:
194 : using OnComplete = std::function<void(bool, bool)>;
195 15071 : SendMessageContext(OnComplete onComplete)
196 15071 : : onComplete(std::move(onComplete))
197 15071 : {}
198 : /** Track new pending message for device */
199 13463 : bool add(const DeviceId& device)
200 : {
201 13463 : std::lock_guard lk(mtx);
202 26926 : return devices.insert(device).second;
203 13463 : }
204 : /** Call after all messages are sent */
205 15072 : void start()
206 : {
207 15072 : std::unique_lock lk(mtx);
208 15072 : started = true;
209 15072 : checkComplete(lk);
210 15072 : }
211 : /** Complete pending message for device */
212 13246 : bool complete(const DeviceId& device, bool success)
213 : {
214 13246 : std::unique_lock lk(mtx);
215 13246 : if (devices.erase(device) == 0)
216 0 : return false;
217 13244 : ++completeCount;
218 13244 : if (success)
219 13240 : ++successCount;
220 13244 : checkComplete(lk);
221 13247 : return true;
222 13247 : }
223 : bool empty() const
224 : {
225 : std::lock_guard lk(mtx);
226 : return devices.empty();
227 : }
228 2929 : bool pending(const DeviceId& device) const
229 : {
230 2929 : std::lock_guard lk(mtx);
231 5858 : return devices.find(device) != devices.end();
232 2928 : }
233 :
234 : private:
235 : mutable std::mutex mtx;
236 : OnComplete onComplete;
237 : std::set<DeviceId> devices;
238 : unsigned completeCount = 0;
239 : unsigned successCount = 0;
240 : bool started {false};
241 :
242 28317 : void checkComplete(std::unique_lock<std::mutex>& lk)
243 : {
244 28317 : if (started && (devices.empty() || successCount)) {
245 15074 : if (onComplete) {
246 15071 : auto cb = std::move(onComplete);
247 15067 : auto success = successCount != 0;
248 15067 : auto complete = completeCount != 0;
249 15067 : onComplete = {};
250 15069 : lk.unlock();
251 15070 : cb(success, complete);
252 15072 : }
253 : }
254 28317 : }
255 : };
256 :
257 : static const constexpr std::string_view RING_URI_PREFIX = "ring:";
258 : static const constexpr std::string_view JAMI_URI_PREFIX = "jami:";
259 : static const auto PROXY_REGEX = std::regex("(https?://)?([\\w\\.\\-_\\~]+)(:(\\d+)|:\\[(.+)-(.+)\\])?");
260 : static const constexpr std::string_view PEER_DISCOVERY_JAMI_SERVICE = "jami";
261 : const constexpr auto PEER_DISCOVERY_EXPIRATION = std::chrono::minutes(1);
262 :
263 : using ValueIdDist = std::uniform_int_distribution<dht::Value::Id>;
264 :
265 : std::string_view
266 42716 : stripPrefix(std::string_view toUrl)
267 : {
268 42716 : auto dhtf = toUrl.find(RING_URI_PREFIX);
269 42736 : if (dhtf != std::string_view::npos) {
270 0 : dhtf += RING_URI_PREFIX.size();
271 : } else {
272 42736 : dhtf = toUrl.find(JAMI_URI_PREFIX);
273 42745 : if (dhtf != std::string_view::npos) {
274 0 : dhtf += JAMI_URI_PREFIX.size();
275 : } else {
276 42745 : dhtf = toUrl.find("sips:");
277 42727 : dhtf = (dhtf == std::string_view::npos) ? 0 : dhtf + 5;
278 : }
279 : }
280 42727 : while (dhtf < toUrl.length() && toUrl[dhtf] == '/')
281 0 : dhtf++;
282 42737 : return toUrl.substr(dhtf);
283 : }
284 :
285 : std::string_view
286 42684 : parseJamiUri(std::string_view toUrl)
287 : {
288 42684 : auto sufix = stripPrefix(toUrl);
289 42721 : if (sufix.length() < 40)
290 0 : throw std::invalid_argument("Not a valid Jami URI: " + toUrl);
291 :
292 42728 : const std::string_view toUri = sufix.substr(0, 40);
293 42725 : if (std::find_if_not(toUri.cbegin(), toUri.cend(), ::isxdigit) != toUri.cend())
294 0 : throw std::invalid_argument("Not a valid Jami URI: " + toUrl);
295 42749 : return toUri;
296 : }
297 :
298 : static constexpr std::string_view
299 3446 : dhtStatusStr(dht::NodeStatus status)
300 : {
301 : return status == dht::NodeStatus::Connected
302 1740 : ? "connected"sv
303 3446 : : (status == dht::NodeStatus::Connecting ? "connecting"sv : "disconnected"sv);
304 : }
305 :
306 684 : JamiAccount::JamiAccount(const std::string& accountId)
307 : : SIPAccountBase(accountId)
308 684 : , cachePath_(fileutils::get_cache_dir() / accountId)
309 684 : , dataPath_(cachePath_ / "values")
310 1368 : , logger_(Logger::dhtLogger(fmt::format("Account {}", accountId)))
311 684 : , certStore_ {std::make_shared<dhtnet::tls::CertificateStore>(idPath_, logger_)}
312 684 : , dht_(std::make_shared<dht::DhtRunner>())
313 684 : , treatedMessages_(cachePath_ / TREATED_PATH)
314 684 : , presenceManager_(std::make_unique<PresenceManager>(dht_))
315 684 : , connectionManager_ {}
316 3420 : , nonSwarmTransferManager_()
317 : {
318 684 : presenceListenerToken_ = presenceManager_->addListener([this](const std::string& uri, bool online) {
319 644 : runOnMainThread([w = weak(), uri, online] {
320 644 : if (auto sthis = w.lock()) {
321 644 : if (online) {
322 582 : sthis->onTrackedBuddyOnline(uri);
323 582 : sthis->messageEngine_.onPeerOnline(uri);
324 : } else {
325 62 : sthis->onTrackedBuddyOffline(uri);
326 : }
327 644 : }
328 644 : });
329 644 : });
330 684 : }
331 :
332 1368 : JamiAccount::~JamiAccount() noexcept
333 : {
334 684 : if (dht_)
335 684 : dht_->join();
336 684 : }
337 :
338 : void
339 706 : JamiAccount::shutdownConnections()
340 : {
341 706 : JAMI_DBG("[Account %s] Shutdown connections", getAccountID().c_str());
342 :
343 706 : decltype(gitServers_) gservers;
344 : {
345 706 : std::lock_guard lk(gitServersMtx_);
346 706 : gservers = std::move(gitServers_);
347 706 : }
348 1109 : for (auto& [_id, gs] : gservers)
349 403 : gs->stop();
350 : {
351 706 : std::lock_guard lk(connManagerMtx_);
352 : // Just move destruction on another thread.
353 1412 : dht::ThreadPool::io().run(
354 706 : [conMgr = std::make_shared<decltype(connectionManager_)>(std::move(connectionManager_))] {});
355 706 : connectionManager_.reset();
356 706 : channelHandlers_.clear();
357 706 : }
358 706 : if (convModule_) {
359 601 : convModule_->shutdownConnections();
360 : }
361 :
362 706 : std::lock_guard lk(sipConnsMtx_);
363 706 : sipConns_.clear();
364 706 : }
365 :
366 : void
367 679 : JamiAccount::flush()
368 : {
369 : // Class base method
370 679 : SIPAccountBase::flush();
371 :
372 679 : dhtnet::fileutils::removeAll(cachePath_);
373 679 : dhtnet::fileutils::removeAll(dataPath_);
374 679 : dhtnet::fileutils::removeAll(idPath_, true);
375 679 : }
376 :
377 : std::shared_ptr<SIPCall>
378 33 : JamiAccount::newIncomingCall(const std::string& from,
379 : const std::vector<libjami::MediaMap>& mediaList,
380 : const std::shared_ptr<SipTransport>& sipTransp)
381 : {
382 132 : JAMI_DEBUG("New incoming call from {:s} with {:d} media", from, mediaList.size());
383 :
384 33 : if (sipTransp) {
385 66 : auto call = Manager::instance().callFactory.newSipCall(shared(), Call::CallType::INCOMING, mediaList);
386 33 : call->setPeerUri(JAMI_URI_PREFIX + from);
387 33 : call->setPeerNumber(from);
388 :
389 33 : call->setSipTransport(sipTransp, getContactHeader(sipTransp));
390 :
391 33 : return call;
392 33 : }
393 :
394 0 : JAMI_ERR("newIncomingCall: unable to find matching call for %s", from.c_str());
395 0 : return nullptr;
396 : }
397 :
398 : std::shared_ptr<Call>
399 47 : JamiAccount::newOutgoingCall(std::string_view toUrl, const std::vector<libjami::MediaMap>& mediaList)
400 : {
401 47 : auto uri = Uri(toUrl);
402 47 : if (uri.scheme() == Uri::Scheme::SWARM || uri.scheme() == Uri::Scheme::RENDEZVOUS) {
403 : // NOTE: In this case newOutgoingCall can act as "resumeConference" and just attach the
404 : // host to the current hosted conference. So, no call will be returned in that case.
405 22 : return newSwarmOutgoingCallHelper(uri, mediaList);
406 : }
407 :
408 25 : auto& manager = Manager::instance();
409 25 : std::shared_ptr<SIPCall> call;
410 :
411 : // SIP allows sending empty invites, this use case is not used with Jami accounts.
412 25 : if (not mediaList.empty()) {
413 16 : call = manager.callFactory.newSipCall(shared(), Call::CallType::OUTGOING, mediaList);
414 : } else {
415 9 : JAMI_WARN("Media list is empty, setting a default list");
416 18 : call = manager.callFactory.newSipCall(shared(),
417 : Call::CallType::OUTGOING,
418 18 : MediaAttribute::mediaAttributesToMediaMaps(
419 27 : createDefaultMediaList(isVideoEnabled())));
420 : }
421 :
422 25 : if (not call)
423 0 : return {};
424 :
425 25 : std::shared_lock lkCM(connManagerMtx_);
426 25 : if (!connectionManager_)
427 0 : return {};
428 :
429 25 : connectionManager_->getIceOptions([call, w = weak(), uri = std::move(uri)](auto&& opts) {
430 25 : if (call->isIceEnabled()) {
431 25 : if (not call->createIceMediaTransport(false)
432 50 : or not call->initIceMediaTransport(true, std::forward<dhtnet::IceTransportOptions>(opts))) {
433 0 : return;
434 : }
435 : }
436 25 : auto shared = w.lock();
437 25 : if (!shared)
438 0 : return;
439 25 : JAMI_DBG() << "New outgoing call with " << uri.toString();
440 25 : call->setPeerNumber(uri.authority());
441 25 : call->setPeerUri(uri.toString());
442 :
443 25 : shared->newOutgoingCallHelper(call, uri);
444 25 : });
445 :
446 25 : return call;
447 47 : }
448 :
449 : void
450 25 : JamiAccount::newOutgoingCallHelper(const std::shared_ptr<SIPCall>& call, const Uri& uri)
451 : {
452 100 : JAMI_LOG("[Account {}] Calling peer {}", getAccountID(), uri.authority());
453 : try {
454 25 : startOutgoingCall(call, uri.authority());
455 0 : } catch (const std::invalid_argument&) {
456 0 : auto suffix = stripPrefix(uri.toString());
457 0 : NameDirectory::lookupUri(suffix,
458 0 : config().nameServer,
459 0 : [wthis_ = weak(), call](const std::string& regName,
460 : const std::string& address,
461 : NameDirectory::Response response) {
462 : // we may run inside an unknown thread, but following code must
463 : // be called in main thread
464 0 : runOnMainThread([wthis_, regName, address, response, call]() {
465 0 : if (response != NameDirectory::Response::found) {
466 0 : call->onFailure(PJSIP_SC_NOT_FOUND);
467 0 : return;
468 : }
469 0 : if (auto sthis = wthis_.lock()) {
470 : try {
471 0 : sthis->startOutgoingCall(call, address);
472 0 : } catch (const std::invalid_argument&) {
473 0 : call->onFailure(PJSIP_SC_NOT_FOUND);
474 0 : }
475 : } else {
476 0 : call->onFailure(PJSIP_SC_SERVICE_UNAVAILABLE);
477 0 : }
478 : });
479 0 : });
480 0 : }
481 25 : }
482 :
483 : std::shared_ptr<SIPCall>
484 22 : JamiAccount::newSwarmOutgoingCallHelper(const Uri& uri, const std::vector<libjami::MediaMap>& mediaList)
485 : {
486 88 : JAMI_DEBUG("[Account {}] Calling conversation {}", getAccountID(), uri.authority());
487 : return convModule()
488 66 : ->call(uri.authority(), mediaList, [this, uri](const auto& accountUri, const auto& deviceId, const auto& call) {
489 11 : if (!call)
490 0 : return;
491 11 : std::unique_lock lkSipConn(sipConnsMtx_);
492 12 : for (auto& [key, value] : sipConns_) {
493 1 : if (key.first != accountUri || key.second != deviceId)
494 1 : continue;
495 0 : if (value.empty())
496 0 : continue;
497 0 : auto& sipConn = value.back();
498 :
499 0 : if (!sipConn.channel) {
500 0 : JAMI_WARN("A SIP transport exists without Channel, this is a bug. Please report");
501 0 : continue;
502 : }
503 :
504 0 : auto transport = sipConn.transport;
505 0 : if (!transport or !sipConn.channel)
506 0 : continue;
507 0 : call->setState(Call::ConnectionState::PROGRESSING);
508 :
509 0 : auto remoted_address = sipConn.channel->getRemoteAddress();
510 : try {
511 0 : onConnectedOutgoingCall(call, uri.authority(), remoted_address);
512 0 : return;
513 0 : } catch (const VoipLinkException&) {
514 : // In this case, the main scenario is that SIPStartCall failed because
515 : // the ICE is dead and the TLS session didn't send any packet on that dead
516 : // link (connectivity change, killed by the operating system, etc)
517 : // Here, we don't need to do anything, the TLS will fail and will delete
518 : // the cached transport
519 0 : continue;
520 : }
521 : }
522 11 : lkSipConn.unlock();
523 : {
524 11 : std::lock_guard lkP(pendingCallsMutex_);
525 11 : pendingCalls_[deviceId].emplace_back(call);
526 11 : }
527 :
528 : // Else, ask for a channel (for future calls/text messages)
529 11 : auto type = call->hasVideo() ? "videoCall" : "audioCall";
530 44 : JAMI_WARNING("[call {}] No channeled socket with this peer. Send request", call->getCallId());
531 11 : requestSIPConnection(accountUri, deviceId, type, true, call);
532 33 : });
533 : }
534 :
535 : void
536 10 : JamiAccount::handleIncomingConversationCall(const std::string& callId, const std::string& destination)
537 : {
538 10 : auto split = jami::split_string(destination, '/');
539 10 : if (split.size() != 4)
540 0 : return;
541 10 : auto conversationId = std::string(split[0]);
542 10 : auto accountUri = std::string(split[1]);
543 10 : auto deviceId = std::string(split[2]);
544 10 : auto confId = std::string(split[3]);
545 :
546 10 : if (getUsername() != accountUri || currentDeviceId() != deviceId)
547 0 : return;
548 :
549 : // Avoid concurrent checks in this part
550 10 : std::lock_guard lk(rdvMtx_);
551 10 : auto isNotHosting = !convModule()->isHosting(conversationId, confId);
552 10 : if (confId == "0") {
553 1 : auto currentCalls = convModule()->getActiveCalls(conversationId);
554 1 : if (!currentCalls.empty()) {
555 0 : confId = currentCalls[0]["id"];
556 0 : isNotHosting = false;
557 : } else {
558 1 : confId = callId;
559 4 : JAMI_DEBUG("No active call to join, create conference");
560 : }
561 1 : }
562 10 : auto preferences = convModule()->getConversationPreferences(conversationId);
563 10 : auto canHost = true;
564 : #if defined(__ANDROID__) || defined(__APPLE__)
565 : // By default, mobile devices SHOULD NOT host conferences.
566 : canHost = false;
567 : #endif
568 10 : auto itPref = preferences.find(ConversationPreferences::HOST_CONFERENCES);
569 10 : if (itPref != preferences.end()) {
570 0 : canHost = itPref->second == TRUE_STR;
571 : }
572 :
573 10 : auto call = getCall(callId);
574 10 : if (!call) {
575 0 : JAMI_ERROR("Call {} not found", callId);
576 0 : return;
577 : }
578 :
579 10 : if (isNotHosting && !canHost) {
580 0 : JAMI_DEBUG("Request for hosting a conference declined");
581 0 : Manager::instance().hangupCall(getAccountID(), callId);
582 0 : return;
583 : }
584 : // Due to the fact that in a conference, the host is not the one who
585 : // provides the initial sdp offer, the following block of code is responsible
586 : // for handling the medialist that the host will form his response with.
587 : // We always want the hosts response to be the same length as that of the
588 : // peer who is asking to join (providing the offer). A priori though the peer
589 : // doesn't know what active media streams the host will have so we deal with the
590 : // possible cases here.
591 10 : std::shared_ptr<Conference> conf;
592 10 : std::vector<libjami::MediaMap> currentMediaList;
593 10 : if (!isNotHosting) {
594 7 : conf = getConference(confId);
595 7 : if (!conf) {
596 0 : JAMI_ERROR("[conf:{}] Conference not found", confId);
597 0 : return;
598 : }
599 7 : auto hostMedias = conf->currentMediaList();
600 7 : auto sipCall = std::dynamic_pointer_cast<SIPCall>(call);
601 7 : if (hostMedias.empty()) {
602 0 : currentMediaList = MediaAttribute::mediaAttributesToMediaMaps(
603 0 : createDefaultMediaList(call->hasVideo(), true));
604 7 : } else if (hostMedias.size() < sipCall->getRtpSessionList().size()) {
605 : // First case: host has less media streams than the other person is joining
606 : // with. We need to add video media to the host before accepting the offer
607 : // This can happen if we host an audio call and someone joins with video
608 0 : currentMediaList = hostMedias;
609 0 : currentMediaList.push_back(
610 : {{libjami::Media::MediaAttributeKey::MEDIA_TYPE, libjami::Media::MediaAttributeValue::VIDEO},
611 : {libjami::Media::MediaAttributeKey::ENABLED, TRUE_STR},
612 : {libjami::Media::MediaAttributeKey::MUTED, TRUE_STR},
613 : {libjami::Media::MediaAttributeKey::SOURCE, ""},
614 : {libjami::Media::MediaAttributeKey::LABEL, "video_0"}});
615 : } else {
616 7 : bool hasVideo = false;
617 7 : if (sipCall) {
618 7 : const auto rtpSessions = sipCall->getRtpSessionList();
619 7 : hasVideo = std::any_of(rtpSessions.begin(), rtpSessions.end(), [](const auto& session) {
620 13 : return session && session->getMediaType() == MediaType::MEDIA_VIDEO;
621 : });
622 7 : }
623 : // The second case is that the host has the same or more media
624 : // streams than the person joining. In this case we match all their
625 : // medias to form our offer. They will then potentially join the call without seeing
626 : // seeing all of our medias. For now we deal with this by calling a
627 : // requestmediachange once they've joined.
628 14 : for (const auto& m : conf->currentMediaList()) {
629 : // We only expect to have 1 audio stream, add it.
630 13 : if (m.at(libjami::Media::MediaAttributeKey::MEDIA_TYPE) == libjami::Media::MediaAttributeValue::AUDIO) {
631 7 : currentMediaList.emplace_back(m);
632 6 : } else if (hasVideo
633 12 : && m.at(libjami::Media::MediaAttributeKey::MEDIA_TYPE)
634 6 : == libjami::Media::MediaAttributeValue::VIDEO) {
635 6 : currentMediaList.emplace_back(m);
636 6 : break;
637 : }
638 7 : }
639 : }
640 7 : }
641 10 : Manager::instance().acceptCall(*call, currentMediaList);
642 :
643 10 : if (isNotHosting) {
644 12 : JAMI_DEBUG("Creating conference for swarm {} with ID {}", conversationId, confId);
645 : // Create conference and host it.
646 3 : convModule()->hostConference(conversationId, confId, callId);
647 : } else {
648 28 : JAMI_DEBUG("Adding participant {} for swarm {} with ID {}", callId, conversationId, confId);
649 7 : Manager::instance().addAudio(*call);
650 7 : conf->addSubCall(callId);
651 7 : emitSignal<libjami::CallSignal::ConferenceChanged>(getAccountID(), conf->getConfId(), conf->getStateStr());
652 : }
653 10 : }
654 :
655 : std::shared_ptr<SIPCall>
656 50 : JamiAccount::createSubCall(const std::shared_ptr<SIPCall>& mainCall)
657 : {
658 50 : auto mediaList = MediaAttribute::mediaAttributesToMediaMaps(mainCall->getMediaAttributeList());
659 100 : return Manager::instance().callFactory.newSipCall(shared(), Call::CallType::OUTGOING, mediaList);
660 50 : }
661 :
662 : void
663 25 : JamiAccount::startOutgoingCall(const std::shared_ptr<SIPCall>& call, const std::string& toUri)
664 : {
665 25 : if (not accountManager_ or not dht_) {
666 0 : call->onFailure(PJSIP_SC_SERVICE_UNAVAILABLE);
667 0 : return;
668 : }
669 :
670 : // TODO: for now, we automatically trust all explicitly called peers
671 25 : setCertificateStatus(toUri, dhtnet::tls::TrustStore::PermissionStatus::ALLOWED);
672 :
673 25 : call->setState(Call::ConnectionState::TRYING);
674 25 : std::weak_ptr<SIPCall> wCall = call;
675 :
676 25 : accountManager_->lookupAddress(toUri,
677 25 : [wCall](const std::string& regName,
678 : const std::string& /*address*/,
679 : const NameDirectory::Response& response) {
680 25 : if (response == NameDirectory::Response::found)
681 1 : if (auto call = wCall.lock()) {
682 1 : call->setPeerRegisteredName(regName);
683 1 : }
684 25 : });
685 :
686 25 : dht::InfoHash peer_account(toUri);
687 25 : if (!peer_account) {
688 0 : throw std::invalid_argument("Invalid peer account: " + toUri);
689 : }
690 :
691 : // Call connected devices
692 25 : std::set<DeviceId> devices;
693 25 : std::unique_lock lkSipConn(sipConnsMtx_);
694 : // NOTE: dummyCall is a call used to avoid to mark the call as failed if the
695 : // cached connection is failing with ICE (close event still not detected).
696 25 : auto dummyCall = createSubCall(call);
697 :
698 25 : call->addSubCall(*dummyCall);
699 25 : dummyCall->setIceMedia(call->getIceMedia());
700 25 : auto sendRequest = [this, wCall, toUri, dummyCall = std::move(dummyCall)](const DeviceId& deviceId,
701 120 : bool eraseDummy) {
702 49 : if (eraseDummy) {
703 : // Mark the temp call as failed to stop the main call if necessary
704 25 : if (dummyCall)
705 25 : dummyCall->onFailure(PJSIP_SC_TEMPORARILY_UNAVAILABLE);
706 25 : return;
707 : }
708 24 : auto call = wCall.lock();
709 24 : if (not call)
710 0 : return;
711 24 : auto state = call->getConnectionState();
712 24 : if (state != Call::ConnectionState::PROGRESSING and state != Call::ConnectionState::TRYING)
713 0 : return;
714 :
715 24 : auto dev_call = createSubCall(call);
716 24 : dev_call->setPeerNumber(call->getPeerNumber());
717 24 : dev_call->setState(Call::ConnectionState::TRYING);
718 24 : call->addStateListener([w = weak(), deviceId](Call::CallState, Call::ConnectionState state, int) {
719 68 : if (state != Call::ConnectionState::PROGRESSING and state != Call::ConnectionState::TRYING) {
720 24 : if (auto shared = w.lock())
721 24 : shared->callConnectionClosed(deviceId, true);
722 24 : return false;
723 : }
724 44 : return true;
725 : });
726 24 : call->addSubCall(*dev_call);
727 24 : dev_call->setIceMedia(call->getIceMedia());
728 : {
729 24 : std::lock_guard lk(pendingCallsMutex_);
730 24 : pendingCalls_[deviceId].emplace_back(dev_call);
731 24 : }
732 :
733 96 : JAMI_WARNING("[call {}] No channeled socket with this peer. Send request", call->getCallId());
734 : // Else, ask for a channel (for future calls/text messages)
735 24 : const auto* type = call->hasVideo() ? "videoCall" : "audioCall";
736 24 : requestSIPConnection(toUri, deviceId, type, true, dev_call);
737 74 : };
738 :
739 25 : std::vector<std::shared_ptr<dhtnet::ChannelSocket>> channels;
740 27 : for (auto& [key, value] : sipConns_) {
741 2 : if (key.first != toUri)
742 1 : continue;
743 1 : if (value.empty())
744 0 : continue;
745 1 : auto& sipConn = value.back();
746 :
747 1 : if (!sipConn.channel) {
748 0 : JAMI_WARNING("A SIP transport exists without Channel, this is a bug. Please report");
749 0 : continue;
750 0 : }
751 :
752 1 : auto transport = sipConn.transport;
753 1 : auto remote_address = sipConn.channel->getRemoteAddress();
754 1 : if (!transport or !remote_address)
755 0 : continue;
756 :
757 1 : channels.emplace_back(sipConn.channel);
758 :
759 4 : JAMI_WARNING("[call {}] A channeled socket is detected with this peer.", call->getCallId());
760 :
761 1 : auto dev_call = createSubCall(call);
762 1 : dev_call->setPeerNumber(call->getPeerNumber());
763 1 : dev_call->setSipTransport(transport, getContactHeader(transport));
764 1 : call->addSubCall(*dev_call);
765 1 : dev_call->setIceMedia(call->getIceMedia());
766 :
767 : // Set the call in PROGRESSING State because the ICE session
768 : // is already ready. Note that this line should be after
769 : // addSubcall() to change the state of the main call
770 : // and avoid to get an active call in a TRYING state.
771 1 : dev_call->setState(Call::ConnectionState::PROGRESSING);
772 :
773 : {
774 1 : std::lock_guard lk(onConnectionClosedMtx_);
775 1 : onConnectionClosed_[key.second] = sendRequest;
776 1 : }
777 :
778 1 : call->addStateListener([w = weak(), deviceId = key.second](Call::CallState, Call::ConnectionState state, int) {
779 3 : if (state != Call::ConnectionState::PROGRESSING and state != Call::ConnectionState::TRYING) {
780 1 : if (auto shared = w.lock())
781 1 : shared->callConnectionClosed(deviceId, true);
782 1 : return false;
783 : }
784 2 : return true;
785 : });
786 :
787 : try {
788 1 : onConnectedOutgoingCall(dev_call, toUri, remote_address);
789 0 : } catch (const VoipLinkException&) {
790 : // In this case, the main scenario is that SIPStartCall failed because
791 : // the ICE is dead and the TLS session didn't send any packet on that dead
792 : // link (connectivity change, killed by the os, etc)
793 : // Here, we don't need to do anything, the TLS will fail and will delete
794 : // the cached transport
795 0 : continue;
796 0 : }
797 1 : devices.emplace(key.second);
798 1 : }
799 :
800 25 : lkSipConn.unlock();
801 : // Note: Send beacon can destroy the socket (if storing last occurence of shared_ptr)
802 : // causing sipConn to be destroyed. So, do it while sipConns_ not locked.
803 26 : for (const auto& channel : channels)
804 1 : channel->sendBeacon();
805 :
806 : // Find listening devices for this account
807 50 : accountManager_->forEachDevice(
808 : peer_account,
809 25 : [this, devices = std::move(devices), sendRequest](const std::shared_ptr<dht::crypto::PublicKey>& dev) {
810 : // Test if already sent via a SIP transport
811 24 : auto deviceId = dev->getLongId();
812 24 : if (devices.find(deviceId) != devices.end())
813 0 : return;
814 : {
815 24 : std::lock_guard lk(onConnectionClosedMtx_);
816 24 : onConnectionClosed_[deviceId] = sendRequest;
817 24 : }
818 24 : sendRequest(deviceId, false);
819 : },
820 25 : [wCall](bool ok) {
821 25 : if (not ok) {
822 2 : if (auto call = wCall.lock()) {
823 4 : JAMI_WARNING("[call:{}] No devices found", call->getCallId());
824 : // Note: if a P2P connection exists, the call will be at least in CONNECTING
825 1 : if (call->getConnectionState() == Call::ConnectionState::TRYING)
826 1 : call->onFailure(PJSIP_SC_TEMPORARILY_UNAVAILABLE);
827 2 : }
828 : }
829 25 : });
830 25 : }
831 :
832 : void
833 33 : JamiAccount::onConnectedOutgoingCall(const std::shared_ptr<SIPCall>& call,
834 : const std::string& to_id,
835 : dhtnet::IpAddr target)
836 : {
837 33 : if (!call)
838 0 : return;
839 132 : JAMI_LOG("[call:{}] Outgoing call connected to {}", call->getCallId(), to_id);
840 :
841 33 : const auto localAddress = dhtnet::ip_utils::getInterfaceAddr(getLocalInterface(), target.getFamily());
842 :
843 33 : dhtnet::IpAddr addrSdp = getPublishedSameasLocal() ? localAddress
844 33 : : connectionManager_->getPublishedIpAddress(target.getFamily());
845 :
846 : // fallback on local address
847 33 : if (not addrSdp)
848 0 : addrSdp = localAddress;
849 :
850 : // Building the local SDP offer
851 33 : auto& sdp = call->getSDP();
852 :
853 33 : sdp.setPublishedIP(addrSdp);
854 :
855 33 : auto mediaAttrList = call->getMediaAttributeList();
856 33 : if (mediaAttrList.empty()) {
857 0 : JAMI_ERROR("[call:{}] No media. Abort!", call->getCallId());
858 0 : return;
859 : }
860 :
861 33 : if (not sdp.createOffer(mediaAttrList)) {
862 0 : JAMI_ERROR("[call:{}] Unable to send outgoing INVITE request for new call", call->getCallId());
863 0 : return;
864 : }
865 :
866 : // Note: pj_ice_strans_create can call onComplete in the same thread
867 : // This means that iceMutex_ in IceTransport can be locked when onInitDone is called
868 : // So, we need to run the call creation in the main thread
869 : // Also, we do not directly call SIPStartCall before receiving onInitDone, because
870 : // there is an inside waitForInitialization that can block the thread.
871 : // Note: avoid runMainThread as SIPStartCall use transportMutex
872 33 : dht::ThreadPool::io().run([w = weak(), call = std::move(call), target] {
873 33 : auto account = w.lock();
874 33 : if (not account)
875 0 : return;
876 :
877 33 : if (not account->SIPStartCall(*call, target)) {
878 0 : JAMI_ERROR("[call:{}] Unable to send outgoing INVITE request for new call", call->getCallId());
879 : }
880 33 : });
881 33 : }
882 :
883 : bool
884 33 : JamiAccount::SIPStartCall(SIPCall& call, const dhtnet::IpAddr& target)
885 : {
886 132 : JAMI_LOG("[call:{}] Start SIP call", call.getCallId());
887 :
888 33 : if (call.isIceEnabled())
889 33 : call.addLocalIceAttributes();
890 :
891 : std::string toUri(
892 66 : getToUri(call.getPeerNumber() + "@" + target.toString(true))); // expecting a fully well formed sip uri
893 :
894 33 : pj_str_t pjTo = sip_utils::CONST_PJ_STR(toUri);
895 :
896 : // Create the from header
897 33 : std::string from(getFromUri());
898 33 : pj_str_t pjFrom = sip_utils::CONST_PJ_STR(from);
899 :
900 33 : std::string targetStr = getToUri(target.toString(true));
901 33 : pj_str_t pjTarget = sip_utils::CONST_PJ_STR(targetStr);
902 :
903 33 : auto contact = call.getContactHeader();
904 33 : auto pjContact = sip_utils::CONST_PJ_STR(contact);
905 :
906 132 : JAMI_LOG("[call:{}] Contact header: {} / {} -> {} / {}", call.getCallId(), contact, from, toUri, targetStr);
907 :
908 33 : auto* local_sdp = call.getSDP().getLocalSdpSession();
909 33 : pjsip_dialog* dialog {nullptr};
910 33 : pjsip_inv_session* inv {nullptr};
911 33 : if (!CreateClientDialogAndInvite(&pjFrom, &pjContact, &pjTo, &pjTarget, local_sdp, &dialog, &inv))
912 0 : return false;
913 :
914 33 : inv->mod_data[link_.getModId()] = &call;
915 33 : call.setInviteSession(inv);
916 :
917 : pjsip_tx_data* tdata;
918 :
919 33 : if (pjsip_inv_invite(call.inviteSession_.get(), &tdata) != PJ_SUCCESS) {
920 0 : JAMI_ERROR("[call:{}] Unable to initialize invite", call.getCallId());
921 0 : return false;
922 : }
923 :
924 : pjsip_tpselector tp_sel;
925 33 : tp_sel.type = PJSIP_TPSELECTOR_TRANSPORT;
926 33 : if (!call.getTransport()) {
927 0 : JAMI_ERROR("[call:{}] Unable to get transport", call.getCallId());
928 0 : return false;
929 : }
930 33 : tp_sel.u.transport = call.getTransport()->get();
931 33 : if (pjsip_dlg_set_transport(dialog, &tp_sel) != PJ_SUCCESS) {
932 0 : JAMI_ERROR("[call:{}] Unable to associate transport for invite session dialog", call.getCallId());
933 0 : return false;
934 : }
935 :
936 132 : JAMI_LOG("[call:{}] Sending SIP invite", call.getCallId());
937 :
938 : // Add user-agent header
939 33 : sip_utils::addUserAgentHeader(getUserAgentName(), tdata);
940 :
941 33 : if (pjsip_inv_send_msg(call.inviteSession_.get(), tdata) != PJ_SUCCESS) {
942 0 : JAMI_ERROR("[call:{}] Unable to send invite message", call.getCallId());
943 0 : return false;
944 : }
945 :
946 33 : call.setState(Call::CallState::ACTIVE, Call::ConnectionState::PROGRESSING);
947 33 : return true;
948 33 : }
949 :
950 : void
951 2281 : JamiAccount::saveConfig() const
952 : {
953 : try {
954 2281 : YAML::Emitter accountOut;
955 2281 : config().serialize(accountOut);
956 2281 : auto accountConfig = config().path / "config.yml";
957 2281 : std::lock_guard lock(dhtnet::fileutils::getFileLock(accountConfig));
958 2281 : std::ofstream fout(accountConfig);
959 2281 : fout.write(accountOut.c_str(), static_cast<std::streamsize>(accountOut.size()));
960 9124 : JAMI_LOG("Saved account config to {}", accountConfig);
961 2281 : } catch (const std::exception& e) {
962 0 : JAMI_ERROR("Error saving account config: {}", e.what());
963 0 : }
964 2281 : }
965 :
966 : void
967 699 : JamiAccount::loadConfig()
968 : {
969 699 : SIPAccountBase::loadConfig();
970 699 : registeredName_ = config().registeredName;
971 699 : if (accountManager_)
972 20 : accountManager_->setAccountDeviceName(config().deviceName);
973 699 : if (connectionManager_) {
974 16 : if (auto c = connectionManager_->getConfig()) {
975 : // Update connectionManager's config
976 16 : c->upnpEnabled = config().upnpEnabled;
977 16 : c->turnEnabled = config().turnEnabled;
978 16 : c->turnServer = config().turnServer;
979 16 : c->turnServerUserName = config().turnServerUserName;
980 16 : c->turnServerPwd = config().turnServerPwd;
981 16 : c->turnServerRealm = config().turnServerRealm;
982 16 : }
983 : }
984 699 : if (config().proxyEnabled) {
985 : try {
986 0 : auto str = fileutils::loadCacheTextFile(cachePath_ / "dhtproxy", std::chrono::hours(24 * 14));
987 0 : Json::Value root;
988 0 : if (json::parse(str, root)) {
989 0 : proxyServerCached_ = root[getProxyConfigKey()].asString();
990 : }
991 0 : } catch (const std::exception& e) {
992 0 : JAMI_LOG("[Account {}] Unable to load proxy URL from cache: {}", getAccountID(), e.what());
993 0 : proxyServerCached_.clear();
994 0 : }
995 : } else {
996 699 : proxyServerCached_.clear();
997 699 : std::error_code ec;
998 699 : std::filesystem::remove(cachePath_ / "dhtproxy", ec);
999 : }
1000 699 : if (not config().dhtProxyServerEnabled) {
1001 699 : dhtProxyServer_.reset();
1002 : }
1003 699 : auto credentials = consumeConfigCredentials();
1004 699 : loadAccount(credentials.archive_password_scheme, credentials.archive_password, credentials.archive_path);
1005 699 : }
1006 :
1007 : bool
1008 7 : JamiAccount::changeArchivePassword(const std::string& password_old, const std::string& password_new)
1009 : {
1010 : try {
1011 7 : if (!accountManager_->changePassword(password_old, password_new)) {
1012 8 : JAMI_ERROR("[Account {}] Unable to change archive password", getAccountID());
1013 2 : return false;
1014 : }
1015 10 : editConfig([&](JamiAccountConfig& config) { config.archiveHasPassword = not password_new.empty(); });
1016 0 : } catch (const std::exception& ex) {
1017 0 : JAMI_ERROR("[Account {}] Unable to change archive password: {}", getAccountID(), ex.what());
1018 0 : if (password_old.empty()) {
1019 0 : editConfig([&](JamiAccountConfig& config) { config.archiveHasPassword = true; });
1020 0 : emitSignal<libjami::ConfigurationSignal::AccountDetailsChanged>(getAccountID(), getAccountDetails());
1021 : }
1022 0 : return false;
1023 0 : }
1024 5 : if (password_old != password_new)
1025 5 : emitSignal<libjami::ConfigurationSignal::AccountDetailsChanged>(getAccountID(), getAccountDetails());
1026 5 : return true;
1027 : }
1028 :
1029 : bool
1030 3 : JamiAccount::isPasswordValid(const std::string& password)
1031 : {
1032 3 : return accountManager_ and accountManager_->isPasswordValid(password);
1033 : }
1034 :
1035 : std::vector<uint8_t>
1036 0 : JamiAccount::getPasswordKey(const std::string& password)
1037 : {
1038 0 : return accountManager_ ? accountManager_->getPasswordKey(password) : std::vector<uint8_t>();
1039 : }
1040 :
1041 : bool
1042 7 : JamiAccount::provideAccountAuthentication(const std::string& credentialsFromUser, const std::string& scheme)
1043 : {
1044 7 : if (auto manager = std::dynamic_pointer_cast<ArchiveAccountManager>(accountManager_)) {
1045 7 : return manager->provideAccountAuthentication(credentialsFromUser, scheme);
1046 7 : }
1047 0 : JAMI_ERR("[LinkDevice] Invalid AccountManager instance while providing current account "
1048 : "authentication.");
1049 0 : return false;
1050 : }
1051 :
1052 : int32_t
1053 5 : JamiAccount::addDevice(const std::string& uriProvided)
1054 : {
1055 20 : JAMI_LOG("[LinkDevice] JamiAccount::addDevice({}, {})", getAccountID(), uriProvided);
1056 5 : if (not accountManager_) {
1057 0 : JAMI_ERR("[LinkDevice] Invalid AccountManager instance while adding a device.");
1058 0 : return static_cast<int32_t>(AccountManager::AddDeviceError::GENERIC);
1059 : }
1060 5 : auto authHandler = channelHandlers_.find(Uri::Scheme::AUTH);
1061 5 : if (authHandler == channelHandlers_.end())
1062 0 : return static_cast<int32_t>(AccountManager::AddDeviceError::GENERIC);
1063 10 : return accountManager_->addDevice(uriProvided,
1064 5 : config().archiveHasPassword ? fileutils::ARCHIVE_AUTH_SCHEME_PASSWORD
1065 : : fileutils::ARCHIVE_AUTH_SCHEME_NONE,
1066 10 : (AuthChannelHandler*) authHandler->second.get());
1067 : }
1068 :
1069 : bool
1070 0 : JamiAccount::cancelAddDevice(uint32_t op_token)
1071 : {
1072 0 : if (!accountManager_)
1073 0 : return false;
1074 0 : return accountManager_->cancelAddDevice(op_token);
1075 : }
1076 :
1077 : bool
1078 4 : JamiAccount::confirmAddDevice(uint32_t op_token)
1079 : {
1080 4 : if (!accountManager_)
1081 0 : return false;
1082 4 : return accountManager_->confirmAddDevice(op_token);
1083 : }
1084 :
1085 : bool
1086 38 : JamiAccount::exportArchive(const std::string& destinationPath, std::string_view scheme, const std::string& password)
1087 : {
1088 38 : if (auto* manager = dynamic_cast<ArchiveAccountManager*>(accountManager_.get())) {
1089 38 : return manager->exportArchive(destinationPath, scheme, password);
1090 : }
1091 0 : return false;
1092 : }
1093 :
1094 : bool
1095 2 : JamiAccount::setValidity(std::string_view scheme, const std::string& pwd, const dht::InfoHash& id, int64_t validity)
1096 : {
1097 2 : if (auto* manager = dynamic_cast<ArchiveAccountManager*>(accountManager_.get())) {
1098 2 : if (manager->setValidity(scheme, pwd, id_, id, validity)) {
1099 2 : saveIdentity(id_, idPath_, DEVICE_ID_PATH);
1100 2 : return true;
1101 : }
1102 : }
1103 0 : return false;
1104 : }
1105 :
1106 : void
1107 4 : JamiAccount::forceReloadAccount()
1108 : {
1109 4 : editConfig([&](JamiAccountConfig& conf) {
1110 4 : conf.receipt.clear();
1111 4 : conf.receiptSignature.clear();
1112 4 : });
1113 4 : loadAccount();
1114 4 : }
1115 :
1116 : void
1117 2 : JamiAccount::unlinkConversations(const std::set<std::string>& removed)
1118 : {
1119 2 : std::lock_guard lock(configurationMutex_);
1120 2 : if (const auto* info = accountManager_->getInfo()) {
1121 2 : auto contacts = info->contacts->getContacts();
1122 4 : for (auto& [id, c] : contacts) {
1123 2 : if (removed.find(c.conversationId) != removed.end()) {
1124 1 : info->contacts->updateConversation(id, "");
1125 4 : JAMI_WARNING("[Account {}] Detected removed conversation ({}) in contact details for {}",
1126 : getAccountID(),
1127 : c.conversationId,
1128 : id.toString());
1129 : }
1130 : }
1131 2 : }
1132 2 : }
1133 :
1134 : bool
1135 1034 : JamiAccount::isValidAccountDevice(const dht::crypto::Certificate& cert) const
1136 : {
1137 1034 : if (accountManager_) {
1138 1034 : if (const auto* info = accountManager_->getInfo()) {
1139 1034 : if (info->contacts)
1140 1034 : return info->contacts->isValidAccountDevice(cert).isValid();
1141 : }
1142 : }
1143 0 : return false;
1144 : }
1145 :
1146 : bool
1147 3 : JamiAccount::revokeDevice(const std::string& device, std::string_view scheme, const std::string& password)
1148 : {
1149 3 : if (not accountManager_)
1150 0 : return false;
1151 3 : return accountManager_
1152 6 : ->revokeDevice(device, scheme, password, [this, device](AccountManager::RevokeDeviceResult result) {
1153 3 : emitSignal<libjami::ConfigurationSignal::DeviceRevocationEnded>(getAccountID(),
1154 3 : device,
1155 : static_cast<int>(result));
1156 6 : });
1157 : return true;
1158 : }
1159 :
1160 : std::pair<std::string, std::string>
1161 683 : JamiAccount::saveIdentity(const dht::crypto::Identity& id, const std::filesystem::path& path, const std::string& name)
1162 : {
1163 1366 : auto names = std::make_pair(name + ".key", name + ".crt");
1164 683 : if (id.first)
1165 683 : fileutils::saveFile(path / names.first, id.first->serialize(), 0600);
1166 683 : if (id.second)
1167 683 : fileutils::saveFile(path / names.second, id.second->getPacked(), 0600);
1168 683 : return names;
1169 0 : }
1170 :
1171 : void
1172 681 : JamiAccount::scheduleAccountReady() const
1173 : {
1174 681 : const auto accountId = getAccountID();
1175 1362 : runOnMainThread([accountId] { Manager::instance().markAccountReady(accountId); });
1176 681 : }
1177 :
1178 : AccountManager::OnChangeCallback
1179 699 : JamiAccount::setupAccountCallbacks()
1180 : {
1181 : return AccountManager::OnChangeCallback {
1182 162 : [this](const std::string& uri, bool confirmed) { onContactAdded(uri, confirmed); },
1183 23 : [this](const std::string& uri, bool banned) { onContactRemoved(uri, banned); },
1184 134 : [this](const std::string& uri,
1185 : const std::string& conversationId,
1186 : const std::vector<uint8_t>& payload,
1187 134 : time_t received) { onIncomingTrustRequest(uri, conversationId, payload, received); },
1188 2836 : [this](const std::map<DeviceId, KnownDevice>& devices) { onKnownDevicesChanged(devices); },
1189 84 : [this](const std::string& conversationId, const std::string& deviceId) {
1190 84 : onConversationRequestAccepted(conversationId, deviceId);
1191 84 : },
1192 737 : [this](const std::string& uri, const std::string& convFromReq) { onContactConfirmed(uri, convFromReq); }};
1193 : }
1194 :
1195 : void
1196 162 : JamiAccount::onContactAdded(const std::string& uri, bool confirmed)
1197 : {
1198 162 : if (!id_.first)
1199 3 : return;
1200 159 : if (jami::Manager::instance().syncOnRegister) {
1201 159 : dht::ThreadPool::io().run([w = weak(), uri, confirmed] {
1202 159 : if (auto shared = w.lock()) {
1203 159 : if (auto* cm = shared->convModule(true)) {
1204 159 : auto activeConv = cm->getOneToOneConversation(uri);
1205 159 : if (!activeConv.empty())
1206 159 : cm->bootstrap(activeConv);
1207 159 : }
1208 159 : emitSignal<libjami::ConfigurationSignal::ContactAdded>(shared->getAccountID(), uri, confirmed);
1209 159 : }
1210 159 : });
1211 : }
1212 : }
1213 :
1214 : void
1215 23 : JamiAccount::onContactRemoved(const std::string& uri, bool banned)
1216 : {
1217 23 : if (!id_.first)
1218 0 : return;
1219 23 : dht::ThreadPool::io().run([w = weak(), uri, banned] {
1220 23 : if (auto shared = w.lock()) {
1221 : // Erase linked conversation's requests
1222 23 : if (auto* convModule = shared->convModule(true))
1223 23 : convModule->removeContact(uri, banned);
1224 : // Remove current connections with contact
1225 : // Note: if contact is ourself, we don't close the connection
1226 : // because it's used for syncing other conversations.
1227 23 : if (shared->connectionManager_ && uri != shared->getUsername()) {
1228 22 : shared->connectionManager_->closeConnectionsWith(uri);
1229 : }
1230 : // Update client.
1231 23 : emitSignal<libjami::ConfigurationSignal::ContactRemoved>(shared->getAccountID(), uri, banned);
1232 23 : }
1233 23 : });
1234 : }
1235 :
1236 : void
1237 134 : JamiAccount::onIncomingTrustRequest(const std::string& uri,
1238 : const std::string& conversationId,
1239 : const std::vector<uint8_t>& payload,
1240 : time_t received)
1241 : {
1242 134 : if (!id_.first)
1243 0 : return;
1244 134 : dht::ThreadPool::io().run([w = weak(), uri, conversationId, payload, received] {
1245 134 : if (auto shared = w.lock()) {
1246 134 : shared->clearProfileCache(uri);
1247 134 : if (conversationId.empty()) {
1248 : // Old path
1249 0 : emitSignal<libjami::ConfigurationSignal::IncomingTrustRequest>(shared->getAccountID(),
1250 0 : conversationId,
1251 0 : uri,
1252 0 : payload,
1253 : received);
1254 0 : return;
1255 : }
1256 : // Here account can be initializing
1257 134 : if (auto* cm = shared->convModule(true)) {
1258 134 : auto activeConv = cm->getOneToOneConversation(uri);
1259 134 : if (activeConv != conversationId)
1260 95 : cm->onTrustRequest(uri, conversationId, payload, received);
1261 134 : }
1262 134 : }
1263 : });
1264 : }
1265 :
1266 : void
1267 2836 : JamiAccount::onKnownDevicesChanged(const std::map<DeviceId, KnownDevice>& devices)
1268 : {
1269 2836 : std::map<std::string, std::string> ids;
1270 1010593 : for (auto& d : devices) {
1271 1007764 : auto id = d.first.toString();
1272 1007714 : auto label = d.second.name.empty() ? id.substr(0, 8) : d.second.name;
1273 1007700 : ids.emplace(std::move(id), std::move(label));
1274 1007750 : }
1275 2834 : runOnMainThread([id = getAccountID(), devices = std::move(ids)] {
1276 2836 : emitSignal<libjami::ConfigurationSignal::KnownDevicesChanged>(id, devices);
1277 2836 : });
1278 2836 : }
1279 :
1280 : void
1281 84 : JamiAccount::onConversationRequestAccepted(const std::string& conversationId, const std::string& deviceId)
1282 : {
1283 : // Note: Do not retrigger on another thread. This has to be done
1284 : // at the same time of acceptTrustRequest a synced state between TrustRequest
1285 : // and convRequests.
1286 84 : if (auto* cm = convModule(true))
1287 84 : cm->acceptConversationRequest(conversationId, deviceId);
1288 84 : }
1289 :
1290 : void
1291 38 : JamiAccount::onContactConfirmed(const std::string& uri, const std::string& convFromReq)
1292 : {
1293 38 : dht::ThreadPool::io().run([w = weak(), convFromReq, uri] {
1294 38 : if (auto shared = w.lock()) {
1295 38 : shared->convModule(true);
1296 : // Remove cached payload if there is one
1297 76 : auto requestPath = shared->cachePath_ / "requests" / uri;
1298 38 : dhtnet::fileutils::remove(requestPath);
1299 76 : }
1300 38 : });
1301 38 : }
1302 :
1303 : std::unique_ptr<AccountManager::AccountCredentials>
1304 683 : JamiAccount::buildAccountCredentials(const JamiAccountConfig& conf,
1305 : const dht::crypto::Identity& id,
1306 : const std::string& archive_password_scheme,
1307 : const std::string& archive_password,
1308 : const std::string& archive_path,
1309 : bool& migrating,
1310 : bool& hasPassword)
1311 : {
1312 683 : std::unique_ptr<AccountManager::AccountCredentials> creds;
1313 :
1314 683 : if (conf.managerUri.empty()) {
1315 683 : auto acreds = std::make_unique<ArchiveAccountManager::ArchiveAccountCredentials>();
1316 683 : auto archivePath = fileutils::getFullPath(idPath_, conf.archivePath);
1317 :
1318 683 : if (!archive_path.empty()) {
1319 39 : acreds->scheme = "file";
1320 39 : acreds->uri = archive_path;
1321 644 : } else if (!conf.archive_url.empty() && conf.archive_url == "jami-auth") {
1322 20 : JAMI_DEBUG("[Account {}] [LinkDevice] scheme p2p & uri {}", getAccountID(), conf.archive_url);
1323 5 : acreds->scheme = "p2p";
1324 5 : acreds->uri = conf.archive_url;
1325 639 : } else if (std::filesystem::is_regular_file(archivePath)) {
1326 4 : acreds->scheme = "local";
1327 4 : acreds->uri = archivePath.string();
1328 4 : acreds->updateIdentity = id;
1329 4 : migrating = true;
1330 : }
1331 :
1332 683 : creds = std::move(acreds);
1333 683 : } else {
1334 0 : auto screds = std::make_unique<ServerAccountManager::ServerAccountCredentials>();
1335 0 : screds->username = conf.managerUsername;
1336 0 : creds = std::move(screds);
1337 0 : }
1338 :
1339 683 : creds->password = archive_password;
1340 683 : hasPassword = !archive_password.empty();
1341 1356 : creds->password_scheme = (hasPassword && archive_password_scheme.empty()) ? fileutils::ARCHIVE_AUTH_SCHEME_PASSWORD
1342 1356 : : archive_password_scheme;
1343 :
1344 683 : return creds;
1345 0 : }
1346 :
1347 : void
1348 681 : JamiAccount::onAuthenticationSuccess(bool migrating,
1349 : bool hasPassword,
1350 : const AccountInfo& info,
1351 : const std::map<std::string, std::string>& configMap,
1352 : std::string&& receipt,
1353 : std::vector<uint8_t>&& receiptSignature)
1354 : {
1355 2724 : JAMI_LOG("[Account {}] Auth success! Device: {}", getAccountID(), info.deviceId);
1356 :
1357 681 : dhtnet::fileutils::check_dir(idPath_, 0700);
1358 :
1359 681 : auto id = info.identity;
1360 681 : editConfig([&](JamiAccountConfig& conf) {
1361 681 : std::tie(conf.tlsPrivateKeyFile, conf.tlsCertificateFile) = saveIdentity(id, idPath_, DEVICE_ID_PATH);
1362 681 : conf.tlsPassword = {};
1363 :
1364 681 : auto passwordIt = configMap.find(libjami::Account::ConfProperties::ARCHIVE_HAS_PASSWORD);
1365 1362 : conf.archiveHasPassword = (passwordIt != configMap.end() && !passwordIt->second.empty())
1366 1362 : ? passwordIt->second == "true"
1367 0 : : hasPassword;
1368 :
1369 681 : if (not conf.managerUri.empty()) {
1370 0 : conf.registeredName = conf.managerUsername;
1371 0 : registeredName_ = conf.managerUsername;
1372 : }
1373 :
1374 681 : conf.username = info.accountId;
1375 681 : conf.deviceName = accountManager_->getAccountDeviceName();
1376 :
1377 681 : auto nameServerIt = configMap.find(libjami::Account::ConfProperties::Nameserver::URI);
1378 681 : if (nameServerIt != configMap.end() && !nameServerIt->second.empty())
1379 0 : conf.nameServer = nameServerIt->second;
1380 :
1381 681 : auto displayNameIt = configMap.find(libjami::Account::ConfProperties::DISPLAYNAME);
1382 681 : if (displayNameIt != configMap.end() && !displayNameIt->second.empty())
1383 41 : conf.displayName = displayNameIt->second;
1384 :
1385 681 : conf.receipt = std::move(receipt);
1386 681 : conf.receiptSignature = std::move(receiptSignature);
1387 681 : conf.fromMap(configMap);
1388 681 : });
1389 :
1390 681 : id_ = std::move(id);
1391 : {
1392 681 : std::lock_guard lk(moduleMtx_);
1393 681 : convModule_.reset();
1394 681 : }
1395 :
1396 681 : if (migrating)
1397 4 : Migration::setState(getAccountID(), Migration::State::SUCCESS);
1398 :
1399 681 : setRegistrationState(RegistrationState::UNREGISTERED);
1400 :
1401 681 : if (!info.photo.empty() || !info.displayName.empty()) {
1402 : try {
1403 0 : auto newProfile = vCard::utils::initVcard();
1404 0 : newProfile[std::string(vCard::Property::FORMATTED_NAME)] = info.displayName;
1405 0 : newProfile[std::string(vCard::Property::PHOTO)] = info.photo;
1406 :
1407 0 : const auto& profiles = idPath_ / "profiles";
1408 0 : const auto& vCardPath = profiles / fmt::format("{}.vcf", base64::encode(info.accountId));
1409 0 : vCard::utils::save(newProfile, vCardPath, profilePath());
1410 :
1411 0 : runOnMainThread([w = weak(), id = info.accountId, vCardPath] {
1412 0 : if (auto shared = w.lock()) {
1413 0 : emitSignal<libjami::ConfigurationSignal::ProfileReceived>(shared->getAccountID(),
1414 0 : id,
1415 0 : vCardPath.string());
1416 0 : }
1417 0 : });
1418 0 : } catch (const std::exception& e) {
1419 0 : JAMI_WARNING("[Account {}] Unable to save profile after authentication: {}", getAccountID(), e.what());
1420 0 : }
1421 : }
1422 :
1423 681 : doRegister();
1424 681 : scheduleAccountReady();
1425 681 : }
1426 :
1427 : void
1428 0 : JamiAccount::onAuthenticationError(const std::weak_ptr<JamiAccount>& w,
1429 : bool hadIdentity,
1430 : bool migrating,
1431 : std::string accountId,
1432 : AccountManager::AuthError error,
1433 : const std::string& message)
1434 : {
1435 0 : JAMI_WARNING("[Account {}] Auth error: {} {}", accountId, (int) error, message);
1436 :
1437 0 : if ((hadIdentity || migrating) && error == AccountManager::AuthError::INVALID_ARGUMENTS) {
1438 0 : Migration::setState(accountId, Migration::State::INVALID);
1439 0 : if (auto acc = w.lock())
1440 0 : acc->setRegistrationState(RegistrationState::ERROR_NEED_MIGRATION);
1441 0 : return;
1442 : }
1443 :
1444 0 : if (auto acc = w.lock())
1445 0 : acc->setRegistrationState(RegistrationState::ERROR_GENERIC);
1446 :
1447 0 : runOnMainThread([accountId = std::move(accountId)] { Manager::instance().removeAccount(accountId, true); });
1448 : }
1449 :
1450 : // must be called while configurationMutex_ is locked
1451 : void
1452 703 : JamiAccount::loadAccount(const std::string& archive_password_scheme,
1453 : const std::string& archive_password,
1454 : const std::string& archive_path)
1455 : {
1456 703 : if (registrationState_ == RegistrationState::INITIALIZING)
1457 20 : return;
1458 :
1459 2796 : JAMI_DEBUG("[Account {:s}] Loading account", getAccountID());
1460 :
1461 699 : const auto scheduleAccountReady = [accountId = getAccountID()] {
1462 16 : runOnMainThread([accountId] {
1463 16 : auto& manager = Manager::instance();
1464 16 : manager.markAccountReady(accountId);
1465 16 : });
1466 715 : };
1467 :
1468 699 : const auto& conf = config();
1469 699 : auto callbacks = setupAccountCallbacks();
1470 :
1471 : try {
1472 699 : auto oldIdentity = id_.first ? id_.first->getPublicKey().getLongId() : DeviceId();
1473 :
1474 699 : if (conf.managerUri.empty()) {
1475 2097 : accountManager_ = std::make_shared<ArchiveAccountManager>(
1476 699 : getAccountID(),
1477 : getPath(),
1478 42 : [this]() { return getAccountDetails(); },
1479 721 : [this](DeviceSync&& syncData) {
1480 721 : if (auto* sm = syncModule()) {
1481 721 : auto syncDataPtr = std::make_shared<SyncMsg>();
1482 721 : syncDataPtr->ds = std::move(syncData);
1483 721 : sm->syncWithConnected(syncDataPtr);
1484 721 : }
1485 721 : },
1486 1398 : conf.archivePath.empty() ? "archive.gz" : conf.archivePath,
1487 1398 : conf.nameServer);
1488 : } else {
1489 0 : accountManager_ = std::make_shared<ServerAccountManager>(getAccountID(),
1490 : getPath(),
1491 0 : conf.managerUri,
1492 0 : conf.nameServer);
1493 : }
1494 :
1495 699 : auto id = accountManager_->loadIdentity(conf.tlsCertificateFile, conf.tlsPrivateKeyFile, conf.tlsPassword);
1496 :
1497 699 : if (const auto* info
1498 699 : = accountManager_->useIdentity(id, conf.receipt, conf.receiptSignature, conf.managerUsername, callbacks)) {
1499 16 : id_ = std::move(id);
1500 16 : config_->username = info->accountId;
1501 64 : JAMI_WARNING("[Account {:s}] Loaded account identity", getAccountID());
1502 :
1503 16 : if (info->identity.first->getPublicKey().getLongId() != oldIdentity) {
1504 0 : JAMI_WARNING("[Account {:s}] Identity changed", getAccountID());
1505 : {
1506 0 : std::lock_guard lk(moduleMtx_);
1507 0 : convModule_.reset();
1508 0 : }
1509 0 : convModule();
1510 : } else {
1511 16 : convModule()->setAccountManager(accountManager_);
1512 : }
1513 :
1514 16 : convModule()->initPresence();
1515 16 : if (not isEnabled())
1516 0 : setRegistrationState(RegistrationState::UNREGISTERED);
1517 :
1518 16 : scheduleAccountReady();
1519 16 : return;
1520 : }
1521 :
1522 683 : if (!isEnabled())
1523 0 : return;
1524 :
1525 2732 : JAMI_WARNING("[Account {}] useIdentity failed!", getAccountID());
1526 :
1527 683 : if (not conf.managerUri.empty() && archive_password.empty()) {
1528 0 : Migration::setState(accountID_, Migration::State::INVALID);
1529 0 : setRegistrationState(RegistrationState::ERROR_NEED_MIGRATION);
1530 0 : return;
1531 : }
1532 :
1533 683 : bool migrating = registrationState_ == RegistrationState::ERROR_NEED_MIGRATION;
1534 683 : setRegistrationState(RegistrationState::INITIALIZING);
1535 :
1536 683 : auto fDeviceKey = dht::ThreadPool::computation().getShared<std::shared_ptr<dht::crypto::PrivateKey>>(
1537 2049 : []() { return std::make_shared<dht::crypto::PrivateKey>(dht::crypto::PrivateKey::generate()); });
1538 :
1539 683 : bool hasPassword = false;
1540 : auto creds = buildAccountCredentials(conf,
1541 : id,
1542 : archive_password_scheme,
1543 : archive_password,
1544 : archive_path,
1545 : migrating,
1546 683 : hasPassword);
1547 :
1548 2732 : JAMI_WARNING("[Account {}] initAuthentication {}", getAccountID(), fmt::ptr(this));
1549 :
1550 683 : const bool hadIdentity = static_cast<bool>(id.first);
1551 3415 : accountManager_->initAuthentication(
1552 : fDeviceKey,
1553 1366 : ip_utils::getDeviceName(),
1554 683 : std::move(creds),
1555 681 : [w = weak(), migrating, hasPassword](const AccountInfo& info,
1556 : const std::map<std::string, std::string>& configMap,
1557 : std::string&& receipt,
1558 681 : std::vector<uint8_t>&& receiptSignature) {
1559 681 : if (auto self = w.lock())
1560 1362 : self->onAuthenticationSuccess(migrating,
1561 : hasPassword,
1562 : info,
1563 : configMap,
1564 681 : std::move(receipt),
1565 1362 : std::move(receiptSignature));
1566 681 : },
1567 683 : [w = weak(), hadIdentity, accountId = getAccountID(), migrating](AccountManager::AuthError error,
1568 0 : const std::string& message) {
1569 0 : JamiAccount::onAuthenticationError(w, hadIdentity, migrating, accountId, error, message);
1570 0 : },
1571 : callbacks);
1572 699 : } catch (const std::exception& e) {
1573 0 : JAMI_WARNING("[Account {}] Error loading account: {}", getAccountID(), e.what());
1574 0 : accountManager_.reset();
1575 0 : setRegistrationState(RegistrationState::ERROR_GENERIC);
1576 0 : }
1577 715 : }
1578 :
1579 : std::map<std::string, std::string>
1580 4043 : JamiAccount::getVolatileAccountDetails() const
1581 : {
1582 4043 : auto a = SIPAccountBase::getVolatileAccountDetails();
1583 4043 : a.emplace(libjami::Account::VolatileProperties::InstantMessaging::OFF_CALL, TRUE_STR);
1584 4043 : auto registeredName = getRegisteredName();
1585 4043 : if (not registeredName.empty())
1586 3 : a.emplace(libjami::Account::VolatileProperties::REGISTERED_NAME, registeredName);
1587 4043 : a.emplace(libjami::Account::ConfProperties::PROXY_SERVER, proxyServerCached_);
1588 4043 : a.emplace(libjami::Account::VolatileProperties::DHT_BOUND_PORT, std::to_string(dhtBoundPort_));
1589 4043 : a.emplace(libjami::Account::VolatileProperties::DEVICE_ANNOUNCED, deviceAnnounced_ ? TRUE_STR : FALSE_STR);
1590 4043 : if (accountManager_) {
1591 4043 : if (const auto* info = accountManager_->getInfo()) {
1592 3316 : a.emplace(libjami::Account::ConfProperties::DEVICE_ID, info->deviceId);
1593 : }
1594 : }
1595 8086 : return a;
1596 4043 : }
1597 :
1598 : void
1599 3 : JamiAccount::lookupName(const std::string& name)
1600 : {
1601 3 : std::lock_guard lock(configurationMutex_);
1602 3 : if (accountManager_)
1603 9 : accountManager_->lookupUri(name,
1604 3 : config().nameServer,
1605 3 : [acc = getAccountID(), name](const std::string& regName,
1606 : const std::string& address,
1607 : NameDirectory::Response response) {
1608 6 : emitSignal<libjami::ConfigurationSignal::RegisteredNameFound>(acc,
1609 3 : name,
1610 : (int) response,
1611 : address,
1612 : regName);
1613 3 : });
1614 3 : }
1615 :
1616 : void
1617 3 : JamiAccount::lookupAddress(const std::string& addr)
1618 : {
1619 3 : std::lock_guard lock(configurationMutex_);
1620 3 : auto acc = getAccountID();
1621 3 : if (accountManager_)
1622 3 : accountManager_->lookupAddress(addr,
1623 3 : [acc, addr](const std::string& regName,
1624 : const std::string& address,
1625 : NameDirectory::Response response) {
1626 6 : emitSignal<libjami::ConfigurationSignal::RegisteredNameFound>(acc,
1627 3 : addr,
1628 : (int) response,
1629 : address,
1630 : regName);
1631 3 : });
1632 3 : }
1633 :
1634 : void
1635 1 : JamiAccount::registerName(const std::string& name, const std::string& scheme, const std::string& password)
1636 : {
1637 1 : std::lock_guard lock(configurationMutex_);
1638 1 : if (accountManager_)
1639 1 : accountManager_
1640 2 : ->registerName(name,
1641 : scheme,
1642 : password,
1643 1 : [acc = getAccountID(), name, w = weak()](NameDirectory::RegistrationResponse response,
1644 : const std::string& regName) {
1645 1 : auto res = (int) std::min(response, NameDirectory::RegistrationResponse::error);
1646 1 : if (response == NameDirectory::RegistrationResponse::success) {
1647 1 : if (auto this_ = w.lock()) {
1648 1 : if (this_->setRegisteredName(regName)) {
1649 1 : this_->editConfig(
1650 1 : [&](JamiAccountConfig& config) { config.registeredName = regName; });
1651 1 : emitSignal<libjami::ConfigurationSignal::VolatileDetailsChanged>(
1652 2 : this_->accountID_, this_->getVolatileAccountDetails());
1653 : }
1654 1 : }
1655 : }
1656 1 : emitSignal<libjami::ConfigurationSignal::NameRegistrationEnded>(acc, res, name);
1657 1 : });
1658 1 : }
1659 :
1660 : bool
1661 0 : JamiAccount::searchUser(const std::string& query)
1662 : {
1663 0 : if (accountManager_)
1664 0 : return accountManager_
1665 0 : ->searchUser(query,
1666 0 : [acc = getAccountID(), query](const jami::NameDirectory::SearchResult& result,
1667 : jami::NameDirectory::Response response) {
1668 0 : jami::emitSignal<libjami::ConfigurationSignal::UserSearchEnded>(acc,
1669 : (int) response,
1670 0 : query,
1671 : result);
1672 0 : });
1673 0 : return false;
1674 : }
1675 :
1676 : void
1677 66 : JamiAccount::forEachPendingCall(const DeviceId& deviceId, const std::function<void(const std::shared_ptr<SIPCall>&)>& cb)
1678 : {
1679 66 : std::vector<std::shared_ptr<SIPCall>> pc;
1680 : {
1681 66 : std::lock_guard lk(pendingCallsMutex_);
1682 66 : pc = std::move(pendingCalls_[deviceId]);
1683 66 : }
1684 98 : for (const auto& pendingCall : pc) {
1685 32 : cb(pendingCall);
1686 : }
1687 66 : }
1688 :
1689 : void
1690 600 : JamiAccount::registerAsyncOps()
1691 : {
1692 600 : loadCachedProxyServer([w = weak()](const std::string&) {
1693 600 : runOnMainThread([w] {
1694 600 : if (auto s = w.lock()) {
1695 600 : std::lock_guard lock(s->configurationMutex_);
1696 600 : s->doRegister_();
1697 1200 : }
1698 600 : });
1699 600 : });
1700 600 : }
1701 :
1702 : void
1703 1419 : JamiAccount::doRegister()
1704 : {
1705 1419 : std::lock_guard lock(configurationMutex_);
1706 1419 : if (not isUsable()) {
1707 560 : JAMI_WARNING("[Account {:s}] Account must be enabled and active to register, ignoring", getAccountID());
1708 140 : return;
1709 : }
1710 :
1711 5116 : JAMI_LOG("[Account {:s}] Starting account…", getAccountID());
1712 :
1713 : // invalid state transitions:
1714 : // INITIALIZING: generating/loading certificates, unable to register
1715 : // NEED_MIGRATION: old account detected, user needs to migrate
1716 1279 : if (registrationState_ == RegistrationState::INITIALIZING
1717 600 : || registrationState_ == RegistrationState::ERROR_NEED_MIGRATION)
1718 679 : return;
1719 :
1720 600 : convModule(); // Init conv module before passing in trying
1721 600 : setRegistrationState(RegistrationState::TRYING);
1722 600 : if (proxyServerCached_.empty()) {
1723 600 : registerAsyncOps();
1724 : } else {
1725 0 : doRegister_();
1726 : }
1727 1419 : }
1728 :
1729 : std::vector<std::string>
1730 600 : JamiAccount::loadBootstrap() const
1731 : {
1732 600 : std::vector<std::string> bootstrap;
1733 600 : std::string_view stream(config().hostname), node_addr;
1734 1200 : while (jami::getline(stream, node_addr, ';'))
1735 600 : bootstrap.emplace_back(node_addr);
1736 1200 : for (const auto& b : bootstrap)
1737 600 : JAMI_DBG("[Account %s] Bootstrap node: %s", getAccountID().c_str(), b.c_str());
1738 1200 : return bootstrap;
1739 0 : }
1740 :
1741 : void
1742 34 : JamiAccount::trackBuddyPresence(const std::string& buddy_id, bool track)
1743 : {
1744 34 : std::string buddyUri;
1745 : try {
1746 34 : buddyUri = parseJamiUri(buddy_id);
1747 0 : } catch (...) {
1748 0 : JAMI_ERROR("[Account {:s}] Failed to track presence: invalid URI {:s}", getAccountID(), buddy_id);
1749 0 : return;
1750 0 : }
1751 136 : JAMI_LOG("[Account {:s}] {:s} presence for {:s}", getAccountID(), track ? "Track" : "Untrack", buddy_id);
1752 :
1753 34 : if (!presenceManager_)
1754 0 : return;
1755 :
1756 34 : if (track) {
1757 34 : presenceManager_->trackBuddy(buddyUri);
1758 34 : std::lock_guard lock(presenceStateMtx_);
1759 34 : auto it = presenceState_.find(buddyUri);
1760 34 : if (it != presenceState_.end() && it->second != PresenceState::DISCONNECTED) {
1761 1 : emitSignal<libjami::PresenceSignal::NewBuddyNotification>(getAccountID(),
1762 : buddyUri,
1763 1 : static_cast<int>(it->second),
1764 : "");
1765 : }
1766 34 : } else {
1767 0 : presenceManager_->untrackBuddy(buddyUri);
1768 : }
1769 34 : }
1770 :
1771 : std::map<std::string, bool>
1772 2 : JamiAccount::getTrackedBuddyPresence() const
1773 : {
1774 2 : if (!presenceManager_)
1775 0 : return {};
1776 2 : return presenceManager_->getTrackedBuddyPresence();
1777 : }
1778 :
1779 : void
1780 582 : JamiAccount::onTrackedBuddyOnline(const std::string& contactId)
1781 : {
1782 2328 : JAMI_DEBUG("[Account {:s}] Buddy {} online", getAccountID(), contactId);
1783 582 : std::lock_guard lock(presenceStateMtx_);
1784 582 : auto& state = presenceState_[contactId];
1785 582 : if (state < PresenceState::AVAILABLE) {
1786 436 : state = PresenceState::AVAILABLE;
1787 436 : emitSignal<libjami::PresenceSignal::NewBuddyNotification>(getAccountID(),
1788 : contactId,
1789 : static_cast<int>(PresenceState::AVAILABLE),
1790 : "");
1791 : }
1792 :
1793 582 : if (auto details = getContactInfo(contactId)) {
1794 98 : if (!details->confirmed) {
1795 54 : auto convId = convModule()->getOneToOneConversation(contactId);
1796 54 : if (convId.empty())
1797 2 : return;
1798 : // In this case, the TrustRequest was sent but never confirmed (cause the contact was
1799 : // offline maybe) To avoid the contact to never receive the conv request, retry there
1800 52 : std::lock_guard lock(configurationMutex_);
1801 52 : if (accountManager_) {
1802 : // Retrieve cached payload for trust request.
1803 104 : auto requestPath = cachePath_ / "requests" / contactId;
1804 52 : std::vector<uint8_t> payload;
1805 : try {
1806 62 : payload = fileutils::loadFile(requestPath);
1807 10 : } catch (...) {
1808 10 : }
1809 52 : if (payload.size() >= 64000) {
1810 4 : JAMI_WARNING("[Account {:s}] Trust request for contact {:s} is too big, reset payload",
1811 : getAccountID(),
1812 : contactId);
1813 1 : payload.clear();
1814 : }
1815 52 : accountManager_->sendTrustRequest(contactId, convId, payload);
1816 52 : }
1817 54 : }
1818 582 : }
1819 582 : }
1820 :
1821 : void
1822 62 : JamiAccount::onTrackedBuddyOffline(const std::string& contactId)
1823 : {
1824 248 : JAMI_DEBUG("[Account {:s}] Buddy {} offline", getAccountID(), contactId);
1825 62 : std::lock_guard lock(presenceStateMtx_);
1826 62 : auto& state = presenceState_[contactId];
1827 62 : if (state > PresenceState::DISCONNECTED) {
1828 62 : if (state == PresenceState::CONNECTED) {
1829 0 : JAMI_WARNING("[Account {:s}] Buddy {} is not present on the DHT, but P2P connected",
1830 : getAccountID(),
1831 : contactId);
1832 0 : return;
1833 : }
1834 62 : state = PresenceState::DISCONNECTED;
1835 62 : emitSignal<libjami::PresenceSignal::NewBuddyNotification>(getAccountID(),
1836 : contactId,
1837 : static_cast<int>(PresenceState::DISCONNECTED),
1838 : "");
1839 : }
1840 62 : }
1841 :
1842 : void
1843 600 : JamiAccount::doRegister_()
1844 : {
1845 600 : if (registrationState_ != RegistrationState::TRYING) {
1846 0 : JAMI_ERROR("[Account {}] Already registered", getAccountID());
1847 0 : return;
1848 : }
1849 :
1850 2400 : JAMI_DEBUG("[Account {}] Starting account…", getAccountID());
1851 600 : const auto& conf = config();
1852 :
1853 : try {
1854 600 : if (not accountManager_ or not accountManager_->getInfo())
1855 0 : throw std::runtime_error("No identity configured for this account.");
1856 :
1857 600 : if (dht_->isRunning()) {
1858 8 : JAMI_ERROR("[Account {}] DHT already running (stopping it first).", getAccountID());
1859 2 : dht_->join();
1860 : }
1861 :
1862 600 : convModule()->clearPendingFetch();
1863 :
1864 : // Look for registered name
1865 600 : accountManager_->lookupAddress(accountManager_->getInfo()->accountId,
1866 600 : [w = weak()](const std::string& regName,
1867 : const std::string& /*address*/,
1868 : const NameDirectory::Response& response) {
1869 600 : if (auto this_ = w.lock())
1870 600 : this_->lookupRegisteredName(regName, response);
1871 600 : });
1872 :
1873 600 : dht::DhtRunner::Config config = initDhtConfig(conf);
1874 :
1875 : // check if dht peer service is enabled
1876 600 : if (conf.accountPeerDiscovery or conf.accountPublish) {
1877 0 : peerDiscovery_ = std::make_shared<dht::PeerDiscovery>();
1878 0 : if (conf.accountPeerDiscovery) {
1879 0 : JAMI_LOG("[Account {}] Starting Jami account discovery…", getAccountID());
1880 0 : startAccountDiscovery();
1881 : }
1882 0 : if (conf.accountPublish)
1883 0 : startAccountPublish();
1884 : }
1885 :
1886 600 : dht::DhtRunner::Context context = initDhtContext();
1887 :
1888 600 : dht_->run(conf.dhtPort, config, std::move(context));
1889 600 : dhtBoundPort_ = dht_->getBoundPort();
1890 :
1891 : // Now that the DHT is running and we know the actual bound port,
1892 : // request a UPnP mapping for it.
1893 600 : if (upnpCtrl_) {
1894 2304 : JAMI_LOG("[Account {:s}] UPnP: requesting mapping for DHT port {}", getAccountID(), dhtBoundPort_);
1895 :
1896 576 : if (dhtUpnpMapping_.isValid()) {
1897 0 : upnpCtrl_->releaseMapping(dhtUpnpMapping_);
1898 : }
1899 :
1900 576 : dhtUpnpMapping_.enableAutoUpdate(true);
1901 :
1902 576 : dhtnet::upnp::Mapping desired(dhtnet::upnp::PortType::UDP, dhtBoundPort_, dhtBoundPort_);
1903 576 : dhtUpnpMapping_.updateFrom(desired);
1904 :
1905 576 : dhtUpnpMapping_.setNotifyCallback([w = weak()](const dhtnet::upnp::Mapping::sharedPtr_t& mapRes) {
1906 551 : if (auto accPtr = w.lock()) {
1907 551 : auto& dhtMap = accPtr->dhtUpnpMapping_;
1908 551 : const auto& accId = accPtr->getAccountID();
1909 :
1910 2204 : JAMI_LOG("[Account {:s}] DHT UPnP mapping changed to {:s}", accId, mapRes->toString(true));
1911 :
1912 551 : if (dhtMap.getMapKey() != mapRes->getMapKey() or dhtMap.getState() != mapRes->getState()) {
1913 544 : dhtMap.updateFrom(mapRes);
1914 544 : if (mapRes->getState() == dhtnet::upnp::MappingState::OPEN) {
1915 0 : JAMI_LOG("[Account {:s}] Mapping {:s} successfully allocated", accId, dhtMap.toString());
1916 0 : accPtr->dht_->connectivityChanged();
1917 544 : } else if (mapRes->getState() == dhtnet::upnp::MappingState::FAILED) {
1918 2176 : JAMI_WARNING("[Account {:s}] UPnP mapping failed", accId);
1919 : }
1920 : } else {
1921 7 : dhtMap.updateFrom(mapRes);
1922 : }
1923 551 : }
1924 551 : });
1925 :
1926 576 : upnpCtrl_->reserveMapping(dhtUpnpMapping_);
1927 576 : }
1928 :
1929 1200 : for (const auto& bootstrap : loadBootstrap())
1930 1200 : dht_->bootstrap(bootstrap);
1931 :
1932 600 : accountManager_->setDht(dht_);
1933 :
1934 600 : if (conf.dhtProxyServerEnabled) {
1935 0 : dht::ProxyServerConfig proxyConfig;
1936 0 : proxyConfig.port = conf.dhtProxyServerPort;
1937 0 : proxyConfig.identity = id_;
1938 0 : dhtProxyServer_ = std::make_shared<dht::DhtProxyServer>(dht_, proxyConfig);
1939 0 : } else {
1940 600 : dhtProxyServer_.reset();
1941 : }
1942 :
1943 600 : std::unique_lock lkCM(connManagerMtx_);
1944 600 : initConnectionManager();
1945 600 : connectionManager_->dhtStarted();
1946 1311 : connectionManager_->onICERequest([this](const DeviceId& deviceId) { return onICERequest(deviceId); });
1947 600 : connectionManager_->onChannelRequest([this](const std::shared_ptr<dht::crypto::Certificate>& cert,
1948 2041 : const std::string& name) { return onChannelRequest(cert, name); });
1949 600 : connectionManager_->onConnectionReady(
1950 3884 : [this](const DeviceId& deviceId, const std::string& name, std::shared_ptr<dhtnet::ChannelSocket> channel) {
1951 3884 : onConnectionReady(deviceId, name, std::move(channel));
1952 3883 : });
1953 600 : lkCM.unlock();
1954 :
1955 600 : if (!conf.managerUri.empty() && accountManager_) {
1956 0 : dynamic_cast<ServerAccountManager*>(accountManager_.get())->onNeedsMigration([this]() {
1957 0 : editConfig([&](JamiAccountConfig& conf) {
1958 0 : conf.receipt.clear();
1959 0 : conf.receiptSignature.clear();
1960 0 : });
1961 0 : Migration::setState(accountID_, Migration::State::INVALID);
1962 0 : setRegistrationState(RegistrationState::ERROR_NEED_MIGRATION);
1963 0 : });
1964 0 : dynamic_cast<ServerAccountManager*>(accountManager_.get())
1965 0 : ->syncBlueprintConfig([this](const std::map<std::string, std::string>& config) {
1966 0 : editConfig([&](JamiAccountConfig& conf) { conf.fromMap(config); });
1967 0 : emitSignal<libjami::ConfigurationSignal::AccountDetailsChanged>(getAccountID(), getAccountDetails());
1968 0 : });
1969 : }
1970 :
1971 600 : if (presenceManager_)
1972 600 : presenceManager_->refresh();
1973 600 : } catch (const std::exception& e) {
1974 0 : JAMI_ERR("Error registering DHT account: %s", e.what());
1975 0 : setRegistrationState(RegistrationState::ERROR_GENERIC);
1976 0 : }
1977 : }
1978 :
1979 : void
1980 598 : JamiAccount::lookupRegisteredName(const std::string& regName, const NameDirectory::Response& response)
1981 : {
1982 598 : if (response == NameDirectory::Response::found or response == NameDirectory::Response::notFound) {
1983 598 : const auto& nameResult = response == NameDirectory::Response::found ? regName : "";
1984 598 : if (setRegisteredName(nameResult)) {
1985 0 : editConfig([&](JamiAccountConfig& config) { config.registeredName = nameResult; });
1986 0 : emitSignal<libjami::ConfigurationSignal::VolatileDetailsChanged>(accountID_, getVolatileAccountDetails());
1987 : }
1988 598 : }
1989 598 : }
1990 :
1991 : dht::DhtRunner::Config
1992 600 : JamiAccount::initDhtConfig(const JamiAccountConfig& conf)
1993 : {
1994 600 : dht::DhtRunner::Config config {};
1995 600 : config.dht_config.node_config.network = 0;
1996 600 : config.dht_config.node_config.maintain_storage = false;
1997 600 : config.dht_config.node_config.persist_path = (cachePath_ / "dhtstate").string();
1998 600 : config.dht_config.id = id_;
1999 600 : config.dht_config.cert_cache_all = true;
2000 600 : config.push_node_id = getAccountID();
2001 600 : config.push_token = conf.deviceKey;
2002 600 : config.push_topic = conf.notificationTopic;
2003 600 : config.push_platform = conf.platform;
2004 600 : config.proxy_user_agent = jami::userAgent();
2005 600 : config.threaded = true;
2006 600 : config.peer_discovery = conf.dhtPeerDiscovery;
2007 600 : config.peer_publish = conf.dhtPeerDiscovery;
2008 600 : if (conf.proxyEnabled)
2009 0 : config.proxy_server = proxyServerCached_;
2010 :
2011 600 : if (not config.proxy_server.empty()) {
2012 0 : JAMI_LOG("[Account {}] Using proxy server {}", getAccountID(), config.proxy_server);
2013 0 : if (not config.push_token.empty()) {
2014 0 : JAMI_LOG("[Account {}] using push notifications with platform: {}, topic: {}, token: {}",
2015 : getAccountID(),
2016 : config.push_platform,
2017 : config.push_topic,
2018 : config.push_token);
2019 : }
2020 : }
2021 600 : return config;
2022 0 : }
2023 :
2024 : dht::DhtRunner::Context
2025 600 : JamiAccount::initDhtContext()
2026 : {
2027 600 : dht::DhtRunner::Context context {};
2028 600 : context.peerDiscovery = peerDiscovery_;
2029 600 : context.rng = std::make_unique<std::mt19937_64>(dht::crypto::getDerivedRandomEngine(rand));
2030 :
2031 600 : auto dht_log_level = Manager::instance().dhtLogLevel;
2032 600 : if (dht_log_level > 0) {
2033 0 : context.logger = logger_;
2034 : }
2035 :
2036 21 : context.certificateStore = [&](const dht::InfoHash& pk_id) {
2037 21 : std::vector<std::shared_ptr<dht::crypto::Certificate>> ret;
2038 42 : if (auto cert = certStore().getCertificate(pk_id.toString()))
2039 21 : ret.emplace_back(std::move(cert));
2040 84 : JAMI_LOG("[Account {}] Query for local certificate store: {}: {} found.",
2041 : getAccountID(),
2042 : pk_id.toString(),
2043 : ret.size());
2044 21 : return ret;
2045 600 : };
2046 :
2047 1264 : context.certificateStorePkId = [&](const DeviceId& pk_id) {
2048 1264 : std::vector<std::shared_ptr<dht::crypto::Certificate>> ret;
2049 2528 : if (auto cert = certStore().getCertificate(pk_id.toString()))
2050 1264 : ret.emplace_back(std::move(cert));
2051 5056 : JAMI_LOG("[Account {}] Query for local certificate store: {}: {} found.",
2052 : getAccountID(),
2053 : pk_id.toString(),
2054 : ret.size());
2055 1264 : return ret;
2056 600 : };
2057 :
2058 5169 : context.statusChangedCallback = [this](dht::NodeStatus s4, dht::NodeStatus s6) {
2059 6892 : JAMI_LOG("[Account {}] DHT status: IPv4 {}; IPv6 {}", getAccountID(), dhtStatusStr(s4), dhtStatusStr(s6));
2060 : RegistrationState state;
2061 1723 : auto newStatus = std::max(s4, s6);
2062 1723 : switch (newStatus) {
2063 579 : case dht::NodeStatus::Connecting:
2064 579 : state = RegistrationState::TRYING;
2065 579 : break;
2066 1144 : case dht::NodeStatus::Connected:
2067 1144 : state = RegistrationState::REGISTERED;
2068 1144 : break;
2069 0 : case dht::NodeStatus::Disconnected:
2070 0 : state = RegistrationState::UNREGISTERED;
2071 0 : break;
2072 0 : default:
2073 0 : state = RegistrationState::ERROR_GENERIC;
2074 0 : break;
2075 : }
2076 :
2077 1723 : setRegistrationState(state);
2078 2323 : };
2079 :
2080 2931 : context.identityAnnouncedCb = [this](bool ok) {
2081 594 : if (!ok) {
2082 52 : JAMI_ERROR("[Account {}] Identity announcement failed", getAccountID());
2083 13 : return;
2084 : }
2085 2324 : JAMI_WARNING("[Account {}] Identity announcement succeeded", getAccountID());
2086 581 : accountManager_
2087 1818 : ->startSync([this](const std::shared_ptr<dht::crypto::Certificate>& crt) { onAccountDeviceFound(crt); },
2088 581 : [this] { onAccountDeviceAnnounced(); },
2089 581 : publishPresence_);
2090 600 : };
2091 :
2092 600 : return context;
2093 0 : }
2094 :
2095 : void
2096 656 : JamiAccount::onAccountDeviceFound(const std::shared_ptr<dht::crypto::Certificate>& crt)
2097 : {
2098 656 : if (jami::Manager::instance().syncOnRegister) {
2099 656 : if (!crt)
2100 584 : return;
2101 656 : auto deviceId = crt->getLongId().toString();
2102 656 : if (accountManager_->getInfo()->deviceId == deviceId)
2103 584 : return;
2104 :
2105 72 : dht::ThreadPool::io().run([w = weak(), deviceId, crt] {
2106 72 : auto shared = w.lock();
2107 72 : if (!shared)
2108 0 : return;
2109 72 : std::unique_lock lk(shared->connManagerMtx_);
2110 72 : shared->initConnectionManager();
2111 72 : lk.unlock();
2112 72 : std::shared_lock slk(shared->connManagerMtx_);
2113 : // NOTE: connectionManager_ and channelHandlers_ get initialized at the
2114 : // same time and are both protected by connManagerMtx_, so this check
2115 : // ensures that the access to channelHandlers_ below is valid.
2116 72 : if (!shared->connectionManager_)
2117 0 : return;
2118 72 : shared->requestMessageConnection(shared->getUsername(), crt->getLongId(), "sync");
2119 72 : if (!shared->syncModule()->isConnected(crt->getLongId())) {
2120 71 : shared->channelHandlers_[Uri::Scheme::SYNC]
2121 71 : ->connect(crt->getLongId(),
2122 : "",
2123 71 : [](const std::shared_ptr<dhtnet::ChannelSocket>& /*socket*/,
2124 71 : const DeviceId& /*deviceId*/) {});
2125 : }
2126 72 : });
2127 656 : }
2128 : }
2129 :
2130 : void
2131 581 : JamiAccount::onAccountDeviceAnnounced()
2132 : {
2133 581 : if (jami::Manager::instance().syncOnRegister) {
2134 581 : deviceAnnounced_ = true;
2135 :
2136 : // Bootstrap at the end to avoid to be long to load.
2137 581 : dht::ThreadPool::io().run([w = weak()] {
2138 581 : if (auto shared = w.lock())
2139 581 : shared->convModule()->bootstrap();
2140 581 : });
2141 581 : emitSignal<libjami::ConfigurationSignal::VolatileDetailsChanged>(accountID_, getVolatileAccountDetails());
2142 : }
2143 581 : }
2144 :
2145 : bool
2146 711 : JamiAccount::onICERequest(const DeviceId& deviceId)
2147 : {
2148 711 : std::promise<bool> accept;
2149 711 : std::future<bool> fut = accept.get_future();
2150 711 : accountManager_->findCertificate(deviceId, [this, &accept](const std::shared_ptr<dht::crypto::Certificate>& cert) {
2151 711 : if (!cert) {
2152 0 : accept.set_value(false);
2153 0 : return;
2154 : }
2155 711 : dht::InfoHash peer_account_id;
2156 711 : auto res = accountManager_->onPeerCertificate(cert, this->config().dhtPublicInCalls, peer_account_id);
2157 2844 : JAMI_LOG("[Account {}] [device {}] {} ICE request from {}",
2158 : getAccountID(),
2159 : cert->getLongId(),
2160 : res ? "Accepting" : "Discarding",
2161 : peer_account_id);
2162 711 : accept.set_value(res);
2163 : });
2164 711 : fut.wait();
2165 711 : auto result = fut.get();
2166 711 : return result;
2167 711 : }
2168 :
2169 : bool
2170 2041 : JamiAccount::onChannelRequest(const std::shared_ptr<dht::crypto::Certificate>& cert, const std::string& name)
2171 : {
2172 8159 : JAMI_LOG("[Account {}] [device {}] New channel requested: '{}'", getAccountID(), cert->getLongId(), name);
2173 :
2174 2041 : if (this->config().turnEnabled && turnCache_) {
2175 2040 : auto addr = turnCache_->getResolvedTurn();
2176 2041 : if (addr == std::nullopt) {
2177 : // If TURN is enabled, but no TURN cached, there can be a temporary
2178 : // resolution error to solve. Sometimes, a connectivity change is not
2179 : // enough, so even if this case is really rare, it should be easy to avoid.
2180 11 : turnCache_->refresh();
2181 : }
2182 : }
2183 :
2184 2040 : auto uri = Uri(name);
2185 2041 : std::shared_lock lk(connManagerMtx_);
2186 2038 : auto itHandler = channelHandlers_.find(uri.scheme());
2187 2040 : if (itHandler != channelHandlers_.end() && itHandler->second)
2188 1990 : return itHandler->second->onRequest(cert, name);
2189 47 : return name == "sip";
2190 2040 : }
2191 :
2192 : void
2193 3884 : JamiAccount::onConnectionReady(const DeviceId& deviceId,
2194 : const std::string& name,
2195 : std::shared_ptr<dhtnet::ChannelSocket> channel)
2196 : {
2197 3884 : if (channel) {
2198 3881 : auto cert = channel->peerCertificate();
2199 3883 : if (!cert || !cert->issuer)
2200 0 : return;
2201 3882 : auto peerId = cert->issuer->getId().toString();
2202 : // A connection request can be sent just before member is banned and this must be ignored.
2203 3882 : if (accountManager()->getCertificateStatus(peerId) == dhtnet::tls::TrustStore::PermissionStatus::BANNED) {
2204 66 : channel->shutdown();
2205 66 : return;
2206 : }
2207 3818 : if (name == "sip") {
2208 66 : cacheSIPConnection(std::move(channel), peerId, deviceId);
2209 3751 : } else if (name.find("git://") == 0) {
2210 1660 : auto sep = name.find_last_of('/');
2211 1660 : auto conversationId = name.substr(sep + 1);
2212 1660 : auto remoteDevice = name.substr(6, sep - 6);
2213 :
2214 1660 : if (channel->isInitiator()) {
2215 : // Check if wanted remote is our side (git://remoteDevice/conversationId)
2216 827 : return;
2217 : }
2218 :
2219 : // Check if pull from banned device
2220 833 : if (convModule()->isBanned(conversationId, remoteDevice)) {
2221 0 : JAMI_WARNING("[Account {:s}] [Conversation {}] Git server requested, but the "
2222 : "device is unauthorized ({:s}) ",
2223 : getAccountID(),
2224 : conversationId,
2225 : remoteDevice);
2226 0 : channel->shutdown();
2227 0 : return;
2228 : }
2229 :
2230 833 : auto sock = convModule()->gitSocket(deviceId.toString(), conversationId);
2231 833 : if (sock == channel) {
2232 : // The onConnectionReady is already used as client (for retrieving messages)
2233 : // So it's not the server socket
2234 0 : return;
2235 : }
2236 3332 : JAMI_LOG("[Account {:s}] [Conversation {}] [device {}] Git server requested",
2237 : accountID_,
2238 : conversationId,
2239 : deviceId.toString());
2240 833 : auto gs = std::make_unique<GitServer>(accountID_, conversationId, channel);
2241 833 : syncCnt_.fetch_add(1);
2242 833 : gs->setOnFetched([w = weak(), conversationId, deviceId](const std::string& commit) {
2243 1115 : dht::ThreadPool::computation().run([w, conversationId, deviceId, commit]() {
2244 1115 : if (auto shared = w.lock()) {
2245 1115 : shared->convModule()->setFetched(conversationId, deviceId.toString(), commit);
2246 2230 : if (shared->syncCnt_.fetch_sub(1) == 1) {
2247 311 : emitSignal<libjami::ConversationSignal::ConversationCloned>(shared->getAccountID().c_str());
2248 : }
2249 1115 : }
2250 1115 : });
2251 1115 : });
2252 833 : const dht::Value::Id serverId = ValueIdDist()(rand);
2253 : {
2254 833 : std::lock_guard lk(gitServersMtx_);
2255 833 : gitServers_[serverId] = std::move(gs);
2256 833 : }
2257 833 : channel->onShutdown([w = weak(), serverId](const std::error_code&) {
2258 : // Run on main thread to avoid to be in mxSock's eventLoop
2259 833 : runOnMainThread([serverId, w]() {
2260 833 : if (auto sthis = w.lock()) {
2261 833 : std::lock_guard lk(sthis->gitServersMtx_);
2262 833 : sthis->gitServers_.erase(serverId);
2263 1666 : }
2264 833 : });
2265 833 : });
2266 2487 : } else {
2267 : // TODO move git://
2268 2091 : std::shared_lock lk(connManagerMtx_);
2269 2092 : auto uri = Uri(name);
2270 2090 : auto itHandler = channelHandlers_.find(uri.scheme());
2271 2092 : if (itHandler != channelHandlers_.end() && itHandler->second)
2272 2086 : itHandler->second->onReady(cert, name, std::move(channel));
2273 2090 : }
2274 4774 : }
2275 : }
2276 :
2277 : void
2278 1570 : JamiAccount::conversationNeedsSyncing(std::shared_ptr<SyncMsg>&& syncMsg)
2279 : {
2280 1570 : dht::ThreadPool::computation().run([w = weak(), syncMsg] {
2281 1570 : if (auto shared = w.lock()) {
2282 1570 : const auto& config = shared->config();
2283 : // For JAMS account, we must update the server
2284 : // for now, only sync with the JAMS server for changes to the conversation list
2285 1570 : if (!config.managerUri.empty() && !syncMsg)
2286 0 : if (auto am = shared->accountManager())
2287 0 : am->syncDevices();
2288 1570 : if (auto* sm = shared->syncModule())
2289 1570 : sm->syncWithConnected(syncMsg);
2290 1570 : }
2291 1570 : });
2292 1570 : }
2293 :
2294 : uint64_t
2295 14398 : JamiAccount::conversationSendMessage(const std::string& uri,
2296 : const DeviceId& device,
2297 : const std::map<std::string, std::string>& msg,
2298 : uint64_t token)
2299 : {
2300 : // No need to retrigger, sendTextMessage will call
2301 : // messageEngine_.sendMessage, already retriggering on
2302 : // main thread.
2303 14398 : auto deviceId = device ? device.toString() : "";
2304 28771 : return sendTextMessage(uri, deviceId, msg, token);
2305 14387 : }
2306 :
2307 : void
2308 2099 : JamiAccount::onConversationNeedSocket(const std::string& convId,
2309 : const std::string& deviceId,
2310 : ChannelCb&& cb,
2311 : const std::string& type)
2312 : {
2313 2099 : dht::ThreadPool::io().run([w = weak(), convId, deviceId, cb = std::move(cb), type] {
2314 2099 : auto shared = w.lock();
2315 2100 : if (!shared)
2316 0 : return;
2317 2100 : if (auto socket = shared->convModule()->gitSocket(deviceId, convId)) {
2318 1213 : if (!cb(socket))
2319 0 : socket->shutdown();
2320 : else
2321 1213 : cb({});
2322 1213 : return;
2323 2100 : }
2324 887 : std::shared_lock lkCM(shared->connManagerMtx_);
2325 886 : if (!shared->connectionManager_) {
2326 5 : lkCM.unlock();
2327 5 : cb({});
2328 5 : return;
2329 : }
2330 :
2331 4409 : shared->connectionManager_->connectDevice(
2332 882 : DeviceId(deviceId),
2333 2646 : fmt::format("git://{}/{}", deviceId, convId),
2334 1764 : [w, cb = std::move(cb), convId](std::shared_ptr<dhtnet::ChannelSocket> socket, const DeviceId&) {
2335 882 : dht::ThreadPool::io().run([w, cb = std::move(cb), socket = std::move(socket), convId] {
2336 882 : if (socket) {
2337 833 : socket->onShutdown([w, deviceId = socket->deviceId(), convId](const std::error_code&) {
2338 833 : dht::ThreadPool::io().run([w, deviceId, convId] {
2339 833 : if (auto shared = w.lock())
2340 833 : shared->convModule()->removeGitSocket(deviceId.toString(), convId);
2341 833 : });
2342 833 : });
2343 833 : if (!cb(socket))
2344 3 : socket->shutdown();
2345 : } else
2346 49 : cb({});
2347 882 : });
2348 882 : },
2349 : false,
2350 : false,
2351 882 : type);
2352 2105 : });
2353 2100 : }
2354 :
2355 : void
2356 546 : JamiAccount::onConversationNeedSwarmSocket(const std::string& convId,
2357 : const std::string& deviceId,
2358 : ChannelCb&& cb,
2359 : const std::string& type)
2360 : {
2361 546 : dht::ThreadPool::io().run([w = weak(), convId, deviceId, cb = std::forward<ChannelCb&&>(cb), type] {
2362 545 : auto shared = w.lock();
2363 546 : if (!shared)
2364 0 : return;
2365 546 : auto* cm = shared->convModule();
2366 546 : std::shared_lock lkCM(shared->connManagerMtx_);
2367 546 : if (!shared->connectionManager_ || !cm || cm->isBanned(convId, deviceId)) {
2368 58 : asio::post(*Manager::instance().ioContext(), [cb = std::move(cb)] { cb({}); });
2369 29 : return;
2370 : }
2371 517 : DeviceId device(deviceId);
2372 517 : auto swarmUri = fmt::format("swarm://{}", convId);
2373 517 : if (!shared->connectionManager_->isConnecting(device, swarmUri)) {
2374 992 : shared->connectionManager_->connectDevice(
2375 : device,
2376 : swarmUri,
2377 496 : [w,
2378 496 : cb = std::move(cb),
2379 : wam = std::weak_ptr(shared->accountManager())](std::shared_ptr<dhtnet::ChannelSocket> socket,
2380 : const DeviceId& deviceId) {
2381 496 : dht::ThreadPool::io().run([w, wam, cb = std::move(cb), socket = std::move(socket), deviceId] {
2382 496 : if (socket) {
2383 244 : auto shared = w.lock();
2384 244 : auto am = wam.lock();
2385 244 : auto remoteCert = socket->peerCertificate();
2386 244 : if (!remoteCert || !remoteCert->issuer) {
2387 0 : cb(nullptr);
2388 0 : return;
2389 : }
2390 244 : auto uri = remoteCert->issuer->getId().toString();
2391 488 : if (!shared || !am
2392 488 : || am->getCertificateStatus(uri) == dhtnet::tls::TrustStore::PermissionStatus::BANNED) {
2393 0 : cb(nullptr);
2394 0 : return;
2395 : }
2396 244 : shared->requestMessageConnection(uri, deviceId, "");
2397 244 : }
2398 496 : cb(socket);
2399 : });
2400 496 : });
2401 : }
2402 575 : });
2403 546 : }
2404 :
2405 : void
2406 1 : JamiAccount::conversationOneToOneReceive(const std::string& convId, const std::string& from)
2407 : {
2408 1 : accountManager_->findCertificate(dht::InfoHash(from),
2409 1 : [this, from, convId](const std::shared_ptr<dht::crypto::Certificate>& cert) {
2410 1 : const auto* info = accountManager_->getInfo();
2411 1 : if (!cert || !info)
2412 0 : return;
2413 2 : info->contacts->onTrustRequest(dht::InfoHash(from),
2414 : cert->getSharedPublicKey(),
2415 : time(nullptr),
2416 : false,
2417 1 : convId,
2418 : {});
2419 : });
2420 1 : }
2421 :
2422 : ConversationModule*
2423 27585 : JamiAccount::convModule(bool noCreation)
2424 : {
2425 27585 : if (noCreation)
2426 5116 : return convModule_.get();
2427 22469 : if (!accountManager() || currentDeviceId() == "") {
2428 0 : JAMI_ERROR("[Account {}] Calling convModule() with an uninitialized account", getAccountID());
2429 0 : return nullptr;
2430 : }
2431 22464 : std::unique_lock lock(configurationMutex_);
2432 22463 : std::lock_guard lk(moduleMtx_);
2433 22465 : if (!convModule_) {
2434 1152 : convModule_ = std::make_unique<ConversationModule>(
2435 576 : shared(),
2436 576 : accountManager_,
2437 1570 : [this](auto&& syncMsg) { conversationNeedsSyncing(std::forward<std::shared_ptr<SyncMsg>>(syncMsg)); },
2438 14398 : [this](auto&& uri, auto&& device, auto&& msg, auto token = 0) {
2439 14398 : return conversationSendMessage(uri, device, msg, token);
2440 : },
2441 2099 : [this](const auto& convId, const auto& deviceId, auto&& cb, const auto& connectionType) {
2442 2099 : onConversationNeedSocket(convId, deviceId, std::forward<decltype(cb)>(cb), connectionType);
2443 2100 : },
2444 546 : [this](const auto& convId, const auto& deviceId, auto&& cb, const auto& connectionType) {
2445 546 : onConversationNeedSwarmSocket(convId, deviceId, std::forward<decltype(cb)>(cb), connectionType);
2446 546 : },
2447 1 : [this](const auto& convId, const auto& from) { conversationOneToOneReceive(convId, from); },
2448 1152 : autoLoadConversations_);
2449 : }
2450 22467 : return convModule_.get();
2451 22468 : }
2452 :
2453 : SyncModule*
2454 2497 : JamiAccount::syncModule()
2455 : {
2456 2497 : if (!accountManager() || currentDeviceId() == "") {
2457 0 : JAMI_ERR() << "Calling syncModule() with an uninitialized account.";
2458 0 : return nullptr;
2459 : }
2460 2497 : std::lock_guard lk(moduleMtx_);
2461 2497 : if (!syncModule_)
2462 564 : syncModule_ = std::make_unique<SyncModule>(shared());
2463 2497 : return syncModule_.get();
2464 2497 : }
2465 :
2466 : void
2467 13228 : JamiAccount::onTextMessage(const std::string& id,
2468 : const std::string& from,
2469 : const std::shared_ptr<dht::crypto::Certificate>& peerCert,
2470 : const std::map<std::string, std::string>& payloads)
2471 : {
2472 : try {
2473 13228 : const std::string fromUri {parseJamiUri(from)};
2474 13224 : SIPAccountBase::onTextMessage(id, fromUri, peerCert, payloads);
2475 13237 : } catch (...) {
2476 0 : }
2477 13235 : }
2478 :
2479 : void
2480 0 : JamiAccount::loadConversation(const std::string& convId)
2481 : {
2482 0 : if (auto* cm = convModule(true))
2483 0 : cm->loadSingleConversation(convId);
2484 0 : }
2485 :
2486 : void
2487 864 : JamiAccount::doUnregister(bool forceShutdownConnections)
2488 : {
2489 864 : std::unique_lock lock(configurationMutex_);
2490 864 : if (registrationState_ >= RegistrationState::ERROR_GENERIC) {
2491 142 : return;
2492 : }
2493 :
2494 722 : std::mutex mtx;
2495 722 : std::condition_variable cv;
2496 722 : bool shutdown_complete {false};
2497 :
2498 722 : if (peerDiscovery_) {
2499 0 : peerDiscovery_->stopPublish(PEER_DISCOVERY_JAMI_SERVICE);
2500 0 : peerDiscovery_->stopDiscovery(PEER_DISCOVERY_JAMI_SERVICE);
2501 : }
2502 :
2503 722 : JAMI_WARN("[Account %s] Unregistering account %p", getAccountID().c_str(), this);
2504 722 : dht_->shutdown(
2505 722 : [&] {
2506 722 : JAMI_WARN("[Account %s] DHT shutdown complete", getAccountID().c_str());
2507 722 : std::lock_guard lock(mtx);
2508 722 : shutdown_complete = true;
2509 722 : cv.notify_all();
2510 722 : },
2511 : true);
2512 :
2513 : {
2514 722 : std::lock_guard lk(pendingCallsMutex_);
2515 722 : pendingCalls_.clear();
2516 722 : }
2517 :
2518 : // Stop all current P2P connections if account is disabled
2519 : // or if explicitly requested by the caller.
2520 : // NOTE: Leaving the connections open is useful when changing an account's config.
2521 722 : if (not isEnabled() || forceShutdownConnections)
2522 706 : shutdownConnections();
2523 :
2524 : // Release current UPnP mapping if any.
2525 722 : if (upnpCtrl_ and dhtUpnpMapping_.isValid()) {
2526 0 : upnpCtrl_->releaseMapping(dhtUpnpMapping_);
2527 : }
2528 :
2529 : {
2530 722 : std::unique_lock lock(mtx);
2531 2038 : cv.wait(lock, [&] { return shutdown_complete; });
2532 722 : }
2533 722 : dht_->join();
2534 722 : setRegistrationState(RegistrationState::UNREGISTERED);
2535 :
2536 722 : lock.unlock();
2537 :
2538 : #ifdef ENABLE_PLUGIN
2539 722 : jami::Manager::instance().getJamiPluginManager().getChatServicesManager().cleanChatSubjects(getAccountID());
2540 : #endif
2541 864 : }
2542 :
2543 : void
2544 4409 : JamiAccount::setRegistrationState(RegistrationState state, int detail_code, const std::string& detail_str)
2545 : {
2546 4409 : if (registrationState_ != state) {
2547 3153 : if (state == RegistrationState::REGISTERED) {
2548 2380 : JAMI_WARNING("[Account {}] Connected", getAccountID());
2549 595 : turnCache_->refresh();
2550 595 : if (connectionManager_)
2551 587 : connectionManager_->storeActiveIpAddress();
2552 2558 : } else if (state == RegistrationState::TRYING) {
2553 2400 : JAMI_WARNING("[Account {}] Connecting…", getAccountID());
2554 : } else {
2555 1958 : deviceAnnounced_ = false;
2556 7832 : JAMI_WARNING("[Account {}] Disconnected", getAccountID());
2557 : }
2558 : }
2559 : // Update registrationState_ & emit signals
2560 4409 : Account::setRegistrationState(state, detail_code, detail_str);
2561 4409 : }
2562 :
2563 : void
2564 0 : JamiAccount::reloadContacts()
2565 : {
2566 0 : accountManager_->reloadContacts();
2567 0 : }
2568 :
2569 : void
2570 0 : JamiAccount::connectivityChanged()
2571 : {
2572 0 : if (not isUsable()) {
2573 : // nothing to do
2574 0 : return;
2575 : }
2576 0 : JAMI_WARNING("[{}] connectivityChanged", getAccountID());
2577 :
2578 0 : if (auto* cm = convModule())
2579 0 : cm->connectivityChanged();
2580 0 : dht_->connectivityChanged();
2581 : {
2582 0 : std::shared_lock lkCM(connManagerMtx_);
2583 0 : if (connectionManager_) {
2584 0 : connectionManager_->connectivityChanged();
2585 : // reset cache
2586 0 : connectionManager_->setPublishedAddress({});
2587 : }
2588 0 : }
2589 : }
2590 :
2591 : bool
2592 0 : JamiAccount::findCertificate(const dht::InfoHash& h,
2593 : std::function<void(const std::shared_ptr<dht::crypto::Certificate>&)>&& cb)
2594 : {
2595 0 : if (accountManager_)
2596 0 : return accountManager_->findCertificate(h, std::move(cb));
2597 0 : return false;
2598 : }
2599 :
2600 : bool
2601 0 : JamiAccount::findCertificate(const dht::PkId& id,
2602 : std::function<void(const std::shared_ptr<dht::crypto::Certificate>&)>&& cb)
2603 : {
2604 0 : if (accountManager_)
2605 0 : return accountManager_->findCertificate(id, std::move(cb));
2606 0 : return false;
2607 : }
2608 :
2609 : bool
2610 22 : JamiAccount::findCertificate(const std::string& crt_id)
2611 : {
2612 22 : if (accountManager_)
2613 22 : return accountManager_->findCertificate(dht::InfoHash(crt_id));
2614 0 : return false;
2615 : }
2616 :
2617 : bool
2618 25 : JamiAccount::setCertificateStatus(const std::string& cert_id, dhtnet::tls::TrustStore::PermissionStatus status)
2619 : {
2620 25 : bool done = accountManager_ ? accountManager_->setCertificateStatus(cert_id, status) : false;
2621 25 : if (done) {
2622 22 : findCertificate(cert_id);
2623 22 : emitSignal<libjami::ConfigurationSignal::CertificateStateChanged>(getAccountID(),
2624 : cert_id,
2625 : dhtnet::tls::TrustStore::statusToStr(status));
2626 : }
2627 25 : return done;
2628 : }
2629 :
2630 : bool
2631 0 : JamiAccount::setCertificateStatus(const std::shared_ptr<crypto::Certificate>& cert,
2632 : dhtnet::tls::TrustStore::PermissionStatus status,
2633 : bool local)
2634 : {
2635 0 : bool done = accountManager_ ? accountManager_->setCertificateStatus(cert, status, local) : false;
2636 0 : if (done) {
2637 0 : findCertificate(cert->getLongId().toString());
2638 0 : emitSignal<libjami::ConfigurationSignal::CertificateStateChanged>(getAccountID(),
2639 0 : cert->getLongId().toString(),
2640 : dhtnet::tls::TrustStore::statusToStr(status));
2641 : }
2642 0 : return done;
2643 : }
2644 :
2645 : std::vector<std::string>
2646 0 : JamiAccount::getCertificatesByStatus(dhtnet::tls::TrustStore::PermissionStatus status)
2647 : {
2648 0 : if (accountManager_)
2649 0 : return accountManager_->getCertificatesByStatus(status);
2650 0 : return {};
2651 : }
2652 :
2653 : bool
2654 0 : JamiAccount::isMessageTreated(dht::Value::Id id)
2655 : {
2656 0 : std::lock_guard lock(messageMutex_);
2657 0 : return !treatedMessages_.add(id);
2658 0 : }
2659 :
2660 : bool
2661 13 : JamiAccount::sha3SumVerify() const
2662 : {
2663 13 : return !noSha3sumVerification_;
2664 : }
2665 :
2666 : #ifdef LIBJAMI_TEST
2667 : void
2668 1 : JamiAccount::noSha3sumVerification(bool newValue)
2669 : {
2670 1 : noSha3sumVerification_ = newValue;
2671 1 : }
2672 : #endif
2673 :
2674 : std::map<std::string, std::string>
2675 0 : JamiAccount::getKnownDevices() const
2676 : {
2677 0 : std::lock_guard lock(configurationMutex_);
2678 0 : if (not accountManager_ or not accountManager_->getInfo())
2679 0 : return {};
2680 0 : std::map<std::string, std::string> ids;
2681 0 : for (const auto& d : accountManager_->getKnownDevices()) {
2682 0 : auto id = d.first.toString();
2683 0 : auto label = d.second.name.empty() ? id.substr(0, 8) : d.second.name;
2684 0 : ids.emplace(std::move(id), std::move(label));
2685 0 : }
2686 0 : return ids;
2687 0 : }
2688 :
2689 : void
2690 0 : JamiAccount::loadCachedUrl(const std::string& url,
2691 : const std::filesystem::path& cachePath,
2692 : const std::chrono::seconds& cacheDuration,
2693 : const std::function<void(const dht::http::Response& response)>& cb)
2694 : {
2695 0 : dht::ThreadPool::io().run([cb, url, cachePath, cacheDuration, w = weak()]() {
2696 : try {
2697 0 : std::string data;
2698 : {
2699 0 : std::lock_guard lk(dhtnet::fileutils::getFileLock(cachePath));
2700 0 : data = fileutils::loadCacheTextFile(cachePath, cacheDuration);
2701 0 : }
2702 0 : dht::http::Response ret;
2703 0 : ret.body = std::move(data);
2704 0 : ret.status_code = 200;
2705 0 : cb(ret);
2706 0 : } catch (const std::exception& e) {
2707 0 : JAMI_LOG("Failed to load '{}' from '{}': {}", url, cachePath, e.what());
2708 :
2709 0 : if (auto sthis = w.lock()) {
2710 : auto req = std::make_shared<dht::http::Request>(
2711 0 : *Manager::instance().ioContext(), url, [cb, cachePath, w](const dht::http::Response& response) {
2712 0 : if (response.status_code == 200) {
2713 : try {
2714 0 : std::lock_guard lk(dhtnet::fileutils::getFileLock(cachePath));
2715 0 : fileutils::saveFile(cachePath,
2716 0 : (const uint8_t*) response.body.data(),
2717 : response.body.size(),
2718 : 0600);
2719 0 : JAMI_LOG("Cached result to '{}'", cachePath);
2720 0 : } catch (const std::exception& ex) {
2721 0 : JAMI_WARNING("Failed to save result to '{}': {}", cachePath, ex.what());
2722 0 : }
2723 0 : cb(response);
2724 : } else {
2725 : try {
2726 0 : if (std::filesystem::exists(cachePath)) {
2727 0 : JAMI_WARNING("Failed to download URL, using cached data");
2728 0 : std::string data;
2729 : {
2730 0 : std::lock_guard lk(dhtnet::fileutils::getFileLock(cachePath));
2731 0 : data = fileutils::loadTextFile(cachePath);
2732 0 : }
2733 0 : dht::http::Response ret;
2734 0 : ret.body = std::move(data);
2735 0 : ret.status_code = 200;
2736 0 : cb(ret);
2737 0 : } else
2738 0 : throw std::runtime_error("No cached data");
2739 0 : } catch (...) {
2740 0 : cb(response);
2741 0 : }
2742 : }
2743 0 : if (auto req = response.request.lock())
2744 0 : if (auto sthis = w.lock())
2745 0 : sthis->requests_.erase(req);
2746 0 : });
2747 0 : sthis->requests_.emplace(req);
2748 0 : req->send();
2749 0 : }
2750 0 : }
2751 0 : });
2752 0 : }
2753 :
2754 : void
2755 600 : JamiAccount::loadCachedProxyServer(std::function<void(const std::string& proxy)> cb)
2756 : {
2757 600 : const auto& conf = config();
2758 600 : if (conf.proxyEnabled and proxyServerCached_.empty()) {
2759 0 : JAMI_DEBUG("[Account {:s}] Loading DHT proxy URL: {:s}", getAccountID(), conf.proxyListUrl);
2760 0 : if (conf.proxyListUrl.empty() or not conf.proxyListEnabled) {
2761 0 : cb(getDhtProxyServer(conf.proxyServer));
2762 : } else {
2763 0 : loadCachedUrl(conf.proxyListUrl,
2764 0 : cachePath_ / "dhtproxylist",
2765 0 : std::chrono::hours(24 * 3),
2766 0 : [w = weak(), cb = std::move(cb)](const dht::http::Response& response) {
2767 0 : if (auto sthis = w.lock()) {
2768 0 : if (response.status_code == 200) {
2769 0 : cb(sthis->getDhtProxyServer(response.body));
2770 : } else {
2771 0 : cb(sthis->getDhtProxyServer(sthis->config().proxyServer));
2772 : }
2773 0 : }
2774 0 : });
2775 : }
2776 : } else {
2777 600 : cb(proxyServerCached_);
2778 : }
2779 600 : }
2780 :
2781 : std::string
2782 0 : JamiAccount::getDhtProxyServer(const std::string& serverList)
2783 : {
2784 0 : if (proxyServerCached_.empty()) {
2785 0 : std::vector<std::string> proxys;
2786 : // Split the list of servers
2787 0 : std::sregex_iterator begin = {serverList.begin(), serverList.end(), PROXY_REGEX}, end;
2788 0 : for (auto it = begin; it != end; ++it) {
2789 0 : auto& match = *it;
2790 0 : if (match[5].matched and match[6].matched) {
2791 : try {
2792 0 : auto start = std::stoi(match[5]), end = std::stoi(match[6]);
2793 0 : for (auto p = start; p <= end; p++)
2794 0 : proxys.emplace_back(match[1].str() + match[2].str() + ":" + std::to_string(p));
2795 0 : } catch (...) {
2796 0 : JAMI_WARN("Malformed proxy, ignore it");
2797 0 : continue;
2798 0 : }
2799 : } else {
2800 0 : proxys.emplace_back(match[0].str());
2801 : }
2802 0 : }
2803 0 : if (proxys.empty())
2804 0 : return {};
2805 : // Select one of the list as the current proxy.
2806 0 : auto randIt = proxys.begin();
2807 0 : std::advance(randIt, std::uniform_int_distribution<unsigned long>(0, proxys.size() - 1)(rand));
2808 0 : proxyServerCached_ = *randIt;
2809 : // Cache it!
2810 0 : dhtnet::fileutils::check_dir(cachePath_, 0700);
2811 0 : auto proxyCachePath = cachePath_ / "dhtproxy";
2812 0 : std::ofstream file(proxyCachePath);
2813 0 : JAMI_DEBUG("Cache DHT proxy server: {}", proxyServerCached_);
2814 0 : Json::Value node(Json::objectValue);
2815 0 : node[getProxyConfigKey()] = proxyServerCached_;
2816 0 : if (file.is_open())
2817 0 : file << node;
2818 : else
2819 0 : JAMI_WARNING("Unable to write into {}", proxyCachePath);
2820 0 : }
2821 0 : return proxyServerCached_;
2822 : }
2823 :
2824 : MatchRank
2825 0 : JamiAccount::matches(std::string_view userName, std::string_view server) const
2826 : {
2827 0 : if (not accountManager_ or not accountManager_->getInfo())
2828 0 : return MatchRank::NONE;
2829 :
2830 0 : if (userName == accountManager_->getInfo()->accountId || server == accountManager_->getInfo()->accountId
2831 0 : || userName == accountManager_->getInfo()->deviceId) {
2832 0 : JAMI_LOG("Matching account ID in request with username {}", userName);
2833 0 : return MatchRank::FULL;
2834 : } else {
2835 0 : return MatchRank::NONE;
2836 : }
2837 : }
2838 :
2839 : std::string
2840 38 : JamiAccount::getFromUri() const
2841 : {
2842 38 : std::string uri = "<sip:" + accountManager_->getInfo()->accountId + "@ring.dht>";
2843 38 : if (not config().displayName.empty())
2844 76 : return "\"" + config().displayName + "\" " + uri;
2845 0 : return uri;
2846 38 : }
2847 :
2848 : std::string
2849 100 : JamiAccount::getToUri(const std::string& to) const
2850 : {
2851 100 : auto username = to;
2852 100 : string_replace(username, "sip:", "");
2853 200 : return fmt::format("<sips:{};transport=tls>", username);
2854 100 : }
2855 :
2856 : std::string
2857 7 : getDisplayed(const std::string& conversationId, const std::string& messageId)
2858 : {
2859 : // implementing https://tools.ietf.org/rfc/rfc5438.txt
2860 : return fmt::format("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
2861 : "<imdn><message-id>{}</message-id>\n"
2862 : "{}"
2863 : "<display-notification><status><displayed/></status></display-notification>\n"
2864 : "</imdn>",
2865 : messageId,
2866 14 : conversationId.empty() ? "" : "<conversation>" + conversationId + "</conversation>");
2867 : }
2868 :
2869 : std::string
2870 7 : getPIDF(const std::string& note)
2871 : {
2872 : // implementing https://datatracker.ietf.org/doc/html/rfc3863
2873 : return fmt::format("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
2874 : "<presence xmlns=\"urn:ietf:params:xml:ns:pidf\">\n"
2875 : " <tuple>\n"
2876 : " <status>\n"
2877 : " <basic>{}</basic>\n"
2878 : " </status>\n"
2879 : " </tuple>\n"
2880 : "</presence>",
2881 14 : note);
2882 : }
2883 :
2884 : void
2885 5 : JamiAccount::setIsComposing(const std::string& conversationUri, bool isWriting)
2886 : {
2887 5 : Uri uri(conversationUri);
2888 5 : std::string conversationId = {};
2889 5 : if (uri.scheme() == Uri::Scheme::SWARM) {
2890 5 : conversationId = uri.authority();
2891 : } else {
2892 0 : return;
2893 : }
2894 :
2895 5 : if (auto* cm = convModule(true)) {
2896 5 : if (auto typer = cm->getTypers(conversationId)) {
2897 5 : if (isWriting)
2898 4 : typer->addTyper(getUsername(), true);
2899 : else
2900 1 : typer->removeTyper(getUsername(), true);
2901 5 : }
2902 : }
2903 5 : }
2904 :
2905 : bool
2906 9 : JamiAccount::setMessageDisplayed(const std::string& conversationUri, const std::string& messageId, int status)
2907 : {
2908 9 : Uri uri(conversationUri);
2909 9 : std::string conversationId = {};
2910 9 : if (uri.scheme() == Uri::Scheme::SWARM)
2911 9 : conversationId = uri.authority();
2912 9 : auto sendMessage = status == (int) libjami::Account::MessageStates::DISPLAYED && isReadReceiptEnabled();
2913 9 : if (!conversationId.empty())
2914 9 : sendMessage &= convModule()->onMessageDisplayed(getUsername(), conversationId, messageId);
2915 9 : if (sendMessage)
2916 14 : sendInstantMessage(uri.authority(), {{MIME_TYPE_IMDN, getDisplayed(conversationId, messageId)}});
2917 9 : return true;
2918 9 : }
2919 :
2920 : std::string
2921 66 : JamiAccount::getContactHeader(const std::shared_ptr<SipTransport>& sipTransport)
2922 : {
2923 66 : if (sipTransport and sipTransport->get() != nullptr) {
2924 66 : auto* transport = sipTransport->get();
2925 66 : auto* td = reinterpret_cast<tls::AbstractSIPTransport::TransportData*>(transport);
2926 66 : auto address = td->self->getLocalAddress().toString(true);
2927 66 : bool reliable = transport->flag & PJSIP_TRANSPORT_RELIABLE;
2928 : return fmt::format("\"{}\" <sips:{}{}{};transport={}>",
2929 66 : config().displayName,
2930 66 : id_.second->getId().toString(),
2931 66 : address.empty() ? "" : "@",
2932 : address,
2933 198 : reliable ? "tls" : "dtls");
2934 66 : } else {
2935 0 : JAMI_ERR("getContactHeader: no SIP transport provided");
2936 0 : return fmt::format("\"{}\" <sips:{}@ring.dht>", config().displayName, id_.second->getId().toString());
2937 : }
2938 : }
2939 :
2940 : void
2941 67 : JamiAccount::addContact(const std::string& uri, bool confirmed)
2942 : {
2943 67 : dht::InfoHash h(uri);
2944 67 : if (not h) {
2945 4 : JAMI_ERROR("addContact: invalid contact URI");
2946 1 : return;
2947 : }
2948 66 : auto conversation = convModule()->getOneToOneConversation(uri);
2949 66 : if (!confirmed && conversation.empty())
2950 66 : conversation = convModule()->startConversation(ConversationMode::ONE_TO_ONE, h);
2951 66 : std::unique_lock lock(configurationMutex_);
2952 66 : if (accountManager_)
2953 66 : accountManager_->addContact(h, confirmed, conversation);
2954 : else
2955 0 : JAMI_WARNING("[Account {}] addContact: account not loaded", getAccountID());
2956 66 : }
2957 :
2958 : void
2959 19 : JamiAccount::removeContact(const std::string& uri, bool ban)
2960 : {
2961 19 : std::lock_guard lock(configurationMutex_);
2962 19 : if (accountManager_)
2963 19 : accountManager_->removeContact(uri, ban);
2964 : else
2965 0 : JAMI_WARNING("[Account {}] removeContact: account not loaded", getAccountID());
2966 19 : }
2967 :
2968 : std::map<std::string, std::string>
2969 6 : JamiAccount::getContactDetails(const std::string& uri) const
2970 : {
2971 6 : std::lock_guard lock(configurationMutex_);
2972 12 : return accountManager_ ? accountManager_->getContactDetails(uri) : std::map<std::string, std::string> {};
2973 6 : }
2974 :
2975 : std::optional<Contact>
2976 582 : JamiAccount::getContactInfo(const std::string& uri) const
2977 : {
2978 582 : std::lock_guard lock(configurationMutex_);
2979 1164 : return accountManager_ ? accountManager_->getContactInfo(uri) : std::nullopt;
2980 582 : }
2981 :
2982 : std::vector<std::map<std::string, std::string>>
2983 1 : JamiAccount::getContacts(bool includeRemoved) const
2984 : {
2985 1 : std::lock_guard lock(configurationMutex_);
2986 1 : if (not accountManager_)
2987 0 : return {};
2988 1 : const auto& contacts = accountManager_->getContacts(includeRemoved);
2989 1 : std::vector<std::map<std::string, std::string>> ret;
2990 1 : ret.reserve(contacts.size());
2991 2 : for (const auto& c : contacts) {
2992 1 : auto details = c.second.toMap();
2993 1 : if (not details.empty()) {
2994 1 : details["id"] = c.first.toString();
2995 1 : ret.emplace_back(std::move(details));
2996 : }
2997 1 : }
2998 1 : return ret;
2999 1 : }
3000 :
3001 : /* trust requests */
3002 :
3003 : std::vector<std::map<std::string, std::string>>
3004 609 : JamiAccount::getTrustRequests() const
3005 : {
3006 609 : std::lock_guard lock(configurationMutex_);
3007 1218 : return accountManager_ ? accountManager_->getTrustRequests() : std::vector<std::map<std::string, std::string>> {};
3008 609 : }
3009 :
3010 : bool
3011 28 : JamiAccount::acceptTrustRequest(const std::string& from, bool includeConversation)
3012 : {
3013 28 : dht::InfoHash h(from);
3014 28 : if (not h) {
3015 0 : JAMI_ERROR("addContact: invalid contact URI");
3016 0 : return false;
3017 : }
3018 28 : std::unique_lock lock(configurationMutex_);
3019 28 : if (accountManager_) {
3020 28 : if (!accountManager_->acceptTrustRequest(from, includeConversation)) {
3021 : // Note: unused for swarm
3022 : // Typically the case where the trust request doesn't exists, only incoming DHT messages
3023 0 : return accountManager_->addContact(h, true);
3024 : }
3025 28 : return true;
3026 : }
3027 0 : JAMI_WARNING("[Account {}] acceptTrustRequest: account not loaded", getAccountID());
3028 0 : return false;
3029 28 : }
3030 :
3031 : bool
3032 2 : JamiAccount::discardTrustRequest(const std::string& from)
3033 : {
3034 : // Remove 1:1 generated conv requests
3035 2 : auto requests = getTrustRequests();
3036 4 : for (const auto& req : requests) {
3037 2 : if (req.at(libjami::Account::TrustRequest::FROM) == from) {
3038 2 : convModule()->declineConversationRequest(req.at(libjami::Account::TrustRequest::CONVERSATIONID));
3039 : }
3040 : }
3041 :
3042 : // Remove trust request
3043 2 : std::lock_guard lock(configurationMutex_);
3044 2 : if (accountManager_)
3045 2 : return accountManager_->discardTrustRequest(from);
3046 0 : JAMI_WARNING("[Account {:s}] discardTrustRequest: account not loaded", getAccountID());
3047 0 : return false;
3048 2 : }
3049 :
3050 : void
3051 3 : JamiAccount::declineConversationRequest(const std::string& conversationId)
3052 : {
3053 3 : auto peerId = convModule()->peerFromConversationRequest(conversationId);
3054 3 : convModule()->declineConversationRequest(conversationId);
3055 3 : if (!peerId.empty()) {
3056 3 : std::lock_guard lock(configurationMutex_);
3057 3 : if (const auto* info = accountManager_->getInfo()) {
3058 : // Verify if we have a trust request with this peer + convId
3059 3 : auto req = info->contacts->getTrustRequest(dht::InfoHash(peerId));
3060 6 : if (req.find(libjami::Account::TrustRequest::CONVERSATIONID) != req.end()
3061 6 : && req.at(libjami::Account::TrustRequest::CONVERSATIONID) == conversationId) {
3062 1 : accountManager_->discardTrustRequest(peerId);
3063 4 : JAMI_DEBUG("[Account {:s}] Declined trust request with {:s}", getAccountID(), peerId);
3064 : }
3065 3 : }
3066 3 : }
3067 3 : }
3068 :
3069 : void
3070 57 : JamiAccount::sendTrustRequest(const std::string& to, const std::vector<uint8_t>& payload)
3071 : {
3072 57 : dht::InfoHash h(to);
3073 57 : if (not h) {
3074 0 : JAMI_ERROR("addContact: invalid contact URI");
3075 0 : return;
3076 : }
3077 : // Here we cache payload sent by the client
3078 57 : auto requestPath = cachePath_ / "requests";
3079 57 : dhtnet::fileutils::recursive_mkdir(requestPath, 0700);
3080 57 : auto cachedFile = requestPath / to;
3081 57 : std::ofstream req(cachedFile, std::ios::trunc | std::ios::binary);
3082 57 : if (!req.is_open()) {
3083 0 : JAMI_ERROR("Unable to write data to {}", cachedFile);
3084 0 : return;
3085 : }
3086 :
3087 57 : if (not payload.empty()) {
3088 3 : req.write(reinterpret_cast<const char*>(payload.data()), static_cast<std::streamsize>(payload.size()));
3089 : }
3090 :
3091 57 : if (payload.size() >= 64000) {
3092 1 : JAMI_WARN() << "Trust request is too big. Remove payload";
3093 : }
3094 :
3095 57 : auto conversation = convModule()->getOneToOneConversation(to);
3096 57 : if (conversation.empty())
3097 0 : conversation = convModule()->startConversation(ConversationMode::ONE_TO_ONE, h);
3098 57 : if (not conversation.empty()) {
3099 57 : std::lock_guard lock(configurationMutex_);
3100 57 : if (accountManager_)
3101 114 : accountManager_->sendTrustRequest(to,
3102 : conversation,
3103 114 : payload.size() >= 64000 ? std::vector<uint8_t> {} : payload);
3104 : else
3105 0 : JAMI_WARNING("[Account {}] sendTrustRequest: account not loaded", getAccountID());
3106 57 : } else
3107 0 : JAMI_WARNING("[Account {}] sendTrustRequest: account not loaded", getAccountID());
3108 57 : }
3109 :
3110 : void
3111 0 : JamiAccount::forEachDevice(const dht::InfoHash& to,
3112 : std::function<void(const std::shared_ptr<dht::crypto::PublicKey>&)>&& op,
3113 : std::function<void(bool)>&& end)
3114 : {
3115 0 : accountManager_->forEachDevice(to, std::move(op), std::move(end));
3116 0 : }
3117 :
3118 : uint64_t
3119 14383 : JamiAccount::sendTextMessage(const std::string& to,
3120 : const std::string& deviceId,
3121 : const std::map<std::string, std::string>& payloads,
3122 : uint64_t refreshToken,
3123 : bool onlyConnected)
3124 : {
3125 14383 : Uri uri(to);
3126 14377 : if (uri.scheme() == Uri::Scheme::SWARM) {
3127 0 : sendInstantMessage(uri.authority(), payloads);
3128 0 : return 0;
3129 : }
3130 :
3131 14379 : std::string toUri;
3132 : try {
3133 14372 : toUri = parseJamiUri(to);
3134 0 : } catch (...) {
3135 0 : JAMI_ERROR("Failed to send a text message due to an invalid URI {}", to);
3136 0 : return 0;
3137 0 : }
3138 14375 : if (payloads.size() != 1) {
3139 0 : JAMI_ERROR("Multi-part im is not supported yet by JamiAccount");
3140 0 : return 0;
3141 : }
3142 14374 : return SIPAccountBase::sendTextMessage(toUri, deviceId, payloads, refreshToken, onlyConnected);
3143 14387 : }
3144 :
3145 : void
3146 15091 : JamiAccount::sendMessage(const std::string& to,
3147 : const std::string& deviceId,
3148 : const std::map<std::string, std::string>& payloads,
3149 : uint64_t token,
3150 : bool retryOnTimeout,
3151 : bool onlyConnected)
3152 : {
3153 15091 : std::string toUri;
3154 : try {
3155 15091 : toUri = parseJamiUri(to);
3156 0 : } catch (...) {
3157 0 : JAMI_ERROR("[Account {}] Failed to send a text message due to an invalid URI {}", getAccountID(), to);
3158 0 : if (!onlyConnected)
3159 0 : messageEngine_.onMessageSent(to, token, false, deviceId);
3160 0 : return;
3161 0 : }
3162 15091 : if (payloads.size() != 1) {
3163 0 : JAMI_ERROR("Multi-part im is not supported");
3164 0 : if (!onlyConnected)
3165 0 : messageEngine_.onMessageSent(toUri, token, false, deviceId);
3166 0 : return;
3167 : }
3168 :
3169 : // Use the Message channel if available
3170 15091 : std::shared_lock clk(connManagerMtx_);
3171 15090 : auto* handler = static_cast<MessageChannelHandler*>(channelHandlers_[Uri::Scheme::MESSAGE].get());
3172 15091 : if (!handler) {
3173 19 : clk.unlock();
3174 19 : if (!onlyConnected)
3175 19 : messageEngine_.onMessageSent(to, token, false, deviceId);
3176 19 : return;
3177 : }
3178 :
3179 : auto devices = std::make_shared<SendMessageContext>(
3180 43370 : [w = weak(), to, token, deviceId, onlyConnected, retryOnTimeout](bool success, bool sent) {
3181 15070 : if (auto acc = w.lock())
3182 15067 : acc->onMessageSent(to, token, deviceId, success, onlyConnected, sent && retryOnTimeout);
3183 30143 : });
3184 :
3185 13247 : auto completed = [w = weak(), to, devices](const DeviceId& device,
3186 : const std::shared_ptr<dhtnet::ChannelSocket>& conn,
3187 : bool success) {
3188 13247 : if (!success)
3189 4 : if (auto acc = w.lock()) {
3190 4 : std::shared_lock clk(acc->connManagerMtx_);
3191 4 : if (auto* handler = static_cast<MessageChannelHandler*>(
3192 4 : acc->channelHandlers_[Uri::Scheme::MESSAGE].get())) {
3193 4 : handler->closeChannel(to, device, conn);
3194 : }
3195 8 : }
3196 13247 : devices->complete(device, success);
3197 28318 : };
3198 :
3199 15072 : const auto& payload = *payloads.begin();
3200 15072 : auto msg = std::make_shared<MessageChannelHandler::Message>();
3201 15071 : msg->id = token;
3202 15071 : msg->t = payload.first;
3203 15071 : msg->c = payload.second;
3204 15071 : auto device = deviceId.empty() ? DeviceId() : DeviceId(deviceId);
3205 15070 : if (deviceId.empty()) {
3206 3465 : auto conns = handler->getChannels(toUri);
3207 3467 : clk.unlock();
3208 5382 : for (const auto& conn : conns) {
3209 1915 : auto connDevice = conn->deviceId();
3210 1915 : if (!devices->add(connDevice))
3211 216 : continue;
3212 1699 : dht::ThreadPool::io().run([completed, connDevice, conn, msg] {
3213 1699 : completed(connDevice, conn, MessageChannelHandler::sendMessage(conn, *msg));
3214 1699 : });
3215 : }
3216 3467 : } else {
3217 11605 : if (auto conn = handler->getChannel(toUri, device)) {
3218 11548 : clk.unlock();
3219 11548 : devices->add(device);
3220 11548 : dht::ThreadPool::io().run([completed, device, conn, msg] {
3221 11548 : completed(device, conn, MessageChannelHandler::sendMessage(conn, *msg));
3222 11548 : });
3223 11548 : devices->start();
3224 11548 : return;
3225 11605 : }
3226 : }
3227 3524 : if (clk)
3228 57 : clk.unlock();
3229 :
3230 3524 : devices->start();
3231 :
3232 3524 : if (onlyConnected)
3233 22 : return;
3234 : // We are unable to send the message directly, try connecting
3235 :
3236 : // Get conversation id, which will be used by the iOS notification extension
3237 : // to load the conversation.
3238 2912 : auto extractIdFromJson = [](const std::string& jsonData) -> std::string {
3239 2912 : Json::Value parsed;
3240 2912 : if (json::parse(jsonData, parsed)) {
3241 2912 : auto value = parsed.get("id", Json::nullValue);
3242 2912 : if (value && value.isString()) {
3243 2912 : return value.asString();
3244 : }
3245 2912 : } else {
3246 0 : JAMI_WARNING("Unable to parse jsonData to get conversation ID");
3247 : }
3248 0 : return "";
3249 2912 : };
3250 :
3251 : // get request type
3252 3502 : auto payload_type = msg->t;
3253 3502 : if (payload_type == MIME_TYPE_GIT) {
3254 2912 : std::string id = extractIdFromJson(msg->c);
3255 2912 : if (!id.empty()) {
3256 2911 : payload_type += "/" + id;
3257 : }
3258 2911 : }
3259 :
3260 3502 : if (deviceId.empty()) {
3261 3445 : auto toH = dht::InfoHash(toUri);
3262 : // Find listening devices for this account
3263 3444 : accountManager_->forEachDevice(toH,
3264 3460 : [this, to, devices, payload_type, currentDevice = DeviceId(currentDeviceId())](
3265 2929 : const std::shared_ptr<dht::crypto::PublicKey>& dev) {
3266 : // Test if already sent
3267 3460 : auto deviceId = dev->getLongId();
3268 3460 : if (deviceId == currentDevice || devices->pending(deviceId)) {
3269 531 : return;
3270 : }
3271 :
3272 : // Else, ask for a channel to send the message
3273 2929 : dht::ThreadPool::io().run([this, to, deviceId, payload_type]() {
3274 2929 : requestMessageConnection(to, deviceId, payload_type);
3275 2929 : });
3276 : });
3277 : } else {
3278 57 : requestMessageConnection(to, device, payload_type);
3279 : }
3280 61390 : }
3281 :
3282 : void
3283 15068 : JamiAccount::onMessageSent(
3284 : const std::string& to, uint64_t id, const std::string& deviceId, bool success, bool onlyConnected, bool retry)
3285 : {
3286 15068 : if (!onlyConnected)
3287 15040 : messageEngine_.onMessageSent(to, id, success, deviceId);
3288 :
3289 15071 : if (!success) {
3290 1834 : if (retry)
3291 4 : messageEngine_.onPeerOnline(to, deviceId);
3292 : }
3293 15071 : }
3294 :
3295 : dhtnet::IceTransportOptions
3296 44 : JamiAccount::getIceOptions() const
3297 : {
3298 44 : return connectionManager_->getIceOptions();
3299 : }
3300 :
3301 : void
3302 11 : JamiAccount::getIceOptions(std::function<void(dhtnet::IceTransportOptions&&)> cb) const
3303 : {
3304 11 : return connectionManager_->getIceOptions(std::move(cb));
3305 : }
3306 :
3307 : dhtnet::IpAddr
3308 34 : JamiAccount::getPublishedIpAddress(uint16_t family) const
3309 : {
3310 34 : return connectionManager_->getPublishedIpAddress(family);
3311 : }
3312 :
3313 : bool
3314 0 : JamiAccount::setPushNotificationToken(const std::string& token)
3315 : {
3316 0 : if (SIPAccountBase::setPushNotificationToken(token)) {
3317 0 : JAMI_WARNING("[Account {:s}] setPushNotificationToken: {:s}", getAccountID(), token);
3318 0 : if (dht_)
3319 0 : dht_->setPushNotificationToken(token);
3320 0 : return true;
3321 : }
3322 0 : return false;
3323 : }
3324 :
3325 : bool
3326 0 : JamiAccount::setPushNotificationTopic(const std::string& topic)
3327 : {
3328 0 : if (SIPAccountBase::setPushNotificationTopic(topic)) {
3329 0 : if (dht_)
3330 0 : dht_->setPushNotificationTopic(topic);
3331 0 : return true;
3332 : }
3333 0 : return false;
3334 : }
3335 :
3336 : bool
3337 0 : JamiAccount::setPushNotificationConfig(const std::map<std::string, std::string>& data)
3338 : {
3339 0 : if (SIPAccountBase::setPushNotificationConfig(data)) {
3340 0 : if (dht_) {
3341 0 : dht_->setPushNotificationPlatform(config_->platform);
3342 0 : dht_->setPushNotificationTopic(config_->notificationTopic);
3343 0 : dht_->setPushNotificationToken(config_->deviceKey);
3344 : }
3345 0 : return true;
3346 : }
3347 0 : return false;
3348 : }
3349 :
3350 : /**
3351 : * To be called by clients with relevant data when a push notification is received.
3352 : */
3353 : void
3354 0 : JamiAccount::pushNotificationReceived(const std::string& /*from*/, const std::map<std::string, std::string>& data)
3355 : {
3356 0 : auto ret_future = dht_->pushNotificationReceived(data);
3357 0 : dht::ThreadPool::computation().run([id = getAccountID(), ret_future = ret_future.share()] {
3358 0 : JAMI_WARNING("[Account {:s}] pushNotificationReceived: {}", id, (uint8_t) ret_future.get());
3359 0 : });
3360 0 : }
3361 :
3362 : std::string
3363 0 : JamiAccount::getUserUri() const
3364 : {
3365 0 : if (not registeredName_.empty())
3366 0 : return JAMI_URI_PREFIX + registeredName_;
3367 0 : return JAMI_URI_PREFIX + config().username;
3368 : }
3369 :
3370 : std::vector<libjami::Message>
3371 0 : JamiAccount::getLastMessages(const uint64_t& base_timestamp)
3372 : {
3373 0 : return SIPAccountBase::getLastMessages(base_timestamp);
3374 : }
3375 :
3376 : void
3377 0 : JamiAccount::startAccountPublish()
3378 : {
3379 0 : AccountPeerInfo info_pub;
3380 0 : info_pub.accountId = dht::InfoHash(accountManager_->getInfo()->accountId);
3381 0 : info_pub.displayName = config().displayName;
3382 0 : peerDiscovery_->startPublish<AccountPeerInfo>(PEER_DISCOVERY_JAMI_SERVICE, info_pub);
3383 0 : }
3384 :
3385 : void
3386 0 : JamiAccount::startAccountDiscovery()
3387 : {
3388 0 : auto id = dht::InfoHash(accountManager_->getInfo()->accountId);
3389 0 : peerDiscovery_
3390 0 : ->startDiscovery<AccountPeerInfo>(PEER_DISCOVERY_JAMI_SERVICE, [this, id](AccountPeerInfo&& v, dht::SockAddr&&) {
3391 0 : std::lock_guard lc(discoveryMapMtx_);
3392 : // Make sure that account itself will not be recorded
3393 0 : if (v.accountId != id) {
3394 : // Create or find the old one
3395 0 : auto& dp = discoveredPeers_[v.accountId];
3396 0 : dp.displayName = v.displayName;
3397 0 : discoveredPeerMap_[v.accountId.toString()] = v.displayName;
3398 0 : if (!dp.cleanupTimer) {
3399 : // Avoid repeat reception of same peer
3400 0 : JAMI_LOG("Account discovered: {}: {}", v.displayName, v.accountId.to_c_str());
3401 : // Send Added Peer and corrsponding accoundID
3402 0 : emitSignal<libjami::PresenceSignal::NearbyPeerNotification>(getAccountID(),
3403 0 : v.accountId.toString(),
3404 : 0,
3405 0 : v.displayName);
3406 0 : dp.cleanupTimer = std::make_unique<asio::steady_timer>(*Manager::instance().ioContext(),
3407 0 : PEER_DISCOVERY_EXPIRATION);
3408 : }
3409 0 : dp.cleanupTimer->expires_after(PEER_DISCOVERY_EXPIRATION);
3410 0 : dp.cleanupTimer->async_wait(
3411 0 : [w = weak(), p = v.accountId, a = v.displayName](const asio::error_code& ec) {
3412 0 : if (ec)
3413 0 : return;
3414 0 : if (auto this_ = w.lock()) {
3415 : {
3416 0 : std::lock_guard lc(this_->discoveryMapMtx_);
3417 0 : this_->discoveredPeers_.erase(p);
3418 0 : this_->discoveredPeerMap_.erase(p.toString());
3419 0 : }
3420 : // Send deleted peer
3421 0 : emitSignal<libjami::PresenceSignal::NearbyPeerNotification>(this_->getAccountID(),
3422 0 : p.toString(),
3423 : 1,
3424 0 : a);
3425 0 : }
3426 0 : JAMI_INFO("Account removed from discovery list: %s", a.c_str());
3427 : });
3428 : }
3429 0 : });
3430 0 : }
3431 :
3432 : std::map<std::string, std::string>
3433 0 : JamiAccount::getNearbyPeers() const
3434 : {
3435 0 : return discoveredPeerMap_;
3436 : }
3437 :
3438 : void
3439 0 : JamiAccount::sendProfileToPeers()
3440 : {
3441 0 : if (!connectionManager_)
3442 0 : return;
3443 0 : std::set<std::string> peers;
3444 0 : const auto& accountUri = accountManager_->getInfo()->accountId;
3445 : // TODO: avoid using getConnectionList
3446 0 : for (const auto& connection : connectionManager_->getConnectionList()) {
3447 0 : const auto& device = connection.at("device");
3448 0 : const auto& peer = connection.at("peer");
3449 0 : if (!peers.emplace(peer).second)
3450 0 : continue;
3451 0 : if (peer == accountUri) {
3452 0 : sendProfile("", accountUri, device);
3453 0 : continue;
3454 : }
3455 0 : const auto& conversationId = convModule()->getOneToOneConversation(peer);
3456 0 : if (!conversationId.empty()) {
3457 0 : sendProfile(conversationId, peer, device);
3458 : }
3459 0 : }
3460 0 : }
3461 :
3462 : void
3463 0 : JamiAccount::updateProfile(const std::string& displayName,
3464 : const std::string& avatar,
3465 : const std::string& fileType,
3466 : int32_t flag)
3467 : {
3468 : // if the fileType is empty then only the display name will be upated
3469 :
3470 0 : const auto& accountUri = accountManager_->getInfo()->accountId;
3471 0 : const auto& path = profilePath();
3472 0 : const auto& profiles = idPath_ / "profiles";
3473 :
3474 : try {
3475 0 : if (!std::filesystem::exists(profiles)) {
3476 0 : std::filesystem::create_directories(profiles);
3477 : }
3478 0 : } catch (const std::exception& e) {
3479 0 : JAMI_ERROR("Failed to create profiles directory: {}", e.what());
3480 0 : return;
3481 0 : }
3482 :
3483 0 : const auto& vCardPath = profiles / fmt::format("{}.vcf", base64::encode(accountUri));
3484 :
3485 0 : auto profile = getProfileVcard();
3486 0 : if (profile.empty()) {
3487 0 : profile = vCard::utils::initVcard();
3488 : }
3489 :
3490 0 : profile[std::string(vCard::Property::FORMATTED_NAME)] = displayName;
3491 0 : editConfig([&](JamiAccountConfig& config) { config.displayName = displayName; });
3492 0 : emitSignal<libjami::ConfigurationSignal::AccountDetailsChanged>(getAccountID(), getAccountDetails());
3493 :
3494 0 : if (!fileType.empty()) {
3495 0 : const std::string& key = "PHOTO;ENCODING=BASE64;TYPE=" + fileType;
3496 0 : if (flag == 0) {
3497 0 : vCard::utils::removeByKey(profile, vCard::Property::PHOTO);
3498 0 : const auto& avatarPath = std::filesystem::path(avatar);
3499 0 : if (std::filesystem::exists(avatarPath)) {
3500 : try {
3501 0 : profile[key] = base64::encode(fileutils::loadFile(avatarPath));
3502 0 : } catch (const std::exception& e) {
3503 0 : JAMI_ERROR("Failed to load avatar: {}", e.what());
3504 0 : }
3505 : }
3506 0 : } else if (flag == 1) {
3507 0 : vCard::utils::removeByKey(profile, vCard::Property::PHOTO);
3508 0 : profile[key] = avatar;
3509 : }
3510 0 : }
3511 0 : if (flag == 2) {
3512 0 : vCard::utils::removeByKey(profile, vCard::Property::PHOTO);
3513 : }
3514 : try {
3515 0 : vCard::utils::save(profile, vCardPath, path);
3516 0 : emitSignal<libjami::ConfigurationSignal::ProfileReceived>(getAccountID(), accountUri, path.string());
3517 :
3518 : // Delete all profile sent markers:
3519 0 : std::error_code ec;
3520 0 : std::filesystem::remove_all(cachePath_ / "vcard", ec);
3521 0 : sendProfileToPeers();
3522 0 : } catch (const std::exception& e) {
3523 0 : JAMI_ERROR("Error writing profile: {}", e.what());
3524 0 : }
3525 0 : }
3526 :
3527 : void
3528 699 : JamiAccount::setActiveCodecs(const std::vector<unsigned>& list)
3529 : {
3530 699 : Account::setActiveCodecs(list);
3531 699 : if (!hasActiveCodec(MEDIA_AUDIO))
3532 679 : setCodecActive(AV_CODEC_ID_OPUS);
3533 699 : if (!hasActiveCodec(MEDIA_VIDEO)) {
3534 679 : setCodecActive(AV_CODEC_ID_HEVC);
3535 679 : setCodecActive(AV_CODEC_ID_H264);
3536 679 : setCodecActive(AV_CODEC_ID_VP8);
3537 : }
3538 699 : config_->activeCodecs = getActiveCodecs(MEDIA_ALL);
3539 699 : }
3540 :
3541 : void
3542 11 : JamiAccount::sendInstantMessage(const std::string& convId, const std::map<std::string, std::string>& msg)
3543 : {
3544 11 : auto members = convModule()->getConversationMembers(convId);
3545 11 : if (convId.empty() && members.empty()) {
3546 : // TODO remove, it's for old API for contacts
3547 0 : sendTextMessage(convId, "", msg);
3548 0 : return;
3549 : }
3550 33 : for (const auto& m : members) {
3551 22 : const auto& uri = m.at("uri");
3552 22 : auto token = std::uniform_int_distribution<uint64_t> {1, JAMI_ID_MAX_VAL}(rand);
3553 : // Announce to all members that a new message is sent
3554 22 : sendMessage(uri, "", msg, token, false, true);
3555 : }
3556 11 : }
3557 :
3558 : bool
3559 13235 : JamiAccount::handleMessage(const std::shared_ptr<dht::crypto::Certificate>& cert,
3560 : const std::string& from,
3561 : const std::pair<std::string, std::string>& m)
3562 : {
3563 13235 : if (not cert or not cert->issuer)
3564 0 : return true; // stop processing message
3565 :
3566 13235 : if (cert->issuer->getId().to_view() != from) {
3567 0 : JAMI_WARNING("[Account {}] [device {}] handleMessage: invalid author {}",
3568 : getAccountID(),
3569 : cert->issuer->getId().to_view(),
3570 : from);
3571 0 : return true;
3572 : }
3573 13236 : if (m.first == MIME_TYPE_GIT) {
3574 12817 : Json::Value json;
3575 12818 : if (!json::parse(m.second, json)) {
3576 0 : return true;
3577 : }
3578 :
3579 : // fetchNewCommits will do heavy stuff like fetching, avoid to block SIP socket
3580 25633 : dht::ThreadPool::io().run([w = weak(),
3581 : from,
3582 12815 : deviceId = json["deviceId"].asString(),
3583 12813 : id = json["id"].asString(),
3584 12817 : commit = json["commit"].asString()] {
3585 12818 : if (auto shared = w.lock()) {
3586 12816 : if (auto* cm = shared->convModule())
3587 12818 : cm->fetchNewCommits(from, deviceId, id, commit);
3588 12810 : }
3589 12813 : });
3590 12818 : return true;
3591 13237 : } else if (m.first == MIME_TYPE_INVITE) {
3592 136 : convModule()->onNeedConversationRequest(from, m.second);
3593 136 : return true;
3594 283 : } else if (m.first == MIME_TYPE_INVITE_JSON) {
3595 264 : Json::Value json;
3596 264 : if (!json::parse(m.second, json)) {
3597 0 : return true;
3598 : }
3599 264 : convModule()->onConversationRequest(from, json);
3600 264 : return true;
3601 283 : } else if (m.first == MIME_TYPE_IM_COMPOSING) {
3602 : try {
3603 4 : static const std::regex COMPOSING_REGEX("<state>\\s*(\\w+)\\s*<\\/state>");
3604 4 : std::smatch matched_pattern;
3605 4 : std::regex_search(m.second, matched_pattern, COMPOSING_REGEX);
3606 4 : bool isComposing {false};
3607 4 : if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
3608 4 : isComposing = matched_pattern[1] == "active";
3609 : }
3610 4 : static const std::regex CONVID_REGEX("<conversation>\\s*(\\w+)\\s*<\\/conversation>");
3611 4 : std::regex_search(m.second, matched_pattern, CONVID_REGEX);
3612 4 : std::string conversationId = "";
3613 4 : if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
3614 4 : conversationId = matched_pattern[1];
3615 : }
3616 4 : if (!conversationId.empty()) {
3617 4 : if (auto* cm = convModule(true)) {
3618 4 : if (auto typer = cm->getTypers(conversationId)) {
3619 4 : if (isComposing)
3620 3 : typer->addTyper(from);
3621 : else
3622 1 : typer->removeTyper(from);
3623 4 : }
3624 : }
3625 : }
3626 4 : return true;
3627 4 : } catch (const std::exception& e) {
3628 0 : JAMI_WARNING("Error parsing composing state: {}", e.what());
3629 0 : }
3630 15 : } else if (m.first == MIME_TYPE_IMDN) {
3631 : try {
3632 9 : static const std::regex IMDN_MSG_ID_REGEX("<message-id>\\s*(\\w+)\\s*<\\/message-id>");
3633 9 : std::smatch matched_pattern;
3634 :
3635 9 : std::regex_search(m.second, matched_pattern, IMDN_MSG_ID_REGEX);
3636 9 : std::string messageId;
3637 9 : if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
3638 9 : messageId = matched_pattern[1];
3639 : } else {
3640 0 : JAMI_WARNING("Message displayed: unable to parse message ID");
3641 0 : return true;
3642 : }
3643 :
3644 9 : static const std::regex STATUS_REGEX("<status>\\s*<(\\w+)\\/>\\s*<\\/status>");
3645 9 : std::regex_search(m.second, matched_pattern, STATUS_REGEX);
3646 9 : bool isDisplayed {false};
3647 9 : if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
3648 9 : isDisplayed = matched_pattern[1] == "displayed";
3649 : } else {
3650 0 : JAMI_WARNING("Message displayed: unable to parse status");
3651 0 : return true;
3652 : }
3653 :
3654 9 : static const std::regex CONVID_REGEX("<conversation>\\s*(\\w+)\\s*<\\/conversation>");
3655 9 : std::regex_search(m.second, matched_pattern, CONVID_REGEX);
3656 9 : std::string conversationId = "";
3657 9 : if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
3658 9 : conversationId = matched_pattern[1];
3659 : }
3660 :
3661 9 : if (!isReadReceiptEnabled())
3662 0 : return true;
3663 9 : if (isDisplayed) {
3664 9 : if (convModule()->onMessageDisplayed(from, conversationId, messageId)) {
3665 32 : JAMI_DEBUG("[message {}] Displayed by peer", messageId);
3666 16 : emitSignal<libjami::ConfigurationSignal::AccountMessageStatusChanged>(
3667 8 : accountID_,
3668 : conversationId,
3669 : from,
3670 : messageId,
3671 : static_cast<int>(libjami::Account::MessageStates::DISPLAYED));
3672 : }
3673 : }
3674 9 : return true;
3675 9 : } catch (const std::exception& e) {
3676 0 : JAMI_ERROR("Error parsing display notification: {}", e.what());
3677 0 : }
3678 6 : } else if (m.first == MIME_TYPE_PIDF) {
3679 6 : std::smatch matched_pattern;
3680 6 : static const std::regex BASIC_REGEX("<basic>([\\w\\s]+)<\\/basic>");
3681 6 : std::regex_search(m.second, matched_pattern, BASIC_REGEX);
3682 6 : std::string customStatus {};
3683 6 : if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
3684 6 : customStatus = matched_pattern[1];
3685 6 : emitSignal<libjami::PresenceSignal::NewBuddyNotification>(getAccountID(),
3686 : from,
3687 : static_cast<int>(PresenceState::CONNECTED),
3688 : customStatus);
3689 : } else {
3690 0 : JAMI_WARNING("Presence: unable to parse status");
3691 : }
3692 6 : return true;
3693 6 : }
3694 :
3695 0 : return false;
3696 : }
3697 :
3698 : void
3699 94 : JamiAccount::callConnectionClosed(const DeviceId& deviceId, bool eraseDummy)
3700 : {
3701 94 : std::function<void(const DeviceId&, bool)> cb;
3702 : {
3703 94 : std::lock_guard lk(onConnectionClosedMtx_);
3704 94 : auto it = onConnectionClosed_.find(deviceId);
3705 94 : if (it != onConnectionClosed_.end()) {
3706 25 : if (eraseDummy) {
3707 25 : cb = std::move(it->second);
3708 25 : onConnectionClosed_.erase(it);
3709 : } else {
3710 : // In this case a new subcall is created and the callback
3711 : // will be re-called once with eraseDummy = true
3712 0 : cb = it->second;
3713 : }
3714 : }
3715 94 : }
3716 94 : dht::ThreadPool::io().run([w = weak(), cb = std::move(cb), id = deviceId, erase = std::move(eraseDummy)] {
3717 94 : if (auto acc = w.lock()) {
3718 94 : if (cb)
3719 25 : cb(id, erase);
3720 94 : }
3721 94 : });
3722 94 : }
3723 :
3724 : void
3725 3302 : JamiAccount::requestMessageConnection(const std::string& peerId,
3726 : const DeviceId& deviceId,
3727 : const std::string& connectionType)
3728 : {
3729 3302 : std::shared_lock lk(connManagerMtx_);
3730 3302 : auto* handler = static_cast<MessageChannelHandler*>(channelHandlers_[Uri::Scheme::MESSAGE].get());
3731 3302 : if (!handler)
3732 3 : return;
3733 3299 : if (deviceId) {
3734 3299 : if (auto connected = handler->getChannel(peerId, deviceId)) {
3735 1869 : return;
3736 3299 : }
3737 : } else {
3738 0 : auto connected = handler->getChannels(peerId);
3739 0 : if (!connected.empty()) {
3740 0 : return;
3741 : }
3742 0 : }
3743 1430 : handler->connect(
3744 : deviceId,
3745 : "",
3746 779 : [w = weak(), peerId](const std::shared_ptr<dhtnet::ChannelSocket>& socket, const DeviceId& deviceId) {
3747 779 : if (socket)
3748 691 : dht::ThreadPool::io().run([w, peerId, deviceId] {
3749 692 : if (auto acc = w.lock()) {
3750 692 : acc->messageEngine_.onPeerOnline(peerId);
3751 692 : acc->messageEngine_.onPeerOnline(peerId, deviceId.toString(), true);
3752 692 : if (!acc->presenceNote_.empty()) {
3753 : // If a presence note is set, send it to this device.
3754 4 : auto token = std::uniform_int_distribution<uint64_t> {1, JAMI_ID_MAX_VAL}(acc->rand);
3755 16 : std::map<std::string, std::string> msg = {{MIME_TYPE_PIDF, getPIDF(acc->presenceNote_)}};
3756 4 : acc->sendMessage(peerId, deviceId.toString(), msg, token, false, true);
3757 4 : }
3758 692 : acc->convModule()->syncConversations(peerId, deviceId.toString());
3759 692 : }
3760 691 : });
3761 779 : },
3762 : connectionType);
3763 3302 : }
3764 :
3765 : void
3766 35 : JamiAccount::requestSIPConnection(const std::string& peerId,
3767 : const DeviceId& deviceId,
3768 : const std::string& connectionType,
3769 : bool forceNewConnection,
3770 : const std::shared_ptr<SIPCall>& pc)
3771 : {
3772 35 : if (peerId == getUsername()) {
3773 0 : if (!syncModule()->isConnected(deviceId))
3774 0 : channelHandlers_[Uri::Scheme::SYNC]->connect(deviceId,
3775 : "",
3776 0 : [](const std::shared_ptr<dhtnet::ChannelSocket>& /*socket*/,
3777 0 : const DeviceId& /*deviceId*/) {});
3778 : }
3779 :
3780 140 : JAMI_LOG("[Account {}] Request SIP connection to peer {} on device {}", getAccountID(), peerId, deviceId);
3781 :
3782 : // If a connection already exists or is in progress, no need to do this
3783 35 : std::lock_guard lk(sipConnsMtx_);
3784 35 : auto id = std::make_pair(peerId, deviceId);
3785 :
3786 35 : if (sipConns_.find(id) != sipConns_.end()) {
3787 0 : JAMI_LOG("[Account {}] A SIP connection with {} already exists", getAccountID(), deviceId);
3788 0 : return;
3789 : }
3790 : // If not present, create it
3791 35 : std::shared_lock lkCM(connManagerMtx_);
3792 35 : if (!connectionManager_)
3793 0 : return;
3794 : // Note, Even if we send 50 "sip" request, the connectionManager_ will only use one socket.
3795 : // however, this will still ask for multiple channels, so only ask
3796 : // if there is no pending request
3797 35 : if (!forceNewConnection && connectionManager_->isConnecting(deviceId, "sip")) {
3798 0 : JAMI_LOG("[Account {}] Already connecting to {}", getAccountID(), deviceId);
3799 0 : return;
3800 : }
3801 140 : JAMI_LOG("[Account {}] Ask {} for a new SIP channel", getAccountID(), deviceId);
3802 35 : dhtnet::ConnectDeviceOptions options;
3803 35 : options.noNewSocket = false;
3804 35 : options.forceNewSocket = forceNewConnection;
3805 35 : options.connType = connectionType;
3806 35 : options.channelTimeout = 3s;
3807 105 : connectionManager_->connectDevice(
3808 : deviceId,
3809 : "sip",
3810 70 : [w = weak(), id = std::move(id), pc = std::move(pc)](const std::shared_ptr<dhtnet::ChannelSocket>& socket,
3811 : const DeviceId&) {
3812 35 : if (socket)
3813 32 : return;
3814 3 : auto shared = w.lock();
3815 3 : if (!shared)
3816 0 : return;
3817 : // If this is triggered, this means that the
3818 : // connectDevice didn't get any response from the DHT.
3819 : // Stop searching pending call.
3820 3 : shared->callConnectionClosed(id.second, true);
3821 3 : if (pc)
3822 3 : pc->onFailure(PJSIP_SC_TEMPORARILY_UNAVAILABLE);
3823 3 : },
3824 : options);
3825 35 : }
3826 :
3827 : bool
3828 352 : JamiAccount::isConnectedWith(const DeviceId& deviceId) const
3829 : {
3830 352 : std::shared_lock lkCM(connManagerMtx_);
3831 352 : if (connectionManager_)
3832 352 : return connectionManager_->isConnected(deviceId);
3833 0 : return false;
3834 352 : }
3835 :
3836 : void
3837 3 : JamiAccount::sendPresenceNote(const std::string& note)
3838 : {
3839 3 : if (const auto* info = accountManager_->getInfo()) {
3840 3 : if (!info || !info->contacts)
3841 0 : return;
3842 3 : presenceNote_ = note;
3843 3 : auto contacts = info->contacts->getContacts();
3844 3 : std::vector<std::pair<std::string, DeviceId>> keys;
3845 : {
3846 3 : std::shared_lock lkCM(connManagerMtx_);
3847 3 : auto* handler = static_cast<MessageChannelHandler*>(channelHandlers_[Uri::Scheme::MESSAGE].get());
3848 3 : if (!handler)
3849 0 : return;
3850 5 : for (const auto& contact : contacts) {
3851 2 : auto peerId = contact.first.toString();
3852 2 : auto channels = handler->getChannels(peerId);
3853 4 : for (const auto& channel : channels) {
3854 2 : keys.emplace_back(peerId, channel->deviceId());
3855 : }
3856 2 : }
3857 3 : }
3858 3 : auto token = std::uniform_int_distribution<uint64_t> {1, JAMI_ID_MAX_VAL}(rand);
3859 12 : std::map<std::string, std::string> msg = {{MIME_TYPE_PIDF, getPIDF(presenceNote_)}};
3860 5 : for (auto& key : keys) {
3861 2 : sendMessage(key.first, key.second.toString(), msg, token, false, true);
3862 : }
3863 3 : }
3864 : }
3865 :
3866 : void
3867 620 : JamiAccount::sendProfile(const std::string& convId, const std::string& peerUri, const std::string& deviceId)
3868 : {
3869 620 : auto accProfilePath = profilePath();
3870 620 : if (not std::filesystem::is_regular_file(accProfilePath))
3871 613 : return;
3872 8 : auto currentSha3 = fileutils::sha3File(accProfilePath);
3873 : // VCard sync for peerUri
3874 8 : if (not needToSendProfile(peerUri, deviceId, currentSha3)) {
3875 0 : JAMI_DEBUG("[Account {}] [device {}] Peer {} already got an up-to-date vCard",
3876 : getAccountID(),
3877 : deviceId,
3878 : peerUri);
3879 0 : return;
3880 : }
3881 : // We need a new channel
3882 24 : transferFile(convId,
3883 16 : accProfilePath.string(),
3884 : deviceId,
3885 : "profile.vcf",
3886 : "",
3887 : 0,
3888 : 0,
3889 : currentSha3,
3890 : fileutils::lastWriteTimeInSeconds(accProfilePath),
3891 8 : [accId = getAccountID(), peerUri, deviceId]() {
3892 : // Mark the VCard as sent
3893 12 : auto sendDir = fileutils::get_cache_dir() / accId / "vcard" / peerUri;
3894 6 : auto path = sendDir / deviceId;
3895 6 : dhtnet::fileutils::recursive_mkdir(sendDir);
3896 6 : std::lock_guard lock(dhtnet::fileutils::getFileLock(path));
3897 6 : if (std::filesystem::is_regular_file(path))
3898 0 : return;
3899 6 : std::ofstream p(path);
3900 6 : });
3901 621 : }
3902 :
3903 : bool
3904 8 : JamiAccount::needToSendProfile(const std::string& peerUri, const std::string& deviceId, const std::string& sha3Sum)
3905 : {
3906 8 : std::string previousSha3 {};
3907 8 : auto vCardPath = cachePath_ / "vcard";
3908 8 : auto sha3Path = vCardPath / "sha3";
3909 8 : dhtnet::fileutils::check_dir(vCardPath, 0700);
3910 : try {
3911 11 : previousSha3 = fileutils::loadTextFile(sha3Path);
3912 3 : } catch (...) {
3913 3 : fileutils::saveFile(sha3Path, (const uint8_t*) sha3Sum.data(), sha3Sum.size(), 0600);
3914 3 : return true;
3915 3 : }
3916 5 : if (sha3Sum != previousSha3) {
3917 : // Incorrect sha3 stored. Update it
3918 0 : dhtnet::fileutils::removeAll(vCardPath, true);
3919 0 : dhtnet::fileutils::check_dir(vCardPath, 0700);
3920 0 : fileutils::saveFile(sha3Path, (const uint8_t*) sha3Sum.data(), sha3Sum.size(), 0600);
3921 0 : return true;
3922 : }
3923 5 : auto peerPath = vCardPath / peerUri;
3924 5 : dhtnet::fileutils::recursive_mkdir(peerPath);
3925 5 : return not std::filesystem::is_regular_file(peerPath / deviceId);
3926 8 : }
3927 :
3928 : void
3929 134 : JamiAccount::clearProfileCache(const std::string& peerUri)
3930 : {
3931 134 : std::error_code ec;
3932 134 : std::filesystem::remove_all(cachePath_ / "vcard" / peerUri, ec);
3933 134 : }
3934 :
3935 : std::filesystem::path
3936 623 : JamiAccount::profilePath() const
3937 : {
3938 623 : return idPath_ / "profile.vcf";
3939 : }
3940 :
3941 : void
3942 66 : JamiAccount::cacheSIPConnection(std::shared_ptr<dhtnet::ChannelSocket>&& socket,
3943 : const std::string& peerId,
3944 : const DeviceId& deviceId)
3945 : {
3946 66 : std::unique_lock lk(sipConnsMtx_);
3947 : // Verify that the connection is not already cached
3948 66 : SipConnectionKey key(peerId, deviceId);
3949 66 : auto& connections = sipConns_[key];
3950 68 : auto conn = std::find_if(connections.begin(), connections.end(), [&](const auto& v) { return v.channel == socket; });
3951 66 : if (conn != connections.end()) {
3952 0 : JAMI_WARNING("[Account {}] Channel socket already cached with this peer", getAccountID());
3953 0 : return;
3954 : }
3955 :
3956 : // Convert to SIP transport
3957 66 : auto onShutdown = [w = weak(), peerId, key, socket]() {
3958 66 : dht::ThreadPool::io().run([w = std::move(w), peerId, key, socket] {
3959 66 : auto shared = w.lock();
3960 66 : if (!shared)
3961 0 : return;
3962 66 : shared->shutdownSIPConnection(socket, key.first, key.second);
3963 : // The connection can be closed during the SIP initialization, so
3964 : // if this happens, the request should be re-sent to ask for a new
3965 : // SIP channel to make the call pass through
3966 66 : shared->callConnectionClosed(key.second, false);
3967 66 : });
3968 132 : };
3969 132 : auto sip_tr = link_.sipTransportBroker->getChanneledTransport(shared(), socket, std::move(onShutdown));
3970 66 : if (!sip_tr) {
3971 0 : JAMI_ERROR("No channeled transport found");
3972 0 : return;
3973 : }
3974 : // Store the connection
3975 66 : connections.emplace_back(SipConnection {sip_tr, socket});
3976 264 : JAMI_WARNING("[Account {:s}] [device {}] New SIP channel opened", getAccountID(), deviceId);
3977 66 : lk.unlock();
3978 :
3979 : // Retry messages
3980 66 : messageEngine_.onPeerOnline(peerId);
3981 66 : messageEngine_.onPeerOnline(peerId, deviceId.toString(), true);
3982 :
3983 : // Connect pending calls
3984 66 : forEachPendingCall(deviceId, [&](const auto& pc) {
3985 32 : if (pc->getConnectionState() != Call::ConnectionState::TRYING
3986 32 : and pc->getConnectionState() != Call::ConnectionState::PROGRESSING)
3987 0 : return;
3988 32 : pc->setSipTransport(sip_tr, getContactHeader(sip_tr));
3989 32 : pc->setState(Call::ConnectionState::PROGRESSING);
3990 32 : if (auto remote_address = socket->getRemoteAddress()) {
3991 : try {
3992 32 : onConnectedOutgoingCall(pc, peerId, remote_address);
3993 0 : } catch (const VoipLinkException&) {
3994 : // In this case, the main scenario is that SIPStartCall failed because
3995 : // the ICE is dead and the TLS session didn't send any packet on that dead
3996 : // link (connectivity change, killed by the os, etc)
3997 : // Here, we don't need to do anything, the TLS will fail and will delete
3998 : // the cached transport
3999 : }
4000 : }
4001 : });
4002 66 : }
4003 :
4004 : void
4005 66 : JamiAccount::shutdownSIPConnection(const std::shared_ptr<dhtnet::ChannelSocket>& channel,
4006 : const std::string& peerId,
4007 : const DeviceId& deviceId)
4008 : {
4009 66 : std::unique_lock lk(sipConnsMtx_);
4010 66 : SipConnectionKey key(peerId, deviceId);
4011 66 : auto it = sipConns_.find(key);
4012 66 : if (it != sipConns_.end()) {
4013 33 : auto& conns = it->second;
4014 67 : conns.erase(std::remove_if(conns.begin(), conns.end(), [&](auto v) { return v.channel == channel; }),
4015 33 : conns.end());
4016 33 : if (conns.empty()) {
4017 32 : sipConns_.erase(it);
4018 : }
4019 : }
4020 66 : lk.unlock();
4021 : // Shutdown after removal to let the callbacks do stuff if needed
4022 66 : if (channel)
4023 66 : channel->shutdown();
4024 66 : }
4025 :
4026 : std::string_view
4027 29806 : JamiAccount::currentDeviceId() const
4028 : {
4029 29806 : if (!accountManager_ or not accountManager_->getInfo())
4030 0 : return {};
4031 29802 : return accountManager_->getInfo()->deviceId;
4032 : }
4033 :
4034 : std::shared_ptr<TransferManager>
4035 159 : JamiAccount::dataTransfer(const std::string& id)
4036 : {
4037 159 : if (id.empty())
4038 73 : return nonSwarmTransferManager_;
4039 86 : if (auto* cm = convModule())
4040 86 : return cm->dataTransfer(id);
4041 0 : return {};
4042 : }
4043 :
4044 : void
4045 0 : JamiAccount::monitor()
4046 : {
4047 0 : JAMI_DEBUG("[Account {:s}] Monitor connections", getAccountID());
4048 0 : JAMI_DEBUG("[Account {:s}] Using proxy: {:s}", getAccountID(), proxyServerCached_);
4049 :
4050 0 : if (auto* cm = convModule())
4051 0 : cm->monitor();
4052 0 : std::shared_lock lkCM(connManagerMtx_);
4053 0 : if (connectionManager_)
4054 0 : connectionManager_->monitor();
4055 0 : }
4056 :
4057 : std::vector<std::map<std::string, std::string>>
4058 0 : JamiAccount::getConnectionList(const std::string& conversationId)
4059 : {
4060 0 : std::shared_lock lkCM(connManagerMtx_);
4061 0 : if (connectionManager_ && conversationId.empty()) {
4062 0 : return connectionManager_->getConnectionList();
4063 0 : } else if (connectionManager_ && convModule_) {
4064 0 : std::vector<std::map<std::string, std::string>> connectionList;
4065 0 : if (auto conv = convModule_->getConversation(conversationId)) {
4066 0 : for (const auto& deviceId : conv->getDeviceIdList()) {
4067 0 : auto connections = connectionManager_->getConnectionList(deviceId);
4068 0 : connectionList.reserve(connectionList.size() + connections.size());
4069 0 : std::move(connections.begin(), connections.end(), std::back_inserter(connectionList));
4070 0 : }
4071 0 : }
4072 0 : return connectionList;
4073 0 : } else {
4074 0 : return {};
4075 : }
4076 0 : }
4077 :
4078 : std::vector<std::map<std::string, std::string>>
4079 0 : JamiAccount::getConversationConnectivity(const std::string& conversationId)
4080 : {
4081 0 : std::shared_lock lkCM(connManagerMtx_);
4082 0 : if (convModule_) {
4083 0 : if (auto conv = convModule_->getConversation(conversationId)) {
4084 0 : return conv->getConnectivity();
4085 0 : }
4086 : }
4087 0 : return {};
4088 0 : }
4089 :
4090 : std::vector<std::map<std::string, std::string>>
4091 0 : JamiAccount::getConversationTrackedMembers(const std::string& conversationId)
4092 : {
4093 0 : std::shared_lock lkCM(connManagerMtx_);
4094 0 : if (convModule_) {
4095 0 : if (auto conv = convModule_->getConversation(conversationId)) {
4096 0 : return conv->getTrackedMembers();
4097 0 : }
4098 : }
4099 0 : return {};
4100 0 : }
4101 :
4102 : std::vector<std::map<std::string, std::string>>
4103 0 : JamiAccount::getChannelList(const std::string& connectionId)
4104 : {
4105 0 : std::shared_lock lkCM(connManagerMtx_);
4106 0 : if (!connectionManager_)
4107 0 : return {};
4108 0 : return connectionManager_->getChannelList(connectionId);
4109 0 : }
4110 :
4111 : void
4112 14 : JamiAccount::sendFile(const std::string& conversationId,
4113 : const std::filesystem::path& path,
4114 : const std::string& name,
4115 : const std::string& replyTo)
4116 : {
4117 14 : std::error_code ec;
4118 14 : if (!std::filesystem::is_regular_file(path, ec)) {
4119 0 : JAMI_ERROR("Invalid filename '{}'", path);
4120 0 : emitSignal<libjami::ConversationSignal::OnConversationError>(getAccountID(),
4121 : conversationId,
4122 : EVALIDFETCH,
4123 : "Invalid filename.");
4124 0 : return;
4125 : }
4126 :
4127 14 : auto fileSize = std::filesystem::file_size(path, ec);
4128 14 : if (ec || fileSize == static_cast<decltype(fileSize)>(-1)) {
4129 0 : JAMI_ERROR("Negative file size, user probably doesn't have the appropriate permissions for '{}'", path);
4130 0 : emitSignal<libjami::ConversationSignal::OnConversationError>(
4131 0 : getAccountID(),
4132 : conversationId,
4133 : EVALIDFETCH,
4134 : "Negative file size, could be due to insufficient file permissions.");
4135 0 : return;
4136 : }
4137 :
4138 : // NOTE: this sendMessage is in a computation thread because
4139 : // sha3sum can take quite some time to computer if the user decide
4140 : // to send a big file
4141 14 : dht::ThreadPool::computation().run([w = weak(), conversationId, path, name, fileSize, replyTo]() {
4142 14 : if (auto shared = w.lock()) {
4143 14 : Json::Value value;
4144 14 : auto tid = jami::generateUID(shared->rand);
4145 14 : auto displayName = name.empty() ? path.filename().string() : name;
4146 14 : value["tid"] = std::to_string(tid);
4147 14 : value["displayName"] = displayName;
4148 14 : value["totalSize"] = std::to_string(fileSize);
4149 14 : value["sha3sum"] = fileutils::sha3File(path);
4150 14 : value["type"] = "application/data-transfer+json";
4151 :
4152 70 : shared->convModule()->sendMessage(
4153 14 : conversationId,
4154 14 : std::move(value),
4155 14 : replyTo,
4156 : true,
4157 42 : [accId = shared->getAccountID(), conversationId, tid, displayName, path](const std::string& commitId) {
4158 : // Create a symlink to answer to re-ask
4159 28 : auto filelinkPath = fileutils::get_data_dir() / accId / "conversation_data" / conversationId
4160 42 : / getFileId(commitId, std::to_string(tid), displayName);
4161 14 : if (path != filelinkPath && !std::filesystem::is_symlink(filelinkPath)) {
4162 14 : if (!fileutils::createFileLink(filelinkPath, path, true)) {
4163 0 : JAMI_WARNING("Unable to create symlink for file transfer {} - {}. Copy file",
4164 : filelinkPath,
4165 : path);
4166 0 : std::error_code ec;
4167 0 : auto success = std::filesystem::copy_file(path, filelinkPath, ec);
4168 0 : if (ec || !success) {
4169 0 : JAMI_ERROR("Unable to copy file for file transfer {} - {}", filelinkPath, path);
4170 : // Signal to notify clients that the operation failed.
4171 : // The fileId field sends the filePath.
4172 : // libjami::DataTransferEventCode::unsupported (2) is unused elsewhere.
4173 0 : emitSignal<libjami::DataTransferSignal::DataTransferEvent>(
4174 0 : accId,
4175 0 : conversationId,
4176 : commitId,
4177 0 : path.string(),
4178 : uint32_t(libjami::DataTransferEventCode::invalid));
4179 : } else {
4180 : // Signal to notify clients that the file is copied and can be
4181 : // safely deleted. The fileId field sends the filePath.
4182 : // libjami::DataTransferEventCode::created (1) is unused elsewhere.
4183 0 : emitSignal<libjami::DataTransferSignal::DataTransferEvent>(
4184 0 : accId,
4185 0 : conversationId,
4186 : commitId,
4187 0 : path.string(),
4188 : uint32_t(libjami::DataTransferEventCode::created));
4189 : }
4190 : } else {
4191 28 : emitSignal<libjami::DataTransferSignal::DataTransferEvent>(
4192 14 : accId,
4193 14 : conversationId,
4194 : commitId,
4195 28 : path.string(),
4196 : uint32_t(libjami::DataTransferEventCode::created));
4197 : }
4198 : }
4199 14 : });
4200 28 : }
4201 14 : });
4202 : }
4203 :
4204 : void
4205 8 : JamiAccount::transferFile(const std::string& conversationId,
4206 : const std::string& path,
4207 : const std::string& deviceId,
4208 : const std::string& fileId,
4209 : const std::string& interactionId,
4210 : size_t start,
4211 : size_t end,
4212 : const std::string& sha3Sum,
4213 : uint64_t lastWriteTime,
4214 : std::function<void()> onFinished)
4215 : {
4216 8 : std::string modified;
4217 8 : if (lastWriteTime != 0) {
4218 16 : modified = fmt::format("&modified={}", lastWriteTime);
4219 : }
4220 16 : auto fid = fileId == "profile.vcf" ? fmt::format("profile.vcf?sha3={}{}", sha3Sum, modified) : fileId;
4221 8 : auto channelName = conversationId.empty()
4222 : ? fmt::format("{}profile.vcf?sha3={}{}", DATA_TRANSFER_SCHEME, sha3Sum, modified)
4223 16 : : fmt::format("{}{}/{}/{}", DATA_TRANSFER_SCHEME, conversationId, currentDeviceId(), fid);
4224 8 : std::shared_lock lkCM(connManagerMtx_);
4225 8 : if (!connectionManager_)
4226 0 : return;
4227 40 : connectionManager_->connectDevice(
4228 16 : DeviceId(deviceId),
4229 : channelName,
4230 8 : [this,
4231 : conversationId,
4232 8 : path = std::move(path),
4233 : fileId,
4234 : interactionId,
4235 : start,
4236 : end,
4237 8 : onFinished = std::move(onFinished)](std::shared_ptr<dhtnet::ChannelSocket> socket, const DeviceId&) {
4238 8 : if (!socket)
4239 0 : return;
4240 40 : dht::ThreadPool::io().run([w = weak(),
4241 8 : path = std::move(path),
4242 8 : socket = std::move(socket),
4243 8 : conversationId = std::move(conversationId),
4244 8 : fileId,
4245 8 : interactionId,
4246 : start,
4247 : end,
4248 8 : onFinished = std::move(onFinished)] {
4249 8 : if (auto shared = w.lock())
4250 8 : if (auto dt = shared->dataTransfer(conversationId))
4251 16 : dt->transferFile(socket, fileId, interactionId, path, start, end, std::move(onFinished));
4252 8 : });
4253 : });
4254 8 : }
4255 :
4256 : void
4257 13 : JamiAccount::askForFileChannel(const std::string& conversationId,
4258 : const std::string& deviceId,
4259 : const std::string& interactionId,
4260 : const std::string& fileId,
4261 : size_t start,
4262 : size_t end)
4263 : {
4264 29 : auto tryDevice = [=](const auto& did) {
4265 29 : std::shared_lock lkCM(connManagerMtx_);
4266 29 : if (!connectionManager_)
4267 0 : return;
4268 :
4269 29 : auto channelName = fmt::format("{}{}/{}/{}", DATA_TRANSFER_SCHEME, conversationId, currentDeviceId(), fileId);
4270 29 : if (start != 0 || end != 0) {
4271 6 : channelName += fmt::format("?start={}&end={}", start, end);
4272 : }
4273 : // We can avoid to negotiate new sessions, as the file notif
4274 : // probably came from an online device or last connected device.
4275 58 : connectionManager_->connectDevice(
4276 : did,
4277 : channelName,
4278 41 : [w = weak(),
4279 29 : conversationId,
4280 29 : fileId,
4281 29 : interactionId,
4282 : start](const std::shared_ptr<dhtnet::ChannelSocket>& channel, const DeviceId&) {
4283 29 : if (!channel)
4284 17 : return;
4285 12 : dht::ThreadPool::io().run([w, conversationId, channel, fileId, interactionId, start] {
4286 12 : auto shared = w.lock();
4287 12 : if (!shared)
4288 0 : return;
4289 12 : auto dt = shared->dataTransfer(conversationId);
4290 12 : if (!dt)
4291 0 : return;
4292 12 : if (interactionId.empty())
4293 0 : dt->onIncomingProfile(channel);
4294 : else
4295 12 : dt->onIncomingFileTransfer(fileId, channel, start);
4296 12 : });
4297 : },
4298 : false);
4299 42 : };
4300 :
4301 13 : if (!deviceId.empty()) {
4302 : // Only ask for device
4303 1 : tryDevice(DeviceId(deviceId));
4304 : } else {
4305 : // Only ask for connected devices. For others we will attempt
4306 : // with new peer online
4307 40 : for (const auto& m : convModule()->getConversationMembers(conversationId)) {
4308 28 : accountManager_->forEachDevice(dht::InfoHash(m.at("uri")),
4309 28 : [tryDevice](const std::shared_ptr<dht::crypto::PublicKey>& dev) {
4310 28 : tryDevice(dev->getLongId());
4311 28 : });
4312 12 : }
4313 : }
4314 13 : }
4315 :
4316 : void
4317 58 : JamiAccount::askForProfile(const std::string& conversationId, const std::string& deviceId, const std::string& memberUri)
4318 : {
4319 58 : std::shared_lock lkCM(connManagerMtx_);
4320 58 : if (!connectionManager_)
4321 0 : return;
4322 :
4323 58 : auto channelName = fmt::format("{}{}/profile/{}.vcf", DATA_TRANSFER_SCHEME, conversationId, memberUri);
4324 : // We can avoid to negotiate new sessions, as the file notif
4325 : // probably came from an online device or last connected device.
4326 174 : connectionManager_->connectDevice(
4327 116 : DeviceId(deviceId),
4328 : channelName,
4329 107 : [this, conversationId](const std::shared_ptr<dhtnet::ChannelSocket>& channel, const DeviceId&) {
4330 58 : if (!channel)
4331 9 : return;
4332 49 : dht::ThreadPool::io().run([w = weak(), conversationId, channel] {
4333 49 : if (auto shared = w.lock())
4334 49 : if (auto dt = shared->dataTransfer(conversationId))
4335 98 : dt->onIncomingProfile(channel);
4336 49 : });
4337 : },
4338 : false);
4339 58 : }
4340 :
4341 : void
4342 2199 : JamiAccount::onPeerConnected(const std::string& peerId, bool connected)
4343 : {
4344 2199 : auto isOnline = presenceManager_ && presenceManager_->isOnline(peerId);
4345 3298 : auto newState = connected ? PresenceState::CONNECTED
4346 1099 : : (isOnline ? PresenceState::AVAILABLE : PresenceState::DISCONNECTED);
4347 :
4348 2199 : runOnMainThread([w = weak(), peerId, newState] {
4349 2199 : if (auto sthis = w.lock()) {
4350 2199 : std::lock_guard lock(sthis->presenceStateMtx_);
4351 2199 : auto& state = sthis->presenceState_[peerId];
4352 2199 : if (state != newState) {
4353 2199 : state = newState;
4354 2199 : emitSignal<libjami::PresenceSignal::NewBuddyNotification>(sthis->getAccountID(),
4355 2199 : peerId,
4356 : static_cast<int>(newState),
4357 : "");
4358 : }
4359 4398 : }
4360 2199 : });
4361 2199 : }
4362 :
4363 : void
4364 672 : JamiAccount::initConnectionManager()
4365 : {
4366 672 : if (!nonSwarmTransferManager_)
4367 567 : nonSwarmTransferManager_ = std::make_shared<TransferManager>(accountID_,
4368 567 : config().username,
4369 : "",
4370 1701 : dht::crypto::getDerivedRandomEngine(rand));
4371 672 : if (!connectionManager_) {
4372 578 : auto connectionManagerConfig = std::make_shared<dhtnet::ConnectionManager::Config>();
4373 578 : connectionManagerConfig->ioContext = Manager::instance().ioContext();
4374 578 : connectionManagerConfig->dht = dht();
4375 578 : connectionManagerConfig->certStore = certStore_;
4376 578 : connectionManagerConfig->id = identity();
4377 578 : connectionManagerConfig->upnpCtrl = upnpCtrl_;
4378 578 : connectionManagerConfig->turnServer = config().turnServer;
4379 578 : connectionManagerConfig->upnpEnabled = config().upnpEnabled;
4380 578 : connectionManagerConfig->turnServerUserName = config().turnServerUserName;
4381 578 : connectionManagerConfig->turnServerPwd = config().turnServerPwd;
4382 578 : connectionManagerConfig->turnServerRealm = config().turnServerRealm;
4383 578 : connectionManagerConfig->turnEnabled = config().turnEnabled;
4384 578 : connectionManagerConfig->cachePath = cachePath_;
4385 578 : if (Manager::instance().dhtnetLogLevel > 0) {
4386 0 : connectionManagerConfig->logger = logger_;
4387 : }
4388 578 : connectionManagerConfig->factory = Manager::instance().getIceTransportFactory();
4389 578 : connectionManagerConfig->turnCache = turnCache_;
4390 578 : connectionManagerConfig->rng = std::make_unique<std::mt19937_64>(dht::crypto::getDerivedRandomEngine(rand));
4391 578 : connectionManagerConfig->legacyMode = dhtnet::LegacyMode::Supported;
4392 578 : connectionManager_ = std::make_unique<dhtnet::ConnectionManager>(connectionManagerConfig);
4393 1156 : channelHandlers_[Uri::Scheme::SWARM] = std::make_unique<SwarmChannelHandler>(shared(),
4394 1156 : *connectionManager_.get());
4395 1156 : channelHandlers_[Uri::Scheme::GIT] = std::make_unique<ConversationChannelHandler>(shared(),
4396 1156 : *connectionManager_.get());
4397 578 : channelHandlers_[Uri::Scheme::SYNC] = std::make_unique<SyncChannelHandler>(shared(), *connectionManager_.get());
4398 578 : channelHandlers_[Uri::Scheme::DATA_TRANSFER]
4399 1156 : = std::make_unique<TransferChannelHandler>(shared(), *connectionManager_.get());
4400 1734 : channelHandlers_[Uri::Scheme::MESSAGE] = std::make_unique<MessageChannelHandler>(
4401 578 : *connectionManager_.get(),
4402 13233 : [this](const auto& cert, std::string& type, const std::string& content) {
4403 26469 : onTextMessage("", cert->issuer->getId().toString(), cert, {{type, content}});
4404 13236 : },
4405 2200 : [w = weak()](const std::string& peer, bool connected) {
4406 2200 : asio::post(*Manager::instance().ioContext(), [w, peer, connected] {
4407 2200 : if (auto acc = w.lock())
4408 2200 : acc->onPeerConnected(peer, connected);
4409 2200 : });
4410 2777 : });
4411 578 : channelHandlers_[Uri::Scheme::AUTH] = std::make_unique<AuthChannelHandler>(shared(), *connectionManager_.get());
4412 :
4413 : #if TARGET_OS_IOS
4414 : connectionManager_->oniOSConnected([&](const std::string& connType, dht::InfoHash peer_h) {
4415 : if ((connType == "videoCall" || connType == "audioCall") && jami::Manager::instance().isIOSExtension) {
4416 : bool hasVideo = connType == "videoCall";
4417 : emitSignal<libjami::ConversationSignal::CallConnectionRequest>("", peer_h.toString(), hasVideo);
4418 : return true;
4419 : }
4420 : return false;
4421 : });
4422 : #endif
4423 578 : }
4424 672 : }
4425 :
4426 : void
4427 911 : JamiAccount::updateUpnpController()
4428 : {
4429 911 : Account::updateUpnpController();
4430 911 : if (connectionManager_) {
4431 47 : auto config = connectionManager_->getConfig();
4432 47 : if (config)
4433 47 : config->upnpCtrl = upnpCtrl_;
4434 47 : }
4435 911 : }
4436 :
4437 : } // namespace jami
|