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